テキスト
Typewriter
複数のテキストを順番にタイプ&デリートするタイプライターエフェクト。
インストール
npx shadcn add https://d-ui.daigo-suhara.com/registry/typewriter.jsonサンプル
I love
使い方
import { Typewriter } from "@/components/ui/typewriter"
export default function Example() {
return (
<h1 className="text-4xl font-bold">
We build{" "}
<Typewriter
texts={["beautiful UIs.", "fast apps.", "great products."]}
/>
</h1>
)
}プロパティ
texts必須string[]順番に表示するテキストの配列
speed任意numberタイピング速度(ミリ秒/文字)
デフォルト:
80deleteSpeed任意number削除速度(ミリ秒/文字)
デフォルト:
40pause任意numberテキスト表示後の待機時間(ミリ秒)
デフォルト:
1500loop任意booleanループ再生
デフォルト:
truecursor任意booleanカーソルの表示
デフォルト:
trueclassName必須string追加のCSSクラス
| 名前 | 型 | デフォルト | 説明 | |
|---|---|---|---|---|
| 必須 | texts | string[] | — | 順番に表示するテキストの配列 |
| 任意 | speed | number | 80 | タイピング速度(ミリ秒/文字) |
| 任意 | deleteSpeed | number | 40 | 削除速度(ミリ秒/文字) |
| 任意 | pause | number | 1500 | テキスト表示後の待機時間(ミリ秒) |
| 任意 | loop | boolean | true | ループ再生 |
| 任意 | cursor | boolean | true | カーソルの表示 |
| 必須 | className | string | — | 追加のCSSクラス |
ソースコード
"use client";
import * as React from "react";
import { cn } from "@/lib/utils";
interface TypewriterProps {
texts: string[];
speed?: number;
deleteSpeed?: number;
pause?: number;
loop?: boolean;
cursor?: boolean;
className?: string;
}
export function Typewriter({
texts,
speed = 80,
deleteSpeed = 40,
pause = 1500,
loop = true,
cursor = true,
className,
}: TypewriterProps) {
const [displayed, setDisplayed] = React.useState("");
const [textIndex, setTextIndex] = React.useState(0);
const [isDeleting, setIsDeleting] = React.useState(false);
const [isPaused, setIsPaused] = React.useState(false);
React.useEffect(() => {
if (texts.length === 0) return;
if (isPaused) {
const timer = setTimeout(() => {
setIsPaused(false);
setIsDeleting(true);
}, pause);
return () => clearTimeout(timer);
}
const current = texts[textIndex];
if (!isDeleting) {
if (displayed.length < current.length) {
const timer = setTimeout(() => {
setDisplayed(current.slice(0, displayed.length + 1));
}, speed);
return () => clearTimeout(timer);
} else {
if (!loop && textIndex === texts.length - 1) return;
setIsPaused(true);
}
} else {
if (displayed.length > 0) {
const timer = setTimeout(() => {
setDisplayed(displayed.slice(0, -1));
}, deleteSpeed);
return () => clearTimeout(timer);
} else {
setIsDeleting(false);
setTextIndex((i) => (i + 1) % texts.length);
}
}
}, [displayed, isDeleting, isPaused, textIndex, texts, speed, deleteSpeed, pause, loop]);
return (
<span className={cn("inline", className)}>
{displayed}
{cursor && (
<span className="animate-[blink_1s_step-end_infinite] border-r-2 border-current ml-0.5" />
)}
</span>
);
}