為了學會更多炫酷的 canvas 效果,我熬夜複習了三角函式相關的知識點

語言: CN / TW / HK

theme: scrolls-light highlight: atom-one-dark


持續創作,加速成長!這是我參與「掘金日新計劃 · 10 月更文挑戰」的第 5 天,點選檢視活動詳情

前言

大家好,我是愛吃魚的桶哥Z,最近在小破站看了很多炫酷的 canvas 效果,發現了一個原理,那就是不管簡單的或是複雜的效果,都需要用到數學中的三角函式,深感當年沒有好好學習而導致現在很多知識點都遺忘了。每當想要實現一個炫酷的效果時都力不從心,因此今天咱們就通過實現一個環繞圓心的正弦曲線動畫來回顧一下三角函式相關的知識點,併為後續實現更多的效果打下基礎。

弧度與角度之間的互相轉換

先來看一下弧度相關的介紹,如下:

弧度的定義:半徑為1的圓,不同的角度所對應的弧形的長度,根據圓周率的定義,一個完整的圓的周長是2PI半徑,由於半徑為1,所以弧度的取值範圍就是0~2*PI。

再來看一下角度相關的介紹,如下:

角度值的取值範圍是-0° 到360°,那麼每一度對應的弧度值就是2*PI/360,簡化一下就是PI/180

因此角度弧度的相互轉化方法如下:

let radian //弧度

let angle //角度

radian = angle * Math.PI / 180

angle = radian / Math.PI * 180

有了上面的這些知識點,接下來就來實現一個正弦曲線吧!

正弦曲線

首先咱們先來實現一個正弦曲線,然後再逐步來實現最終的整個效果。

老規矩,還是先來編寫一下 htmlcss,也都很簡單,具體程式碼如下: ```html

`html` 中只有一個 `canvas` 標籤;`css` 也很簡單,如下:css {margin: 0; padding: 0;} body { display: flex; justify-content: center; align-items: center; width: 100%; height: 100vh; overflow: hidden; } 就這幾行樣式,我想大家應該都已經很熟悉了,接下來 `JS` 才是我們的重點,這裡依舊採用面向物件的方式來編寫程式碼,這樣讓我們的程式碼看起來更有條理性,首先還是相關的基礎準備程式碼,如下:js class SineAnimate { constructor() { / @type {HTMLCanvasElement} / this.canvas = document.getElementById('canvas'); this.ctx = this.canvas.getContext('2d'); this.canvas.width = 800; this.canvas.height = 800; this.dots = []; } } `` 前面幾行就是基本的獲取canvas以及ctx,並設定canvas` 的寬和高,最後一行主要是用於存放我們後續生成的點,這裡用一個個小點來畫出正弦效果。

接下來我們需要先準備一下畫點的方法,我們重新定義一個新的類,方便我們後續的使用,具體程式碼如下: js class Dots { constructor(x, canvas, ctx) { this.x = x; this.y = 0; this.canvas = canvas; this.ctx = ctx; } update() { this.y = 20 * Math.sin(this.x * Math.PI / 36) + this.canvas.height / 2; } draw() { this.ctx.beginPath(); this.ctx.arc(this.x, this.y, 1, 0, Math.PI * 2, 0); this.ctx.fill(); } }Dots 這個類中,我們只需要關注 update() 方法中的 this.y 的設定。在這個方法裡面,我們使用到了 Math.sin() ,這個方法會返回一個 -11 之間的數值,表示給定角度(單位:弧度)的正弦值,然後我們還使用到了 Math.PI,這個方法是獲取的圓周率,讀過書的童鞋們都知道圓周率是什麼,這裡就不做過多的贅述了。在 JS 中我們要獲取一個夾角的弧度就需要用到 Math.PI 方法,通過一個角度 x 乘以 Math.PI 併除以一個角度 36,最終就能夠我們需要的正弦角度。

