2026年3月20日金曜日

svgを動的に生成してみた話

ドット絵SVGを動的生成

今回はsvgを小さくデータで持ちたいなっていう超局地的な内容です。

HTMLに直接埋め込む

svgファイルはテキストデータで作る画像なので、以下のように直接htmlに埋め込む事も可能です。

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" shape-rendering="crispEdges">
  <!-- 画像の座標情報 -->
</svg>

また、cssに直接書いて参照させる事も可能です。

javascript化

今回のコードは以下の通り。

<script>
    (function renderGolemIcon() {
        const container = document.getElementById('golem-icon-container');
        if (!container) return;

        // カラーパレット
        const colors = {
            "#ffa500": 0,
            "#8b4513": 1,
            "#808080": 2,
            "#000000": 3,
            "#ffff00": 4
        };
        const colorList = ["#ffa500", "#8b4513", "#808080", "#000000", "#ffff00"];

        // SVGから1pxずつ抽出した完全なデータ
        const dots = [
            [7,0,0],[8,0,0],[9,0,0],[10,0,1],[11,0,0],
            [7,1,0],[8,1,0],[9,1,0],[10,1,0],[11,1,1],[12,1,0],
            [6,2,0],[7,2,0],[8,2,0],[9,2,0],[10,2,1],[11,2,0],[12,2,1],
            [3,3,2],[6,3,0],[7,3,0],[8,3,0],[9,3,1],[10,3,0],[11,3,1],[12,3,0],
            [3,4,2],[4,4,2],[6,4,0],[7,4,0],[8,4,1],[9,4,0],[10,4,1],[11,4,1],[12,4,1],[13,4,0],[15,4,2],
            [4,5,2],[5,5,0],[6,5,0],[7,5,1],[8,5,0],[9,5,1],[10,5,0],[11,5,1],[12,5,1],[13,5,1],[15,5,2],
            [3,6,2],[4,6,0],[5,6,0],[6,6,0],[7,6,0],[8,6,0],[9,6,0],[10,6,0],[11,6,1],[12,6,1],[13,6,2],[14,6,2],[15,6,2],
            [2,7,2],[3,7,2],[4,7,0],[5,7,1],[6,7,3],[7,7,3],[8,7,3],[9,7,3],[10,7,0],[11,7,0],[12,7,2],[13,7,2],[14,7,2],[15,7,2],
            [2,8,2],[3,8,2],[4,8,2],[5,8,3],[6,8,4],[7,8,3],[8,8,3],[9,8,4],[10,8,3],[11,8,3],[12,8,2],[13,8,2],[14,8,2],
            [3,9,2],[4,9,0],[5,9,1],[6,9,4],[7,9,3],[8,9,3],[9,9,4],[10,9,3],[11,9,3],[12,9,0],[13,9,1],[14,9,0],
            [4,10,0],[5,10,0],[6,10,3],[7,10,3],[8,10,3],[9,10,3],[10,10,3],[11,10,0],[12,10,1],[13,10,0],[14,10,1],[15,10,0],
            [1,11,0],[2,11,0],[3,11,0],[4,11,1],[5,11,0],[6,11,0],[7,11,0],[8,11,0],[9,11,0],[10,11,0],[11,11,1],[12,11,1],[13,11,0],[14,11,1],[15,11,0],
            [0,12,0],[1,12,0],[2,12,0],[3,12,1],[4,12,1],[5,12,1],[6,12,0],[7,12,1],[8,12,1],[9,12,1],[10,12,1],[11,12,1],[12,12,1],[13,12,1],[14,12,0],[15,12,1],
            [0,13,0],[1,13,0],[2,13,1],[3,13,1],[4,13,1],[5,13,0],[6,13,0],[7,13,0],[8,13,1],[9,13,1],[10,13,1],[11,13,1],[12,13,0],[13,13,1],[14,13,0],[15,13,0],
            [0,14,0],[1,14,1],[2,14,1],[3,14,0],[4,14,0],[5,14,0],[6,14,0],[7,14,0],[8,14,0],[9,14,0],[10,14,0],[11,14,0],[12,14,0],[13,14,1],[14,14,1],[15,14,0],
            [0,15,0],[1,15,1],[2,15,1],[3,15,0],[4,15,0],[5,15,0],[6,15,0],[7,15,0],[8,15,0],[9,15,0],[10,15,0],[11,15,0],[12,15,0],[13,15,0],[14,15,1],[15,15,1]
        ];

        let rectsHtml = '';
        dots.forEach(([x, y, colorIdx]) => {
            rectsHtml += `<rect x="${x}" y="${y}" width="1" height="1" fill="${colorList[colorIdx]}" />`;
        });

        container.innerHTML = `
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" shape-rendering="crispEdges">
                ${rectsHtml}
            </svg>
        `;
    })();
</script>

実際に描画されたSVG

ちなみに、この通り実際に復元可能です。
仕組み的には前に作ったsvgドット絵エディタで作ったsvgのデータをforを回して復元するだけです。なのでwidthもheightも1固定。
カラーパレットは配列化してindex値で取り出し。
16x16ですが、空白の位置もあるので配列横軸は16個ない場所もあります。
逆に縦軸は全部色があるので16個あります。

