コンテンツにスキップ

Claude Code で一人完結型のWebアプリを作る:アンビエント・コラージュ・スタンプカード

WranglerでCloudflareに公開するまで進んだなら、作って公開するところまでは一通りできている。この記事は、その先を3つ作った記録。全画面で眺めるアンビエント、写真を並べるコラージュ、毎日押すスタンプカード。

3つのアプリの画面を横に並べたもの。左が暗い画面に輪が広がるアンビエント、中央が白い台紙にイラストを並べたコラージュ、右がスタンプの付いた9月のカレンダー

左からアンビエント、コラージュ、スタンプカード。この画像自体、2章のコラージュで並べて書き出したもの

どれもサーバにデータを置かない。ブラウザの中だけで完結する。

制作や公開の手順の詳細は書かない。作っている途中で出てきた判断を中心に書く。どちらの描き方を選ぶか、「保存したい」が何を指しているか、どこでやめるか、いつデータベースが要るようになるか、など。

前提は、CloudflareにHTMLをアップロードして公開するWranglerでCloudflareに公開するを終えていること。準備と公開の手順は載せていない。どこを見ればよいかは記事末の補足にまとめてある。

3つのアプリは残すものの重さで並べてある。下に行くほど身軽さが減る。

普段どう使うか何を残すかどこに
1 アンビエント置いておく何も残さないどこにも
2 コラージュやりたいときに開く成果物だけ画像ファイルにして手元へ
3 スタンプカード毎日開くデータ本体ブラウザの中

同じ並びが、そのまま「データベースが欲しくなる順」にもなっている。理由は4章で書く。

本記事では、実際に作ったときの記録を中心に書く。同じ依頼文を渡しても、同じものは出てこない。エージェントは毎回違う書き方をするし、こちらが気づく順番も違う。出てきたものが違っても、たいてい問題ないので、流れを見て参考にしてほしい。

1. アンビエント:ただ置いておくもの

Section titled “1. アンビエント:ただ置いておくもの”

全画面に、雨粒が落ちたように点が輪になって広がる。ランダムに、ゆっくり。輪が広がるたびに、音も鳴る。ぼーっと眺めるためのもの。

暗い画面に、大小の輪がいくつも広がっているアンビエントアプリの画面

輪が広がっては薄くなって消えていく。同じ模様は二度と出ない

▶ 作例サイト: ambient-sample.vibecodingnotes.com:この章で作ったアンビエントに、音の高さや雨の降り方の調整を加えたもの。タップすると始まり、音が鳴る。

まず広がる輪の絵(表面)、次に鳴る音を実装していく。

使い方としては、スクリーンセーバー代わりに流しっぱなしにする、オンライン会議の開始前に映しておく、作業中に脇のディスプレイで流す、古いタブレットを立てて店先に置く、など。公開してあれば URL を開くだけなので、端末を選ばない。

作業フォルダを作って Claude Code を起動し、こう頼んだ。

Claude
全画面で、雨粒が落ちたみたいに点が輪になって広がるのが
ランダムに出てくるWebアプリを作って。ぼーっと眺めるやつ。
public/index.html 1ファイルで。

開いてみると、暗い画面に輪が広がっては消えていく。中身は <canvas> という要素がひとつあるだけ。1枚の紙に描くのと同じで、輪を毎回まとめて描き直している。描いた線は紙と一体になるので、あとから「この輪だけ動かす」はできない。動かしたければ、消してもう一度描く。

このアプリはそれで困らない。掴んで動かすものが無いし、どうせ毎回描き直すから。輪は同時に何個も出ていて、そのすべてが毎回変わる。1個ずつ別々の部品にすると、その数だけ手間が増える。

しばらく眺めてみる。何が出てくるかは、そのとき次第。気に入ればそれでよい。

絵が動くようになったので、音を足した。

Claude
輪が広がるときに音も鳴るようにして

ここで出てくる音は、頼むたびにかなり変わる。手元では「ポン、ピョン」という水滴の効果音になった。周波数をランダムに選んで下に落とす作りで、「雨粒」という言葉には忠実。別のときは、ピアノに近い音が出てきた。

何がよいかは、聴いた人にしか決められない。

このアプリは何のデータも保存しない。 毎回違う内容になるところがポイントなので、再現する理由はあまりなく、再現のためのデータを保存する必要もない。

このあとの2つのアプリでは「保存したい」が出てくる。

ここまでで一度公開した。手順は補足を参照。

