使用 Rust 和 WebAssembly 创建 Dev 的离线页面🦄💡✨
Dev 的离线页面很有趣。我们能用 Rust 和 WebAssembly 实现吗?
答案是肯定的。让我们行动起来。
首先,我们将使用 Webpack 创建一个简单的 Rust 和 WebAssembly 应用程序。
npm init rust-webpack dev-offline-canvas
Rust 和 WebAssembly 生态系统提供了对 Web API 的必要绑定。点击此处web_sys查看。
示例应用程序已具有web_sys依赖项。该web_syscrate 包含所有可用的 WebAPI 绑定。
包含所有 WebAPI 绑定会增加绑定文件的大小。因此,只包含我们需要的 API 非常重要。
我们将删除现有功能
features = [
    'console'
]
并将其替换为以下内容:
features = [
  'CanvasRenderingContext2d',
  'CssStyleDeclaration',
  'Document',
  'Element',
  'EventTarget',
  'HtmlCanvasElement',
  'HtmlElement',
  'MouseEvent',
  'Node',
  'Window',
]
上述功能列表是我们将在本例中使用的完整功能集。
让我们写一些 Rust
打开src/lib.rs。
start()用以下内容替换该函数:
#[wasm_bindgen(start)]
pub fn start() -> Result<(), JsValue> {
   Ok()
}
WebAssembly 模块实例化后,会立即调用此函数。点击此处#[wasm_bindgen(start)]查看规范中关于 start 函数的更多信息。
window我们将在 Rust 中获取对象。
    let window = web_sys::window().expect("should have a window in this context");
然后从对象中获取文档window。
     let document = window.document().expect("window should have a document");
创建一个 Canvas 元素并将其附加到文档。
    let canvas = document
         .create_element("canvas")?
         .dyn_into::<web_sys::HtmlCanvasElement>()?;
    document.body().unwrap().append_child(&canvas)?;
设置画布元素的宽度、高度和边框。
    canvas.set_width(640);
    canvas.set_height(480);
    canvas.style().set_property("border", "solid")?;
在 Rust 中,一旦执行超出上下文或方法返回任何值,内存就会被丢弃。但在 JavaScript 中,只要页面正常运行, 就会一直存在window。document
因此,为内存创建引用并使其静态存在直到程序完全关闭非常重要。
获取 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);
因为我们要捕获鼠标事件,所以我们将创建一个名为 的布尔变量pressed。它将pressed保存 的当前值mouse click。
let pressed = Rc::new(Cell::new(false));
mouseDown现在我们需要为| mouseUp|创建一个闭包(回调函数)mouseMove。
    { mouse_down(&context, &pressed, &canvas); }
    { mouse_move(&context, &pressed, &canvas); }
    { mouse_up(&context, &pressed, &canvas); }
我们将把这些事件期间需要执行的操作定义为单独的函数。这些函数以 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();
}
它们与您的JavaScriptAPI 非常相似,但它们是用 Rust 编写的。
现在一切就绪。我们可以运行应用程序并在画布上绘图了。🎉 🎉 🎉
但我们没有任何颜色。
让我们添加一些颜色。
添加颜色样本。创建一个 div 列表,并将其用作选择器。
定义我们需要在程序中添加的颜色列表start。
#[wasm_bindgen(start)]
pub fn start() -> Result<(), JsValue> {
    // ....... Some content
    let colors = vec!["#F4908E", "#F2F097", "#88B0DC", "#F7B5D1", "#53C4AF", "#FDE38C"];
   Ok()
}
然后遍历列表,为所有颜色创建一个 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)?;
    }
点击处理程序如下:
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();
}
现在进行一些美化。打开static/index.html并添加颜色 div 的样式。
 <style>
       .color {
            display: inline-block;
            width: 50px;
            height: 50px;
            border-radius: 50%;
            cursor: pointer;
            margin: 10px;
       }
 </style>
就这样,我们已经创建了应用程序。🎉
查看此处提供的演示应用程序。
希望以上内容能激励你开启精彩的 WebAssembly 之旅。如果你有任何问题/建议,或者觉得我遗漏了什么,欢迎随时留言。
您可以在Twitter上关注我。
如果你喜欢这篇文章,请点赞或者留言。❤️
这篇文章。
在这里查看我的更多 WebAssembly 文章。
文章来源:https://dev.to/sendilkumarn/create-dev-s-offline-page-with-rust-and-web assembly-21gn 后端开发教程 - Java、Spring Boot 实战 - msg200.com
            后端开发教程 - Java、Spring Boot 实战 - msg200.com
          