本文首發(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);
顯示如下:
我們可以看出劝萤,在沒有設(shè)置顏色的情況下渊涝,默認(rèn)是黑色的。
我們還可以通過設(shè)置 fillStyle
或者 fillStyle
改變其填充顏色床嫌。
context.fillStyle = "pink";
context.strokeStyle = "darkred";
context.fillRect(0, 0, 100, 100);
context.strokeRect(120, 0, 100, 100);
效果如下
清除矩形區(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();
效果如下:
圓弧
如果不填充顏色,實(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();
效果如圖:
- 系統(tǒng)默認(rèn)在繪制第一個路徑的開始點(diǎn)為beginPath
- 如果畫完前面的路徑?jīng)]有重新指定beginPath厌处,那么畫第其他路徑的時候會將前面最近指定的beginPath后的全部路徑重新繪制
- 每次調(diào)用context.fill()的時候會自動把當(dāng)次繪制的路徑的開始點(diǎn)和結(jié)束點(diǎn)相連鳖谈,接著填充封閉的部分
所以說,如果第一個圓弧沒有 closePath()
并且第二個圓弧沒有 beginPath()
的話就是這樣的效果:
繪制線段
-
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();*/
效果如下:
如果沒有 moveTo 那么第一次 lineTo 的效果和 moveTo 一樣,
例如:
context.strokeStyle = 'pink';
context.lineTo(100, 100);
context.lineTo(200, 200);
context.stroke();*/
效果如下:
每次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 的線段繪制各種各樣的圖形疙驾,比如繪制一個六邊形
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();
繪制 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);
效果如下:
徑向漸變
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();
//不同圓心的徑向漸變模型
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ò):
圖形組合
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
:可選在张。要使用的圖像的高度。(伸展或縮小圖像)
圖像平鋪
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ū)域)蛇尚。*
所以說我們可以在使用 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)
這樣就可以正常顯示了:
繪制文字
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)
效果如下:
準(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
班利、cache
、move
和 die
方法都設(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)一二