出于学习目的,我正在使用 vanilla javascript 构建无限网格缩放。 到目前为止一切顺利,但我有一个问题:
当我放大时,当最小像元大小是其原始大小的 2 倍时,轴上的值以及网格线会自行更新。这样我就可以无限放大。这很好。
为了在缩小时获得效果,我需要在实际单元格大小比其最小大小小 2 倍时更新网格线和轴上的值。
这在下面的预览中效果很好。 但这意味着放大和缩小效果不是对称的。
我希望网格线和值在完全相同的时间/点更新,而不管缩放方向(放大/缩小)。 像这样看:我希望缩小就像放大的简单倒带一样。事实并非如此。
我想要这样的原因是,当我放大时,我确信两个值之间的最小距离为 100 像素,我希望缩小时也是如此。 但是当我缩小时,两个轴值之间的最小距离下降到 50px。
这是我在 Grid 类的“draw()”函数中为放大更新所做的:
if (this.cellSize >= 2 * minCellSize) {}
这是我为缩小所做的
else if (this.cellSize < minCellSize / 2) {}
那些是周期性事件,它们会不时触发行和值的更新。
为了得到我想要的缩小,理论上,我应该做与放大指令完全相反的事情:
else if (this.cellSize < 2 * minCellSize) {}
但实际上,当我缩小时总是这样,因为由于放大条件,实际单元格大小总是小于其最小值的 2 倍,因此“else if”在缩小时始终触发事件,不是周期性的。
所以我真的不知道该怎么做。
任何帮助将不胜感激。
///////////////////////////////////////////////////////////////////////////////////////////
// Constantes
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d', { alpha: false });
const canvasColor = '#282c34';
const axisColor = "#ffffff";
const minCellSize = 20;
///////////////////////////////////////////////////////////////////////////////////////////
// Variables
let mousePosX = 0;
let mousePosY = 0;
let zoom = 1;
let zoomMin = 0;
let zoomMax = 0;
let opacity = 0;
let opacityMin = 0;
let opacityMax = 0;
let gen = 1;
let ratio = 4;
///////////////////////////////////////////////////////////////////////////////////////////
// Classes
class Grid {
constructor() {
this.width = stdCanvasW,
this.height = stdCanvasH,
this.axisPosX = stdCanvasW / 2,
this.axisPosY = stdCanvasH / 2,
this.cellSize = minCellSize
}
draw() {
// Grille au zoom infini
// Déterminer le loop des cellules et le multiplicateur/diviseur à utiliser pour les nombres selon
// le nombre de zoom /dézoom faits.
if (this.cellSize >= 2 * minCellSize) {
if (Math.abs(Math.log2(gen)) % 3 == 0) {
this.cellSize = 20;
gen = gen * 2;
ratio = ratio * 2;
}
else if (Math.abs(Math.log2(gen)) % 3 == 1) {
this.cellSize = 16;
gen = gen * 2;
if (gen > 1) { ratio = ratio * 2.5; }
else { ratio = ratio * 2; }
}
else if (Math.abs(Math.log2(gen)) % 3 == 2) {
this.cellSize = 20;
gen = gen * 2;
if (gen > 1) { ratio = ratio * 2; }
else { ratio = ratio * 2.5; }
}
}
else if (this.cellSize < minCellSize / 2) {
if (Math.abs(Math.log2(gen)) % 3 == 0) {
ratio = ratio / 2;
this.cellSize = 20;
gen = gen / 2
}
else if (Math.abs(Math.log2(gen)) % 3 == 1) {
if (gen < 1) {ratio = ratio / 2.5;}
else {ratio = ratio / 2;}
this.cellSize = 16;
gen = gen / 2;
}
else if (Math.abs(Math.log2(gen)) % 3 == 2) {
if (gen < 1) {ratio = ratio / 2;}
else {ratio = ratio / 2.5;}
this.cellSize = 20;
gen = gen / 2;
}
}
// Afficher la couleur de fond du canevas
context.fillStyle = canvasColor;
context.fillRect(0, 0, this.width, this.height);
// Afficher les lignes
const minDivY = -Math.ceil(this.axisPosY / this.cellSize);
const minDivX = -Math.ceil(this.axisPosX / this.cellSize);
const maxDivY = Math.ceil((this.height - this.axisPosY) / this.cellSize);
const maxDivX = Math.ceil((this.width - this.axisPosX) / this.cellSize);
for (let lineV = minDivY; lineV <= maxDivY; lineV++) {
this.setGridLines('v', lineV); this.setGridValues('v', lineV);
}
for (let lineH = minDivX; lineH <= maxDivX; lineH++) {
this.setGridLines('h', lineH); this.setGridValues('h', lineH);
}
// Afficher les axes
this.setAxis();
}
setLineStyle(line) {
if (line == 'axis') {
// Les axes
context.lineWidth = 1;
context.strokeStyle = axisColor;
}
else {
context.lineWidth = 0.5;
if (line % 8 == 0) {
if (zoom > 0) {context.strokeStyle = 'rgba(250,250,250,0.9)';}
else if (zoom < 0) {context.strokeStyle = 'rgba(250,250,250,' + this.setOpacity((this.cellSize / minCellSize), 0.9, 0.6) + ')';}
}
else if (line % 4 == 0) {
if (zoom > 0) {context.strokeStyle = 'rgba(250,250,250,' + this.setOpacity((this.cellSize / minCellSize), 0.6, 0.9) + ')';}
else if (zoom < 0) {context.strokeStyle = 'rgba(250,250,250,' + this.setOpacity((this.cellSize / minCellSize), 0.6, 0.2) + ')';}
}
else {
if (zoom > 0) {context.strokeStyle = 'rgba(250,250,250,' + this.setOpacity((this.cellSize / minCellSize), 0, 0.14) + ')'; }
else if (zoom < 0) {context.strokeStyle = 'rgba(250,250,250,' + this.setOpacity((this.cellSize / minCellSize), 0.05, 0) + ')'; }
}
}
}
setGridLines(direction, line) {
// Styler
this.setLineStyle(line);
if (direction == 'v') {
// Tracer
const y = (this.axisPosY + this.cellSize * line);
context.beginPath();
context.moveTo(0, y);
context.lineTo(this.width, y);
}
else if (direction == 'h') {
// Tracer
const x = (this.axisPosX + this.cellSize * line);
context.beginPath();
context.moveTo(x, 0);
context.lineTo(x, this.height);
}
context.stroke();
context.closePath();
}
setGridValues(direction, line) {
// Styler
context.font = '12px Arial';
context.fillStyle = '#aaaaaa';
// Tracer
context.beginPath();
if (direction == 'v' && line % 4 == 0 && line != 0) {
let value = -line / ratio;
let valueOffset = context.measureText(value).width + 15
context.textAlign = 'right'
if (this.axisPosX >= this.width) {
context.fillText(value, this.width - 15, this.axisPosY - line * (-this.cellSize) + 3)
}
else if (this.axisPosX <= valueOffset + 15) {
context.fillText(value, valueOffset, this.axisPosY - line * (-this.cellSize) + 3)
}
else {
context.fillText(value, this.axisPosX - 15, this.axisPosY + line * this.cellSize + 3)
}
}
else if (direction == 'h' && line % 4 == 0 && line != 0) {
let value = line / ratio;
context.textAlign = 'center'
if (this.axisPosY >= this.height - canvas.offsetTop) {
context.fillText(value, this.axisPosX + line * this.cellSize, this.height - 20)
}
else if (this.axisPosY <= 0) {
context.fillText(value, this.axisPosX + line * this.cellSize, 20)
}
else {
context.fillText(value, this.axisPosX + line * this.cellSize, this.axisPosY + 20)
}
}
context.closePath();
}
setAxis() {
// Styler
this.setLineStyle('axis');
//Tracer
context.beginPath();
// Tracer l'horizontale
context.moveTo(0, this.axisPosY);
context.lineTo(this.width, this.axisPosY);
// Tracer la verticale
context.moveTo(this.axisPosX, 0);
context.lineTo(this.axisPosX, this.height);
context.stroke();
context.closePath();
// Numéroter le point 0
context.font = '12px arial';
context.fillStyle = '#aaaaaa';
context.textAlign = 'center'
context.beginPath();
context.fillText(0, this.axisPosX - 15, this.axisPosY + 20)
context.closePath();
}
setPan() {
// As long as we pan, the (0;0) coordinate is not updated yet
const beforeX = mouseStartX / this.cellSize;
const beforeY = mouseStartY / this.cellSize;
const afterX = mousePosX / this.cellSize;
const afterY = mousePosY / this.cellSize;
const deltaX = afterX - beforeX;
const deltaY = afterY - beforeY;
this.axisPosX = lastAxisPosX;
this.axisPosY = lastAxisPosY;
this.axisPosX += deltaX * this.cellSize;
this.axisPosY += deltaY * this.cellSize;
this.draw();
}
setZoom() {
// Calculate the mouse position before applying the zoom
// in the coordinate system of the grid
const beforeX = (mousePosX - this.axisPosX) / this.cellSize;
const beforeY = (mousePosY - this.axisPosY) / this.cellSize;
this.cellSize = this.cellSize + zoom;
// After zoom, you'll see the coordinates changed
const afterX = (mousePosX - this.axisPosX) / this.cellSize;
const afterY = (mousePosY - this.axisPosY) / this.cellSize;
// Calculate the shift
const deltaX = afterX - beforeX;
const deltaY = afterY - beforeY;
// "Undo" the shift by shifting the coordinate system's center
this.axisPosX += deltaX * this.cellSize;
this.axisPosY += deltaY * this.cellSize;
this.draw();
}
setOpacity(zoomLevel, val1, val2) {
if (zoom > 0) {
opacityMin = val1; opacityMax = val2; zoomMin = 1 ; zoomMax = 2;
}
else if (zoom < 0) {
opacityMin = val2; opacityMax = val1; zoomMin = 0.5; zoomMax = 1;
}
const zoomRange = (zoomMax - zoomMin);
const opacityRange = (opacityMax - opacityMin);
const zoomLevelPercent = (zoomLevel - zoomMin) / zoomRange;
const opacityLevel = (opacityRange * zoomLevelPercent) + opacityMin;
return opacityLevel;
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Fonctions
function init() {
// Déterminer la résolution d'affichage
stdCanvasW = document.body.clientWidth - 2 * (canvas.offsetLeft);
stdCanvasH = stdCanvasW / 2;
optCanvasW = stdCanvasW * window.devicePixelRatio;
optCanvasH = stdCanvasH * window.devicePixelRatio;
if (window.devicePixelRatio > 1) {
canvas.width = optCanvasW;
canvas.height = optCanvasH;
context.scale(window.devicePixelRatio, window.devicePixelRatio);
}
else {
canvas.width = stdCanvasW;
canvas.height = stdCanvasH;
}
canvas.style.width = stdCanvasW + "px";
canvas.style.height = stdCanvasH + "px";
lastAxisPosX = stdCanvasW / 2
lastAxisPosY = stdCanvasH / 2
// Créer et afficher la grille
grid = new Grid();
grid.draw();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Démarrage de la webapp
init();
///////////////////////////////////////////////////////////////////////////////////////////
// Evenements
window.addEventListener("resize", init);
// Zoomer le canvas avec la roulette
canvas.addEventListener('wheel', function (e) {
e.preventDefault();
e.stopPropagation();
zoom = e.wheelDelta / 120;
grid.setZoom();
// Get last (0;0) coordinates
lastAxisPosX = grid.axisPosX;
lastAxisPosY = grid.axisPosY;
})
// Pan canvas on drag
canvas.addEventListener('mousedown', function (e) {
e.preventDefault();
e.stopPropagation();
mouseStartX = parseInt(e.clientX) - canvas.offsetLeft;
mouseStartY = parseInt(e.clientY) - canvas.offsetTop;
canvas.onmousemove = function (e) {
e.preventDefault();
e.stopPropagation();
grid.setPan();
}
})
// Récupérer les coordonnées de la souris en mouvement.
canvas.addEventListener('mousemove', function (e) {
e.preventDefault();
e.stopPropagation();
mousePosX = parseInt(e.clientX) - canvas.offsetLeft;
mousePosY = parseInt(e.clientY) - canvas.offsetTop;
})
canvas.addEventListener('mouseup', function (e) {
e.preventDefault();
e.stopPropagation();
canvas.onmousemove = null;
canvas.onmousewheel = null;
// Get last (0;0) coordinates
lastAxisPosX = grid.axisPosX;
lastAxisPosY = grid.axisPosY;
})
canvas.addEventListener('mouseout', function (e) {
e.preventDefault();
e.stopPropagation();
canvas.onmousemove = null;
canvas.onmousewheel = null;
// Get last (0;0) coordinates
lastAxisPosX = grid.axisPosX;
lastAxisPosY = grid.axisPosY;
})
///////////////////////////////////////////////////////////////////////////////////////////
html, body
{
background:#21252b;
width:100%;
height:100%;
margin:0px;
padding:0px;
overflow: hidden;
}
#canvas{
margin:20px;
border: 1px solid white;
}
<canvas id="canvas"></canvas>
我认为如果将片段屏幕设置为全尺寸,网格将更具可读性。
如果我需要进行一些更改以使整个内容更具可读性,请询问。 但是要知道与我的问题相关的部分是 Grid 类中 draw() 函数的前几行(第 51 和 73 行)。
谢谢
我修复了你的代码。基本上,我必须确保偏移计算也考虑了比率,并在缩放计算后移回原位。
https://codesandbox.io/s/infinite-grid-2ocxcg?file=/src/index.js