大家好,我是刘布斯。
我们之前也给大家分享过 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/)或者刷题小程序的同学,如果近期准备或者正在找工作,千万不要错过,题库主打题全和更新快哦~。
有会员购买、辅导咨询的小伙伴,可以通过下面的二维码,联系我们的小助手。