Skip to content

Instantly share code, notes, and snippets.

@peplocanto
Last active October 3, 2022 10:14
Show Gist options
  • Save peplocanto/22c2c8500ed4e1fa766b3cc58735677e to your computer and use it in GitHub Desktop.
Save peplocanto/22c2c8500ed4e1fa766b3cc58735677e to your computer and use it in GitHub Desktop.

Click & Drag Scroll in Vanilla JS

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment