大家好!我是貓小白,本期給大家帶來一個簡單的實戰demo,希望大家食用得開心,有收穫。
==首先聲明:此demo存粹為了學習其中的動畫知識,請不要用於真實的場景,否則被JC叔叔抓起包吃包住就不應該了瑟!==
此項目基於==成都天府健康通==,其它地區的場所碼不知道是否有類似的動畫,如果沒有也沒關係,可以學習下是如何實現的。
為啥子要做這個呢?那是因為有一天我出去買菜,回來保安叔叔恩是要我掃一下場所碼,作為一個5星好公民,肯定是非常配合的掏出手機開始掃 。叮~,屏幕上出現一個不挺放大縮小的箭頭。我就想,要不用代碼來實現一下這個動畫吧。
於是就有了這篇文章,我一共是實現了4種方法:
- 第一種,css,零js代碼(最簡單)
- 第二種,svg,零js代碼 (較簡單)
- 第三種,js+dom (較複雜)
- 第四種,js+canvas (較複雜)
全文源碼地址:點擊跳轉到github,可以拉下來親操練操練,順便給一個start!
接下來,我們一個一個來分析如何實現的。
準備工作
首先我們假裝出去買個菜(未解封的同胞除外例如上海的朋友),回來的時候去保安那裏故作鎮定,連貫的掏出手機打開天府健康通(成都的哈)掃一下,然後給保安亮一下,不退出界面,淡定回家。
回到家後,拿出手機,打開剛剛的界面,截一個圖:
加入html中,作為div容器的背景圖片,然後我們創建一個白色的框把中間的圓圈和勾遮住,因為我們要自己實現其中的動畫效果。
實現效果如下:
遮板的css代碼:
/**遮板**/
.shutter {
position: absolute;
display: block;
width: 120px;
height: 120px;
background-color: #fff;
top: 206px;
left: 150px;
}
核心html代碼結構如下,:
<body>
<div class="app">
<div class="content">
<!--遮板把原來的箭頭用白色背景遮住,自己實現箭頭-->
<div class="shutter"></div>
<!--自己實現的箭頭和圓-->
<div class="circle"></div>
</div>
</div>
</body>
只是列舉關鍵的代碼,完整代碼請到github倉庫查看。
準備工作完成,開始正片!
第一種,css,零js代碼
使用css3的animation動畫,可以很容易實現這樣的效果,不用一行js代碼。單關鍵是要看明白我們目標動畫是由哪些部分組成的,動畫是怎麼變化的。所以先再次看看這個gif,注意其中動畫部分:
不難發現:
- 圓圈是由小到大,再由大到小
- 內圈放大到一定程度時,外圈(擴散部分)出現,並且半徑不停增大,當內圈放到最大時,擴散圈消失
- 內部的勾也會一起放大縮小,頻率和內圈相同
根據上面的規律,我們先實現一個內圈(綠色圈部分)
內部圈css代碼:
/**內部圓圈**/
.circle {
position: absolute;
z-index: 9;
width: var(--circle-size);
height: var(--circle-size);
top: 218px;
left: 165px;
background-color: var(--theme-green);
/* background-color: red; */
border-radius: 45px;
animation: circleMove 0.7s alternate infinite cubic-bezier(1, 1, 1, 1);
}
--circle-size、--theme-green是我們在開頭定義的變量,方便後續使用。
:root {
--theme-green: #4ba776; /**主題綠色**/
--circle-size: 90px; /**圓圈大小 寬高一樣**/
}
內部圈畫好了,還差一個白色的"√",為了簡單我直接使用了"√"這個字符串,加入偽類元素的content中。你們也可以用圖片或者svg。
"√"的css代碼如下:
/**內部的勾,使用`.circle`的`before`偽類**/
.circle::before {
z-index: 2;//z-index要比外圈(闊散圈)大,否則要被背景覆蓋
position: absolute;
display: block;
content: "✔";
font-size: 63px;
color: #fff;
left: 18px;
top: -1px;
}
讓我們預覽下現在的效果:
喲也~看起來是那麼回事了。
現在我們按照內圈相同的顏色和大小,畫一個外部圈(擴散圈)。
參考上面的"√"的實現,我們用after偽類來實現:
/**動畫時外部漸變圈*/
.circle::after {
z-index: 1;//z-index要比內部的勾小,否則要覆蓋掉勾
position: absolute;
display: block;
content: "";
width: calc(var(--circle-size));
height: calc(var(--circle-size));
box-sizing: border-box;
border-radius: 50%;
background-color: #4ba776;
transform: scale(1.2);
/**外圈動畫 一輪時間是內圈的2倍 內部圈放大0.7s+縮小0.7s=1.4s infinite無限重複**/
/* animation: outCircleMove 1.4s infinite cubic-bezier(1, 1, 1, 1); */
}
內圈加入放大縮小動畫,在.circle中計入animation
/**內部圓圈**/
.circle {
position: absolute;
z-index: 9;
width: var(--circle-size);
height: var(--circle-size);
top: 218px;
left: 165px;
background-color: var(--theme-green);
/* background-color: red; */
border-radius: 45px;
/*加入放大縮小動畫 alternate:輪流播放,放大再放小 infinite:無限播放 ease-in-out:變化函數*/
animation: circleMove 0.7s alternate infinite ease-in-out;
}
/**定義放大縮小動畫,最大放大到1.2倍**/
@keyframes circleMove {
0% {
transform: scale(1);
}
100% {
transform: scale(1.2);
}
}
解讀:時間設置為0.7s,我們定義了circleMove 動畫,意思是在0秒的時候圓圈保持1倍大小,在0.1~0.7s圓圈逐漸變為1.2倍大小。變化的快慢是由animation-timing-function參數決定的,文中設置的ease-in-out是進入和退出較慢,中間速度較快的一種變化曲線。
我們可以打開開發者工具,點擊animation屬性的曲線圖標,查看和選擇想要的動畫曲線
![]()
好內圈動畫已經完成,我們來看下效果:
perfect!現在我們加入外圈動畫,讓我們回顧一下:外圈動畫是要在內圈放大到一定時間外圈才出現,我們暫定1/5,也就是20%的時候顯示並且開始放大(放大對應屬性:scale);放大後外圈會越來越便透明,直到內圈放到最大,此時完全透明,感覺像是擴散的效果(透明對應屬性:opacity)。
知道這些後,我們設置外圈動畫如下:
/*動畫時外部漸變圈/
.circle::after {
z-index: 1;
position: absolute;
display: block;
content: "";
width: calc(var(--circle-size));
height: calc(var(--circle-size));
box-sizing: border-box;
border-radius: 50%;
background-color: #4ba776;
/**外圈動畫 一輪時間是內圈的2倍 內部圈放大0.7s+縮小0.7s=1.4s infinite無限重複**/
animation: outCircleMove 1.4s infinite ease-in-out;
}
/**定義擴散圈的放大縮小動畫**/
@keyframes outCircleMove {
/**0%~50% 0s~ 0.7s前的時間 此時內圈放大,擴散圈等待時機出現**/
0% {
opacity: 0;
}
/**10% 此時內圈已經放大一段時間, 擴散全顯示opacity設為1,倍率為1 **/
10% {
opacity: 1;
transform: scale(1);
}
/**50%到100%是動畫0.7s~1.4s的時間 此時內圈縮小,擴散圈變為1.3倍,但是透明度設為0**/
50%,
100% {
transform: scale(1.3);
opacity: 0;
}
}
至此就大功告成了,看下效果:
第二種,svg,零js代碼 (較簡單)
上面了利用css3的動畫屬性,操作起來非常簡單。現在介紹一種用svg動畫的方式實現相同的效果。我們知道svg也是有比較豐富的動畫特性支持。
我們簡單列舉一下svg實現動畫的幾種方法:
1.set
set元素是SVG動畫元素中最簡單的元素。在經過特定時間間隔後,它只是將屬性設置為特定值。因此,形狀不會連續進行動畫處理,而只是更改屬性值一次。
2.animate
<animate>元素通常放置到一個SVG圖像元素裏面,用來定義這個圖像元素的某個屬性的動畫變化過程。
3.animateMotion
<animateMotion>元素也是放置一個圖像元素裏面,它可以引用一個事先定義好的動畫路徑,讓圖像元素按路徑定義的方式運動。
4.animateTransform
動畫上一個目標元素變換屬性,從而使動畫控制平移,縮放,旋轉或傾斜。
簡單瞭解過後,我們今天主要用animate來實現動畫。
首先,我們還是拿出上面的基礎佈局,祖傳代碼(背景+遮罩)
<div class="app">
<div class="content">
<!--遮板把原來的箭頭用白色背景遮住,自己實現箭頭-->
<div class="shutter"></div>
<!--自己實現的箭頭動畫-svg-->
<svg></svg>
</div>
</div>
</body>
基本框架的樣式和上一節css動畫的樣式相同,就不重複粘貼了。
然後我們畫一個圓和勾,也就是內部的部分;svg中畫圓是用<circle>,於是我們有這樣的代碼:
<div class="app">
<div class="content">
<!--遮板把原來的箭頭用白色背景遮住,自己實現箭頭-->
<div class="shutter"></div>
<!--自己實現的箭頭和圓-->
<svg
class="circle"
width="130"
height="130"
xmlns="http://www.w3.org/2000/svg"
>
<g>
<circle cx="45" cy="45" r="45" fill="#4ba776"></circle>
</g>
</svg>
</div>
</div>
</body>
效果如下:
繼續添加中間的白色"√"。為了簡介,我們用<text>文字標籤:
添加<text>後的代碼如下:
<div class="app">
<div class="conten ">
<!--遮板把原來的箭頭用白色背景遮住,自己實現箭頭-->
<div class="shutter"></div>
<!--自己實現的箭頭和圓-->
<svg
class="circle"
width="130"
height="130"
xmlns="http://www.w3.org/2000/svg"
>
<g>
<circle cx="65" cy="65" r="45" fill="#4ba776"></circle>
<!--text添加-->
<text font-size="63" fill="#fff" y="90" x="38">✔</text>
</g>
</svg>
</div>
</div>
</body>
添加文字後效果如下:
好了,內圈添加好了,我們再添加一個外圈(擴散圈),還是用<circle>,大小和位置都和內圈一模一樣,這裏就不粘貼代碼了。
現在來添加動畫,根據上一章節的css3動畫,我們需要不挺的放大縮小內圈的大小。所以我們通過動畫改編圓的半徑就ok了。上面介紹了<animate>標籤可以動態改變某一屬,這裏的屬性就是半徑R:
...其它代碼
<circle cx="65" cy="65" r="45" fill="#4ba776">
<!--添加到circle標籤內部,指定attributeName為半徑r,values是半徑變化的值,我們讓它先變大再變小,dur是動畫時間 repeatCount:indefinite無限循環-->
<animate
attributeName="r"
values="45;54;45"
dur="1.4s"
repeatCount="indefinite"
/>
</circle>
...其它代碼
實現效果:
完美!看起來就是那麼回事,後面的步驟其實都是依葫蘆畫瓢。
添加內部"√"的動畫,用<animate>改變font-size。
<text font-size="63" fill="#fff" y="90" x="38">
✔
<animate
attributeName="font-size"
values="63;69;63"
dur="1.4s"
repeatCount="indefinite"
/>
</text>
添加後效果我們來看下:
最後,我們添加擴散效果。擴散效果其實分兩步,第一步是放大半徑,另一步是從不透明到透明的過程。所以我們用<animate>改變半徑的同時,改變它的透明度。
...其它代碼
<!--擴散圈--->
<circle cx="65" cy="65" r="45" fill="#4ba776">
<!--設置放大縮小動畫--->
<animate
attributeName="r"
values="45;50;65;65;50;45"
dur="1.4s"
repeatCount="indefinite"
/>
<!--設置透明度變化動畫--->
<animate
attributeName="opacity"
values="1;1;0.2;0;0;0"
dur="1.4s"
repeatCount="indefinite"
/>
</circle>
...其它代碼
最終效果如下:
第三種,js+dom (較複雜)
前面都是通過css或者svg自帶特性完成的動畫效果,下面這種是通過js動態改變圓圈div大小實現的,較為複雜,理解起來需要一定的js基礎,在做項目優先選擇css3,我們一起來看下是如何實現的。
首先,我們還是拿出上面的基礎佈局,添加內外圈的div和佈局。
關鍵css部分:
/**外部圓圈**/
.circle,
.outCircle {
z-index: 1;
position: absolute;
width: var(--circle-size);
height: var(--circle-size);
top: 218px;
left: 165px;
background-color: var(--theme-green);
/* background-color: red; */
border-radius: 45px;
}
/**內部的勾**/
.circle::before {
position: absolute;
display: block;
content: "✔";
font-size: 63px;
color: #fff;
left: 18px;
top: -1px;
}
html部分:
<body>
<div class="app">
<div class="content">
<!--遮板把原來的箭頭用白色背景遮住,自己實現箭頭-->
<div class="shutter"></div>
<!--自己實現的箭頭和圓-->
<div class="circle"></div>
<div class="outCircle"></div>
</div>
</div>
</body>
至此,元素已經畫好了,就差動畫部分了,簡單分析一波:
內圈需要逐漸放大再逐漸放小,我們用一個變量size_inner來代表放大縮小的倍數,STEP_INNER代表內圈遞增或者遞減的步長,最終設置到style上的transform屬性。
外圈也需要有一個放大過程,且放大的速度比內圈快,我們用size_outer來代表放大倍數,STEP_OUT代表外圈遞增步長。
外圈需要一個透明度逐漸小的值opacity_out ,透明度變化的步長用OPA_STEP代替,最終設置style上的opacity屬性。
動畫是持續不斷的,所以我們需要不停的執行,先暫時採用setInterval函數;
放大到一定倍數然後再縮小,最大倍數我們用MAX_SCALE常量代表,如果遞增到最大然後STEP_INNER設置為負數,意味着開始縮小。
最終的源代碼如下:
<script>
const circle = document.querySelector(".circle");
const outCircle = document.querySelector(".outCircle");
let size_inner = 1.0; //內圈的放大倍數
let size_outer = 1.0; //內圈的放大倍數
let STEP_INNER = 0.009; //內圈的遞增步值
let STEP_OUT = STEP_INNER + 0.009; //外圈要比內圈擴散倍速大一點
let opacity_out = 1; //外圈透明度
const OPA_STEP = 0.03;
const MAX_SCALE = 1.4;
function randerInner() {
//放大過程
if (STEP_INNER >= 0) {
if (size_inner <= MAX_SCALE) {
size_inner += STEP_INNER;
//控制外圈光環--start
if (size_inner >= 1.1) {
//內圈放大到1.1倍 外圈光環才出現,大小和內圈一致,透明度為1
if (opacity_out === 0) {
opacity_out = 1;
size_outer = size_inner;
}
size_outer += STEP_OUT;
opacity_out -= OPA_STEP;//透明度逐漸
}
//控制外圈光環--end
} else {
//放大到MAX_SCALE倍了 重新縮小
size_inner = MAX_SCALE;
STEP_INNER = STEP_INNER * -1; //步長乘以-1,開始方方向變化
//控制外圈光環-隱藏
opacity_out = 0;
}
} else {
//縮小過程
if (size_inner > 1) {
size_inner += STEP_INNER;
} else {
//縮到1倍了 重新放大
STEP_INNER = STEP_INNER * -1; //步長乘以-1,開始方方向變化
size_inner = 1;
}
}
circle.style.transform = `scale(${size_inner})`; //設這內圈放大樣式
outCircle.style.transform = `scale(${size_outer})`; //設這內圈放大樣式
outCircle.style.opacity = opacity_out; //設這內圈透明度
}
setInterval(() => {
randerInner();
}, 17);
</script>
因篇幅有限,不逐一解釋各行代碼的含義,有不理解的可以私聊我。
最終效果:
眾所周知,因為瀏覽器的eventloop機制,setInterval往往並不能按照設置的時間執行,在稍微複雜一些的頁面中會出現卡頓。固可採用requestAnimationFrameapi代替。
代碼如下:
<script>
const circle = document.querySelector(".circle");
const outCircle = document.querySelector(".outCircle");
let size_inner = 1.0; //內圈的放大倍數
let size_outer = 1.0; //內圈的放大倍數
let STEP_INNER = 0.009; //內圈的遞增步值
let STEP_OUT = STEP_INNER + 0.009; //外圈要比內圈擴散倍速大一點
let opacity_out = 1; //外圈透明度
const OPA_STEP = 0.03;
const MAX_SCALE = 1.4;
(function randerInner() {
//放大過程
if (STEP_INNER >= 0) {
if (size_inner <= MAX_SCALE) {
size_inner += STEP_INNER;
//控制外圈光環--start
if (size_inner >= 1.1) {
//內圈放大到1.2倍 外圈光環才出現大小一致
if (opacity_out === 0) {
opacity_out = 1;
size_outer = size_inner;
}
size_outer += STEP_OUT;
opacity_out -= OPA_STEP;
}
//控制外圈光環--end
} else {
//放大到MAX_SCALE倍了 重新縮小
size_inner = MAX_SCALE;
STEP_INNER = STEP_INNER * -1;
//控制外圈光環-隱藏
opacity_out = 0;
}
} else {
//縮小過程
if (size_inner > 1) {
size_inner += STEP_INNER;
} else {
//縮到1倍了 重新放大
STEP_INNER = STEP_INNER * -1;
size_inner = 1;
}
}
circle.style.transform = `scale(${size_inner})`; //設這內圈放大樣式
outCircle.style.transform = `scale(${size_outer})`; //設這內圈放大樣式
outCircle.style.opacity = opacity_out; //設這內圈透明度
requestAnimationFrame(() => {
randerInner();
_test();
});
})()
</script>
第四種,js+canvas (較複雜)
經過上面章節的洗禮,canvas版本和js+dom版本非常相似,唯一的不同就是繪畫圓或者文字的api不同。
我們分析下要點:
- 需要在畫布上用
arcapi繪製2個圓,一個內圈一個外圈,並且不停改變圓圈的大小或透明度。 - 需要用
fillText繪製一個✔字樣,並且改變字體大小。 - 需要不停執行繪製函數,這裏使用流暢的
requestAnimationFrameapi。 - 每次繪製前,需清空畫布
clearRect。
其實原理和第三種差不多,都需要用變量記錄內部圈變化的大小,到最大值後方向遞減。外部圈是需要等到內圈放大到一定倍數(1.1)倍,才顯示出來,並且放大速度要比內圈快,隨着外圈變大,透明度逐漸減小到0。
我想第四種和第三種其實都有這樣的邏輯,為此我花了一點事件把這個動作進行了封裝,採用了一種訂閲發佈模式來實現。
為了大家瀏覽我這篇文章的同時,除了能學習到動畫知識,也能學習到一些其它的比較重要的js知識。大家應該瞭解或者聽過js的各種模式,什麼工廠模式、單列模式、觀察者模式、發佈訂閲模式等等,都是學習到了理論知識。今天我就用這個例子介紹下發布訂閲模式是如何在實踐中使用的。
剛我們分析了canvas實現的方式和js+dom的方式,這兩種方式都需要用變量來記錄一個值(這裏是放大倍數),而且每次執行渲染函數時都要遞增一個常量值(成為步長),到達某個最大值(比如1.3倍大小)後開始反向遞減(步長乘以-1),然後遞減到最初值(1倍大小)。同時還有一個細節,當內圈遞增到一定倍數(比如1.1倍)要讓外圈開始加入動畫,當內圈遞增到最大時,外圈要消失(透明度設置為0)
基於這個分析,我們得出封裝的類需要設計成這樣:
/***
* 放大縮小數字變化類
* 根據放大縮小初始值和步長等遞增
* 提供到達某一數值時的回調
* @params {Number} from 數字初始值
* @params {Number} to 數字最大值
* @params {Number} step 數字步長
* @params {Array} dingNums 達到dingNums的區間觸發回調 只再放大輪迴觸發
*/
export default class MoveCrycle {
constructor({ from = 0, to = 100, step = 1, dingNums = [] }) {
this.from = from;
this.to = to;
this.step = step;
this.dingNums = dingNums;
this.currValue = this.from;
this._turnNum = 0; //放大縮小輪次 一次放大一次縮小為一輪
this._event = {
onchange: [], //每一次變化觸發的回調
onlarge: [], //每次到最大值觸發的回調
onsmall: [], //每次到最小值觸發的回調
onding: [], //每次到dingNums區間值時觸發的回調
};
this._init();
}
_init() {
//註冊放大事件
//記錄輪次 每次放大+1
this.on("onlarge", function turn() {
this._turnNum++;
});
}
//開始滾動數字
jump() {
if (this.step > 0) {
//放大過程
if (this.currValue <= this.to) {
//遞增過程中
this.currValue += this.step;
//判斷是否在dingNums區間
if (
this.currValue >= this.dingNums[0] &&
this.currValue <= this.dingNums[1]
) {
this._emit("onding", this.currValue, this._turnNum);
}
} else {
this._emit("onsmall", this.currValue);
this.currValue = this.to;
this.step *= -1;
}
} else {
//縮小過程
if (this.currValue > this.from) {
this.currValue += this.step;
} else {
//縮到初始值了 重新放大
this.step *= -1;
this.currValue = this.from;
this._emit("onlarge", this.currValue);
}
}
this._emit("onchange", this.currValue, this.step > 0, this._turnNum);
}
//觸發事件函數
_emit(type, ...args) {
//觸發每次變動函數
this._event[type].forEach((f) => {
try {
f.apply({}, args);
} catch (e) {
console.error(e);
}
});
}
//註冊回調
on(type, func) {
if (
this._event[type] &&
!this._event[type].filter((f) => f === func).length
) {
this._event[type].push(func);
}
}
//註銷回調
off(type, func) {
if (this._event[type]) {
this._event[type] = this._event[type].filter((f) => f !== func);
}
}
}
一看比較長,別急慢慢分析:
constructor部分就是接收入參,沒有什麼好説的。需要注意的是this._init(),初始化時自己也註冊一個onlarge事件,來記錄每次輪迴的次數_turnNum。jump是核心代碼,主要管理每一次被執行時的遞增或遞減狀態,同時通過this.emit()函數觸發響應的回調事件。放大過程中判斷是否到達dingNums的區間,如果在其中則不停觸發onding事件,當到達最大值時觸發onsmall事件代表即將變小,同時this.step *= -1步長取反方向值。到達最小值時觸發onlarge事件,代表即將增大,同時this.step *= -1步長取反方向值。jump函數並沒有這個類中進行遞歸調用,是因為把這個調用權交給使用者會更加靈活,後面會介紹如何調用。_emit函數是觸發事件的控制中心,根據傳入的type類型找到事件對象中對應的函數數組,遍歷執行。用try{} catch(e){}是為了避免在某個回調函數中出錯而影響了其它回調函數正常執行。on和off顧名思義,就是註冊和註銷回調的入口。on函數把傳入的事件類型和函數加入對應的數組中,如果重複添加同一個函數是無效的。off函數接收事件類型和函數,然後數組中過濾掉。這裏留給你想像一下,如果用匿名函數註冊的事件能取消掉嗎?
好了,MoveCrycle類封裝好了,回到我們的主題,看下如何在這個箭頭動畫中使用。
HTML部分
<body>
<div class="app">
<div class="content">
<!--canvas替換箭頭部分-->
<canvas
id="canvas"
class="canvas"
width="140px"
height="140px"
></canvas>
</div>
</div>
</body>
script部分
引入MoveCrycle類,初始化
import MoveCrycle from "./js/MoveCrycle.js";
let cavas = document.getElementById("canvas");
var ctx = cavas.getContext("2d");
//定義的一些常量和變量下面會用到
const R_SMALL = 45;//內圈初始大小
...省略部分代碼
//看着裏!!!!初始化封裝的類
let move = new MoveCrycle({
from: R_SMALL, //初始值
to: R_SMALL * 1.35, //最大1.35倍
step: 0.3, //步長
dingNums: [R_SMALL * 1.1, R_SMALL * 1.35], //外圈開始出現的區間內圈的1.1倍到1.35倍
});
...
註冊onchange事件,監聽每一次變化,然後渲染畫布
//註冊數值變化事件,每次變化獲取值渲染
move.on("onchange", (value, step) => {
//繪製內圓
clear(); //清空畫布
//繪製內圈
drawInnerCircle(value);
//繪製外圈
drawOutCircle();
//文字放在最後,不然會被上面的圓屬性fillStyle覆蓋
drawText(value);
});
註冊onding事件,控制外圈的變大,透明度減小
//再設定的區間觸發事件,控制外圈的變大,透明度減小
move.on("onding", (value) => {
r_out += R_OUT_STEP; //外圈變大
opa -= OPA_STEP; //外圈變大的同時透明度減小
});
註冊onlarge事件,控制外圈
//動畫圈開始變大控制外圈
move.on("onlarge", (value) => {
opa = 1; //開始變大,透明度為1
r_out = 45; //外部開始變大時半徑回覆到初始值
});
註冊onsmall事件,控制外圈透明度為0
move.on("onsmall", (value) => {
//控制外圈光環-隱藏
opa = 0;
});
繪製內圈、外圈、文字函數
//繪製內圓
function drawInnerCircle(val) {
ctx.beginPath();
ctx.arc(SIZE / 2, SIZE / 2, val, 0, 2 * Math.PI);
ctx.fillStyle = `rgba(${COLOR}, 1)`;
ctx.fill();
}
//繪製外圈圓 變大的同時透明度下降
function drawOutCircle() {
ctx.beginPath();
ctx.arc(SIZE / 2, SIZE / 2, r_out, 0, 2 * Math.PI);
ctx.fillStyle = `rgba(${COLOR}, ${opa})`;
ctx.fill();
}
//放大字體 大小隨內圈一起變化
function drawText(val) {
//font放大倍數和內圈一樣
ctx.font = (val / R_SMALL) * 60 + "px Arial";
ctx.textBaseline = "center"; //設置字體底線居中
ctx.textAlign = "center"; //設置字體對齊方式居中
ctx.fillStyle = `rgba(255,255,255,1)`;
ctx.fillText("✔", 70, 90);
}
清除畫布
//清除畫布
function clear() {
ctx.clearRect(0, 0, SIZE, SIZE);
}
循環遞歸函數
//循環遞歸函數
(function start() {
move.jump(); //開始執行
requestAnimationFrame(() => {
start();
});
})
script部分完整代碼:
<script type="module">
import MoveCrycle from "./js/MoveCrycle.js";
let cavas = document.getElementById("canvas");
var ctx = cavas.getContext("2d");
const COLOR = "75, 167, 118";
const R_SMALL = 45;//內圈初始大小
const SIZE = 140; //畫布寬高
const R_OUT_STEP = 0.3 * 2.5; //外圈變化係數
const OPA_STEP = 0.028;
let r_out = R_SMALL; //外圈半徑累加值
let opa = 1; //外圈透明度累加值
let move = new MoveCrycle({
from: R_SMALL, //初始值
to: R_SMALL * 1.35, //最大1.35倍
step: 0.3, //步長
dingNums: [R_SMALL * 1.1, R_SMALL * 1.35], //外圈開始出現的區間內圈的1.1倍到1.35倍
});
//註冊數值變化事件,每次變化獲取值渲染
move.on("onchange", (value, step) => {
//繪製內圓
clear(); //清空畫布
//繪製內圈
drawInnerCircle(value);
//繪製外圈
drawOutCircle();
//文字放在最後,不然會被上面的圓屬性fillStyle覆蓋
drawText(value);
});
//再設定的區間觸發事件
move.on("onding", (value) => {
r_out += R_OUT_STEP; //外圈變大
opa -= OPA_STEP; //外圈變大的同時透明度減小
});
//動畫圈開始變大
move.on("onlarge", (value) => {
opa = 1; //開始變大,透明度為1
r_out = 45; //開始變大時半徑回覆到初始值
});
//動畫圈開始變小
move.on("onsmall", (value) => {
//控制外圈光環-隱藏
opa = 0;
});
//繪製內圓
function drawInnerCircle(val) {
ctx.beginPath();
ctx.arc(SIZE / 2, SIZE / 2, val, 0, 2 * Math.PI);
ctx.fillStyle = `rgba(${COLOR}, 1)`;
ctx.fill();
}
//繪製外圈圓 變大的同時透明度下降
function drawOutCircle() {
ctx.beginPath();
ctx.arc(SIZE / 2, SIZE / 2, r_out, 0, 2 * Math.PI);
ctx.fillStyle = `rgba(${COLOR}, ${opa})`;
ctx.fill();
}
//放大字體 大小隨內圈一起變化
function drawText(val) {
//font放大倍數和內圈一樣
ctx.font = (val / R_SMALL) * 60 + "px Arial";
ctx.textBaseline = "center"; //設置字體底線居中
ctx.textAlign = "center"; //設置字體對齊方式居中
ctx.fillStyle = `rgba(255,255,255,1)`;
ctx.fillText("✔", 70, 90);
}
//循環遞歸函數
(function start() {
move.jump(); //開始執行
requestAnimationFrame(() => {
start();
});
})
//清除畫布
function clear() {
ctx.clearRect(0, 0, SIZE, SIZE);
}
//開始函數
start();
</script>
以上。
完整代碼可以到我的github倉庫獲取!
各位大佬,不要忘了給我點贊+評論+收藏 。