iT邦幫忙

2024 iThome 鐵人賽

DAY 16
0
Modern Web

創意前端設計:用 Vue.js 打造 30 個互動實用功能系列 第 16

Day16 Vue.js 動效分類實戰 (8) 進度條特輯 - 超酷互動計時器+動態視覺化

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20240930/20124462JKjnQBZh3j.jpg


讓你的計時器不再無聊!帶你實現進度條的互動視覺化

你是否曾經想過,讓時間不只是冷冰冰的數字,而是變成一個動態、直觀、充滿視覺衝擊的體驗?

今天,我們要用 Vue.js 帶你進入計時器與圈圈進度條的世界!
不再只是普通的倒數,而是結合進度條和圈圈的互動效果,讓計時變得更酷、更吸引眼球。

無論你是用來做任務倒數、跑步計時,還是追蹤項目進展,這個動態計時器搭配圈圈進度條的效果,保證讓你的頁面煥然一新,增強使用者的體驗感。
準備好來學習這個超實用的效果嗎?
那就跟我一起進入今天的實戰吧!


img


計時器與進度條效果的設計與實作

這個超酷的計時器還搭配了圈圈進度條,讓倒數時間變得更直觀又炫酷!
再加上「Start」、「Pause」、「Reset」三個按鈕,每個按鈕還有簡單的 hover 回饋,互動性滿分~
用 Vue.js 輕鬆打造這個小功能變得有趣又動感~
透過精美的動效,讓整體體驗更生動,還能瞬間抓住使用者的目光!

核心實作步驟

  1. 計時器的基礎設置

    • 計時器會根據輸入的秒數開始倒數,並且當計時器開始後,進度條會根據剩餘時間動態縮減。
    • 我們透過 Vue 的 refcomputed 來管理時間變量和格式化顯示。
  2. 進度條設計

    • 我們使用 SVG 圓形進行進度條的視覺化設計,搭配 stroke-dasharraystroke-dashoffset,讓進度條隨著時間的流逝動態改變。
    • 當倒數時間結束時,進度條會完全縮減至 0,這樣使用者可以一目了然地掌握剩餘時間。

Template 結構與動態顯示

  • 程式碼詳解
<template>
    <div class="flex justify-center items-center h-screen bg-[#5628EE] font-mono">
        <div class="container">
            <div class="flex justify-center items-center relative">
                <svg class="h-[150px] w-[150px]" viewBox="0 0 100 100">
                    <defs>
                        <linearGradient id="warm-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
                            <stop offset="0%" style="stop-color:#85FFBD"></stop>
                            <stop offset="100%" style="stop-color:#FFFB7D"></stop>
                        </linearGradient>
                    </defs>
                    <!-- Circle with animation -->
                    <circle class="fill-none transition-all duration-1000 ease-linear" cx="50" cy="50" r="45"
                        stroke-width="4" style="stroke: url(#warm-gradient); stroke-dasharray: 282.743;"
                        :stroke-dashoffset="dashOffset" />
                </svg>
                <div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-4xl text-white">
                    {{ formattedTime }}
                </div>
            </div>
            <div class="mt-5 flex flex-col items-center">
                <input v-model.number="timeInput" placeholder="請輸入秒數" min="1"
                    class="outline-none border-none w-70 h-10 border-white text-[#5628EE] text-center rounded-full">
                <div class="mt-4 flex space-x-6">
                    <button @click="startTimer" class="inline-flex items-center px-4 py-2 bg-red-600 hover:bg-red-700 text-white text-sm font-medium rounded-md">
                        Start
                    </button>
                    <button @click="pauseTimer" class="bg-gray-300 p-4 text-center text-[#5628EE] flex flex-wrap justify-center rounded-md hover:bg-gray-400">
                        Pause
                    </button>
                    <button @click="resetTimer" class="inline-flex items-center px-4 py-2 bg-green-700 text-white hover:bg-green-800 text-sm font-medium rounded-md">
                        Reset
                    </button>
                </div>
            </div>
        </div>
    </div>
