一個少女心滿滿的例子帶你入門 Canvas

sunshine小小倩發表於2017-08-06

canvas入門
canvas入門

本文首發於我的個人部落格:cherryblog.site/
github專案地址:github.com/sunshine940…
專案演示地址:sunshine940326.github.io/canvasStar/

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

github地址:github.com/sunshine940…

程式碼還有很多的不足,求大神 review (づ。◕‿‿◕。)づ~

canvas 基本知識

什麼是 canvas

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

預設情況下,<canvas> 沒有邊框和內容。預設是一個 300*150 的畫布,所以我們建立了 <canvas> 之後要對其設定寬高。

我們可以通過html屬性‘width’,‘height’來設定canvas的寬高,不可以通過 css 屬性來設定寬高。因為通過 css 屬性設定的寬高會使 canvas 內的影象按照 300*150 時的比例放大或縮小

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()//填充的樣式
strokeStyle()//邊框樣式
context.lineWidth()//圖形邊框寬度複製程式碼

繪製矩形

用 canvas 繪製一個矩形很簡單

fillRect(x,y,width,height)  // 實心矩形 
strokeRect(x,y,width,height)        // 空心矩形複製程式碼
  • x :起始點的 x 座標
  • y :起始點的 y 座標
  • width : 矩形的寬
  • height : 矩形的高
//html程式碼
<canvas id="canvas"></canvas>
//script程式碼
   var canvas = document.getElementById('canvas');
    var context = canvas.getContext('2d');
    context.fillRect(0, 0, 100, 100);
    context.strokeRect(120, 0, 100, 100);複製程式碼

顯示如下:

canvas繪製矩形有填充顏色
canvas繪製矩形有填充顏色

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

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

context.fillStyle = "pink";
context.strokeStyle = "darkred";
context.fillRect(0, 0, 100, 100);
context.strokeRect(120, 0, 100, 100);複製程式碼

效果如下

canvas繪製矩形有填充顏色
canvas繪製矩形有填充顏色

清除矩形區域

clearRect(x,y,width,height)複製程式碼
  • x :清除矩形起始點的 x 座標
  • y :清除矩形起始點的 y 座標
  • width : 清除矩形矩形的寬
  • height : 清除矩形矩形的高
    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();
    context.arc(300, 350, 100, 0, Math.PI * 2, true);
    //不關閉路徑路徑會一直保留下去
    context.closePath();
    context.fillStyle = 'rgba(0,255,0,0.25)';
    context.fill();複製程式碼
    效果如下:
    canvas繪製圓弧
    canvas繪製圓弧

圓弧

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

    context.beginPath();
    context.arc(600, 350, 100, 0, Math.PI , true);
    context.strokeStyle = 'pink';
    context.closePath();
    context.stroke();

    context.beginPath();
    context.arc(300, 350, 100, 0, Math.PI , true);
    context.strokeStyle = 'red';
    //沒有closePath
    context.stroke();複製程式碼

效果如圖:

canvas繪製圓弧
canvas繪製圓弧

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

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

canvas繪製矩形
canvas繪製矩形

繪製線段

  • moveTo(x,y):把路徑移動到畫布中的指定點,不建立線條
  • lineTo(x,y):新增一個新點,然後在畫布中建立從該點到最後指定點的線條
  • 每次畫線都從 moveTo 的點到 lineTo 的點,
     context.strokeStyle = 'pink';
     context.moveTo(0, 0);
     context.lineTo(100, 100);
     context.stroke();*/複製程式碼
    效果如下:
    canvas繪製片段
    canvas繪製片段

    如果沒有 moveTo 那麼第一次 lineTo 的效果和 moveTo 一樣,
    例如:
    context.strokeStyle = 'pink';
    context.lineTo(100, 100);
    context.lineTo(200, 200);
    context.stroke();*/複製程式碼

效果如下:

canvas繪製線段
canvas繪製線段

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

// 繪製片段
    context.strokeStyle = 'pink';
    context.lineTo(200, 200);
    context.lineTo(200, 100);
    context.lineTo(100,50);
    context.stroke();複製程式碼

效果如下:

canvas繪製線段
canvas繪製線段

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

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

使用canvas繪製六邊形
使用canvas繪製六邊形

繪製 30 邊形:

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

效果如下:

canvas繪製 30 邊形
canvas繪製 30 邊形

線性漸變

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);
    g1.addColorStop(0, '#E55D87'); 
    g1.addColorStop(1, '#5FC3E4');
    context.fillStyle = g1;
    context.fillRect(0, 0, 400, 300);複製程式碼

效果如下:

canvas繪製漸變
canvas繪製漸變

徑向漸變

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:繪製時要使用的顏色

徑向漸變原理
徑向漸變原理

