HTMLとCSSで、引き出しから自分が飛び出す仕掛けを作ってみた
こんなことを試しました。
リンクにカーソルを重ねると、引き出しが動いてアバターがひょこっと登場。HTMLとCSSのサンプルを動かしながら作れます。
公開サイトで検証・動くデモと全コードあり

この記事の目次
白い背景は好き。でも、ちょっとおとなしいな……。
「りゅうのらぼ」のホームを作るとき、そんなところから見た目を調整していきました。きれいに並んでいるだけより、おもちゃ箱みたいに、触ると何か起きる場所にしたい。
そこで引き出し型のリンクに、アバターが出てくる仕掛けを入れています。今回は公開中のページを実際に操作しながら、どう見えるか確かめてみました。
閉じているときは、普通に読める入口に
普段はこんな見た目です。プロフィール、おしごと、ブログ、SNS、日記。どこへ行くかは、何もしなくても読めます。

引き出しは、ページへ移動するためのリンクです。飛び出す演出が分からなくても、押したら目的のページへ行ける。その土台に、少しだけ遊びを足しています。
触れると、箱がずれて顔が出る
「ブログ」の引き出しにフォーカスを移してみると、枠が少しずれ、その上からアバターと紙が出てきました。

顔はGitHubのプロフィール画像。体と腕は別の小さなパーツです。全部を一枚の絵にして動かすのではなく、顔・体・小物を組み合わせています。
最初に引き出しが動いて、その少しあとに顔が出る。ぴったり同時に動くよりも、ひょこっと反応した感じにしたかった部分です。
中身を見てみると、意外と地味な仕組み
立体の箱を用意しているわけではありません。実装を確認すると、引き出しの位置を左へ9px、下へ3pxずらし、影の位置も変えています。
アバターは、普段は下へ隠しておいて、反応すると上へ移動させる仕組み。出てくるときの動き方を少し弾むように調整しています。
コードの量を見せるより、まずは画面で「何が違うか」を見たほうが伝わりそうだったので、今回の写真は同じページの通常時と反応した状態にしました。
別の引き出しも見てみた
続いて「おしごと」へフォーカスを戻すと、紙の代わりにパソコンが出てきます。

実装上は、プロフィールでは手を振り、日記では鉛筆が書くように動く、といった違いも付けています。今回写真にしたのは、おしごととブログの2種類です。
動けばいい、というわけでもなさそう
画面を確認していて、アバターは引き出しの上側へ出るので、ひとつ上の引き出しに重なることも分かります。遊びを足した分だけ、文字の読みやすさも見ておきたいところです。
また、スマートフォンにはマウスのカーソルがありません。動きを見せることだけを前提にせず、普段からラベルを読めるようにしています。今回の検証はPC上のキーボード操作で、スマートフォンでのタップ操作を実機検証した記録ではありません。
ちょっとした仕掛けですが、「ここ、触ると何か出るんだ」と気付いてもらえると楽しそう。そんな寄り道を、少しずつ増やしていければと思います。
確認日:2026年11月4日。スクリーンショットは公開サイトを実際に操作して撮影。仕組みはホームのコードとアバターのコードで照合しました。
同じものを作るなら、まずこの1ファイルで試してみる
ここからは自分のサイトに入れてみたい方向けです。まずは本番の仕掛けを小さくしたサンプルを作りました。必要なのはテキストエディタとブラウザだけ。Node.jsやライブラリのインストールは不要です。
本番は「押すと移動するリンク」ですが、このサンプルはスマートフォンでも動きを試せるように、「押すと開く引き出し」と「中にあるリンク」に分けています。顔は配布しやすい絵文字です。
- 下のコードをすべてコピーします。
- エディタで
index.htmlという名前のUTF-8のプレーンテキストファイルに保存します。末尾が.html.txtにならないようにします。Macのテキストエディットを使う場合は「フォーマット」→「標準テキストにする」を選びます。 - 保存したファイルをブラウザで開きます。
- 引き出しにカーソルを重ねて顔が出るか確認。次にクリックして中のリンクを開きます。
- Tabキーで引き出しを選び、Enterで開閉できるかも試します。スマートフォンではタップで開閉します。

