我之前用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