関数化してみる

もう一度使うかと言われると使わない気はするけど関数化してみるテスト。
idは唯一キーなのでidだと1か所にしか復元できないものの一旦はこれで良いかな。
そして、ここまで作ったものの結局使わなかったっていう・・・。

<script type="module">
    /**
     * ピクセルアイコンを描画する汎用関数
     * @param {string} id - 描画先の要素ID
     * @param {Array} pixels - [x, y, colorIdx] の配列
     * @param {Array} colors - カラーコードの配列
     */
    function renderPixelIcon(id, pixels, colors) {
        const container = document.getElementById(id);
        if (!container) return;

        let rectsHtml = '';
        pixels.forEach(([x, y, colorIdx]) => {
            const fill = colors[colorIdx] || '#000';
            rectsHtml += `<rect x="${x}" y="${y}" width="1" height="1" fill="${fill}" />`;
        });

        container.innerHTML = `
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" shape-rendering="crispEdges">
                ${rectsHtml}
            </svg>
        `;
    }

    // 初回実行処理
    const init = () => {
        // ゴーレムのカラーパレット
        const golemColors = ["#ffa500", "#8b4513", "#808080", "#000000", "#ffff00"];

        // ゴーレムのドットデータ (All dots included)
        const golemPixels = [
            [7,0,0],[8,0,0],[9,0,0],[10,0,1],[11,0,0],
            [7,1,0],[8,1,0],[9,1,0],[10,1,0],[11,1,1],[12,1,0],
            [6,2,0],[7,2,0],[8,2,0],[9,2,0],[10,2,1],[11,2,0],[12,2,1],
            [3,3,2],[6,3,0],[7,3,0],[8,3,0],[9,3,1],[10,3,0],[11,3,1],[12,3,0],
            [3,4,2],[4,4,2],[6,4,0],[7,4,0],[8,4,1],[9,4,0],[10,4,1],[11,4,1],[12,4,1],[13,4,0],[15,4,2],
            [4,5,2],[5,5,0],[6,5,0],[7,5,1],[8,5,0],[9,5,1],[10,5,0],[11,5,1],[12,5,1],[13,5,1],[15,5,2],
            [3,6,2],[4,6,0],[5,6,0],[6,6,0],[7,6,0],[8,6,0],[9,6,0],[10,6,0],[11,6,1],[12,6,1],[13,6,2],[14,6,2],[15,6,2],
            [2,7,2],[3,7,2],[4,7,0],[5,7,1],[6,7,3],[7,7,3],[8,7,3],[9,7,3],[10,7,0],[11,7,0],[12,7,2],[13,7,2],[14,7,2],[15,7,2],
            [2,8,2],[3,8,2],[4,8,2],[5,8,3],[6,8,4],[7,8,3],[8,8,3],[9,8,4],[10,8,3],[11,8,3],[12,8,2],[13,8,2],[14,8,2],
            [3,9,2],[4,9,0],[5,9,1],[6,9,4],[7,9,3],[8,9,3],[9,9,4],[10,9,3],[11,9,3],[12,9,0],[13,9,1],[14,9,0],
            [4,10,0],[5,10,0],[6,10,3],[7,10,3],[8,10,3],[9,10,3],[10,10,3],[11,10,0],[12,10,1],[13,10,0],[14,10,1],[15,10,0],
            [1,11,0],[2,11,0],[3,11,0],[4,11,1],[5,11,0],[6,11,0],[7,11,0],[8,11,0],[9,11,0],[10,11,0],[11,11,1],[12,11,1],[13,11,0],[14,11,1],[15,11,0],
            [0,12,0],[1,12,0],[2,12,0],[3,12,1],[4,12,1],[5,12,1],[6,12,0],[7,12,1],[8,12,1],[9,12,1],[10,12,1],[11,12,1],[12,12,1],[13,12,1],[14,12,0],[15,12,1],
            [0,13,0],[1,13,0],[2,13,1],[3,13,1],[4,13,1],[5,13,0],[6,13,0],[7,13,0],[8,13,1],[9,13,1],[10,13,1],[11,13,1],[12,13,0],[13,13,1],[14,13,0],[15,13,0],
            [0,14,0],[1,14,1],[2,14,1],[3,14,0],[4,14,0],[5,14,0],[6,14,0],[7,14,0],[8,14,0],[9,14,0],[10,14,0],[11,14,0],[12,14,0],[13,14,1],[14,14,1],[15,14,0],
            [0,15,0],[1,15,1],[2,15,1],[3,15,0],[4,15,0],[5,15,0],[6,15,0],[7,15,0],[8,15,0],[9,15,0],[10,15,0],[11,15,0],[12,15,0],[13,15,0],[14,15,1],[15,15,1]
        ];

        // アイコンを描画
        renderPixelIcon('golem-icon-container', golemPixels, golemColors);
    };

    // 実行
    init();
</script>

最後に

技術的に面白いかとやってみましたが、根本的にはそもそも画像と同様に使えるのでsvgファイルとしてimgで参照させましょう。
そうすればcssも汚されないし、htmlも汚れません。

0 件のコメント:

コメントを投稿