Yew 与 WebGL 实战:用 NodeRef + web-sys 在 Rust/Wasm 中绘制 Canvas 渲染循环

发布时间:2026/9/19 13:44:17

Yew 与 WebGL 实战:用 NodeRef + web-sys 在 Rust/Wasm 中绘制 Canvas 渲染循环 Yew 与 WebGL 实战用 NodeRef web-sys 在 Rust/Wasm 中绘制 Canvas 渲染循环【免费下载链接】yewRust / Wasm framework for creating reliable and efficient web applications项目地址: https://gitcode.com/gh_mirrors/ye/yewYew 是一个使用 Rust 编写可靠且高效 Web 应用的框架Rust / Wasm framework for creating reliable and efficient web applications。本文以仓库中的 examples/webgl 示例为主线讲解如何用 Yew 组件驱动 WebGL通过NodeRef获取canvasDOM 元素、用web-sys初始化 GL 上下文、编写着色器并建立基于requestAnimationFrame的渲染循环。读完本文你将掌握在 Yew 应用中直接调用浏览器 WebGL API 的完整落地路径包括类型转换、着色器绑定、缓冲区上传与逐帧更新等关键环节。示例概览Yew 官方 WebGL Demo 做了什么examples/webgl是 Yew 仓库中一个极简但完整的 WebGL 示例。它的目标非常明确用 Yew 渲染一个canvas元素通过NodeRef拿到真实的HtmlCanvasElement用web-sys的WebGlRenderingContext初始化 GL 上下文编写基础顶点着色器与片元着色器绘制两个覆盖整个画布的三角形借助requestAnimationFrame建立渲染循环用u_time时间统一变量驱动画面颜色持续变化。该示例对应的 HTML 入口 index.html 十分精简只声明了data-trunk relrust让 Trunk 完成 Rust 到 Wasm 的构建!DOCTYPE html html langen head meta charsetutf-8 / titleYew • WebGL/title link>pub struct App { node_ref: NodeRef, } impl Component for App { type Message (); type Properties (); fn create(_ctx: ContextSelf) - Self { Self { node_ref: NodeRef::default(), } } fn view(self, _ctx: ContextSelf) - Html { html! { canvas ref{self.node_ref.clone()} / } } // ... }从源码结构看NodeRef在 packages/yew/src/html/mod.rs 中定义为pub struct NodeRef(RcRefCellNodeRefInner)内部持有OptionNode且实现了Default、Clone与ImplicitClone因此可以在组件字段与html!宏的ref属性之间安全克隆传递。在 rendered 生命周期中取出节点DOM 只有在渲染完成后才存在因此获取节点必须放在rendered回调里。示例在first_render为true时才启动渲染循环examples/webgl/src/main.rsfn rendered(mut self, _ctx: ContextSelf, first_render: bool) { // Only start the render loop if its the first render // Theres no loop cancellation taking place, so if multiple renders happen, // there would be multiple loops running. That doesnt *really* matter here because // theres no props update and no SSR is taking place, but it is something to keep in // consideration if !first_render { return; } let canvas self.node_ref.cast::HtmlCanvasElement().unwrap(); let gl: GL canvas .get_context(webgl) .unwrap() .unwrap() .dyn_into() .unwrap(); Self::render_gl(gl); }其中cast::HtmlCanvasElement()对应NodeRef::cast的实现packages/yew/src/html/mod.rs它要求目标类型满足AsRefNode FromJsValue从而把泛型Node转成具体的HtmlCanvasElement。示例源码注释也提醒了first_render判断的意义由于这里没有做循环取消若组件多次渲染就会启动多个渲染循环当前场景没有 props 更新、也不涉及 SSR所以无碍但这是值得留意的通用注意事项。此外文档明确提示不要手动修改 Yew 渲染出的 DOM 树NodeRef应当被视为只读访问见 node-refs.mdx。WebGL 的绘图发生在 canvas 的 GL 上下文中不属于对 DOM 树的篡改因此与本约定并不冲突。核心概念二用 web-sys 调用浏览器 JavaScript APIweb-sys是 wasm-bindgen 生态中提供 Web API 绑定的 crate。本示例通过 Cargo.toml 按需启用所需 feature[dependencies] js-sys.workspace true wasm-bindgen.workspace true yew { path ../../packages/yew, features [csr] } [dependencies.web-sys] workspace true features [ HtmlCanvasElement, WebGlBuffer, WebGlProgram, WebGlRenderingContext, WebGlShader, WebGlUniformLocation, ]这些 feature 全部来自工作区统一声明的web-sys 0.3.70见仓库根目录 Cargo.toml。启用后即可在 Rust 中以近乎原生的类型安全方式调用浏览器 API例如let canvas self.node_ref.cast::HtmlCanvasElement().unwrap(); let gl: GL canvas .get_context(webgl) .unwrap() .unwrap() .dyn_into() .unwrap();这里get_context(webgl)返回的是一个OptionJsValue形式的Result需要用两次unwrap()再加一次dyn_into()才能得到WebGlRenderingContext。dyn_into来自wasm_bindgen::JsCasttrait是 wasm-bindgen 中做动态类型转换的标准手段。从零搭建渲染管线缓冲区、着色器与程序render_gl函数集中演示了 WebGL 渲染管线的标准装配流程examples/webgl/src/main.rs。着色器源码通过include_str!在编译期直接嵌入 Rust 二进制避免了运行时网络加载let vert_code include_str!(./basic.vert); let frag_code include_str!(./basic.frag);顶点数据与缓冲区示例用 6 个顶点即两个三角形铺满整个裁剪空间形成覆盖全画布的四边形let vertices: Vecf32 vec![ -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, ]; let vertex_buffer gl.create_buffer().unwrap(); let verts js_sys::Float32Array::from(vertices.as_slice()); gl.bind_buffer(GL::ARRAY_BUFFER, Some(vertex_buffer)); gl.buffer_data_with_array_buffer_view(GL::ARRAY_BUFFER, verts, GL::STATIC_DRAW);关键点在于Rust 的Vecf32不能直接交给 WebGL必须先经js_sys::Float32Array::from(...)转成 JavaScript 的 TypedArray再通过buffer_data_with_array_buffer_view上传到 GPU这正是js-sys在示例中发挥的作用。着色器编译与链接接下来依次创建顶点着色器与片元着色器编译后链接成着色器程序examples/webgl/src/main.rslet vert_shader gl.create_shader(GL::VERTEX_SHADER).unwrap(); gl.shader_source(vert_shader, vert_code); gl.compile_shader(vert_shader); let frag_shader gl.create_shader(GL::FRAGMENT_SHADER).unwrap(); gl.shader_source(frag_shader, frag_code); gl.compile_shader(frag_shader); let shader_program gl.create_program().unwrap(); gl.attach_shader(shader_program, vert_shader); gl.attach_shader(shader_program, frag_shader); gl.link_program(shader_program); gl.use_program(Some(shader_program));这套“创建 → 写源码 → 编译 → 挂载 → 链接 → 启用”的流程与原生 JavaScript WebGL 教程完全对应只是把每个 API 都换成了 Rust 类型安全的调用。顶点属性与 uniform 绑定顶点位置通过 attributea_position交给着色器时间通过 uniformu_time传入examples/webgl/src/main.rslet position gl.get_attrib_location(shader_program, a_position) as u32; gl.vertex_attrib_pointer_with_i32(position, 2, GL::FLOAT, false, 0, 0); gl.enable_vertex_attrib_array(position); let time gl.get_uniform_location(shader_program, u_time); gl.uniform1f(time.as_ref(), timestamp as f32); gl.draw_arrays(GL::TRIANGLES, 0, 6);vertex_attrib_pointer_with_i32告诉 GPU 每个顶点取 2 个FLOAT分量x、y步长与偏移为 0u_time是片元着色器中驱动颜色变化的核心参数首次绘制时传入0.0。两个着色器的配合顶点着色器 basic.vert 非常朴素只把二维位置透传为裁剪坐标precision mediump float; attribute vec2 a_position; void main() { gl_Position vec4(a_position, 0.0, 1.0); }片元着色器 basic.frag 用u_time的正弦值生成随时间流动的 RGB 颜色precision mediump float; uniform float u_time; void main() { float r sin(u_time * 0.0003); float g sin(u_time * 0.0005); float b sin(u_time * 0.0007); gl_FragColor vec4(r, g, b, 1.0); }三个正弦函数使用不同频率系数0.0003 / 0.0005 / 0.0007使红、绿、蓝三通道以不同速率振荡从而产生持续渐变的动态画面。渲染循环requestAnimationFrame 与闭包的自引用技巧示例的逐帧更新没有使用gloo的渲染封装而是直接调用window.requestAnimationFrame。它在 main.rs 中封装为一个静态方法fn request_animation_frame(f: Closuredyn FnMut()) { window() .unwrap() .request_animation_frame(f.as_ref().unchecked_ref()) .expect(should register requestAnimationFrame OK); }难点在于每一帧回调执行完后要再次注册下一帧即闭包需要“引用自己”。示例采用RcRefCellOptionClosure先占位、后填充的标准技巧examples/webgl/src/main.rslet cb Rc::new(RefCell::new(None)); *cb.borrow_mut() Some(Closure::wrap(Box::new({ let cb cb.clone(); move || { // This should repeat every frame timestamp 20.0; gl.uniform1f(time.as_ref(), timestamp as f32); gl.draw_arrays(GL::TRIANGLES, 0, 6); App::request_animation_frame(cb.borrow().as_ref().unwrap()); } }) as Boxdyn FnMut())); App::request_animation_frame(cb.borrow().as_ref().unwrap());这段代码的含义创建可变的共享容器cb初始为None用Closure::wrap把 Rust 闭包包装成 wasm-bindgen 可传给 JS 的回调闭包内部先克隆cb实现循环引用注册——每一帧结束时调用App::request_animation_frame把同一个闭包再次交给浏览器timestamp 20.0让时间每帧递增驱动u_time变化随后重新上传 uniform 并draw_arrays重绘。示例源码注释还专门解释了为什么不复用gloo-renderrequest_animation_frame存在每帧额外的闭包包装逻辑属于不必要的开销这里只构造一次包装闭包性能更优。启动方式与依赖说明运行命令按官方 README在examples/webgl目录下用 Trunk 开发服务器启动即可trunk serve --open该命令会编译 Rust 到 Wasm、启动本地开发服务器并自动在浏览器打开页面看到由正弦颜色驱动的全屏渐变画面即代表 WebGL 管线正常工作。完整可运行前提包括本机装有 Rust 工具链、wasm32-unknown-unknowntarget 与trunk命令行工具。依赖约束示例依赖全部来自工作区统一定义Cargo.tomlwasm-bindgen 0.2、js-sys 0.3、web-sys 0.3.70。由于 WebGL 的获取与绘制都发生在浏览器环境示例以features [csr]依赖本地yew包examples/webgl/Cargo.toml即纯客户端渲染模式不需要服务端渲染能力。延伸官方 README 中列出的改进方向README 的 “Improvements” 一节给出了一条明确的后续路线使用更加花哨的着色器a much more flashy shader。当前 basic.frag 仅用三个正弦函数做颜色渐变是最小可运行示例。感兴趣的读者可以从以下几点入手扩展增加varying插值变量让颜色随顶点位置在片元间平滑过渡引入WebGLBuffer之外的多属性布局法线、UV 等把示例升级为三维网格渲染加入交互监听鼠标/触摸事件修改 uniform实现实时形变或粒子系统注意示例注释中提到的循环生命周期管理——若引入 props 更新或 SSR需要为渲染循环增加取消逻辑避免多次启动。小结通过examples/webgl这个示例可以完整看到 Yew 与浏览器底层 API 协同工作的三种关键能力一是NodeRef在rendered生命周期中把虚拟 DOM 桥接到真实 DOM 节点packages/yew/src/html/mod.rs二是web-sys以类型安全方式调用 WebGL 全套 API三是RcRefCellOptionClosure模式实现可自续的requestAnimationFrame渲染循环。这套“组件 引用 原生 API”的组合拳是后续在 Yew 中实现 WebGL 游戏、数据可视化或 GPU 计算等高阶场景的基础模板。【免费下载链接】yewRust / Wasm framework for creating reliable and efficient web applications项目地址: https://gitcode.com/gh_mirrors/ye/yew创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/9/19 13:44:17

