中級 graphicsdom

Canvas と 2Dグラフィックス

はじめに

HTML Canvasは、Rust/Wasmの最適なユースケースの一つです。Rustが計算処理(物理演算、ゲームロジック、プロシージャル生成)を担当し、Canvas APIがレンダリングを処理します。この分担により、重い処理部分でネイティブに近いパフォーマンスが得られます。

セットアップ

[dependencies.web-sys]
version = "0.3"
features = [
    "Window",
    "Document",
    "HtmlCanvasElement",
    "CanvasRenderingContext2d",
]

Canvasコンテキストの取得

use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};

fn get_context(canvas_id: &str) -> Result<CanvasRenderingContext2d, JsValue> {
    let document = web_sys::window().unwrap().document().unwrap();
    let canvas = document
        .get_element_by_id(canvas_id)
        .unwrap()
        .dyn_into::<HtmlCanvasElement>()?;

    canvas
        .get_context("2d")?
        .unwrap()
        .dyn_into::<CanvasRenderingContext2d>()
}

基本図形の描画

// 矩形
ctx.set_fill_style_str("#654ff0");
ctx.fill_rect(10.0, 10.0, 100.0, 50.0);

// 円
ctx.begin_path();
ctx.arc(200.0, 200.0, 40.0, 0.0, std::f64::consts::PI * 2.0)?;
ctx.set_fill_style_str("#ce422b");
ctx.fill();

// 線
ctx.begin_path();
ctx.move_to(0.0, 0.0);
ctx.line_to(300.0, 150.0);
ctx.set_stroke_style_str("#34d399");
ctx.set_line_width(2.0);
ctx.stroke();

// テキスト
ctx.set_font("24px Inter");
ctx.set_fill_style_str("#f1f5f9");
ctx.fill_text("Hello Wasm!", 50.0, 50.0)?;

アニメーションループ

アニメーションはJavaScript側でrequestAnimationFrameを使って実行し、各フレームでRustを呼び出します:

import init, { Canvas } from './pkg/my_app.js';

async function run() {
    await init();
    const canvas = new Canvas("game-canvas");

    let x = 0;
    function frame() {
        canvas.clear();
        canvas.draw_circle(x, 300, 20, "#654ff0");
        x = (x + 2) % 800;
        requestAnimationFrame(frame);
    }
    requestAnimationFrame(frame);
}
run();

マウス入力

use web_sys::MouseEvent;
use wasm_bindgen::closure::Closure;

pub fn setup_mouse(canvas: &HtmlCanvasElement) -> Result<(), JsValue> {
    let closure = Closure::wrap(Box::new(move |event: MouseEvent| {
        let x = event.offset_x() as f64;
        let y = event.offset_y() as f64;
        // (x, y) でのクリックを処理
    }) as Box<dyn FnMut(_)>);

    canvas.add_event_listener_with_callback(
        "click",
        closure.as_ref().unchecked_ref(),
    )?;
    closure.forget();
    Ok(())
}

画像の描画

use web_sys::HtmlImageElement;

pub fn draw_image(ctx: &CanvasRenderingContext2d, src: &str, x: f64, y: f64)
    -> Result<(), JsValue>
{
    let img = HtmlImageElement::new()?;
    img.set_src(src);

    let ctx = ctx.clone();
    let closure = Closure::once(move || {
        ctx.draw_image_with_html_image_element(&img, x, y).unwrap();
    });
    img.set_onload(Some(closure.as_ref().unchecked_ref()));
    closure.forget();
    Ok(())
}

パフォーマンス比較:Canvas vs WebGL

特徴 Canvas 2D WebGL
セットアップの複雑さ 低い 高い
描画呼び出し 約1,000回/フレーム 約10,000回/フレーム
シェーダー なし あり
3D なし あり
最適な用途 2Dゲーム、チャート、UI 3D、大量パーティクルシステム

ほとんどの2D Rust/Wasmプロジェクトでは、Canvas 2Dで十分であり、はるかにシンプルです。

試してみよう

スターターコードは、基本的な描画メソッドを持つCanvasラッパー構造体を示しています。実際のプロジェクトでは、JavaScriptのアニメーションループからこれらのメソッドを呼び出します。

試してみる

チャプタークイズ

すべての問題に正解してレッスンを完了しましょう