The below code works great, there is only one problem. If the user is panning at a boundary, i.e. the user dragged the image all the way to the bottom and they swipe in a diagonally down/left of down/right direction, the image fails to pan on the x-axis. It’s sticking.
I have no idea how to solve this, I’ve tried everything and I’ve never been so stuck.
To test this just swipe in a large circular motion and it becomes clear how the image sticks.
const image = {
startX: undefined,
startY: undefined,
currentX: undefined,
currentY: undefined,
scrollX: undefined,
scrollY: undefined,
currentOffsetX: undefined,
currentOffsetY: undefined,
maxScrollX: undefined,
maxScrollY: undefined
}
const imageContainer = document.querySelector('.image-container')
const zoomImageWrapper = document.querySelector('.zoom-image-wrapper')
let isAlreadySwiped = false
const { x, y } = computeInitialOffset()
document.querySelector('.image-container').style.transform = `translate3d(-${x}px, -${y}px, 0)`
image.maxScrollX = Math.abs(imageContainer.offsetWidth - zoomImageWrapper.offsetWidth)
image.maxScrollY = Math.abs(imageContainer.offsetHeight - zoomImageWrapper.offsetHeight)
function computeInitialOffset() {
return {
x: (imageContainer.offsetWidth - zoomImageWrapper.offsetWidth) / 2,
y: (imageContainer.offsetHeight - zoomImageWrapper.offsetHeight) / 2
}
}
function getImageOffsets(img) {
return {
imageOffsetX: Math.abs((img.getBoundingClientRect().left)),
imageOffsetY: Math.abs((img.getBoundingClientRect().top))
}
}
// zoom out on double tap
function resetOffset(img) {
const { x, y } = computeInitialOffset()
image.currentOffsetX = x
image.currentOffsetY = y
}
function handleDragMove(e) {
image.currentX = e.touches[0].pageX
image.currentY = e.touches[0].pageY
const swipingLeft = image.startX > image.currentX
const swipingUp = image.startY > image.currentY
if (isAlreadySwiped) {
image.scrollX = Math.min(
(image.currentOffsetX - (e.touches[0].pageX - image.startX)),
image.maxScrollX
)
image.scrollY = Math.min(
(image.currentOffsetY - (e.touches[0].pageY - image.startY)),
image.maxScrollY
)
}
else {
const { x, y } = computeInitialOffset()
image.scrollX = Math.min(
(Math.abs(x) - (e.touches[0].pageX - image.startX)),
image.maxScrollX
)
image.scrollY = Math.min(
(Math.abs(y) - (e.touches[0].pageY - image.startY)),
image.maxScrollY
)
}
imageContainer.style.transform = `translate3d(-${image.scrollX}px, -${image.scrollY}px, 0)`
}
function handleDragStart(e) {
image.startX = e.touches[0].pageX
image.startY = e.touches[0].pageY
}
function handleDragEnd(e) {
image.currentOffsetX = getImageOffsets(imageContainer).imageOffsetX
image.currentOffsetY = getImageOffsets(imageContainer).imageOffsetY
isAlreadySwiped = true
}
imageContainer.addEventListener('touchstart', handleDragStart)
imageContainer.addEventListener('touchmove', handleDragMove)
imageContainer.addEventListener('touchend', handleDragEnd)
<!DOCTYPE html>
<html lang="en">
<head>
<title>Home</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<style>
* {
box-sizing: border-box;
}
.modal {
height: 100%;
width: 100%;
z-index: 100000;
position: fixed;
display: flex;
top: 0;
left: 0;
justify-content: center;
}
.modal__wrapper {
display: flex;
position: relative;
background: #fff;
flex-direction: column;
height: 100%;
width: 100%;
align-items: center;
}
.modal__content {
display: flex;
flex-direction: column;
overflow-x: hidden;
overflow-y: scroll;
padding: 0;
margin: 0;
height: 100%;
white-space: nowrap;
}
.zoom-container {
position: relative;
height: 100%;
}
.zoom-container-inner {
position: absolute;
z-index: 9;
opacity: 1;
top: 0;
left: 0;
height: 100%;
width: 100%;
}
.image-container {
position: relative;
overflow: hidden;
}
.controls {
display: flex;
background: #fff;
border: #ccc;
width: 100%;
flex: 0 0 auto;
margin-top: auto;
max-height: 73px;
height: 73px;
}
.controls__inner {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding: 24px;
}
</style>
</head>
<body>
<div class="modal">
<div class="modal__wrapper">
<div class="modal__content">
<div class="zoom-container" style="margin: 0 auto; width: 375px;">
<div class="zoom-container-inner">
<div style="height: 100%; width: 100%;">
<div style="touch-action: none; height: 100%; width: 100%; overflow: hidden;" class="zoom-image-wrapper">
<div class="image-container"
style="overflow: hidden; will-change: transform; user-select: none; transform-origin: 0px 0px; transform: translate3d(0, 0, 0); padding-top: 144%; width: 610px; height: 880px;"
>
<div style="position: absolute; top: 0; left: 0; height: 100%; width: 100%;">
<img src="https://img01.ztat.net/article/spp-media-p1/3a301a3d8a274a18821af76f9a21bfe4/137fd73e910c45db8be381e6d8c72fee.jpg?imwidth=1800&filter=packshot" style="max-width: 100%; position: relative; text-align: center; width: 100%; height: auto; display: block;">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div style="max-height: 73px;" class="controls">
<div class="controls__inner">
<div>1 of 5</div>
<div>x</div>
</div>
</div>
</div>
</div>
<script src="./index.js" defer></script>
</body>
</html>