填充与描边的顺序
Canvas 画一个有填充也有描边的图形时,两者必有先后。
Konva 用 fillAfterStrokeEnabled 控制这个顺序。
new Konva.Rect({
fill: 'tomato',
stroke: 'rgba(0, 0, 255, 0.5)',
strokeWidth: 20,
fillAfterStrokeEnabled: true, // 默认 false
});
默认值是 false,也就是先填充、后描边,描边压在填充之上。
什么时候看得出区别
这是关键的一节,也解释了为什么大多数人从没注意过这个属性。
Canvas 的描边是以路径为中心线的:strokeWidth: 20 意味着
路径两侧各占 10px,其中向内的 10px 会盖住填充区域。
两种顺序的差别只在这条重叠带上,所以:
描边半透明时有区别。 先填充后描边,描边压在填充上, 两层颜色叠加,看到的是"蓝色混橙色"; 先描边后填充,填充盖住了描边向内的那一半, 只剩外侧一半与背景混色。
描边很粗时有区别。 重叠带越宽,差异越明显。
描边不透明且很细时完全没有区别。 不透明意味着叠加与否结果一样, 细意味着重叠带只有一两个像素。这两个条件覆盖了绝大多数实际用法—— 所以这个属性大部分时候确实无关紧要。
上面的演示里,上排是半透明的 20px 粗边(差别一眼可见), 下排是不透明的 2px 细边(两边完全相同)。
实测把探针放在重叠带上(距离边界向内 5px),采到的颜色是:
| 描边 | fillAfterStrokeEnabled: false | fillAfterStrokeEnabled: true |
|---|---|---|
rgba(0,0,255,0.5),20px | 127,49,163(蓝叠在橙上) | 255,99,71(纯填充色) |
#003366,2px | 255,99,71 | 255,99,71(完全相同) |
第一行说明重叠带确实被描边染了色;第二行说明细的不透明描边下 两种顺序采到的是同一个值——这不是"差别很小",是没有差别。
常见问题
为什么我的半透明描边看起来比设定的深?
因为默认顺序下描边压在填充上,描边向内的那一半与填充色叠加, 向外的一半与背景叠加——同一条描边呈现出两种颜色, 内侧看起来更深。
想让整条描边颜色均匀,设 fillAfterStrokeEnabled: true,
填充会盖住内侧那一半,剩下的外侧部分只与背景混色。
粗描边让图形看起来变小了?
也是中心线的缘故:描边向内吃掉了 strokeWidth / 2 的填充区域。
一个 100×100、描边 20px 的矩形,可见的填充区只有 80×80。
Canvas 没有 CSS 那样的 box-sizing,也没有"外描边"选项。
要让描边完全在外侧,三个办法:
- 把图形放大一圈:尺寸加上
strokeWidth,位置减去strokeWidth / 2。 - 叠两个图形:底下一个只有描边、尺寸大一圈,上面一个只有填充。
- 用
sceneFunc自己画:完全控制路径和绘制顺序, 见自定义图形。
第一种最简单,但图形是 Konva.Path 或复杂形状时不好算;
第二种通用但节点数翻倍。
这个属性影响性能吗?
不影响。两种顺序都是 fill() 和 stroke() 各调用一次,
只是先后不同。
真正影响性能的是描边本身——尤其是配合 shadowBlur
或非整数的 strokeWidth 时,见优化描边。
缩放时描边跟着变粗,和这个有关吗?
没关系,那是另一个属性:strokeScaleEnabled。
shape.strokeScaleEnabled(false); // 描边宽度不随 scale 变化
两者会叠加影响视觉效果——放大之后描边变粗,重叠带随之变宽, 原本看不出差别的顺序问题可能就显现了。 见忽略描边。
与其他方案的取舍
fillAfterStrokeEnabled 只解决"两层谁在上面"这一个问题。
需求稍微复杂一点,它就不够了。
只要描边颜色均匀 → fillAfterStrokeEnabled: true,
一个属性搞定,零成本。
要真正的外描边(描边完全在图形外,不侵占填充) → 这个属性做不到,因为它改变的是绘制顺序不是路径几何。 用上面「粗描边」那一节的三个办法。
要内外双色描边(像文字描边那样内白外黑) →
必须叠多个图形,或者在 sceneFunc 里连续 stroke() 多次、
每次改 lineWidth 和 strokeStyle。后者节点数不增加,
但要自己处理命中区域。
只是想要一个边框效果 → 如果图形是矩形,
考虑用两个 Konva.Rect 而不是描边——
可以分别控制圆角、可以做渐变边框,比调描边参数灵活得多,
代价是多一个节点。
文字描边 → Konva.Text 同样支持 fillAfterStrokeEnabled,
而且这是它最有用的场景:文字笔画细,描边压在填充上会明显"吃掉"笔画,
让字看起来更瘦甚至糊成一团。给描边文字设 fillAfterStrokeEnabled: true
通常能明显改善可读性。