有了上述的 Dots 類後,我們就需要在 SineAnimate 類中新增一個初始化方法,並將整個正弦畫在頁面中,相關的程式碼如下: ```js class SineAnimate { constructor() { //...other code

    this.init();
}

init() {
    for (let i = 0; i < this.canvas.width; i++) {
        this.dots.push(new Dots(i, this.canvas, this.ctx));
    }

    this.draw();
}

draw() {
    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
    for (const key in this.dots) {
        let dot = this.dots[key];
        dot.update();
        dot.draw();
    }
}

}

new SineAnimate(); `` 在SineAnimate類中,定義了一個init方法,並通過當前canvas的寬度動態生成了 **800** 個點,然後還定義了一個draw方法,在這個方法中將前面生成的 **800** 個點渲染在頁面中,最後不要忘記例項化SineAnimate` 類,最終實現的正弦效果如下圖所示:

image.png

你知道為什麼上面是 800 個點嗎?因為我們在前面定義的 canvas 寬度為 800,然後在 init 方法中通過迴圈遍歷的方式不斷生成小點,最大值就是 canvas 的寬度,因此最終生成的小圓點有 800 個。

接著我們繪製一條輔助線,只需要新增一行程式碼即可,如下: ```js class SineAnimate { //...other code

draw() {
    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
    this.ctx.fillRect(0, this.canvas.height / 2, this.canvas.width, 1);
    for (const key in this.dots) {
        let dot = this.dots[key];
        dot.update();
        dot.draw();
    }
}

} `` 在SineAnimate類的draw方法中通過canvasfillRect` 方法,繪製一條輔助線,實現出來的效果如下所示:

image.png

通過上面的示例,我們可以看出正弦曲線是由多個離參考線不同距離的點組成的,我們還可以通過 fillRect 方法實現點到直線的垂直線段,只需要修改 Dots 類中的 draw 方法即可,程式碼如下: ```js class Dots { //...other code

draw() {
    //...other code
    this.ctx.fillRect(this.x, this.y, 1, this.canvas.height / 2 - this.y);
}

} `` 在Dots類的draw方法中,通過fillRect` 方法來繪製點到垂直距離的線段,實現的效果如下所示:

image.png

有了上面的這些基礎知識點,如果我們將距離改為沿圓軸排列,也就是用來定義圓的半徑,那麼是不是就可以實現一條環繞圓心的正弦曲線了?讓我們一起來實現一下吧!

環繞圓心的正弦曲線

在上面咱們已經實現了一個基本的正弦曲線,接下來通過前面的描述,咱們就一起來改造一下前面的程式碼,讓它能夠環繞一個圓,首先要改造的就是 SineAnimate 類中的 init 方法,之前咱們生成的點是按 canvas 的寬度來,現在修改為 360 即可,因為一個圓就是 360 度,所以只需要改成 360 即可,相關修改程式碼如下: ```js class SineAnimate { ...other code

init() {
    for (let i = 0; i < 360; i++) {
        this.dots.push(new Dots(i, this.canvas, this.ctx));
    }

    this.draw();
}

} 修改完 `SineAnimate` 類,接下來就需要修改 `Dots` 類了,讓我們一起來看相關的程式碼,如下:js class Dots { constructor(d, canvas, ctx) { this.d = d; this.x = 0; this.y = 0; this.r = 0; this.w = 0; this.canvas = canvas; this.ctx = ctx; } update() { this.r = this.w * Math.sin(this.d * Math.PI / 4) + 350; this.x = this.r * Math.cos(this.d * Math.PI / 180) + this.canvas.width / 2; this.y = this.r * Math.sin(this.d * Math.PI / 180) + this.canvas.height / 2; } draw() { this.ctx.beginPath(); this.ctx.arc(this.x, this.y, 1, 0, Math.PI * 2, 0); this.ctx.fill(); } } `` 在Dots類中,重新定義了圓的半徑和寬度,然後在update` 方法中,按照 360 度的角度來生成點,根據上面正弦曲線公式生成不同角度下的半徑值,然後再根據最前面的知識點,通過圓的半徑與角度得到最終確定的座標點,最終實現了將所有的點連線成一個圓,如下圖所示:

image.png

通過上面的計算可以得知,當正弦函式的振幅(this.w)為 0 時,就實現瞭如上完整的圓。只需要修改 this.w 也就是振幅,就可以看到正弦函式的輪廓,這裡我們可以隨意修改,例如將 this.w 修改為 10,實現的效果如下所示:

