使用 Rust 和 WebAssembly 创建 Dev 的离线页面🦄💡✨

2025-06-07

使用 Rust 和 WebAssembly 创建 Dev 的离线页面🦄💡✨

Dev 的离线页面很有趣。我们能用 Rust 和 WebAssembly 实现吗?

答案是肯定的。让我们行动起来。

首先,我们将使用 Webpack 创建一个简单的 Rust 和 WebAssembly 应用程序。

npm init rust-webpack dev-offline-canvas
Enter fullscreen mode Exit fullscreen mode

Rust 和 WebAssembly 生态系统提供了对 Web API 的必要绑定。点击此处web_sys查看

示例应用程序已具有web_sys依赖项。该web_syscrate 包含所有可用的 WebAPI 绑定。

包含所有 WebAPI 绑定会增加绑定文件的大小。因此,只包含我们需要的 API 非常重要。

我们将删除现有功能

features = [
    'console'
]
Enter fullscreen mode Exit fullscreen mode

并将其替换为以下内容:

features = [
  'CanvasRenderingContext2d',
  'CssStyleDeclaration',
  'Document',
  'Element',
  'EventTarget',
  'HtmlCanvasElement',
  'HtmlElement',
  'MouseEvent',
  'Node',
  'Window',
]
Enter fullscreen mode Exit fullscreen mode

上述功能列表是我们将在本例中使用的完整功能集。

让我们写一些 Rust

打开src/lib.rs

start()用以下内容替换该函数:



#[wasm_bindgen(start)]
pub fn start() -> Result<(), JsValue> {

   Ok()
}
Enter fullscreen mode Exit fullscreen mode

WebAssembly 模块实例化后,会立即调用此函数。点击此处#[wasm_bindgen(start)]查看规范中关于 start 函数的更多信息

window我们将在 Rust 中获取对象。

    let window = web_sys::window().expect("should have a window in this context");
Enter fullscreen mode Exit fullscreen mode

然后从对象中获取文档window

     let document = window.document().expect("window should have a document");
Enter fullscreen mode Exit fullscreen mode

创建一个 Canvas 元素并将其附加到文档。

    let canvas = document
         .create_element("canvas")?
         .dyn_into::<web_sys::HtmlCanvasElement>()?;

    document.body().unwrap().append_child(&canvas)?;
Enter fullscreen mode Exit fullscreen mode

设置画布元素的宽度、高度和边框。

    canvas.set_width(640);
    canvas.set_height(480);
    canvas.style().set_property("border", "solid")?;
Enter fullscreen mode Exit fullscreen mode

在 Rust 中,一旦执行超出上下文或方法返回任何值,内存就会被丢弃。但在 JavaScript 中,只要页面正常运行, 就会一直存在windowdocument

因此,为内存创建引用并使其静态存在直到程序完全关闭非常重要。

获取 Canvas 的渲染上下文并在其周围创建一个包装器以保留其生命周期。

RC代表Reference Counted

Rc 类型提供对堆中分配的 T 类型值的共享所有权。对 Rc 调用 clone 会在堆中生成一个指向相同值的新指针。当最后一个指向给定值的 Rc 指针被销毁时,其指向的值也会被销毁。—— RC 文档

此引用被克隆并用于回调方法。

let context = canvas
        .get_context("2d")?
        .unwrap()
        .dyn_into::<web_sys::CanvasRenderingContext2d>()?;

let context = Rc::new(context);
Enter fullscreen mode Exit fullscreen mode

因为我们要捕获鼠标事件,所以我们将创建一个名为 的布尔变量pressed。它将pressed保存 的当前值mouse click

let pressed = Rc::new(Cell::new(false));
Enter fullscreen mode Exit fullscreen mode

mouseDown现在我们需要为| mouseUp|创建一个闭包(回调函数)mouseMove

    { mouse_down(&context, &pressed, &canvas); }
    { mouse_move(&context, &pressed, &canvas); }
    { mouse_up(&context, &pressed, &canvas); }
Enter fullscreen mode Exit fullscreen mode

我们将把这些事件期间需要执行的操作定义为单独的函数。这些函数以 Canvas 元素的上下文和按下状态为参数。


fn mouse_up(context: &std::rc::Rc<web_sys::CanvasRenderingContext2d>, pressed: &std::rc::Rc<std::cell::Cell<bool>>, canvas: &web_sys::HtmlCanvasElement) {
    let context = context.clone();
    let pressed = pressed.clone();
    let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
        pressed.set(false);
        context.line_to(event.offset_x() as f64, event.offset_y() as f64);
        context.stroke();
    }) as Box<dyn FnMut(_)>);
    canvas.add_event_listener_with_callback("mouseup", closure.as_ref().unchecked_ref()).unwrap();
    closure.forget();
}

