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

canvas入門

本文首發(fā)于我的個人博客:http://cherryblog.site/
github項(xiàng)目地址:https://github.com/sunshine940326/canvasStar
項(xiàng)目演示地址:https://sunshine940326.github.io/canvasStar/

[toc]

之前看到了一個很好看的canvas效果,然后拿來做我的博客背景砖顷,不少童鞋留言說求教程解孙,并且反應(yīng)說太耗內(nèi)存,于是前一段我就重寫了一遍歇由,并且使用離屏渲染進(jìn)行優(yōu)化,效果還是挺顯著的。但是因?yàn)楫吘故莄anvas,需要一直進(jìn)行重繪哪审,所以還是比較耗內(nèi)存的,但是比優(yōu)化之前已經(jīng)好很多了虑瀑。并且最近準(zhǔn)備自己寫插件湿滓,于是就拿這個練手了,

github地址:https://github.com/sunshine940326/canvasStar

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

canvas 基本知識

什么是 canvas

canvas 是 HTML5 新定義的標(biāo)簽叽奥,通過使用腳本(通常是 JavaScript)繪制圖形。
<canvas> 標(biāo)簽只是圖形容器痛侍,相當(dāng)于一個畫布朝氓,canvas 元素本身是沒有繪圖能力的。所有的繪制工作必須在 JavaScript 內(nèi)部完成,相當(dāng)于使用畫筆在畫布上畫畫赵哲。

默認(rèn)情況下待德,<canvas> 沒有邊框和內(nèi)容。默認(rèn)是一個 300150 的畫布枫夺,所以我們創(chuàng)建了 <canvas> 之后要對其設(shè)置寬高将宪。
我們可以通過html屬性‘width’,‘height’來設(shè)置canvas的寬高橡庞,不可以通過 css 屬性來設(shè)置寬高较坛。因?yàn)橥ㄟ^ css 屬性設(shè)置的寬高會使 canvas 內(nèi)的圖像按照 300150 時的比例放大或縮小

getContext()

context 是一個封裝了很多繪圖功能的對象,我們在頁面中創(chuàng)建一個 canvas 標(biāo)簽之后扒最,首先要使用 getContext() 獲取 canvas 的上下文環(huán)境丑勤,目前 getContext() 的參數(shù)只有 2d,暫時還不支持 3d

getContext("2d") 對象是內(nèi)建的 HTML5 對象扼倘,擁有多種繪制路徑确封、矩形、圓形再菊、字符以及添加圖像的方法爪喘。

canvas 元素繪制圖像

canvas 創(chuàng)建圖形有兩種方式

context.fill()

fill() 方法填充當(dāng)前的圖像(路徑)。默認(rèn)顏色是黑色纠拔。在填充前要先使用 fillStyle 設(shè)置填充的顏色或者漸變秉剑,并且如果路徑未關(guān)閉,那么 fill() 方法會從路徑結(jié)束點(diǎn)到開始點(diǎn)之間添加一條線稠诲,以關(guān)閉該路徑(正如 closePath() 一樣)侦鹏,然后填充該路徑。

context.stroke()

stroke() 方法會實(shí)際地繪制出通過 moveTo()lineTo() 方法定義的路徑臀叙。默認(rèn)顏色是黑色略水。在進(jìn)行圖形繪制前,要設(shè)置好繪圖的樣式

fillStyle()//填充的樣式
strokeStyle()//邊框樣式
context.lineWidth()//圖形邊框?qū)挾?

繪制矩形

用 canvas 繪制一個矩形很簡單

fillRect(x,y,width,height)  // 實(shí)心矩形 
strokeRect(x,y,width,height)        // 空心矩形
  • x :起始點(diǎn)的 x 坐標(biāo)
  • y :起始點(diǎn)的 y 坐標(biāo)
  • 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繪制矩形有填充顏色

我們可以看出劝萤,在沒有設(shè)置顏色的情況下渊涝,默認(rèn)是黑色的。

我們還可以通過設(shè)置 fillStyle 或者 fillStyle 改變其填充顏色床嫌。

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

