Daniel Kobe
Daniel Kobe

Reputation: 9825

Drag and move image using plain JavaScript doesn't work well

I've gone ahead and implemented a drag feature for my image that should move the image in whichever direction you drag the image. I;m not sure whats wrong with it, if its slow and needs to be throttled, if my maths off, or I'm just plain doing something wrong. I made a minimal viable example. Any help would be really appreciated.

Here's a codepen.

HTML

<div class="img-cntnr">
  <img class="my-img" src="http://www.fillmurray.com/g/900/900" alt="" />
</div>

CSS

.img-cntnr {
  position: relative;
  border: dodgerBlue solid 20px;
  width: 500px;
  height: 500px;
  overflow: hidden;
}

.img-cntnr img {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  top: 0;
  bottom: 0;
  margin: auto;
  cursor: -webkit-grab;
}

JS

  var images = document.getElementsByClassName('my-img');
  var imgCntnrs = document.getElementsByClassName('img-cntnr');
  var dragImgMouseStart = {};
  var imgCntnrH;
  var imgCntnrW;

  function mousedownDragImg(e) {
    dragImgMouseStart.x = e.pageX;
    dragImgMouseStart.y = e.pageY;
    imgCntnrH = imgCntnrs[0].offsetHeight;
    imgCntnrW = imgCntnrs[0].offsetWidth;

    // add listeners for mousemove, mouseup
    window.addEventListener('mousemove', mousemoveDragImg);
    window.addEventListener('mouseup', mouseupDragImg);
  }

  function mousemoveDragImg(e) {
    var diffX = -1 * (dragImgMouseStart.x - e.pageX);
    var diffY = -1 * (dragImgMouseStart.y - e.pageY);
    var oldLeft = isNaN(parseFloat(images[0].style.left)) ? 0 : parseFloat(images[0].style.left);
    var newLeft = oldLeft + diffX / imgCntnrW;
    images[0].style.left = newLeft + '%';
    var oldTop = isNaN(parseFloat(images[0].style.top)) ? 0 : parseFloat(images[0].style.top);
    var newTop = oldTop + diffY / imgCntnrH;
    images[0].style.top = newTop + '%';
    console.log(newLeft, newTop);
  }

  function mouseupDragImg(e) {
    window.removeEventListener('mousemove', mousemoveDragImg);
    window.removeEventListener('mouseup', mouseupDragImg);
  }

  for (var i = images.length - 1; i >= 0; i--) {
    // make images unselectable
    images[i].ondragstart = function() {
      return false;
    };
  };

  images[0].addEventListener('mousedown', mousedownDragImg);

Upvotes: 1

Views: 315

Answers (1)

Redu
Redu

Reputation: 26161

There were a few rectifications needed in your code.

  • First of all all DOM access must be done through requestAnimationFrame(). Then i had to refactor the code for a better performance. Moving DOM objects are best done by CSS3 transform: translate(x,y);
  • Then I had to refactor the code further. I modified the semantics a little bit. In order to move the image element we will "only" alter the transform: translate(x,y); CSS property as we move the mouse. So we only need this CSS property's initial values. Since you have chosen to center the over-sized image horizontally by doing left: 50%; (first move the image to the right up until it's left edge is at the containing div's half width) then transform: translateX(-50%); (move the image to the left as much as haft of it's width) we can easily see that our translateX value is half of the image width (-50%). The translateY value has never been modified so it remains as 0. I have changed this property into transform: translate(-50%, 0); for consistency. Well, now lets calculate our initial x value for the translate(x,y). The image.getBoundingClientRect() tool is very useful to get the width of the image element in integers.

So please see the below snippets for the rest. It's pretty self explanatory.

Also at JSBin and CodePen

https://jsbin.com/bawalebowi/1/edit?css,js,output

http://codepen.io/omerillo/pen/EKWOZQ

  var         image = document.getElementsByClassName('my-img')[0],
          imgCntnrs = document.getElementsByClassName('img-cntnr'),
  dragImgMouseStart = {},
           lastDiff = {x:0, y:0},
         initialPos = image.getBoundingClientRect(),
         currentPos = {x: -initialPos.width/2, y: 0};

  function mousedownDragImg(e) {
    e.preventDefault();
    dragImgMouseStart.x = e.clientX;
    dragImgMouseStart.y = e.clientY;
          currentPos.x += lastDiff.x;
          currentPos.y += lastDiff.y;
               lastDiff = {x: 0, y: 0};
    window.addEventListener('mousemove', mousemoveDragImg);
    window.addEventListener('mouseup', mouseupDragImg);
    requestAnimationFrame(function(){
                            image.style.transform = "translate(" + (currentPos.x + lastDiff.x) + "px," + (currentPos.y + lastDiff.y) + "px)";
                          });
  }

  function mousemoveDragImg(e) {
    e.preventDefault();
    lastDiff.x = e.clientX - dragImgMouseStart.x;
    lastDiff.y = e.clientY - dragImgMouseStart.y;
    requestAnimationFrame(function(){
                            image.style.transform = "translate(" + (currentPos.x + lastDiff.x) + "px," + (currentPos.y + lastDiff.y) + "px)";
                          });
  }

  function mouseupDragImg(e) {
    e.preventDefault();
    window.removeEventListener('mousemove', mousemoveDragImg);
    window.removeEventListener('mouseup', mouseupDragImg);
  }

  image.addEventListener('mousedown', mousedownDragImg);
.img-cntnr {
  position: relative;
  border: dodgerBlue solid 10px;
  border-radius: 10px;
  width: 250px;
  height: 250px;
  overflow: hidden;
  margin: 0;
  padding:0;
}

.img-cntnr img {
  position: absolute;
  left: 50%;
  transform: translate(-50%, 0);
  top: 0px;
  bottom:0px;
  margin: auto;
  padding: 0;
  cursor: -webkit-grab;
}
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
<div class="img-cntnr">
  <img class="my-img" src="http://www.fillmurray.com/g/900/900" alt="" />
</div>
</body>
</html>

Upvotes: 1

Related Questions