STM32智能小车毕设指南:从供电驱动到PID速度闭环

简介:基于STM32F103的智能小车毕业设计说明文档,面向电子、嵌入式方向学生及开发者,系统阐述智能小车控制系统整体设计思路,重点解决循迹与避障的软硬件协同问题。文档为1个DOCX格式文件,压缩包大小约3.51MB&#xff0…

2026/9/19 13:44:17

基于STC89C52的HC-SR04超声波倒车雷达:原理、代码与标定

简介:一份以AT89C51单片机为核心、基于超声波测距原理的倒车雷达系统设计文档,面向电子与嵌入式方向的课程设计、毕业设计及电子竞赛备赛人群,适合需要完成从方案论证到实物调试完整流程的读者。文档从课题背景、国内外现状切入,详…

2026/9/19 14:54:19

DFT频谱偏差根源与窗函数工程选型指南

简介:本资源是华南理工大学《信号与系统》课程配套的第三份实验报告,面向电子信息、通信工程等专业本科生及信号处理初学者,聚焦离散傅里叶变换(DFT)在模拟信号频谱分析中的核心应用。报告通过三大实验模块系统展开&am…

2026/9/18 14:13:01

拯救者Y7000黑屏故障排查与维修实战指南

1. 项目概述:一台黑屏的拯救者Y7000,到底卡在哪一步? 联想拯救者Y7000系列笔记本,从2018年第一代搭载i5-8300H开始,到后来的i7-9750H、i7-10750H、i5-11400H,再到2023年款的R7-7840HS,它始终是学…

2026/9/19 0:03:10

验证 OpenSpec 兼容性,Cursor 的 Token 从 TaoToken 出

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/19 0:03:10

书桌角落的 Mac mini,OpenClaw 通过 TaoToken 跑任务。

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/19 0:03:10

oh-my-hermes:打造跨工具的命令编排与插件化工作流

1. 项目概述与设计初衷1.1 它到底是什么先说结论:oh-my-hermes 是一个面向开发者日常终端操作的效率工具套件,核心定位是“把分散在各类命令行工具里的高频操作,统一收拢成一套插件化、可编排的工作流”。项目灵感来源很明显——oh-my-zsh 重…

2026/9/18 14:13:03

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/18 14:13:02

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/18 14:13:02

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码