无限网格缩放:对称放大和缩小



我在香草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

相关内容

  • 没有找到相关文章

最新更新