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

2021-12-29 HTML5cn

每周一點canvas動畫代碼文件

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

1.原理分析


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

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


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

2.差分方程


說到差分方程也許很多人會頭疼,不過也沒本法,疼就疼會吧!這個知識點在高數裡講微分方程那一節,如果不明白,就算了吧!記住下面的用法也不錯,不過為了逼格我們還是簡單的介紹下。

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

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


只關注原函數,紅色的那條曲線就可以了,是不是特別像水波。我們要做的就是讓那一堆點按照這樣的波形去排列。

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中。我們先初始化一些要用的東西:

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 = [],      //差分值

然後,創建點並把它push進vertexes中,同時也創建相應數量的差分值,同樣把它放到diffPt數組中,這樣每個點都有了對應的差分值。

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!!!

原文連結

https://segmentfault.com/a/1190000007206262

原作者

我仍舊在這裡




相關焦點

  • 每周一點 canvas 動畫丨差分函數的妙用(文末有福利)
    每周一點 canvas 動畫代碼文件:https://github.com/supperjet/H5-Animation/tree/master
  • 《每周一點canvas動畫》——3D物理效果
    在上一節《每周一點canvas動畫》——3維環境搭建中我們詳細的介紹了要想在2D的畫布上實現立體效果,需要做哪些事情。
  • 《每周一點canvas動畫》——3D點線與水波動畫
    每周一點canvas動畫代碼文件在上一章中,我們介紹了很多在三維環境下物體的運動效果。
  • 《每周一點canvas動畫》——3維環境搭建
    每周一點canvas動畫代碼文件在上一篇《每周一點canvas動畫》——從2D到3D中,我們討論了要在2D的平面實現3D的效果,是一件多麼複雜的事情
  • HTML5 Canvas 動畫實例
    動畫的概念及原理1.動畫動畫是通過一幅幅靜止的,內容不同的畫面(即幀)快速播放使人們在視覺上產生運動的感覺。這是利用了人類眼睛的視覺暫留原理。利用人的這種生理特性可製作出具有高度想像力和表現力的動畫影片。
  • 打造高大上的Canvas粒子動畫
    1   繪製粒子輪廓圖首先要在canvas畫布上繪製一個由粒子組成的輪廓圖,記錄下每一個粒子的坐標,這樣才能有後續的動畫。1. 創建一個<canvas>元素,並獲取Canvas畫布渲染上下文
  • Canvas 動畫的性能優化實踐
    在實現這個動畫的過程中加深了對 canvas 動畫的一些了解,在這裡我僅是拋磚引玉的分享一下,歡迎各位大佬批評。代碼已上傳至 github 【https://github.com/wanqihua/blog】,感興趣的可以 clone 代碼到本地運行。
  • Canvas 動畫製作
    學過SVG的童鞋應該知道它是可以製作動畫,那麼Canvas是否能製作動畫呢?答案是肯定的。所以今天我們就給大家來介紹一下Canvas製作動畫。Canvas動畫製作原理簡單一句話概括:不斷的繪製與清除。Canvas實現動畫步驟(不斷循環)1、更新繪製的對象(比如位置的移動)2、清除畫布3、在畫布上重新繪製對象Canvas 動畫相關命令clearRect方法context.clearRect
  • canvas繪製折線路徑動畫
    image.png其中的效果是一個折線路徑動畫效果,如下圖所示:動畫.gif要實現以上路徑動畫,一般可以使用svg的動畫功能。或者使用canvas繪製,結合路徑數學計算來實現。動畫1.gif漸變我們知道漸變沒法沿著任意路徑,如果計算折線,分段計算漸變又很麻煩。
  • 簡單的 canvas 翻角效果
    右上角需要從無的狀態撕開一個標記,且有動畫過程,上圖是實現的效果圖,不是gif。對這個翻角效果的難點在於沒有翻開的時候露出的是dom下面的內容,實現角度來說純dom + css動畫的設計方案並沒有相出一個好的對策: 於是撿起了好久之前學的入門級別的canvas:下面說一下實現思路。
  • 必備的Canvas接口和動畫效果大全
    由於圖像的本來大小,只有圖像加載成功以後才能拿到,因此調整畫布的大小,必須放在image.onload這個監聽函數裡面。canvas.toBlob(callback, mimeType, quality)canvas.toBlob(function (blob) {...}, 'image/jpeg', 0.95)callback:回調函數。它接受生成的 Blob 對象作為參數。
  • LVGL V8 Canvas控制項詳解
    要設置畫布上的像素,使用以下函數定義:lv_canvas_set_px(canvas,                  x,                  y,                  LV_COLOR_RED)    以下宏定義為顏色的索引:    (1)
  • 超炫酷HTML5 Canvas蝴蝶飛舞動畫
    收錄於話題 #炫酷動畫 插件簡介還記得早些時候我們為大家分享過一款非常炫酷的HTML5蝴蝶3D動畫,它是基於HTML5和SVG實現的,效果十分逼真。
  • HTML5 Canvas火焰畫筆動畫
    收錄於話題 #炫酷動畫 插件簡介在上一篇文章中,我們分享了一個利用HTML5技術在Canvas上實現的煙花動畫,效果非常震撼。
  • 數學與canvas碰撞出環形進度條
    這樣,一個基本的canvas環形進度條就成型了。動畫展示靜態的東西逼格自然是不夠的,因此我們需要再搞點動畫效果裝裝逼。基礎動畫我們先簡單實現一個線性的動畫效果。基本思路是把開始角度和結束角度的差值分為N段,利用window.requestAnimationFrame依次執行動畫。比如從30°到90°,我給它分為6段,每次畫10°。
  • 屬性動畫ValueAnimator在自定義View中的使用
    @Overrideprotected void onDraw(Canvas canvas) {    super.onDraw(canvas);    canvas.drawCircle(XPoint, heightSpecSize / 2, 30, mPaint);}對外提供開始動畫的start方法:
  • HTML5炫酷體驗:Canvas Video雷人視頻
    如:<video src="movie.ogg" controls="controls"></video>  Canvas+Video  HTML5中引入新的元素canvas,其drawImage 方法允許在 canvas 中插入其他圖像( img 和 canvas
  • Canvas在超級瑪麗遊戲中的應用
    本文將會對 canvas 的一些基礎做一些大致的講解。canvas 基礎知識畫布元素canvas 標籤可以讓我們能夠使用 JavaScript 在網頁上繪製各種樣式的圖形。要訪問實際的繪圖接口, 首先我們需要創建一個上下文 (context), 它是一個對象, 提供了繪圖的接口。
  • 7款超華麗的HTML5 Canvas文字動畫特效
    在線演示/源碼下載2、超華麗CSS3 3D五彩發光文字動畫不久前我們已經為大家介紹過一些炫酷的CSS3文字動畫和HTML5文字特效,有些都非常不錯,比如最近剛分享的CSS3文字跳動旋轉動畫以及HTML5 Canvas幻彩火焰文字特效。
  • 效果炸了,自定義Drawable實現靈動的紅鯉魚動畫(上)
    此篇中的小魚動畫是模仿國外一個大牛做的 Flash 動畫,第一眼就愛上它了,簡約靈動又不失美學,於是抽空試著嘗試了一下,如下是我用 Android 實現的效果圖:二、動畫拆解拿到動畫需求或者模仿一個動畫,首先需要分析動畫主體,如何繪製部件如何活動,就此動畫外觀分析如下:從頭到尾方向的部件擺動幅度越來越大、頻率越來越高;三、技術分析