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

问题描述 投票:0回答:1

出于学习目的,我正在使用 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 行)。

谢谢

javascript if-statement zooming
1个回答
0
投票

我修复了你的代码。基本上,我必须确保偏移计算也考虑了比率,并在缩放计算后移回原位。

https://codesandbox.io/s/infinite-grid-2ocxcg?file=/src/index.js

© www.soinside.com 2019 - 2024. All rights reserved.