この記事について
自分で作った画像圧縮アプリに、Claude Code を使って機能を追加していった記録です。
全3回のうちの1回目にあたります。
- 対象読者: アプリ開発を始めたばかりの人(筆者と同じくらい)
- この回で扱うこと: 要望の伝え方、AIが返してきた判断、そして見つかったバグ
- 成果物: https://xs218295.xsrv.jp/image-compressor/
専門用語は都度説明します。分からない単語が出てきても、そこで止まらずに読み進められる
ように書いたつもりです。
出発点:すでに動くアプリがあった
作業を始めた時点で、アプリはすでに動いていました。JPEG・PNG・WebP を圧縮して、
容量を減らすだけのシンプルなツールです。
ひとつだけ特徴があって、画像がどこにも送信されません。 圧縮はブラウザの中だけで
完結します。だからサーバーに写真が保存される心配がありません。
| 項目 | 内容 |
|---|---|
| フレームワーク | Next.js 14 |
| 圧縮処理 | ブラウザの Canvas API |
| ファイル数 | 実質4つ |
この状態を v0.1.0 と呼ぶことにします。
まず「現状を把握して」と頼んだ
いきなり機能追加を頼むのではなく、最初にこうお願いしました。
D:\PF_Claude\image-compressorは画像圧縮アプリです。README.md を読んで現状を把握してください。
これは結果的に良かったと思います。Claude は README だけでなくソースコードも読んで、
何ができるアプリなのかを整理して返してきました。
そして、頼んでいないのに懸念点を3つ指摘してきました。
- 圧縮処理が全部メインスレッドで動いているので、大きい画像を多数入れるとUIが固まる
- ダウンロード用のURLを解放するタイミングが早すぎて、保存が始まる前に無効化される可能性がある
- 圧縮結果を全部メモリに持ち続けているので、大量ファイルだと積み上がる
正直、このとき私は3つとも意味が分かっていませんでした。でもあとで全部効いてきます。
1番は第2回で丸ごと扱うテーマになり、2番はこの回で直すことになりました。
初心者的な学びとして、「まず読ませる」だけで問題点のリストが手に入るのは大きいです。
自分では気づけないところを指摘してもらえます。
要望を6つまとめて投げた
次に、やりたいことをまとめて伝えました。箇条書きで、思いついた順です。
画質のプリセットボタン(軽さ優先/バランス/画質優先)や、圧縮前後の比較プレビューなども追加したいです。
あと、圧縮画像をダウンロードするにはzipファイルにしていましたが、これは必須ですか?そのままダウンロードできればいいのですが。
その他、
・圧縮加工したファイル名に「-compressed」と追記
・選択した画像のファイル容量サイズは表示されているが、サイズ(縦横)も表示してほしい
・画像回転機能
などは追加できますか?
整理された仕様書ではありません。質問と要望が混ざっています。 でも問題なく通じました。
ここで気づいたのは、きれいにまとめようとしなくていいということです。
「これは必須ですか?」のような疑問も一緒に書いておけば、答えてくれます。
「ZIPは必須ですか?」への回答が予想と違った
私は「ZIPをやめて直接ダウンロードにしてほしい」という意味で聞きました。
返ってきた答えは、どちらかを選ぶのではなく「両方置く」でした。
理由はこうです。ZIPを使わず1枚ずつダウンロードすることは技術的に可能ですが、
ブラウザは1つのサイトが連続でファイルを落とそうとすると
「複数ファイルのダウンロードを許可しますか?」と確認を出します。
1回許可すれば以降は出ませんが、10枚20枚になるとこの挙動が煩わしい。
だから「個別にすべて保存」を追加しつつ、ZIPも残して
「枚数が多いときはZIPが確実です」と案内する形になりました。
私が知らなかった制約を踏まえて、私の希望を否定せずに設計してきたわけです。
こういうのは自分ひとりだと気づけません。
勝手に直されたところ(理由付き)
ファイル名の追記について、私は「-compressed」と書きました。全角のハイフンです。
実装されたのは 半角の -compressed でした。
理由も添えられていました。全角文字はサーバーやツールでの扱いで問題が起きやすいので、
ASCII のハイフンのほうが安全だと。そして「不要なら1か所直すだけで変えられます」と、
戻し方まで示されていました。
さらに、同じファイルを二度圧縮しても -compressed-compressed にならない
処理まで入っていました。私は指示していません。
ここも学びでした。指示を字面どおりに実行するのではなく、意図を汲んで調整されることがある。
そして納得できない場合に戻せるよう、判断の理由と変更箇所が示される。
実装されたもの
結果として v0.2.0 で入った機能です。
- 画質プリセット — 軽さ優先(60) / バランス(80) / 画質優先(92) のボタン。
細かく調整したいときは従来のスライダーも使える - 比較プレビュー — 境界線を左右に動かして、元画像と圧縮後を重ねて見比べる
- 回転 — 画像ごとに90°ずつ回せる
- 寸法表示 — 画像を選んだ直後に「3000×2000」と出る(圧縮完了を待たない)
- ファイル名 —
写真.jpg→写真-compressed.jpg - 個別にすべて保存 — ZIPを使わないダウンロード
あわせて、最初に指摘された懸念2(ダウンロードURLの解放タイミング)も直りました。
それと、私が頼んでいない改善がもうひとつ入っていました。
従来は設定を1つ変えるたびに全画像を圧縮し直していたのですが、
変わったものだけを処理するようになりました。
回転ボタンを1枚に押しても、再圧縮されるのはその1枚だけです。
ここからが本題:1pxのずれ
実装が終わったあと、Claude は自分でブラウザを起動して動作確認を始めました。
ここで問題が見つかります。
何が起きたか
比較プレビューは、元画像と圧縮後の画像をぴったり重ねて、
境界線から左右に見せ分ける仕組みです。ずれていたら比較になりません。
回転した画像で確認したところ、2枚の表示領域が
277×277 と 277×416 になっていました。縦のサイズが全然違います。
原因は「親切な初期スタイル」だった
犯人は Tailwind CSS(見た目を整えるための道具)が最初から入れている、
たった1行の指定でした。
img { max-width: 100% }
これは「画像が枠より大きくなって、はみ出さないようにする」ための、
普通ならありがたい設定です。スマホで写真がはみ出さないのは、こういう指定のおかげです。
ところが回転と組み合わせると邪魔になります。
90°回転させる場合、回転する前の画像は横に長く広げておく必要があります。
回して初めて縦長の枠にぴったり収まるからです。
つまり一時的に枠の幅を超えていないといけないのに、max-width: 100% がそれを「はみ出し」と判断して切り詰めてしまっていた。
解決策は、この画像だけ制限を外すことでした。
max-width: none;
初心者として何を学んだか
「便利な初期設定」が、想定外の使い方をすると足を引っ張る。
Tailwind は何も悪いことをしていません。99%の場面で正しい設定です。
ただ「回転させた画像を枠に合わせる」という使い方が想定外だっただけ。
こういうバグは、エラーメッセージが出ません。ただ静かに1px〜100pxずれるだけです。
だから「見た目が合っているか実際に測る」という作業が必要になります。
修正後、2枚の表示領域は完全に一致しました。
回転の向きをどう確かめたか
もうひとつ面白かったのが、回転方向の検証方法です。
このアプリでは、CSSで画像を回す処理と、Canvas(画像加工の仕組み)で回す処理の
2種類の回転が使われています。この2つが同じ向きに回っていないと、
プレビューと実際の保存結果が食い違ってしまいます。
Claude がやった確認方法はこうでした。
- テスト画像の上辺の中央に赤い三角を描く
- 右に90°回転させて圧縮する
- 出力画像の右辺の中央の色を調べる
- そこが赤なら、時計回りに回っている
実際に rgb(253,0,3) という値が返ってきて、一致が確認できました。
「たぶん合ってる」で済ませず、判定できる目印を仕込んで数値で確認する。
自分でテストを書くときの参考になりました。
この回のまとめ
- まず読ませる。 現状把握を頼むだけで、自分では気づけない問題点のリストが出てくる
- 要望はきれいにまとめなくていい。 質問混じりの箇条書きでも通じる
- 「必須ですか?」と聞くと、知らなかった制約が出てくる。 今回はブラウザの
ダウンロード確認ダイアログ。それを踏まえた第三の案が返ってきた - 勝手に調整されることがあるが、理由と戻し方が示される。 全角→半角のハイフンなど
- エラーが出ないバグがある。 1pxのずれは実際に測らないと分からない
- 検証は「判定できる形」にする。 赤い三角を目印にする、寸法を数値で比べる
次回は、最初に指摘された懸念1「圧縮中にUIが固まる」を解決する話です。
Web Worker という仕組みを使うのですが、ここで私は2回、数字にだまされます。
→ ②高速化編へ

コメント