Canvas Interaction & State Management
ビューポートと解像度の差異を埋める
高精度スタンプ実装の裏側
SNSで会話をシェアする際、相手のアイコンを「指先一つで、綺麗に」隠す。一見シンプルですが、高解像度なスクリーンショットをWebブラウザで軽快に扱うためには、数学的な座標変換とメモリ管理の工夫が不可欠です。
1. CSSスケーリングを考慮した「双方向座標マッピング」
このツールでは、Canvasの物理サイズ(画像本来の解像度)と、画面上の表示サイズ(CSSによる縮小表示)が一致しません。そのままタップ座標を取得すると、隠したい場所とスタンプの位置が大きくズレてしまいます。
これを解決するため、getBoundingClientRect() で取得した要素の矩形サイズと、Canvas自体の width / height から動的にスケール比率を算出し、タップ位置を再計算するマッピングロジックを実装しています。
// 表示サイズと物理解像度のズレを解消する座標変換
function getMappedPos(evt, container, canvas) {
const rect = container.getBoundingClientRect();
// スケール比率の算出
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
// ビューポート座標をCanvas内部座標へ変換
return {
x: (clientX - rect.left) * scaleX,
y: (clientY - rect.top) * scaleY
};
} この実装により、縦に非常に長いスクリーンショットであっても、指でタップした位置に寸分の狂いなく「隠しマーク」を配置することが可能になっています。
2. ImageDataスタックによる軽量Undoシステム
加工ミスを防ぐための「1つ戻る(Undo)」機能は、通常であれば描画命令の履歴(パス)を保持し、再描画を行うことで実現します。しかし、スタンプや色塗りを行うビットマップ処理では、命令が増えるほど再描画コストが増大します。
本ツールでは、ImageData(ピクセルデータ)のフルスナップショットを配列に退避させる方式を採用しました。これにより、どんなに複雑な加工を行った後でも、定数時間で前の状態へ復元できます。
// メモリ消費と利便性のバランスをとった履歴管理
function saveState() {
// 独自のメモリ制限閾値を設定し、古い履歴から破棄
if (history.length >= // 独自の履歴上限数) {
history.shift();
}
// 現在のCanvasの全ピクセル情報をスナップショットとして保存
history.push(ctx.getImageData(0, 0, canvas.width, canvas.height));
}
// 復元処理
function undo() {
const previous = history.pop();
if (previous) {
ctx.putImageData(previous, 0, 0);
}
} モバイル端末のメモリ制約を考慮し、履歴の上限数を調整することで、クラッシュを防ぎつつ快適な編集体験を提供しています。
Developer's Note
「モザイクやぼかしはAIで復元されるリスクがある」という事実に基づき、本ツールではあえて不透明な円形や絵文字スタンプで「完全に上書きする」仕様にこだわりました。
また、タップした際の感触を良くするために、CSSアニメーションを用いた「タップエフェクト」をCanvasのレイヤーとは別に実装しています。ユーザーが「今、どこを隠したのか」を視覚的にフィードバックすることで、操作の確信性を高めています。