そのまま動くHTML・CSS
以下がサンプルの全コードです。長めですが、まず丸ごと動かしてから、下で説明する3か所だけ変えると追いやすいと思います。
<!doctype html>
<html lang="ja">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>飛び出す引き出し・最小サンプル</title>
<style>
* { box-sizing: border-box; }
body { margin: 0; padding: 32px 20px; color: #303d48; background: #fff;
font-family: system-ui, sans-serif; line-height: 1.8; }
main { max-width: 540px; margin: auto; }
h1 { font-size: 26px; }
.cabinet { padding: 85px 24px 24px; background: #eee9df;
border: 2px solid #303d48; border-radius: 16px; }
.drawer { --color: #fff0b8; position: relative; }
.drawer summary { position: relative; padding: 24px; list-style: none;
border: 2px solid #303d48; border-radius: 8px; background: var(--color);
box-shadow: 0 5px 0 #303d48; cursor: pointer;
transition: transform .2s, box-shadow .2s; }
.drawer summary::-webkit-details-marker { display: none; }
.drawer summary::after { content: '▰'; float: right; }
.drawer summary:focus-visible { outline: 3px solid #315f91; outline-offset: 5px; }
/* 上側は空ける。隠すのはキャラクター用の窓だけ。 */
.window { position: absolute; right: 18px; top: -72px; width: 84px;
height: 72px; overflow: hidden; pointer-events: none; }
.avatar { display: block; font-size: 54px; text-align: center;
transform: translateY(80px); opacity: 0;
transition: transform .5s .1s cubic-bezier(.2,1.5,.4,1), opacity .2s; }
.drawer:is(:hover, :focus-within, [open]) summary {
transform: translate(-6px, 2px); box-shadow: 6px 7px 0 #303d48; }
.drawer:is(:hover, :focus-within, [open]) .avatar {
transform: translateY(0); opacity: 1; }
.contents { margin-top: 18px; padding: 16px; background: #fff;
border-radius: 8px; }
a { color: #245b89; }
#destination { margin-top: 36px; padding: 20px; background: #eef5ef; }
@media (prefers-reduced-motion: reduce) {
.drawer summary, .avatar { transition: none; }
}
</style>
<main>
<h1>引き出しを、ひとつ作ってみる。</h1>
<p>カーソルを重ねると顔が出ます。クリック・タップ・Enterで開閉できます。</p>
<div class="cabinet">
<details class="drawer">
<summary>
<span class="window" aria-hidden="true"><span class="avatar">🙂</span></span>
ブログのひきだし
</summary>
<div class="contents"><a href="#destination">記事の置き場所へ進む →</a></div>
</details>
</div>
<section id="destination"><h2>記事の置き場所</h2><p>ここまで移動できれば、リンクも動いています。</p></section>
</main>
</html>
自分のサイト用に変えるのは、まず3か所
- 名前:
ブログのひきだしを表示したい言葉へ。 - 行き先:
href="#destination"を自分のブログURLへ。確認用のsection id="destination"は、その後削除して構いません。 - 色:
--color: #fff0b8を好みの色へ。文字とのコントラストも確認してください。
自分のアバターにしたいときは、HTMLと同じフォルダーへ avatar.png を置き、絵文字の span を次の画像に置き換えます。
<img class="avatar" src="avatar.png" alt="" width="64" height="64">
CSSには .avatar { width: 64px; height: 64px; object-fit: cover; margin: 4px auto; } を追加します。画像は装飾なので、この例では空のaltにしています。名前やリンク先は画像の外に文字で残します。
なぜ動く? ここだけ分かれば調整できる
translateY(80px) で顔を窓の下へ隠し、反応したら translateY(0) へ戻しています。transition は、その移動を一瞬ではなく時間をかけて見せる設定です。
:hover はカーソル、:focus-within はキーボード操作、[open] は開いた状態への対応。details と summary の標準機能を使うため、開閉用のJavaScriptはありません。
動きを減らす設定では transition: none にしています。開閉できることはそのままで、移動アニメーションを省きます。
表示がおかしいときに見るところ
- 顔が出ない:引き出しや外側の箱に
overflow: hiddenを付けていないか確認。隠すのは.windowの中だけです。 - 画像だけ出ない:
avatar.pngの名前・拡張子・保存場所が合っているか確認。 - リンク先へ進めない:このサンプルは一度開いてから、中のリンクを押します。summaryは移動ボタンではありません。
- 別の引き出しの文字に重なる:複数並べる場合は上に顔が出る分の余白を取ります。まず
margin-top: 85pxを目安に調整してください。
完成の確認:顔が出る/クリックやEnterで開閉する/中のリンクで移動する。この3つを自分の環境でも確認できたら、最初のひとつは完成です。