最近重溫黑客帝國,發現這個數字雨特效很炫酷,以前也看到網絡上有相關相似的代碼,我先本身思考了一種實現方式,最後參考網上給出的一種思路,最後寫成了一個vue插件放在npm上,下面先上特效gif
css
對於這種較爲複雜的動畫特效,canvas是首選,固然css確定也能夠作,不過確定超級複雜,代碼量巨大。首先我第一眼看到這個特效,思路是這樣的:
(1) 通常canvas用於繪製靜態的圖像,因爲本例是動畫效果,確定得調用setTimeout或者setInterval或者raf,這裏採用raf,不斷繪製圖像達到動態的效果,並且應當採用raf,它優於setTimeout/setInterval的地方在於它是由瀏覽器專門爲動畫提供的API,在運行時瀏覽器會自動優化方法的調用,而且若是頁面不是激活狀態下的話,動畫會自動暫停,有效節省了CPU開銷
(2) 繪製一個黑色的背景
(3) 因爲數字雨看上去是獨立的一條條的向下運動的數字字母序列,所以我須要新建一個DigitRain類,裏面設置了該數字雨的各類屬性,來控制該條數字雨的運動特性,代碼見下面
vue
//數字雨類(參數是配置對象)
function DigitRain(configObj){
//數字雨的位置(x軸)
this.digitRainXPos = configObj.digitRainXPos,
//數字雨的位置(y軸)
this.digitRainYPos = configObj.digitRainYPos,
//數字雨的下落速度
this.rainVelocity = configObj.rainVelocity,
//數字雨的顏色
this.rainColor = configObj.rainColor,
//數字雨的拖尾長度
this.rainTailLength = configObj.rainTailLength,
//數字雨的文本內容
this.rainText = configObj.rainText,
...
}
複製代碼
(4)而後寫一個draw方法來控制其運動,最終在canvas裏面調用fillText來畫出文字
最終我寫了一會發現困難太多,特別是文字拖尾效果的處理很麻煩,並且達不到效果,因而便做罷git
參考了網上的一種思路,這種思路可謂是化繁爲簡,並且很容易理解,不得不佩服
(1) 一樣是採用raf實現動畫效果,首先根據canvas寬度和字體大小計算出雨滴下落的列數(寬度/字體大小),採用一個rainDropArray
(長度是列數)記錄下每一個列的文字的y軸的位置,初始都爲0,核心數據結構就是這個rainDropArray
(2) requestAnimationFrame
的參數函數裏,用for循環遍歷rainDropArray
,而後用fillText
向canvas畫上文字,x軸位置就是數組的index*字體大小,y軸位置就是rainDropArray[i]
的值,並且每次fillText都用封裝的random方法獲取字符串的隨機數字字母
(3)拖尾效果的處理:這裏很巧妙,對於拖尾效果,只須要在requestAnimationFrame
的參數函數裏fillRect(0,0,.canvas.width,canvas.height)
便可,而fillStyle
設置爲rgba(0,0,0,alpha)
,這樣每次畫圖時都會畫這麼一個黑色背景,從而覆蓋了以前畫的字母,讓字母顏色變淡,達到拖尾效果,經過控制alpha的值的大小來控制拖尾的長短,注意畫圖時沒有用clearReact清除上次所畫的內容,每次都是疊加上次所畫的效果
github
requestAnimationFrame
每次只畫了紅圈內的字母,也就是對應每列的字母,其他顏色變淡的字母都是
requestAnimationFrame
之前畫出來的,只不過被新畫的黑色背景遮住了從而變暗,這樣就完美的實現了拖尾效果 (4)最開始時
rainDropArray
的每一個值都是0,且全部列下落速度同樣,所以動畫剛開始是會是以下效果
if(textYPostion>this.canvasHeight){
if(Math.random()>0.9){
this.rainDropPositionArray[i]=0;
}
}
複製代碼
整體代碼量很少,不到150行,template部分就一個canvasweb
<template>
<canvas id="vue-matrix-raindrop"></canvas>
</template>
<script>
export default {
name: 'vue-matrix-raindrop',
//插件的各類參數
props:{
//canvas寬度
canvasWidth:{
type:Number,
default:800
},
//canvas高度
canvasHeight:{
type:Number,
default:600
},
//下落字體大小
fontSize:{
type:Number,
default:20
},
//字體類型
fontFamily:{
type:String,
default:'arial'
},
//字體文本內容,會隨機從字符串裏取一個
textContent:{
type:String,
default:'abcdefghijklmnopqrstuvwxyz'
},
//字體顏色
textColor:{
type:String,
default:'#0F0',
validator:function(value){
var colorReg = /^#([0-9a-fA-F]{6})|([0-9a-fA-F]{3})$/g
return colorReg.test(value)
}
},
//canvas背景顏色,可自定義
backgroundColor:{
type:String,
default:'rgba(0,0,0,0.1)',
validator:function(value){
var reg = /^[rR][gG][Bb][Aa][\(]((2[0-4][0-9]|25[0-5]|[01]?[0-9][0-9]?),){2}(2[0-4][0-9]|25[0-5]|[01]?[0-9][0-9]?),?(0\.\d{1,2}|1|0)?[\)]{1}$/;
return reg.test(value);
}
},
//下落速度
speed:{
type:Number,
default:2,
validator:function(value){
return value%1 === 0;
}
}
},
mounted:function(){
this.initRAF();
this.initCanvas();
this.initRainDrop();
this.animationUpdate();
},
methods:{
//初始化requestAnitaionFrame,注意兼容性
initRAF(){
window.requestAnimationFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000 / 60);
};
})();
window.cancelAnimationFrame = (function () {
return window.cancelAnimationFrame ||
window.webkitCancelAnimationFrame ||
window.mozCancelAnimationFrame ||
window.oCancelAnimationFrame ||
function (id) {
window.clearTimeout(id);
};
})();
},
//初始化canvas
initCanvas(){
this.canvas = document.getElementById('vue-matrix-raindrop');
//須要判斷獲取到的canvas是不是真的canvas
if(this.canvas.tagName.toLowerCase() !== 'canvas'){
console.error("Error! Invalid canvas! Please check the canvas's id!")
}
this.canvas.width = this.canvasWidth;
this.canvas.height = this.canvasHeight;
this.canvasCtx = this.canvas.getContext('2d');
this.canvasCtx.font = this.fontSize+'px '+this.fontFamily;
this.columns = this.canvas.width / this.fontSize;
},
//初始化數字雨下落的初始y軸位置
initRainDrop(){
for(var i=0;i<this.columns;i++){
this.rainDropPositionArray.push(0);
}
},
//核心動畫函數,控制數字雨下落
animationUpdate(){
//控制雨滴下落的速度
this.speedCnt++;
//speed爲1最快,越大越慢
if(this.speedCnt===this.speed){
this.speedCnt = 0;
//繪製背景
this.canvasCtx.fillStyle=this.backgroundColor;
this.canvasCtx.fillRect(0,0,this.canvas.width,this.canvas.height);
//繪製文字
this.canvasCtx.fillStyle=this.textColor;
//遍歷每一列的數字雨,而後在canvas上繪製該數字字母
for(var i=0,len=this.rainDropPositionArray.length;i<len;i++){
this.rainDropPositionArray[i]++;
var randomTextIndex = Math.floor(Math.random()*this.textContent.length);
var randomText = this.textContent[randomTextIndex];
var textYPostion = this.rainDropPositionArray[i]*this.fontSize;
this.canvasCtx.fillText(randomText,i*this.fontSize,textYPostion);
//數字雨觸碰canvas底部則必定機率從新回到頂部繼續下落
if(textYPostion>this.canvasHeight){
if(Math.random()>0.9){
this.rainDropPositionArray[i]=0;
}
}
}
}
window.requestAnimationFrame(this.animationUpdate)
}
},
data () {
return {
canvasCtx:null,
canvas:null,
columns:0,
rainDropPositionArray:[],
speedCnt:0
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
複製代碼
github地址點這裏npm