效果如下


canvas繪制矩形有填充顏色

清除矩形區(qū)域

clearRect(x,y,width,height)

 - x :清除矩形起始點(diǎn)的 x 坐標(biāo)
 - y :清除矩形起始點(diǎn)的 y 坐標(biāo)
 - 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)

效果如下:

清除矩形

實(shí)心圓

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

  • x : 圓心的 x 坐標(biāo)
  • y:圓心的 y 坐標(biāo)
  • radius : 半徑
  • starAngle :開始角度
  • endAngle:結(jié)束角度
  • anticlockwise :是否逆時針(true)為逆時針跨释,(false)為順時針
context.beginPath();
context.arc(300, 350, 100, 0, Math.PI * 2, true);
//不關(guān)閉路徑路徑會一直保留下去
context.closePath();
context.fillStyle = 'rgba(0,255,0,0.25)';
context.fill();

效果如下:

canvas繪制圓弧

圓弧

如果不填充顏色,實(shí)心圓就是圓弧

    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繪制圓弧
  • 系統(tǒng)默認(rèn)在繪制第一個路徑的開始點(diǎn)為beginPath
  • 如果畫完前面的路徑?jīng)]有重新指定beginPath厌处,那么畫第其他路徑的時候會將前面最近指定的beginPath后的全部路徑重新繪制
  • 每次調(diào)用context.fill()的時候會自動把當(dāng)次繪制的路徑的開始點(diǎn)和結(jié)束點(diǎn)相連鳖谈,接著填充封閉的部分

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

canvas繪制矩形

繪制線段

  • moveTo(x,y):把路徑移動到畫布中的指定點(diǎn)阔涉,不創(chuàng)建線條
  • lineTo(x,y):添加一個新點(diǎn)缆娃,然后在畫布中創(chuàng)建從該點(diǎn)到最后指定點(diǎn)的線條
  • 每次畫線都從 moveTo 的點(diǎn)到 lineTo 的點(diǎn)捷绒,
    context.strokeStyle = 'pink';
    context.moveTo(0, 0);
    context.lineTo(100, 100);
    context.stroke();*/

效果如下:

canvas繪制片段

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

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

效果如下:

canvas繪制線段

每次lineTo后如果沒有moveTo龄恋,那么下次lineTo的開始點(diǎn)為前一次lineTo的結(jié)束點(diǎn)
例如:

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

效果如下:


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繪制六邊形

