如何用canvas實現大波紋灌水效果

旭旭旭旭渣發表於2018-02-08

在掘金的第一篇文章,可能不專業,也可能對你有幫助,有什麼問題大家隨時交流。

效果圖

如何用canvas實現大波紋灌水效果

涉及到的知識點

貝塞爾曲線
  • 線性貝塞爾曲線

線性貝塞爾曲線函式中的t會經過由P0至P1的B(t)所描述的曲線。例如當t=0.25時,B(t)即一條由點P0至P1路徑的四分之一處。就像由0至1的連續t,B(t)描述一條由P0至P1的直線。

如何用canvas實現大波紋灌水效果

  • 二次方貝塞爾曲線

為建構二次方貝塞爾曲線,可以中介點Q0和Q1作為由0至1的t:

由P0至P1的連續點Q0,描述一條線性貝塞爾曲線。 由P1至P2的連續點Q1,描述一條線性貝塞爾曲線。 由Q0至Q1的連續點B(t),描述一條二次貝塞爾曲線。

如何用canvas實現大波紋灌水效果
如何用canvas實現大波紋灌水效果

我們在這裡用到的是下面這個方法。 CanvasRenderingContext2D.quadraticCurveTo()Canvas 2D API 新增二次貝塞爾曲線路徑的方法。它需要2個點。 第一個點是控制點,第二個點是終點。 起始點是當前路徑最新的點,當建立二次貝賽爾曲線之前,可以使用 moveTo() 方法進行改變。

由此我們就可以繪製出波浪

    ctx.beginPath();
    var offsetY = startY - radius*2*process/100;
    ctx.moveTo(startX - offset, offsetY);
    ctx.fillStyle = waveColorDeep;
    for (var i = 0; i < kWaveCount; i++) {
        var dx = i * kWaveWidth;
        var offsetX = dx + startX - offset;
        ctx.quadraticCurveTo(offsetX + kWaveWidth/4, offsetY + kWaveHeight, offsetX + kWaveWidth/2, offsetY);
        ctx.quadraticCurveTo(offsetX + kWaveWidth/4 + kWaveWidth/2, offsetY - kWaveHeight, offsetX + kWaveWidth, offsetY);
    }
    ctx.lineTo(startX + kWaveWidthAll, canvas.height);
    ctx.lineTo(startX, canvas.height);
    ctx.fill();
    ctx.closePath();
複製程式碼
請求動畫幀

window.requestAnimationFrame() 方法告訴瀏覽器您希望執行動畫並請求瀏覽器在下一次重繪之前呼叫指定的函式來更新動畫。

這麼我們就可以每次調整波浪的起始點使得產生一個波浪滾動的效果。

相容處理

每個瀏覽器對requestAnimationFrame函式的呼叫方法都有差別,所以再做一個簡單的修正。 根據Firefox的特性來看,其mozRequestAnimationFrame提供的最高FPS為60,並且會根據每一幀的計算的耗時來進行調整,比如每一幀計算用了1s,那他只會提供1FPS的動畫效果。 而Chrome的高版本同樣也實現了這個函式,叫webkitRequestAnimationFrame,可以預見未來還會有OperaoRequestAnimationFrameIEmsRequestAnimationFrame,所以這裡一併做一個簡單的相容處理:

window.requestAnimFrame = (function() {
    return window.requestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        window.oRequestAnimationFrame ||
        window.msRequestAnimationFrame ||
        function(callback) {
            window.setTimeout(callback, 1000 / 60);
        };
})();

複製程式碼

全部程式碼

剩下的都是一些基本操作,這裡就不做過多解釋,大部分程式碼都有註釋。

ss-wave-animation.js:

var canvas = document.getElementById('wave');
var ctx = canvas.getContext('2d');
// 中點座標
var centerX = canvas.width/2;
var centerY = canvas.height/2;
// 波浪總寬度
var kWaveWidthAll = 300;
// 波浪高度
var kWaveHeight = 10;
// 波浪個數
var kWaveCount = 4;
// 偏移量
var offset = 0;
// 遮罩半徑
var radius = 90;
// 起始x
var startX = -100;
// 起始y
var startY = centerY + radius;
// 單個波浪的寬度
var kWaveWidth = kWaveWidthAll / kWaveCount;
// 將360度分成100份,那麼每一份就是rad度
var rad = Math.PI*2/100;
// 載入進度
var process = 0;
// 目標進度
var targetProcess = 20;
// 波浪顏色
var waveColorDeep = "rgba(0, 0, 255, 1)";
var waveColorLight = "rgba(0, 0, 255, 0.5)";
// 遮罩顏色
var maskColor = "rgb(255, 0, 0)";
// 環形進度條顏色
var processBgColor = "rgb(0,255,0)";
var processColor = "rgb(0,0,0)";
// 文字顏色
var textColor = "rgb(0, 0, 0)";

