大家好,我是刘布斯。
我们之前也给大家分享过 Rust 相关的文章,但大部分都是开发的一些工具,给前端的工程化提效,比如 Oxlint、Rolldown。
咱们能在页面中直接使用 Rust 吗?
答案是可以的,今天就带来一篇文章,介绍如何使用 Rust 来实现 canvas。
以下是正文:
以前一直有一些高性能的渲染问题困扰着我,比如canvas渲染百万、千万级数据,以及一些图片像素的操作等。虽然现在的js也给了很多优化的方案(比如web workers)。但是说到底还是有语言方面的局限性,直到我看到了wasm(WebAssembly),可以在客户端处理二进制程序。
简单来说是一种为网络而生的新型代码格式,旨在提供一种比传统 JavaScript 更快的执行速度
,可以大大的提升网络的性能,这个提升不是传统意义上的优化,而是真正意义上的从根本上完成代码运行质的飞跃。
提高性能
wasm允许浏览器中运行高性能代码,其运行速度接近原生。当我们前端需要进行处理大型密集的计算工作,以及实时图片处理等。跨语言支持
wasm并不是什么特定的编译语言,而是一种平台。开发者可以使用C
,C++
,Rust
,go
等编写代码,然后转为wasm,最后在浏览器中运行。与js功能互补
wasm出现并不是为了取代js,而是为了更好的为js提供服务。在一些特定的环境(比如可视化等)js可以做为交互,wasm可以做为密集计算。优化资源占用
wasm的设计是为了占用较少的内存,这使得它非常适合资源有限的设备,如手机等移动设备。通过减少内存占用,Wasm可以帮助前端应用在各种类型的设备上都能顺利运行,提升用户体验。现代浏览器是支持直接兼容.wasm文件
的。所以我们就是通过go
、Rust
等语言去编写wasm文件,然后通过编译器转为为.wasm文件在前端调用。
简单的介绍一下Rust的使用吧。
按照官网的安装流程来就好。
curl https://sh.rustup.rs -sSf | sh
安装完成之后,我们在终端输入:
rustc --version
cargo --version
出现这两个版本,表示安装成功!
这里面要注意一下:rustc
和cargo
都是Rust的编程工具,但是功能不同。
rustc
是Rust
的源代码编译器,把Rust
源码编译成可执行文件或库。cargo
是Rust
包管理工具(可以理解为npm)。主要目的就是管理Rust
的项目依赖,自动生成构建脚本等。完成安装之后,我们可以试一下新建一个Rust
项目。
cargo new my_project --lib
--lib
是 cargo
命令行工具的一个选项,用于指示Cargo创建一个新的库(library)项目。
我们看一下文件目录:
主要核心是这两个lib.rs
、Cargo.toml
。
lib.ts
源码的入口地方。Cargo.toml
项目的依赖管理文件。简单了解了一下Rust
之后,就有个疑问?Rust
如何编译为可以执行的wasm
文件?答案就是:wasm-bindgen
是一个强大的工具链,旨在简化 WebAssembly(WASM)模块与 JavaScript 之间的交互。主要目的是将 Rust 的性能优势引入Web开发中,并实现与 JavaScript 的无缝集成。
主要功能就是自动生成可以必要的绑定和胶水代码,确保Rust和js之间可以正常的平滑通信。
简单来说,wasm-bindgen
就是一个桥梁,沟通Rust
和js
之间运行的桥梁。
通过wasm-bindgen
编译的代码可以在js中使用。
如何在rust中使用呢?这就要用到我们之前说的Cargo.toml
文件了。把wasm-bindgen的依赖声明到对应的文件中:
[package]
name = "my_wasm_project"
version = "0.1.0"
edition = "els"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
[dependencies]
指定依赖和依赖的版本。比如:wasm-bindgen
的版本为0.2
。[package]
生成的这个包的一些基本信息。[crate-type]
是一个配置项,用于指定当你构建项目时生成的输出类型 。简单介绍了Rust的一些基本使用,那下面我们用一个案例来看一下Rust如何生成wasm
文件的。这里用一个简单的例子,通过传入一个canvas
实例,然后绘制一个圆。
我们新建一个项目,然后先安装依赖,回到Cargo.toml
文件中:
[package]
name = "my_wasm_project"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
web-sys = { version = "0.3", features = ["Window", "Document", "HtmlCanvasElement", "CanvasRenderingContext2d"] }
主要是看一下dependencies
。
wasm-bindgen
就是前面说的编译Rust代码的桥梁。
web-sys
是一个Rust
的标准库,它提供了对Web API
的绑定。包括 DOM、HTML、CSS、XMLHttpRequest、Fetch API、WebSocket
等等。使得你可以使用 Rust 语言来编写与浏览器环境交互的代码。
features
字段指定了你要启用的 Web API 特性。web-sys
默认并不包含所有的 Web API,你需要显式地指定你想要使用的那些特性。比如这里我们需要用canvas绘图,就需要显示的指定Document
,HtmlCanvasElement
,CanvasRenderingContext2d
等canvs的特性。
完成配置之后,我们回到lib.rs
:编写一段代码。
use wasm_bindgen::prelude::*;
use web_sys::{window, Document, HtmlCanvasElement, CanvasRenderingContext2d};
#[wasm_bindgen]
pub fn draw_circle(canvas_id: &str) -> Result<(), JsValue> {
// 获取全局window对象
let window = window().expect("no global `window` exists");
// 获取document
let document = window.document().expect("window should have a document");
// 通过 ID 获取 canvas 元素
let canvas = document.get_element_by_id(canvas_id)
.and_then(|e| e.dyn_into::<HtmlCanvasElement>().ok())
.expect("canvas element not found");
// 设置 canvas 尺寸
canvas.set_width(500);
canvas.set_height(500);
// 获取 2D 渲染上下文
let context = canvas
.get_context("2d")
.expect("failed to get context")
.unwrap()
.dyn_into::<CanvasRenderingContext2d>()
.expect("context is not of type 2d");
// 开始路径
context.begin_path();
// 绘制圆
context.arc(250.0, 250.0, 100.0, 0.0, std::f64::consts::PI * 2.0)?;
// 设置填充颜色
context.set_fill_style(&JsValue::from_str("blue"));
// 填充圆
context.fill();
// 设置描边颜色
context.set_stroke_style(&JsValue::from_str("black"));
// 描边圆
context.stroke();
Ok(())
}
实现了一个draw_circle
方法,然后可以提供在web端使用。
看起来上面代码有点懵逼,没关系我们可以一行一行分析一下,确实很难!!!!
use
是把模块,项,或者路径导入当前的目录中。这使得你可以在代码中直接使用这些导入的名称,而不需要每次都写完整的路径
。
use std::io;
这个意思就是把std
下面的io模块
导出,就可以直接使用了。
use co::*;
这个意思就是把co模块下的全部方法、定义等导出。
use wasm_bindgen::prelude::*;
意思就是从【wasm_bindgen】
的模块【prelude】
中导入所有的内容。
在rust中::
是一个很重要的操作符,主要用于访问模块、结构体、枚举、函数、常量等的命名空间中的成员。
如下例子:
引用模块中的函数:
mod math {
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
}
fn main() {
let result = math::add(1, 2); // 使用 :: 引用模块中的函数
println("{}", result);
}
// 引用枚举中的类型
enum Color {
Red = 'red',
Blue ='blue'
}
fn main() {
let color = Color::Red; // 使用 :: 引用枚举中的类型
}
use web_sys::{window, Document, HtmlCanvasElement, CanvasRenderingContext2d}
意思就是:引用web_sys模块中的window, Document, HtmlCanvasElement, CanvasRenderingContext2d使用。
pub
是访问修饰符,表示“公共”的意思,fn 是函数的关键字。通过 pub fn 定义的函数可以在模块外部被访问和调用。
pub fn draw_circle
声明的函数就可以在外部引入使用。
使用#[wasm_bindgen]
定义的函数,可以使得Rust 代码能够与 JavaScript 无缝交互。通过使用这个属性,你可以轻松地将 Rust 函数导出给 JavaScript 使用,或者从 Rust 代码中调用 JavaScript 函数。
表示当前的函数在js中导入使用是自动执行的。
JsValue
是 wasm-bindgen 库中的一个类型,用于表示JavaScript中的值。它可以表示任何JavaScript值,如数字、字符串、对象等。在Wasm环境中,JsValue 用于在Rust和JavaScript之间传递数据。
将 JavaScript 对象转换为特定的 Rust 类型。这个函数通常用于处理从 JavaScript 传递过来的对象,这些对象可能需要被转换成更具体的 Rust 类型,以便你可以调用该类型特有的方法或访问其属性。
由于 WebAssembly 和 JavaScript 之间的交互是通过接口定义来进行的,有时候你从 JavaScript 接收到的对象可能是通用的类型(如 JsValue
),但你需要将其转换为特定的 Rust 类型(如 HtmlElement
或 Document
)来使用。dyn_into
函数提供了这种转换能力,并且它会进行类型检查,确保转换是安全的。
// 尝试将 Node 转换为 HtmlElement
let element: HtmlElement = node.dyn_into().map_err(|_| {
console_error!("Failed to convert Node to HtmlElement");
}).unwrap();
通常用于 Option
和 Result
类型。它的主要作用是当值为 None
或 Err
时提供自定义的错误消息,并触发 panic!
宏,从而终止程序。说白了,就是Rust的错误处理机制。这也是Rust
为什么很安全的原因,每一句话都会有对应的错误处理。
OK,大概了解了一下上面的语法之后,再去看这个方法就很简单了,其实就是一个简单的canvas绘制,只不过函数的写法和一些异常处理变多了。
完成代码之后,我们把Rust
代码编译为wasm
。
wasm-pack build --target pkg
我们会发现在文件夹中多出了几个包:
这个pkg
就是在client的运行内容。
我们准备把Rust
生成的wasm
文件放到vue3.0项目中使用。
新建一个vue3.0项目:
npm init vite
然后我们把Rust
这个包放到对应的文件夹下面。
完成之后,我们可以在node_modules
里面关联一下my_wasm_project
下面的pkg包。
pnpm i ./my_wasm_project/pkg
就可以在node_modules
完成关联。
为了可以自动编译,然后同步更新node_modules
里面内容,我们在package.json
里面写一个脚步执行。
"scripts": {
"wasm": "cd ./my_wasm_project && wasm-pack build --target web && cd .. && pnpm install ./my_wasm_project/pkg"
},
cd ./my_wasm_project
进入到my_wasm_project
包中。wasm-pack build --target web
编译一下。cd .. && pnpm install ./my_wasm_project/pkg
回到根目录,然后install一下对应的包。然后在组件里面使用一下:
import init, { draw_circle } from 'my_wasm_project/my_wasm_project'
onMounted(async () => {
await init();
draw_circle('my_canvas')
})
这里要注意一下,需要把引入一个init函数先执行,这个目的是为了先构建wasm的运行环境。
然后执行一下draw_circle
方法。
ok,完成啦,一个最简单的canvas在页面中使用。
原文地址:https://juejin.cn/post/7442554317051461658
还没有使用过我们刷题网站(https://fe.ecool.fun/)或者刷题小程序的同学,如果近期准备或者正在找工作,千万不要错过,题库主打题全和更新快哦~。
有会员购买、辅导咨询的小伙伴,可以通过下面的二维码,联系我们的小助手。