想在網頁加上動畫效果,資料表格中在某列資料變化時改變背景色吸引注意,再使用淡化效果恢復原本背景色。

以此目標我設計了一個綜合練習題,在網頁上放五個 DIV,點擊後使用 CSS 動畫效果將背景色改為紫色再逐漸淡化,1.5 秒後恢復原來顏色。若連續點擊同一 DIV,需停止現有動畫,重頭播放。另外,設計一按鈕,按下時會連續隨機點擊 DIV。

先看成果:

網頁以原生 CSS + JavaScript 實現,使用到以下技巧:

  1. @keyframes 定義起始及結束狀態之 CSS 樣式
  2. animation: <keyframes-name> 1.5s ease-out; 定義 .hit 時播放背景色漸變動畫(由藍紫轉淺藍),長度 1.5 秒,採先快後慢(ease-out)配速
    延伸閱讀:CSS transition 各種速率 by 卡斯伯
  3. 在容器掛 click 事件但操控 e.target 元素,相當於 jQuery .on('click', '子元素選取器', function() {...})
  4. 點擊 DIV 時移除 .hit,稍後再加上 .hit 以停止目前動畫重新播放
  5. 稍後加上 .hit 使用 requestAnimationFrame() 實現,精準抓住下次繪製時觸發。傳統使用 setTimeout 要自己抓延遲,可能過長可能過短
  6. 動畫播放完畢靠 animationend 事件移除 .hit
  7. animationend 只需執行一次,在 jQuery 可用 .one(),香草 JavaScirpt 的話可寫成 addEventListener(fn, )
  8. document.querySelectorAll('.list div') 傳回型別為 NodeList,類似陣列但 API 較少(例如沒有 map()、filter()),我習慣用 [...nodeListVar] 轉為標準陣列 (... 為 Spread Syntax 展開語法,可用於函式參數、展開併入陣列、合併物件屬性,好用!)

完整程式碼如下,想試玩這裡有線上展示

<!DOCTYPE html>
<html>

<head>
    <title>Animation Test</title>
    <style>
        .list {
            display: flex;
        }

        .list div {
            width: 32px;
            height: 32px;
            text-align: center;
            font-size: 16pt;
            border-radius: 15px;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            margin: 6px;
            cursor: pointer;
            background-color: #eee;
            user-select: none;
        }

        .list div.hit {
            animation: fade 1.5s ease-out;
        }

        @keyframes fade {
            from {
                background-color: blueviolet;
            }

            to {
                background-color: #ddf;
            }
        }
        button {
            margin: 12px;
            width: 100px;
        }
    </style>
</head>

<body>
    <div class="list">
        <div>1</div>
        <div>2</div>
        <div>3</div>
        <div>4</div>
        <div>5</div>
    </div>
    <button id="b">Play</button>
    <input type="range" id="r" min="50" max="1000" value="450">
    <script>
        document.querySelector('.list').addEventListener('click',
            function (e) {
                // 聽 .list click 事件再由 e.target 判斷是不是 div
                // 相當於 $('.list').on('click', 'div', function(e) { ... });
                const t = e.target;
                if (t.tagName === 'DIV') {
                    // 移除 hit class 再加回去,若仍在播放動畫可停止現有動畫,觸發重新播放
                    console.log('remove');
                    t.classList.remove('hit');
                    // 這裡用 setTimeout 或 requestAnimationFrame 再加回 hit class
                    // 如果直接加回去,不會播放動畫
                    // requestAnimationFrame 比 setTimeout 好,會抓準在下次繪製時執行
                    // 不像 setTimeout 自己抓時間可能過長或過短
                    requestAnimationFrame(function () {
                        t.classList.add('hit');
                        // 動畫播放完後移除 hit class,這裡利用 animationend 事件
                        t.addEventListener('animationend', function () {
                            t.classList.remove('hit');
                        }, { // addEventListener 第三個參數可以設定 options, once 表示只執行一次
                            once: true
                        });
                    });
                }
            });
        // 按鈕自動播放
        let running = false;
        document.getElementById('b').addEventListener('click', (e) => {
            running = !running; // 切換播放狀態
            e.target.textContent = running ? 'Stop' : 'Play';
            if (running) play();
        });
        // 取得 .list div 陣列
        // querySelectorAll 回傳的是 NodeList,不是 Array
        // 這裡用 [...NodeList] 展開成 Array
        let divs = [...document.querySelectorAll('.list div')];
        function play() {
            // 隨機點擊一個 div
            let i = Math.floor(Math.random() * divs.length);
            divs[i].click();
            // 如果還在播放,就隨機時間後點擊一個 div,隨機長度範圍由 input range 決定
            if (running) 
                setTimeout(play, Math.random() * document.getElementById('r').value);

        }
    </script>
</body>

</html>

A CSS + JavaScript exercise to add background color change and fade out animation on clicked div.


Comments

# by slash

document.querySelectorAll

# by Jeffrey

to slash, 3Q

Post a comment