I'm currently working on adding a panning system to the canvas in my Vue app. While I've successfully implemented the panning system, I'm facing challenges rendering an infinite grid of dots efficiently.
Here's a code of my component:
<template>
<div>
<div>
<canvas
@mousedown="baseEditorMouseDown"
@mouseup="baseEditorMouseUp"
@mousemove="baseEditorMouseMove"
ref="baseEditor"
></canvas>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, type Ref, computed, onMounted } from "vue";
const baseEditor: Ref<HTMLCanvasElement | null> = ref(null);
const context: Ref<CanvasRenderingContext2D | null> = ref(null);
const gridOffset = ref(50);
const imagePosition: Ref<any> = ref({
x: 0,
y: 0,
});
const canvasBaseImage = ref(new Image());
const isMasking = ref(false);
const lastMaskPosition: Ref<any> = ref({
x: 0,
y: 0,
});
const isPanning = ref(false);
const lastClickPosition = ref({ x: 0, y: 0 });
const lastOffsetTemp = ref({ x: 0, y: 0 });
const lastOffset = ref({ x: 0, y: 0 });
const computedGridCircles = computed(() => {
var coords = [];
for (
let x = -window.innerWidth + gridOffset.value / 2;
x < window.innerWidth * 2;
x += gridOffset.value
) {
for (
let y = -window.innerHeight + gridOffset.value / 2;
y < window.innerHeight * 2;
y += gridOffset.value
) {
coords.push({
x,
y,
});
}
}
return coords;
});
function drawGridCircles() {
if (context.value) {
context.value.beginPath();
for (let i = 0; i < computedGridCircles.value.length; i++) {
const coord = computedGridCircles.value[i];
context.value.rect(
coord.x + lastOffsetTemp.value.x + lastOffset.value.x,
coord.y + lastOffsetTemp.value.y + lastOffset.value.y,
3,
3
);
}
context.value.fillStyle = "#333";
context.value.fill();
}
}
function baseEditorMouseMove(event: MouseEvent) {
if (isPanning.value) {
lastOffset.value.x = event.clientX - lastClickPosition.value.x;
lastOffset.value.y = event.clientY - lastClickPosition.value.y;
updatePanning();
}
}
function baseEditorMouseDown(event: MouseEvent) {
isPanning.value = true;
lastClickPosition.value.x = event.clientX;
lastClickPosition.value.y = event.clientY;
}
function baseEditorMouseUp(event: MouseEvent) {
isMasking.value = false;
isPanning.value = false;
lastOffsetTemp.value.x += lastOffset.value.x;
lastOffsetTemp.value.y += lastOffset.value.y;
}
function clearCanvas() {
if (context.value) {
context.value.clearRect(0, 0, window.innerWidth, window.innerHeight);
}
}
function updatePanning() {
clearCanvas();
drawGridCircles();
}
onMounted(() => {
if (baseEditor.value) {
context.value = baseEditor.value.getContext("2d");
baseEditor.value.width = window.innerWidth;
baseEditor.value.height = window.innerHeight;
}
drawGridCircles();
});
</script>
In the computedGridCircles function, I calculate coordinates for grid dots based on the user's window size. However, this doesn't provide an infinite grid. I've referred to several resources, including:
- https://www.sandromaglione.com/articles/infinite-canvas-html-with-zoom-and-pan
- HTML infinite pan-able canvas
- How can I make infinite grid with canvas? (accepted answer looks similar but it uses context translate which I tried to implement and I couldn't manage to)
- Make a canvas infinite
but I'm missing something in the math calculations.
Here's a StackBlitz demo for better understanding. https://stackblitz.com/edit/vue3-vite-typescript-starter-21gys6?file=src%2FApp.vue
I appreciate any guidance on achieving an infinite grid with efficient rendering. Thank you for your help!