缩放时如何绑定图像平移(HTML Canvas) [英] How to bound image pan when zooming (HTML Canvas)

查看:24
本文介绍了缩放时如何绑定图像平移(HTML Canvas)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试限制界限,但遇到了问题.我正在从另一个画布放大图像,然后实现缩放和平移.我的问题(下面的代码)是限制/限制 offsetx/y 以便您永远看不到空格;仅图像的一部分.

请原谅我的混乱!任何帮助表示赞赏!:P

var zoomIntensity = 0.2;var canvas = document.getElementById("canvas");var canvas2 = document.getElementById("canvas2");var context = canvas.getContext("2d");var context2 = canvas2.getContext("2d");无功宽度= 200;变量高度 = 200;无功比例= 1;var originx = 0;变量原点 = 0;var 偏移 = {x:0, y:0};//用随机像素填充较小的画布for(var x = 0; x <100; x++)for(var y = 0; y <100; y++){varrando = function(){return Math.floor(Math.random() * 9)};var val = rando();context2.fillStyle = "#" + val + val + val;context2.fillRect(x,y,1,1);}//绘制更大的画布函数绘制(){context.imageSmoothingEnabled = false;//将屏幕清除为白色.context.fillStyle = "白色";context.fillRect(originx - offset.x, originy - offset.y, width/scale, height/scale);context.drawImage(canvas2, 0,0, width, height);}//以 60FPS 绘制循环.设置间隔(绘制,1000/60);canvas.onmousewheel = 函数(事件){event.preventDefault();//获取鼠标偏移.var mousex = event.clientX - canvas.offsetLeft;var mousey = event.clientY - canvas.offsetTop;//将轮归一化为 +1 或 -1.var wheel = event.wheelDelta/120;//计算缩放系数.var zoom = Math.exp(wheel*zoomIntensity);//进行翻译,使可见原点位于上下文的原点.context.translate(originx - offset.x, originy - offset.y);//偏移量是平移//确保我们的缩小不会超过正常比例var resultScale = 比例 * 缩放;if(resultingScale <1)缩放 = 1/比例;//计算新的可见原点.原来鼠标在一个//鼠标/缩放距角的距离,我们希望点在下方//鼠标缩放后保持原位,但是这个//在鼠标/new_scale 处远离角落.因此我们需要//移动原点(角的坐标)来解决这个问题.originx -= mousex/(scale*zoom) - mousex/scale;originy -= mousey/(scale*zoom) - mousey/scale;//缩放它(由于上面的转换以原点为中心).context.scale(缩放,缩放);//将可见原点偏移到它的正确位置.context.translate(-originx + offset.x, -originy + offset.y);//偏移量是平移//更新比例和其他.缩放 *= 缩放;}document.onkeydown = 函数 (evt){无功偏移x = 0;var offsety = 0;开关(evt.which){case 37://左偏移x = 1;休息;case 38://向上偏移量 = 1;休息;case 39://对偏移量x = -1休息;case 40://向下偏移量 = -1;休息;}offsetx/= 比例;偏移量/= 比例;offset.x += offsetx;offset.y += offsety;context.translate(offsetx,offsety);}

<canvas id="canvas2" width="100" height="100"></canvas>

解决方案

使用变换矩阵约束视图

要限制位置,您需要将图像的角坐标转换为屏幕坐标.由于在浏览器中获取转换仍然不是标准的,下面的演示包含转换的副本.

对象 view 保存画布视图.当您使用函数 view.setBounds(top,left,right,bottom); 视图将被锁定到该区域(您正在查看的图像 0,0,100,100)

比例和位置(原点)将受到约束,以将边界保持在 view.setContext(context) 设置的画布上下文的外部或边缘.

函数scaleAt(pos,amount); 将在指定的位置(例如鼠标位置)进行缩放

要设置转换,请使用 view.apply() 这将更新视图转换并设置上下文转换.

还有一些其他函数可能证明很方便,请参阅代码.

演示使用鼠标点击拖动来平移和滚轮缩放.

Demo 是 OP 的示例宽度修改以回答问题的副本.