繪制 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 腳形](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:漸變開始點(diǎn)x坐標(biāo)
  • ystart:漸變開始點(diǎn)y坐標(biāo)
  • xEnd:漸變結(jié)束點(diǎn)x坐標(biāo)
  • yEnd:漸變結(jié)束點(diǎn)y坐標(biāo)
  • offset:設(shè)定的顏色離漸變結(jié)束點(diǎn)的偏移量(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繪制漸變

徑向漸變

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

  • xStart:發(fā)散開始圓心x坐標(biāo)
  • yStart:發(fā)散開始圓心y坐標(biāo)
  • radiusStart:發(fā)散開始圓的半徑
  • xEnd:發(fā)散結(jié)束圓心的x坐標(biāo)
  • yEnd:發(fā)散結(jié)束圓心的y坐標(biāo)
  • radiusEnd:發(fā)散結(jié)束圓的半徑
  • offset:設(shè)定的顏色離漸變結(jié)束點(diǎn)的偏移量(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繪制同心圓徑向漸變
//不同圓心的徑向漸變模型
    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坐標(biāo)軸按 x 比例縮放
  • y :x坐標(biāo)軸按 y 比例縮放

旋轉(zhuǎn)

rotate(angle)

  • angle :坐標(biāo)軸旋轉(zhuǎn)x角度(角度變化模型和畫圓的模型一樣)

平移

translate(x,y)

  • x :坐標(biāo)原點(diǎn)向x軸方向平移x
  • y :坐標(biāo)原點(diǎn)向y軸方向平移y

平移,縮放郭毕,旋轉(zhuǎn)先后順序不同它碎,坐標(biāo)軸的變化圖,圖片來源于網(wǎng)絡(luò):

平移縮放旋轉(zhuǎn)先后順序不同坐標(biāo)軸的變化圖

圖形組合

globalCompositeOperation=type
設(shè)置或返回新圖像如何繪制到已有的圖像上显押。最后的效果取決于 type 的值
type:

  • source-over(默認(rèn)值):在原有圖形上繪制新圖形
  • destination-over:在原有圖形下繪制新圖形
  • source-in:顯示原有圖形和新圖形的交集扳肛,新圖形在上,所以顏色為新圖形的顏色
  • destination-in:顯示原有圖形和新圖形的交集乘碑,原有圖形在上挖息,所以顏色為原有圖形的顏色
  • source-out:只顯示新圖形非交集部分
  • destination-out:只顯示原有圖形非交集部分
  • source-atop:顯示原有圖形和交集部分,新圖形在上兽肤,所以交集部分的顏色為新圖形的顏色
  • destination-atop:顯示新圖形和交集部分套腹,新圖形在下,所以交集部分的顏色為原有圖形的顏色
  • lighter:原有圖形和新圖形都顯示资铡,交集部分做顏色疊加
  • xor:重疊飛部分不現(xiàn)實(shí)
  • copy:只顯示新圖形
    效果圖如下电禀,圖片來源于網(wǎng)絡(luò)
效果圖

陰影

shadowOffsetX:設(shè)置或返回陰影距形狀的水平距離(默認(rèn)值為 0)
shadowOffsetY:設(shè)置或返回陰影距形狀的垂直距離(默認(rèn)值為 0)
shadowColor:設(shè)置或返回用于陰影的顏色
shadowBlur:設(shè)置或返回用于陰影的模糊級別(值越大越模糊)

例如:

    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 粒子的效果,因?yàn)槠溆邪咨幱暗男Ч孕荩钥雌饋硐袷前l(fā)光一樣尖飞,效果如下圖:

帶陰影效果的圓形

圖像繪制

drawImage()
向畫布上繪制圖像、畫布或視頻

  • 在畫布上定位圖像:context.drawImage(img,x,y);
  • 在畫布上定位圖像店雅,并規(guī)定圖像的寬度和高度:context.drawImage(img,x,y,width,height);
  • 剪切圖像政基,并在畫布上定位被剪切的部分:context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height);
  • img:規(guī)定要使用的圖像、畫布或視頻闹啦。
  • sx:可選沮明。開始剪切的 x 坐標(biāo)位置。
  • sy:可選窍奋。開始剪切的 y 坐標(biāo)位置珊擂。
  • swidth:可選。被剪切圖像的寬度费变。
  • sheight:可選。被剪切圖像的高度圣贸。
  • x:在畫布上放置圖像的 x 坐標(biāo)位置挚歧。
  • y:在畫布上放置圖像的 y 坐標(biāo)位置。
  • width:可選吁峻。要使用的圖像的寬度滑负。(伸展或縮小圖像)
  • height:可選在张。要使用的圖像的高度。(伸展或縮小圖像)
canvas繪制圖形例子

圖像平鋪

createPattern(image,type)
type:

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

圖像裁剪

clip()從原始畫布剪切任意形狀和尺寸的區(qū)域矮慕,需要先創(chuàng)建裁剪區(qū)域帮匾,再繪制圖像;一旦剪切了某個區(qū)域痴鳄,則所有之后的繪圖都會被限制在被剪切的區(qū)域內(nèi)(不能訪問畫布上的其他區(qū)域)瘟斜。您也可以在使用 clip() 方法前通過使用 save() 方法對當(dāng)前畫布區(qū)域進(jìn)行保存,并在以后的任意時間對其進(jìn)行恢復(fù)(通過 restore() 方法)痪寻。
例如:

    // 設(shè)置剪切區(qū)域(粉色矩形)
    context.rect(0,0,500,400);
    context.fillStyle = "pink";
    context.fill();
    context.clip();

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

    // 之后繪制的圖形只能顯示在剪切區(qū)域之內(nèi)(紅色矩形)
    context.fillStyle = "red";
    context.fillRect(100,100,600,600)

效果如下:可以看到我們設(shè)置的紅色矩形是一個 600600 的矩形螺句,但是顯然是沒有顯示完的,一旦剪切了某個區(qū)域橡类,則所有之后的繪圖都會被限制在被剪切的區(qū)域內(nèi)(不能訪問畫布上的其他區(qū)域)蛇尚。*

canvas進(jìn)行圖像剪切

所以說我們可以在使用 clip() 方法前通過使用 save() 方法對當(dāng)前畫布區(qū)域進(jìn)行保存,并在以后的任意時間對其進(jìn)行恢復(fù)(通過 restore() 方法)顾画。
代碼如下:

context.save();
    // 設(shè)置剪切區(qū)域
    context.rect(0,0,500,400);
    context.fillStyle = "pink";
    context.fill();
    context.clip();

    // 在剪切區(qū)域中繪制圖形
    context.fillStyle = "white";
    context.fillRect(10,10,100,100);

    context.restore();
    // 之后繪制的圖形只能顯示在剪切區(qū)域之內(nèi)
    context.fillStyle = "red";
    context.fillRect(100,100,600,600)

這樣就可以正常顯示了:

canvas進(jìn)行圖像裁剪

繪制文字

fillText(text,x,y):繪制實(shí)心文字
strokeText():繪制文字描邊(空心)
textAlign:設(shè)置或返回文本內(nèi)容的當(dāng)前對齊方式
textBaseline:設(shè)置或返回在繪制文本時使用的當(dāng)前文本基線
font:設(shè)置或返回文本內(nèi)容的當(dāng)前字體屬性

例如:

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

效果如下:

canvas繪制文字

準(zhǔn)備工作

好的開始是成功的一半

簡單介紹了下 canvas 的常用 api取劫,大家發(fā)現(xiàn)是不是也沒有那么難呢( ̄▽ ̄)*,那么讓我們回到標(biāo)題研侣,一起來看一下這個少女心滿滿的例子是怎樣實(shí)現(xiàn)的~

canvas 其實(shí)寫一個炫酷的特效在技術(shù)上并不難谱邪,難的是你的創(chuàng)意,因?yàn)?canvas 實(shí)現(xiàn)粒子的效果還是比較驚艷的义辕,但其實(shí)代碼都是比較簡單的虾标,無非就是隨機(jī)的創(chuàng)建圖形或者路徑,當(dāng)然圖形也是閉合的路徑灌砖。在加上一定的位移就可以了璧函。但是你要設(shè)計(jì)出一個好的特效是非常不容易的。

