Я пытаюсь изменить начало масштабирования на курсор мыши вместо верхнего левого угла по умолчанию, и я не могу использовать ctx.translate с ctx.scale, поскольку буфер сетки необходимо перерисовать и его нельзя масштабировать ( одна линия всегда должна быть шириной 1 пиксель). Сетка можно масштабировать и перемещать, это просто неверное начало координат.
Я не понимаю, как вычислить новые координаты x и новые координаты y сетки после масштабирования.
Важный фрагмент кода и то, что я уже пробовал, прокомментированы в классе Camera.
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
// utils //
function getCursorPos(evt) {
const rect = canvas.getBoundingClientRect();
return {
x: Math.floor(((evt.clientX - rect.left) / (rect.right - rect.left)) * canvas.offsetWidth),
y: Math.floor(((evt.clientY - rect.top) / (rect.bottom - rect.top)) * canvas.offsetHeight),
};
}
//////////
const scene = {
renderer: canvas,
context: ctx,
width: 1200,
height: 1000,
cellSize: 30,
render: function (buffer, x, y) {
this.context.clearRect(0, 0, this.renderer.width, this.renderer.height);
this.context.drawImage(buffer, x, y);
},
};
class Grid {
constructor() {
this.width = scene.width;
this.height = scene.height;
this.cellSize = scene.cellSize;
this.color = "black";
this.buffer = document.createElement("canvas");
this.buffer.width = this.width;
this.buffer.height = this.height;
}
build() {
// we don't directly make the draw calls on the main canvas (scene.renderer) ,
// instead we create a buffer (a canvas element in this case),
// which will be drawn as an image on the main canvas when we call scene.render();
const ctx = this.buffer.getContext("2d");
ctx.clearRect(0, 0, this.buffer.width, this.buffer.height);
ctx.setLineDash([2, 5]);
for (let u = 0, len = this.height; u < len; u += this.cellSize) {
ctx.beginPath();
ctx.moveTo(0.5, u + 0.5);
ctx.lineTo(0.5 + this.width, u + 0.5);
ctx.stroke();
}
for (let u = 0, len = this.width; u < len; u += this.cellSize) {
ctx.beginPath();
ctx.moveTo(u + 0.5, 0.5);
ctx.lineTo(u + 0.5, 0.5 + this.height);
ctx.stroke();
}
}
setDimensions(w, h) {
this.width = w;
this.height = h;
}
getDimensions() {
return { gw: this.width, gh: this.height };
}
setCellSize(size) {
this.cellSize = size;
}
getCellSize() {
return this.cellSize;
}
getBuffer() {
return this.buffer;
}
}
class Camera {
constructor() {
this.x = 0;
this.y = 0;
this.startDrag = null;
this.zoom = 1;
this.zoomInc = 0.05;
}
// converts screen coordinates to world coordinates
toWorld(number) {
return Math.floor(number / this.zoom);
}
toScreen(number) {
return Math.floor(number / this.zoom);
}
setStartDrag(coord) {
this.startDrag = { x: this.x + coord.x, y: this.y + coord.y };
}
isStartedDrag() {
return !!this.startDrag;
}
drag(coord) {
this.x = this.startDrag.x - coord.x;
this.y = this.startDrag.y - coord.y;
}
stopDrag() {
this.startDrag = null;
}
// the bit of code I can't figure //
setScale({ x, y, deltaY }) {
const step = deltaY > 0 ? -this.zoomInc : this.zoomInc;
this.zoom += step;
// this.x and this.y is where the grid is going to be rendered on the canvas;
// first I thought about doing it this way :
//this.x = -this.toScreen(this.toWorld(x) - x);
//this.y = -this.toScreen(this.toWorld(y) - y);
// but it only work if the grid is at x: 0 y: 0;
// after some research I tried to shift x and y relatively to the cursor world position in the grid;
//const worldPos = { x: this.toWorld(x) - this.x, y: this.toWorld(y) - this.y };
//this.x = -(this.x - worldPos.x * step);
//this.y = -(this.y - worldPos.y * step);
// if x and y aren't changed the zoom origin defaults to the current origin of the camera;
}
getZoom() {
return this.zoom;
}
}
function init() {
// initial setup //
const grid = new Grid();
const camera = new Camera();
grid.build();
const gridBuffer = grid.getBuffer();
scene.context.drawImage(gridBuffer, 0, 0);
scene.renderer.addEventListener("mousemove", (evt) => {
if (camera.isStartedDrag()) {
camera.drag(getCursorPos(evt));
scene.render(gridBuffer, -camera.x, -camera.y);
}
});
scene.renderer.addEventListener("mousedown", (evt) => {
camera.setStartDrag(getCursorPos(evt));
});
scene.renderer.addEventListener("mouseup", () => {
camera.stopDrag();
});
scene.renderer.addEventListener("wheel", (evt) => {
evt.preventDefault();
camera.setScale(evt);
const zoom = camera.getZoom();
grid.setCellSize(scene.cellSize * zoom);
grid.setDimensions(scene.width * zoom, scene.height * zoom);
// we rebuild a smaller or bigger grid according to the new zoom level;
grid.build();
const gridBuffer = grid.getBuffer();
scene.render(gridBuffer, -camera.x, -camera.y);
});
}
init();
<html lang="en">
<head>
<script defer src="main.js"></script>
</head>
<body>
<canvas id="canvas" width="800" height="600" style="border: 1px solid black"></canvas>
</body>
</html>
Вот скрипка: https://jsbin.com/wecupoxefe/edit?html, js, вывод