0%

如何做出會隨時間變化的同心圓?

文章目的

在做今年的 THE F2E 第一週番茄鐘時,遇到最大難題就是要做一個隨時間逐漸填滿的同心圓,想藉由此文章為製作流程做個紀錄。

開發思維

同心圓內圓與外圓之間是有間隔的,隨時間倒數間隔會逐漸填滿,示意圖如下:

我的想法是用3個圓去達成效果:

  • 第一個圓在最外圍負責最外圍的 border 顯示。
  • 第二個圓就是兩圓之間的間隔,它會隨時間變色進而達到效果。
  • 第三個圓就是內圓,與第一個圓有大小差距,進而產生間隔效果。

SVG 與 Stroke 介紹

在這次效果製作中,這兩者扮演了很重要的角色,因此先來為它們做個簡單介紹。

  • SVG 是向量標籤,我們可以在向量標籤裡透過座標的方式畫出不同的圖案。
  • Stroke 是 SVG 的一種屬性,它代表邊框的意思,它總共有5種屬性:
    • stroke:邊框顏色
    • stroke-width:邊框寬度
    • stroke-dasharray:虛線
    • stroke-linecap:邊框端點的屬性 ( butt ( 預設 )、square、round ) —本次開發不會用到
    • strkoe-linejoin:邊框接合尖角的屬性 ( miter ( 預設 )、round、bevel ) —本次開發不會用到

開發流程

畫出前兩個同心圓

我們先創造一個 SVG 標籤並且在裡面畫出一個圓,SVG 就是我們的第一個圓,SVG 裡的圓就是我們的第二個圓。

1
2
3
<svg style="transform:rotate(-90deg);width:540px;height:540px;" class="rounded-circle">
circle cx='270' cy='270' r='135' fill='none'>
</svg>

這邊我定義了外圓 SVG 的長寬(540px),並令它呈現圓形(rounded-circle),內部 circle 方面則是畫出大小是外圓的一半的圓,至於外圓的 SVG 再記得給它畫 border線即可。
先為 circle 裡的屬性做個解釋:

  • cx:圓心的 x 座標
  • cy:圓心的 y 座標
  • r:圓的半徑
  • fill:填色

因為我是要做出同心圓所以 cx 和 cy 的座標就是外圓的圓心位置。

利用 stroke 做出填色效果

我們將 stroke 屬性下在 circle 裡,因為要做效果的是第二個圓
前面有提到 stroke-width 可以控制邊框的寬度,在這邊我們就設定成 270px 讓它可以填滿第二個圓,並利用 stroke 設定邊框顏色。
做到這裡重點來了,我們將利用 stroke-dasharray 和 stroke-dashoffset 來做出效果。

  • stroke-dasharray 是我們的虛線,假設我們設定 stroke-dasharray="60",呈現出的效果會是 60px 的虛線跟 60px 的空格一組一組呈現。
  • stroke-dashoffset 則會推移我們的虛線,假設我們設定 stroke-dashoffset="40",第一個虛線就會僅剩 20px 後面的循環則是正常進行。
    利用這兩個性質,將 stroke-dasharray 動態綁定到 Vue.js 裡的一個函數,綁定的函數負責幫我們算出圓周長並回傳給我們,因此 stroke-dasharray 就會是我們的圓周長。
    第一個虛線就是整個圓周代表一開始虛線就會填滿整個圓,所以我們就要控制 stroke-dashoffset 讓它來決定虛線出現的大小。
    我們一樣將 stroke-dashoffset 動態綁定到 Vue.js 的另一個函數,這個函數會幫我們算出符合的 stroke-dashoffset 回傳給我們。
    講到這裡是不是茫了,沒關係我們來看看程式碼就會知道是怎麼回事。
    1
    2
    3
    4
    <svg style="transform:rotate(-90deg);width:540px;height:540px;" class="rounded-circle">
    <circle cx='270' cy='270' r='135' stroke-width="270" :stroke-dasharray='strokeDasharray(135)'
    :stroke-dashoffset='strokeDashoffset(135,Math.min(1,startTime/setTime))' fill='none'>
    </svg>
    Math.min(1,startTime/setTime) ,startTime 和 setTime 是我自定義的變數,分別代表進行中的時間(startTime)與設定的時間(setTime),Math.min 會回傳數列中的最小值。
    1
    2
    3
    4
    5
    6
    7
    8
    // 透過傳遞半徑給此函數,算出圓周長後回傳
    strokeDasharray:function(r){
    return r * 2 * Math.PI;
    },
    // 傳遞半徑和 Math.min() 參數後,算出現在剩餘的時間屬於圓周長的幾分之幾並回傳
    strokeDashoffset:function(r,el){
    return (this.strokeDasharray(r) * el );
    },
    看完程式碼之後有沒有比較清楚了,startTime 一直在減少,所以我們會一直將它與 setTime 的比值傳給我們的函數,函數會算出剩餘時間是圓周長的幾分之幾,並將其回傳,這就會是我們的 stroke-dashoffset 。
    簡單來講,這個方法就是透過 stroke-dashoffset 的推移來控制 stroke-dasharray 的出現。

製作第三個圓

至於,第三個圓就是為了讓它幫助我們擋住第二個圓多餘的部分,因為我們的 stroke 會填滿整個圓,如果不加上第三個圓就無法呈現出只有部分填滿的視覺效果。
第三個圓做法我就不多敘述,簡單來說,第三個圓圓心位置要跟前兩個圓一樣,在開發時我是用絕對定位做到這一點,再把它的長寬設定相較前兩個圓小即可。


參考資料

SVG 研究之路 (6) - stroke 邊框
CSS + SVG stroke 動態描繪