《每週一點canvas動畫》——差分函數的妙用

每週一點canvas動畫代碼文件

好像上次更新仍是十一前,這唰唰唰的就過去大半個月了,如今更新我也沒什麼很差意思的。此次咱們不涉及canvas 3D的內容,主要分享一個比較炫的動畫效果,能夠算是上一篇文章《每週一點canvas動畫》——3D點線與水波動畫的增強版。動畫效果來自codePen。在這篇文章中咱們就分析這種效果是如何實現的,若是你對源碼比較懵逼,相信看完解析就會恍然大悟。先上效果圖:git

圖片描述

1.原理分析

相比與上篇文章中簡陋的水波動畫的效果,本文的動畫效果不只可以和鼠標進行交互,並且波浪的造成更加天然,更加符合物理規律。整個動畫的造成過程就如動圖中所展現的那樣,在液麪的位置點擊鼠標,此處的液麪就會有一個比較大的起伏,而後此處的震動會向兩邊傳播,隨着能量的衰減,後面的震動幅度會愈來愈下,最後能量衰減到零,頁面趨於平靜。聽上去是否是很玄乎,感受很高深!毛主席告訴咱們千萬不要被物體的表面現象所迷惑(誰知道是誰說的呢o(^▽^)o)。下面咱們就來一步一步的分析,這其中的原理。github

首先,在靜止狀態下咱們能夠看到整個液麪就至關因而個矩形。而當咱們點擊液麪的位置時,這個矩形就發生了相應的變化。但其實並非整個矩形都發生了變化,而只是矩形的上邊發生了變化。那是如何作到僅僅讓矩形的上邊發生變化的呢?祕訣就在矩形的上邊並非簡單的從左邊的點lineTo()到右邊的點。而是由不少的點lineTo()組成。這樣講可能不太好理解,看圖說話:canvas

圖片描述

在上部咱們設置了不少的點,這些點的縱座標都是同樣的,只是在水平方向相隔必定的間距。這樣在靜止的狀態下,咱們就能夠它看見與普通的矩形別無二致。而改變這些點的位置時咱們就能同時改變矩形的形狀,從而造成不一樣的效果。segmentfault

2.差分方程

說到差分方程也許不少人會頭疼,不過也沒本法,疼就疼會吧!這個知識點在高數裏講微分方程那一節,若是不明白,就算了吧!記住下面的用法也不錯,不過爲了逼格咱們仍是簡單的介紹下。數組

在數學上,遞推關係(recurrence relation),也就是差分方程(difference equation),是一種遞推地定義一個序列的方程式:序列的每一項目是定義爲前一項的函數。某些簡單定義的遞推關係式可能會表現出很是複雜的(混沌的)性質,他們屬於數學中的非線性分析領域。

記住一點,序列的每一項是定義爲前一項的函數,咱們用的就是這個原理。他的圖像若是用matalab來繪製就是下面這樣:bash

![](pic/liquaid2.png)

只關注原函數,紅色的那條曲線就能夠了,是否是特別像水波。咱們要作的就是讓那一堆點按照這樣的波形去排列。函數

3.代碼實現

1.準備工做

下面就到了你們最喜歡的代碼時間。首先,咱們建立一個點類Vertexes, 它的做用就是定義並更新那一堆點,代碼在vertex.js中,以下:動畫

function Vertex(x,y,baseY){
        this.baseY = baseY;         //基線
        this.x = x;                 //點的座標
        this.y = y;            
        this.vy = 0;                //豎直方向的速度
        this.targetY = 0;           //目標位置
        this.friction = 0.15;       //摩擦力
        this.deceleration = 0.95;   //減速
    }
//y座標更新
Vertex.prototype.updateY = function(diffVal){
        this.targetY = diffVal + this.baseY;   //改變目標位置
        this.vy += (this.targetY - this.y);       //速度
        this.vy *= this.deceleration;
        this.y += this.vy * this.friction;     //改變座標豎直方向的位置
    }

咱們要用這個函數去建立那一堆點。回到咱們的主文件index.js中。咱們先初始化一些要用的東西:this

var canvas = document.getElementById('canvas'),
    ctx = canvas.getContext('2d'),
    W = window.innerWidth;
    H = window.innerHeight;

    canvas.width = W;
    canvas.height = H;

var color1 = "#6ca0f6",    //矩形1的顏色
    color2 = "#367aec";   //矩形2的顏色
    
var vertexes = [],    //頂點座標
    verNum = 250,     //頂點數
    diffPt = [],      //差分值

而後,建立點並把它pushvertexes中,同時也建立相應數量的差分值,一樣把它放到diffPt數組中,這樣每一個點都有了對應的差分值。spa

for(var i=0; i<verNum; i++){
    vertexes[i] = new Vertex(W/(verNum-1)*i, H/2, H/2);
    diffPt[i] = 0;                                         //初始值都爲0
}

結果是,每一個頂點的y座標都在(H/2)的高度,水平座標每隔必定的間隔取一個點。在這裏是每隔4.5個像素取一個點,這與你canvas的寬度和點的數目有關。這樣咱們就把點建立完成了,來繪製一下看看效果。

圖片描述

代碼以下:

function draw(){
        
        //矩形1
        ctx.save()
        ctx.fillStyle = color1;
        ctx.beginPath();
        ctx.moveTo(0, H);
        ctx.lineTo(vertexes[0].x, vertexes[0].y);
        for(var i=1; i<vertexes.length; i++){
            ctx.lineTo(vertexes[i].x, vertexes[i].y);
        }
        ctx.lineTo(W,H);
        ctx.lineTo(0,H);
        ctx.fill();
        ctx.restore();
        
        //矩形2
        ctx.save();
        ctx.fillStyle = color2;
        ctx.beginPath();
        ctx.moveTo(0, H);
        ctx.lineTo(vertexes[0].x, vertexes[0].y+5);
        for(var i=1; i<vertexes.length; i++){
            ctx.lineTo(vertexes[i].x, vertexes[i].y+5);
        }
        ctx.lineTo(W, H);
        ctx.lineTo(0, H);
        ctx.fill();
        ctx.restore();
}

