



WithCodeMedia-1-pc
WithCodeMedia-2-pc
WithCodeMedia-3-pc
WithCodeMedia-4-pc




WithCodeMedia-1-sp
WithCodeMedia-2-sp
WithCodeMedia-3-sp
WithCodeMedia-4-sp









生徒チャットやターミナルみたいに、文字が1文字ずつ打たれる演出ってCSSで作れますか?
ペン博士作れるよ!steps()関数とwidthアニメーション、それにカーソルの点滅を組み合わせるのがコツ。今日はコピペで動くパターンを用意したよ。
ターミナルやチャット画面のように「文字が1文字ずつ打ち込まれていく」タイピングアニメーション。Webサイトのヒーローセクションや404ページなど、さまざまな場所で見かけます。
実はこの演出、JavaScriptを使わずCSSだけで実装できます。ただし「なぜこの書き方なのか」を理解せずにコピペすると、文字数が変わったときに壊れたり、日本語でうまく動かなかったりというトラブルが起きます。
この記事では、CSSタイピングアニメーションの仕組みから丁寧に解説したうえで、コピペで使える実装パターンを複数用意しています。ループさせる方法、複数行に対応する方法、日本語での注意点、そしてCSSの限界とJavaScript1行で解決する方法まで網羅します。

タイピングアニメーションは「width を 0 から 最終幅まで広げる」ことで実現します。テキストは overflow: hidden で隠しておき、widthが広がるにつれて左から文字が見えてくるという原理です。
ポイントはsteps() イージング関数を使うことです。通常の animation は滑らかに変化しますが、steps(N) を使うと N 段階のステップ状の変化になります。これにより「1文字ずつぱっぱっと現れる」タイプライター感が生まれます。
/* 滑らかに広がる(タイピングっぽくない) */
.smooth {
animation: expand 3s linear forwards;
}
/* 段階的に広がる(タイピング風) */
.typewriter {
animation: expand 3s steps(20, end) forwards;
/* steps(文字数, end) と書く */
}
@keyframes expand {
from { width: 0; }
to { width: 20ch; } /* 20文字分の幅 */
}
steps(20, end) の 20 は「アニメーション全体を20段階に分ける」という意味です。テキストが20文字なら steps(20) と書くことで、1ステップにつき1文字分進むように合わせます。

