You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将Canvas图像定位为与重叠同尺寸Div中的图像完全一致

问题描述

我有两个尺寸相同的Div,通过z-index实现重叠,第一个Div包含图片,第二个Div包含Canvas。使用HammerJS库实现了图片的缩放、平移和旋转功能,现在需要将调整到目标位置的图片绘制到Canvas上,以便后续用dataURL方法保存。

尝试了以下代码在Canvas上绘制与Div中位置、尺寸、角度完全一致的图像,但图像位置无法完全匹配,不旋转时绘制正常,问题应该出在drawRotated函数上。


交互控制代码

function getMeta(url){   
     img = new Image();
    var remoteImage = {}
    img.src = url;
    remoteImage.width = img.naturalWidth
    remoteImage.height = img.naturalHeight
    remoteImage.src = url;
    return remoteImage
}

var urlImage = getMeta('https://www.penghu-nsa.gov.tw/FileDownload/Album/Big/20161012162551758864338.jpg')

var text = document.querySelector("#text");
var oImg=document.querySelector("#img_scan");

oImg.style['transition-duration'] = '100ms';
var timeInMs = 0;
var preAngle = 0;
var rotateAngle = 0;
var preRotation = 0;


var originalSize = {
    width : oImg.offsetWidth,
    height : oImg.offsetHeight,
}


var current = {
    x : 0,
    y : 0,
    z : 1,
    angle : 0,
    width: originalSize.width,
    height: originalSize.height,
}

var last = {
    x : 0,
    y : 0,
    z : 1,
}

var oImgRec = oImg.getBoundingClientRect();

var cx = oImgRec.left + oImgRec.width * 0.5;

var cy = oImgRec.top + oImgRec.height * 0.5;

var imageCenter = {
    x:cx,
    y:cy
}

var pinchImageCenter = {}

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

var pinchStart = { x: undefined, y: undefined, isPanend:false}

var panendDeltaFix = {x:0,y:0,isPanend:false}

var pinchZoomOrigin = undefined;

var lastEvent = ''

var hammer = new Hammer(oImg);

hammer.get('pinch').set({enable: true});

hammer.get('pan').set({direction: Hammer.DIRECTION_ALL}).recognizeWith(hammer.get('pinch'));

hammer.on("pinchstart", function(e) {

    last.x = current.x;
    last.y = current.y;
    pinchStart.x = e.center.x;
    pinchStart.y = e.center.y;
    pinchImageCenter = {
        x: imageCenter.x + last.x,
        y: imageCenter.y + last.y
    }

    
    lastEvent = 'pinchstart';

});

hammer.on("pinchmove", function(e) {

    if(preAngle == 0){
        preAngle = Math.round(e.rotation);
        preRotation = Math.round(e.rotation);
    }else{
        if(Math.abs(Math.round(e.rotation)-preRotation)>=300){

            if(e.rotation > 0){
                preAngle+=360;
            }else if(e.rotation < 0){
                preAngle-=360;

            }

        }

        current.angle = rotateAngle + (Math.round(e.rotation)-preAngle);
        preRotation = Math.round(e.rotation);

    }


    var newScale = (last.z * e.scale) >= 0.1 ? (last.z * e.scale) : 0.1;

    var d = scaleCal(e.center, pinchImageCenter, last.z, newScale)

    current.x = d.x + last.x;

    current.y = d.y + last.y;

    current.z = d.z + last.z;

    update();

    lastEvent = 'pinchmove';
});

hammer.on("pinchend", function(e) {
    last.x = current.x;
    last.y = current.y;
    last.z = current.z;
    rotateAngle = current.angle;
    preAngle = 0;
    lastEvent = 'pinchend';

});

hammer.on("panmove", function(e) {
    var panDelta = {
        x:e.deltaX,
        y:e.deltaY
    }


    if (lastEvent !== 'panmove') {

        deltaIssue = {
            x: panDelta.x,
            y: panDelta.y
        }
    }

    current.x = (last.x+panDelta.x-deltaIssue.x);

    current.y = (last.y+panDelta.y-deltaIssue.y);

    lastEvent = 'panmove'
    update();
});

hammer.on("panend", function(e) {
    last.x = current.x;
    last.y = current.y;
    lastEvent = 'panend';
});

hammer.on('tap', function(e) {

    if((Date.now()-timeInMs)<300){

        if(last.z > 1){
            last.z = 1;
            current.z = 1;
            update();
        }else if(last.z <= 1){
            last.z = 2;
            current.z = 2;
            update();
        }
    }

    timeInMs = Date.now();
    lastEvent = 'tap';

});

