使用 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_sys
crate 包含所有可用的 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();
}
它们与您的JavaScript
API 非常相似,但它们是用 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