</template>
  • SVG 圓形設計:
    • 我們使用了一個具有 stroke-dasharraystroke-dashoffset 的 SVG 圓形來呈現進度條。當倒數開始時,進度條會根據剩餘時間不斷減少。
    • 我們還應用了線性漸變(linearGradient)來強化視覺效果,使進度條看起來更加立體且炫彩。

TypeScript 計時器邏輯**

<script lang="ts" setup>
import { ref, computed } from 'vue';

const timeInput = ref<number>(0); 
const remainingTime = ref<number>(0); 
const totalTime = ref<number>(0);
const isPaused = ref<boolean>(false); 
const dashOffset = ref<string>('282.743');
let timer: ReturnType<typeof setInterval> | null = null;

// 格式化時間,顯示為 MM:SS
const formattedTime = computed(() => {
    const mins = Math.floor(remainingTime.value / 60);
    const secs = remainingTime.value % 60;
    return `${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
});

// 更新計時器與圈圈的 stroke-dashoffset
const updateTimer = () => {
    if (remainingTime.value <= 0) {
        clearInterval(timer!);
        dashOffset.value = '0';
        return;
    }
    remainingTime.value--;
    dashOffset.value = (282.743 * (remainingTime.value / totalTime.value)).toFixed(3);
};

// 開始計時
const startTimer = () => {
    if (isPaused.value && timer) {
        isPaused.value = false;
        timer = setInterval(updateTimer, 1000);
        return;
    }

    clearInterval(timer!);
    totalTime.value = timeInput.value || 0;
    remainingTime.value = totalTime.value;

    if (totalTime.value > 0) {
        dashOffset.value = '282.743';
        timer = setInterval(updateTimer, 1000);
    }
};

// 暫停計時
const pauseTimer = () => {
    if (!isPaused.value) {
        clearInterval(timer!);
        isPaused.value = true;
    }
};

// 重置計時器
const resetTimer = () => {
    clearInterval(timer!);
    remainingTime.value = 0;
    dashOffset.value = '282.743';
    isPaused.value = false;
};
</script>
  • 計時器邏輯:
    • startTimer 函式會根據使用者輸入的秒數啟動計時,並每秒更新一次倒數時間。同時,進度條也會根據剩餘時間動態減少。
    • pauseTimer 允許使用者暫停計時,而 resetTimer 則會重置計時器和進度條,讓使用者可以重新開始倒數。

「開始」、「暫停」和「重新開始」的實作邏輯

  • 開始計時:根據使用者輸入的時間啟動計時器,並同步更新進度條。
  • 暫停計時:讓使用者可以暫停計時,保留當前的時間和進度條狀態,以便稍後繼續。
  • 重置計時器:將計時器和進度條恢復到初始狀態,並準備下一次的倒數。

1. 開始計時 (Start Timer)

這個功能的主要目的是在使用者點擊「開始」按鈕時,根據輸入的時間來啟動倒數計時,同時啟動進度條的動畫。

const startTimer = () => {
    // 如果計時器是暫停狀態且已經有一個計時器在運行,就恢復計時
    if (isPaused.value && timer) {
        isPaused.value = false;
        timer = setInterval(updateTimer, 1000);  // 每秒更新計時器
        return;
    }

    // 清除當前的計時器,準備重新開始
    clearInterval(timer!);

    // 初始化計時時間與總時間
    totalTime.value = timeInput.value || 0; // 使用者輸入的時間(秒)
    remainingTime.value = totalTime.value;  // 剩餘的倒數時間初始化為總時間

    // 如果輸入時間大於 0,啟動計時器
    if (totalTime.value > 0) {
        dashOffset.value = '282.743';  // 設定圓圈進度條的初始值
        timer = setInterval(updateTimer, 1000);  // 每秒更新一次計時器
    }
};

解釋

  • 當使用者點擊「開始」按鈕時,如果計時器是暫停狀態,就重新啟動倒數。
  • 如果計時器已經運行過,我們會先清除當前的計時器(clearInterval),然後根據使用者輸入的時間重新啟動。
  • 設定 totalTimeremainingTime 來初始化總時間和剩餘時間,並設置進度條的初始位置。
  • 最後通過 setInterval 每秒執行一次 updateTimer,讓計時器和進度條同步更新。

2. 暫停計時 (Pause Timer)

這個功能的目的是在使用者點擊「暫停」按鈕時暫停計時器,但保留當前的剩餘時間和進度條位置,以便稍後可以繼續倒數。

const pauseTimer = () => {
    if (!isPaused.value) {  // 如果計時器不是暫停狀態
        clearInterval(timer!);  // 暫停計時器
        isPaused.value = true;  // 標記為暫停狀態
    }
};

解釋

  • 當使用者點擊「暫停」按鈕時,我們會使用 clearInterval 來暫停計時器的倒數。
  • 然後,我們將 isPaused 設定為 true,以標記計時器當前處於暫停狀態。
  • 這樣就保留了當前的剩餘時間,並且當我們再次點擊「開始」按鈕時,可以從剩餘的時間繼續計時。

3. 重新開始 (Reset Timer)

這個功能的目的是在使用者點擊「重置」按鈕時,將計時器和進度條重置為初始狀態。

const resetTimer = () => {
    clearInterval(timer!);  // 停止當前的計時器
    remainingTime.value = 0;  // 將剩餘時間重置為 0
    dashOffset.value = '282.743';  // 重置進度條的位置
    isPaused.value = false;  // 標記為非暫停狀態
};

解釋

  • 當使用者點擊「重置」按鈕時,我們首先清除當前的計時器,停止倒數。
  • 然後將 remainingTime 重置為 0,表示計時已經結束。
  • 進度條的 dashOffset 也會被重置為初始狀態(即進度條的滿格值),以準備下一次的倒數計時。
  • 最後將 isPaused 設定為 false,這樣在下一次點擊「開始」時,計時器將重新運行。

更新計時器與進度條的核心邏輯

這個函數負責在每次倒數時更新剩餘時間和進度條的位置。

const updateTimer = () => {
    // 如果剩餘時間小於等於 0,清除計時器並將進度條歸 0
    if (remainingTime.value <= 0) {
        clearInterval(timer!);
        dashOffset.value = '0';
        return;
    }

    // 更新剩餘時間與進度條位置
    remainingTime.value--;
    dashOffset.value = (282.743 * (remainingTime.value / totalTime.value)).toFixed(3);
};

解釋

  • updateTimer 函數每秒更新一次,當剩餘時間減少時,進度條也會相應地縮減。
  • 當倒數時間結束時(即 remainingTime.value <= 0),計時器會停止,進度條完全消失。
  • dashOffset 的計算是根據剩餘時間和總時間之間的比例來更新進度條的長度。

核心設計亮點

  1. 視覺化計時器:

    • 結合了 SVG 的 stroke-dasharraystroke-dashoffset,我們可以輕鬆地將時間的變化轉化為可視化的進度條。
    • 漸變效果讓進度條看起來更有層次感,增強了整體的視覺衝擊力。
  2. 計時器的可互動性:

    • 使用者可以自定義計時的長度,並且可以隨時開始、暫停或重置計時器,這樣的互動性提升了使用者體驗。

結語

倒數不是結束,而是下一個開始的倒計時!

每個倒數的瞬間,其實都是為了下一次的突破做準備。
無論進度條怎麼走,重要的是你怎麼利用這些時間去成就更大的夢想。

計時器告訴我們,時間是有限的,但機會卻是無限的!每一次的「Reset」都是讓我們更強大的起點,下一個挑戰永遠在等著你。✨

所以,勇敢按下「Start」,無論過程如何,記住,你的每一步都是進步的見證!
現在,就讓這些技術成為你實現夢想的利器,邁向下一個精彩吧!🚀


上一篇
Day15 Vue.js 動效分類實戰 (7) 3D 翻轉卡特輯 - 視覺震撼的完美翻轉效果
下一篇
Day17 Vue.js 動效分類實戰 (9) 萌兔吹泡泡特輯 - 顛覆等待的互動視覺體驗
系列文
創意前端設計:用 Vue.js 打造 30 個互動實用功能30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言