首页 > 解决方案 > 为什么在 Canvas 的绘制应用程序中使用 beginPath 方法?

问题描述

这是一个关于Canvas. 这是一个编写的示例绘画应用程序,rust可编译为WebAssembly. 它使用画布进行绘图。当鼠标移动和停止时,它开始用铅笔绘制mouseDown事件mouseUp正在运行。

如果mouseMove示例具有功能

        context.line_to(event.offset_x() as f64, event.offset_y() as f64);
        context.stroke();
        context.begin_path(); //whats the use of this?
        context.move_to(event.offset_x() as f64, event.offset_y() as f64); //whats the use of this?

为什么我们有最后两个begin_pathmove_to函数调用?line_to当鼠标移动时,已经从开始到结束绘制一条线,然后从结束到下一个点。begin_path和有什么用move_to

#[wasm_bindgen]
pub fn greet() -> Result<(), JsValue> {
    utils::set_panic_hook();

    let document = web_sys::window().unwrap().document().unwrap();
    let canvas = document
        .create_element("canvas")?
        .dyn_into::<web_sys::HtmlCanvasElement>()?;
    document.body().unwrap().append_child(&canvas)?;
    canvas.set_width(640);
    canvas.set_height(480);
    canvas.style().set_property("border", "solid")?;
    let context = canvas
        .get_context("2d")?
        .unwrap()
        .dyn_into::<web_sys::CanvasRenderingContext2d>()?;
    let context = Rc::new(context);
    let pressed = Rc::new(Cell::new(false));
    {
        let context = context.clone();
        let pressed = pressed.clone();
        let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
            context.begin_path();
            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())?;
        closure.forget();
    }
    {
        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())?;
        closure.forget();
    }
    {
        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())?;
        closure.forget();
    }

    Ok(())
}

标签: canvasrusthtml5-canvas

解决方案


它是为了每次鼠标移动画一条线。

调用失败beginPath(),上下文的子路径仍将包含所有先前的绘制调用。因此,下一次stroke()调用时,所有这些子路径将再次绘制在先前的像素上,从而产生丑陋的伪影。
然后调用moveTo只是将未来的小段初始化为下一个鼠标移动向量。

这是一个示例,strokeStyle每次新鼠标移动都会更改 。由于我们不调用beginPath(),因此整个路径的颜色都发生了变化,而不仅仅是最后一段:

const canvas = document.querySelector( "canvas" );
const ctx = canvas.getContext("2d");
var hue = 0;

canvas.onmousemove = (evt) => {
  const rect = canvas.getBoundingClientRect();
  draw(evt.clientX - rect.left, evt.clientY - rect.top);
};

function draw(x, y) {
  ctx.lineTo( x, y );
  hue += 5;
  ctx.strokeStyle = `hsl(${hue}deg,100%,50%)`;
  ctx.stroke();
}
canvas {
  border: 1px solid;
}
<canvas></canvas>

相比之下,调用 时会发生以下情况beginPath

const canvas = document.querySelector( "canvas" );
const ctx = canvas.getContext("2d");
var hue = 0;

canvas.onmousemove = (evt) => {
  const rect = canvas.getBoundingClientRect();
  draw(evt.clientX - rect.left, evt.clientY - rect.top);
};

function draw(x, y) {
  ctx.lineTo( x, y );
  hue += 5;
  ctx.strokeStyle = `hsl(${hue}deg,100%,50%)`;
  ctx.stroke();
  ctx.beginPath();
  ctx.moveTo( x, y );
}
canvas {
  border: 1px solid;
}
<canvas></canvas>

但是,这可能会在线路交汇处产生问题,因为实际上没有接头。
因此,更好的方法是将所有向量存储在一个数组中,每帧清除整个上下文,然后重新绘制完整路径:(对不起,仍在 JS 中,我的 rust is rusted不存在)

const canvas = document.querySelector( "canvas" );
const ctx = canvas.getContext("2d");
const vectors = [];

canvas.onmousemove = (evt) => {
  const rect = canvas.getBoundingClientRect();
  draw(evt.clientX - rect.left, evt.clientY - rect.top);
};

function draw(x, y) {
  vectors.push( { x, y } );
  ctx.clearRect( 0, 0, canvas.width, canvas.height );
  ctx.beginPath();
  vectors.forEach( ({x, y}) => ctx.lineTo( x, y ) );
  ctx.stroke();
}
canvas {
  border: 1px solid;
}
<canvas></canvas>


推荐阅读