缩放游标 - 语言不可知

时间:2015-03-19 19:05:59

标签: canvas language-agnostic drawing

我在绘图应用程序中实现了缩放功能,但它总是从左上角缩放/缩放。一张图片会比我更好解释...... zooming and viewport schema
绘图区域是无限网格。绿色区域是当前显示在屏幕上的网格部分。当用户平移相机时,scrollPosition的坐标会改变,随之移动查看区域。当用户缩放时,它会更改pixelsPerInch变量。当我缩放时,如何使相机实际上在光标上居中?我也希望能够取消缩放,如果我只是在不移动光标的情况下保持缩放和不变,我应该总是在同一个地方。

编辑:我拥有的变量是scrollPosition(具有x和y的对象),pixelsPerInch,屏幕上实际窗口的宽度和高度,光标(x和y)。当我更改pixelsPerInch以便光标成为新放大区域的中心时,我正在寻找一种设置scrollPosition新值的方法。


我是如何解决的:

我从头开始重建一个可拖动且可扩展的画布系统,以确保隐藏在原始代码中的错误不是我的问题的原因。然后,我花了更多的时间来写下我想做的文字和图画。这应该更清楚。zooming on cursor, step by step

我做了一个函数,它将鼠标的坐标转换为底层无限表面上的坐标。它基本上将光标位置放在窗口上,将其乘以当前缩放因子,然后减去原点偏移(我在上一张图中称为scrollPosition)。

function relative(canvasCoord) {
    return {x: canvasCoord.x/ppi - offset.x, y: canvasCoord.y/ppi - offset.y };
}

这允许我在应用缩放之前和之后检查光标位置。然后,我可以计算两个坐标之间的差异,并将该差异应用于偏移。 Javascript代码:

context.canvas.addEventListener("wheel", function(e) {
    var canvasPoint = getCanvasCoordinates(e.pageX, e.pageY);
    var oldPoint = relative(canvasPoint);

    var scrollDirection = -Math.min(1, Math.max(-1, e.deltaY));
    ppi += scrollDirection / 10;

    var newPoint = relative(canvasPoint);
    offset.x += newPoint.x - oldPoint.x;
    offset.y += newPoint.y - oldPoint.y;
    blit();
});

这让我意识到我实际上是在基于视图的像素中存储偏移量而不是原始代码中的英寸,这在我实现缩放之前没有引起任何问题。愚蠢的错误。但从头开始肯定帮助我理解了视口。这里只是HTML5浏览器的快速演示(可能无法在chrome之外工作,我在本地版本中使用了polyfill脚本)。

如果你发现它比SO片段更加用户友好,那么JS会小提琴:http://jsfiddle.net/buu7h0be/5/



context = document.getElementById("myCanvas").getContext("2d");
context.fillStyle = "#5555FF";
context.imageSmoothingEnabled = context.webkitImageSmoothingEnabled = context.mozImageSmoothingEnabled;
ppi = 1; // pixels per inch
shapes = [];
offset = {x:0, y:0};
cursor = {x:0, y:0};
isDragging = false;

function Rectangle(x, y, width, height) {
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
}

Rectangle.prototype.getLeft = function() { return this.x - width/2; };
Rectangle.prototype.getRight = function() { return this.x + width/2; };
Rectangle.prototype.getTop = function() { return this.y - height/2; };
Rectangle.prototype.getBottom = function() { return this.y + height/2; };

Rectangle.prototype.draw = function() {
    context.save();
    context.translate(this.x * ppi, this.y * ppi);
    var w = this.width * ppi,
        h = this.height * ppi;
    context.fillRect(-w/2, -h/2, w, h);
    context.restore();
}

function clear() {
    context.save();
    context.setTransform(1, 0, 0, 1, 0, 0);
    context.clearRect(0, 0, context.canvas.width, context.canvas.height);
    context.restore();
}