公開すると、URL を開くだけでどの端末でも流せるようになる。手元のタブレットでも、会議の画面共有でも。

同じ「眺めるもの」を作り込んだ事例として turnlefto が参考になる。左にしか曲がれない一筆書きが画面を埋めていき、閉じた領域に色が塗られ、そのたびに音が鳴る。音もペンタトニックとなっている。

この章のアンビエントは、60分の動画にもした(YouTube)。雨の強弱が入れ替わり、最後に雨がやむ。

2. コラージュ:やりたいときに開くもの

Section titled “2. コラージュ:やりたいときに開くもの”

真っ白な台紙に、写真をドラッグ&ドロップで放り込む。置く、動かす、大きさを変える、重ね順を変える。並べ終わったら、1枚の画像としてダウンロードする。

白い台紙に3枚のイラストを重ねて並べたコラージュアプリの画面。右上に「画像として保存」ボタンがある

選んでいる写真だけ細い枠が出る。右上の 画像として保存 で1枚に書き出す

写真は手元のファイルから読むだけで、どこにもアップロードしない

2-2. まず画像を置けるようにした

Section titled “2-2. まず画像を置けるようにした”
Claude
画像をドラッグ&ドロップで置ける、真っ白な台紙みたいなWebアプリを作って。
public/index.html 1ファイルで。

「キャンバス」という言葉を使わず、わざわざ「真っ白な台紙」という表現を使ったのには理由がある。HTML には <canvas> という要素があって、1章で使った紙に描くほうの仕組みなので、そちらのことだと認識されることがあるため。

描くほうで作られると困る。写真は1枚ずつ動かしたいが、描いた絵(写真も)は背景と一体になるので、「この写真だけ掴んで動かす」仕組みをわざわざ作る必要がある。だからここでは、台紙に「貼る」、つまり別レイヤーで扱うほうが合う。台紙と言えば、まず読み違えられない。

分かれ目は「掴むか」。1章のアンビエントは掴むものが無くて、どうせ毎回描き直すものだった。だから描くほうが向いていた。同じ「絵を出す」でも、正解が入れ替わる。

プロンプトを入力するとアプリができた。 写真を放り込むと、落とした場所に置かれた。手元では、そのまま動かせて、大きさも変えられて、消すこともできた。頼んだのは「置ける」ことだけなのに、いろいろな機能が同時に実装された。

何が入ってくるかは、そのとき次第。

2-3. どこにも送っていないことを、自分で確かめる

Section titled “2-3. どこにも送っていないことを、自分で確かめる”

このアプリは、写真をドラッグ&ドロップで読み込むが、そのファイルはブラウザから外部へは送信されない。

人に見せたくない写真や家族の写真などを扱うときに外部に流出しないか心配なときは、確かめたほうがよい。いちばん簡単なのは、Wi-Fi を切ること。

ページを開いたあとに Wi-Fi を切って、写真を放り込んでみた。ふつうに置けた。送っていれば、切った時点でできなくなる。

2-4. 1枚の画像としてダウンロードする

Section titled “2-4. 1枚の画像としてダウンロードする”

完成したコラージュ画像を保存したくなった。ただ、その「保存」が何を指すかは3通りある。

何を残すかこの記事では
サーバに置くアカウントを作ってクラウドへ扱わない
作りかけを残す途中の配置扱わない。3章で別のものを扱う
成果物を持ち出す完成したコラージュこれ

やりたいのは3つ目。用途は、できあがったものを人に見せたり、SNS に上げたりすること。手元にファイルが要る。

「保存」という言葉が出てきたら、どれのことかを先に確認する。 全部やろうとすると、要らないものまで作ることになる。

Claude
作ったコラージュを1枚の画像ファイルとしてダウンロードできるようにして

プロンプトでは「保存」という言葉を使わず、本当にやりたいことである「ダウンロード」を使っている。判断は自分でして、頼むときは言い切る。

しかし、「ダウンロードできるようにして」と頼んだだけでは、決まっていないことが残る。できあがったものを見て、自分の用途に合っているか確かめる。

何を決める必要があるか
切り取る範囲置いた写真をぴったり囲むか、画面全体か
解像度画面に見えている大きさのままか、大きめに書き出すか
形式PNG か JPEG か
背景白で塗るか、透明にするか
ファイル名毎回同じ名前か、日時を入れるか

切り取る範囲は、手元では2回試して、2回とも異なる方式になった。一度は置いた写真をぴったり囲む形になり、もう一度は台紙全体をそのまま書き出す形になった。