//在通过 javascript 做任何形式的动画时使用 requestAnimationFrame请求动画帧(绘制);var zoomIntensity = 0.2;var canvas = document.getElementById("canvas");var canvas2 = document.getElementById("canvas2");var context = canvas.getContext("2d");var context2 = canvas2.getContext("2d");无功宽度= 200;变量高度 = 200;context.font = "24px arial";context.textAlign = "center";context.lineJoin = "round";//防止在strokeText 上出现斜接//用随机像素填充较小的画布for(var x = 0; x <100; x++){for(var y = 0; y <100; y++) {varrando = function(){return Math.floor(Math.random() * 9)};var val = rando();if(x === 0 || y === 0 || x === 99 || y === 99){context2.fillStyle = "#FF0000";}别的{context2.fillStyle = "#" + val + val + val;}context2.fillRect(x,y,1,1);}}//鼠标保持鼠标位置按钮状态,如果鼠标悬停在带有overid的画布上var 鼠标 = {位置:{x:0,y:0},worldPos : {x : 0, y : 0},posLast : {x : 0, y : 0},按钮:假,overId : "",//元素鼠标的id结束拖动:假,whichWheel : -1,//第一个轮子事件将获得轮子轮子:0,}//视图处理缩放和平移(也可以处理旋转但已将其取出,因为在不丢失一些图像或看到一些背景的情况下无法限制旋转.const 视图 = (()=>{const 矩阵 = [1,0,0,1,0,0];//当前视图变换const invMatrix = [1,0,0,1,0,0];//当前逆视图变换var m = 矩阵;//别名var im = invMatrix;//别名无功比例= 1;//当前比例常量边界 = {左上角:0,左:0,右:200,底部 : 200,}var useConstraint = true;//如果为真,则限制平移和缩放到//在当前上下文中保持边界var maxScale = 1;const workPoint1 = {x:0, y:0};const workPoint2 = {x:0, y:0};const wp1 = workPoint1;//别名const wp2 = workPoint2;//别名var ctx;const pos = {//原点的当前位置x : 0,y : 0,}var 脏 = 真;常量 API = {canvasDefault () { ctx.setTransform(1,0,0,1,0,0) },申请(){如果(脏){ this.update() }ctx.setTransform(m[0],m[1],m[2],m[3],m[4],m[5]);},getScale(){返回比例},getMaxScale() { return maxScale },matrix,//暴露矩阵invMatrix,//暴露逆矩阵update(){//调用更新变换脏 = 假;m[3] = m[0] = 比例;m[1] = m[2] = 0;m[4] = pos.x;m[5] = pos.y;如果(使用约束){this.constrain();}this.invScale = 1/比例;//计算逆变换var cross = m[0] * m[3] - m[1] * m[2];im[0] = m[3]/交叉;im[1] = -m[1]/交叉;im[2] = -m[2]/交叉;im[3] = m[0]/交叉;},约束(){maxScale = Math.min(ctx.canvas.width/(bounds.right - bounds.left) ,ctx.canvas.height/(bounds.bottom - bounds.top));if (scale  0) { m[4] = pos.x -= wp2.x }如果 (wp2.y > 0) { m[5] = pos.y -= wp2.y }wp1.x = bounds.right;wp1.y = bounds.bottom;this.toScreen(wp1,wp2);if (wp2.x document.addEventListener(eventName,mouseEvent));功能鼠标事件(事件){mouse.overId = event.target.id;if(event.target.id === "canvas" || mouse.dragging){//只对画布鼠标事件感兴趣,包括在画布上启动的拖动事件.mouse.posLast.x = mouse.pos.x;mouse.posLast.y = mouse.pos.y;mouse.pos.x = event.clientX - canvas.offsetLeft;mouse.pos.y = event.clientY - canvas.offsetTop;view.toWorld(mouse.pos, mouse.worldPos);//获取世界坐标(鼠标在画布 2 上的位置)if (event.type === "mousemove"){如果(鼠标.按钮){视图.移动(mouse.pos.x - mouse.posLast.x,mouse.pos.y - mouse.posLast.y)}} else if (event.type === "mousedown") { mouse.button = true;mouse.dragging = true }else if (event.type === "mouseup") { mouse.button = false;mouse.dragging = false }else if(event.type === "mousewheel" && (mouse.whichWheel === 1 || mouse.whichWheel === -1)){鼠标.whichWheel = 1;mouse.wheel = event.wheelDelta;}else if(event.type === "wheel" && (mouse.whichWheel === 2 || mouse.whichWheel === -1)){鼠标.whichWheel = 2;mouse.wheel = -event.deltaY;}else if(event.type === "DOMMouseScroll" && (mouse.whichWheel === 3 || mouse.whichWheel === -1)){鼠标.whichWheel = 3;mouse.wheel = -event.detail;}如果(鼠标滚轮!== 0){event.preventDefault();view.scaleAt(mouse.pos, Math.exp((mouse.wheel/120) *zoomIntensity));鼠标滚轮 = 0;}}}