例如:

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

canvas繪製同心圓徑向漸變
canvas繪製同心圓徑向漸變

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

    context.fillStyle = g1;
    context.fillRect(0, 0, 300, 500);複製程式碼

效果圖:

不同圓心徑向漸變
不同圓心徑向漸變

圖形變形

縮放

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)
shadowOffsetY:設定或返回陰影距形狀的垂直距離(預設值為 0)
shadowColor:設定或返回用於陰影的顏色
shadowBlur:設定或返回用於陰影的模糊級別(值越大越模糊)複製程式碼

例如:

    context.fillStyle = 'white';
    context.beginPath();
    context.arc(100,100,10,0,2 * Math.PI);
    context.shadowColor = 'white';
    context.shadowBlur = 10;
    context.fill();
    context.closePath();複製程式碼

我們看到的效果就是我們在開頭提起的例子中的 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:可選。要使用的影象的高度。(伸展或縮小影象)

canvas繪製圖形例子
canvas繪製圖形例子

影象平鋪

createPattern(image,type)
type:

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

影象裁剪

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

    // 設定剪下區域(粉色矩形)
    context.rect(0,0,500,400);
    context.fillStyle = "pink";
    context.fill();
    context.clip();

    // 在剪下區域中繪製圖形(白色矩形)
    context.fillStyle = "white";
    context.fillRect(10,10,100,100);

    // 之後繪製的圖形只能顯示在剪下區域之內(紅色矩形)
    context.fillStyle = "red";
    context.fillRect(100,100,600,600)複製程式碼

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

canvas進行影象剪下
canvas進行影象剪下

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

context.save();
    // 設定剪下區域
    context.rect(0,0,500,400);
    context.fillStyle = "pink";
    context.fill();
    context.clip();

    // 在剪下區域中繪製圖形
    context.fillStyle = "white";
    context.fillRect(10,10,100,100);

    context.restore();
    // 之後繪製的圖形只能顯示在剪下區域之內
    context.fillStyle = "red";
    context.fillRect(100,100,600,600)複製程式碼

這樣就可以正常顯示了:

canvas進行影象裁剪
canvas進行影象裁剪

繪製文字

fillText(text,x,y):繪製實心文字
strokeText():繪製文字描邊(空心)
textAlign:設定或返回文字內容的當前對齊方式
textBaseline:設定或返回在繪製文字時使用的當前文字基線
font:設定或返回文字內容的當前字型屬性複製程式碼

例如:

    context.font="40px Arial";
    context.fillText("Hello world",200,200);
    context.strokeText("Hello world",200,300)複製程式碼

效果如下:

canvas繪製文字
canvas繪製文字

準備工作

好的開始是成功的一半

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

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

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

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

分析 star 的表現和行為

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

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

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

分析 dot 的表現和行為

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

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

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

背景的 HTML 和 CSS

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

<div class="filter"></div>
<canvas id="canvas"></canvas>複製程式碼

CSS 如下:

html, body {
            margin: 0;
            padding: 0;
            width: 100%;
            height: 100%;
            overflow: hidden;
            background: black;
            background: linear-gradient(to bottom, #dcdcdc 0%, palevioletred 100%);
        }

        #main-canvas {
            width: 100%;
            height: 100%;
        }

        .filter {
            width: 100%;
            height: 100%;
            position: absolute;
            top: 0;
            left: 0;
            background: #fe5757;
            animation: colorChange 30s ease-in-out infinite;
            animation-fill-mode: both;
            mix-blend-mode: overlay;

        }

        @keyframes colorChange {
            0%, 100% {
                opacity: 0;
            }
            50% {
                opacity: .7;
            }
        }複製程式碼

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

漸變背景
漸變背景

設定引數以及獲取 dom 物件

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

繪製單個 star

    /* 設定單個 star
     * @param id:id
     * @param x:x座標
     * @param y:y座標
     * @param useCache:是否使用快取
     * */
    function Star(id, x, y) {
        this.id = id;
        this.x = x;
        this.y = y;
        this.cacheCanvas = document.createElement("canvas");
        this.cacheCtx = this.cacheCanvas.getContext("2d");
        this.r = Math.floor(Math.random() * star_r) + 1;
        this.cacheCtx.width = 6 * this.r;
        this.cacheCtx.height = 6 * this.r;
        var alpha = ( Math.floor(Math.random() * 10) + 1) / star_alpha;
        this.color = "rgba(255,255,255," + alpha + ")";
        if (useCache) {
            this.cache()
        }
    }複製程式碼

讓每一個 star 動起來

這裡我使用的是原型的方式,將 drawcachemovedie 方法都設定在 Star 的原型上,這樣在使用 new 建立物件的時候,每一個 star 都可以繼承這些方法。