画面の外にはみ出した写真写真の無いところ
ぴったり囲む一緒に入る入らない
台紙全体切れる余白として入る

ファイル名も対処が必要だった。毎回同じファイル名だと扱いづらい。

2-5. とりあえず、ここまでにしておく

Section titled “2-5. とりあえず、ここまでにしておく”

使っていると、もう少し足したくなる。

  • 複数の写真をまとめて選んで、同時に動かす・大きさを変える
  • 写真の要らないところを切り抜く
  • 縦横の比率を変える
  • 回転させる

どれも、ここでは取り上げない。 理由は2つあって、重さが違う。

ひとつめ。複数選択だけ、作り方の前提が変わる。 それまでは「選ばれているのは1枚」で通っていたのが、「選ばれているのは何枚か」になる。選択を扱う処理を、全部書き直すことになる。手元で試したときは、足したコードは20行ほどなのに、それまで書いてあった処理がほぼ全部作り替えになった。行数が増えることと、書き直しになることは別。

ふたつめ。残りは一つずつなら軽いが、キリがない。 切り抜きを足せば「角を丸めたい」が来る。回転を足せば「少しだけ傾けたい」が来る。画像編集ソフトを作りたいわけではない。

機能を足すのは楽しい。足したければ足せばよい。ただ、どこで止めるかを決めるのは自分。

そして書き直しが続くと、確かめないまま決めたことが積み上がっていく。次の節はその話。

2-6. 決めたことと確かめたことを書き留める

Section titled “2-6. 決めたことと確かめたことを書き留める”

この章では、作りながら細かい仕様を決めていった。しかし、なぜそう決めたのかの理由はコードには残らない。

日をまたいで作業の続きを頼むと、これが問題になる。エージェントには、それが守るべき仕様なのか、そのとき都合で入れただけのものなのか区別がつかない。消してよいか分からないまま、消されることもある。

調べたことや確かめたことも同様。半年後に同じことを調べ直したくない。

これらは別途記録しておくとよい。エージェントが記録して、必要に応じてエージェントが読む体制が便利。やり方は作業メモと決定をリポジトリの中に残すで解説している。

3. スタンプカード:毎日開くもの

Section titled “3. スタンプカード:毎日開くもの”

カレンダーに、毎日スタンプを押していく。押した日が一目で分かる。何をカードにするかは自分で決める。

9月のカレンダー。1日・2日・4日・6日にオレンジのスタンプが付き、今日の6日には青い枠が出ている

押した日はオレンジ、今日は青い枠。下に JSONで保存JSONを読み込む

続いているかどうかが、面積で見えるのがこの形の値打ち。

3-2. データはブラウザの中に記録

Section titled “3-2. データはブラウザの中に記録”
Claude
カレンダーに毎日スタンプを押していけるWebアプリを作って。
押した日が一目で分かるようにしたい。
ページを閉じても記録が残るようにして。
public/index.html 1ファイルで。

「閉じても残る」を実現するのに使われるのが localStorage。ブラウザの中にある、小さなデータ保存場所。サーバは要らない。

しかし、localStorage に保存されたデータは、条件によっては消えてしまう。

Safari と、iPhone や iPad のブラウザ全部が該当する。WebKit の説明にこうある。

ITP deletes all cookies created in JavaScript and all other script-writeable storage after 7 days of no user interaction with the website.

(WebKit: Tracking Prevention)

トラッカー対策として書かれているが、対象は全てのサイト。削除までの日数(7日)の数え方に癖がある。

7日の数え方暦の7日ではなく、そのブラウザを使った7日
数え直される条件そのサイトを操作すること(クリック・タップ・キー入力)

毎日押しているうちは消えない。だから気づけない。途切れて久しぶりに開いたときに、初めて分かる。

スタンプカードでは、これがいちばん重要なポイント。続けている人には何も起きず、途切れた人だけが記録(全スタンプ)を失う。

iPhone には逃げ道がある。ホーム画面に追加した Webアプリは、この7日のルールの対象外になる(WebKit がそう書いている。もし消えるなら重大な不具合として報告してほしい、とまである)。追加のしかたはCloudflareにHTMLをアップロードして公開するにある。

ただし、そのまま移ってはいけない。 追加した時点で別のアプリとして扱われ、Safari で貯めた記録は引き継がれない。実際に試すと、ホーム画面から開いた時点で記録が消えている。移るには、一度書き出して読み込み直すことになる(→ 3-4)。

