目次

Webページでスプライト画像のアニメーションを再生する

目次

ぐあもん
質問: 2Dキャラのモーション再生
こちらの記事でキャラクターのモーション画像を作成できました。キャラをアニメーションさせたいです。 Webページで実装して、Webブラウザを利用して動作確認できる方法が良いです。

手順

今回はOpenAIのCodexを利用して実装します。
あらかじめ、実装したいHTMLファイルは作成しておきます。

ファイルの配置は下図です。スプライト画像は"char1"ディレクトリ内に配置します。 スプライト画像の作成手順はこちらの記事を参照してください。
Webページでスプライト画像のアニメーションを再生する:画像1

Codexを起動して、以下のプロンプトを実行します。

Prompt
(プロジェクト名)プロジェクトの char-anime-01.htmlにHTML Canvasを配置して、リンクをクリックすると、 c-01.pngからc-04.pngの画像がアニメーション切り替えするプログラムを実装してください。


実装結果を確認します。最初のフレームに戻らなかったため、追加で以下の指摘を入れます。

Prompt
良いですね。最後に01に戻るようにできますか?


作成されたHTMLを開いて動作確認します。リンクをクリックするとスプライトの画像を切り替え、 キャラクターがアニメーションするように見えます。

Webページでスプライト画像のアニメーションを再生する:画像2 Webページでスプライト画像のアニメーションを再生する:画像3

実装結果は以下のURLにもあります。



コードは以下です。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Canvas Sprite Animation</title>
    <style>
        body {
            margin: 24px;
            font-family: "Yu Gothic UI", "Meiryo", sans-serif;
            background-color: #f7f7f7;
            color: #222;
        }

        .container {
            max-width: 720px;
        }

        #animationCanvas {
            display: block;
            margin-top: 16px;
            border: 1px solid #b8b8b8;
            background-color: #fff;
        }

        #playLink {
            color: #0a63c9;
            text-decoration: underline;
            cursor: pointer;
        }

        #message {
            margin-top: 12px;
            min-height: 1.5em;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>キャラクターアニメーション</h1>
        <p><a id="playLink" href="#">アニメーションを再生する</a></p>
        <canvas id="animationCanvas" width="320" height="320"></canvas>
        <div id="message">画像を読み込み中です。</div>
    </div>

    <script>
        const framePaths = [
            "char1/c-01.png",
            "char1/c-02.png",
            "char1/c-03.png",
            "char1/c-04.png"
        ];

        const playLink = document.getElementById("playLink");
        const canvas = document.getElementById("animationCanvas");
        const context = canvas.getContext("2d");
        const message = document.getElementById("message");

        let frames = [];
        let animationTimerId = null;
        let currentFrameIndex = 0;

        function drawFrame(frameIndex) {
            const image = frames[frameIndex];
            if (!image) {
                return;
            }

            if (canvas.width !== image.width || canvas.height !== image.height) {
                canvas.width = image.width;
                canvas.height = image.height;
            }

            context.clearRect(0, 0, canvas.width, canvas.height);
            context.drawImage(image, 0, 0);
        }

        function stopAnimation() {
            if (animationTimerId !== null) {
                clearInterval(animationTimerId);
                animationTimerId = null;
            }
        }

        function playAnimation() {
            if (frames.length === 0) {
                return;
            }

            stopAnimation();
            currentFrameIndex = 0;
            drawFrame(currentFrameIndex);
            message.textContent = "アニメーションを再生中です。";

            animationTimerId = window.setInterval(() => {
                currentFrameIndex += 1;

                if (currentFrameIndex >= frames.length) {
                    stopAnimation();
                    currentFrameIndex = 0;
                    drawFrame(currentFrameIndex);
                    message.textContent = "再生が完了しました。先頭フレームに戻りました。";
                    return;
                }

                drawFrame(currentFrameIndex);
            }, 150);
        }

        function loadFrames() {
            const imagePromises = framePaths.map((path) => {
                return new Promise((resolve, reject) => {
                    const image = new Image();
                    image.onload = () => resolve(image);
                    image.onerror = () => reject(new Error(path + " の読み込みに失敗しました。"));
                    image.src = path;
                });
            });

            Promise.all(imagePromises)
                .then((loadedFrames) => {
                    frames = loadedFrames;
                    drawFrame(0);
                    message.textContent = "準備ができました。リンクをクリックすると再生します。";
                })
                .catch((error) => {
                    message.textContent = error.message;
                });
        }

        playLink.addEventListener("click", (event) => {
            event.preventDefault();
            playAnimation();
        });

        loadFrames();
    </script>
