このブログの404ページを、ターミナルのエラー風に作り直しました。
先に結論を書くと、「404」の真ん中の0を肉球が押しのける動きは、0と肉球をCSSのgridで同じ場所に重ねて、CSSのアニメーションだけで作っています。
JavaScriptは使っていません。
🎥 この記事の内容は動画でも解説しています。
404ページは、存在しないURLを開いたときに出るページです。
404とは何か、という基本は404とは?の記事に書いています。
この記事は、その404ページの見た目を変えたときの記録です。
できあがった404ページ
存在しないURLを開くと、黒い画面にこう出ます。
- 大きな「404」。読み込んで1秒後に、肉球がバンッと押されて、その途中で真ん中の0が弾き飛ばされる
- 見出しの「ページが見つかりません」と、短い説明
- ターミナル風の3行。開いたURLを
catして、No such file or directoryと返ってきたように見せる - その下に、キーワード検索と新着記事。いちばん下に「トップページへ戻る」ボタン
ターミナル風の3行は、たとえば/abc/を開いたら、こう出ます。
$ cat /abc/
cat: /abc/: No such file or directory
$
最後の$のうしろでは、カーソルが点滅しています。
自分のブログはWeb制作とAIの話が中心なので、ターミナルの見た目が合うと思って、この形にしました。
404.phpでやっていること
WordPressでは、使っているテーマのフォルダ(wp-content/themes/テーマ名/)の直下に404.phpを置くと、ページが見つからないときにそれが使われます。
実装はClaude Codeにやってもらいました。
開いたURLをターミナル風の行に出すところは、こうなっています(説明のために一部を抜き出しています)。
$req = isset( $_SERVER['REQUEST_URI'] )
? wp_parse_url( wp_unslash( $_SERVER['REQUEST_URI'] ), PHP_URL_PATH )
: '';
$req = $req ? mb_strimwidth( $req, 0, 80, '…' ) : '/';
echo '<span class="c-notfound__prompt">$ cat ' . esc_html( $req ) . '</span>';
echo '<span class="c-notfound__error">cat: ' . esc_html( $req ) . ': No such file or directory</span>';
気をつけたのは2つです。
- URLは、パスの部分だけを取り出して、表示幅80で省略する(長いURLで画面が崩れないように。全角は2つ分に数えられます)
- 画面に出すときは
esc_html()を通す(URLに変な文字を入れられても、HTMLとして動かないように)
URLをそのまま画面に出すと、そこにスクリプトを混ぜられるおそれがあります。
見た目のための遊びでも、ここだけは外せません。
0を肉球が押しのける仕組み
「404」は、こういうHTMLにしています。
<p class="c-notfound__code" aria-hidden="true">
<span>4</span>
<span class="c-notfound__slot">
<span class="c-notfound__zero">0</span>
<span class="c-notfound__paw"></span>
</span>
<span>4</span>
</p>
大事なのは、真ん中のc-notfound__slotです。
中の0と肉球を、gridで同じマスに重ねています。
.c-notfound__slot {
display: grid;
place-items: center;
> * {
grid-area: 1 / 1;
}
}
どちらもgrid-area: 1 / 1なので、同じ場所にぴったり重なります。position: absoluteで重ねるより、0と肉球の大きさがずれにくいのがいいところです。
動きは、こんな順番でタイミングを合わせています。
- 肉球: 読み込みの1秒後に、大きく傾いた状態から縮みながら着地する(押されたように見せる)
- 0: 肉球が縮んでいく途中で、右上へ回りながら小さくなって消える(弾き飛ばされたように見せる)
- 0が飛ぶのと同時に、水色の輪が広がる。数字全体も少し沈んで戻る
0を飛ばすところは、こんな形です。
.c-notfound__zero {
animation: notfound-knock 0.5s 1.12s ease-in forwards;
}
@keyframes notfound-knock {
to {
opacity: 0;
transform: translate(0.55em, -0.7em) rotate(55deg) scale(0.5);
}
}
動かす量をemで書いているので、スマホで数字が小さくなっても、飛び方の比率は変わりません。
肉球は着地したあと、数秒おきに小さく揺れ続けます。
肉球の形は、ブログのロゴの画像をmaskで切り抜いて、白で塗っています。
画像の色に関係なく、CSSで色を決められます。
.c-notfound__paw {
width: 100%;
aspect-ratio: 160 / 146;
background: #fff;
mask: url("assets/img/icon.png") center / contain no-repeat;
opacity: 0;
animation: notfound-stamp 0.42s 1s cubic-bezier(0.2, 1.4, 0.4, 1) forwards;
}
@keyframes notfound-stamp {
0% {
opacity: 0;
transform: scale(2.8) rotate(-12deg);
}
55% {
opacity: 1;
transform: scale(0.86) rotate(4deg);
}
100% {
opacity: 1;
transform: none;
}
}
ここに載せたのは着地の部分だけです(揺れ続ける動きは省いています)。
最初は透明にしておいて、1秒待ってから着地させます。
0のほうは1.12秒から動き出すので、肉球が縮みきる少し前に、0が飛び始めます。
動きを止めたい人には、止まった絵で見せる
OSの設定で「視差効果を減らす」(iPhone)や「動きを減らす」(Mac)をオンにしている人には、アニメーションを止めています。
@media (prefers-reduced-motion: reduce) {
.c-notfound__paw {
opacity: 1;
animation: none;
}
.c-notfound__zero {
display: none;
}
}
止めるだけだと、肉球が見えないまま0が残ってしまいます。
なので、最初から肉球を出して、0は消しておく。
動いたあとの絵が、最初から出ている状態です。
大きな「404」とターミナル風の行は、見た目のための飾りです。
どちらにもaria-hidden="true"を付けて、読み上げの対象から外しています。
ページの中身は、「ページが見つかりません」の見出しと説明から読まれます。
ボタンは、新着記事の下に置いた
最初は「トップページへ戻る」ボタンを、説明のすぐ下に置いていました。
ただ、404ページに来た人がまず探すのは、元々見たかった記事のはずです。
なので、検索と新着記事を先に出して、ボタンはいちばん下にしました。
これは自分が実際の画面を見て、決めて直してもらったところです。
見た目を変えても、404を返しているか確かめる
404ページを作り直したときに、いちばん気をつけたいのがここです。
見た目が「ページが見つかりません」でも、サーバーが200(成功)を返していると、検索エンジンには普通のページに見えてしまいます。
本番で、存在しないURLを開いたときの応答を確かめました。
$ curl -sI https://cats-hand.pro/nonexistent-xyz/ | head -1
HTTP/2 404
ちゃんと404を返しています。404.phpはWordPressが404と判断したときに使われるテンプレートなので、普通に作れば404のままです。
ただ、プラグインや設定で変わることもあるので、作り直したら一度は確かめておくと安心です。
まとめ
404ページは、見た目でかなり遊べるページです。
ただ、来た人がしたいのは、元々見たかった記事を探すこと。
なので、演出は最初の数秒だけにして、その下に検索と新着記事を並べました。
URLを出すところはesc_html()、動きを止めたい人には止まった絵、作り直したら404を返しているかの確認。
ここだけ押さえておけば、上の飾りは好きに変えて大丈夫だと思います。