⚠️ 7日のルールから外れるだけで、「消えない」とまでは言えない。端末の空き容量が足りないときなどに消える可能性はある。

3-4. ダウンロードできるようにした

Section titled “3-4. ダウンロードできるようにした”

データが消えることがあるので事前に対処する。手元にファイルとしてダウンロードしておく。

Claude
記録を JSON ファイルとしてダウンロードできて、
そのファイルを読み込んで戻せるようにして

2章で作ったダウンロードと同じ仕組みが使える。書き出すものが画像からテキストに変わるだけ。

ひとつ確かめておく。読み込んだときに、いまの記録がどうなるか。丸ごと置き換わるのか、混ざるのか。バックアップから戻す用途なら置き換えでよい。

手元では、選んだファイルの中身で丸ごと置き換わり、終わってから「2件を読み込みました」と出た。間違ったファイルを選んでも、消えてから気づくことになる。取り消せない。

書き出しと読み込みがあれば、記録は守れる。ただし localStorage を使う限り、限界が残る。

  • 手でファイルを持ち回ることになる。 書き出して、移して、読み込む
  • ブラウザをまたぐと別物になる。 同じ Mac でも、Safari で押したスタンプは Chrome には無い。「この端末に保存されている」のではなく「このブラウザに保存されている」
  • 入る量に上限がある。 テキストなら十分だが、画像は入らない。入らないものは保存しない側に戻る(2章のコラージュがそうだった)

ここで分かれる。基準は頻度。

向くもの
書き出して持ち回る年に何度かのバックアップ、端末の買い替え
データベースに置く端末をまたいで毎日使う

たまにでよいなら、バックアップをダウンロードしておくだけでよい。そうでない場合はデータベースの利用検討を始める。

ただし、データベースを扱う場合にはそのための追加の仕組みがいろいろと必要となる。データベース自体もそうだが、データの性質によって認証(ログインの仕組み)も考える必要がある。

4. 自分の作りたいものは、どれに近いか

Section titled “4. 自分の作りたいものは、どれに近いか”

3つを並べると、データベースが欲しくなるかどうかが分かれていた。

欲しくなるかなぜ
アンビエントならない残すものが無い
コラージュほぼならない出したら終わり。貯まらない
スタンプカードなるアプリだけが持ち主で、しかも増える

理由はデータの性質にある。

アプリが唯一の持ち主で、かつ増えていくものだけが、データ共有型への引力を持つ。

アンビエントは残すものが無い。コラージュは成果物を出して終わり。スタンプカードだけが、消えたら取り返せないものを抱えている。

だから、自分が作りたいものを当てはめてみるとよい。

  • アンビエントやコラージュに似ている → ブラウザの中だけでよい。データベースは要らない
  • スタンプカードに似ている → いつか要るようになる。ただし、その日までは要らない

全体像がはっきりしていない段階で、先回りしてデータベースを用意しないほうがよい。 一人完結型のうちは、設計なしでも進めやすく、思いついたときに作り変えられる。2章でやったように機能を足していけるのは、その身軽さのおかげでもある。

どんな種類のアプリがあるかを俯瞰したくなったら、Webアプリの種類と選び方にまとめてある。

この記事は「何を作るか・どこで迷ったか」を書く場所なので、準備と公開の手順は載せない。 どちらも既存の記事にある。

やることどこを見るか
Cloudflare のアカウントを作り、ブラウザにドラッグ&ドロップして公開するCloudflareにHTMLをアップロードして公開する
Node.js と Wrangler を用意し、Claude Code から公開するWranglerでCloudflareに公開する
新しくプロジェクトを作って公開するWranglerでCloudflareに公開する

3つとも public/index.html の1ファイルなので、公開のしかたはどれも同じ。 作業フォルダを作って Claude Code を起動し、各章の依頼文でアプリを作って、そのまま公開を頼めばよい。直したあとは「デプロイして」だけで通る。

  • 3つとも public/index.html の1ファイルで完結する。フレームワークもライブラリも使わない
  • 何が出てくるかはそのとき次第。 短く頼んで、見て(聴いて)、寄せたければ具体を足して頼み直す(→ 1-2・1-3)
  • 音は聴いて確かめる。エージェントには判定できない(→ 1-3)
  • localStorage が消える条件は Safari と iOS のブラウザに固有のもの。Chrome では起きない(→ 3-3)