最終效果: codepencanvas
一開始都是一個單一的用例,定位在畫布中央,再擴展開來數組
先獲取canvas元素及可視寬高dom
let canvas = document.querySelector('#canvas')
let context = canvas.getContext('2d')
let cw = canvas.width = window.innerWidth
let ch = canvas.height = window.innerHeight
複製代碼
// 建立一個閃爍圓的類
class Kirakira {
constructor(){
// 目標點,這裏先指定爲屏幕中央
this.targetLocation = {x: cw/2, y: ch/2}
this.radius = 1
}
draw() {
// 繪製一個圓
context.beginPath()
context.arc(this.targetLocation.x, this.targetLocation.y, 5, 0, Math.PI * 2)
context.lineWidth = 2
context.strokeStyle = '#FFFFFF';
context.stroke()
}
update(){
if(this.radius < 5){
this.radius += 0.3
}else{
this.radius = 1
}
}
init() {
this.draw()
}
}
class Animate {
run() {
window.requestAnimationFrame(this.run.bind(this))
if(o){
o.init()
}
}
}
let o = new Kirakira()
let a = new Animate()
a.run()
複製代碼
由此,能夠看到一個由小到大擴張的圓。因爲沒有擦除上一幀,每一幀的繪製結果都顯示出來,因此呈現出來的是一個實心的圓。我想繪製的是一個閃爍的圓,那麼能夠把上一幀給擦除。函數
context.clearRect(0, 0, cw, ch)
複製代碼
首先,先畫一由底部到畫布中央的延伸線。既然是運動的延伸線條,那起碼會有一個起點座標和一個終點座標動畫
class Biubiubiu {
constructor(startX, startY, targetX, targetY){
this.startLocation = {x: startX, y: startY}
// 運動當前的座標,初始默認爲起點座標
this.nowLoaction = {x: startX, y: startY}
this.targetLocation = {x: targetX, y: targetY}
}
draw(){
context.beginPath()
context.moveTo(this.startLocation.x, this.startLocation.y)
context.lineWidth = 3
context.lineCap = 'round'
// 線條須要定位到當前的運動座標,才能使線條運動起來
context.lineTo(this.nowLoaction.x, this.nowLoaction.y)
context.strokeStyle = '#FFFFFF'
context.stroke()
}
update(){}
init(){
this.draw()
this.update()
}
}
class Animate {
run() {
window.requestAnimationFrame(this.run.bind(this))
context.clearRect(0, 0, cw, ch)
if(b){
b.init()
}
}
}
// 這裏的打算是定位起點在畫布的底部隨機位置, 終點在畫布中央。
let b = new Biubiubiu(Math.random()*(cw/2), ch, cw/2, ch/2)
let a = new Animate()
a.run()
複製代碼
已知座標起點和座標終點, 那麼問題來了,要怎麼知道從起點到終點的每一幀的座標呢 ui
對於座標間距離的計算,很明顯的可使用勾股定理完成。
設起點座標爲x0, y0
, 終點座標爲x1, y1
,便可得 distance = √(x1-x0)² + (y1-y0)²
,用代碼表示則是Math.sqrt(Math.pow((x1-x0), 2) + Math.pow((y1-y0), 2))
this
上一幀的總距離(d) + 當前幀下走過的路程(v) = 當前幀的距離(D)
假設一個速度 speed = 2
, 起點和終點造成的角度爲(θ), 路程(v)的座標分別爲vx, vy
那麼 vx = cos(θ) * speed, vy = sin(θ) * speed
因爲起點(x0, y0)
和終點(x1, y1)
已知,由圖可知,經過三角函數中的tan
能夠取到兩點成線和水平線之間的夾角角度,代碼表示爲Math.atan2(y1 - y0, x1 - x0)
spa
回到繪製延伸線的代碼。 給Biubiubiu類添加上角度和距離的計算,code
class Biubiubiu {
constructor(startX, startY, targetX, targetY){
...
// 到目標點的距離
this.targetDistance = this.getDistance(this.startLocation.x, this.startLocation.y, this.targetLocation.x, this.targetLocation.y);
// 速度
this.speed = 2
// 角度
this.angle = Math.atan2(this.targetLocation.y - this.startLocation.y, this.targetLocation.x - this.startLocation.x)
// 是否到達目標點
this.arrived = false
}
draw(){ ... }
update(){
// 計算當前幀的路程v
let vx = Math.cos(this.angle) * this.speed
let vy = Math.sin(this.angle) * this.speed
// 計算當前運動距離
let nowDistance = this.getDistance(this.startLocation.x, this.startLocation.y, this.nowLoaction.x+vx, this.nowLoaction.y+vy)
// 若是當前運動的距離超出目標點距離,則不須要繼續運動
if(nowDistance >= this.targetDistance){
this.arrived = true
}else{
this.nowLoaction.x += vx
this.nowLoaction.y += vy
this.arrived = false
}
}
getDistance(x0, y0, x1, y1) {
// 計算兩座標點之間的距離
let locX = x1 - x0
let locY = y1 - y0
// 勾股定理
return Math.sqrt(Math.pow(locX, 2) + Math.pow(locY, 2))
}
init(){
this.draw()
this.update()
}
}
class Animate { ... }
// 這裏的打算是定位起點在畫布的底部隨機位置, 終點在畫布中央。
let b = new Biubiubiu(Math.random()*(cw/2), ch, cw/2, ch/2)
let a = new Animate()
a.run()
複製代碼
因爲speed
是固定的,這裏呈現的是勻速運動。能夠加個加速度``,使其改變爲變速運動。 個人目標效果並非一整條線條,而是當前運行的一截線段軌跡。這裏有個思路,把必定量的座標點存爲一個數組,在繪製的時候能夠由數組內的座標指向當前運動的座標,並在隨着幀數變化不停對數組進行數據更替,由此能夠繪製出一小截的運動線段cdn
實現代碼:
class Biubiubiu {
constructor(startX, startY, targetX, targetY) {
...
// 線段集合, 每次存10個,取10個幀的距離
this.collection = new Array(10)
}
draw() {
context.beginPath()
// 這裏改成由集合的第一位開始定位
try{
context.moveTo(this.collection[0][0], this.collection[0][1])
}catch(e){
context.moveTo(this.nowLoaction.x, this.nowLoaction.y)
}
...
}
update(){
// 對集合進行數據更替,彈出數組第一個數據,並把當前運動的座標push到集合。只要取數組的頭尾兩個座標相連,則是10個幀的長度
this.collection.shift()
this.collection.push([this.nowLoaction.x, this.nowLoaction.y])
// 給speed添加加速度
this.speed *= this.acceleration
...
}
}
複製代碼
由上面的延伸線的代碼,擴展開來,若是不取10幀,取個兩三幀的小線段,而後改變延伸方向,多條射線組合,就能夠造成了爆炸效果。火花是會受重力,摩擦力等影響到,擴散趨勢是偏向下的,因此須要加上一些重力,摩擦力系數
class Boom {
// 爆炸物是沒有肯定的結束點座標, 這個能夠經過設定必定的閥值來限定
constructor(startX, startY){
this.startLocation = {x: startX, y: startY}
this.nowLocation = {x: startX, y: startY}
// 速度
this.speed = Math.random()*10+2
// 加速度
this.acceleration = 0.95
// 沒有肯定的結束點,因此沒有固定的角度,能夠隨機角度擴散
this.angle = Math.random()*Math.PI*2
// 這裏設置閥值爲100
this.targetCount = 100
// 當前計算爲1,用於判斷是否會超出閥值
this.nowNum = 1
// 透明度
this.alpha = 1
// 重力系數
this.gravity = 0.98
this.decay = 0.015
// 線段集合, 每次存10個,取10個幀的距離
this.collection = new Array(CONFIG.boomCollectionCont)
// 是否到達目標點
this.arrived = false
}
draw(){
context.beginPath()
try{
context.moveTo(this.collection[0][0], this.collection[0][1])
}catch(e){
context.moveTo(this.nowLocation.x, this.nowLocation.y)
}
context.lineWidth = 3
context.lineCap = 'round'
context.lineTo(this.nowLocation.x, this.nowLocation.y)
// 設置由透明度減少產生的漸隱效果,看起來沒這麼突兀
context.strokeStyle = `rgba(255, 255, 255, ${this.alpha})`
context.stroke()
}
update(){
this.collection.shift()
this.collection.push([this.nowLocation.x, this.nowLocation.y])
this.speed *= this.acceleration
let vx = Math.cos(this.angle) * this.speed
// 加上重力系數,運動軌跡會趨向下
let vy = Math.sin(this.angle) * this.speed + this.gravity
// 當前計算大於閥值的時候的時候,開始進行漸隱處理
if(this.nowNum >= this.targetCount){
this.alpha -= this.decay
}else{
this.nowLocation.x += vx
this.nowLocation.y += vy
this.nowNum++
}
// 透明度爲0的話,能夠進行移除處理,釋放空間
if(this.alpha <= 0){
this.arrived = true
}
}
init(){
this.draw()
this.update()
}
}
class Animate {
constructor(){
// 定義一個數組作爲爆炸點的集合
this.booms = []
// 避免每幀都進行繪製致使的過量繪製,設置閥值,到達閥值的時候再進行繪製
this.timerTarget = 80
this.timerNum = 0
}
pushBoom(){
// 實例化爆炸效果,隨機條數的射線擴散
for(let bi = Math.random()*10+20; bi>0; bi--){
this.booms.push(new Boom(cw/2, ch/2))
}
}
run() {
window.requestAnimationFrame(this.run.bind(this))
context.clearRect(0, 0, cw, ch)
let bnum = this.booms.length
while(bnum--){
// 觸發動畫
this.booms[bnum].init()
if(this.booms[bnum].arrived){
// 到達目標透明度後,把炸點給移除,釋放空間
this.booms.splice(bnum, 1)
}
}
if(this.timerNum >= this.timerTarget){
// 到達閥值,進行爆炸效果的實例化
this.pushBoom()
this.timerNum = 0
}else{
this.timerNum ++
}
}
}
let a = new Animate()
a.run()
複製代碼
合併代碼的話,主要是個順序問題。
地點上,閃爍圓的座標點便是射線的目標終點,同時也是爆炸效果的座標起點。 時間上,在和射線到達終點後,再觸發爆炸方法便可。
let canvas = document.querySelector('#canvas')
let context = canvas.getContext('2d')
let cw = canvas.width = window.innerWidth
let ch = canvas.height = window.innerHeight
function randomColor(){
// 返回一個0-255的數值,三個隨機組合爲一塊兒可定位一種rgb顏色
let num = 3
let color = []
while(num--){
color.push(Math.floor(Math.random()*254+1))
}
return color.join(', ')
}
class Kirakira {
constructor(targetX, targetY){
// 指定產生的座標點
this.targetLocation = {x: targetX, y: targetY}
this.radius = 1
}
draw() {
// 繪製一個圓
context.beginPath()
context.arc(this.targetLocation.x, this.targetLocation.y, this.radius, 0, Math.PI * 2)
context.lineWidth = 2
context.strokeStyle = `rgba(${randomColor()}, 1)`;
context.stroke()
}
update(){
// 讓圓進行擴張,實現閃爍效果
if(this.radius < 5){
this.radius += 0.3
}else{
this.radius = 1
}
}
init() {
this.draw()
this.update()
}
}
class Biubiubiu {
constructor(startX, startY, targetX, targetY) {
this.startLocation = {x: startX, y: startY}
this.targetLocation = {x: targetX, y: targetY}
// 運動當前的座標,初始默認爲起點座標
this.nowLoaction = {x: startX, y: startY}
// 到目標點的距離
this.targetDistance = this.getDistance(this.startLocation.x, this.startLocation.y, this.targetLocation.x, this.targetLocation.y);
// 速度
this.speed = 2
// 加速度
this.acceleration = 1.02
// 角度
this.angle = Math.atan2(this.targetLocation.y - this.startLocation.y, this.targetLocation.x - this.startLocation.x)
// 線段集合
this.collection = []
// 線段集合, 每次存10個,取10個幀的距離
this.collection = new Array(CONFIG.biuCollectionCont)
// 是否到達目標點
this.arrived = false
}
draw() {
context.beginPath()
try{
context.moveTo(this.collection[0][0], this.collection[0][1])
}catch(e){
context.moveTo(this.nowLoaction.x, this.nowLoaction.y)
}
context.lineWidth = 3
context.lineCap = 'round'
context.lineTo(this.nowLoaction.x, this.nowLoaction.y)
context.strokeStyle = `rgba(${randomColor()}, 1)`;
context.stroke()
}
update() {
this.collection.shift()
this.collection.push([this.nowLoaction.x, this.nowLoaction.y])
this.speed *= this.acceleration
let vx = Math.cos(this.angle) * this.speed
let vy = Math.sin(this.angle) * this.speed
let nowDistance = this.getDistance(this.startLocation.x, this.startLocation.y, this.nowLoaction.x+vx, this.nowLoaction.y+vy)
if(nowDistance >= this.targetDistance){
this.arrived = true
}else{
this.nowLoaction.x += vx
this.nowLoaction.y += vy
this.arrived = false
}
}
getDistance(x0, y0, x1, y1) {
// 計算兩座標點之間的距離
let locX = x1 - x0
let locY = y1 - y0
// 勾股定理
return Math.sqrt(Math.pow(locX, 2) + Math.pow(locY, 2))
}
init() {
this.draw()
this.update()
}
}
class Boom {
// 爆炸物是沒有肯定的結束點座標, 這個能夠經過設定必定的閥值來限定
constructor(startX, startY){
this.startLocation = {x: startX, y: startY}
this.nowLocation = {x: startX, y: startY}
// 速度
this.speed = Math.random()*10+2
// 加速度
this.acceleration = 0.95
// 沒有肯定的結束點,因此沒有固定的角度,能夠隨機角度擴散
this.angle = Math.random()*Math.PI*2
// 這裏設置閥值爲100
this.targetCount = 100
// 當前計算爲1,用於判斷是否會超出閥值
this.nowNum = 1
// 透明度
this.alpha = 1
// 透明度減小梯度
this.grads = 0.015
// 重力系數
this.gravity = 0.98
// 線段集合, 每次存10個,取10個幀的距離
this.collection = new Array(10)
// 是否到達目標點
this.arrived = false
}
draw(){
context.beginPath()
try{
context.moveTo(this.collection[0][0], this.collection[0][1])
}catch(e){
context.moveTo(this.nowLoaction.x, this.nowLoaction.y)
}
context.lineWidth = 3
context.lineCap = 'round'
context.lineTo(this.nowLocation.x, this.nowLocation.y)
// 設置由透明度減少產生的漸隱效果,看起來沒這麼突兀
context.strokeStyle = `rgba(${randomColor()}, ${this.alpha})`
context.stroke()
}
update(){
this.collection.shift()
this.collection.push([this.nowLocation.x, this.nowLocation.y])
this.speed *= this.acceleration
let vx = Math.cos(this.angle) * this.speed
// 加上重力系數,運動軌跡會趨向下
let vy = Math.sin(this.angle) * this.speed + this.gravity
// 當前計算大於閥值的時候的時候,開始進行漸隱處理
if(this.nowNum >= this.targetCount){
this.alpha -= this.grads
}else{
this.nowLocation.x += vx
this.nowLocation.y += vy
this.nowNum++
}
// 透明度爲0的話,能夠進行移除處理,釋放空間
if(this.alpha <= 0){
this.arrived = true
}
}
init(){
this.draw()
this.update()
}
}
class Animate {
constructor(){
// 用於記錄當前實例化的座標點
this.startX = null
this.startY = null
this.targetX = null
this.targetY = null
// 定義一個數組作爲閃爍球的集合
this.kiras = []
// 定義一個數組作爲射線類的集合
this.bius = []
// 定義一個數組作爲爆炸類的集合
this.booms = []
// 避免每幀都進行繪製致使的過量繪製,設置閥值,到達閥值的時候再進行繪製
this.timerTarget = 80
this.timerNum = 0
}
pushBoom(x, y){
// 實例化爆炸效果,隨機條數的射線擴散
for(let bi = Math.random()*10+20; bi>0; bi--){
this.booms.push(new Boom(x, y))
}
}
run() {
window.requestAnimationFrame(this.run.bind(this))
context.clearRect(0, 0, cw, ch)
let biuNum = this.bius.length
while(biuNum-- ){
this.bius[biuNum].init()
this.kiras[biuNum].init()
if(this.bius[biuNum].arrived){
// 到達目標後,能夠開始繪製爆炸效果, 當前線條的目標點則是爆炸實例的起始點
this.pushBoom(this.bius[biuNum].nowLoaction.x, this.bius[biuNum].nowLoaction.y)
// 到達目標後,把當前類給移除,釋放空間
this.bius.splice(biuNum, 1)
this.kiras.splice(biuNum, 1)
}
}
let bnum = this.booms.length
while(bnum--){
// 觸發動畫
this.booms[bnum].init()
if(this.booms[bnum].arrived){
// 到達目標透明度後,把炸點給移除,釋放空間
this.booms.splice(bnum, 1)
}
}
if(this.timerNum >= this.timerTarget){
// 到達閥值後開始繪製實例化射線
this.startX = Math.random()*(cw/2)
this.startY = ch
this.targetX = Math.random()*cw
this.targetY = Math.random()*(ch/2)
let exBiu = new Biubiubiu(this.startX, this.startY, this.targetX, this.targetY)
let exKira = new Kirakira(this.targetX, this.targetY)
this.bius.push(exBiu)
this.kiras.push(exKira)
// 到達閥值後把當前計數重置一下
this.timerNum = 0
}else{
this.timerNum ++
}
}
}
let a = new Animate()
a.run()
複製代碼
製做過程當中衍生出來的比較好玩的效果