就像你看到的那樣此時咱們的液麪徹底是靜止的(由於沒更新點嘛)。之因此要繪製兩個矩形,你看看效果圖就明白了,只是爲了更好看,你徹底能夠繪製第三層,第四層。下面咱們就來更新這些點的座標。

2.核心代碼

點的更新咱們放在了update函數中。首先,咱們設置一個初始的震盪點,緩衝變量初始差分值

var vPos = 125;  //震盪點
var dd = 15;     //緩衝
var autoDiff = 1000;  //初始差分值

這裏的震盪點就是咱們的起震位置,意思是vertexes中的第125號點開始起震,它對應的差分值就是autoDiff。它的改變會引發其餘點的變化,從而達到更新其餘差分值的效果。

function update(){
        autoDiff -= autoDiff*0.9;        //1
        diffPt[vPos] = autoDiff;         

        //左側
        for(var i=vPos-1; i>0; i--){     //2
            var d = vPos-i;
            if(d > dd){
                d=dd;
            }
            diffPt[i]-=(diffPt[i] - diffPt[i+1])*(1-0.01*d);
        }
        //右側
        for(var i=vPos+1; i<verNum; i++){   //3
            var d = i-vPos;
            if(d>dd){
                d=dd;
            }
            diffPt[i] -= (diffPt[i] - diffPt[i-1])*(1-0.01*d);
        }

        //更新Y座標
        for(var i=0; i<vertexes.length; i++){  //4
            vertexes[i].updateY(diffPt[i]);
        }
    }

如今咱們對上面的部分作詳細解釋:
代碼1: 咱們設置了起震位置的差分偏移量爲autoDiff=100,注意autoDiff -= autoDiff*0.9;, 也就是說它的值每一幀都會變化。

代碼2:爲起震位置的左邊,主要關注diffPt[i]-=(diffPt[i] - diffPt[i+1])*(1-0.01*d);這一行。i的起始位置爲124,默認差分值爲0。稍做簡單推算,你會發現,通過更新後第124號點的差分值爲99,同理第123號爲97.02。以此類推,咱們就能夠獲得第一幀的全部點的差分值。右邊同理。

代碼4:在獲得第一幀的差分值後就該調用每一個點的更新函數了,而且傳入計算好的差分值。造成的效果以下圖所示

圖片描述

看一下updateY函數,咱們把目標位置targetY設置爲差分值diffVal和基線baseY的和。而後,經過距離計算須要運動的速度vy,最後將速度做用於點的縱座標。這一段是否是與彈性動畫緩動動畫那一節很類似呢?

在緩衝係數dd的做用下,兩側的波會在擴散的過程當中愈來愈小,最後趨近於0.咱們也是經過這個變量去控制液體的粘度係數,達到粘稠度高的物體擴散的越緩慢而且起伏比較低,粘稠度低的物體擴散迅速但起伏大的效果。

隨後,由於autoDiff的不斷衰減,不一樣幅值波形的疊加造成波浪效果,最終衰減到0.液麪也就趨於平靜了

如今,咱們把update()和draw()放入動畫循環中你就會看到水波起伏而後趨於平靜的效果。

(function drawframe(){
        ctx.clearRect(0, 0, W, H);
        window.requestAnimationFrame(drawframe, canvas);
        update()
        draw();
    })()

3.鼠標交互

上面的代碼已經實現了波浪動畫的效果,可是震盪完成後就平靜了,不會再發生震盪的效果。這一步咱們就來實現點哪,哪震的效果。實現的思路很簡單:水波之因此區域平靜是由於起震位置的差分值不斷衰減的結果,咱們只須要在點擊鼠標的位置重設autoDiff就能夠了。此外,起震點的位置也要變成鼠標點擊的位置。代碼以下:

canvas.addEventListener('mousedown', function(e){
        var mouse = {x:null, y:null};

        if(e.pageX||e.pageY){
            mouse.x = e.pageX;
            mouse.y = e.pageY;
        }else{
            mouse.x = e.clientX + document.body.scrollLeft +document.documentElement.scrollLeft;
            mouse.y = e.clientY + document.body.scrollTop +document.documentElement.scrollTop;
        }

        //重設差分值
        if(mouse.y>(H/2-50) && mouse.y<(H/2 +50)){
            autoDiff = 1000;
            vPos = 1 + Math.floor((verNum - 2) * mouse.x / W);
            diffPt[vPos] = autoDiff;
        }

        console.log(mouse.x, mouse.y)

    }, false)

在獲取鼠標位置這裏應該注意一點,咱們沒有減去canvas的偏移量,這是由於在這裏canvas作的是全屏設置。因此,若是你的畫布並非全屏大小,建議你使用咱們的utils.js文件中的方法captureMouse來獲取鼠標的座標。

另外在判斷鼠標是否點擊在了液麪上,咱們設定了一個比較寬的範圍,上下共100px。這樣作的目的是讓用戶很容易就能觸發這個事件,而不是隻在頁面那惟一的一個值上才能觸發。這種作法相信你之前作過,對於比較小的物體咱們會遮罩一個大一些的透明物體,而後在該物體上作事件的觸發,便於用戶操做。

其餘的顏色改變等細小功能就不作過多的介紹了,see you!!!

相關文章
相關標籤/搜索