【译】使用 Rust 和 WebAssembly 构建离线画图页面
- 原文地址:https://dev.to/sendilkumarn/create-dev-s-offline-page-with-rust-and-webassembly-21gn
- 原文仓库:https://github.com/sendilkumarn/draw-page
- 原文作者:Sendil Kumar N
- 译文出自:https://github.com/suhanyujie
- 本文永久链接:(缺省)
- 译者:suhanyujie
- 翻译不当之处,还请指出,谢谢!
- 文中的页面效果可以参考这里:离线画图页
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));
现在,我们需要为 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();
}
他们非常类似于你平时写的 JavaScript
的 API,但它们是用 Rust 编写的。
现在我们都设置好了。我们可以运行应用程序并在画布中画画。
【译】使用 Rust 和 WebAssembly 构建离线画图页面的更多相关文章
- [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 ...
- [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 ...
- 5分钟APIG实战: 使用Rust语言快速构建API能力开放
序言:Rust语言简介 参与过C/C++大型项目的同学可能都经历过因为Null Pointer.Memory Leak等问题“被” 加班了不知道多少个晚上.别沮丧,你不是一个人,Mozilla Fir ...
- 「译」用 Blazor WebAssembly 实现微前端
原文作者: Wael Kdouh 原文链接:https://medium.com/@waelkdouh/microfrontends-with-blazor-webassembly-b25e4ba3f ...
- Rust 中项目构建管理工具 Cargo简单介绍
cargo是Rust内置的项目管理工具.用于Rust 项目的创建.编译.执行,同一时候对项目的依赖进行管理,自己主动推断使用的第三方依赖库,进行下载和版本号升级. 一.查看 cargo 版本号 安装R ...
- 【译】Rust中的array、vector和slice
原文链接:https://hashrust.com/blog/arrays-vectors-and-slices-in-rust/ 原文标题:Arrays, vectors and slices in ...
- 【译】Rust,无畏并发
原文链接:https://dev.to/imaculate3/fearless-concurrency-5fk8 > 原文标题:That's so Rusty! Fearless concurr ...
- 【译】Rust宏:教程与示例(一)
原文标题:Macros in Rust: A tutorial with examples 原文链接:https://blog.logrocket.com/macros-in-rust-a-tutor ...
- 【译】Rust宏:教程与示例(二)
原文标题:Macros in Rust: A tutorial with examples 原文链接:https://blog.logrocket.com/macros-in-rust-a-tutor ...
随机推荐
- Hadoop学习之路(7)MapReduce自定义排序
本文测试文本: tom 20 8000 nancy 22 8000 ketty 22 9000 stone 19 10000 green 19 11000 white 39 29000 socrate ...
- P2710 数列[fhq treap]
调了一辈子的fhq treap- 如果不会最大子段和 如果不会fhq treap 7个操作- 其中三个查询 单点查询其实可以和区间查询写成一个( fhq treap 的修改操作大概就是 \(split ...
- JavaDay10(下)
生产者消费者问题 问题描述 有两个进程:一组生产者进程和一组消费者进程共享一个初始为空.固定大小为n的缓存(缓冲区).生产者的工作是制造一段数据,只有缓冲区没满时,生产者才能把消息放入到缓冲区,否则必 ...
- 树hash/树哈希 刷题记录
不同hash姿势: 树的括号序列最小表示法 s[i] 如果i为叶子节点:() 如果i的子节点为j1~jn:(s[j1]...s[jn]),注意s[j]要按照字典序排列
- 与第三方系统对接,生成Cloud出入库单据
案例: Cloud的采购订单同步到第三方系统,第三方系统入库后同步生成Cloud采购入库单. 解决方案:调用采购订单的下推API,先生成保存状态的采购入库单(采购入库单中的仓库是必填项,可以在采购订单 ...
- MAT(memory anlayzer tool)使用方法
Analyzing and understanding the memory use of an application is challenging. A subtle logic error ca ...
- 【笔记0-开篇】面试官系统精讲Java源码及大厂真题
背景 开始阅读 Java 源码的契机,还是在第一年换工作的时候,被大厂的技术面虐的体无完肤,后来总结大厂的面试套路,发现很喜欢问 Java 底层实现,即 Java 源码,于是我花了半年时间,啃下了 J ...
- Vuejs中created和mounted的区别
created:在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图. mounted:在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作
- 跨AppDomain通信
public class AppDomainTest : MarshalByRefObject { public string TestMethodStr(string srcAppDomain) { ...
- [ZJOI2007] 仓库建设 - 斜率优化dp
大脑真是个很优秀的器官,做事情之前总会想着这太难,真的逼着自己做下去,回头看看,其实也不过如此 很朴素的斜率优化dp了 首先要读懂题目(我的理解能力好BUG啊) 然后设\(dp[i]\)表示处理完前\ ...