Issue
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>
Solution
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
Answered By - Roko C. Buljan
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.