聊聊Canvas從入門(mén)到豬頭
一、Canvas簡(jiǎn)介
1.1 什么是canvas
Canvas(畫(huà)布)是在HTML5中新增的標(biāo)簽用于在網(wǎng)頁(yè)實(shí)時(shí)生成圖像,可以操作圖像內(nèi)容,是一個(gè)可以用JavaScript操作的位圖(bitmap)。
1.2 canvas的坐標(biāo)系統(tǒng)
canvas的坐標(biāo)系統(tǒng)如下圖所示,其具有如下特點(diǎn):
- x軸正方向向右、y軸正方向向下
- 畫(huà)布的原點(diǎn)在左上角
- 橫縱坐標(biāo)單位為像素
- 每個(gè)軸的最小單元為一個(gè)像素(柵格)
1.3 canvas的繪制流程
- 創(chuàng)建一個(gè)標(biāo)簽
- 獲取canvas元素對(duì)應(yīng)的DOM對(duì)象,這是一個(gè)Canvas對(duì)象
- 調(diào)用Canvas對(duì)象的getContext()方法,該方法返回一個(gè)CanvasRenderingContext2D對(duì)象,該對(duì)象即可繪制圖形
- 調(diào)用CanvasRenderingContext2D對(duì)象的方法繪圖
1.4 canvas的應(yīng)用領(lǐng)域
canvas這個(gè)神奇的東西有很多領(lǐng)域可以得到應(yīng)用,下面我們一起嘮一嘮。
- 游戲:canvas 在基于 Web 的圖像顯示方面比 Flash 更加立體、更加精巧,canvas 游戲在流暢度和跨平臺(tái)方面更優(yōu)秀,例如這25款canvas游戲
- 可視化的庫(kù):Echart
- banner廣告:Canvas 實(shí)現(xiàn)動(dòng)態(tài)的廣告效果非常合適
- 圖形編輯器:后續(xù)Photoshop能夠100%基于Web實(shí)現(xiàn)
- 微信讀書(shū)、騰訊文檔均是通過(guò)canvas實(shí)現(xiàn)
二、基礎(chǔ)功能
通過(guò)第一章對(duì)canvas有了初步的認(rèn)識(shí),本章就按照一個(gè)人繪制一幅畫(huà)的思路進(jìn)行演化,逐步了解canvas的基本功能,從而更好的使用它實(shí)現(xiàn)一些酷炫的效果。
2.1 坐標(biāo)系選擇
當(dāng)要繪制一幅畫(huà)時(shí)首先要確定坐標(biāo)系,只有選擇好了坐標(biāo)系之后才好動(dòng)筆,在canvas中默認(rèn)坐標(biāo)系在左上角(X軸正方向向右、Y軸正方向向下),可是有的時(shí)候需要變換坐標(biāo)系才能更方便的實(shí)現(xiàn)所需的效果,此時(shí)需要變換坐標(biāo)系,canvas提供了以下幾種變換坐標(biāo)系的方式:
- translate(dx,dy):平移坐標(biāo)系。相當(dāng)于把原來(lái)位于(0,0)位置的坐標(biāo)原點(diǎn)平移到(dx、dy)點(diǎn)。
- rotate(angle):旋轉(zhuǎn)坐標(biāo)系。該方法控制坐標(biāo)系統(tǒng)順時(shí)針旋轉(zhuǎn)angle弧度。
- scale(sx,sy):縮放坐標(biāo)系。該方法控制坐標(biāo)系統(tǒng)水平方向上縮放sx,垂直方向上縮放sy。
- transform(a,b,c,d,e,f):允許縮放、旋轉(zhuǎn)、移動(dòng)并傾斜當(dāng)前的環(huán)境坐標(biāo)系,其中a表示水平縮放、b表示水平切斜、c表示垂直切斜、d表示垂直縮放、e表示水平移動(dòng)、f表示垂直移動(dòng)。
- function main() {
- const canvas = document.getElementById('canvasId');
- const ctx = canvas.getContext('2d');
- ctx.lineWidth = 4;
- // 默認(rèn)
- ctx.save();
- ctx.strokeStyle = '#F00';
- drawCoordiante(ctx);
- ctx.restore();
- // 平移
- ctx.save();
- ctx.translate(150, 150);
- ctx.strokeStyle = '#0F0';
- drawCoordiante(ctx);
- ctx.restore();
- // 旋轉(zhuǎn)
- ctx.save();
- ctx.translate(300, 300);
- ctx.rotate(-Math.PI / 2);
- ctx.strokeStyle = '#00F';
- drawCoordiante(ctx);
- ctx.restore();
- // 縮放
- ctx.save();
- ctx.translate(400, 400);
- ctx.rotate(-Math.PI / 2);
- ctx.scale(0.5, 0.5);
- ctx.strokeStyle = '#000';
- drawCoordiante(ctx);
- ctx.restore();
- }
- function drawCoordiante(ctx) {
- ctx.beginPath();
- ctx.moveTo(0, 0);
- ctx.lineTo(120, 0);
- ctx.moveTo(0, 0);
- ctx.lineTo(0, 80);
- ctx.closePath();
- ctx.stroke();
- }
- main();
2.2 圖形繪制
坐標(biāo)系選擇好了之后,就要開(kāi)始動(dòng)筆創(chuàng)作大作了,那么canvas到底允許繪制哪些內(nèi)容呢?
直線(xiàn)
- function drawLine(ctx, startX, startY, endX, endY) {
- ctx.moveTo(startX, startY);
- ctx.lineTo(endX, endY);
- ctx.stroke();
- }
圓弧
- function drawCircle(ctx, x, y, R, startAngle, endAngle) {
- ctx.arc(x, y, R, startAngle, endAngle);
- ctx.stroke();
- }
曲線(xiàn)
- // 貝濟(jì)埃曲線(xiàn)
- function drawBezierCurve(ctx, cpX1, cpY1, cpX, cpY2, endX, endY) {
- ctx.bezierCurveTo(cpX1, cpY1, cpX, cpY2, endX, endY);
- ctx.stroke();
- }
- // 二次曲線(xiàn)
- function drawQuadraticCurve(ctx, cpX, cpY, endX, endY) {
- ctx.quadraticCurveTo(cpX, cpY, endX, endY);
- ctx.stroke();
- }
矩形
- // 填充矩形
- function drawFillRect(ctx, x, y, width, height) {
- ctx.fillRect(x, y, width, height);
- }
- // 邊框矩形
- function drawStrokeRect(ctx, x, y, width, height) {
- ctx.strokeRect( x, y, width, height);
- }
字符串
- / 填充字符串
- function drawFillText(ctx, text, x, y) {
- ctx.fillText(text, x, y);
- }
- // 邊框字符串
- function drawStrokeText(ctx, text, x, y) {
- ctx.strokeText(text, x, y);
- }
復(fù)雜圖形繪制——路徑
- // 利用路徑繪制
- function drawFigureByPath(ctx) {
- ctx.beginPath();
- ctx.moveTo(100, 400);
- ctx.lineTo(200, 450);
- ctx.lineTo(150, 480);
- ctx.closePath();
- ctx.fill();
- }
- function main() {
- const canvas = document.getElementById('canvasId');
- const ctx = canvas.getContext('2d');
- ctx.lineWidth = 2;
- ctx.strokeStyle = '#F00';
- ctx.fillStyle = '#F00';
- ctx.font = 'normal 50px 宋體';
- drawLine(ctx, 50, 10, 150, 10);
- ctx.moveTo(150, 100);
- drawCircle(ctx, 100, 100, 50, 0, Math.PI);
- ctx.moveTo(300, 100);
- drawCircle(ctx, 250, 100, 50, 0, Math.PI * 2);
- ctx.moveTo(350, 150);
- drawBezierCurve(ctx, 200, 200, 450, 250, 300, 300);
- ctx.moveTo(50, 250);
- drawQuadraticCurve(ctx, 50, 400, 80, 400);
- drawFillRect(ctx, 100, 300, 100, 50);
- drawStrokeRect(ctx, 300, 300, 100, 50);
- drawFillText(ctx, 'I', 100, 400);
- drawStrokeText(ctx, 'I', 300, 400);
- drawFigureByPath(ctx);
- }
2.3 填充風(fēng)格
利用canvas繪制圖形時(shí)勢(shì)必要上點(diǎn)顏料,通過(guò)設(shè)置fillStyle屬性即可設(shè)置對(duì)應(yīng)的顏料,對(duì)于顏料值主要有以下四種:純顏色、線(xiàn)性漸變顏色、徑向漸變顏色、位圖。
純顏色
- function useColorFill(ctx) {
- ctx.save();
- ctx.fillStyle = '#F00';
- ctx.fillRect(10, 10, 100, 100);
- ctx.restore();
- }
線(xiàn)性漸變顏色
- function useLinearGradientFill(ctx) {
- ctx.save();
- const lg = ctx.createLinearGradient(110, 10, 210, 10);
- lg.addColorStop(0.2, '#F00');
- lg.addColorStop(0.5, '#0F0');
- lg.addColorStop(0.9, '#00F');
- ctx.fillStyle = lg;
- ctx.fillRect(120, 10, 100, 100);
- ctx.restore();
- }
徑向漸變顏色
- function useRadialGradientFill(ctx) {
- ctx.save();
- const lg = ctx.createRadialGradient(260, 60, 10, 260, 60, 60);
- lg.addColorStop(0.2, '#F00');
- lg.addColorStop(0.5, '#0F0');
- lg.addColorStop(0.9, '#00F');
- ctx.fillStyle = lg;
- ctx.fillRect(230, 10, 100, 100);
- ctx.restore();
- }
位圖填充
- function useImageFill(ctx) {
- ctx.save();
- const image = new Image();
- image.src = 'https://dss2.baidu.com/6ONYsjip0QIZ8tyhnq/it/u=442547030,98631113&fm=58';
- image.onload = function () {
- // 創(chuàng)建位圖填充
- const imgPattern = ctx.createPattern(image, 'repeat');
- ctx.fillStyle = imgPattern;
- ctx.fillRect(340, 10, 100, 100);
- ctx.restore();
- }
- }
2.4 臨時(shí)保存
用一只畫(huà)筆在畫(huà)某個(gè)美女時(shí),忽然來(lái)了靈感需要畫(huà)另一個(gè)帥哥,這個(gè)時(shí)候又不想放棄這個(gè)美女,則就需要將當(dāng)前畫(huà)美女的顏料、坐標(biāo)等狀態(tài)進(jìn)行暫存,等到畫(huà)完帥哥后恢復(fù)狀態(tài)進(jìn)行美女的繪制。在canvas中可以通過(guò)save()和restore()方法實(shí)現(xiàn),調(diào)用save()方法后將這一時(shí)刻的設(shè)置放到一個(gè)暫存棧中,然后可以放心的修改上下文,在需要繪制之前的上下文時(shí),可以調(diào)用restore()方法。
- // 從左往右是依次繪制(中間為用新的樣式填充,最后一個(gè)是恢復(fù)到原來(lái)樣式填充)
- function main() {
- const canvas = document.getElementById('canvasId');
- const ctx = canvas.getContext('2d');
- ctx.fillStyle = '#F00';
- ctx.fillRect(10, 10, 100, 100);
- ctx.save();
- ctx.fillStyle = '#0F0';
- ctx.fillRect(150, 10, 100, 100);
- ctx.restore();
- ctx.fillRect(290, 10, 100, 100);
- }
2.5 引入外部圖像
有的時(shí)候需要引入外部圖片,然后對(duì)外部圖片進(jìn)行像素級(jí)別的處理,最后進(jìn)行保存。
- 繪制圖像:drawImage
- 取得圖像數(shù)據(jù):getIamgeData
- 將修改后的數(shù)據(jù)重新填充到Canvas中:putImageData
- 輸出位圖:toDataURL
- function main() {
- const canvas = document.getElementById('canvasId');
- const ctx = canvas.getContext('2d');
- const image = document.getElementById('image');
- // 繪制圖像
- ctx.drawImage(image, 0, 0);
- // 獲取圖像數(shù)據(jù)
- const imageData = ctx.getImageData(0, 0, image.width, image.height);
- const data = imageData.data;
- for (let i = 0, len = data.length; i < len; i += 4) {
- const red = data[i];
- const green = data[i + 1];
- const blue = data[i + 2];
- const average = Math.floor((red + green + blue) / 3);
- data[i] = average;
- data[i + 1] = average;
- data[i + 2] = average;
- }
- imageData.data = data;
- ctx.putImageData(imageData, 0, 0);
- document.getElementById('result').src = canvas.toDataURL('image/png');
- }
三、豬頭實(shí)戰(zhàn)
學(xué)習(xí)了這么多,就利用canvas繪制一個(gè)豬頭吧,畢竟每個(gè)程序員身邊肯定有一個(gè)陪伴自己的小胖豬,嘿嘿。
- function main() {
- const canvas = document.getElementById('canvasId');
- const ctx = canvas.getContext('2d');
- ctx.lineWidth = 4;
- ctx.strokeStyle = '#000';
- ctx.fillStyle = '#ffd8e1';
- ctx.translate(260, 20);
- drawPigEar(ctx);
- ctx.save();
- ctx.rotate(Math.PI / 2);
- drawPigEar(ctx);
- ctx.restore();
- drawPigFace(ctx);
- ctx.save();
- ctx.translate(-100, -100);
- drawPigEye(ctx);
- ctx.restore();
- ctx.save();
- ctx.translate(100, -100);
- drawPigEye(ctx);
- ctx.restore();
- ctx.save();
- ctx.translate(0, 60);
- drawPigNose(ctx);
- ctx.restore();
- }
- function drawPigEar(ctx) {
- ctx.save();
- ctx.beginPath();
- ctx.arc(-250, 0, 250, 0, -Math.PI / 2, true);
- ctx.arc(0, -250, 250, -Math.PI, Math.PI / 2, true);
- ctx.closePath();
- ctx.fill();
- ctx.stroke();
- ctx.restore();
- }
- function drawPigFace(ctx) {
- ctx.save();
- ctx.beginPath();
- ctx.arc(0, 0, 250, 0, Math.PI * 2);
- ctx.fill();
- ctx.stroke();
- ctx.closePath();
- ctx.restore();
- }
- function drawPigEye(ctx) {
- ctx.save();
- ctx.fillStyle = '#000';
- ctx.beginPath();
- ctx.arc(0, 0, 20, 0, Math.PI * 2);
- ctx.closePath();
- ctx.fill();
- ctx.restore();
- }
- function drawPigNose(ctx) {
- ctx.save();
- ctx.fillStyle = '#fca7aa';
- ctx.beginPath();
- ctx.ellipse(0, 0, 150, 100, 0, 0, Math.PI * 2);
- ctx.closePath();
- ctx.fill();
- ctx.stroke();
- ctx.save();
- ctx.translate(-60, 0);
- drawPigNostrils(ctx);
- ctx.restore();
- ctx.save();
- ctx.translate(60, 0);
- drawPigNostrils(ctx);
- ctx.restore();
- ctx.restore();
- }
- function drawPigNostrils(ctx) {
- ctx.save();
- ctx.fillStyle = '#b55151';
- ctx.beginPath();
- ctx.ellipse(0, 0, 40, 60, 0, 0, Math.PI * 2);
- ctx.closePath();
- ctx.fill();
- ctx.stroke();
- ctx.restore();
- }
- main();
本文轉(zhuǎn)載自微信公眾號(hào)「執(zhí)鳶者」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系執(zhí)鳶者公眾號(hào)。