猫の手ツール

Algorithm & UI/UX

Canvasでのタイポグラフィ制御と
座標変換の裏側

「雑誌の表紙風メーカー」は、一見シンプルな画像合成ツールですが、ブラウザ上での「直感的な操作感」と「書き出し品質」を両立させるために、Canvas座標の動的変換やオフスクリーン処理、そしてモダンなフォーマットへの対応を組み合わせています。

1. Canvas上の要素を「掴む」ための座標変換アルゴリズム

ユーザーがタイトルや見出しをドラッグして移動させる際、最も大きな課題となるのが「画面上のクリック座標」と「Canvas内の描画座標」のズレです。特にレスポンシブデザインでは Canvas が CSS で伸縮されるため、単純な `event.offsetX` では正確な位置が取得できません。

そこで、Canvas の実サイズ(`width`)とブラウザ上の表示サイズ(`getBoundingClientRect`)の比率を算出し、補正をかけることで、高解像度なキャンバス内でもミリ単位の操作を可能にしています。

// 画面上のマウス/タッチ座標をCanvas内の座標に変換する
function getPos(evt) {
    const rect = canvas.getBoundingClientRect();
    // マウスまたはタッチイベントからクライアント座標を取得
    const { clientX, clientY } = // ...イベント種類の判別処理;

    // 表示サイズに対する実描画解像度の比率(スケール)を算出
    const scaleX = canvas.width / rect.width;
    const scaleY = canvas.height / rect.height;

    return {
        x: (clientX - rect.left) * scaleX,
        y: (clientY - rect.top) * scaleY
    };
}

2. オフスクリーン処理による高解像度画像の前処理

ユーザーがアップロードする画像は数メガバイトに及ぶ高解像度なものも少なくありません。これを直接メインの Canvas に描画して加工を行うと、メモリ消費量が増大し、特にモバイル端末で動作が不安定になる原因となります。

本ツールでは、一度「オフスクリーン Canvas(メモリ上の見えないキャンバス)」でリサイズとアスペクト比の調整を行い、最適化された画像データのみをメイン Canvas のレイヤーとして利用することで、軽量かつ高速なプレビューと、劣化の少ない最終出力を実現しています。

// アップロードされた画像を最適なサイズにリサイズ
async function processImage(img) {
    const MAX_VAL = // 独自のチューニング値;
    let [w, h] = [img.width, img.height];

    // 長辺に基づいてスケーリング倍率を計算
    if (w > MAX_VAL || h > MAX_VAL) {
        const scale = MAX_VAL / Math.max(w, h);
        w = Math.floor(w * scale);
        h = Math.floor(h * scale);
    }

    const offscreen = document.createElement('canvas');
    // ...リサイズ処理の実行
    return offscreen.toDataURL('image/jpeg', // クオリティ設定);
}

3. HEIC対応とダイナミックインポートによる最適化

iPhoneで撮影された写真は標準で HEIC 形式(高効率画像ファイル)となっていますが、標準的なブラウザの Canvas API はこの形式を直接扱うことができません。

そこで、変換ライブラリを「HEICファイルが選択された時だけ」動的に読み込む(Dynamic Import)手法を採用しました。これにより、初期ロード時の通信量を抑えつつ、iPhoneユーザーが変換の手間なく直接写真をアップロードできる高い利便性を提供しています。

// HEIC形式の判定と動的ライブラリロード
if (file.type === 'image/heic' || file.name.match(/\.(heic|heif)$/i)) {
    // 必要な時だけインポートすることで初期ロードを軽量化
    const heic2any = (await import('heic2any')).default;
    const blob = await heic2any({
        blob: file,
        toType: "image/jpeg"
    });
    // ...以降の描画処理へ
}

Developer's Note

このツールで最もこだわったのは、スマホでの「文字移動の心地よさ」です。Canvasは本来ただのビットマップですが、判定ロジックを自前で実装することで、あたかもアプリ上のUI要素を触っているかのような操作感を目指しました。

また、外部サーバーを一切使わない「ブラウザ完結型」にしたのは、ユーザーのプライバシーを守るためだけではなく、ネットワーク遅延(レイテンシ)をゼロにすることで、リアルタイムなプレビューフィードバックを追求した結果でもあります。