Better grid resizing behavior.

This commit is contained in:
Yaro Kasear 2025-12-12 12:10:14 -06:00
parent 3f4aee73a3
commit 0b85715c1e

View file

@ -2,24 +2,21 @@
{% set dot_size = [grid_size * 1.25, 32]|max|int %}
{% block style %}
:root {
--grid: {{ grid_size }}px;
#gridWrap {
width: 100%;
height: 80vh;
position: relative;
}
#grid {
position: relative;
cursor: crosshair;
height: 80vh;
width: 100%;
height: 100%;
touch-action: none;
}
@supports (height: calc(round(nearest, 80vh, {{ grid_size }}px))) {
#grid {
height: calc(round(nearest, 80vh, var(--grid)) + 1px);
width: calc(round(nearest, 100%, var(--grid)) + 1px);
}
}
#toolBar {
top: 10px;
transform: translateX(-50%);
@ -46,6 +43,7 @@
{% block main %}
<div class="container">
<div id="gridWrap">
<div id="grid" class="position-relative overflow-hidden">
<div id="toolBar"
class="btn-toolbar bg-light position-absolute border border-secondary-subtle rounded start-50 p-1 align-items-center flex-nowrap overflow-auto">
@ -184,6 +182,7 @@
class="border border-black position-absolute d-none bg-warning-subtle px-1 py-0 user-select-none"></div>
<canvas id="overlay" class="position-absolute w-100 h-100"></canvas>
</div>
</div>
</div>
{% endblock %}
@ -199,6 +198,7 @@ const gridEl = document.getElementById('grid');
const importButtonEl = document.getElementById('importButton');
const importEl = document.getElementById('import');
const gridSizeEl = document.getElementById('gridSize');
const gridWrapEl = document.getElementById('gridWrap');
let gridSize = Number(gridSizeEl.value) || {{ grid_size }};
let dotSize = Math.floor(Math.max(gridSize * 1.25, 32));
@ -210,8 +210,11 @@ let selectedColor;
let currentShape = null;
let shapes = loadShapes();
const ro = new ResizeObserver(() => resizeAndSetupCanvas());
ro.observe(gridEl);
let sizingRAF = 0;
let lastApplied = { w: 0, h: 0 };
const ro = new ResizeObserver(scheduleSnappedGridSize);
ro.observe(gridWrapEl);
const savedTool = localStorage.getItem('gridTool');
if (savedTool) {
@ -228,12 +231,43 @@ window.addEventListener('resize', resizeAndSetupCanvas);
setGrid();
function snapDown(n, step) {
return Math.floor(n / step) * step;
}
function applySnappedGridSize() {
sizingRAF = 0;
const grid = gridSize;
if (!Number.isFinite(grid) || grid < 1) return;
const w = gridWrapEl.clientWidth;
const h = gridWrapEl.clientHeight;
const snappedW = snapDown(w, grid);
const snappedH = snapDown(h, grid);
if (snappedW === lastApplied.w && snappedH === lastApplied.h) return;
lastApplied = { w: snappedW, h: snappedH };
gridEl.style.width = `${snappedW}px`;
gridEl.style.height = `${snappedH}px`;
resizeAndSetupCanvas();
}
function scheduleSnappedGridSize() {
if (sizingRAF) return;
sizingRAF = requestAnimationFrame(applySnappedGridSize);
}
function applyGridSize(newSize) {
const n = Number(newSize);
if (!Number.isFinite(n) || n < 1) return;
gridSize = n;
document.documentElement.style.setProperty('--grid', `${gridSize}px`);
snapSizeToGrid();
dotSize = Math.floor(Math.max(gridSize * 1.25, 32));
@ -241,7 +275,7 @@ function applyGridSize(newSize) {
dotSVGEl.setAttribute('height', dotSize);
setGrid();
resizeAndSetupCanvas();
scheduleSnappedGridSize();
}
function pxToGrid(v) {
@ -274,7 +308,29 @@ function setActiveType(typeId) {
}
}
function snapSizeToGrid() {
const grid = gridSize;
const rect = gridEl.getBoundingClientRect();
const targetW = rect.width;
const targetH = rect.height;
const snappedW = Math.floor(targetW / grid) * grid + 1;
const snappedH = Math.floor(targetH / grid) * grid + 1;
gridEl.style.width = `${snappedW}px`;
gridEl.style.height = `${snappedH}px`;
}
function snapToGrid(x, y) {
/*
For portability, we do not allow pixel coordinates in the data model
and only use pixels for rendering. We display both spaces on the screen
to ensure no matter the mode, the user is reasoning in the same two
coordinate spaces as they need. Thus, snapping will happen even if
the tool doesn't use it.
*/
const rect = gridEl.getBoundingClientRect();
const clampedX = Math.min(Math.max(x, rect.left), rect.right);
const clampedY = Math.min(Math.max(y, rect.top), rect.bottom);
@ -344,6 +400,7 @@ function normalizeLine(shape) {
}
function resizeAndSetupCanvas() {
snapSizeToGrid();
dpr = window.devicePixelRatio || 1;
const rect = canvasEl.getBoundingClientRect();
@ -581,9 +638,6 @@ document.addEventListener('keydown', (e) => {
});
gridEl.addEventListener('pointermove', (e) => {
// Note to certain minds that keep thinking we want to mix grid and pixel coordinates.
// No. No we do not. Pixels are not portable. Stop it.
if (!ctx) return;
const { ix, iy, x: snapX, y: snapY } = snapToGrid(e.clientX, e.clientY);