实现Canvas2D绘图 使元素绕中心居中旋转
我之前用canvas写了个头像剪切的demo,但是关于让载入的图片旋转是个问题,虽然通过其它方法实现了,但是感觉并不太好,于是查了些资料,想试着重新做一下canvas的旋转。
在开始之前,先让我们来做一些准备工作:
1 (function () { 2 // 设置画布的宽高 3 var width = 300, 4 heigh = 100, 5 cache = {}; // 存储canvas上下文 6 7 // 获取绘图上下文 8 function getCtx(name, w, h) { 9 var cv = document.getElementById(name), 10 ctx = cv.getContext('2d'), 11 wh = getWH(w, h); 12 13 w = wh[0]; 14 h = wh[1]; 15 16 cv.width = w; 17 cv.height = h; 18 19 ctx && (cache['name'] = ctx); 20 init(ctx); 21 return ctx; 22 } 23 24 // 设置角度 25 function (ctx, deg) { 26 ctx.rotate(deg / 180 * Math.PI); // 转成角度值 27 } 28 29 // 填充画布 30 function fill(ctx, color, arr) { 31 ctx.fillStyle = color; 32 ctx.fillRect(arr[0], arr[1], arr[2], arr[3]); 33 } 34 35 // 格式化画布 36 function init(ctx, w, h) { 37 var color = '#333', // 填充背景色 38 wh = getWH(w, h); 39 40 w = wh[0]; 41 h = wh[1]; 42 43 fill(ctx, color, [0, 0, w, h]); 44 } 45 46 // 进行位移 47 function translate(ctx, x, y) { 48 ctx.translate(x, y); 49 } 50 51 function getWH(w, h) { 52 w = w || width; 53 h = h || height; 54 return [w, h]; 55 } 56 57 })();
准备完毕,先来绘制一个简单的矩形
1 // d1 2 var cv1 = getCtx('cv1'); 3 fill(cv1, '#fff', [125, 25, 50, 50]);
然后,我们试着让它旋转10deg
1 // d2 2 var cv2 = getCtx('cv2'); 3 rotate(cv2, 10); 4 fill(cv2, '#fff', [125, 25, 50, 50]);
再看看旋转30deg会变成什么样
1 // d3 2 var cv3 = getCtx('cv3'); 3 rotate(cv3, 30); 4 fill(cv3, '#fff', [125, 25, 50, 50]);
现在已经可以看出了,canvas旋转rotate
是以画布左上角为中心点旋转的,由此我们可以想象得到90deg的样子
(图片已死)
1 .box2 { 2 margin: 0 auto; 3 width: 300px; 4 line-height: 100px; 5 background: #333; 6 text-align: center; 7 color: #fff; 8 } 9 10 .box3 { 11 margin: 0 auto; 12 width: 300px; 13 line-height: 100px; 14 background: #666; 15 text-align: center; 16 color: #fff; 17 transform: rotate(90deg) translate(0, 200px); 18 }
因此,就像css3通过transform-origin来修改旋转的中心一样的道理,我们使用translate为canvas修改旋转中心即可 ctx.translate(canvas.width / 2, canvas.height / 2);
使左上角偏移到宽高的一半的位置(中点)
1 //d4 2 var cv4 = getCtx('cv4'); 3 translate(cv4, width / 2, height / 2); 4 fill(cv4, '#fff', [0, 0, width, height]);
那么现在再一次旋转90deg会得到我们想要的效果吗?
1 //d5 2 var cv5 = getCtx('cv5'); 3 translate(cv5, width / 2, height / 2); 4 rotate(cv5, 90); 5 fill(cv5, '#fff', [0, 0, width, height]);
事实证明,还不行,但是已经靠近了,从现在看来只要再偏移一次回到原来的点就可以了就可以了
1 //d6 2 var cv6 = getCtx('cv6'); 3 translate(cv6, width / 2, height / 2); 4 rotate(cv6, 90); 5 translate(cv6, -width / 2, -height / 2); 6 fill(cv6, '#fff', [0, 0, width, height]);
至于为什么会这样,请看下图:
或者猛戳这里看示例!!
所以现在实现了围绕中心旋转,而实现元素居中就简单了,正如以上的示例所展示的,正中的正方形已然居中,因为我在一开始就给它定好了刚好居中的开始坐标:
1 fill(cv3, '#fff', [125, 25, 50, 50]);
就好像position居中定位一样,这里的居中定位也一样计算:
1 (默认宽高为300 * 100) 2 (width / 2) - (50 / 2) = 125; 3 (height / 2) - (50 / 2) = 25;
我们把旋转和居中这些来封装一下,方便使用,代码如下:
1 RotateCenter.prototype = { 2 constructor: RotateCenter, 3 4 init: function (id, w, h) { 5 this.width = w = w || this.width; 6 this.height = h = h || this.height; 7 8 var canvas = this.getContext(id, '2d'); 9 10 // 设置宽高 11 this.setSize(canvas, w, h); 12 }, 13 14 // 获取上下文 15 getContext: function (id, type) { 16 var canvas = document.getElementById(id), 17 nowCtx = canvas.getContext(type); 18 19 this.cache[id] = nowCtx; 20 return canvas; 21 }, 22 23 // 填充画布 24 fill: function (arr, color) { 25 this.nowCtx.fillStyle = color; 26 this.nowCtx.fillRect(arr[0], arr[1], arr[1] ? arr[1] : this.width, arr[2] ? arr[2] : this.height); 27 }, 28 29 setSize: function (c, w, h) { 30 c.width = w; 31 c.height = h; 32 }, 33 34 // 旋转 35 rotate: function (deg) { 36 this.nowCtx.rotate(deg / 180 * Math.PI); 37 }, 38 39 // 位移 40 translate: function (x, y) { 41 this.nowCtx.translate(x, y); 42 }, 43 44 // 切换上下文 45 checkout: function (id) { 46 this.nowCtx = this.cache[id]; 47 }, 48 49 // 绘制不居中绕中心旋转矩形 50 rotateRect: function (arr, color, deg) { 51 var w = this.width / 2, 52 h = this.height / 2; 53 54 this.translate(w, h); 55 this.rotate(deg); 56 this.translate(-w, -h); 57 this.fill(arr, color); 58 }, 59 60 // 绘制居中不绕中心旋转矩形 61 centerRect: function (width, height, color) { 62 var w = this.width / 2, 63 h = this.height / 2, 64 w1 = width / 2, 65 h1 = height / 2; 66 67 this.fill([w - w1, h - h1, width, height], color); 68 }, 69 70 // 绘制居中同时绕中心旋转矩形 71 centerRotateRect: function (width, height, deg, color) { 72 var w = this.width / 2, 73 h = this.height / 2, 74 w1 = width / 2, 75 h1 = height / 2; 76 77 this.translate(w, h); 78 this.rotate(deg); 79 this.translate(-w, -h); 80 this.fill([w - w1, h - h1, width, height], color); 81 } 82 };
现在来测试一下:
绘制居中同时绕中心旋转矩形
45deg
1 // d7 2 var rc = new RotateCenter(); 3 rc.init('cv7'); 4 rc.centerRotateRect(50, 50, 45, '#fff');
163deg
1 // d8 2 rc.init('cv8'); 3 rc.centerRotateRect(50, 50, 163, '#fff');
绘制居中不绕中心旋转矩形
1 // d9 2 rc.init('cv9'); 3 rc.centerRect(60, 60, '#fff');
绘制不居中绕中心旋转矩形
30deg
278deg
1 // d10 2 rc.init('cv10'); 3 rc.rotateRect([50, 50, 50, 50], '#fff', 30); 4 5 // d11 6 rc.init('cv11'); 7 rc.rotateRect([50, 50, 50, 50], '#fff', 278);
从示例来看,rotateRect方法有点不太理想,而在这里想要的就是centerRotateRect方法的效果,所以到此OVER。
如有不正确的地方,欢迎指出!!!
/******************************************************** 优美的分割线 ********************************************************/
/************************************************ 更新时间:2019-01-24 ************************************************/
之前写的内容只能实现中心旋转,而由于在前阵子写的一个关于canvas的封装中又需要实现旋转,而且是任意位置/任意角度的随意旋转,又找了不少资料和测试才实现了,所以在这里更新一下这篇文章!!!
先看效果:
这次的实现主要修改了一下需要绘制的每个元素的偏移,还有为了实现多个元素的不同角度旋转,调用了save与restore这两个函数
主要代码如下:
ctx.save(); ctx.beginPath(); ctx.fillStyle = '#fff'; ctx.translate(x + (width / 2), y + (height) / 2); ctx.rotate(deg * Math.PI / 180); ctx.translate(-x - (width / 2), -y -(height / 2)); ctx.fillRect(x, y, width, height); ctx.closePath(); ctx.fill(); ctx.restore();
完整代码:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> <script type="text/javascript" src="./1.js"></script> <style type="text/css"> canvas { background: #333; } strong { color: #f00; } </style> </head> <body> <h1>Canvas Rotate</h1> <h3>#1 <strong>不居中旋转30deg</strong></h3> <canvas id="cv"></canvas> <h3>#2 <strong>不居中旋转60deg</strong></h3> <canvas id="cv1"></canvas> <h3>#3 <strong>不居中绘制多个, 旋转45deg, 160deg</strong></h3> <canvas id="cv2"></canvas> <h3>#4 <strong>自定义旋转</strong></h3> <p> <span>deg:</span> <input type="range" id="range" max="360" min="0" value="45"> <span id="nowDeg">45deg</span> </p> <canvas id="cv3"></canvas> <script type="text/javascript"> { function Rotate(id, x, y, width, height, deg) { if (!Rotate.initer) { Rotate.initer = new Rotate.init(); Rotate.initer.fill(id, x, y, width, height, deg); } Rotate.initer.fill(id, x, y, width, height, deg); return Rotate.initer; } Rotate.init = function () { this.defaultWidth = 300; this.defaultHeight = 200; this.ctx = null; }; Rotate.prototype = { constrcutor: Rotate, fill: function (id, x, y, width, height, deg, add) { !add && this.getCanvas(id); let ctx = this.getContext(); ctx.save(); ctx.beginPath(); ctx.fillStyle = '#fff'; ctx.translate(x + (width / 2), y + (height) / 2); ctx.rotate(deg * Math.PI / 180); ctx.translate(-x - (width / 2), -y -(height / 2)); ctx.fillRect(x, y, width, height); ctx.closePath(); ctx.fill(); ctx.restore(); }, addFill: function (x, y, width, height, deg) { this.fill(null, x, y, width, height, deg, true); }, getCanvas: function (id) { this.canvas = document.getElementById(id); this.canvas.width = this.defaultWidth; this.canvas.height = this.defaultHeight; }, getContext: function () { this.ctx = this.canvas.getContext('2d'); return this.ctx; }, clear: function () { this.ctx.clearRect(0, 0, this.defaultWidth, this.defaultHeight); } }; Rotate.init.prototype = Rotate.prototype; Rotate('cv', 50, 50, 50, 50, 30); // 不居中旋转30deg Rotate('cv1', 50, 50, 50, 50, 60); // 不居中旋转60deg Rotate('cv2', 50, 50, 50, 50, 45).addFill(150, 50, 50, 50, 160); // 不居中绘制多个, 旋转45deg, 160deg /* * 自定义旋转 */ let nowDeg = document.getElementById('nowDeg'), cv3 = Rotate('cv3', 50, 50, 50, 50, 45); document.getElementById('range').addEventListener('change', function (e) { nowDeg.innerHTML = `${this.value}deg`; cv3.clear(); cv3.addFill(50, 50, 50, 50, parseInt(this.value)); }, false); } </script> </body> </html>
关于canvas的封装,有兴趣的可以来看看: https://gitee.com/nowtd/cnavas_engine