Star.prototype = {
        draw : function () {
            if (!this.useCacha) {
                ctx.save();
                ctx.fillStyle = this.color;
                ctx.shadowBlur = this.r * 2;
                ctx.beginPath();
                ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI, false);
                ctx.closePath();
                ctx.fill();
                ctx.restore();
            } else {
                ctx.drawImage(this.cacheCanvas, this.x - this.r, this.y - this.r);
            }
        },

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

        move : function () {
            this.y -= move_distance;
            if (this.y <= -10) {
                this.y += HEIGHT + 10;
            }
            this.draw();
        },

        die : function () {
            stars[this.id] = null;
            delete stars[this.id]
        }
    };複製程式碼

繪製 dot

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

        if( useCache){
            this.cache()
        }
    }複製程式碼

讓每一個 dot 動起來

Dot.prototype = {
        draw : function () {
            if( !this.useCache){
                ctx.save();
                ctx.fillStyle = this.color;
                ctx.shadowColor = "white";
                ctx.shadowBlur = this.r * 2;
                ctx.beginPath();
                ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI, false);
                ctx.closePath();
                ctx.fill();
                ctx.restore();
            }else{
                ctx.drawImage(this.dotCanvas, this.x - this.r * 3, this.y - this.r *3);

            }
        },

        cache : function () {
            this.dotCtx.save();
            this.dotCtx.a  -= this.aReduction;
            this.dotCtx.color = "rgba(255,255,255," + this.dotCtx.a + ")";
            this.dotCtx.fillStyle = this.dotCtx.color;
            this.dotCtx.shadowColor = "white";
            this.dotCtx.shadowBlur = this.r * 2;
            this.dotCtx.beginPath();
            this.dotCtx.arc(this.r * 3, this.r * 3, this.r, 0, 2 * Math.PI, false);
            this.dotCtx.closePath();
            this.dotCtx.fill();
            this.dotCtx.restore();
        },
        link : function () {
            if (this.id == 0) return;
            var previousDot1 = getPreviousDot(this.id, 1);
            var previousDot2 = getPreviousDot(this.id, 2);
            var previousDot3 = getPreviousDot(this.id, 3);
            var previousDot4 = getPreviousDot(this.id, 4);


            if (!previousDot1) return;
            ctx.strokeStyle = this.linkColor;
            ctx.moveTo(previousDot1.x, previousDot1.y);
            ctx.beginPath();
            ctx.lineTo(this.x, this.y);
            if (previousDot2 != false) ctx.lineTo(previousDot2.x, previousDot2.y);
            if (previousDot3 != false) ctx.lineTo(previousDot3.x, previousDot3.y);
            if (previousDot4 != false) ctx.lineTo(previousDot4.x, previousDot4.y);

            ctx.stroke();
            ctx.closePath();
        },

        move : function () {


            this.a -= this.aReduction;
            if(this.a <= 0 ){
                this.die();
                return
            }
            this.dotCtx.a  -= this.aReduction;
            this.dotCtx.color = "rgba(255,255,255," + this.dotCtx.a + ")";
            this.color = "rgba(255,255,255," + this.a + ")";
            this.linkColor = "rgba(255,255,255," + this.a/4 + ")";
            this.x = this.x + Math.cos(degToRad(this.dir)) * this.speed;
            this.y = this.y + Math.sin(degToRad(this.dir)) * this.speed;

            this.draw();
            this.link();

        },

        die : function () {
            dots[this.id] = null;
            delete dots[this.id];
        }
    };複製程式碼

滑鼠移入事件監聽

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

canvas 離屏渲染優化

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

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

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

其實已經體現在上述的程式碼中的,比如,建立 star 的程式碼中:

 /* 設定單個star
     * @param id:id
     * @param x:x座標
     * @param y:y座標
     * @param useCache:是否使用快取
     * */
    function Star(id, x, y, useCache) {
        this.id = id;
        this.x = x;
        this.y = y;
        this.useCacha = useCache;
        this.cacheCanvas = document.createElement("canvas");
        this.cacheCtx = this.cacheCanvas.getContext("2d");
        this.r = Math.floor(Math.random() * star_r) + 1;
        this.cacheCtx.width = 6 * this.r;
        this.cacheCtx.height = 6 * this.r;
        var alpha = ( Math.floor(Math.random() * 10) + 1) / star_alpha;
        this.color = "rgba(255,255,255," + alpha + ")";
        if (useCache) {
            this.cache()
        }
    }複製程式碼

細心的同學可能就會發現

        this.cacheCanvas = document.createElement("canvas");
        this.cacheCtx = this.cacheCanvas.getContext("2d");複製程式碼

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

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

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

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

發福利

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

github 下載地址:github.com/sunshine940…

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

相關文章