div { user-select: none;}/* 鼠标防止拖动选择内容 */画布 { 边框:2px 纯黑色;}

<canvas id="canvas" width="200" height="200"></canvas><canvas id="canvas2" width="100" height="100"></canvas><p>鼠标滚轮进行缩放.鼠标单击拖动以平移.</p><p>缩放图像受限于画布</p>

I'm trying to limit boundaries and I'm running into issues. I'm upscaling an image from another canvas and then implementing zoom and pan. My issue (code below) is with limiting/capping the offsetx/y so that you never see the whitespace; only parts of the image.

Pardon the mess! Any help is appreciated! :P

var zoomIntensity = 0.2;

var canvas = document.getElementById("canvas");
var canvas2 = document.getElementById("canvas2");
var context = canvas.getContext("2d");
var context2 = canvas2.getContext("2d");
var width = 200;
var height = 200;

var scale = 1;
var originx = 0;
var originy = 0;

var offset = {x:0, y:0};

//fill smaller canvas with random pixels
for(var x = 0; x < 100; x++)
for(var y = 0; y < 100; y++)
{
  var rando = function(){return Math.floor(Math.random() * 9)};
  var val = rando();
  context2.fillStyle = "#" + val + val + val;
  context2.fillRect(x,y,1,1);
}

//draw the larger canvas
function draw()
{
		context.imageSmoothingEnabled = false;
    
    // Clear screen to white.
    context.fillStyle = "white";
    context.fillRect(originx - offset.x, originy - offset.y, width/scale, height/scale);
		context.drawImage(canvas2, 0,0, width, height);
}

// Draw loop at 60FPS.
setInterval(draw, 1000/60);

canvas.onmousewheel = function (event){
    event.preventDefault();
    
    // Get mouse offset.
    var mousex = event.clientX - canvas.offsetLeft;
    var mousey = event.clientY - canvas.offsetTop;
    
    // Normalize wheel to +1 or -1.
    var wheel = event.wheelDelta/120;

    // Compute zoom factor.
    var zoom = Math.exp(wheel*zoomIntensity);
    
    // Translate so the visible origin is at the context's origin.
    context.translate(originx - offset.x, originy - offset.y); //offset is panning
    
    //make sure we don't zoom out further than normal scale
    var resultingScale = scale * zoom;
    if(resultingScale < 1)
    	zoom = 1/scale;
  
    // Compute the new visible origin. Originally the mouse is at a
    // distance mouse/scale from the corner, we want the point under
    // the mouse to remain in the same place after the zoom, but this
    // is at mouse/new_scale away from the corner. Therefore we need to
    // shift the origin (coordinates of the corner) to account for this.
    originx -= mousex/(scale*zoom) - mousex/scale;
    originy -= mousey/(scale*zoom) - mousey/scale;
    
    // Scale it (centered around the origin due to the trasnslate above).
    context.scale(zoom, zoom);
    
    // Offset the visible origin to it's proper position.
    context.translate(-originx + offset.x, -originy + offset.y); //offset is panning

    // Update scale and others.
    scale *= zoom;
}

document.onkeydown = function (evt)
{
	var offsetx = 0;
  var offsety = 0;
  
	switch(evt.which)
	{
      case 37: //left
        offsetx = 1;
        break;
      case 38: //up
        offsety = 1;
        break;
      case 39: //right
        offsetx = -1
        break;
      case 40: //down
        offsety = -1;
        break;
  }
  
  offsetx /= scale;
  offsety /= scale;
  
  offset.x += offsetx;
  offset.y += offsety;
  
 	context.translate(offsetx,offsety);
}

<canvas id="canvas" width="200" height="200"></canvas>
<canvas id="canvas2" width="100" height="100"></canvas>

解决方案

Using transformation matrix to constrain a view

To constrain the position you need to transform the corner coordinates of the image to screen coordinates. As getting the transform is still not standard across browsers the demo below holds a copy of the transform.

The object view holds the canvas view. When you use the function view.setBounds(top,left,right,bottom); the view will be locked to that area (the image you are viewing 0,0,100,100)

The scale and position (origin) will be constrained to keep the bounds outside or one the edge of the canvas context set by view.setContext(context).

The function scaleAt(pos,amount); will scale at a specified pos (eg mouse position)

To set the transform use view.apply() this will update the view transform and set the context transform.

The are a few other functions that may prove handy see code.

