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 to implement Panning 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>
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).
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>
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
Element.scrollTo(x, y)
where x and y are the initial position minus the current positionconst 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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With