【canvas】一個少女心滿滿的例子帶你入門 canvas

之前看到了一個很好看的canvas效果,然後拿來做我的博客背景,不少童鞋留言說求教程,並且反應說太耗內存,於是前一段我就重寫了一遍,並且使用離屏渲染進行優化,效果還是挺顯著的。但是因為畢竟是canvas,需要一直進行重繪,所以還是比較耗內存的,但是比優化之前已經好很多了。並且最近準備自己寫插件,於是就拿這個練手了。

本文首發於我的個人博客:cherryblog.site/

github項目地址:github.com/sunshine9403

本文作者: Cherry

本文鏈接: cherryblog.site/canvas-

版權聲明: 本博客所有文章除特別聲明外,均採用 CC BY-NC-SA 3.0 許可協議。轉載請註明出處!

代碼還有很多的不足,求大神 review (づ??????)づ~

canvas 基本知識

什麼是 canvas

canvas 是 HTML5 新定義的標籤,通過使用腳本(通常是 JavaScript)繪製圖形。 <canvas>標籤只是圖形容器,相當於一個畫布,canvas 元素本身是沒有繪圖能力的。所有的繪製工作必須在 JavaScript 內部完成,相當於使用畫筆在畫布上畫畫。

默認情況下,<canvas> 沒有邊框和內容。默認是一個 300150 的畫布,所以我們創建了 <canvas> 之後要對其設置寬高。 我們可以通過html屬性『width』,『height』來設置canvas的寬高,不可以通過 css 屬性來設置寬高。因為通過 css 屬性設置的寬高會使 canvas 內的圖像按照 300150 時的比例放大或縮小

getContext()

context 是一個封裝了很多繪圖功能的對象,我們在頁面中創建一個 canvas 標籤之後,首先要使用 getContext() 獲取 canvas 的上下文環境,目前 getContext() 的參數只有 2d,暫時還不支持 3d

getContext("2d") 對象是內建的 HTML5 對象,擁有多種繪製路徑、矩形、圓形、字元以及添加圖像的方法。

canvas 元素繪製圖像

canvas 創建圖形有兩種方式

context.fill()

fill() 方法填充當前的圖像(路徑)。默認顏色是黑色。在填充前要先使用 fillStyle 設置填充的顏色或者漸變,並且如果路徑未關閉,那麼 fill() 方法會從路徑結束點到開始點之間添加一條線,以關閉該路徑(正如 closePath() 一樣),然後填充該路徑。

context.stroke()

stroke() 方法會實際地繪製出通過 moveTo() 和 lineTo() 方法定義的路徑。默認顏色是黑色。在進行圖形繪製前,要設置好繪圖的樣式

fillStyle()//填充的樣式nstrokeStyle()//邊框樣式ncontext.lineWidth()//圖形邊框寬度n

繪製矩形

用 canvas 繪製一個矩形很簡單

fillRect(x,y,width,height) // 實心矩形 nstrokeRect(x,y,width,height) // 空心矩形n

  • x :起始點的 x 坐標
  • y :起始點的 y 坐標
  • width : 矩形的寬
  • height : 矩形的高

//html代碼n<canvas id="canvas"></canvas>n//script代碼n var canvas = document.getElementById(canvas);n var context = canvas.getContext(2d);n context.fillRect(0, 0, 100, 100);n context.strokeRect(120, 0, 100, 100);n

顯示如下:

我們可以看出,在沒有設置顏色的情況下,默認是黑色的。

我們還可以通過設置 fillStyle 或者 fillStyle 改變其填充顏色。

context.fillStyle = "pink";ncontext.strokeStyle = "darkred";ncontext.fillRect(0, 0, 100, 100);ncontext.strokeRect(120, 0, 100, 100);n

效果如下

清除矩形區域

clearRect(x,y,width,height)

- x :清除矩形起始點的 x 坐標n - y :清除矩形起始點的 y 坐標n - width : 清除矩形矩形的寬n - height : 清除矩形矩形的高n