image.png

接著我們可以調整一下角度的間隙,直到所有的點組成一條連續的曲線,只修改要修改 SineAnimate 類中 init 方法的迴圈即可,修改程式碼如下: ```js class SineAnimate { ...other code

init() {
    for (let i = 0; i < 360; i += 0.2) {
        this.dots.push(new Dots(i, this.canvas, this.ctx));
    }
}

...other code

} `` 修改init` 中迴圈的最後一個引數,實現出來的效果如下所示:

image.png

同樣的,這裡我們也把這個圓的正弦參考線畫出來,修改 SineAnimate 中的 draw 方法,程式碼如下: ```js class SineAnimate { ...other code

draw() {
    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
    this.ctx.beginPath();
    this.ctx.arc(this.canvas.width/2, this.canvas.height/2, 350, 0, Math.PI * 2, 0);
    this.ctx.stroke();
    for (const key in this.dots) {
        let dot = this.dots[key];
        dot.update();
        dot.draw();
    }
}

} ``` 跟前面的正弦輔助線一樣的,這裡只是畫在圓上,實現的效果如下圖所示:

image.png

到這裡,咱們這個環繞圓心的正弦曲線已經有了一個大概的模樣了,但是光靜態的效果還不夠,咱們要實現一個動態的環繞圓心的正弦曲線效果,看看怎麼實現吧!

動態環繞圓心的正弦曲線

在前面咱們是直接在 SineAnimate 類的 init 方法中呼叫 draw 方法來進行繪製,這裡我們通過 requestAnimationFrame 方法來實現動畫的效果,它比 setInterval 方法要更好一些,前面介紹過,這裡也不做贅述。

接下來只需要修改 Dots 類中的振幅值,讓它不斷的變換,就能讓這個環繞圓心的正弦曲線先動起來,修改程式碼如下: js class Dots { constructor(d, canvas, ctx) { ...other code this.w = 10; this.s = 0.2; ...other code } update() { if (Math.abs(this.w) > 10) { this.s = -this.s; } this.w += this.s; ...other code } } 通過 Math.abs 獲取當前振幅的絕對值,然後不斷的修改 this.w 的值,這樣就能讓正弦曲線動起來,並將圓的輔助線去掉,實現的效果如下所示:

demo1.gif

當然,除了上面修改振幅改變正弦曲線,咱們還可以通過修改圓的角度,讓它轉動起來,只需要修改 Dots 類即可,修改的程式碼如下: js class Dots { constructor(d, canvas, ctx) { ...other code this.w = 10; this.s = 0.2; this.deg = d; ...other code } update() { if (Math.abs(this.w) > 10) { this.s = -this.s; } this.w += this.s; this.r = this.w * Math.sin(this.d * Math.PI / 4) + 350; this.x = this.r * Math.cos(this.deg * Math.PI / 180) + this.canvas.width / 2; this.y = this.r * Math.sin(this.deg * Math.PI / 180) + this.canvas.height / 2; this.deg++; this.deg = this.deg % 360; } } 當我們不斷的修改當前的圓的角度時,它就會滾動起來了,效果如下所示:

demo2.gif

最後我們再來修飾一下這個效果,只需要修改 Dots 類中的正弦曲線的相關值,以及圓的半徑,就能得到一個動態環繞圓心的正弦曲線,最終的實現和完整的程式碼可以在這裡進行檢視: 程式碼片段

總結

寫這篇文章花了三個小時,因為對於三角函式以及弧度和角度之間的知識都很模糊了,因此在查閱教程的情況下才完成了這個效果,雖然這個效果不是特別的驚豔,但是它其中包含的知識點,在後續我們製作 canvas 相關的其它效果是都是必不可少的知識點,因此這一節的內容真的值得大家好好的去學習一下。

最後,如果這篇文章有幫助到你,❤️關注+點贊❤️鼓勵一下作者,謝謝大家

往期回顧

嚯,五角星還能這麼玩?快摘下來送給你的她/他/ta😁

這個國慶,帶老婆去看一場煙花雨