兩週前,項目裏須要實現一個紅心飄飄的點贊效果。抓耳撓腮了老半天,看了幾篇大佬的文章,終於算是摸了個七七八八。不由長嘆一聲,仍是菜啊。先來看一下效果:(傳送門進去點一波)
git
其實用大白話描述一下需求就是讓一個紅心圖片沿着貝塞爾曲線的軌跡走,而後邊走邊消失。核心在於獲得貝塞爾曲線上的一系列點。本文不會講解貝塞爾曲線的原理,由於大佬們已經講過了,並且講的比我好。參考文章以下:github
其中第二篇文章講到了生成二階和三階貝塞爾曲線可使用canvas自帶的方法:quadraticCurveTo
和bezierCurveTo
,而高階的則先獲得曲線上一系列的點,而後順次鏈接這些點來擬合高階的貝塞爾曲線。沒錯,咱們要的就是這一系列的點,有了這些點,就能夠控制紅心的軌跡了。下面是我基於做者的BezierMarker.js寫的一個demo,能夠直觀地看出高階貝塞爾曲線上的點:canvas
上面100個曲線上的點座標是由下面這段代碼計算得出的:數組
BezierMaker.prototype.bezier = function(t) { //貝塞爾公式調用 var x = 0, y = 0, bezierCtrlNodesArr = this.bezierCtrlNodesArr, n = bezierCtrlNodesArr.length - 1, self = this bezierCtrlNodesArr.forEach(function(item, index) { if(!index) { x += item.x * Math.pow(( 1 - t ), n - index) * Math.pow(t, index) y += item.y * Math.pow(( 1 - t ), n - index) * Math.pow(t, index) } else { x += self.factorial(n) / self.factorial(index) / self.factorial(n - index) * item.x * Math.pow(( 1 - t ), n - index) * Math.pow(t, index) y += self.factorial(n) / self.factorial(index) / self.factorial(n - index) * item.y * Math.pow(( 1 - t ), n - index) * Math.pow(t, index) } }) return { x: x, y: y } }
這個方法就是對貝塞爾公式的實現。以3階貝塞爾公式爲例(見下圖),它的方程須要四個控制點(P1,P2,P3,P4)和一個t值,就能計算出曲線上的某一點的座標。dom
根據給定的t
值,結合控制點的座標,算出相應t
值下的貝塞爾曲線上的點的座標。拿下圖(來自第一篇文章)來講,給定t
值爲0.25,就能夠獲得B點的座標動畫
當將t
由0遞增到1時,就能夠獲得100個曲線上的點,進而擬合出相應的曲線。當咱們拿到這一系列點時,其實問題已經解決了一大半了。this
拿到擬合點數組後,繪製軌跡就是從數組中依次拿出座標,並將紅心圖片繪製到相應的座標上。並根據當前擬合點在曲線數組中的位置,改變圖片的不透明度,就可讓紅心飄起來了,上一部分代碼,講解見註釋:spa
// 生成隨機數 function rnd () { let flag = Math.random() > 0.5 ? 1 : -1 return 80 * Math.random() * flag } class FlyHeart { constructor (ctx, img) { this.ctx = ctx; this.img = heart; // 拿到紅心的運動軌跡,一系列擬合點座標 this.bezierArr = new BezierMaker(ctx, [ {x: 187, y: 245}, {x: 170 + rnd(), y: 200}, {x: 200 + rnd() , y: 120}, {x: 140 + rnd(), y: 60}], 90).bezierArr //90表示擬合點的數量,rnd使紅心的軌跡有必定的隨機性 } draw () { // 依次取出軌跡的每一個點 let position = this.bezierArr.shift(); // 清除上次畫的 this.clear(); if (position) { this.ctx.save() // 根據當前數組長度算出透明度 this.ctx.globalAlpha = this.bezierArr.length / 30; this.ctx.drawImage(this.img, position.x , position.y, 20, 20); this.ctx.restore(); this.prevPosition = position; } } // 清除上次畫的 clear () { if (this.prevPosition) { this.ctx.clearRect(this.prevPosition.x, this.prevPosition.y, 20, 20); } } }
接下來就是給body添加點擊事件,當點擊時,就新生成一個紅心:prototype
document.body.addEventListener('click', function() { heartArr.push(new FlyHeart(ctx, heart)); }) let heartArr = [] const cvs = document.getElementById('cvs') const ctx = cvs.getContext('2d') const heart = document.getElementById('heart') //圖片 function draw () { if(heartArr.length) { for(let heart of heartArr) { heart.draw(); if(heart.bezierArr.length === 0) { heart.clear(); let index = heartArr.indexOf(heart) heartArr.splice(index, 1) } } } requestAnimationFrame(draw) } draw()
當時看到這個需求的時候,真的是束手無策,看到n階貝塞爾曲線時更是一頭霧水,可是看不懂也要看,而後看着看着,看多了也就慢慢明白了。但願沒浪費你們的時間,各位看官看完後有所收穫(完)rest