var canvas = document.getElementById(canvas); var context = canvas.getContext("2d"); context.fillRect(0, 0, 100, 100); context.strokeRect(120, 0, 100, 100); context.fillStyle = "pink"; context.strokeStyle = "darkred"; context.fillRect(0, 120, 100, 100); context.strokeRect(120, 120, 100, 100); context.clearRect( 50,50,120,120)

效果如下:

實心圓

context.arc(x, y, radius, starAngle,endAngle, anticlockwise)

  • x : 圓心的 x 坐標
  • y:圓心的 y 坐標
  • radius : 半徑
  • starAngle :開始角度
  • endAngle:結束角度
  • anticlockwise :是否逆時針(true)為逆時針,(false)為順時針

context.beginPath();ncontext.arc(300, 350, 100, 0, Math.PI * 2, true);n//不關閉路徑路徑會一直保留下去ncontext.closePath();ncontext.fillStyle = rgba(0,255,0,0.25);ncontext.fill();n

效果如下:

圓弧

如果不填充顏色,實心圓就是圓弧

context.beginPath();n context.arc(600, 350, 100, 0, Math.PI , true);n context.strokeStyle = pink;n context.closePath();n context.stroke();n n context.beginPath();n context.arc(300, 350, 100, 0, Math.PI , true);n context.strokeStyle = red;n //沒有closePathn context.stroke();n

效果如圖:

  • 系統默認在繪製第一個路徑的開始點為beginPath
  • 如果畫完前面的路徑沒有重新指定beginPath,那麼畫第其他路徑的時候會將前面最近指定的beginPath後的全部路徑重新繪製
  • 每次調用context.fill()的時候會自動把當次繪製的路徑的開始點和結束點相連,接著填充封閉的部分

所以說,如果第一個圓弧沒有 closePath() 並且第二個圓弧沒有 beginPath() 的話就是這樣的效果:

繪製線段

  • moveTo(x,y):把路徑移動到畫布中的指定點,不創建線條
  • lineTo(x,y):添加一個新點,然後在畫布中創建從該點到最後指定點的線條
  • 每次畫線都從 moveTo 的點到 lineTo 的點,

context.strokeStyle = pink;n context.moveTo(0, 0);n context.lineTo(100, 100);n context.stroke();*/n

效果如下:

如果沒有 moveTo 那麼第一次 lineTo 的效果和 moveTo 一樣, 例如:

context.strokeStyle = pink;n context.lineTo(100, 100);n context.lineTo(200, 200);n context.stroke();*/n

效果如下:

每次lineTo後如果沒有moveTo,那麼下次lineTo的開始點為前一次lineTo的結束點 例如:

// 繪製片段n context.strokeStyle = pink;n context.lineTo(200, 200);n context.lineTo(200, 100);n context.lineTo(100,50);n context.stroke();n

效果如下:

我們可以使用 canvas 的線段繪製各種各樣的圖形,比如繪製一個六邊形

var n = 0;n var dx = 150;n var dy = 150;n var s = 100;n context.beginPath();n context.fillStyle = pink;n context.strokeStyle = rgb(0,0,100);n var x = Math.sin(0);n var y = Math.cos(0);n var dig = Math.PI / 15 * 5;n for (var i = 0; i < 6; i++) {n var x = Math.sin(i * dig);n var y = Math.cos(i * dig);n context.lineTo(dx + x * s, dy + y * s);n console.log( x ,y )n }n context.closePath();n context.fill();n context.stroke();n

繪製 30 角形:

var n = 0;n var dx = 150;n var dy = 150;n var s = 100;n context.beginPath();n context.fillStyle = pink;n context.strokeStyle = rgb(0,0,100);n var x = Math.sin(0);n var y = Math.cos(0);n var dig = Math.PI / 15 * 7;n for (var i = 0; i < 30; i++) {n var x = Math.sin(i * dig);n var y = Math.cos(i * dig);n context.lineTo(dx + x * s, dy + y * s);n console.log( x ,y )n }n context.closePath();n context.fill();n context.stroke();n

效果如下:

