www.youtube.com
この記事の流れ
なぜ作ろうと思ったか
4つのファイルでできている
動物の一生をコードで組み立てる
クリックすると何が起きるのか
効果音を自前で鳴らす
popup と設定の同期
作ってみた感想
なぜ作ろうと思ったか
私たちが幼いころには学校に携帯電話を持ち込むことは禁止され、スマートフォンやパソコンに触れる機会の若年化が進んでいる。
生まれたころから検索エンジンで検索するのが当たり前になってくる子供たちになっているはずである。
小さい子が楽しくインターネットをできる「どうぶつかくれんぼ」アプリをClaudeで作成してみたので、作成までの流れを書いていく。
機能で言えば無いに等しい。SEOにも生産性にも寄与しない。だからこそ、実装がそのまま 「見た目がどれだけ可愛く動くか」に直結する、素材としてちょうどよいお題だった。
4つのファイルでできている
構成はシンプルで、拡張機能の設定ファイル1つとロジック3つだけ。
manifest.json — 拡張機能の宣言。どのページに何を差し込むかを決める
content.js / content.css — 実際にページ上で動物を動かす本体
popup.html / popup.js — ツールバーアイコンから開く設定パネル
manifest.json
{
"manifest_version": 3,
"permissions": ["storage"],
"content_scripts": [{
"matches": ["<all_urls>"],
"css": ["content.css"],
"js": ["content.js"],
"run_at": "document_idle"
}]
}
必要な権限は storage だけ。ページの内容を読んだり書き換えたりする権限は要求していない。 動物はあくまで画面の最前面に浮かせているだけで、元のページのDOMには一切手を触れていない。
動物の一生をコードで組み立てる
本体は content.js の1ファイルで、動物1体につき「隠れる → 出現する → しばらく居座る → また隠れる」というサイクルを、setTimeoutの再帰呼び出しだけで表現している。そしてクリックして動物をクリックすると網が出て、音が鳴るという簡単な仕組みである。
content.js — サイクルを回す部分
function scheduleCycle(el, timers) {
const waitBeforeAppear = rand(6000, 18000);
const t1 = setTimeout(() => {
relocate(el);
show(el);
const dwell = rand(2000, 4500);
const t2 = setTimeout(() => {
if (!el.classList.contains('dkb-caught')) hide(el);
scheduleCycle(el, timers);
}, dwell);
}, waitBeforeAppear);
}
「隠れ場所」は上下左右4辺からランダムに1つ選び、relocate()が画面外の translateX/Y(±120%)から画面内のtranslateX/Y(±10%)へのCSS transform2値を計算するだけ。実際に動かすのはcontent.css側の transition: transform 1.1s cubic-bezier(...)で、JS側は「行き先の値を差し替える」だけに 徹している。アニメーションの責務を完全にCSSへ渡すことで、JS側のロジックが状態遷移だけに集中できる。
ブラウザのビューポート
hidden: translate(±120%) → visible: translate(±10%)
relocate() が4辺のどこかを抽選し、画面外の座標から画面内10%までtransformを遷移させて「にじり出る」動きを作る
クリックすると何が起きるのか
動物にはクリックイベントが仕込んであり、押すとcatchAnimal()が呼ばれる。 ここは3段階のタイマーで演出を組み立てている。
0ms — dkb-caughtクラスを付与し「ビクッ」と一瞬膨らむflinchアニメーションを再生。同時に捕獲音を再生し、動物の中心座標にSVG製の網を出現させる
300ms — 網が消えるアニメーションに切り替わりつつ、動物自体も縮小・上昇・フェードするliftアニメーションへ
750ms — 網要素をDOMから削除し、動物は絵文字を次のランダムな1匹に差し替えて、また隠れ状態に戻す
網はCSSの図形ではなく、content.js内に文字列として埋め込んだ小さなインラインSVG (円と格子状の線を組み合わせただけ)を都度DOMに挿入している。画像ファイルを1枚も使っていないのは、 拡張機能をできるだけ軽くしたかったのと、単純に「コードだけで完結させる」ことにこだわりたかったため。
捕まえた瞬間の網も、その後の効果音も、外部アセットは一切使わず全部コードで生成している。
効果音を自前で鳴らす
音声ファイルを用意する代わりに、Web Audio APIのOscillatorNodeを直接叩いて 「ポヨン」という捕獲音をその場で合成している。
content.js— 3音を重ねて捕獲音を作る
>|javascript|
function scheduleCatchTones(ctx) {
const now = ctx.currentTime + 0.01;
playTone(ctx, 200.0, now, 0.09, 'square', 0.30); // 低く短い「トン」
playTone(ctx, 784.0, now + 0.09, 0.14, 'sine', 0.35); // ソの音
playTone(ctx, 1046.5, now + 0.19, 0.24, 'sine', 0.35); // 1オクターブ上のド
}
|
playTone()の中身は、音量(gain)をlinearRampToValueAtTimeで一瞬立ち上げてから exponentialRampToValueAtTimeでスッと減衰させているだけ。波形の種類(square / sine)と 周波数とタイミングをずらして3つ重ねるだけで、想像していたよりずっとゲームらしい「捕獲音」になった。
つまずいた点ブラウザの自動再生ポリシー上、ページ読み込み直後はAudioContextが suspended状態のことがある。playCatchSound()では再生前に状態を確認し、 必要ならctx.resume()してから音を鳴らすようにして回避している。
ツールバーのアイコンを押すとpopup.htmlが開き、ON/OFFのトグルスイッチと 動物の大きさを変えるスライダーが出てくる。この2つの状態はchrome.storage.localに 保存され、開いている全タブのcontent.jsへリアルタイムに伝播する。
content.js — 設定変更をその場で反映する
chrome.storage.onChanged.addListener((changes, area) => {
if (area !== 'local') return;
if (changes.dkbEnabled) {
changes.dkbEnabled.newValue ? start() : stop();
}
if (changes.dkbSize) {
applySize(changes.dkbSize.newValue);
}
});
ページをリロードしなくても、ポップアップでスライダーを動かした瞬間にそのタブの動物のサイズが 変わるのはこの仕組みのおかげ。サイズの実体はJSの変数ではなくCSSカスタムプロパティ --dkb-sizeなので、反映のために要素を作り直す必要がない。
popup.js
>|javascript|
ON/OFF・サイズ操作
chrome.storage
dkbEnabled / dkbSize
|
content.js
ページ上の動物
.set()
onChanged
動物1体ぶんのサイクル
隠れる (hide)
出現→ぷるぷる (show)
クリック→捕獲演出
respawn
設定の同期経路(左〜中央)と、動物1体が回す hide → show → catch → respawn のサイクル(右下)
作ってみた感想
一番の収穫は、効果音をWeb Audio APIだけで自作できたこと。音声ファイルを1つも用意せず、 オシレータの周波数とゲインのエンベロープを数行のコードで組むだけで、 思っていたよりずっと「ゲームらしい」捕獲音になったのは素直に驚いた。
動きの部分もほぼ全てCSSのtransitionとkeyframes任せで、 JS側はタイミングと座標を決めているだけ。それでも「ぷるぷる」「ビクッ」「スッと消える」といった 生き物らしい情緒は十分に出せて、フレームワークもビルドツールも使わない小さな遊びでも、 手触りのあるものは作れるのだと実感できた。子供のおもちゃを両親が自作するのはよく見かけるが、紙コップや画用紙をお母さんが切って片手間で作るのが大半なので、生成AIを使ってデジタルネイティブを育てる「どうぶつかくれんぼ」アプリを作成してみた。
実用性のかけらもない拡張機能だが、たまに自分のブラウザで動物が捕まっているのを見かけると、 地味に得した気分になる。その一点だけを狙って作った甲斐はあったと思う。網で捕獲するとどうぶつの鳴き声が出る仕様にもしたかったが、今後の課題ということで残しておく。