如何让线宽不管怎么缩放画布,都保持渲染为 1px

513 阅读4分钟

大家好,我是前端西瓜哥。

本文讲解如何实现画布缩放后线宽(strokeWIdth)保持为 1px。

我们需要的效果

画布缩放,就是用滚轮放大或缩小整个画布,会导致画布中的图形变大会变小,虽然我们没有改图形的属性值。

实现上是 修改图形树根节点的 matrix 矩阵,这个其实就是视图矩阵,作用是把场景坐标转换为视口坐标。

画布缩放,会让图形的线宽在渲染上也发生变化。

比如图形的线宽为 1px,如果画布放大为 2 倍,线宽也会渲染为原来的两倍。如果画布缩小为原来的一半,线宽也会跟随变成 0.5px(抗锯齿,变成颜色比较浅的线)。

图片

我们希望无论怎么缩放画布,线宽永远保持不变,为 1px。

为什么需要这个效果?

  1. 绘制选中图形的高亮轮廓线。做法通常是复制一份图形,fill 丢掉,stroke 的颜色改成高亮色,然后线宽不能随画布缩放改变

  2. 可用于类 CAD 的绘制线框图形的编辑器。这类编辑器注重的是图形的形状,图形是基于线条,也希望画布缩放前后线宽不变。

SVG 下的做法

SVG 就非常方便,我们只要给图形节点的样式设置为:

vector-effect: non-scaling-stroke;

就可以了。

我给矩形添加属性 vector-effect="non-scaling-stroke",圆形保持不变,对比看看效果。

图片

也可以写成全局的。

svg * {
  vector-effect: non-scaling-stroke;
}

strokeWidth 不强制要求为 1px,可以设置为任意你希望的像素值。

图形库

SVG 在图形很多的时候性能不太好,我们选择基于 Canvas 实现的图形库。

有些图形库是支持类似效果的。

比如性能非常强的图形库 Pixijs7,可以这样写:

const rect = new Graphics()
  .lineStyle(10xffffffundefinedundefinedtrue)
  .drawRect(2020200100);

lineStyle 方法的最后一个参数 native 如果为 true,就会使用 LINES 模式而不是 TRIANGLE_STRIP 模式。

同样给矩形应用效果,和没有应用效果的圆形对比,效果如下:

图片

遗憾的是,strokeWidth 不管设置为多少,渲染的都是 1px,这是 WebGL 特性带来的效果。

另外,Pixi.js 8 目前不支持这个特性,因为还没把 pixi.js 7 的全部功能都迁移完成。大概是因为要兼容 WebGPU 的原因。

paperjs 可以这样设置:

paper.project.currentStyle.strokeScaling = false;

其它都就不知道了,读者可自行查阅官方文档。

数据层修改

上面是从渲染器层面着手的,SVG 支持这种特性,所以我们才能做到线宽不变。

如果你的渲染器不支持这个特性,只能从数据层修改了。

当画布缩放值 zoom 发生改变时,要修改图形的 strokeWidth 为真实值的 1 / zoom 倍

假设图形 strokeWidth 为 1,zoom 为 2,strokeWidth 就会渲染为 2。你说,诶,我把图形 stokeWidth值设置为 0.5,那放大 2 倍,渲染出来的线宽还是 1,这样也实现了渲染 strokeWidth 不变的效果。

这样就保持 strokeWidth 不变了,缺点是会破坏图形的缓存,需要重新做三角化。

不适用于有大量图形的场景。

还有一种就是计算图形的所有场景坐标系点,转换为视口坐标系的点,然后放到在视口坐标容器上,因为 zoom 永远为 1,此时 strokeWidth 设置为 1px 即可。

结尾

我是前端西瓜哥,关注我,学习更多图形化知识。

相关阅读,

一起学 WebGL:图元的类型