![canvas繪製 30 腳形](http://img.blog.csdn .net/20170804152344651?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3Vuc2hpbmU5NDAzMjY=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

線性漸變

var lg= context.createLinearGradient(xStart,yStart,xEnd,yEnd)lg.addColorStop(offset,color)

  • xstart:漸變開始點x坐標
  • ystart:漸變開始點y坐標
  • xEnd:漸變結束點x坐標
  • yEnd:漸變結束點y坐標
  • offset:設定的顏色離漸變結束點的偏移量(0~1)
  • color:繪製時要使用的顏色

例如:

var g1 = context.createLinearGradient(0, 0, 0, 300);n g1.addColorStop(0, #E55D87); n g1.addColorStop(1, #5FC3E4);n context.fillStyle = g1;n context.fillRect(0, 0, 400, 300);n

效果如下:

徑向漸變

var rg=context.createRadialGradient(xStart,yStart,radiusStart,xEnd,yEnd,radiusEnd)rg.addColorStop(offset,color)

  • xStart:發散開始圓心x坐標
  • yStart:發散開始圓心y坐標
  • radiusStart:發散開始圓的半徑
  • xEnd:發散結束圓心的x坐標
  • yEnd:發散結束圓心的y坐標
  • radiusEnd:發散結束圓的半徑
  • offset:設定的顏色離漸變結束點的偏移量(0~1)
  • color:繪製時要使用的顏色

例如:

// 同心圓徑向漸變n var g1 = context.createRadialGradient(200, 150, 0, 200, 150, 200);n g1.addColorStop(0.1, #F09819);n g1.addColorStop(1, #EDDE5D);n context.fillStyle = g1;n context.beginPath();n context.arc(200, 150, 100, 0, Math.PI * 2, true);n context.closePath();n context.fill();n

//不同圓心的徑向漸變模型n var g1 = context.createRadialGradient(100, 150, 10, 300, 150, 80);n g1.addColorStop(0.1, #F09819);n g1.addColorStop(0.8, red);n g1.addColorStop(1, #EDDE5D);nn context.fillStyle = g1;n context.fillRect(0, 0, 300, 500);n

效果圖:

圖形變形

縮放

scale(x,y)

  • x :x坐標軸按 x 比例縮放
  • y :x坐標軸按 y 比例縮放

旋轉

rotate(angle)

  • angle :坐標軸旋轉x角度(角度變化模型和畫圓的模型一樣)

平移

translate(x,y)

  • x :坐標原點向x軸方向平移x
  • y :坐標原點向y軸方向平移y

平移,縮放,旋轉先後順序不同,坐標軸的變化圖,圖片來源於網路:

圖形組合

globalCompositeOperation=type 設置或返回新圖像如何繪製到已有的圖像上。最後的效果取決於 type 的值 type:

  • source-over(默認值):在原有圖形上繪製新圖形
  • destination-over:在原有圖形下繪製新圖形
  • source-in:顯示原有圖形和新圖形的交集,新圖形在上,所以顏色為新圖形的顏色
  • destination-in:顯示原有圖形和新圖形的交集,原有圖形在上,所以顏色為原有圖形的顏色
  • source-out:只顯示新圖形非交集部分
  • destination-out:只顯示原有圖形非交集部分
  • source-atop:顯示原有圖形和交集部分,新圖形在上,所以交集部分的顏色為新圖形的顏色
  • destination-atop:顯示新圖形和交集部分,新圖形在下,所以交集部分的顏色為原有圖形的顏色
  • lighter:原有圖形和新圖形都顯示,交集部分做顏色疊加
  • xor:重疊飛部分不現實
  • copy:只顯示新圖形 效果圖如下,圖片來源於網路

陰影

shadowOffsetX:設置或返回陰影距形狀的水平距離(默認值為 0)nshadowOffsetY:設置或返回陰影距形狀的垂直距離(默認值為 0)nshadowColor:設置或返回用於陰影的顏色nshadowBlur:設置或返回用於陰影的模糊級別(值越大越模糊)n

例如:

context.fillStyle = white;n context.beginPath();n context.arc(100,100,10,0,2 * Math.PI);n context.shadowColor = white;n context.shadowBlur = 10;n context.fill();n context.closePath();n

我們看到的效果就是我們在開頭提起的例子中的 star 粒子的效果,因為其有白色陰影的效果,所以看起來像是發光一樣,效果如下圖:

圖像繪製

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:可選。要使用的圖像的高度。(伸展或縮小圖像)

圖像平鋪

createPattern(image,type) type:

  • no-repeat:不平鋪
  • repeat-x:橫方向平鋪
  • repeat-y:縱方向平鋪
  • repeat:全方向平鋪

圖像裁剪

clip()從原始畫布剪切任意形狀和尺寸的區域,需要先創建裁剪區域,再繪製圖像;一旦剪切了某個區域,則所有之後的繪圖都會被限制在被剪切的區域內(不能訪問畫布上的其他區域)。您也可以在使用 clip() 方法前通過使用 save() 方法對當前畫布區域進行保存,並在以後的任意時間對其進行恢復(通過 restore() 方法)。 例如:

// 設置剪切區域(粉色矩形)n context.rect(0,0,500,400);n context.fillStyle = "pink";n context.fill();n context.clip();nn // 在剪切區域中繪製圖形(白色矩形)n context.fillStyle = "white";n context.fillRect(10,10,100,100);nn // 之後繪製的圖形只能顯示在剪切區域之內(紅色矩形)n context.fillStyle = "red";n context.fillRect(100,100,600,600)n

效果如下:可以看到我們設置的紅色矩形是一個 600*600 的矩形,但是顯然是沒有顯示完的,一旦剪切了某個區域,則所有之後的繪圖都會被限制在被剪切的區域內(不能訪問畫布上的其他區域)。

所以說我們可以在使用 clip() 方法前通過使用 save() 方法對當前畫布區域進行保存,並在以後的任意時間對其進行恢復(通過 restore() 方法)。 代碼如下:

context.save();n // 設置剪切區域n context.rect(0,0,500,400);n context.fillStyle = "pink";n context.fill();n context.clip();nn // 在剪切區域中繪製圖形n context.fillStyle = "white";n context.fillRect(10,10,100,100);nn context.restore();n // 之後繪製的圖形只能顯示在剪切區域之內n context.fillStyle = "red";n context.fillRect(100,100,600,600)n

這樣就可以正常顯示了:

繪製文字

fillText(text,x,y):繪製實心文字nstrokeText():繪製文字描邊(空心)ntextAlign:設置或返迴文本內容的當前對齊方式ntextBaseline:設置或返回在繪製文本時使用的當前文本基線nfont:設置或返迴文本內容的當前字體屬性n

例如:

context.font="40px Arial";n context.fillText("Hello world",200,200);n context.strokeText("Hello world",200,300)n

效果如下:

準備工作

好的開始是成功的一半

簡單介紹了下 canvas 的常用 api,大家發現是不是也沒有那麼難呢( ̄▽ ̄)*,那麼讓我們回到標題,一起來看一下這個少女心滿滿的例子是怎樣實現的~

canvas 其實寫一個炫酷的特效在技術上並不難,難的是你的創意,因為 canvas 實現粒子的效果還是比較驚艷的,但其實代碼都是比較簡單的,無非就是隨機的創建圖形或者路徑,當然圖形也是閉合的路徑。在加上一定的位移就可以了。但是你要設計出一個好的特效是非常不容易的。

所以我們就先來分析一下這個效果由那幾部分構成,將其拆分開來。

特效pc端演示地址:sunshine940326.github.io (當然,可以直接查看我的博客,背景暫時就是這個,不知道什麼時候會變,捂臉ing:http://cherryblog.site/)

分析 star 的表現和行為

我們可以將其一直位移向上的粒子稱為 star,我們觀察 star 的特點:

  • 開始創建時位置隨機(坐標隨機)
  • 透明度隨機
  • 創建時的大小在一定範圍內(半徑在一定範圍內)
  • 勻速上升
  • 總數不變

所以我們就可以總結出 star 的特點就是總數固定,創建時坐標和半徑還有透明度隨機,勻速上升。是不是很簡單了呢[]( ̄▽ ̄)~*

分析 dot 的表現和行為

再讓我們來看一下隨著滑鼠移入產生的粒子,我們稱為 dot,同理,我們觀察得到 dot 的特點

  • 列表內容
  • 滑鼠移動時產生
  • 新產生的 dot 和之前的 3 個 dot 產生連線
  • 向四周移動
  • 達到一定條件消失

這樣,我們就完成了一半了呢~將事件屢清楚之後我們就可以開始著手擼代碼了!

背景的 HTML 和 CSS

其實需要的 HTML 代碼和 CSS 代碼很簡答的,HTML 只需要一行就可以了呢,設置一個漸變的背景蒙層和一個 canvas 標籤。

HTML 和 CSS 如下:

<div class="filter"></div>n<canvas id="canvas"></canvas>nnhtml, body {n margin: 0;n padding: 0;n width: 100%;n height: 100%;n overflow: hidden;n background: black;n background: linear-gradient(to bottom, #dcdcdc 0%, palevioletred 100%);n }nn #main-canvas {n width: 100%;n height: 100%;n }nn .filter {n width: 100%;n height: 100%;n position: absolute;n top: 0;n left: 0;n background: #fe5757;n animation: colorChange 30s ease-in-out infinite;n animation-fill-mode: both;n mix-blend-mode: overlay;nn }nn @keyframes colorChange {n 0%, 100% {n opacity: 0;n }n 50% {n opacity: .7;n }n }n

是的,我使用的是一個漸變的背景,不僅是從上到下的漸變,並且顏色也是會漸變的,效果如下:

設置參數以及獲取 dom 對象

/*n * @var star_r:star半徑係數,係數越大,半徑越大n * @var star_alpha:生成star的透明度,star_alpha越大,透明度越低n * @var initStarsPopulation:初始化stars的個數n * @var move_distance:star位移的距離,數值越大,位移越大n * @var dot_r : dot半徑係數,係數越大,半徑越大n * @var dot_speeds : dots運動的速度n * @var dot_alpha : dots的透明度n * @var aReduction:dot消失條件,透明度小於aReduction時消失n * @var dotsMinDist:dot最小距離n * @var maxDistFromCursor:dot最大距離n * */n var config = {n star_r : 3,n star_alpha : 5,n initStarsPopulation : 150,n move_distance : 0.25,n dot_r : 5,n dot_speeds : 0.5,n dot_alpha : 0.5,n dot_aReduction : 0.01,n dotsMinDist : 5,n maxDistFromCursor : 50,n };n var stars = [],n dots = [],n canvas = document.getElementById(canvas),n ctx = canvas.getContext(2d),n WIDTH,n HEIGHT,n mouseMoving = false,n mouseMoveChecker,n mouseX,n mouseY;n

繪製單個 star

/* 設置單個 starn * @param id:idn * @param x:x坐標n * @param y:y坐標n * @param useCache:是否使用緩存n * */n function Star(id, x, y) {n this.id = id;n this.x = x;n this.y = y;n this.cacheCanvas = document.createElement("canvas");n this.cacheCtx = this.cacheCanvas.getContext("2d");n this.r = Math.floor(Math.random() * star_r) + 1;n this.cacheCtx.width = 6 * this.r;n this.cacheCtx.height = 6 * this.r;n var alpha = ( Math.floor(Math.random() * 10) + 1) / star_alpha;n this.color = "rgba(255,255,255," + alpha + ")";n if (useCache) {n this.cache()n }n }n

讓每一個 star 動起來

這裡我使用的是原型的方式,將 draw、cache、move 和 die 方法都設置在 Star 的原型上,這樣在使用 new 創建對象的時候,每一個 star 都可以繼承這些方法。

Star.prototype = {n draw : function () {n if (!this.useCacha) {n ctx.save();n ctx.fillStyle = this.color;n ctx.shadowBlur = this.r * 2;n ctx.beginPath();n ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI, false);n ctx.closePath();n ctx.fill();n ctx.restore();n } else {n ctx.drawImage(this.cacheCanvas, this.x - this.r, this.y - this.r);n }n },nn cache : function () {n this.cacheCtx.save();n this.cacheCtx.fillStyle = this.color;n this.cacheCtx.shadowColor = "white";n this.cacheCtx.shadowBlur = this.r * 2;n this.cacheCtx.beginPath();n this.cacheCtx.arc(this.r * 3, this.r * 3, this.r, 0, 2 * Math.PI);n this.cacheCtx.closePath();n this.cacheCtx.fill();n this.cacheCtx.restore();n },nn move : function () {n this.y -= move_distance;n if (this.y <= -10) {n this.y += HEIGHT + 10;n }n this.draw();n },nn die : function () {n stars[this.id] = null;n delete stars[this.id]n }n };n

繪製 dot

function Dot(id, x, y, useCache) {n this.id = id;n this.x = x;n this.y = y;n this.r = Math.floor(Math.random() * dot_r)+1;n this.speed = dot_speeds;n this.a = dot_alpha;n this.aReduction = dot_aReduction;n this.useCache = useCache;n this.dotCanvas = document.createElement("canvas");n this.dotCtx = this.dotCanvas.getContext("2d");n this.dotCtx.width = 6 * this.r;n this.dotCtx.height = 6 * this.r;n this.dotCtx.a = 0.5;n this.color = "rgba(255,255,255," + this.a +")";n this.dotCtx.color = "rgba(255,255,255," + this.dotCtx.a + ")";n this.linkColor = "rgba(255,255,255," + this.a/4 + ")";n this.dir = Math.floor(Math.random()*140)+200;nn if( useCache){n this.cache()n }n }n

讓每一個 dot 動起來

Dot.prototype = {n draw : function () {n if( !this.useCache){n ctx.save();n ctx.fillStyle = this.color;n ctx.shadowColor = "white";n ctx.shadowBlur = this.r * 2;n ctx.beginPath();n ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI, false);n ctx.closePath();n ctx.fill();n ctx.restore();n }else{n ctx.drawImage(this.dotCanvas, this.x - this.r * 3, this.y - this.r *3);nn }n },nn cache : function () {n this.dotCtx.save();n this.dotCtx.a -= this.aReduction;n this.dotCtx.color = "rgba(255,255,255," + this.dotCtx.a + ")";n this.dotCtx.fillStyle = this.dotCtx.color;n this.dotCtx.shadowColor = "white";n this.dotCtx.shadowBlur = this.r * 2;n this.dotCtx.beginPath();n this.dotCtx.arc(this.r * 3, this.r * 3, this.r, 0, 2 * Math.PI, false);n this.dotCtx.closePath();n this.dotCtx.fill();n this.dotCtx.restore();n },n link : function () {n if (this.id == 0) return;n var previousDot1 = getPreviousDot(this.id, 1);n var previousDot2 = getPreviousDot(this.id, 2);n var previousDot3 = getPreviousDot(this.id, 3);n var previousDot4 = getPreviousDot(this.id, 4);nnn if (!previousDot1) return;n ctx.strokeStyle = this.linkColor;n ctx.moveTo(previousDot1.x, previousDot1.y);n ctx.beginPath();n ctx.lineTo(this.x, this.y);n if (previousDot2 != false) ctx.lineTo(previousDot2.x, previousDot2.y);n if (previousDot3 != false) ctx.lineTo(previousDot3.x, previousDot3.y);n if (previousDot4 != false) ctx.lineTo(previousDot4.x, previousDot4.y);nn ctx.stroke();n ctx.closePath();n },nn move : function () {nnn this.a -= this.aReduction;n if(this.a <= 0 ){n this.die();n returnn }n this.dotCtx.a -= this.aReduction;n this.dotCtx.color = "rgba(255,255,255," + this.dotCtx.a + ")";n this.color = "rgba(255,255,255," + this.a + ")";n this.linkColor = "rgba(255,255,255," + this.a/4 + ")";n this.x = this.x + Math.cos(degToRad(this.dir)) * this.speed;n this.y = this.y + Math.sin(degToRad(this.dir)) * this.speed;nn this.draw();n this.link();nn },nn die : function () {n dots[this.id] = null;n delete dots[this.id];n }n };n

滑鼠移入事件監聽

此外,我們還需要設置一些其他的函數和對滑鼠移入事件的監聽,這裡就不再贅述了,感興趣的同學可以直接到 github 下載源碼。

canvas 離屏渲染優化

我所使用的離屏優化是基於此文,原文寫的很好,大家感興趣的話可以去看一下:http://www.cnblogs.com/axes/p/3567364.html?utm_source=tuicool&utm_medium=referral。 因為這個效果之前我也在博客用當做背景過,不少同學都反應很卡,所以我就找了下優化的教程做了下優化,我發現對性能影響最大的可能就是 canvas 的離屏渲染優化了,這也是 canvas 的最常見優化之一。

名字聽起來很複雜,什麼離屏渲染,其實就是設置緩存,繪製圖像的時候在屏幕之外的地方繪製好,然後再直接拿過來用,這不就是緩存的概念嗎?!︿( ̄︶ ̄)︿.

建立兩個 canvas 標籤,大小一致,一個正常顯示,一個隱藏(緩存用的,不插入dom中),先將結果draw緩存用的canvas上下文中,因為遊離canvas不會造成ui的渲染,所以它不會展現出來,再把緩存的內容整個裁剪再 draw 到正常顯示用的 canvas 上,這樣能優化不少。

其實已經體現在上述的代碼中的,比如,創建 star 的代碼中:

/* 設置單個starn * @param id:idn * @param x:x坐標n * @param y:y坐標n * @param useCache:是否使用緩存n * */n function Star(id, x, y, useCache) {n this.id = id;n this.x = x;n this.y = y;n this.useCacha = useCache;n this.cacheCanvas = document.createElement("canvas");n this.cacheCtx = this.cacheCanvas.getContext("2d");n this.r = Math.floor(Math.random() * star_r) + 1;n this.cacheCtx.width = 6 * this.r;n this.cacheCtx.height = 6 * this.r;n var alpha = ( Math.floor(Math.random() * 10) + 1) / star_alpha;n this.color = "rgba(255,255,255," + alpha + ")";n if (useCache) {n this.cache()n }n }n

細心的同學可能就會發現

this.cacheCanvas = document.createElement("canvas");n this.cacheCtx = this.cacheCanvas.getContext("2d");n

這段代碼就是又創建了一個 canvas 標籤,然後再 star 的原型中有一個 cache 方法,這個 cache 方法就是在剛剛創建的 canvas 中繪製 star,而不是直接在原來的 canvas 畫布中繪製的。

cache : function () {n this.cacheCtx.save();n this.cacheCtx.fillStyle = this.color;n this.cacheCtx.shadowColor = "white";n this.cacheCtx.shadowBlur = this.r * 2;n this.cacheCtx.beginPath();n this.cacheCtx.arc(this.r * 3, this.r * 3, this.r, 0, 2 * Math.PI);n this.cacheCtx.closePath();n this.cacheCtx.fill();n this.cacheCtx.restore();n },n

之後我們需要將我們繪製的離屏 canvas 使用 drawImage 方法插入到我們最先開始創建的 canvas 畫布中。

這裡要注意的是,創建的離屏 canvas 的大小,因為太大的話同樣會浪費性能,所以我們可以創建和我們每一個 star 粒子相同的 canvas ,但是這個例子中不適用,要將離屏的 canvas 設置的稍微大一些,因為我們還需要設置發光的效果(也就是設置陰影)。

發福利

發福利的時間到了~╰( ̄▽ ̄)╭,很多小夥伴對 canvas 不是很感興趣,但是想直接使用這個效果,於是我就將其封裝起來,你只需要引入這個 JS,在 HTML 中添加一個 id 為 canvas 的標籤,然後設置相應的 CSS 就可以~

github 下載地址:github.com/sunshine9403

在 README 中有使用方法因為是第一次自己封裝函數,自己一個人在不停的摸索中前進,所以還有很多的不足,希望有大神可以指點一二


推薦閱讀:

vue-schart : vue.js 的圖表組件
canvas可以替代html與css了嗎?

TAG:前端开发 | Canvas |