function draw() {
    offset -= 5;
    if (-1 * offset === kWaveWidth) offset = 0;
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // 第一個波浪
    ctx.beginPath();
    var offsetY = startY - radius*2*process/100;
    ctx.moveTo(startX - offset, offsetY);
    ctx.fillStyle = waveColorDeep;
    for (var i = 0; i < kWaveCount; i++) {
        var dx = i * kWaveWidth;
        var offsetX = dx + startX - offset;
        ctx.quadraticCurveTo(offsetX + kWaveWidth/4, offsetY + kWaveHeight, offsetX + kWaveWidth/2, offsetY);
        ctx.quadraticCurveTo(offsetX + kWaveWidth/4 + kWaveWidth/2, offsetY - kWaveHeight, offsetX + kWaveWidth, offsetY);
    }
    ctx.lineTo(startX + kWaveWidthAll, canvas.height);
    ctx.lineTo(startX, canvas.height);
    ctx.fill();
    ctx.closePath();

    // 第二個波浪
    ctx.beginPath();
    ctx.moveTo(startX - offset, offsetY);
    ctx.fillStyle = waveColorLight;
    for (var i = 0; i < kWaveCount; i++) {
        var dx = i * kWaveWidth;
        var offsetX = dx + startX - offset - offset;
        ctx.quadraticCurveTo(offsetX + kWaveWidth/4, offsetY + kWaveHeight, offsetX + kWaveWidth/2, offsetY);
        ctx.quadraticCurveTo(offsetX + kWaveWidth/4 + kWaveWidth/2, offsetY - kWaveHeight, offsetX + kWaveWidth, offsetY);
    }
    ctx.lineTo(startX + kWaveWidthAll, canvas.height);
    ctx.lineTo(startX, canvas.height);
    ctx.fill();
    ctx.closePath();

    // 遮罩
    ctx.fillStyle = maskColor;
    ctx.beginPath();
    ctx.rect(0, 0, canvas.width, canvas.height);
    ctx.arc(centerX,centerY,radius,0,Math.PI*2,true);
    ctx.fill();
    ctx.closePath();

    // 環形進度條不變部分
    ctx.strokeStyle = processBgColor;
    ctx.lineWidth = 2; //設定線寬
    ctx.beginPath();
    ctx.arc(centerX,centerY,radius,0,Math.PI*2,true);
    ctx.stroke();
    ctx.closePath();

    // 環境進度條變化部分
    ctx.lineWidth = 5;
    ctx.strokeStyle = processColor;
    ctx.beginPath();
    ctx.arc(centerX,centerY,radius,-Math.PI/2,-Math.PI/2 + process*rad,false);
    ctx.stroke();
    ctx.closePath();

    // 進度文字
    ctx.beginPath();
    ctx.textBaseline = 'middle'; // 設定文字的垂直對齊方式
    ctx.textAlign = 'center';    // 設定文字的水平對齊方式
    ctx.strokeStyle = textColor;    // 設定描邊樣式
    ctx.font = "40px Arial";     // 設定字型大小和字型
    ctx.strokeText(process.toFixed(0)+"%", 100, 100); //繪製字型,並且指定位置
    ctx.stroke(); //執行繪製
    ctx.closePath();

    if(process > targetProcess) {
        // 無限迴圈
        // process = 0;

        // 關閉無限迴圈
        cancelAnimationFrame(draw);
        return;
    };
    process += 0.1;

    requestAnimationFrame(draw);
}
draw();
複製程式碼

index.html:

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <style type="text/css">
        #wave {
            background-color: white;
            border: solid 1px black;
        }
    </style>
    <title>SSWaveAnimation</title>
</head>
<body>
<canvas id="wave" width="200px" height="200px">
</canvas>
<script type="text/javascript" src="ss-wave-animation.js"></script>
</body>
</html>
複製程式碼

最後

詳細程式碼和demo都在這裡 github.com/sosoneo/SSW…

相關文章