canvas图片旋转扩展出原生JS实现移动端横竖屏手写签名示例
前提知识
canvas是提供了各种各样的接口去控制画布,比如旋转rotate方法。
这里的旋转并不是真的把这个画布旋转了,例如ctx.rotate(90 * Math.PI / 180)顺时针旋转90°了,并不是说我们在页面上就会看到canvas旋转了90°。我们可以理解为其实canvas是有两个部分组成的,一个是肉眼看得到的画布,一个是用于操作的虚拟画布,我们所有在虚拟画布上的动作都会映射到真实画布中去。
旋转画布的原点默认画布的左上角(0,0)点,如图:
总结下,常用画布相关的知识点:
- 1.旋转画布的原点默认画布的左上角(0,0)点,可以使用ctx.translate(x, y)方法移动画布原点;
- 2.ctx.rotate(90 * Math.PI / 180)顺时针旋转90°,ctx.rotate(-90 * Math.PI / 180)逆时针旋转90°;
- 3.context.drawImage方法的使用,如下:
语法一-在画布上定位图像:
context.drawImage(img,x,y);
语法二-在画布上定位图像,并规定图像的宽度和高度:
context.drawImage(img,x,y,width,height);
语法三-剪切图像,并在画布上定位被剪切的部分:
context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height);
参数值:
参数 | 描述 |
---|---|
img | 规定要使用的图像、画布或视频。 |
sx | 可选。开始剪切的 x 坐标位置。 |
sy | 可选。开始剪切的 y 坐标位置。 |
swidth | 可选。被剪切图像的宽度。 |
sheight | 可选。被剪切图像的高度。 |
x | 在画布上放置图像的 x 坐标位置。 |
y | 在画布上放置图像的 y 坐标位置。 |
width | 可选。要使用的图像的宽度。(伸展或缩小图像) |
height | 可选。要使用的图像的高度。(伸展或缩小图像) |
参考地址:《HTML5 canvas drawImage() 方法》。
- 4.会使用canvas的save和restore方法。
save:用来保存Canvas的状态。save之后,可以调用Canvas的平移、放缩、旋转、错切、裁剪等操作。 restore:用来恢复Canvas之前保存的状态。防止save后对Canvas执行的操作对后续的绘制有影响。
示例:
ar canvas = document.getElementById("canvas"); var context = canvas.getContext('2d'); context.lineWidth = "10"; context.strokeStyle = "#fe9901"; context.translate(canvas.width / 2, canvas.height / 2); context.rotate( 30 / 180 * Math.PI); context.beginPath(); context.moveTo(0, -180); context.lineTo(0, -200); context.stroke(); context.rotate( 30 / 180 * Math.PI); context.beginPath(); context.moveTo(0, -140); context.lineTo(0, -160); context.stroke();
没有使用save,restore,第一次绘制时画布旋转30度,然后按照坐标绘制直线,第二次绘制在第一次基础上继续绘制,旋转30度,实际上相对于起点,旋转了60. 所以画出来两条直线不在一条指线上。
context.save(); context.rotate( 30 / 180 * Math.PI); context.beginPath(); context.moveTo(0, -180); context.lineTo(0, -200); context.stroke(); context.restore(); context.rotate( 30 / 180 * Math.PI); context.beginPath(); context.moveTo(0, -140); context.lineTo(0, -160); context.stroke();
第二次绘制加入save和restore, 再第一次绘制后,resore到起始状态,也就是现在画布又回到了0度位置,而不是30度,所以第二次绘制是从0度开始绘制,绘制出来应该和第一次的直线在一条指线上。
canvas移动与旋转示例
首先我们用如下图片绘制到canvas画布中,然后我们要保存旋转后的图片,如图所示。
画布图片:
旋转后保存的图片:
demo代码:
<!DOCTYPE html> <html> <head> <title>canvas移动与旋转</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" /> <style> html,body { padding:0; margin:0; width: 100%; height: 100%; } .canvasparent { width: 100px; height: 200px; margin: 100px auto 20px; border: 1px solid #cccccc; box-sizing: content-box; position: relative; } #canvas { position: absolute; top: 0; left: 0; width: 100px; height: 200px; border-top-left-radius: 30px; } </style> </head> <body> <div class="canvasparent" id="canvasparent"> <canvas id="canvas"></canvas> </div> <img src="" id="img" style="border: 1px solid #ccc"/> <script> var canvas = document.getElementById('canvas'); var ctx = canvas.getContext('2d'); canvas.width = 100; canvas.height = 200; var img = new Image(); img.src = './images/1.jpg'; img.onload = function() { ctx.drawImage(img,0,0,100,200); var imgSRC = canvas.toDataURL('image/png', 1); var img2 = new Image(); img2.src = imgSRC; img2.onload = function() { var clipCanvas = document.createElement("canvas"); var ctx2 = clipCanvas.getContext('2d'); clipCanvas.width = 200; clipCanvas.height = 100; ctx2.translate(0,100); ctx2.rotate(-90 * Math.PI / 180); ctx2.drawImage(img2,0,0,100,200); var imgSRC2 = clipCanvas.toDataURL('image/png', 1); document.getElementById('img').src = imgSRC2; } } </script> </html>
当然上面使用了translate和rotate,我们只有rotate和drawImage也是可以的,修改的JS代码如下:
var canvas = document.getElementById('canvas'); var ctx = canvas.getContext('2d'); canvas.width = 100; canvas.height = 200; var img = new Image(); img.src = './images/1.jpg'; img.onload = function() { ctx.drawImage(img,0,0,100,200); var imgSRC = canvas.toDataURL('image/png', 1); var img2 = new Image(); img2.src = imgSRC; img2.onload = function() { var clipCanvas = document.createElement("canvas"); var ctx2 = clipCanvas.getContext('2d'); clipCanvas.width = 200; clipCanvas.height = 100; ctx2.rotate(-90 * Math.PI / 180); ctx2.drawImage(img2,-100,0,100,200); var imgSRC2 = clipCanvas.toDataURL('image/png', 1); document.getElementById('img').src = imgSRC2; } }
手写签名demo
html代码:
<body> <div class="app-container" style="padding-top: 100px;"> <button id="signature-button">开始手写签名</button> <div class="signature-result__img"> <img src="" id="signature-img" alt="手写签名图片预览"/> </div> </div> <div id="signature-pop"> <div id="signature-container"> <div class="demo-top-info" id="rotate-container"> <div class="return" id="return">返回</div> <div class="signature-title text-center">手写签名</div> </div> <div class="canvas-container"> <div class="canvas-parent"> <canvas id="canvas"></canvas> </div> </div> <div class="demo-bottom-info"> <div id="clean_canvas">清空</div> <div id="sure_canvas">确认</div> </div> </div> <canvas id="canvas2"></canvas> </div> </body>
CSS代码:
html,body { padding:0; margin:0; width: 100%; height: 100%; } .text-center { text-align: center; } #signature-button { display: block; margin: 0 auto; } #signature-img { width: 200px; height: 100px; margin: 10px auto; display: none; } #signature-pop { position: fixed; background-color: #fff; top:0; left:0; bottom:0; right:0; width: 100%; height: 100%; display: none; } #signature-container { width: 100%; height: 100%; } .demo-top-info { position: absolute; top: 0; left: 0; width: 100%; height: 40px; background: #cccccc; box-sizing: border-box; text-align: center; } .demo-top-info .return { padding-left: 5px; height: 40px; line-height: 40px; position: absolute; left: 10px; display: inline-block; } .signature-title { height: 40px; line-height: 40px; display: inline-block; } .canvas-container { width: 100%; height: 100%; background-color: #ffffff; box-sizing: border-box; padding: 40px 0 40px 0; } .canvas-parent { width: 100%; height: 100%; } .demo-bottom-info { position: absolute; bottom:0; left:0; width: 100%; height: 40px; background: #cccccc; padding: 0 2%; box-sizing: border-box; overflow: hidden; } #clean_canvas { float: left; font-size: 18px; margin-top: 2px; background: #f8f8f8; border: 1px solid #ddd; padding: 5px 10px; border-radius: 4px; } #sure_canvas { float: right; padding: 3px 0; background: #00005F; padding: 5px 10px; border: 1px solid #00005F; border-radius: 4px; color: #fff; margin-top: 3px; } #canvas2 { position: absolute; top: 0; left:0; z-index: 999; margin-left: 40px; }
JS代码:
// 手绘签名类 var SignatureClass = (function(){ //获取当前位置(返回px) function getStyle(obj, attr) { if (obj.currentStyle) { return obj.currentStyle[attr]; } else { return getComputedStyle(obj, false)[attr]; } } function Signatrue(options) { this.cavnas = null; this.ctx = null; this.maxparams = {}; this.minparams = {}; this.entershuping = true; //是否是竖屏 true=是 this.firstTouch = true; // 第一次点击或touch this.allowSubmit = false; // 是否手写了内容 是=才能提交 否=不能提交 if(options === void 0) { options = {}; } this.options = options; } // 初始化 Signatrue.prototype.init = function() { var self = this; console.log(getStyle(document.getElementById("rotate-container"), "height")); // 不知道为什么加,否则有些Apple出问题 this.hengshuping(); this.clear(); this.submit(); window.addEventListener('resize',function() { self.hengshuping(); },false); } // 判断横竖屏 Signatrue.prototype.hengshuping = function() { if (window.orientation == 90 || window.orientation == -90) { this.entershuping = false; // 横屏 document.getElementById("canvas2").style.display = "none"; document.getElementById("canvas").style.display = "block"; this.canvas = document.getElementById("canvas"); this.ctx = this.canvas.getContext("2d"); this.ctx.lineWidth = 1; this.ctx.strokeStyle = "#000000"; this.drawEvent(); document.querySelector("#signature-container").style.width = window.innerWidth + "px"; document.querySelector("#signature-container").style.height = window.innerHeight + "px"; this.canvas.height = parseInt(getStyle(document.getElementById("canvas").parentNode, 'height')); this.canvas.width = parseInt(getStyle(document.getElementById("canvas").parentNode, 'width')); document.querySelector("#signature-container").style.transform = "rotate(0deg)"; document.querySelector("#signature-container").style.transformOrigin = "0 0"; document.querySelector("#signature-container").style.marginLeft = "0px"; } else { // 竖屏 this.entershuping = true; document.getElementById("canvas").style.display = "none"; document.getElementById("canvas2").style.display = "block"; this.canvas = document.getElementById("canvas2"); this.ctx = this.canvas.getContext("2d"); this.canvas.height = window.innerHeight; this.canvas.width = window.innerWidth - 40 - 40; this.ctx.lineWidth = 1; this.ctx.strokeStyle = "#000000"; this.drawEvent(); document.querySelector("#signature-container").style.width = window.innerHeight + "px"; document.querySelector("#signature-container").style.height = window.innerWidth + "px"; document.querySelector("#signature-container").style.transform = "rotate(90deg)"; document.querySelector("#signature-container").style.transformOrigin = "0 0"; document.querySelector("#signature-container").style.marginLeft = window.innerWidth + "px"; } } // 绘画事件 Signatrue.prototype.drawEvent = function() { var self = this; this.canvas.addEventListener("touchstart", function(evt) { var oEvent = evt || event; oEvent.preventDefault(); var position = self.pos(oEvent); self.ctx.beginPath(); self.ctx.moveTo(position.x, position.y); if (self.firstTouch) { self.minparams = { x: position.x, y: position.y }; self.maxparams = { x: position.x, y: position.y }; self.firstTouch = false; } else { self.judgeSize(position); } self.canvas.addEventListener("touchmove", touchmove, false) function touchmove(evt) { var oEvent = evt || event; oEvent.preventDefault(); var position = self.pos(oEvent); self.ctx.lineTo(position.x, position.y); self.judgeSize(position); self.ctx.stroke(); } document.addEventListener("touchend", touchend, false) function touchend(evt) { var oEvent = evt || event; oEvent.preventDefault(); self.allowSubmit = true; self.canvas.removeEventListener("touchmove", touchmove); document.removeEventListener("touchend", touchend); } }) } // 清空画笔 Signatrue.prototype.clear = function() { var self = this; document.getElementById("clean_canvas").addEventListener("click", function() { self.ctx.clearRect(0, 0, self.canvas.width, self.canvas.height); self.allowSubmit = false; }) } // 提交画笔内容 Signatrue.prototype.submit = function() { var self = this; document.getElementById("sure_canvas").addEventListener("click", function() { if (self.allowSubmit) { var imgSRC = self.canvas.toDataURL('image/png', 1); var img = new Image(); img.src = imgSRC; self.ctx.clearRect(0, 0, self.canvas.width, self.canvas.height); img.onload = function() { var width = self.maxparams.x - self.minparams.x; var height = self.maxparams.y - self.minparams.y; var targetWidth = 200; var targetHeight = 100; var clipCanvas = document.createElement("canvas"); clipCanvas.width = targetWidth; clipCanvas.height = targetHeight; var ctx2 = clipCanvas.getContext("2d"); if (self.entershuping) { ctx2.translate(0, targetHeight); ctx2.rotate(-90 * Math.PI / 180); ctx2.drawImage(img, self.minparams.x, self.minparams.y, width, height, 0, 0, targetHeight, targetWidth); } else { ctx2.drawImage(img, self.minparams.x, self.minparams.y, width, height, 0, 0, targetWidth, targetHeight); } var imgSRC2 = clipCanvas.toDataURL('image/png', 1); if(self.options && self.options.callback && typeof self.options.callback == 'function') { self.options.callback(imgSRC2); } self.canvas = null; self.ctx = null; ctx2 = null; clipCanvas = null; } } else { alert("没有手写签名,不能提交!") } }) } // 获取当前位置 Signatrue.prototype.pos = function(event) { var x = event.touches[0].pageX - event.target.offsetLeft; var y = event.touches[0].pageY - event.target.offsetTop; return { x: x, y: y }; } // 优化画笔界限,去掉空白区域 Signatrue.prototype.judgeSize = function(value) { if (this.minparams.x > value.x) { this.minparams.x = value.x; } if (this.maxparams.x < value.x) { this.maxparams.x = value.x; } if (this.minparams.y > value.y) { this.minparams.y = value.y; } if (this.maxparams.y < value.y) { this.maxparams.y = value.y; } } return Signatrue; })(); // 点击去手写签名-展示手写签名弹框 document.getElementById('signature-button').addEventListener('click',function(){ document.getElementById('signature-pop').style.display = 'block'; var signatrueDraw = new SignatureClass({ callback: function(base64) { document.getElementById('signature-img').src = base64; document.getElementById('signature-pop').style.display = 'none'; document.getElementById('signature-img').style.display = 'block'; } }); signatrueDraw.init(); },false); // 点击弹框返回 document.getElementById('return').addEventListener('click',function(){ document.getElementById('signature-pop').style.display = 'none'; },false);
完整的demo代码:
<!DOCTYPE html> <html> <head> <title>手写签名demo</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" /> <style> html,body { padding:0; margin:0; width: 100%; height: 100%; } .text-center { text-align: center; } #signature-button { display: block; margin: 0 auto; } #signature-img { width: 200px; height: 100px; margin: 10px auto; display: none; } #signature-pop { position: fixed; background-color: #fff; top:0; left:0; bottom:0; right:0; width: 100%; height: 100%; display: none; } #signature-container { width: 100%; height: 100%; } .demo-top-info { position: absolute; top: 0; left: 0; width: 100%; height: 40px; background: #cccccc; box-sizing: border-box; text-align: center; } .demo-top-info .return { padding-left: 5px; height: 40px; line-height: 40px; position: absolute; left: 10px; display: inline-block; } .signature-title { height: 40px; line-height: 40px; display: inline-block; } .canvas-container { width: 100%; height: 100%; background-color: #ffffff; box-sizing: border-box; padding: 40px 0 40px 0; } .canvas-parent { width: 100%; height: 100%; } .demo-bottom-info { position: absolute; bottom:0; left:0; width: 100%; height: 40px; background: #cccccc; padding: 0 2%; box-sizing: border-box; overflow: hidden; } #clean_canvas { float: left; font-size: 18px; margin-top: 2px; background: #f8f8f8; border: 1px solid #ddd; padding: 5px 10px; border-radius: 4px; } #sure_canvas { float: right; padding: 3px 0; background: #00005F; padding: 5px 10px; border: 1px solid #00005F; border-radius: 4px; color: #fff; margin-top: 3px; } #canvas2 { position: absolute; top: 0; left:0; z-index: 999; margin-left: 40px; } </style> </head> <body> <div class="app-container" style="padding-top: 100px;"> <button id="signature-button">开始手写签名</button> <div class="signature-result__img"> <img src="" id="signature-img" alt="手写签名图片预览"/> </div> </div> <div id="signature-pop"> <div id="signature-container"> <div class="demo-top-info" id="rotate-container"> <div class="return" id="return">返回</div> <div class="signature-title text-center">手写签名</div> </div> <div class="canvas-container"> <div class="canvas-parent"> <canvas id="canvas"></canvas> </div> </div> <div class="demo-bottom-info"> <div id="clean_canvas">清空</div> <div id="sure_canvas">确认</div> </div> </div> <canvas id="canvas2"></canvas> </div> </body> <script type="text/javascript"> // 手绘签名类 var SignatureClass = (function(){ //获取当前位置(返回px) function getStyle(obj, attr) { if (obj.currentStyle) { return obj.currentStyle[attr]; } else { return getComputedStyle(obj, false)[attr]; } } function Signatrue(options) { this.cavnas = null; this.ctx = null; this.maxparams = {}; this.minparams = {}; this.entershuping = true; //是否是竖屏 true=是 this.firstTouch = true; // 第一次点击或touch this.allowSubmit = false; // 是否手写了内容 是=才能提交 否=不能提交 if(options === void 0) { options = {}; } this.options = options; } // 初始化 Signatrue.prototype.init = function() { var self = this; console.log(getStyle(document.getElementById("rotate-container"), "height")); // 不知道为什么加,否则有些Apple出问题 this.hengshuping(); this.clear(); this.submit(); window.addEventListener('resize',function() { self.hengshuping(); },false); } // 判断横竖屏 Signatrue.prototype.hengshuping = function() { if (window.orientation == 90 || window.orientation == -90) { this.entershuping = false; // 横屏 document.getElementById("canvas2").style.display = "none"; document.getElementById("canvas").style.display = "block"; this.canvas = document.getElementById("canvas"); this.ctx = this.canvas.getContext("2d"); this.ctx.lineWidth = 1; this.ctx.strokeStyle = "#000000"; this.drawEvent(); document.querySelector("#signature-container").style.width = window.innerWidth + "px"; document.querySelector("#signature-container").style.height = window.innerHeight + "px"; this.canvas.height = parseInt(getStyle(document.getElementById("canvas").parentNode, 'height')); this.canvas.width = parseInt(getStyle(document.getElementById("canvas").parentNode, 'width')); document.querySelector("#signature-container").style.transform = "rotate(0deg)"; document.querySelector("#signature-container").style.transformOrigin = "0 0"; document.querySelector("#signature-container").style.marginLeft = "0px"; } else { // 竖屏 this.entershuping = true; document.getElementById("canvas").style.display = "none"; document.getElementById("canvas2").style.display = "block"; this.canvas = document.getElementById("canvas2"); this.ctx = this.canvas.getContext("2d"); this.canvas.height = window.innerHeight; this.canvas.width = window.innerWidth - 40 - 40; this.ctx.lineWidth = 1; this.ctx.strokeStyle = "#000000"; this.drawEvent(); document.querySelector("#signature-container").style.width = window.innerHeight + "px"; document.querySelector("#signature-container").style.height = window.innerWidth + "px"; document.querySelector("#signature-container").style.transform = "rotate(90deg)"; document.querySelector("#signature-container").style.transformOrigin = "0 0"; document.querySelector("#signature-container").style.marginLeft = window.innerWidth + "px"; } } // 绘画事件 Signatrue.prototype.drawEvent = function() { var self = this; this.canvas.addEventListener("touchstart", function(evt) { var oEvent = evt || event; oEvent.preventDefault(); var position = self.pos(oEvent); self.ctx.beginPath(); self.ctx.moveTo(position.x, position.y); if (self.firstTouch) { self.minparams = { x: position.x, y: position.y }; self.maxparams = { x: position.x, y: position.y }; self.firstTouch = false; } else { self.judgeSize(position); } self.canvas.addEventListener("touchmove", touchmove, false) function touchmove(evt) { var oEvent = evt || event; oEvent.preventDefault(); var position = self.pos(oEvent); self.ctx.lineTo(position.x, position.y); self.judgeSize(position); self.ctx.stroke(); } document.addEventListener("touchend", touchend, false) function touchend(evt) { var oEvent = evt || event; oEvent.preventDefault(); self.allowSubmit = true; self.canvas.removeEventListener("touchmove", touchmove); document.removeEventListener("touchend", touchend); } }) } // 清空画笔 Signatrue.prototype.clear = function() { var self = this; document.getElementById("clean_canvas").addEventListener("click", function() { self.ctx.clearRect(0, 0, self.canvas.width, self.canvas.height); self.allowSubmit = false; }) } // 提交画笔内容 Signatrue.prototype.submit = function() { var self = this; document.getElementById("sure_canvas").addEventListener("click", function() { if (self.allowSubmit) { var imgSRC = self.canvas.toDataURL('image/png', 1); var img = new Image(); img.src = imgSRC; self.ctx.clearRect(0, 0, self.canvas.width, self.canvas.height); img.onload = function() { var width = self.maxparams.x - self.minparams.x; var height = self.maxparams.y - self.minparams.y; var targetWidth = 200; var targetHeight = 100; var clipCanvas = document.createElement("canvas"); clipCanvas.width = targetWidth; clipCanvas.height = targetHeight; var ctx2 = clipCanvas.getContext("2d"); if (self.entershuping) { ctx2.translate(0, targetHeight); ctx2.rotate(-90 * Math.PI / 180); ctx2.drawImage(img, self.minparams.x, self.minparams.y, width, height, 0, 0, targetHeight, targetWidth); } else { ctx2.drawImage(img, self.minparams.x, self.minparams.y, width, height, 0, 0, targetWidth, targetHeight); } var imgSRC2 = clipCanvas.toDataURL('image/png', 1); if(self.options && self.options.callback && typeof self.options.callback == 'function') { self.options.callback(imgSRC2); } self.canvas = null; self.ctx = null; ctx2 = null; clipCanvas = null; } } else { alert("没有手写签名,不能提交!") } }) } // 获取当前位置 Signatrue.prototype.pos = function(event) { var x = event.touches[0].pageX - event.target.offsetLeft; var y = event.touches[0].pageY - event.target.offsetTop; return { x: x, y: y }; } // 优化画笔界限,去掉空白区域 Signatrue.prototype.judgeSize = function(value) { if (this.minparams.x > value.x) { this.minparams.x = value.x; } if (this.maxparams.x < value.x) { this.maxparams.x = value.x; } if (this.minparams.y > value.y) { this.minparams.y = value.y; } if (this.maxparams.y < value.y) { this.maxparams.y = value.y; } } return Signatrue; })(); // 点击去手写签名-展示手写签名弹框 document.getElementById('signature-button').addEventListener('click',function(){ document.getElementById('signature-pop').style.display = 'block'; var signatrueDraw = new SignatureClass({ callback: function(base64) { document.getElementById('signature-img').src = base64; document.getElementById('signature-pop').style.display = 'none'; document.getElementById('signature-img').style.display = 'block'; } }); signatrueDraw.init(); },false); // 点击弹框返回 document.getElementById('return').addEventListener('click',function(){ document.getElementById('signature-pop').style.display = 'none'; },false); </script> </html>