猫の手ツール

Algorithm & UI/UX

Canvasで描く「動く吹き出し」:
座標計算とイベント処理の裏側

写真に漫画のような吹き出しを合成するプロセス。Canvas APIを用いたテキスト計測、マルチデバイス対応のドラッグ操作、そして「爆発吹き出し」などを描画する幾何学的なアルゴリズムの実装について解説します。

1. テキスト量に追随する「動的領域」計算

セリフの長さに合わせて吹き出しのサイズを自動調整するため、Canvasの measureText メソッドを活用しています。単に文字数で測るのではなく、改行を含めた最大幅と行高から矩形領域を算出し、楕円形の吹き出しでは文字がはみ出さないよう、幾何学的な安全係数を掛けてサイズを決定しています。

// テキストメトリクスに基づいたサイズ算出
const lines = balloon.text.split('\n');
let maxLineWidth = 0;
lines.forEach(line => {
    const w = ctx.measureText(line).width;
    if (w > maxLineWidth) maxLineWidth = w;
});

// 独自のチューニング値を用いて吹き出しの幅(bw)と高さ(bh)を決定
// 楕円の場合は内接矩形を考慮し、特定の係数でスケーリング
let bw = maxLineWidth * SCALING_FACTOR_W + MARGIN_W;
let bh = (lines.length * lineHeight) * SCALING_FACTOR_H + MARGIN_H;

この計算により、ユーザーが文字サイズやセリフを変更した瞬間に、最適な大きさの吹き出しが再描画されます。

2. PC/スマホ共用:座標系の正規化とドラッグ制御

マウスとタッチ操作の両方で「直感的な配置」を実現するため、入力イベントの座標をCanvas内部の座標系に正規化する処理を行っています。CSS上の表示サイズとCanvasの論理サイズが異なる場合(レスポンシブ対応時)でも、ドラッグした指の真下に吹き出しが吸い付くように計算しています。

// マウス/タッチ座標をCanvas座標に変換するロジック
function getPos(evt) {
    const rect = mainCanvas.getBoundingClientRect();
    let clientX, clientY;
    
    // イベント種別の判定と正規化処理
    // ここに MouseEvent / TouchEvent の座標抽出処理が入ります

    // CSSピクセルとCanvas内部解像度の比率を考慮
    const scaleX = mainCanvas.width / rect.width;
    const scaleY = mainCanvas.height / rect.height;

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

3. 「爆発吹き出し」の幾何学的描画アルゴリズム

漫画特有の「ギザギザした爆発吹き出し」は、極座標系を利用したアルゴリズムで描画しています。円周を一定数に分割し、中心からの距離を「外側(頂点)」と「内側(谷間)」で交互に変化させながら、パス(lineTo)を繋いでいくことで、勢いのある形状を生成しています。

// ギザギザ吹き出しの頂点計算ロジック概要
const spikes = // 独自の頂点数設定;
const outerR = // 算出した外半径;
const innerR = // 算出した内半径;

ctx.beginPath();
for (let i = 0; i < spikes * 2; i++) {
    const angle = (i * Math.PI) / spikes;
    // 偶数・奇数で半径を切り替え
    const r = (i % 2 === 0) ? outerR : innerR;
    
    // ここに三角関数(cos, sin)を用いた座標算出とmoveTo/lineToが入ります
}
ctx.closePath();
ctx.stroke();

Developer's Note

「大喜利ツール」として最も重要視したのは、思考を妨げないレスポンスの速さです。画像処理の重い部分は Web Worker やサーバーサイドに逃がすのが定石ですが、このツールでは「文字の打ち替え」という頻繁なインタラクションが発生するため、あえてメインスレッドでの軽量な描画に特化させました。

また、iPhoneユーザーが生成画像を保存する際の体験を損なわないよう、ダウンロードリンクだけでなく、画像を長押しして保存する習慣(iOS特有の挙動)を考慮したUI設計にしています。ブラウザさえあれば、いつでもどこでも「ネタ画像」が生み出せる。そんなシンプルで力強い道具を目指しました。