- 原文地址:dev.to/sendilkumar…
- 原文倉庫:github.com/sendilkumar…
- 原文做者:Sendil Kumar N
- 譯文出自:github.com/suhanyujie
- 本文永久連接:(缺省)
- 譯者:suhanyujie
- 翻譯不當之處,還請指出,謝謝!
- 文中的頁面效果能夠參考這裏:離線畫圖頁
Dev 網站的離線畫圖頁頗有趣。咱們能用 Rust 和 WebAssembly 來實現嗎?css
答案是確定的。讓咱們如今就來實現它。html
首先,咱們經過 Webpack 建立了一個基於 Rust 和 WebAssembly 的簡單應用。webpack
npm init rust-webpack dev-offline-canvas
複製代碼
Rust 和 WebAssembly 生態提供了 web_sys
,它在 Web API 上提供了不少須要的綁定。能夠從這裏檢出。git
示例應用已經引入了 web_sys
依賴。web_sys
crate 中包含了全部可用的 WebAPI 綁定。github
若是引入全部的 WebAPI 綁定將會增長綁定文件的大小。按需引入必要的 API 是比較重要的。web
咱們移除已經存在的 feature 列表(位於 toml 文件中)shell
features = [
'console'
]
複製代碼
並使用下面的替代:npm
features = [
'CanvasRenderingContext2d',
'CssStyleDeclaration',
'Document',
'Element',
'EventTarget',
'HtmlCanvasElement',
'HtmlElement',
'MouseEvent',
'Node',
'Window',
]
複製代碼
上面的 features 列表是咱們將在本例中須要使用的一些 features。canvas
打開文件 src/lib.rs
。api
使用下面的代碼替換掉文件中的 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 編寫的。
如今咱們都設置好了。咱們能夠運行應用程序並在畫布中畫畫。 🎉 🎉 🎉
但咱們尚未設定顏色。
增長顏色樣本,建立一個 div 列表,並使用它們做爲顏色選擇器。
在 start
函數中定義咱們須要的顏色列表。
#[wasm_bindgen(start)]
pub fn start() -> Result<(), JsValue> {
// ....... Some content
let colors = vec!["#F4908E", "#F2F097", "#88B0DC", "#F7B5D1", "#53C4AF", "#FDE38C"];
Ok()
}
複製代碼
而後遍歷顏色列表,爲全部顏色建立一個 div,並將其加入到 document 中。對於每一個 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)?;
}
複製代碼
其中 click 函數實現以下所示:
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 文章,點擊這兒。