Dev 网站的离线画图页很有趣。我们能用 Rust 和 WebAssembly 来实现吗?

答案是肯定的。让我们现在就来实现它。

首先,我们通过 Webpack 创建了一个基于 Rust 和 WebAssembly 的简单应用。

npm init rust-webpack dev-offline-canvas

Rust 和 WebAssembly 生态提供了 web_sys,它在 Web API 上提供了很多需要的绑定。可以从这里检出。

示例应用已经引入了 web_sys 依赖。web_sys crate 中包含了所有可用的 WebAPI 绑定。

如果引入所有的 WebAPI 绑定将会增加绑定文件的大小。按需引入必要的 API 是比较重要的。

我们移除已经存在的 feature 列表(位于 toml 文件中)

features = [
'console'
]

并使用下面的替代:

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

上面的 features 列表是我们将在本例中需要使用的一些 features。

开始写段 Rust 代码

打开文件 src/lib.rs

使用下面的代码替换掉文件中的 start() 函数:

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

一旦实例化了 WebAssembly 模块,#[wasm_bindgen(start)] 就会调用这个函数。可以查看规范中关于 start 函数的详细信息

我们在 Rust 中将得到 window 对象。

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

接着从 window 对象中获取 document。

let document = window.document().expect("window should have a document");

创建一个 Canvas 元素,将其插入到 document 中。

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

设置 canvas 元素的宽、高和边框。

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

在 Rust 中,一旦离开当前上下文或者函数已经 return,对应的内存就会被释放。但在 JavaScript 中,window, document 在页面的启动和运行时都是活动的(位于生命周期中)。

因此,为内存创建一个引用并使其静态化,直到程序运行结束,这一点很重要。

获取 Canvas 渲染的上下文,并在其外层包装一个 wrapper,以保证它的生命周期。

RC 表示 Reference Counted

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

这个引用被 clone 并用于回调方法。

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

Since we are going to capture the mouse events. We will create a boolean variable called pressed. The pressed will hold the current value of mouse click.

因为我们要响应 mouse 事件。因此我们将创建一个名为 pressed 的布尔类型的变量。pressed 用于保存 mouse click(鼠标点击)的当前值。

let pressed = Rc::new(Cell::new(false));

现在,我们需要为 mouseDownmouseUpmouseMove 创建一个闭包(回调函数)。

{ 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();
}

他们非常类似于你平时写的 JavaScript 的 API,但它们是用 Rust 编写的。

现在我们都设置好了。我们可以运行应用程序并在画布中画画。

【译】使用 Rust 和 WebAssembly 构建离线画图页面的更多相关文章

  1. [WASM Rust] Create and Publish a NPM Package Containing Rust Generated WebAssembly using wasm-pack

    wasm-pack is a tool that seeks to be a one-stop shop for building and working with Rust generated We ...

  2. [Rust] Setup Rust for WebAssembly

    In order to setup a project we need to install the nightly build of Rust and add the WebAssembly tar ...

  3. 5分钟APIG实战: 使用Rust语言快速构建API能力开放

    序言:Rust语言简介 参与过C/C++大型项目的同学可能都经历过因为Null Pointer.Memory Leak等问题“被” 加班了不知道多少个晚上.别沮丧,你不是一个人,Mozilla Fir ...

  4. 「译」用 Blazor WebAssembly 实现微前端

    原文作者: Wael Kdouh 原文链接:https://medium.com/@waelkdouh/microfrontends-with-blazor-webassembly-b25e4ba3f ...

  5. Rust 中项目构建管理工具 Cargo简单介绍

    cargo是Rust内置的项目管理工具.用于Rust 项目的创建.编译.执行,同一时候对项目的依赖进行管理,自己主动推断使用的第三方依赖库,进行下载和版本号升级. 一.查看 cargo 版本号 安装R ...

  6. 【译】Rust中的array、vector和slice

    原文链接:https://hashrust.com/blog/arrays-vectors-and-slices-in-rust/ 原文标题:Arrays, vectors and slices in ...

  7. 【译】Rust,无畏并发

    原文链接:https://dev.to/imaculate3/fearless-concurrency-5fk8 > 原文标题:That's so Rusty! Fearless concurr ...

  8. 【译】Rust宏:教程与示例(一)

    原文标题:Macros in Rust: A tutorial with examples 原文链接:https://blog.logrocket.com/macros-in-rust-a-tutor ...

  9. 【译】Rust宏:教程与示例(二)

    原文标题:Macros in Rust: A tutorial with examples 原文链接:https://blog.logrocket.com/macros-in-rust-a-tutor ...

随机推荐

  1. 【spring boot】SpringBoot初学(3)– application配置和profile隔离配置

    前言 github: https://github.com/vergilyn/SpringBootDemo 说明:我代码的结构是用profile来区分/激活要加载的配置,从而在一个project中写各 ...

  2. gulp常用插件之gulp-load-plugins使用

    更多gulp常用插件使用请访问:gulp常用插件汇总 gulp-load-plugins这是一款批量引入package.json文件中的依赖项工具. 更多使用文档请点击访问gulp-load-plug ...

  3. 咸鱼的ACM之路:动态规划(DP)学习记录

    按挑战程序设计竞赛介绍的顺序记录一遍学习DP的过程. 1. 01背包问题 问题如下: 有N个物品,每个物品(N[i])都有一定的体积(W[i]),和一定的价值(V[i]) 现在给定一个背包,背包的容量 ...

  4. Easy_language

    http://www.guosen.com.cn/gxzq/tradestation/help/operate/operate06.html power language https://seekin ...

  5. C++中c_str()的用法

    这个函数经常用到,总是记不住,记下来,方便翻阅 c_str()函数返回一个指向正规C字符串的指针,内容与string串相同. C++中c_str()主要用法就是为了与C语言兼容,在C语言中没有stri ...

  6. mysql 获取当前时间加上一个月

    select DATE_ADD(NOW(), interval 1 MONTH) NOW()此处必须为时间格式 date_add() 增加 date_sub()减少 month 月份 minute 分 ...

  7. 风变编程笔记(一)-Python基础语法

    第0关  print()函数与变量 1. print()函数print()函数:告诉计算机,把括号的内容显示在屏幕上 # 不带引号 print(1+1) # 让计算机读懂括号里的内容,打印最终的结果 ...

  8. SmtpStatusCode Enum

  9. python is 与==区别

    总结 is 比较的是两个实例对象是不是完全相同,它们是不是同一个对象,占用的内存地址是否相同.莱布尼茨说过:“世界上没有两片完全相同的叶子”,这个is正是这样的比较,比较是不是同一片叶子(即比较的id ...

  10. fastadmin中curd生成的表单将数字展示为文字

    1.在require-table.js文件中找到formatter 在status中将下列参数自行替换为你的表达方式 var custom = {2: 'success', 3: 'danger', ...