概要
ブログと Wiki の記事編集画面に、画像アップロード機能を追加した。 従来は画像を別ツールでアップロードして URL を手動で貼り付ける必要があったが、エディタ上で完結するようになった。
実装した機能
3 種類のアップロード方法
ドラッグ&ドロップ エディタ上部のドロップゾーンに画像ファイルを直接ドラッグして離すとアップロードが始まる。複数ファイルを同時にドロップしても全件処理される。
ファイル選択ボタン
ドロップゾーンをクリックするとファイル選択ダイアログが開く。Ctrl(または Cmd)を押しながら複数選択することで、まとめてアップロードできる。
クリップボードペースト(Ctrl+V) スクリーンショットツールやブラウザの「コピー」で取得した画像を、ページ上で Ctrl+V を押すだけでアップロードできる。テキストのペースト操作(文字列が含まれる場合)とは干渉しないように制御した。
自動挿入
アップロードが完了すると、Markdown の画像記法  をテキストエリアのカーソル位置に自動挿入し、プレビューも即時更新される。
サムネイルにある「挿入」ボタンで後から再挿入したり、「コピー」ボタンで URL をクリップボードにコピーすることもできる。
対応形式と上限
| 形式 | アップロード上限 | 備考 |
|---|---|---|
| JPEG / PNG / WebP | 10MB(圧縮後) | Canvas で自動圧縮 |
| GIF | 50MB | アニメーション保持のため圧縮なし |
GIF をアニメーション付きのまま扱うには、Canvas への描画を避ける必要がある。Canvas に描くとアニメーションの最初のフレームしか残らないため、GIF だけ圧縮をスキップして元のファイルをそのまま送信する設計にした。
画像の自動圧縮
JPEG・PNG・WebP については、送信前にブラウザ上で画像を JPEG に変換・圧縮してからアップロードする。
圧縮の手順は次のとおり。
- Canvas に画像を描画し、JPEG 品質 92% で変換する。PNG の透明部分は白背景で合成する。
- 変換後が 10MB を超えている場合は、品質を 87%・82% と 5% ずつ下げながら繰り返す。
- それでも収まらない場合は解像度を 0.8 倍・0.7 倍と段階的に縮小して再試行する。
品質 92% の JPEG 変換では、写真や UI スクリーンショットの多くは視覚的な劣化なしにファイルサイズを削減できる。
セキュリティ
画像アップロードの API には認証チェックを設けており、ログイン済みのセッションを持つユーザーのみが利用できる。 ファイルの MIME タイプはサーバー側でも検証し、許可していない形式は受け付けない。 GIF と JPEG/PNG/WebP でサイズ上限を個別に設定し、サーバー側でもそれぞれバリデーションしている。 Cloudinary へのアップロードには署名付きリクエストを使用しており、API シークレットはサーバーサイドでのみ扱う。
共通コンポーネント化
ブログと Wiki で同じアップロード UI を共有するため、コンポーネントとして切り出した。 どの記事編集画面に追加する場合も、Markdown エディタのテキストエリア ID を指定するだけで動作する設計にした。
記事の作成・編集がより手軽になった。