用react與scss實現打字機效果

自己在練習的時候,突然覺得很像用react解題,於是決定照慣例來寫個解題報告!!

題目

實現 打字 → 停留 → 刪字 → 換下一句 → 重複循環

在許多網站首頁(Hero Section)中,常會出現「打字機文字效果」(Typewriter Effect),透過逐字呈現的方式吸引用戶注意。

請你使用 ReactSCSS 實作一個打字機效果元件,使其可以依序顯示多段文字,並於每段文字輸出完成後短暫停頓,再自動切換到下一段。

你的任務是設計一個元件 <Typewriter />,並滿足下列行為要求:

功能要求

  1. 逐字輸出文字
    • 給定一段字串,需每隔固定時間顯示下一個字元。
    • 顯示方式應類似打字機逐字呈現。
  2. 支援多段文字循環播放
    • 輸入一個字串陣列 texts
    • 每段文字顯示完成後需:
      • 保留完整文字一段時間(如:1 秒)
      • 再自動切換到下一段文字
    • 播放到最後一段後需重新回到第一段。
  3. 文字切換時不得造成 DOM 閃爍或布局跳動
    • 頁面不應因為文字長度變化而發生高度跳動(layout shift)。
    • 切換過程中不得出現空白 DOM。
  4. 使用 React Hooks 實作
    • 必須使用 useStateuseEffect 來控制字元增加與切換。
    • 為避免 ESLint 警告,字串陣列需搭配 useMemo() 保證不在每次 render 重新建立。
  5. 樣式需使用 SCSS
    • 實現文字閃爍游標(blinking cursor)
    • 效果需以 SCSS 撰寫,例如動畫、過渡等。

輸入格式

  • 傳入 <Typewriter /> 組件的 props:
interface TypewriterProps {
  texts: string[];   // 欲顯示之多段文字
}

輸出格式

  • 元件需渲染一段逐字顯示的文字,同時包含閃爍游標:
<span class="typewriter-text">目前顯示中的文字</span>
<span class="cursor"></span>

範例

若傳入:

texts = ['Hello', 'Welcome to my website', 'Frontend Developer'];

顯示流程應如下:

H →
He →
Hel →
Hell →
Hello →
(停 1 秒)
W →
We →
Wel →
…
Frontend Developer →
(停 1 秒)
→ 回到 Hello 重新開始

整個過程須連續平滑進行,不得有閃爍與跳動。


解題絲路

其實要實現打字機效果,核心絲路只有:

  1. 動畫效果
  2. 現在是打字還是刪字
  3. 現在要顯示哪一句
    1. 現在這句的第幾個字

設一個變數subIndex,追蹤現在的句子(S)的第幾字(i),如果是打字,subIndex++,反之亦然。

詳解採用Dynamic Style Injection。

我們可以先根據題目說的內容以及想法,把一些基礎的東西寫出來。

import { useState } from "react";

export default function Index({texts}: {string[]}) {
		const [index, setIndex] = useState(0); // 現在第index句
		const [subIndex, setSubIndex] = useState(0); // 現在第index句的subIndex個字
		const [deleting, setDeleting] = useState(true); // 是否是打字狀態
		
    const current = texts[index]; // 取出當前字

	  return (
	    <div>
		    {/* 用substring()方法取出子字串*/}
	      <span>{current.substring(0, subIndex)}</span>
	      <span className="cursor">|</span>
	    </div>
	  );
}
const style = document.createElement('style');
style.textContent = `
    .cursor {
        margin-left: 2px;
        animation: blink 0.8s infinite;
    }

    @keyframes blink {
        0%, 50% { opacity: 0; }
        51%, 100% { opacity: 1; }
    }
    
`;
document.head.appendChild(style);

基礎的東西寫完了,現在的問題是: 如何將字元一個個的顯示出來?

先來釐清打字機效果的真正意義:是一種 「時間驅動的動畫,隨時間變化的 state」

React 只有兩種方式能讓 state 隨時間變化:

  • setTimeout
  • useEffect

useEffect 是唯一會在 state 變化時執行副作用的HOOK,所以這題採用useEffect !!

開始來寫useEffect!!

    useEffect(() => {
        if (deleting && subIndex === current.length){ // 如果打字打到跟句子一樣長
            const timeout = setTimeout(() => setDeleting(false), 1200);// 改成刪字,並停留1200秒
            return () => clearTimeout(timeout); // 清理副作用
        }
        if (!deleting && subIndex === 0) {// 如果刪字刪到0 換下一句
            setDeleting(true); // 開始打字
            setIndex((index + 1) % texts.length);
            return;
        }
        // 用 setTimeout 讓它每隔 60~120ms 動一次
        const timeout = setTimeout(
            () => setSubIndex(subIndex + (!deleting ? -1 : 1)),
            deleting ? 120 : 60 // 打字慢刪字快
        );
        return () => clearTimeout(timeout);// 清理副作用
    }, [subIndex, deleting, index, texts, current]);

css

const style = document.createElement('style');
style.textContent = `
    .typewriter-line {
        font-size: 15px;
        white-space: nowrap;
        display: inline-flex;
        align-items: center; 
        height: 1.5em; 
    }

    .cursor {
        margin-left: 2px;
        animation: blink 0.8s infinite;
       
    }

    @keyframes blink {
        0%, 50% { opacity: 0; }
        51%, 100% { opacity: 1; }
    }
    
    
`;
document.head.appendChild(style);

記得在父層呼叫時,父層的css要固定高度,避免JS再換行的時候出現dom是Null,導致葉面其他元素被往上移。“height: 1.5em;“

Leave a Reply

Your email address will not be published. Required fields are marked *