我在香草javascript中构建一个无限网格缩放用于学习目的。到目前为止一切顺利,但我有一个问题:
当我放大时,当最小的单元格大小是其原始大小的2倍时,轴上的值以及网格线会自我更新。这样就得到了无限变焦。这很好。
当我缩小时,我需要网格线和轴上的值在实际单元格大小比其最小大小小2倍时更新。
这个效果非常好,就像下面的预览一样。但这意味着放大和缩小效果是不对称的。
我希望网格线和值在完全相同的时间/地点更新,无论缩放方向如何(放大/缩小)。可以这样看:我想要缩小,就好像它是放大的简单倒带。它不是。
我想要它这样的原因是,当我放大时,我确信两个值之间的最小距离将是100像素,我希望它在我缩小时也是如此。但是当我缩小时,两个轴值之间的最小距离下降到50px。
下面是我在"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