Demo uses mouse click drag to pan and wheel to zoom.

Demo is a copy of the OP's example width modifications to answer question.

// use requestAnimationFrame when doing any form of animation via javascript
requestAnimationFrame(draw);

var zoomIntensity = 0.2;

var canvas = document.getElementById("canvas");
var canvas2 = document.getElementById("canvas2");
var context = canvas.getContext("2d");
var context2 = canvas2.getContext("2d");
var width = 200;
var height = 200;
context.font = "24px arial";
context.textAlign = "center";
context.lineJoin = "round"; // to prevent miter spurs on strokeText 
//fill smaller canvas with random pixels
for(var x = 0; x < 100; x++){
  for(var y = 0; y < 100; y++) {
    var rando = function(){return Math.floor(Math.random() * 9)};
    var val = rando();
    if(x === 0 || y === 0 || x === 99 || y === 99){
        context2.fillStyle = "#FF0000";
    }else{
        context2.fillStyle = "#" + val + val + val;
    
    }
    context2.fillRect(x,y,1,1);
  }
}

// mouse holds mouse position button state, and if mouse over canvas with overid
var mouse = {
    pos : {x : 0, y : 0},
    worldPos : {x : 0, y : 0},
    posLast : {x : 0, y : 0},
    button : false,
    overId : "",  // id of element mouse is over
    dragging : false,
    whichWheel : -1, // first wheel event will get the wheel
    wheel : 0,
}

// View handles zoom and pan (can also handle rotate but have taken that out as rotate can not be contrained without losing some of the image or seeing some of the background.
const view = (()=>{
    const matrix = [1,0,0,1,0,0]; // current view transform
    const invMatrix = [1,0,0,1,0,0]; // current inverse view transform
    var m = matrix;  // alias
    var im = invMatrix; // alias
    var scale = 1;   // current scale
    const bounds = {
        topLeft : 0,
        left : 0,
        right : 200,
        bottom : 200,
    }
    var useConstraint = true; // if true then limit pan and zoom to 
                              // keep bounds within the current context
    
    var maxScale = 1;
    const workPoint1 = {x :0, y : 0};
    const workPoint2 = {x :0, y : 0};
    const wp1 = workPoint1; // alias
    const wp2 = workPoint2; // alias
    var ctx;
    const pos = {      // current position of origin
        x : 0,
        y : 0,
    }
    var dirty = true;
    const API = {
        canvasDefault () { ctx.setTransform(1,0,0,1,0,0) },
        apply(){
            if(dirty){ this.update() }
            ctx.setTransform(m[0],m[1],m[2],m[3],m[4],m[5]);
        },
        getScale () { return scale },
        getMaxScale () { return maxScale },
        matrix,  // expose the matrix
        invMatrix, // expose the inverse matrix
        update(){ // call to update transforms
            dirty = false;
            m[3] = m[0] = scale;
            m[1] = m[2] = 0;
            m[4] = pos.x;
            m[5] = pos.y;
            if(useConstraint){
                this.constrain();
            }
            this.invScale = 1 / scale;
            // calculate the inverse transformation
            var cross = m[0] * m[3] - m[1] * m[2];
            im[0] =  m[3] / cross;
            im[1] = -m[1] / cross;
            im[2] = -m[2] / cross;
            im[3] =  m[0] / cross;
        },
        constrain(){
            maxScale = Math.min(
                ctx.canvas.width / (bounds.right - bounds.left) ,
                ctx.canvas.height / (bounds.bottom - bounds.top)
            );
            if (scale < maxScale) {  m[0] = m[3] = scale = maxScale }
            wp1.x = bounds.left;
            wp1.y = bounds.top;
            this.toScreen(wp1,wp2);
            if (wp2.x > 0) { m[4] = pos.x -= wp2.x }
            if (wp2.y > 0) { m[5] = pos.y -= wp2.y }
            wp1.x = bounds.right;
            wp1.y = bounds.bottom;
            this.toScreen(wp1,wp2);
            if (wp2.x < ctx.canvas.width) { m[4] = (pos.x -= wp2.x -  ctx.canvas.width) }
            if (wp2.y < ctx.canvas.height) { m[5] = (pos.y -= wp2.y -  ctx.canvas.height) }
        
        },
        toWorld(from,point = {}){  // convert screen to world coords
            var xx, yy;
            if(dirty){ this.update() }
            xx = from.x - m[4];     
            yy = from.y - m[5];     
            point.x = xx * im[0] + yy * im[2]; 
            point.y = xx * im[1] + yy * im[3];
            return point;
        },        
        toScreen(from,point = {}){  // convert world coords to screen coords
            if(dirty){ this.update() }
            point.x =  from.x * m[0] + from.y * m[2] + m[4]; 
            point.y = from.x * m[1] + from.y * m[3] + m[5];
            return point;
        },        
        scaleAt(at, amount){ // at in screen coords
            if(dirty){ this.update() }
            scale *= amount;
            pos.x = at.x - (at.x - pos.x) * amount;
            pos.y = at.y - (at.y - pos.y) * amount;            
            dirty = true;
        },
        move(x,y){  // move is in screen coords
            pos.x += x;
            pos.y += y;
            dirty = true;
        },
        setContext(context){
            ctx = context;
            dirty = true;
        },
        setBounds(top,left,right,bottom){
            bounds.top = top;
            bounds.left = left;
            bounds.right = right;
            bounds.bottom = bottom;
            useConstraint = true;
            dirty = true;
        }
    };
    return API;
})();
view.setBounds(0,0,canvas2.width,canvas2.height);
view.setContext(context); 



