'Automatically scroll to a grid cell under cursor during zoom in/out

I have a grid of cells which can be zoom in/out via mouse wheel by changing cells size. It works ok, except it doesn't "follow" grid position on the screen when zooming in/out.

Here is an example snippet, if you position cursor on cell 13 and start zoom w/mouse wheel, the cell moves away from the cursor.

let cols = 5,
    rows = 5,
    zoom = 12,
    zoomMin = 1,
    zoomMax = 25,
    mouseDown,
    clientX,
    clientY;

for(let i = 0, cell = document.createElement("div"); i < cols*rows; i++)
{
  cell.textContent = i + 1;
  table.appendChild(cell);
  cell = cell.cloneNode(false);
}

document.body.style.setProperty("--cols", cols);
document.body.style.setProperty("--rows", rows);
setZoom(zoom);


table.addEventListener("wheel", e =>
{
  e.preventDefault();
  e.stopPropagation();
  zoom = Math.min(zoomMax, Math.max(zoomMin, zoom + (e.deltaY < 0 ? 1 : -1)));
  setZoom(zoom);
}, {passive:false});

table.addEventListener("mousedown", e =>
{
  e.preventDefault();
  table.classList.add("drag");
  mouseDown = e;
  ({clientX, clientY} = e);
  window.addEventListener("mouseup", onMouseUp);
  window.addEventListener("mousemove", onMouseMove);
});

function onMouseMove (e)
{
  if (e.clientX == clientX && e.clientY == clientY)
    return;

  table.scrollBy(clientX - e.clientX, clientY - e.clientY);
  ({clientX, clientY} = e);
}

function onMouseUp(e)
{
  table.classList.remove("drag");
  window.removeEventListener("mouseup", onMouseUp);
  window.removeEventListener("mousemove", onMouseMove);
}

function setZoom(z)
{
  document.body.style.setProperty("--zoom", ((z/10*z/10) + 1) +"em");
}
#table
{
  display: inline-grid;
  grid-template-columns: repeat(var(--cols), 1fr);
  text-align: center;
  max-width: calc(2em * var(--cols) + 0.4em);
  max-height: calc(2em * var(--rows) + 0.4em);
  overflow: auto;
  padding: 1px 0 0 1px;
  cursor: grab;
}

#table.drag
{
  cursor: grabbing;
}

#table > div
{
  border: 1px solid black;
  margin-left: -1px;
  margin-top: -1px;
  width: var(--zoom);
  height: var(--zoom);
  line-height: var(--zoom);
  padding: 0.5em;
}
<div id="table"></div>

My current thought of steps needed to follow cell under cursor during zoom:

  1. get cell under cursor
  2. get rectangle of the cell
  3. get position of the cell relative to top-left corner of the table
  4. get position of the cursor relative to top-left corner of the cell (in percent)
  5. get position of the cursor relative to top-left corner of the table (in percent)
  6. change zoom
  7. get new position of the cell relative to top-left corner of the table
  8. get new position of the cursor relative to top-left corner of the table
  9. based on percent from step 5, get new cursor position relative to top-left corner of the table
  10. based on percent from step 4, get new top-left corner of the cell relative to the cursor
  11. calculate needed scroll position of the cell based on step 3
  12. scroll table to result of step 11

This is quite complicated, and I hope there is a better/simpler formula for this.

Any tips are appreciated



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source