logo
post image

How to Drag / Pan an Image in a Container div using Javascript

There are situations sometimes when it is required that an image should be draggable within the container div. Something like the demo below is required :

Demo

Try dragging the image

How It Works

  • Dragging can be achieved by combining mousedown, mousemove and mouseup events.

  • While dragging we are changing the top and left properties of the image. For this we need the image to be relative/absolute positioned.

  • When the top and left properties of the image are changed, the part of the image seen in the container will change as well. For example below, the first image has top and left property set to 0 and 0. The second image has top and left set to -50px and -50px. The third one has top and left as -100px and -100px.

    When the image is dragged we can change the top and left properties depending on the amount of the drag. For example if we drag the image 50px upwards and 50px to the left, we should reduce the top value by 50px and left value by 50px to see the new part of the image.

Implementing the Drag Effect

Dragging is a combination of 3 mouse events. When the user starts dragging he presses the mouse down (mousedown event). Then he moves the mouse (mousemove events). To stop dragging he releases the mouse (mouseup event).

We can implement the dragging by :

  • Firstly check whether the image has been loaded or not. Otherwise on the first page load, Javascript may return invalid height and width of the image.

    There can also can be a chance that the image is already loaded and available in the browser cache.

    // image element
    const imageElement = document.querySelector(".drag-image")
    
    // image width
    let imageWidth
    
    // image height
    let imageHeight
    
    // boolean whether image loaded or not
    let imageLoaded = false
    
    // check whether image is cached or wait for the image to load 
    // this is necessary before calculating width and height of the image
    if(imageElement.complete) {
    	imageFullyLoaded()
    } else {
    	imageElement.addEventListener('load', function() {
    		imageFullyLoaded()
    	})
    }
    
    // image is loaded
    function imageFullyLoaded() {
    	imageWidth = imageElement.naturalWidth
    	imageHeight = imageElement.naturalHeight
    	imageLoaded = true
    }
    
  • When the user performs a mousedown event we set a global variable — for example we set draggingInProgress = true

    This indicates start of dragging.

    let draggingInProgress
    
    document.querySelector(".image-container").addEventListener('mousedown', (event) => {
    	if(imageLoaded) { 
    		draggingInProgress = true
    	}
    })
    
  • When the mouseup event occurs we set draggingInProgress = false

    This indicates end of dragging.

    document.querySelector(".image-container").addEventListener('mouseup', (event) => {
    	draggingInProgress = false
    })
    
  • Upon a mousemove event we check whether draggingInProgress === true. This is important because the user may move the mouse without keeping the mouse pressed. Dragging occurs only when the mouse is moved keeping the mouse pressed.

    This event indicates that the image is being dragged.

    document.querySelector(".image-container").addEventListener('mousemove', (event) => {
    	if(draggingInProgress) {
    		// code to drag the image
    	}
    })
    

    On each occurring mousemove event we find the position of the mouse within the image container (through the offsetX and offsetY properties of the event object).

    document.querySelector(".image-container").addEventListener('mousemove', (event) => {
    	// rest of the codes
    		
    	const currentMousePosition = { x: event.offsetX, y: event.offsetY }
    	
    	// rest of the codes
    })
    

    This mouse position is compared with the previous mouse position. This comparison will give the amount by which the mouse has been moved. For example the previous mouse position was (100, 100) and current mouse position is (50, 50), this means that the mouse has been moved 50px upwards and 50px to the left.

    We then subtract the current top and left properties of the image by the amount of mouse drag. For example if top and left are 0 & 0, and mouse movement is 50px up and 50px left, the new top and left values will be -50px and -50px. Similarly if top and left are -25px & 30px, the new top and left values will be -75px and -20px.

Complete Codes

<div class="image-container">
	<img class="drag-image" src="img.jpg" />
</div>
#image-container {
	display: block;
	height: 300px;
	width: 300px;
	margin: 40px auto;
	overflow: hidden;
	border: 1px solid #cccccc;
	box-sizing: border-box;
	position: relative;
	cursor: move;
}

#drag-image {
	left: 0;
	pointer-events: none;
	position: relative;
	top: 0;
	-moz-user-select: none;
}
const containerWidth = document.querySelector('.image-container').offsetWidth
const containerHeight = document.querySelector('.image-container').offsetHeight
const imageElement = document.querySelector(".drag-image")
let draggingInProgress
let lastMousemovePosition = { x: null, y: null }
let imageWidth
let imageHeight
let imageLoaded = false

// check whether image is cached or wait for the image to load 
// this is necessary before calculating width and height of the image
if(imageElement.complete) {
	imageFullyLoaded()
} else {
	imageElement.addEventListener('load', function() {
		imageFullyLoaded()
	})
}

// image is loaded
function imageFullyLoaded() {
	imageWidth = imageElement.naturalWidth
	imageHeight = imageElement.naturalHeight
	imageLoaded = true
}

document.querySelector(".image-container").addEventListener('mousedown', (event) => {
	if(imageLoaded) { 
		draggingInProgress = true
		lastMousemovePosition = { x: event.offsetX, y: event.offsetY }
	}
})

document.querySelector(".image-container").addEventListener('mouseup', (event) => {
	draggingInProgress = false
})

document.querySelector(".image-container").addEventListener('mousemove', (event) => {
	if(draggingInProgress) {
		const currentMousePosition = { x: event.offsetX, y: event.offsetY }
		const changeX = currentMousePosition.x - lastMousemovePosition.x
		const changeY = currentMousePosition.y - lastMousemovePosition.y

		// save mouse position
		lastMousemovePosition = currentMousePosition

		const imgTop = parseInt(window.getComputedStyle(imageElement).getPropertyValue('top'), 10)
		const imgLeft = parseInt(window.getComputedStyle(imageElement).getPropertyValue('left'), 10)

		const imgTopNew = imgTop + changeY
		const imgLeftNew = imgLeft + changeX

		// validate top and left do not fall outside the image, otherwise white space will be seen
		if(imgTopNew > 0)
			imgTopNew = 0
		if(imgTopNew < (containerHeight - imageHeight))
			imgTopNew = containerHeight - imageHeight

		if(imgLeftNew > 0)
			imgLeftNew = 0
		if(imgLeftNew < (containerWidth - imageWidth))
			imgLeftNew = containerWidth - imageWidth

		imageElement.style.top = imgTopNew + 'px'
		imageElement.style.left = imgLeftNew + 'px'
	}
})

Alternative Method

We can use the transform CSS property of the image to achieve the same thing. This will be better performance-wise and also the dragging effect can be animated.

Implementing in Mobiles

The same thing can be easily achieved in mobiles by using pointer events, insted of using mouse events. Codes will remain ~ same. Consult Advantages of Using Pointer Events Over Mouse & Touch Events to know more.