Box bounds

Prevent child box from scrolling past edges of parent

by Leolloyd Andrade

HTML

<div class="parent">
  <div class="grid">
  </div>
</div>

SCSS

.parent {
  width: 300px;
  height: 300px;
  position: relative;
  border: 1px solid red;
  background: yellow;
  overflow: hidden;
}

.grid {
  width: 500px;
  height: 500px;
  position: absolute;
  //border: 1px solid blue;
  background: #ddd;
}

JavaScript

const qs = (selector, parent = document) => parent.querySelector(selector);

const gridEl = qs('.grid');
const parentEl = qs('.parent');

const position = {
  mouseDownX: 0,
  mouseDownY: 0,
  mouseMoveX: 0,
  mouseMoveY: 0,
  startX: 0, // start position. translate is applied relative to this.
  startY: 0,
};
let isPanning = false;
let ticking = false;

gridEl.addEventListener('mousedown', (e) => {
  e.stopPropagation();

  position.mouseDownX = e.clientX;
  position.mouseDownY = e.clientY;

  const bounds = gridEl.getBoundingClientRect();
  const parentBounds = parentEl.getBoundingClientRect();

  position.startX = bounds.left - parentBounds.left;
  position.startY = bounds.top - parentBounds.top;
  
  isPanning = true;
});

gridEl.addEventListener('mousemove', (e) => {
  e.stopPropagation();

  if (!isPanning) return;

  position.mouseMoveX = e.clientX;
  position.mouseMoveY = e.clientY;

  // rAF code adapted from https://css-tricks.com/debouncing-throttling-explained-examples/
  if (!ticking) window.requestAnimationFrame(render);

  ticking = true;
});

gridEl.addEventListener('mouseup', (e) => {
  e.stopPropagation();
  isPanning = false;
  ticking = false;
});

const calcDistance = (oldPos, newPos) => {
  const xDiff = newPos.x - oldPos.x;
  const yDiff = newPos.y - oldPos.y;
  return {
    xDiff,
    yDiff
  };
};

const render = () => {
  ticking = false;

  const pos = position;
  const oldPos = {
    x: pos.mouseDownX,
    y: pos.mouseDownY
  };
  const newPos = {
    x: pos.mouseMoveX,
    y: pos.mouseMoveY
  };
  let {
    xDiff,
    yDiff
  } = calcDistance(oldPos, newPos);
  xDiff += pos.startX;
  yDiff += pos.startY;

  gridEl.style.transform = `translate(${xDiff}px, ${yDiff}px)`;
};