const DELTA = 6;
const CONTAINER_CLASS = 'drag-to-scroll__container';
const sliders = document.querySelectorAll('[data-drag-to-scroll]');
let isDown = false;
let isMoving = false;
let relativeStartX = 0;
let relativeStartY = 0;
let startX = 0;
let startY = 0;
let scrollLeft = 0;
let scrollTop = 0;
let draggable = false;
let cursor = undefined;
function storeTarget(e) {
draggable = e.target.draggable;
}
function setTarget(e) {
if (e.target?.draggable) {
e.target.draggable = false;
}
}
function resetTarget(e) {
e.target.draggable = draggable;
}
function resetVariables() {
isDown = false;
isMoving = false;
relativeStartX = 0;
relativeStartY = 0;
startX = 0;
startY = 0;
scrollLeft = 0;
scrollTop = 0;
draggable = false;
cursor = undefined;
}
function checkMovement(e) {
const diffX = Math.abs(e.pageX - startX);
const diffY = Math.abs(e.pageY - startY);
return diffX > DELTA || diffY > DELTA;
}
sliders.forEach((element) => {
const orientation = element.dataset.dragToScroll;
function mouseDownHandler(e) {
e.stopImmediatePropagation();
storeTarget(e);
setTarget(e);
isDown = true;
element.classList.add(`${CONTAINER_CLASS}--active`);
relativeStartX = e.pageX - element.offsetLeft;
relativeStartY = e.pageY - element.offsetTop;
startX = e.pageX;
startY = e.pageY;
scrollLeft = element.scrollLeft;
scrollTop = element.scrollTop;
element.addEventListener('mousemove', mouseMoveHandler);
element.addEventListener('mouseup', mouseOffHandler);
element.addEventListener('mouseleave', mouseOffHandler);
}
function clickInterceptor(e) {
e.preventDefault();
e.stopImmediatePropagation();
}
function mouseMoveHandler(e) {
if (!isDown) return;
if (!isMoving && checkMovement(e)) {
isMoving = true;
element.addEventListener('click', clickInterceptor, { capture: true });
cursor = element.style.cursor.slice();
element.style.cursor = 'grabbing';
element.style.userSelect = 'none';
}
const x = e.pageX - element.offsetLeft;
const shiftX = x - relativeStartX;
const y = e.pageY - element.offsetTop;
const shiftY = y - relativeStartY;
if (orientation === 'horizontal') {
element.scrollLeft = scrollLeft - shiftX;
} else {
element.scrollTop = scrollTop - shiftY;
}
}
function mouseOffHandler(e) {
if (isMoving) {
setTimeout(() => {
element.removeEventListener('click', clickInterceptor, {
capture: true,
});
resetTarget(e);
}, 10);
element.style.cursor = cursor.slice();
} else {
element.removeEventListener('click', clickInterceptor, { capture: true });
resetTarget(e);
}
element.classList.remove(`${CONTAINER_CLASS}--active`);
resetVariables();
element.style.removeProperty('user-select');
element.removeEventListener('mousemove', mouseMoveHandler);
element.removeEventListener('mouseup', mouseOffHandler);
element.removeEventListener('mouseleave', mouseOffHandler);
}
element.addEventListener('mousedown', mouseDownHandler);
});
Here's the playable FiddleJS.