Canvas绘图必须在主线程,Worker用于路径预处理以避免卡顿;因高频率采样需实时计算速度、加速度、贝塞尔拟合等,Worker后台运算保障UI流畅,再将结构化段数据传回主线程绘制。

Canvas 本身不支持在 Worker 中直接绘图(getContext 只能在主线程调用),但 Worker 可以高效承担**路径预处理、点序列优化与压力建模计算**等纯逻辑任务,再将精炼后的坐标+样式参数传回主线程绘制。这是实现高精度手写字迹的关键分工策略。
为什么必须用 Worker 处理路径采样?
手写轨迹原始采样频率高(尤其触摸屏可达 120Hz+),大量点实时计算速度、加速度、贝塞尔拟合、飞白判定等,会阻塞主线程,导致卡顿或丢点。Worker 在后台线程运行,不干扰 UI 渲染和事件响应,特别适合以下任务:
- 对原始 pointermove 点列做降噪与重采样(如 Douglas-Peucker 算法压缩冗余点)
- 逐段计算瞬时速度、加速度、曲率,生成每段的 width/alpha/blur 参数
- 将折线路径转为三阶贝塞尔控制点序列(需前、中、后三点插值)
- 预判“顿笔”“提笔”“飞白”区间,标记语义标签供渲染层差异化处理
Worker 与主线程协作流程
核心是「采集—分发—计算—合成」四步闭环,不跨线程操作 canvas:
-
主线程:监听
pointerdown/move/up,缓存原始点({x, y, t, pressure?}),当点数达阈值(如 20–30 点)或pointerup触发时,用worker.postMessage()发送点数组 -
Worker:接收点数组,执行路径平滑、速度映射、贝塞尔拟合、飞白检测;输出结构化段数据,例如:
[{x1,y1,x2,y2,x3,y3,width,alpha,hasFlyback:true}, ...] -
主线程:收到结果后,遍历段数据,用
bezierCurveTo或quadraticCurveTo绘制,并动态设置lineWidth、globalAlpha、createRadialGradient - 为保证视觉连续性,最后一段未提交的点可暂存,与下一批合并处理,避免断笔
关键实现细节
Worker 无法访问 DOM 和 canvas,但能高效运算。需注意:
将 Markdown 文章转换为微信公众号兼容的纯 HTML 格式。当用户要求"转成微信格式"、"生成公众号文章 HTML"、"排版到微信草稿箱"、"微信粘贴格式"时触发。输出带内联样式的纯 HTML,微信编辑器可直接渲染,无需额外适配。支持标题、段落、列表、粗体、行内代码、表格、引用块、代码块、配图的完整转换。
立即学习“前端免费学习笔记(深入)”;
- 传递数据用
Transferable(如postMessage(points, [points.buffer]))减少拷贝开销 - Worker 内不依赖
Date.now(),统一用传入点中的timestamp做差分计算,避免时钟漂移 - 压力 fallback:若
event.pressure不可用,Worker 可仅基于Δt和Δd推算等效 pressure,公式如:effPressure = Math.min(1, Math.max(0.1, 1.2 - speed * 0.05)) - 移动端需在主线程调用
event.preventDefault()阻止滚动,Worker 不参与事件处理
一个轻量 Worker 示例(path-processor.js)
只需导出纯函数逻辑,无需 DOM 操作:
self.onmessage = function(e) {
const points = e.data;
const segments = [];
for (let i = 1; i < points.length - 1; i++) {
const p0 = points[i-1], p1 = points[i], p2 = points[i+1];
const dt = p1.t - p0.t;
const dist = Math.hypot(p1.x - p0.x, p1.y - p0.y);
const speed = dt > 0 ? dist / dt : 0;
const width = Math.max(0.8, 6 - speed * 0.4);
const alpha = 0.7 + Math.sin(i * 0.3) * 0.15; // 轻微墨色波动
const isFlyback = speed > 8 && i === points.length - 2;
segments.push({
x1: p0.x, y1: p0.y,
x2: p1.x, y2: p1.y,
x3: p2.x, y3: p2.y,
width,
alpha,
hasFlyback: isFlyback
});
}
self.postMessage(segments);
};主线程创建 Worker 并通信即可,无需额外依赖。这种解耦让手写体验更稳、更准、更接近真实毛笔的呼吸感。


















