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. 案例-pyqt进度条案例

    import sys from PyQt5.QtWidgets import * from PyQt5.QtGui import * from PyQt5.QtCore import * import ...

  2. ECMAScript基本对象——Math数学对象

    1.创建 不用创建直接使用.直接Math.方法名(): 2.方法 ①random():返回0.0-1.0的随机数,左闭有开 区间有开区间和闭区间,其中又分为全开区间( ),全闭区间[ ],左开右闭区间 ...

  3. js image to base64 摘录

    //传入图片路径,返回base64 //用处,由于在新增时,使用的是base64的格式,为了统一,在编辑时,也将图片转为base64,以便于统一处理 /* 使用: 因为图像处理存在一定的延时,所以通过 ...

  4. sklearn.metrics中的评估方法

    https://www.cnblogs.com/mindy-snail/p/12445973.html 1.confusion_matrix 利用混淆矩阵进行评估 混淆矩阵说白了就是一张表格- 所有正 ...

  5. JZOJ5988 珂学计树题

    题意 liu_runda曾经是个喜欢切数数题的OIer,往往看到数数题他就开始刚数数题.于是liu_runda出了一个数树题.听说OI圈子珂学盛行,他就在题目名字里加了珂学二字.一开始liu_rund ...

  6. Java链表常见操作【剑指Offer】03:从尾到头打印链表

    题目描述 输入一个链表,按链表从尾到头的顺序返回一个ArrayList. 题解一:递归 /* 在最后一次递归方法返回以后,每一层的递归方法都会做一个arrayList.add(listNode.val ...

  7. AI 所需的数学基础

    一.[微积分] 基础概念(极限.可微与可导.全导数与偏导数):只要学微积分,就必须要明白的概念,否则后面什么都无法继续学习. 函数求导:求导是梯度的基础,而梯度是 AI 算法的基础,因此求导非常重要! ...

  8. python django项目的搭建及初始配置

    1.创建项目并创建应用 django-admin startproject my_project # 创建项目python manage.py startapp my_app # 创建应用 1.1 项 ...

  9. Windows 性能监视器的基本指标(CPU,内存,硬盘参数)

    转载:http://kms.lenovots.com/kb/article.php?id=7045 Windows 性能监视器的基本指标(CPU,内存,硬盘参数) 作为一个系统工程师来说,要看懂监控的 ...

  10. Vuejs+elementUI项目,在进行打包时,要注意的问题

    注意:打包之前,需要注意修改一些地方 (1)若是前后端分离开发的,前端开发过程中可能会在api.js中设置访问路径为服务器所在电脑的ip:端口,打包前,最好将它改回localhost:8080 (2) ...