//draw the larger canvas
function draw(){
    view.canvasDefault(); // se default transform to clear screen
		context.imageSmoothingEnabled = false;
    context.fillStyle = "white";
    context.fillRect(0, 0, width, height);
    view.apply();  // set the current view
		context.drawImage(canvas2, 0,0);
    view.canvasDefault();
    if(view.getScale() === view.getMaxScale()){
       context.fillStyle = "black";
       context.strokeStyle = "white";
       context.lineWidth = 2.5;
       context.strokeText("Max scale.",context.canvas.width / 2,24);
       context.fillText("Max scale.",context.canvas.width / 2,24);
    }
    requestAnimationFrame(draw);
    if(mouse.overId === "canvas"){
        canvas.style.cursor = mouse.button ? "none" : "move";
    }else{
        canvas.style.cursor = "default";
    }
}
// add events to document so that mouse is captured when down on canvas
// This allows the mouseup event to be heard no matter where the mouse has
// moved to.
"mousemove,mousedown,mouseup,mousewheel,wheel,DOMMouseScroll".split(",")
    .forEach(eventName=>document.addEventListener(eventName,mouseEvent));

function mouseEvent (event){
    mouse.overId = event.target.id;
    if(event.target.id === "canvas" || mouse.dragging){ // only interested in canvas mouse events including drag event started on the canvas.

        mouse.posLast.x = mouse.pos.x;
        mouse.posLast.y = mouse.pos.y;    
        mouse.pos.x = event.clientX - canvas.offsetLeft;
        mouse.pos.y = event.clientY - canvas.offsetTop;    
        view.toWorld(mouse.pos, mouse.worldPos); // gets the world coords (where on canvas 2 the mouse is)
        if (event.type === "mousemove"){
            if(mouse.button){
                view.move(
                   mouse.pos.x - mouse.posLast.x,
                   mouse.pos.y - mouse.posLast.y
                )
            }
        } else if (event.type === "mousedown") { mouse.button = true; mouse.dragging = true }        
        else if (event.type === "mouseup") { mouse.button = false; mouse.dragging = false }
        else if(event.type === "mousewheel" && (mouse.whichWheel === 1 || mouse.whichWheel === -1)){
            mouse.whichWheel = 1;
            mouse.wheel = event.wheelDelta;
        }else if(event.type === "wheel" && (mouse.whichWheel === 2 || mouse.whichWheel === -1)){
            mouse.whichWheel = 2;
            mouse.wheel = -event.deltaY;
        }else if(event.type === "DOMMouseScroll" && (mouse.whichWheel === 3 || mouse.whichWheel === -1)){
            mouse.whichWheel = 3;
            mouse.wheel = -event.detail;
        }
        if(mouse.wheel !== 0){
            event.preventDefault();
            view.scaleAt(mouse.pos, Math.exp((mouse.wheel / 120) *zoomIntensity));
            mouse.wheel = 0;
        }
    }
}

div { user-select: none;}   /* mouse prevent drag selecting content */
canvas { border:2px solid black;}

<div>
<canvas id="canvas" width="200" height="200"></canvas>
<canvas id="canvas2" width="100" height="100"></canvas>
<p>Mouse wheel to zoom. Mouse click drag to pan.</p>
<p>Zoomed image constrained to canvas</p>
</div>

这篇关于缩放时如何绑定图像平移(HTML Canvas)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