</body>
</html>


続いて効果音を追加します。効果音の作成はこちらの記事を参照してください

ファイル配置は下図です。効果音のサウンドファイル(slash.mp3)はchar1ディレクトリ内に配置しています。
Webページでスプライト画像のアニメーションを再生する:画像4

以下のプロンプトで指示をします。

Prompt
char-anime-02.html では効果音も入れたいです。slash.mp3 をアニメ時に再生するようにできますか?

動作を確認します。アニメーションの速度を速めないと効果音と合わないため、以下の指示を出します。

Prompt
char-anime-02のアニメをもっと早く切り替えたいです。できますか?


実装結果は以下のURLです。先ほどと同じ画面が表示されますが、リンクをクリックすると効果音付きでアニメーションします。

コードは以下です。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Canvas Sprite Animation</title>
    <style>
        body {
            margin: 24px;
            font-family: "Yu Gothic UI", "Meiryo", sans-serif;
            background-color: #f7f7f7;
            color: #222;
        }

        .container {
            max-width: 720px;
        }

        #animationCanvas {
            display: block;
            margin-top: 16px;
            border: 1px solid #b8b8b8;
            background-color: #fff;
        }

        #playLink {
            color: #0a63c9;
            text-decoration: underline;
            cursor: pointer;
        }

        #message {
            margin-top: 12px;
            min-height: 1.5em;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>キャラクターアニメーション</h1>
        <p><a id="playLink" href="#">アニメーションを再生する</a></p>
        <canvas id="animationCanvas" width="320" height="320"></canvas>
        <div id="message">画像を読み込み中です。</div>
    </div>

    <script>
        const framePaths = [
            "char1/c-01.png",
            "char1/c-02.png",
            "char1/c-03.png",
            "char1/c-04.png"
        ];

        const playLink = document.getElementById("playLink");
        const canvas = document.getElementById("animationCanvas");
        const context = canvas.getContext("2d");
        const message = document.getElementById("message");
        const slashSound = new Audio("char1/slash.mp3");
        const frameIntervalMs = 80;

        slashSound.preload = "auto";

        let frames = [];
        let animationTimerId = null;
        let currentFrameIndex = 0;

        function drawFrame(frameIndex) {
            const image = frames[frameIndex];
            if (!image) {
                return;
            }

            if (canvas.width !== image.width || canvas.height !== image.height) {
                canvas.width = image.width;
                canvas.height = image.height;
            }

            context.clearRect(0, 0, canvas.width, canvas.height);
            context.drawImage(image, 0, 0);
        }

        function stopAnimation() {
            if (animationTimerId !== null) {
                clearInterval(animationTimerId);
                animationTimerId = null;
            }
        }

        function playAnimation() {
            if (frames.length === 0) {
                return;
            }

            stopAnimation();
            slashSound.pause();
            slashSound.currentTime = 0;
            slashSound.play().catch(() => {
                message.textContent = "アニメーションを再生中です。効果音の再生はブラウザにより制限されました。";
            });
            currentFrameIndex = 0;
            drawFrame(currentFrameIndex);
            message.textContent = "アニメーションを再生中です。";

            animationTimerId = window.setInterval(() => {
                currentFrameIndex += 1;

                if (currentFrameIndex >= frames.length) {
                    stopAnimation();
                    currentFrameIndex = 0;
                    drawFrame(currentFrameIndex);
                    message.textContent = "再生が完了しました。先頭フレームに戻りました。";
                    return;
                }

                drawFrame(currentFrameIndex);
            }, frameIntervalMs);
        }

        function loadFrames() {
            const imagePromises = framePaths.map((path) => {
                return new Promise((resolve, reject) => {
                    const image = new Image();
                    image.onload = () => resolve(image);
                    image.onerror = () => reject(new Error(path + " の読み込みに失敗しました。"));
                    image.src = path;
                });
            });

            Promise.all(imagePromises)
                .then((loadedFrames) => {
                    frames = loadedFrames;
                    drawFrame(0);
                    message.textContent = "準備ができました。リンクをクリックすると再生します。";
                })
                .catch((error) => {
                    message.textContent = error.message;
                });
        }

        playLink.addEventListener("click", (event) => {
            event.preventDefault();
            playAnimation();
        });

        loadFrames();
    </script>
</body>
</html>
AuthorPortraitAlt
著者
iPentecのメインデザイナー
Webページ、Webクリエイティブのデザインを担当。PhotoshopやIllustratorの作業もする。 最近は生成AIの画像生成の沼に沈んでいる。
作成日: 2026-03-15