如何将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(); }
关键修正说明
- 变换顺序匹配:CSS中
translate3d(...)rotate(...)scale(...)的实际执行顺序是从右到左(先缩放,再旋转,最后平移),Canvas变换是顺序执行,因此需要逆序操作才能得到一致效果。 - 基准点统一:以图片自身中心作为变换基准,和CSS transform默认行为一致,避免使用Canvas中心导致的偏移。
- 移除冗余坐标计算:直接使用
current对象记录的平移、缩放、旋转参数,无需通过getBoundingClientRect()获取页面绝对坐标,减少误差。 - 修复Canvas分辨率:设置Canvas实际宽高与显示尺寸一致,避免绘制时拉伸模糊。
额外CSS修复
将#canvas样式中的width:300;改为width:300px;,确保单位正确。
内容的提问来源于stack exchange,提问作者user3146565
相关产品推荐
相关产品推荐