.typewriter {
font-family: monospace; /* 等幅フォント必須 */
white-space: nowrap; /* 折り返し禁止 */
overflow: hidden; /* はみ出し非表示 */
width: 0;
animation: typing 3s steps(30, end) forwards;
}
@keyframes typing {
to {
width: 30ch; /* テキストの文字数に合わせる */
}
}
<!-- 「Hello, WithCode!」は15文字 → steps(15)、width: 15ch -->
<p class="typewriter" style="animation: typing 2s steps(15, end) forwards;">
Hello, WithCode!
</p>
ch 単位は等幅フォントの「0」1文字分の幅を表します。等幅フォント(monospace)では全文字が同じ幅なので、「文字数ch = テキストの全幅」が成り立ちます。
border-right でカーソルを疑似的に表現し、animation で点滅させます。
.typewriter-cursor {
font-family: monospace;
white-space: nowrap;
overflow: hidden;
width: 0;
border-right: 2px solid #333; /* これがカーソル */
animation:
typing 3s steps(20, end) forwards,
blink 0.75s step-end infinite;
}
@keyframes typing {
to {
width: 20ch;
}
}
@keyframes blink {
0%, 100% { border-color: #333; } /* 表示 */
50% { border-color: transparent; } /* 非表示 */
}
<h1 class="typewriter-cursor">Welcome to WithCode</h1>
blink アニメーションに step-end を使うことで、滑らかにフェードするのではなく「パッ・パッ」とオン/オフが切り替わるカーソルらしい点滅になります。
タイピングが終わった後、カーソルを点滅させ続けることもできますし、数秒後に消すこともできます。
/* タイピング完了後もカーソル点滅を続ける */
.typewriter-keep-cursor {
font-family: monospace;
white-space: nowrap;
overflow: hidden;
width: 0;
border-right: 2px solid currentColor;
animation:
typing 3s steps(18, end) forwards,
blink 0.75s step-end infinite; /* infinite で永続 */
}
/* タイピング完了後にカーソルを消す */
.typewriter-hide-cursor {
font-family: monospace;
white-space: nowrap;
overflow: hidden;
width: 0;
border-right: 2px solid currentColor;
animation:
typing 3s steps(18, end) forwards,
blink 0.75s step-end 4, /* 4回だけ点滅(約3秒) */
hideCursor 0s 3.2s forwards; /* タイピング後に消す */
}
@keyframes hideCursor {
to {
border-color: transparent;
}
}
文字が打ち込まれてから消えて、また打ち込まれる……というループを作るには、@keyframes で「広がる → 縮む」の動きを定義します。
.typewriter-loop {
font-family: monospace;
white-space: nowrap;
overflow: hidden;
border-right: 2px solid #333;
width: 0;
animation:
typingLoop 4s steps(20, end) infinite,
blink 0.75s step-end infinite;
}
@keyframes typingLoop {
/* 0〜40%:文字を打ち込む */
0% { width: 0; }
40% { width: 20ch; }
/* 40〜60%:打ち終わった状態でカーソルを点滅させて待機 */
60% { width: 20ch; }
/* 60〜100%:文字を消す */
100% { width: 0; }
}
ただし1種類のテキストをループさせるだけなら上記で十分ですが、複数のテキストを切り替えてループさせる場合は CSS だけでは困難です。後述の JavaScript 版を使いましょう。
1行目が打ち終わったら2行目が打ち込まれる、という順番制御は animation-delay で実現します。
.line1,
.line2,
.line3 {
font-family: monospace;
white-space: nowrap;
overflow: hidden;
width: 0;
display: block; /* 各行をブロック要素に */
}
/* 1行目:すぐ開始、14文字 */
.line1 {
animation:
typeLine1 1.4s steps(14, end) forwards,
blink 0.75s step-end 3;
border-right: 2px solid #333;
}
/* 2行目:1行目完了後に開始(1.5秒後)、18文字 */
.line2 {
animation:
typeLine2 1.8s steps(18, end) 1.6s forwards,
blink 0.75s step-end 3;
animation-delay: 1.6s, 1.6s;
border-right: 2px solid #333;
}
/* 3行目:2行目完了後に開始(3.5秒後)、12文字 */
.line3 {
animation:
typeLine3 1.2s steps(12, end) 3.6s forwards,
blink 0.75s step-end infinite;
animation-delay: 3.6s, 3.6s;
border-right: 2px solid #333;
}
@keyframes typeLine1 { to { width: 14ch; } }
@keyframes typeLine2 { to { width: 18ch; } }
@keyframes typeLine3 { to { width: 12ch; } }
<div class="terminal">
<span class="line1">$ npm install</span>
<span class="line2">Installing packages...</span>
<span class="line3">Done! 12 added</span>
</div>
.terminal {
background: #1a1a2e;
color: #00ff41;
padding: 24px 28px;
border-radius: 8px;
font-family: 'Courier New', Courier, monospace;
font-size: 1rem;
line-height: 2;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}
.terminal::before {
content: '';
display: block;
width: 12px;
height: 12px;
border-radius: 50%;
background: #ff5f56;
box-shadow: 20px 0 0 #ffbd2e, 40px 0 0 #27c93f;
margin-bottom: 16px;
}
日本語テキストに対してCSSタイピングアニメーションを使う場合、英数字のときとは異なる問題が発生します。
ch 単位は「0(ゼロ)の幅」を基準にするため、等幅フォントの英数字では「1文字 = 1ch」が成立します。しかし日本語の全角文字は通常 2ch 分の幅になるため、steps の数と幅が合いません。
/* 日本語:em 単位を使う方法 */
.typewriter-ja {
font-family: monospace;
white-space: nowrap;
overflow: hidden;
width: 0;
/* 日本語10文字なら 10em(目安) */
animation: typingJa 3s steps(10, end) forwards;
}
@keyframes typingJa {
to {
width: 10em; /* 文字数 × em で指定 */
}
}
ただしem単位もフォントによって変わるため、完全に正確ではありません。最も確実なのは、実際にテキストを表示した状態の幅をブラウザの開発者ツールで測定し、px 単位で指定することです。
/* px 単位で確実に指定する方法 */
.typewriter-ja-px {
font-size: 1.5rem;
font-family: 'Hiragino Kaku Gothic Pro', 'Meiryo', sans-serif;
white-space: nowrap;
overflow: hidden;
width: 0;
/* 実際の幅を開発者ツールで計測して指定 */
animation: typingJaPx 3s steps(10, end) forwards;
}
@keyframes typingJaPx {
to {
width: 240px; /* 実測値を入れる */
}
}
プロポーショナルフォント(文字ごとに幅が異なるフォント)では、1文字ずつぴったり広がるタイピング効果はCSSだけでは実現できません。この場合は後述のJavaScript版を使います。

CSSタイピングアニメーションには以下の限界があります。
| 機能 | CSS | JavaScript |
|---|---|---|
| 1種類のテキストを表示 | 可能 | 可能 |
| 複数テキストを順番に切り替え | 困難(長い@keyframesが必要) | 簡単 |
| プロポーショナルフォント対応 | 不可 | 可能 |
| 日本語を正確に1文字ずつ | 困難 | 可能 |
| 動的なテキスト(APIから取得など) | 不可 | 可能 |
| ループ中のテキスト変更 | 不可 | 可能 |
// 1種類のテキストをタイピング表示
function typewriter(element, text, speed = 80) {
let i = 0;
element.textContent = '';
const timer = setInterval(() => {
if (i < text.length) {
element.textContent += text.charAt(i);
i++;
} else {
clearInterval(timer);
}
}, speed);
}
// 使い方
const el = document.querySelector('.typewriter-js');
typewriter(el, 'CSSタイピングアニメーション', 100);
// 複数テキストを順番にタイピング→削除→次のテキスト
function multiTypewriter(element, texts, {
typeSpeed = 80,
deleteSpeed = 40,
pauseAfterType = 1500,
pauseAfterDelete = 400
} = {}) {
let textIndex = 0;
let charIndex = 0;
let isDeleting = false;
function tick() {
const currentText = texts[textIndex];
if (!isDeleting) {
// タイピング中
element.textContent = currentText.substring(0, charIndex + 1);
charIndex++;
if (charIndex === currentText.length) {
// 打ち終わり → 待機してから削除開始
isDeleting = true;
setTimeout(tick, pauseAfterType);
return;
}
setTimeout(tick, typeSpeed);
} else {
// 削除中
element.textContent = currentText.substring(0, charIndex - 1);
charIndex--;
if (charIndex === 0) {
// 削除完了 → 次のテキストへ
isDeleting = false;
textIndex = (textIndex + 1) % texts.length;
setTimeout(tick, pauseAfterDelete);
return;
}
setTimeout(tick, deleteSpeed);
}
}
tick();
}
// 使い方
const el = document.querySelector('.multi-typewriter');
multiTypewriter(el, [
'Webサイトを作ろう',
'デザインを磨こう',
'CSSを学ぼう'
]);
/* JS版のカーソルスタイル */
.multi-typewriter {
display: inline-block;
border-right: 2px solid currentColor;
padding-right: 2px;
animation: blink 0.75s step-end infinite;
min-height: 1.5em; /* テキストが消えたときの高さ確保 */
}
@keyframes blink {
0%, 100% { border-color: currentColor; }
50% { border-color: transparent; }
}
CSS @keyframesの基礎を理解した上でJavaScriptと組み合わせると、柔軟でメンテナンスしやすいアニメーションが書けます。
.terminal-green {
background: #0d1117;
color: #39d353;
font-family: 'Courier New', monospace;
font-size: 1.1rem;
padding: 20px 24px;
border-radius: 6px;
display: inline-block;
white-space: nowrap;
overflow: hidden;
width: 0;
border-right: 2px solid #39d353;
animation:
typing 2.5s steps(20, end) forwards,
blink 0.6s step-end infinite;
}
@keyframes typing {
to { width: 20ch; }
}
@keyframes blink {
50% { border-color: transparent; }
}
.typewriter-minimal {
font-family: 'Georgia', serif;
font-size: 2rem;
color: #1a1a1a;
white-space: nowrap;
overflow: hidden;
width: 0;
border-right: 3px solid #d16176;
padding-right: 4px;
animation:
typing 3s steps(16, end) forwards,
blink 0.8s step-end infinite;
}
.typewriter-underline {
position: relative;
display: inline-block;
white-space: nowrap;
overflow: hidden;
width: 0;
font-family: monospace;
animation: typing 2s steps(14, end) forwards;
}
.typewriter-underline::after {
content: '';
position: absolute;
bottom: -4px;
left: 0;
width: 100%;
height: 3px;
background: #d16176;
transform: scaleX(0);
transform-origin: left;
animation: underlineIn 0.4s ease 2.1s forwards; /* タイピング完了後に発火 */
}
@keyframes underlineIn {
to { transform: scaleX(1); }
}

steps(N) の N はテキストの文字数に一致させる必要があります。ずれると、最後の数文字が一瞬で表示されたり、手前で止まったりします。
/* テキスト「Hello World」= 11文字(スペース含む) */
/* NG:文字数と steps が合っていない */
.bad { animation: typing 2s steps(10, end) forwards; }
/* NG:widthも合っていない */
@keyframes typing { to { width: 9ch; } } /* 10文字分しかない */
/* OK:文字数に合わせる */
.good { animation: typing 2s steps(11, end) forwards; }
@keyframes typing { to { width: 11ch; } }
width: fit-content は固定値ではないため、@keyframes の to で使うと動作しません。必ず固定の長さ(ch, em, px)で指定します。
span など inline 要素では width が効きません。display: inline-block か display: block に変更する必要があります。
/* NG */
span.typewriter {
width: 0; /* inline要素にwidthは効かない */
overflow: hidden;
}
/* OK */
span.typewriter {
display: inline-block; /* または block */
width: 0;
overflow: hidden;
}
@media (prefers-reduced-motion: reduce) {
.typewriter,
.typewriter-cursor,
.typewriter-loop {
animation: none;
width: auto; /* テキストをすべて表示 */
overflow: visible;
border-right: none;
}
}
reduced-motion 環境では width: 0 + overflow: hidden のままアニメーションを止めてしまうと、テキストが見えない状態のままになります。必ず width: auto と overflow: visible を設定してテキストが読める状態にしてください。
タイピングアニメーションは、steps()でwidthを段階的に広げ、border-rightでカーソルを表現するのが基本です。ポイントは①文字数に合わせてwidthとsteps()の数を合わせる②等幅フォントで文字ズレを防ぐ③カーソル点滅は別の@keyframesで作る、の3点。複数行やJavaScript併用の応用も紹介したので、用途に合わせて選んでください。
等幅フォント(monospace)を使うことを推奨します。等幅フォントでは全文字が同じ幅なので「N文字 = Nch」が正確に成立し、steps() と width: Nch の計算が簡単になります。代表的な等幅フォントは Courier New、Consolas、’Source Code Pro’、’Fira Code’ などです。プロポーショナルフォント(ヒラギノ角ゴなど)でどうしてもタイピング風を実現したい場合は JavaScript 版を使ってください。
CSSのみでは困難です。steps() の数値と width の値をあらかじめ固定で書く必要があるため、動的なテキストへの対応はJavaScriptが必要です。JavaScriptでテキストの文字数を取得して steps と width を動的に設定するか、本記事で紹介したJavaScriptタイピング関数を使う方法を選んでください。
Intersection Observer API と組み合わせると、要素がビューポートに入ったタイミングでアニメーションを開始できます。初期状態ではアニメーションを animation: none に設定しておき、Intersection Observer のコールバックで is-visible などのクラスを追加してアニメーションを発火させるパターンが一般的です。
border-right のスタイルを変更することで色・太さを変えられます。border-right: 3px solid #d16176 のように書けばカーソルが太くなり色も変わります。ブロック状のカーソル(■)を作りたい場合は、::after 疑似要素でinline-blockの四角形を作り、blink アニメーションで opacity を 0/1 切り替える方法が使えます。
Typed.js や TypeIt などの軽量ライブラリが有名です。ただし、記事で紹介したJavaScriptのmultiTypewriter関数程度の機能であればライブラリ不要で実装できます。外部依存を減らしたい場合は自前実装をおすすめします。パフォーマンス上も自前実装の方がバンドルサイズを抑えられます。
CSSの実装テクニックを「レイアウト・装飾・アニメーション・基礎・トラブル解決」の目的別に探せる総まとめページを用意しています。実装で迷ったときの索引としてどうぞ。

目的に合わせて選べる「AI副業」「AI転職」「AI活用」の3コースを用意。副収入・キャリアチェンジ・日常の生産性アップまで、あなたのゴールに合わせてAIを学べます。
会員登録はカンタン30秒で完了します。まずは公式LINEから、無料でAI学習をスタートしましょう!
WithCodeでWeb制作を習得後、フリーランスエンジニアとして活動。HTML/CSS・JavaScript・WordPress案件を中心に年間20件以上の制作実績を持つ。「難しい技術をわかりやすく」をモットーに、初心者〜中級者向けの技術記事を執筆。副業・フリーランス独立を目指す方に向けた情報発信に注力している。
公式サイト より
今すぐ
無料カウンセリング
を予約!