function blit() {
    context.save();
    clear();
    context.translate(offset.x * ppi, offset.y * ppi);
    for(var i=0; i<shapes.length; ++i) {
        shapes[i].draw();
    }
    context.restore();
}

// relative coordinates : in inches and relative to 0,0 on the imaginary infinite surface
// canvas coordinates : in pixels and relative to the top left corner of the canvas element

function getCanvasCoordinates(pageX, pageY) {
  var rect = context.canvas.getBoundingClientRect();
  return {x: pageX - rect.left - document.body.scrollLeft, y: pageY - rect.top - document.body.scrollTop};
}

function relative(canvasCoord) {
    return {x: canvasCoord.x/ppi - offset.x, y: canvasCoord.y/ppi - offset.y };
}


context.canvas.addEventListener("mousedown", function(e) {
    isDragging = true;
    cursor = getCanvasCoordinates(e.pageX, e.pageY);
});

context.canvas.addEventListener("mousemove", function(e) {
    var newcursor = getCanvasCoordinates(e.pageX, e.pageY);
    if(isDragging) {
        offset.x += (newcursor.x - cursor.x) / ppi;
        offset.y += (newcursor.y - cursor.y) / ppi;
    }
    cursor = newcursor;
    blit();
});

context.canvas.addEventListener("mouseup", function(e) {
    isDragging = false;
});


context.canvas.addEventListener("wheel", function(e) {
    var canvasPoint = getCanvasCoordinates(e.pageX, e.pageY);
    var oldPoint = relative(canvasPoint);
    
    var scrollDirection = -Math.min(1, Math.max(-1, e.deltaY));
    ppi += scrollDirection / 10;
    
    var newPoint = relative(canvasPoint);
    offset.x += newPoint.x - oldPoint.x;
    offset.y += newPoint.y - oldPoint.y;
    blit();
});

context.canvas.addEventListener("keydown", function(e) {
    switch(e.keyCode) {
        case 107: //add (numpad)
            ppi += 0.05
            break;
        case 109: //subtract (numpad)
            ppi -= 0.05;
            break;
        case 37: //left
            offset.x -= 10;
            break;
        case 39: //right
            offset.x += 10;
            break;
        case 38: //up
            offset.y -= 10;
            break;
        case 40: //down
            offset.y += 10;
            break;
    }
    blit();
});


shapes.push(new Rectangle(100, 100, 100, 100));
shapes.push(new Rectangle(400, 200, 75, 150));
shapes.push(new Rectangle(200, 400, 175, 95));
blit();
&#13;
canvas {
    border: 0px solid transparent;
    outline: 1px solid silver;
    cursor: move;
}
&#13;
<canvas id="myCanvas" width="500" height="500" tabindex="0">
    Your browser is not compatible with the HTML5 canvas.
</canvas>
&#13;
&#13;
&#13;

1 个答案:

答案 0 :(得分:1)

好的,用语言无关的方式......向后解决问题。

您需要计算的是绘制场景的左上角位置(原点),以使场景中的选定点(光标)始终位于设备显示屏上的同一视点。

例如,假设您的场景:

  • 其左上角原点位于[0,0],
  • 缩放为100%,
  • 所选(光标)点为[100,50]。

向后工作,您的原点必须相对于所选点的[-100,-50]。换句话说,左上角原点或场景的X偏移为-100,相对于光标点的偏移为-50。

如果将场景缩放200%,则必须以原始[-100,-50]偏移的2倍绘制双倍大小的场景:

// the new originX & originY is [-200,-100]
originX = -100 * 2.00 
originY =  -50 * 2.00

因此,如果您在[-200,-100]处绘制双倍大小的场景,那么即使您的场景是原始场景的两倍,您的光标点也将位于设备显示屏上的相同位置。

因此,对于任何尺寸,您的场景都会缩放:

// calculate the required top-left of your scene for any given scaleFactor
originX = -100 * scaleFactor/100
originY =  -50 * scaleFactor/100