fn mouse_move(context: &std::rc::Rc<web_sys::CanvasRenderingContext2d>, pressed: &std::rc::Rc<std::cell::Cell<bool>>, canvas: &web_sys::HtmlCanvasElement){
    let context = context.clone();
    let pressed = pressed.clone();
    let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
        if pressed.get() {
            context.line_to(event.offset_x() as f64, event.offset_y() as f64);
            context.stroke();
            context.begin_path();
            context.move_to(event.offset_x() as f64, event.offset_y() as f64);
        }
    }) as Box<dyn FnMut(_)>);
    canvas.add_event_listener_with_callback("mousemove", closure.as_ref().unchecked_ref()).unwrap();
    closure.forget();
}

fn mouse_down(context: &std::rc::Rc<web_sys::CanvasRenderingContext2d>, pressed: &std::rc::Rc<std::cell::Cell<bool>>, canvas: &web_sys::HtmlCanvasElement){
    let context = context.clone();
    let pressed = pressed.clone();

    let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
        context.begin_path();
        context.set_line_width(5.0);
        context.move_to(event.offset_x() as f64, event.offset_y() as f64);
        pressed.set(true);
    }) as Box<dyn FnMut(_)>);
    canvas.add_event_listener_with_callback("mousedown", closure.as_ref().unchecked_ref()).unwrap();
    closure.forget();
}

Enter fullscreen mode Exit fullscreen mode

它们与您的JavaScriptAPI 非常相似,但它们是用 Rust 编写的。

现在一切就绪。我们可以运行应用程序并在画布上绘图了。🎉 🎉 🎉

但我们没有任何颜色。

让我们添加一些颜色。

添加颜色样本。创建一个 div 列表,并将其用作选择器。

定义我们需要在程序中添加的颜色列表start

#[wasm_bindgen(start)]
pub fn start() -> Result<(), JsValue> {
    // ....... Some content
    let colors = vec!["#F4908E", "#F2F097", "#88B0DC", "#F7B5D1", "#53C4AF", "#FDE38C"];

   Ok()
}
Enter fullscreen mode Exit fullscreen mode

然后遍历列表,为所有颜色创建一个 div,并将其附加到文档中。每个 div 也添加一个onClick处理程序来更改颜色。


    for c in colors {
        let div = document
            .create_element("div")?
            .dyn_into::<web_sys::HtmlElement>()?;
        div.set_class_name("color");
        {
            click(&context, &div, c.clone());  // On Click Closure.
        }

        div.style().set_property("background-color", c);
        let div = div.dyn_into::<web_sys::Node>()?;
        document.body().unwrap().append_child(&div)?;
    }
Enter fullscreen mode Exit fullscreen mode

点击处理程序如下:

fn click(context: &std::rc::Rc<web_sys::CanvasRenderingContext2d>, div: &web_sys::HtmlElement, c: &str) {
    let context = context.clone();
    let c = JsValue::from(String::from(c));
    let closure = Closure::wrap(Box::new(move || {
        context.set_stroke_style(&c);            
    }) as Box<dyn FnMut()>);

    div.set_onclick(Some(closure.as_ref().unchecked_ref()));
    closure.forget();
}
Enter fullscreen mode Exit fullscreen mode

现在进行一些美化。打开static/index.html并添加颜色 div 的样式。

 <style>
       .color {
            display: inline-block;
            width: 50px;
            height: 50px;
            border-radius: 50%;
            cursor: pointer;
            margin: 10px;
       }
 </style>
Enter fullscreen mode Exit fullscreen mode

就这样,我们已经创建了应用程序。🎉

查看此处提供的演示应用程序。

希望以上内容能激励你开启精彩的 WebAssembly 之旅。如果你有任何问题/建议,或者觉得我遗漏了什么,欢迎随时留言。

您可以在Twitter上关注我。

如果你喜欢这篇文章,请点赞或者留言。❤️

这篇文章

在这里查看我的更多 WebAssembly 文章

文章来源:https://dev.to/sendilkumarn/create-dev-s-offline-page-with-rust-and-web assembly-21gn
PREV
提升浏览器开发者工具技能的 9 种方法 1. 颜色 2. 动画 3. 阴影编辑器 4. 命令工具 5. 覆盖范围 6. 渲染 7. 模拟暗模式 8. 传感器 9. 快捷键 :)
NEXT
您应该关注的两个媒体查询