【译】使用 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 ...
随机推荐
- linux(服务器)如何确认网卡(网口)对应的配置文件
服务器装完系统就要配置网络,然而服务器经常是多网卡多网口,我们在某个网口插上网线后,到/etc/sysconfig/network-scripts/下配置ip时无法确定网口对应的配置文件.(比如是et ...
- 剑指offer-面试题22-链表中倒数第k个节点-双指针
/* 题目: 链表中倒数第k个节点 */ /* 思路: 考虑边界条件:链表为空,k值超过链表长度. 双指针: q指针指向第k个节点,p指针指向第1个节点. q指针指向最后一个节点,p指针指向倒数第k个 ...
- 必看!macOS进阶不得不知的实用小技巧
不知道大家对使用苹果电脑的体验如何?您充分利用您的mac了吗?其实macOS上存在着许多快捷方式和技巧可以帮助简化我们的工作流程,提高效率,但是在日常生活中经常被人们忽略或者遗忘.以下是macdown ...
- js - 除法
取整数 1.丢弃小数部分,保留整数部分 js:parseInt(7/2) 2.向上取整,有小数就整数部分加1 js: Math.ceil(7/2) 3,四舍五入. js: Math.round(7/2 ...
- vue中的this.$nextTick()使用
首先我们来翻译一下nextTick是什么意思:下一个刻度 再来看看vue官网怎么说的: Vue.nextTick( [callback, context] )在下次 DOM 更新循环结束之后执行延迟回 ...
- linux C++ 读取mysql结果保存
c++读取mysql数据库结果保存 #include <fstream> #include <iomanip> #include <iostream> #inclu ...
- Google Waymo 2017自动驾驶安全技术报告(一)
2017年10月Google Waymo向美国交通部提交了一份43页的安全报告,报告中详细说明了Waymo如何装备和训练自动驾驶车辆,从而避免驾驶中的一般和意外情况发生.这份报告对Waymo的自动驾驶 ...
- VMware安装CentOS系统与配置全过程
1.需要哪些安装包 VMware Workstation 15 Pro CentOS-7-x86_64-DVD-1810 hadoop-2.7.3 apache-hive-3.1.1 jdk-8u18 ...
- php 文件追加写入
//追加写入 file_put_contents('xml.text',json_encode($postObj,JSON_FORCE_OBJECT).PHP_EOL,FILE_APPEND);
- 在macOS 上添加 JAVA_HOME 环境变量
If you are planing to develop Java Apps on your Mac, you may have to set $JAVA_HOME environment vari ...