4

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>
 
Roko C. Buljan
  • 196,159
  • 39
  • 305
  • 313
Chempooro
  • 415
  • 1
  • 7
  • 24

1 Answers1

11

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 translate

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 = (elViewport) => {

  const elCanvas = elViewport.firstElementChild;
  const start = {x: 0, y: 0};
  const offset = {x: 0, y: 0}; // The transform offset (from center)
  let isPan = false;

  const panStart = (ev) => {
    ev.preventDefault();
    isPan = true;
    start.x = ev.clientX - offset.x;
    start.y = ev.clientY - offset.y;
  };

  const panMove = (ev) => {
    if (!isPan) return; // Do nothing
    offset.x = ev.clientX - start.x;
    offset.y = ev.clientY - start.y;
    elCanvas.style.translate = `${offset.x}px ${offset.y}px`;
  };

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

  elViewport.addEventListener("pointerdown", panStart);
  addEventListener("pointermove", panMove);
  addEventListener("pointerup", 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;
  touch-action: none; /* Prevent pointermove default panning */
}

.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>

Or even simpler, by using Event.movementX,Y - because we don't have to remember and store the initial coordinates and manually calculate the difference:

const Pannable = (elViewport) => {

  const elCanvas = elViewport.firstElementChild;
  const offset = {x: 0, y: 0};
  let isPan = false;

  const panStart = (ev) => {
    ev.preventDefault();
    isPan = true;
  };

  const panMove = (ev) => {
    if (!isPan) return;
    offset.x += ev.movementX;
    offset.y += ev.movementY;
    elCanvas.style.translate = `${offset.x}px ${offset.y}px`;
  };

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

  elViewport.addEventListener("pointerdown", panStart);
  addEventListener("pointermove", panMove);
  addEventListener("pointerup", panEnd);
};

document.querySelectorAll(".viewport").forEach(Pannable);
.viewport {
  display: flex;
  justify-content: center;
  align-items: center;
  margin: 30px auto;
  width: 300px;
  height: 150px;
  overflow: hidden;
  outline: 1px solid #000;
  touch-action: none; /* Prevent pointermove default panning */
}

.viewport>* {
  transform-origin: 0 0;
  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 = (elViewport) => {

  const start = {x: 0, y: 0};
  let isPan = false;

  const panStart = (ev) => {
    ev.preventDefault();
    isPan = true;
    start.x = elViewport.scrollLeft + ev.clientX;
    start.y = elViewport.scrollTop + ev.clientY;
  };

  const panMove = (ev) => {
    if (!isPan) return;
    elViewport.scrollTo(
      start.x - ev.clientX,
      start.y - ev.clientY
    );
  };

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

  elViewport.addEventListener("pointerdown", panStart);
  addEventListener("pointermove", panMove);
  addEventListener("pointerup", panEnd);
};


document.querySelectorAll(".viewport").forEach(Pannable);
.viewport {
  margin: 50px auto;
  width: 300px;
  height: 150px;
  overflow: auto; /* Set to hidden to remove scrollbars */
  touch-action: none; /* Prevent pointermove default panning */
}

.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>

or also, by using Event.movementX,Y

const Pannable = (elViewport) => {

  let isPan = false;

  const panStart = (ev) => {
    ev.preventDefault();
    isPan = true;
  };

  const panMove = (ev) => {
    if (!isPan) return;
    elViewport.scrollTo(
      elViewport.scrollLeft - ev.movementX,
      elViewport.scrollTop - ev.movementY
    );
  };

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

  elViewport.addEventListener("pointerdown", panStart);
  addEventListener("pointermove", panMove);
  addEventListener("pointerup", panEnd);
};


document.querySelectorAll(".viewport").forEach(Pannable);
.viewport {
  margin: 50px auto;
  width: 300px;
  height: 150px;
  overflow: auto; /* Set to hidden to remove scrollbars */
  touch-action: none; /* Prevent pointermove default panning */
}

.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>

To center-scroll the scrollable element see: Auto center horizontal scrollbar

Roko C. Buljan
  • 196,159
  • 39
  • 305
  • 313