function scaleCal(eCenter, originCenter, currentScale, newScale) {

    var zoomDistance = newScale - currentScale;

    var x = (originCenter.x - eCenter.x)*(zoomDistance)/currentScale;

    var y = (originCenter.y - eCenter.y)*(zoomDistance)/currentScale;

    var output = {
        x: x,
        y: y,
        z: zoomDistance
    }
    return output
}

function update() {

    current.height = originalSize.height * current.z;

    current.width = originalSize.width * current.z;

    if(current.z < 0.1){
        current.z = 0.1;
    }

    oImg.style.transform = " translate3d(" + current.x + "px, " + current.y + "px, 0)rotate("+current.angle+"deg)scale("+current.z+")"

}

Canvas绘制代码

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

$("#btn").click(copyoncanvas);

function copyoncanvas(){

var bound=objimg.getBoundingClientRect();

var objimg=new Image();

objimg.src="https://www.penghu-nsa.gov.tw/FileDownload/Album/Big/20161012162551758864338.jpg";

ctx.drawImage(objimg,bound.left,bound.top,current.width,current.height);

drawRotated(current.angle,bound.left,bound.top);
}



function drawRotated(degrees,l,t){

const objimg=document.getElementById("img_scan");

ctx.clearRect(0,0,canvas.width,canvas.height);

ctx.save();

var x=canvas.width/2;

var y=canvas.height/2;

ctx.translate(x,y);

ctx.rotate(degrees * Math.PI/180);

ctx.drawImage(objimg,-l,-t,current.width,current.height);

ctx.restore();

}

HTML结构

<div id="container">
        
<div class="box"><canvas id="canvas"></canvas></div>
      
<div id="imgcont">

<img src="https://www.penghu-nsa.gov.tw/FileDownload/Album/Big/20161012162551758864338.jpg" id="img_scan" class="img-custom-img2"/>

</div>
  
</div>

<button id="btn">Copy on canvas</button>

CSS样式

#container{
  position:relative;margin: 0px; 
padding:0px;background:#ff0;
top:0px; overflow:hidden;
width:300px;
border:1px solid #000; height:250}

    #img_scan,.box{
      width:100%; height:100%;      
        position: absolute;
        top: 0;
        left: 0; margin:0; padding:0px
    }

.box{z-index:98;height:250}

#canvas{width:100%;margin:0;
padding:0;
width:300;height:250}

#img_scan{width:300px;height:250px}

#imgcont{width:100%;height:250px;z-index:99}

解决方案

问题核心是Canvas旋转时的坐标转换逻辑错误,原代码未匹配CSS transform的变换顺序与基准点,导致旋转后位置偏移。

修正后的Canvas绘制代码

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
// 复用页面已加载的图片,避免重复请求
var objimg = document.getElementById("img_scan");

// 初始化Canvas实际分辨率,避免拉伸模糊
canvas.width = 300;
canvas.height = 250;

$("#btn").click(copyoncanvas);

function copyoncanvas(){
    ctx.clearRect(0,0,canvas.width,canvas.height);
    // 直接使用current对象记录的变换参数
    drawRotated(current.angle, current.x, current.y, current.z);
}

function drawRotated(degrees, tx, ty, scale){
    ctx.save();
    
    // 复刻CSS transform的变换顺序:scale → rotate → translate(CSS变换从右到左解析)
    // Canvas变换顺序相反,需按translate → rotate → scale执行
    var imgCenterX = originalSize.width / 2;
    var imgCenterY = originalSize.height / 2;

    // 1. 平移到图片变换后的中心位置
    ctx.translate(tx + imgCenterX * scale, ty + imgCenterY * scale);
    // 2. 旋转对应角度
    ctx.rotate(degrees * Math.PI/180);
    // 3. 缩放对应比例
    ctx.scale(scale, scale);
    // 4. 以图片中心为基准,偏移绘制图片左上角
    ctx.drawImage(objimg, -imgCenterX, -imgCenterY);
    
    ctx.restore();
}

关键修正说明

  1. 变换顺序匹配:CSS中translate3d(...)rotate(...)scale(...)的实际执行顺序是从右到左(先缩放,再旋转,最后平移),Canvas变换是顺序执行,因此需要逆序操作才能得到一致效果。
  2. 基准点统一:以图片自身中心作为变换基准,和CSS transform默认行为一致,避免使用Canvas中心导致的偏移。
  3. 移除冗余坐标计算:直接使用current对象记录的平移、缩放、旋转参数,无需通过getBoundingClientRect()获取页面绝对坐标,减少误差。
  4. 修复Canvas分辨率:设置Canvas实际宽高与显示尺寸一致,避免绘制时拉伸模糊。

额外CSS修复

将#canvas样式中的width:300;改为width:300px;,确保单位正确。

内容的提问来源于stack exchange,提问作者user3146565

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 06:32:10