如何将粒子特效限制在 SVG Konva.Path 的不规则路径内?
数据小陈
结论
将粒子放入带有 clipFunc 的 Konva.Group,并让该组使用与 room 相同的变换。裁剪路径和粒子位置都按组的局部坐标计算,粒子便只会显示在 SVG 路径内。
room 的缩放为 scaleX: 3.8, scaleY: 3.8,而现有的 fireGroup 没有相同的缩放。粒子又是根据 getClientRect() 返回的画布坐标生成的。裁剪路径、粒子和房间如果使用不同的坐标系,即使添加了裁剪函数,边界也可能对不齐。
修改 startFire
现代浏览器的 Path2D 可以读取 SVG path 数据。Konva 的 clipFunc 支持返回 [Path2D, clipRule],因此可以直接用原 SVG 路径裁剪组内内容:
function startFire(room) {
if (room.fireAnimation) {
room.fireAnimation.stop();
room.fireGroup.destroy();
room.fireAnimation = null;
room.fireGroup = null;
}
const rect = room.getSelfRect();
const clipPath = new Path2D(room.data());
const layer = room.getLayer();
const fireGroup = new Konva.Group({
x: room.x(),
y: room.y(),
scaleX: room.scaleX(),
scaleY: room.scaleY(),
rotation: room.rotation(),
offsetX: room.offsetX(),
offsetY: room.offsetY(),
clipFunc: () => [clipPath, 'nonzero'],
});
layer.add(fireGroup);
const centerX = rect.x + rect.width / 2;
const centerY = rect.y + rect.height / 2;
const anim = new Konva.Animation(() => {
if (Math.random() > 0.1) {
fireGroup.add(new Konva.Circle({
x: centerX + Math.random() * 20 - 10,
y: centerY + Math.random() * 20 - 10,
radius: Math.random() * 3 + 1,
fill: Math.random() > 0.5 ? 'orange' : 'red',
opacity: 0.8,
}));
}
fireGroup.getChildren().forEach((particle) => {
particle.y(particle.y() - 1.5);
particle.opacity(particle.opacity() - 0.015);
if (particle.opacity() <= 0) {
particle.destroy();
}
});
}, layer);
anim.start();
room.fireAnimation = anim;
room.fireGroup = fireGroup;
}
getSelfRect() 返回路径自身的局部边界,所以中心点也使用组的局部坐标。组复制了路径的位置、缩放、旋转和偏移。如果路径还应用了倾斜等其他变换,粒子组也需要保持相同变换。
Konva 文档说明,clipFunc 可以通过返回 Path2D 和填充规则实现复杂裁剪,Konva.Path.data() 可以读取 SVG 路径数据。clipFunc 文档, Konva.Path 文档
注意事项
- 如果缩放后的粒子显得过大或移动过快,可以调小
radius和每帧的纵向位移。这些数值也会随fireGroup缩放。 - 如果 SVG 路径包含镂空区域,请检查路径的填充规则。需要偶奇规则时,可以将
'nonzero'改为'evenodd'。 globalCompositeOperation控制画布绘制的合成方式,效果取决于绘制顺序和画布状态。它不能按指定的几何路径裁剪内容;在这个场景中,使用组的clipFunc更直接。Path2D是否支持 SVG 数据取决于运行环境。目标浏览器若不支持,可以改用支持该功能的环境,或采用单独的 Canvas 遮罩方案。
备注:内容仅供参考。
