---
title: "CanvasEvents 组件 — zenit Zig UI"
description: "画布级事件：滚动、缩放手势、拖拽与剪贴板图片往返。原始事件 payload 直达节点，不经过组件抽象。"
url: https://zenit.z.express/zh/components/canvasevents
language: zh-CN
alternate_en: https://zenit.z.express/components/canvasevents.md
alternate_es: https://zenit.z.express/es/components/canvasevents.md
alternate_ja: https://zenit.z.express/ja/components/canvasevents.md
alternate_ko: https://zenit.z.express/ko/components/canvasevents.md
alternate_fr: https://zenit.z.express/fr/components/canvasevents.md
alternate_de: https://zenit.z.express/de/components/canvasevents.md
project: zenit v0.1.0-alpha (Zig 0.15.2, macOS)
source: https://github.com/version-next/zenit
---

# CanvasEvents

`ui.events`

画布级事件：滚动、缩放手势、拖拽与剪贴板图片往返。

[Video](https://zenit.z.express/media/stories/canvasevents.mp4?v=1762a86227)

**BEHAVIOR CONTRACT**

原始事件 payload 直达节点，不经过组件抽象。

**RECORDED INTERACTION**

Harness 在画布上滚动，事件日志实时更新。

`examples/storybook/stories.zig:4111`

```zig
pub fn buildCanvasEvents(scope: *ui.Scope, cx: *ui.Cx) anyerror!*ui.Node {
    const a = cx.allocator;
    const c = try col(cx, 12);
    try c.appendChild(a, try label(cx, "Canvas events — scroll modifiers / magnify / drag / clipboard image"));

    const canvas = try ui.box(cx, .{
        .width = .{ .px = 420 },
        .height = .{ .px = 160 },
        .background = light.color.bg_secondary,
    }, .{});
    canvas.meta.ownership.meta.test_id = "story.canvasevents.canvas";

    const scroll_label = try ui.text(cx, "scroll (none)", .{ .font_size = 13, .color = light.color.fg_primary });
    scroll_label.meta.ownership.meta.test_id = "story.canvasevents.scroll";
    const magnify_label = try ui.text(cx, "magnify (none)", .{ .font_size = 13, .color = light.color.fg_primary });
    magnify_label.meta.ownership.meta.test_id = "story.canvasevents.magnify";
    const drag_label = try ui.text(cx, "drag (none)", .{ .font_size = 13, .color = light.color.fg_primary });
    drag_label.meta.ownership.meta.test_id = "story.canvasevents.drag";
    const clip_label = try ui.text(cx, "clip (none)", .{ .font_size = 13, .color = light.color.fg_primary });
    clip_label.meta.ownership.meta.test_id = "story.canvasevents.clip";

    const st = try cx.bindState(CanvasEventsStory, .{
        .cx = cx,
        .scroll_label = scroll_label,
        .magnify_label = magnify_label,
        .drag_label = drag_label,
        .clip_label = clip_label,
    });
    canvas.behavior.events.on_scroll = CanvasEventsStory.onScroll;
    canvas.behavior.events.on_event = CanvasEventsStory.onEvent;
    canvas.behavior.events.event_context = st;

    try c.appendChild(a, canvas);
    try c.appendChild(a, scroll_label);
    try c.appendChild(a, magnify_label);
    try c.appendChild(a, drag_label);
    try c.appendChild(a, clip_label);

    const clip_btn = try W.Button(.{ .label = "Clipboard PNG roundtrip", .on_click = cx.on(CanvasEventsStory, st, CanvasEventsStory.clipboardRoundtrip) }).mount(scope, cx);
    clip_btn.meta.ownership.meta.test_id = "story.canvasevents.clipbtn";
    try c.appendChild(a, clip_btn);
    return c;
}
```

[在 GitHub 查看源码examples/storybook/stories.zig:4111](https://github.com/version-next/zenit/blob/HEAD/examples/storybook/stories.zig#L4111)

这是 Storybook 中该 story 的原样代码（zenit 5f9add5+wip 2026-09-30）。其中 `W` 即 `ui.widgets`，`col` / `row` / `label` 是 Storybook 内的布局小工具。
