You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Yew框架MNIST ONNX绘图组件Rust所有权问题求助

Rust Yew框架MNIST绘图组件所有权问题修复方案

问题核心

在Yew框架复现MNIST ONNX Runtime绘图组件时,draw回调中的所有权处理逻辑错误:通过clone创建临时向量导致后续循环无法正确访问笔画数据,且循环内错误将points覆盖为第一个笔画的克隆,完全偏离了绘制当前笔画的逻辑。

问题代码分析

原代码中存在两个关键问题:

  1. let mut points = strokes.last().and_then(|p| Some(p.clone())).unwrap_or_else(|| Vec::new()); 每次创建新向量,未将新点同步回draw_context的状态中
  2. 循环内points = strokes[0].clone(); 错误覆盖了当前要绘制的点集合,导致遍历对象错误

原问题代码片段:

let draw = Callback::from(move |e: MouseEvent| {
    let canvas = canvas_node_ref.cast::<HtmlCanvasElement>();
    log::info!("draw");

    if let Some(canvas) = canvas {
        let ctx = match canvas.get_context("2d") {
            Ok(Some(ctx)) => ctx,
            _ => return,
        }
        .dyn_into::<web_sys::CanvasRenderingContext2d>()
        .unwrap();
    
        ctx.set_line_width(20.0);
        ctx.set_line_join("round");
        ctx.set_line_cap("round");
        ctx.set_stroke_style(&JsValue::from_str("#393E46"));
        ctx.clear_rect(0.0, 0.0, canvas.width() as f64, canvas.height() as f64);

        let strokes = draw_context_clone.state().strokes;
        let mut points = strokes.last().and_then(|p| Some(p.clone())).unwrap_or_else(|| Vec::new());
        points.push(math_utils::get_coordinates(e));
        let len = points.len();
    
        for s in 0..len-1 {
            points = strokes[0].clone();
            let p1 = points[s];
            let p2 = points[s + 1];
            log::info!("inside for loopp {}", p1.0);
            ctx.begin_path();
            ctx.move_to(p1.0 as f64, p1.1 as f64);
            let midpoint = math_utils::get_midpoint(p1, p2);
            ctx.quadratic_curve_to(p1.0 as f64, p1.1 as f64, midpoint.0 as f64, midpoint.1 as f64);
            ctx.stroke();   
        }
    }
});

let draw_clone = draw.clone();
let activate_draw = Callback::from(move |e: MouseEvent| {
    log::info!("activate_draw");
    drawing.set(!*drawing);
    draw_context.new_stroke();
    let strokes = draw_context.state().strokes;

    let mut points = strokes.last().and_then(|p| Some(p.clone())).unwrap_or_else(|| Vec::new());
    log::info!("{}", points.len());
    points.push(math_utils::get_coordinates(e.clone()));
    draw_clone.emit(e);
});

修复后的代码

let draw = Callback::from(move |e: MouseEvent| {
    // 仅在绘图激活状态下处理
    if !*drawing.get() {
        return;
    }

    let canvas = canvas_node_ref.cast::<HtmlCanvasElement>();
    log::info!("draw");

    if let Some(canvas) = canvas {
        let ctx = match canvas.get_context("2d") {
            Ok(Some(ctx)) => ctx,
            _ => return,
        }
        .dyn_into::<web_sys::CanvasRenderingContext2d>()
        .unwrap();
    
        ctx.set_line_width(20.0);
        ctx.set_line_join("round");
        ctx.set_line_cap("round");
        ctx.set_stroke_style(&JsValue::from_str("#393E46"));
        ctx.clear_rect(0.0, 0.0, canvas.width() as f64, canvas.height() as f64);

        // 获取当前状态的可变引用,直接更新笔画数据
        let mut state = draw_context_clone.state_mut();
        let current_stroke = state.strokes.last_mut().unwrap_or_else(|| {
            state.strokes.push(Vec::new());
            state.strokes.last_mut().unwrap()
        });
        current_stroke.push(math_utils::get_coordinates(e));

        // 重新绘制所有笔画
        for stroke in &state.strokes {
            if stroke.len() < 2 {
                continue;
            }
            ctx.begin_path();
            let mut prev_point = stroke[0];
            ctx.move_to(prev_point.0 as f64, prev_point.1 as f64);
            
            for i in 1..stroke.len() {
                let curr_point = stroke[i];
                let midpoint = math_utils::get_midpoint(prev_point, curr_point);
                ctx.quadratic_curve_to(prev_point.0 as f64, prev_point.1 as f64, midpoint.0 as f64, midpoint.1 as f64);
                prev_point = curr_point;
            }
            ctx.stroke();
        }
    }
});

let draw_clone = draw.clone();
let activate_draw = Callback::from(move |e: MouseEvent| {
    log::info!("activate_draw");
    let new_drawing_state = !*drawing.get();
    drawing.set(new_drawing_state);
    
    if new_drawing_state {
        // 激活绘图时创建新笔画
        let mut state = draw_context.state_mut();
        state.strokes.push(Vec::new());
        let current_stroke = state.strokes.last_mut().unwrap();
        current_stroke.push(math_utils::get_coordinates(e.clone()));
    }
    
    draw_clone.emit(e);
});

关键改进说明

  • 避免不必要的Clone:通过state_mut()获取draw_context的可变引用,直接修改内部的笔画向量,无需克隆整个向量
  • 修正绘制逻辑:循环遍历所有笔画,对每个笔画内的点依次绘制二次贝塞尔曲线,确保所有已绘制内容都被正确渲染
  • 状态同步:在activate_draw中直接更新draw_context的状态,而不是创建临时向量后丢弃,保证UI状态与数据一致
  • 添加状态判断:在draw回调开头检查绘图激活状态,避免无效的鼠标移动事件处理

内容的提问来源于stack exchange,提问作者Pash

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 23:15:57