Panning Image When Overflow: scroll using Javascript

Viewed 1365

So what I have is a bigger image wrapped inside a smaller div. So i have scrollbar to see the hidden image parts. But what I want is the image to pan through mouse drag rather than the scroll bars , basically I want Panning to implement on my image.

I have used Panzoom JS but its other functionalities are ruining my code. So is there any javascript hack that I can use to Pan image

body,html
{
    width: 100%;
    height: 100%;
}

.mainContent
     {
        width: 400px;
        height: 400px;
        overflow: scroll;
        display: flex;
        justify-content: center;
        align-items: center;
     }
    <div class="mainContent">
        <img src="https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885_960_720.jpg">
    </div>
 

1 Answers

Two examples,
one using a CSS flexbox centered canvas in viewport that uses CSS transform: translate() (No scrollbars out of the box), and the other using scrollTo() and scrollLeft/Top (Can have browser scrollbars).

Centered canvas inside viewport area. CSS transform

The formula is quite simple, the transform is relative to the canvas center, therefore you simply need to translate it using:

offsetX = currentPointerX - oldPointerX - oldOffsetX

Here's a basic example without the constraints calculation:

const Pannable = (EL) => {

  const EL_canvas = EL.firstElementChild;
  const initial = {x: 0, y: 0};
  const offset = {x: 0, y: 0}; // The transform offset (from center)
  let isPan = false;

  const getXY = ({clientX, clientY}) => {
    const {left, top} = EL.getBoundingClientRect();
    return {x: clientX - left, y: clientY - top}
  };

  const panStart = (ev) => {
    ev.preventDefault();
    isPan = true;
    const {x, y} = getXY(ev);
    initial.x = x - offset.x;
    initial.y = y - offset.y;
  };

  const panMove = (ev) => {
    if (!isPan) return; // Do nothing
    const {x, y} = getXY(ev);
    offset.x = x - initial.x;
    offset.y = y - initial.y;
    EL_canvas.style.transform = `translate(${offset.x}px, ${offset.y}px)`;
  };

  const panEnd = (ev) => {
    isPan = false;
  };

  EL.addEventListener("mousedown", panStart);
  document.addEventListener("mousemove", panMove);
  document.addEventListener("mouseup", panEnd);
};

document.querySelectorAll(".viewport").forEach(Pannable);
.viewport {
  margin: 50px auto;
  width: 300px;
  height: 150px;
  overflow: hidden;
  display: flex;
  justify-content: center;
  align-items: center;
  outline: 1px solid #000;
}

.viewport>* {
  max-width: 500px;
}
<div class="viewport">
  <img src="https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885_960_720.jpg">
</div>

Using scrollTo() and scrollLeft/Top

This one is not that good since the inner canvas transforms are as browser default: top left corner. (It might be necessary to trigger an automatic scroll in order to center the canvas inside the viewport on init)

The formula:

scrollLeft = oldScrollLeft + oldPointerX - currentpointerX
  • On pan start ("mousedown") - store the start X,Y positions (account also for the current scrollLeft,scrollTop values)
  • On pan ("mousemove") - use Element.scrollTo(x, y) where x and y are the initial position minus the current position

const Pannable = (EL) => {

  const initial = {x: 0, y: 0};
  let isPan = false;
  
  const getXY = ({clientX, clientY}) => {
    const bcr = EL.getBoundingClientRect();
    return {
      x: clientX - bcr.left,
      y: clientY - bcr.top,
    }
  }; 

  const panStart = (ev) => {
    ev.preventDefault();
    isPan = true;
    const {x, y} = getXY(ev);
    initial.x = EL.scrollLeft + x;
    initial.y = EL.scrollTop + y;
  };

  const panMove = (ev) => {
    if (!isPan) return; // Do nothing
    const {x, y} = getXY(ev);
    EL.scrollTo(initial.x - x,  initial.y - y);
  };

  const panEnd = (ev) => {
    isPan = false;
  };

  EL.addEventListener("mousedown", panStart);
  document.addEventListener("mousemove", panMove);
  document.addEventListener("mouseup", panEnd);
};


document.querySelectorAll(".viewport").forEach(Pannable);
.viewport {
  margin: 50px auto;
  width: 300px;
  height: 150px;
  overflow: auto; /* Set to hidden to remove scrollbars */
}

.viewport > *{
  display: block;
  max-width: 500px;
}
<div class="viewport">
  <img src="https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885_960_720.jpg">
</div>

Related