所以我們就先來分析一下這個效果由那幾部分構(gòu)成基显,將其拆分開來蘸吓。

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

分析 star 的表現(xiàn)和行為

我們可以將其一直位移向上的粒子稱為 star窜醉,我們觀察 star 的特點(diǎn):

  • 開始創(chuàng)建時位置隨機(jī)(坐標(biāo)隨機(jī))
  • 透明度隨機(jī)
  • 創(chuàng)建時的大小在一定范圍內(nèi)(半徑在一定范圍內(nèi))
  • 勻速上升
  • 總數(shù)不變

所以我們就可以總結(jié)出 star 的特點(diǎn)就是總數(shù)固定宪萄,創(chuàng)建時坐標(biāo)和半徑還有透明度隨機(jī),勻速上升榨惰。是不是很簡單了呢[]( ̄▽ ̄)~*

分析 dot 的表現(xiàn)和行為

再讓我們來看一下隨著鼠標(biāo)移入產(chǎn)生的粒子拜英,我們稱為 dot,同理琅催,我們觀察得到 dot 的特點(diǎn)

  • 列表內(nèi)容
  • 鼠標(biāo)移動時產(chǎn)生
  • 新產(chǎn)生的 dot 和之前的 3 個 dot 產(chǎn)生連線
  • 向四周移動
  • 達(dá)到一定條件消失

