使用 Canvas
說了這么多,Canvas 究竟是個(gè)啥?
英文中 Canvas 的意思是“畫布”,不過這里說的 Canvas 是 HTML5 中新出的一個(gè)元素,開發(fā)者可以在上面繪制一系列圖形。Canvas 在 HTML 文件中的寫法很簡單:
其中 id 屬性是所有 HTML 元素都可以用的,Canvas 自帶的屬性只有后面兩個(gè)(分別控制寬度、高度),沒有其它的了。至于兼容性,CanIUse 上面寫了,基礎(chǔ)的功能目前用戶使用的 90% 的瀏覽器都支持,所以大部分情況下還是可以放心使用的。
注意,一定要使用 Canvas 自帶的 width 和 height 屬性,不要使用 CSS 來控制,因?yàn)?CSS 控制會(huì)導(dǎo)致 Canvas 變形。可以試著與 PhptpShop 對(duì)比一下,后者是改變“圖像大小”,前者才是正確的改變“畫布大小”。例如下圖是三張圖片的橫向拼接:最左邊的黑框中是大小為 50px * 50px 的原圖;中間是改變了圖像大小為 100px * 100px 的效果,圖像變得模糊,但是對(duì)于圖像本身來說坐標(biāo)范圍并沒有變大;最右邊才是正確的 100px * 100px 的 Canvas。
Canvas 絕大部分的繪圖方法都與
我們首先獲取到這個(gè)元素:
var canvas = document.getElementById('canvas');
然后通過一個(gè)方法來獲取可以調(diào)用一切 Canvas API 的入口:
var ctx = canvas.getContext('2d');
看到 2d 是不是很激動(dòng)地聯(lián)想到有沒有 3d 呢?沒有 3d 的寫法,不過如果想要開啟 3D 世界的大門,則可以寫 canvas.getContext('webgl')。然而 WebGL 是基于 OpenGL ES 2.0 的一套標(biāo)準(zhǔn),與本文是徹徹底底的兩條路,因此這里就不討論了。
Canvas 中的基本概念
坐標(biāo)
與數(shù)學(xué)上常見的笛卡爾坐標(biāo)系不太相同,Canvas 的坐標(biāo)系是計(jì)算機(jī)中常見的坐標(biāo)系,它長這樣:
畫布的最左上角是 (0,0),往右 x 增大,往下 y 增大,而且 x 和 y 都是整數(shù)(就算在計(jì)算過程中不是整數(shù),在繪制的時(shí)候也會(huì)當(dāng)作整數(shù)處理),單位是像素。
繪圖
帶大家懷舊一下。不知道有多少同學(xué)小時(shí)候玩過 logo 語言,在里面你可以控制一只小海龜在一塊板子上行走、畫畫、提筆、落筆。Canvas 中也一樣,你需要控制一只畫筆的移動(dòng)和繪制。然而 Canvas 更高級(jí)一些,你可以直接利用一些函數(shù)來畫圖,不用去控制那只畫筆的位置。
Canvas 中的基本圖形
通過上文定義的 ctx 變量可以干許多有意思的事情,我們先看看如何繪制一些基本圖形。
線條
我們指定畫筆移動(dòng)到某一點(diǎn),然后告訴畫筆需要從當(dāng)前這一點(diǎn)畫到另一點(diǎn)。我們可以讓畫筆多次移動(dòng)、繪制,最后統(tǒng)一輸出到屏幕上。例子如下:
ctx.moveTo(10, 10); ctx.lineTo(150, 50); ctx.lineTo(10, 50); ctx.moveTo(10, 20); ctx.lineTo(40, 70); ctx.stroke();
上面的代碼中,lineTo 是產(chǎn)生線條用的函數(shù),執(zhí)行完之后畫筆就移到了線條的終點(diǎn)。需要注意的是,線條此時(shí)并沒有顯示在屏幕上,必須調(diào)用 stroke 才會(huì)顯示。這樣設(shè)計(jì)是有道理的,因?yàn)橄蚱聊簧?/script>輸出內(nèi)容需要耗費(fèi)大量的資源,我們完全可以先攢夠一波 lineTo,最后用 stroke 放一個(gè)大的。
路徑
繪制路徑非常簡單,只需要先告訴 ctx 一聲“我要開始畫路徑了”,然后通過各種方法(例如 lineTo)繪制路徑。如果需要畫一個(gè)封閉路徑,那就最后告訴 ctx一聲:“我畫完了,你把它封閉起來吧?!碑?dāng)然,不要忘記利用 stroke 輸出到屏幕上。
一個(gè)簡單的例子:
ctx.beginPath(); ctx.moveTo(10, 10); ctx.lineTo(150, 50); ctx.lineTo(10, 50); ctx.closePath(); ctx.stroke();
如果我不想只描繪路徑線條,而是想填充整個(gè)路徑呢?可以將最后一行的 stroke 改成 fill,這樣就跟使用了畫圖中的油漆桶一樣,封閉路徑里面的內(nèi)容就都被填充上顏色了:
ctx.fill();
弧 / 圓形
繪制弧的函數(shù)參數(shù)比較多:
ctx.arc(圓心 x 坐標(biāo), 圓心 y 坐標(biāo), 半徑, 起始角度, 終止角度, 是否為逆時(shí)針);
注意,在 Canvas 的坐標(biāo)系中,角的一邊是以圓心為中心的水平向右的直線。角度單位均為弧度。例如下圖,確定了圓心、起始角度(圖中標(biāo)明的銳角)和終止角度(圖中標(biāo)明的鈍角),方向?yàn)槟鏁r(shí)針,于是就有了這么一個(gè)弧。如果方向?yàn)轫槙r(shí)針,那么就會(huì)是一個(gè)跟它互補(bǔ)的、非常非常大的弧……
所以如果轉(zhuǎn)了 2π 圈之后,弧就成了圓形,因此也可以使用繪制弧的方式來繪制圓形:
ctx.beginPath(); ctx.arc(圓心 x 坐標(biāo), 圓心 y 坐標(biāo), 半徑, 0, Math.PI * 2, true); ctx.closePath();
最后一個(gè)參數(shù)隨便填(當(dāng)然也可以不填),因?yàn)椴还苁琼槙r(shí)針還是逆時(shí)針,轉(zhuǎn)了 2π 圈之后都是一個(gè)圓。
矩形
如果只是想繪制一個(gè)橫平豎直的矩形,可以使用下面的兩個(gè)方法:
// 只描邊 ctx.strokeRect(左上角 x 坐標(biāo), 左上角 y 坐標(biāo), 寬度, 高度); // 只填充 ctx.fillRect(左上角 x 坐標(biāo), 左上角 y 坐標(biāo), 寬度, 高度);
線條樣式 / 填充樣式
之前繪制的所有圖形都是黑色的,但是 Canvas 肯定不止這么一種顏色(不然標(biāo)準(zhǔn)的制定者會(huì)被噴的很慘)。事實(shí)上,Canvas 可以單獨(dú)設(shè)置線條樣式和填充樣式,分別使用的是 strokeStyle 和 fillStyle??赡艿闹涤腥N:純色、漸變、圖像。既然線條樣式與填充樣式的使用方法相同,那么下面統(tǒng)一以填充樣式為例。如果想設(shè)置線條樣式,直接將所有的 fillStyle 改成 strokeStyle 即可,里面的參數(shù)都不變。
/* 純色填充 */ // 普通的顏色 ctx.fillStyle = '#0000ff'; // 帶有透明度的顏色 ctx.fillStyle = 'rgba(64, 0, 127, 0.5)'; /* 漸變填充 */ // 設(shè)置漸變的尺寸(參數(shù)分別為起始點(diǎn)的 x 和 y、終止點(diǎn)的 x 和 y) var gradient = ctx.createLinearGradient(0, 0, 170, 0); // 設(shè)置過渡色,第一個(gè)參數(shù)是漸變的位置,第二個(gè)參數(shù)是顏色 gradient.addColorStop(0, 'magenta'); gradient.addColorStop(0.5, 'blue'); gradient.addColorStop(1.0, 'red'); // 設(shè)置填充樣式 ctx.fillStyle = gradient; /* 圖片填充 */ // 創(chuàng)建圖片 var image = new Image; image.src = '/path/to/image.png'; // 創(chuàng)建圖片筆觸,可以指定圖片的平鋪方式,這里是橫向平鋪 var pattern = ctx.createPattern(image, 'repeat-x'); // 設(shè)置筆觸填充 ctx.fillStyle = pattern;
關(guān)于漸變,除了代碼中提到的線性漸變以外,還有 createRadialGradient,也就是徑向漸變。
設(shè)置完填充樣式之后,就可以使用 fill 來填充啦!如果設(shè)置的是線條樣式,那么就可以使用 stroke 來描邊。
當(dāng)然,對(duì)于線條樣式,還有個(gè)額外的方法叫 lineWidth 可以用來控制線條的寬度。
文字
要想在畫布上畫文字,首先需要知道所使用的字體和字號(hào):
ctx.font = '30px Verdana';
然后就可以通過 strokeText 或者 fillText 來對(duì)字體描邊或者填充字體。
ctx.strokeText("Hello Coding!", 23, 33); ctx.fillText("Hello Coding!", 23, 66);
圖片
在 Canvas 中繪制圖片有三種方法:
// 指定繪制位置 ctx.drawImage(image, x, y); // 指定繪制位置和圖像寬高 ctx.drawImage(image, x, y, width, height); // 指定剪裁區(qū)域、繪制位置和圖像寬高 ctx.drawImage(image, sx, sy, swidth, sheight, x, y, width, height);
參數(shù)的含義依次如下:
image: 要使用的 Image、Canvas 或 Video sx: 可選,開始剪切的 x 坐標(biāo) sy: 可選,開始剪切的 y 坐標(biāo) swidth: 可選,被剪切圖像的寬度 sheight: 可選,被剪切圖像的高度 x: 在畫布上放置圖像的 x 坐標(biāo) y: 在畫布上放置圖像的 y 坐標(biāo) width: 可選,要使用的圖像的寬度 height: 可選,要使用的圖像的高度
畫布設(shè)置
細(xì)心的同學(xué)可能會(huì)發(fā)現(xiàn),剛才有些屬性是直接對(duì) ctx 變量做設(shè)置,例如 ctx.lineWidth,只要設(shè)置了它,那么后續(xù)畫出來的線條全都是這么個(gè)寬度。
其實(shí),Canvas 的設(shè)置項(xiàng)還有許多,例如我們可以直接移動(dòng)畫布、旋轉(zhuǎn)畫布、設(shè)置全局的繪制透明度等等。這些設(shè)置還可以隨時(shí)保存和恢復(fù)。
要注意的一點(diǎn)是,所有已經(jīng)畫在畫布上的東西,是已經(jīng)定死了的,不管之后再次進(jìn)行任何設(shè)置都不會(huì)再改變。這個(gè)很像 Windows 下的畫圖程序。
廢話不多說,直接上代碼:
// 移動(dòng)畫布,其實(shí)就是移動(dòng)坐標(biāo)系 ctx.translate(往右移動(dòng)的量, 往下移動(dòng)的量); // 旋轉(zhuǎn)畫布,旋轉(zhuǎn)中心為坐標(biāo)系原點(diǎn) ctx.rotate(順時(shí)針旋轉(zhuǎn)的角度); // 以坐標(biāo)系原點(diǎn)為中心縮放畫布 ctx.scale(橫向放大倍數(shù), 縱向放大倍數(shù)); // 設(shè)置繪制透明度,如果 fillStyle 等屬性設(shè)置了透明度則會(huì)疊加 ctx.globalAlpha(零到一的小數(shù)); // 設(shè)置全局組合操作 ctx.globalCompositeOperation = 'lighter'; // 保存當(dāng)前設(shè)置 ctx.save(); // 恢復(fù)上次保存的設(shè)置 ctx.restore();
移動(dòng)、旋轉(zhuǎn)、縮放其實(shí)就是在控制繪圖的坐標(biāo)系,如果你在調(diào)用這三個(gè)方法的時(shí)候,腦子里時(shí)刻有一個(gè)帶刻度的坐標(biāo)系,效果會(huì)非常好。
事實(shí)上,Canvas 的坐標(biāo)變換遵循計(jì)算機(jī)圖形學(xué)的知識(shí):變換矩陣。簡單來說,一個(gè)坐標(biāo)可以看成是一個(gè)矩陣,坐標(biāo)所對(duì)應(yīng)的矩陣乘上變換矩陣就可以實(shí)現(xiàn)對(duì)坐標(biāo)的變換。為了提升計(jì)算的效率,可以先計(jì)算出幾種變換復(fù)合之后的變換矩陣,然后直接通過 transform 函數(shù)對(duì)當(dāng)前坐標(biāo)系進(jìn)行變換,或者通過 setTransform 函數(shù)將坐標(biāo)系重置為初始狀態(tài)后再進(jìn)行變換。至于變換矩陣的內(nèi)容,對(duì)于本文來說就有些超綱了。
全局組合操作有點(diǎn)像 PhotoShop 里面的“混合選項(xiàng)”,具體的實(shí)現(xiàn)方式還沒有完全確定,目前常見瀏覽器都統(tǒng)一了的實(shí)現(xiàn)方式有:source-over、source-atop、destination-over、destination-out、lighter、xor。具體的行為可以看 Mozilla 官方文檔,但是由于標(biāo)準(zhǔn)還未完全確定,因此其它瀏覽器不保證所有的行為都跟 Mozilla 的標(biāo)準(zhǔn)一致。一般來說,比較常見的是 source-over 和 lighter 兩種,這兩種的標(biāo)準(zhǔn)在瀏覽器界也算是無可爭議的。
至于保存和恢復(fù)設(shè)置就有點(diǎn)好玩了,首先需要了解一個(gè)叫“?!钡臇|西。
棧是一個(gè)一維數(shù)組,規(guī)定只能從一個(gè)方向操作。棧一開始是空的,我們可以從這個(gè)方向往數(shù)組 push 元素,也只能從這個(gè)方向把最后一個(gè)元素(棧頂元素)pop 出來,除此以外沒有任何多余的操作。當(dāng)然,pop 的次數(shù)不能多于 push 的次數(shù),因?yàn)?pop 到棧底的時(shí)候棧里就已經(jīng)沒有元素了,此時(shí)再 pop 是沒有意義的。棧的用處有很多,例如括號(hào)匹配、表達(dá)式求值、深度優(yōu)先搜索,甚至絕大部分語言的函數(shù)調(diào)用都要用到棧。
每次我們調(diào)用 save 函數(shù),實(shí)際上是將當(dāng)前的全局設(shè)置 push 到了一個(gè)專門棧上,每次調(diào)用 restore 函數(shù)的時(shí)候?qū)⒆詈笠淮伪4娴膬?nèi)容 pop 出來并用它覆蓋當(dāng)前的全局設(shè)置,這樣棧頂就是最近一次保存的內(nèi)容了。保存和恢復(fù)在某些情況下很好用,例如我需要畫一個(gè)歪著的圖形,然后繼續(xù)畫正著的圖形,這樣就可以先調(diào)用 save,然后調(diào)用 rotate,畫完圖形之后再restore 回來,繼續(xù)畫其它的圖形。
其實(shí) Canvas 還有許多方法,例如 toDataURL 直接將當(dāng)前畫布上的內(nèi)容轉(zhuǎn)換為十六進(jìn)制的 data-url,getImageData 直接將圖像轉(zhuǎn)換為 RGBA 數(shù)組以供圖像處理算法使用,putImageData 將 RGBA 數(shù)組轉(zhuǎn)換為圖片顯示在畫布上等等。如果配上 JavaScript 的定時(shí)更新(最好用 requestAnimationFrame 而不是 setInterval),則可以產(chǎn)生動(dòng)畫效果。網(wǎng)上還有許多 Canvas 的庫,可以讓程序員更簡便地基于 Canvas 編寫屬于自己的特效或功能。在這兒我想說一句話:大家的腦洞有多大,Canvas 的能力就有多強(qiáng)~
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com