タイプライター効果
ターミナル風UIで複数のフレーズを1文字ずつ打って消すループ。点滅カーソル付きで、ヒーローの動的キャッチコピーに使えます。
ライブデモ
使用例(お題: SaaS FlowDesk)
この技法を「SaaS FlowDesk」というテーマのダミーサイトで実際に使った例です。
HTML
<div class="page">
<header class="nav">
<div class="brand"><span class="logo"></span>FlowDesk</div>
<span class="badge">v2.4 リリース</span>
</header>
<section class="hero">
<p class="eyebrow">DEVELOPER FIRST</p>
<h1 class="head">FlowDeskで、<br><span class="type" aria-live="polite"></span><span class="caret"></span></h1>
<p class="lead">コマンド一発で連携。あなたのワークフローに溶け込むSaaS。</p>
<div class="terminal">
<div class="bar"><i></i><i></i><i></i><span>flowdesk — bash</span></div>
<pre class="body"><span class="prompt">$</span> flowdesk init my-team
<span class="ok">✓</span> ワークスペースを作成しました
<span class="prompt">$</span> flowdesk deploy
<span class="ok">✓</span> 公開URLを発行: https://my-team.flowdesk.app</pre>
</div>
</section>
</div>
CSS
/* FlowDesk:タイプライターのキャッチコピーが主役 */
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: "Segoe UI", system-ui, sans-serif;
background:
radial-gradient(700px 360px at 18% -10%, #18294f 0%, transparent 60%),
linear-gradient(160deg, #0f1b34 0%, #0b1326 100%);
color: #e7ecf7;
min-height: 400px;
overflow: hidden;
}
.page { padding: 16px 26px; }
.nav { display: flex; align-items: center; justify-content: space-between; }
.brand { display: flex; align-items: center; gap: 9px; font-weight: 700; font-size: 16px; }
.logo {
width: 17px; height: 17px; border-radius: 6px;
background: linear-gradient(135deg, #4f7cff, #8ab4ff);
box-shadow: 0 0 10px rgba(79,124,255,0.6);
}
.badge {
font-size: 11px; color: #8ab4ff;
border: 1px solid #2c3c63; padding: 4px 10px; border-radius: 20px;
}
.hero { padding: 24px 4px 0; }
.eyebrow { font-size: 11px; letter-spacing: 0.3em; color: #6f86c2; font-weight: 700; }
.head {
margin-top: 12px;
font-size: clamp(26px, 5.5vw, 40px);
font-weight: 800; line-height: 1.25;
}
/* タイプされる本文(ブランド青で強調) */
.type { color: #6a90ff; }
.caret {
display: inline-block; width: 3px; height: 1em;
background: #6a90ff; margin-left: 2px;
vertical-align: -0.12em;
animation: blink 1s steps(1) infinite;
}
@keyframes blink { 50% { opacity: 0; } }
.lead { margin-top: 14px; font-size: 13.5px; color: #aeb9d4; }
/* ターミナル風カード */
.terminal {
margin-top: 20px;
background: #0a1124;
border: 1px solid #20305a;
border-radius: 11px;
overflow: hidden;
box-shadow: 0 14px 36px rgba(0,0,0,0.45);
}
.bar {
display: flex; align-items: center; gap: 7px;
padding: 9px 13px; background: #111c38;
font-size: 11px; color: #7e8fb8;
}
.bar i { width: 10px; height: 10px; border-radius: 50%; background: #2b3c64; }
.bar i:nth-child(1) { background: #ff5f57; }
.bar i:nth-child(2) { background: #febc2e; }
.bar i:nth-child(3) { background: #28c840; }
.bar span { margin-left: 6px; }
.body {
padding: 13px 16px;
font-family: "Consolas", "SFMono-Regular", monospace;
font-size: 12.5px; line-height: 1.8; color: #c3cee6;
white-space: pre-wrap;
}
.prompt { color: #6a90ff; }
.ok { color: #28c840; }
@media (prefers-reduced-motion: reduce) {
.caret { animation: none; }
}
JavaScript
// 複数フレーズを1文字ずつ打って消すループ
(function () {
const el = document.querySelector('.type');
if (!el) return; // null安全
const phrases = [
'チームはひとつに。',
'デプロイは一瞬。',
'面倒は自動化。',
'今日から始まる。'
];
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
// 控えめ設定では先頭フレーズを静的表示
if (reduce) { el.textContent = phrases[0]; return; }
let p = 0, i = 0, deleting = false;
function tick() {
const word = phrases[p];
// 打つ/消すで文字数を増減
i += deleting ? -1 : 1;
el.textContent = word.slice(0, i);
let wait = deleting ? 55 : 95;
if (!deleting && i === word.length) {
wait = 1300; // 打ち終えたら少し待つ
deleting = true;
} else if (deleting && i === 0) {
deleting = false;
p = (p + 1) % phrases.length; // 次のフレーズへ
wait = 320;
}
setTimeout(tick, wait);
}
tick();
})();
コード
HTML
<main class="stage">
<div class="terminal">
<div class="bar">
<span class="dot red"></span>
<span class="dot yellow"></span>
<span class="dot green"></span>
<span class="tname">~/portfolio</span>
</div>
<p class="line">
<span class="prompt">$</span>
<!-- ここにJSが1文字ずつ打ち込む -->
<span class="typed" id="typed"></span><span class="caret" aria-hidden="true"></span>
</p>
</div>
</main>
CSS
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
min-height: 360px;
display: grid;
place-items: center;
font-family: "Segoe UI", system-ui, sans-serif;
background:
radial-gradient(800px 500px at 50% 120%, #15324a 0%, transparent 60%),
#0b0f17;
padding: 24px;
overflow: hidden;
}
/* ターミナル風ウィンドウ */
.terminal {
width: min(560px, 92vw);
background: #11161f;
border: 1px solid #232b3a;
border-radius: 12px;
box-shadow: 0 24px 60px rgba(0,0,0,0.55);
overflow: hidden;
}
.bar {
display: flex;
align-items: center;
gap: 8px;
padding: 11px 14px;
background: #1a2130;
border-bottom: 1px solid #232b3a;
}
.dot { width: 12px; height: 12px; border-radius: 50%; }
.red { background: #ff5f56; }
.yellow { background: #ffbd2e; }
.green { background: #27c93f; }
.tname {
margin-left: 8px;
font-size: 12px;
color: #6b768c;
font-family: "Consolas", monospace;
}
.line {
padding: 26px 20px 30px;
font-family: "Consolas", "Courier New", monospace;
font-size: clamp(16px, 4.5vw, 24px);
color: #e6edf3;
display: flex;
align-items: baseline;
white-space: pre-wrap;
word-break: break-word;
}
.prompt { color: #27c93f; margin-right: 10px; }
.typed { color: #7ee787; }
/* 点滅カーソル */
.caret {
display: inline-block;
width: 10px;
height: 1.1em;
margin-left: 2px;
background: #7ee787;
transform: translateY(0.12em);
animation: blink 1s step-end infinite;
}
@keyframes blink {
50% { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
.caret { animation: none; }
}
JavaScript
// タイプライター効果: 複数フレーズを打って消す
(function () {
const out = document.getElementById('typed');
if (!out) return; // null安全
const phrases = [
'npm run build',
'deploy --prod',
'git push origin main',
'echo "Hello, Web"'
];
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
let p = 0; // 現在のフレーズindex
let i = 0; // 現在の文字位置
let deleting = false;
// setTimeoutを使い、速度を可変にして自然なタイピングに
function tick() {
const word = phrases[p];
if (!deleting) {
i++;
out.textContent = word.slice(0, i);
if (i === word.length) {
deleting = true;
return schedule(1100); // 打ち終えたら少し待つ
}
return schedule(70 + Math.random() * 60);
} else {
i--;
out.textContent = word.slice(0, i);
if (i === 0) {
deleting = false;
p = (p + 1) % phrases.length; // 次のフレーズへ
return schedule(280);
}
return schedule(38);
}
}
function schedule(ms) { window.setTimeout(tick, ms); }
if (reduce) {
// モーション控えめ: 1フレーズだけ静的表示
out.textContent = phrases[0];
} else {
schedule(500);
}
})();
🤖 AIエージェント用プロンプト
このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「タイプライター効果」の効果を追加してください。
# 追加してほしい効果
タイプライター効果(タイポグラフィ)
ターミナル風UIで複数のフレーズを1文字ずつ打って消すループ。点滅カーソル付きで、ヒーローの動的キャッチコピーに使えます。
# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】
# 参考実装(この見た目・挙動を再現してください)
【HTML】
<main class="stage">
<div class="terminal">
<div class="bar">
<span class="dot red"></span>
<span class="dot yellow"></span>
<span class="dot green"></span>
<span class="tname">~/portfolio</span>
</div>
<p class="line">
<span class="prompt">$</span>
<!-- ここにJSが1文字ずつ打ち込む -->
<span class="typed" id="typed"></span><span class="caret" aria-hidden="true"></span>
</p>
</div>
</main>
【CSS】
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
min-height: 360px;
display: grid;
place-items: center;
font-family: "Segoe UI", system-ui, sans-serif;
background:
radial-gradient(800px 500px at 50% 120%, #15324a 0%, transparent 60%),
#0b0f17;
padding: 24px;
overflow: hidden;
}
/* ターミナル風ウィンドウ */
.terminal {
width: min(560px, 92vw);
background: #11161f;
border: 1px solid #232b3a;
border-radius: 12px;
box-shadow: 0 24px 60px rgba(0,0,0,0.55);
overflow: hidden;
}
.bar {
display: flex;
align-items: center;
gap: 8px;
padding: 11px 14px;
background: #1a2130;
border-bottom: 1px solid #232b3a;
}
.dot { width: 12px; height: 12px; border-radius: 50%; }
.red { background: #ff5f56; }
.yellow { background: #ffbd2e; }
.green { background: #27c93f; }
.tname {
margin-left: 8px;
font-size: 12px;
color: #6b768c;
font-family: "Consolas", monospace;
}
.line {
padding: 26px 20px 30px;
font-family: "Consolas", "Courier New", monospace;
font-size: clamp(16px, 4.5vw, 24px);
color: #e6edf3;
display: flex;
align-items: baseline;
white-space: pre-wrap;
word-break: break-word;
}
.prompt { color: #27c93f; margin-right: 10px; }
.typed { color: #7ee787; }
/* 点滅カーソル */
.caret {
display: inline-block;
width: 10px;
height: 1.1em;
margin-left: 2px;
background: #7ee787;
transform: translateY(0.12em);
animation: blink 1s step-end infinite;
}
@keyframes blink {
50% { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
.caret { animation: none; }
}
【JavaScript】
// タイプライター効果: 複数フレーズを打って消す
(function () {
const out = document.getElementById('typed');
if (!out) return; // null安全
const phrases = [
'npm run build',
'deploy --prod',
'git push origin main',
'echo "Hello, Web"'
];
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
let p = 0; // 現在のフレーズindex
let i = 0; // 現在の文字位置
let deleting = false;
// setTimeoutを使い、速度を可変にして自然なタイピングに
function tick() {
const word = phrases[p];
if (!deleting) {
i++;
out.textContent = word.slice(0, i);
if (i === word.length) {
deleting = true;
return schedule(1100); // 打ち終えたら少し待つ
}
return schedule(70 + Math.random() * 60);
} else {
i--;
out.textContent = word.slice(0, i);
if (i === 0) {
deleting = false;
p = (p + 1) % phrases.length; // 次のフレーズへ
return schedule(280);
}
return schedule(38);
}
}
function schedule(ms) { window.setTimeout(tick, ms); }
if (reduce) {
// モーション控えめ: 1フレーズだけ静的表示
out.textContent = phrases[0];
} else {
schedule(500);
}
})();
# 外部ライブラリ
なし(追加ライブラリ不要)
# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。