這樣居凶,我們就完成了一半了呢~將事件屢清楚之后我們就可以開始著手?jǐn)]代碼了虫给!

背景的 HTML 和 CSS

其實(shí)需要的 HTML 代碼和 CSS 代碼很簡答的,HTML 只需要一行就可以了呢侠碧,設(shè)置一個漸變的背景蒙層和一個 canvas 標(biāo)簽抹估。

HTML 和 CSS 如下:


<div class="filter"></div>
<canvas id="canvas"></canvas>

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;
            }
        }

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

漸變背景

設(shè)置參數(shù)以及獲取 dom 對象

    /*
     * @var star_r:star半徑系數(shù)挨队,系數(shù)越大谷暮,半徑越大
     * @var star_alpha:生成star的透明度,star_alpha越大盛垦,透明度越低
     * @var initStarsPopulation:初始化stars的個數(shù)
     * @var move_distance:star位移的距離湿弦,數(shù)值越大,位移越大
     * @var dot_r : dot半徑系數(shù)腾夯,系數(shù)越大颊埃,半徑越大
     * @var dot_speeds : dots運(yùn)動的速度
     * @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

    /* 設(shè)置單個 star
     * @param id:id
     * @param x:x坐標(biāo)
     * @param y:y坐標(biāo)
     * @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 動起來

這里我使用的是原型的方式蝶俱,將 draw班利、cachemovedie 方法都設(shè)置在 Star 的原型上榨呆,這樣在使用 new 創(chuàng)建對象的時候罗标,每一個 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];
        }
    };

鼠標(biāo)移入事件監(jiān)聽

此外积蜻,我們還需要設(shè)置一些其他的函數(shù)和對鼠標(biāo)移入事件的監(jiān)聽闯割,這里就不再贅述了,感興趣的同學(xué)可以直接到 github 下載源碼竿拆。

canvas 離屏渲染優(yōu)化

我所使用的離屏優(yōu)化是基于此文宙拉,原文寫的很好,大家感興趣的話可以去看一下:http://www.cnblogs.com/axes/p/3567364.html?utm_source=tuicool&utm_medium=referral丙笋。
因?yàn)檫@個效果之前我也在博客用當(dāng)做背景過谢澈,不少同學(xué)都反應(yīng)很卡,所以我就找了下優(yōu)化的教程做了下優(yōu)化御板,我發(fā)現(xiàn)對性能影響最大的可能就是 canvas 的離屏渲染優(yōu)化了锥忿,這也是 canvas 的最常見優(yōu)化之一。

名字聽起來很復(fù)雜怠肋,什么離屏渲染缎谷,其實(shí)就是設(shè)置緩存,繪制圖像的時候在屏幕之外的地方繪制好,然后再直接拿過來用列林,這不就是緩存的概念嗎?!︿( ̄︶ ̄)︿.

建立兩個 canvas 標(biāo)簽,大小一致酪惭,一個正常顯示希痴,一個隱藏(緩存用的,不插入dom中)春感,先將結(jié)果draw緩存用的canvas上下文中砌创,因?yàn)橛坞xcanvas不會造成ui的渲染,所以它不會展現(xiàn)出來鲫懒,再把緩存的內(nèi)容整個裁剪再 draw 到正常顯示用的 canvas 上嫩实,這樣能優(yōu)化不少。

其實(shí)已經(jīng)體現(xiàn)在上述的代碼中的窥岩,比如甲献,創(chuàng)建 star 的代碼中:

 /* 設(shè)置單個star
     * @param id:id
     * @param x:x坐標(biāo)
     * @param y:y坐標(biāo)
     * @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()
        }
    }

細(xì)心的同學(xué)可能就會發(fā)現(xiàn)

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

這段代碼就是又創(chuàng)建了一個 canvas 標(biāo)簽,然后再 star 的原型中有一個 cache 方法颂翼,這個 cache 方法就是在剛剛創(chuàng)建的 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 方法插入到我們最先開始創(chuàng)建的 canvas 畫布中朦乏。

這里要注意的是球及,創(chuàng)建的離屏 canvas 的大小,因?yàn)樘蟮脑捦瑯訒速M(fèi)性能呻疹,所以我們可以創(chuàng)建和我們每一個 star 粒子相同的 canvas 吃引,但是這個例子中不適用,要將離屏的 canvas 設(shè)置的稍微大一些刽锤,因?yàn)槲覀冞€需要設(shè)置發(fā)光的效果(也就是設(shè)置陰影)镊尺。

發(fā)福利

發(fā)福利的時間到了~╰( ̄▽ ̄)╭,很多小伙伴對 canvas 不是很感興趣姑蓝,但是想直接使用這個效果鹅心,于是我就將其封裝起來,你只需要引入這個 JS纺荧,在 HTML 中添加一個 id 為 canvas 的標(biāo)簽旭愧,然后設(shè)置相應(yīng)的 CSS 就可以~

github 下載地址:https://github.com/sunshine940326/canvasStar

在 README 中有使用方法因?yàn)槭堑谝淮巫约悍庋b函數(shù),自己一個人在不停的摸索中前進(jìn)宙暇,所以還有很多的不足输枯,希望有大神可以指點(diǎn)一二

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市占贫,隨后出現(xiàn)的幾起案子桃熄,更是在濱河造成了極大的恐慌,老刑警劉巖型奥,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瞳收,死亡現(xiàn)場離奇詭異碉京,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)螟深,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門谐宙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人界弧,你說我怎么就攤上這事凡蜻。” “怎么了垢箕?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵划栓,是天一觀的道長。 經(jīng)常有香客問我条获,道長忠荞,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任月匣,我火速辦了婚禮钻洒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘锄开。我一直安慰自己素标,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布萍悴。 她就那樣靜靜地躺著头遭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪癣诱。 梳的紋絲不亂的頭發(fā)上计维,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天,我揣著相機(jī)與錄音撕予,去河邊找鬼鲫惶。 笑死,一個胖子當(dāng)著我的面吹牛实抡,可吹牛的內(nèi)容都是我干的欠母。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼吆寨,長吁一口氣:“原來是場噩夢啊……” “哼赏淌!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起啄清,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤六水,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體掷贾,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡睛榄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了想帅。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片懈费。...
    茶點(diǎn)故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖博脑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情叉趣,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布该押,位于F島的核電站疗杉,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蚕礼。R本人自食惡果不足惜烟具,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望奠蹬。 院中可真熱鬧朝聋,春花似錦、人聲如沸囤躁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽狸演。三九已至言蛇,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間宵距,已是汗流浹背腊尚。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留满哪,地道東北人婿斥。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像翩瓜,于是被迫代替她去往敵國和親受扳。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 一:canvas簡介 1.1什么是canvas兔跌? ①:canvas是HTML5提供的一種新標(biāo)簽 ②:HTML5 ...
    GreenHand1閱讀 4,663評論 2 32
  • --繪圖與濾鏡全面解析 概述 在iOS中可以很容易的開發(fā)出絢麗的界面效果勘高,一方面得益于成功系統(tǒng)的設(shè)計(jì),另一方面得益...
    韓七夏閱讀 2,710評論 2 10
  • 一、canvas簡介 1.1 什么是canvas华望?(了解) 是HTML5提供的一種新標(biāo)簽 Canvas是一個矩形區(qū)...
    Looog閱讀 3,936評論 3 40
  • 一蕊蝗、圖形的組合方式 globalAlpha是一個介于0和1之間的值(包括0和1),用于指定所有繪制的透明度赖舟。默認(rèn)值...
    空谷悠閱讀 1,252評論 0 0
  • 一蓬戚、canvas簡介 1.1 什么是canvas?(了解) 是HTML5提供的一種新標(biāo)簽 Canvas是一個矩形區(qū)...
    J_L_L閱讀 1,496評論 0 4