Canvas加载可缩放平移图片后保存画布内容及本地文件选择实现问题
问题原因
你调用toDataURL报错确实是跨源资源导致的:当Canvas加载了未配置跨域许可的外域图片后,会被浏览器标记为「污染画布」,禁止调用导出类API,这是浏览器的默认安全限制。
实现方案
以下是同时满足两个需求的完整可运行修改版代码:
修改后的HTML代码
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <p>演示如何使用HTML5 Canvas Context的变换方法实现选择性缩放:拖拽平移,点击放大对应位置,Shift+点击缩小对应位置,鼠标滚轮控制对应位置缩放。</p> <!-- 新增本地图片选择控件 --> <input type="file" id="imgUpload" accept="image/*"> <canvas></canvas> <button onclick="saveImg()">SAVE IMG</button>
修改后的CSS代码
body { background:#eee; margin:1em; text-align:center; } canvas { border-radius:25%;display:block; margin:1em auto; background:#fff; border:1px solid #ccc } #imgUpload { margin: 10px 0; }
修改后的JavaScript代码
var canvas = document.getElementsByTagName('canvas')[0]; canvas.width = 200; canvas.height = 200; var ctx = canvas.getContext('2d'); var gkhead = new Image; // 图片加载状态标记 var imgLoaded = false; // 保存图片逻辑 function saveImg() { if (!imgLoaded) { alert('请先上传图片再保存'); return; } try { var fullQuality = canvas.toDataURL('image/jpeg', 1.0); // 自动触发下载 var link = document.createElement('a'); link.download = '调整后图片.jpg'; link.href = fullQuality; link.click(); } catch (e) { alert('保存失败,请使用本地上传的图片操作'); } } // 本地图片读取逻辑 document.getElementById('imgUpload').addEventListener('change', function(e) { var file = e.target.files[0]; if (!file) return; var reader = new FileReader(); reader.onload = function(evt) { gkhead.src = evt.target.result; } reader.readAsDataURL(file); }) // 图片加载完成后初始化交互逻辑 gkhead.onload = function() { imgLoaded = true; trackTransforms(ctx); redraw(); var lastX=canvas.width/2, lastY=canvas.height/2; var dragStart,dragged; canvas.addEventListener('mousedown',function(evt){ document.body.style.mozUserSelect = document.body.style.webkitUserSelect = document.body.style.userSelect = 'none'; lastX = evt.offsetX || (evt.pageX - canvas.offsetLeft); lastY = evt.offsetY || (evt.pageY - canvas.offsetTop); dragStart = ctx.transformedPoint(lastX,lastY); dragged = false; },false); canvas.addEventListener('mousemove',function(evt){ lastX = evt.offsetX || (evt.pageX - canvas.offsetLeft); lastY = evt.offsetY || (evt.pageY - canvas.offsetTop); dragged = true; if (dragStart){ var pt = ctx.transformedPoint(lastX,lastY); ctx.translate(pt.x-dragStart.x,pt.y-dragStart.y); redraw(); } },false); canvas.addEventListener('mouseup',function(evt){ dragStart = null; if (!dragged) zoom(evt.shiftKey ? -1 : 1 ); },false); var scaleFactor = 1.1; var zoom = function(clicks){ var pt = ctx.transformedPoint(lastX,lastY); ctx.translate(pt.x,pt.y); var factor = Math.pow(scaleFactor,clicks); ctx.scale(factor,factor); ctx.translate(-pt.x,-pt.y); redraw(); } var handleScroll = function(evt){ var delta = evt.wheelDelta ? evt.wheelDelta/40 : evt.detail ? -evt.detail : 0; if (delta) zoom(delta); return evt.preventDefault() && false; }; canvas.addEventListener('DOMMouseScroll',handleScroll,false); canvas.addEventListener('mousewheel',handleScroll,false); } function redraw(){ if (!imgLoaded) return; // 清空画布 var p1 = ctx.transformedPoint(0,0); var p2 = ctx.transformedPoint(canvas.width,canvas.height); ctx.clearRect(p1.x,p1.y,p2.x-p1.x,p2.y-p1.y); ctx.save(); ctx.setTransform(1,0,0,1,0,0); ctx.clearRect(0,0,canvas.width,canvas.height); ctx.restore(); ctx.drawImage(gkhead,0,0); } // 坐标变换工具方法 function trackTransforms(ctx){ var svg = document.createElementNS("http://www.w3.org/2000/svg",'svg'); var xform = svg.createSVGMatrix(); ctx.getTransform = function(){ return xform; }; var savedTransforms = []; var save = ctx.save; ctx.save = function(){ savedTransforms.push(xform.translate(0,0)); return save.call(ctx); }; var restore = ctx.restore; ctx.restore = function(){ xform = savedTransforms.pop(); return restore.call(ctx); }; var scale = ctx.scale; ctx.scale = function(sx,sy){ xform = xform.scaleNonUniform(sx,sy); return scale.call(ctx,sx,sy); }; var rotate = ctx.rotate; ctx.rotate = function(radians){ xform = xform.rotate(radians*180/Math.PI); return rotate.call(ctx,radians); }; var translate = ctx.translate; ctx.translate = function(dx,dy){ xform = xform.translate(dx,dy); return translate.call(ctx,dx,dy); }; var transform = ctx.transform; ctx.transform = function(a,b,c,d,e,f){ var m2 = svg.createSVGMatrix(); m2.a=a; m2.b=b; m2.c=c; m2.d=d; m2.e=e; m2.f=f; xform = xform.multiply(m2); return transform.call(ctx,a,b,c,d,e,f); }; var setTransform = ctx.setTransform; ctx.setTransform = function(a,b,c,d,e,f){ xform.a = a; xform.b = b; xform.c = c; xform.d = d; xform.e = e; xform.f = f; return setTransform.call(ctx,a,b,c,d,e,f); }; var pt = svg.createSVGPoint(); ctx.transformedPoint = function(x,y){ pt.x=x; pt.y=y; return pt.matrixTransform(xform.inverse()); } }
核心修改说明
- 新增本地图片上传能力,通过
FileReader读取本地图片为base64格式加载,完全规避跨域问题,无需写死图片路径 - 优化保存逻辑,导出成功后自动触发浏览器下载,增加异常捕获和状态判断,避免无效操作报错
- 调整初始化逻辑,仅在图片加载完成后绑定交互事件,避免空操作报错
内容的提问来源于stack exchange,提问作者JulesUK
相关产品推荐
相关产品推荐

