跳到主要内容

HTML5 Canvas 隔离组:组不透明度与混合模式

默认情况下,Konva.Group 只负责组织其子节点。绘制组时,每个子节点都会直接绘制到图层 Canvas 上:

  • 组的 opacity 会乘到每个子节点上,因此你可以看到子节点的重叠区域。
  • 设置了 globalCompositeOperation(例如 multiplydestination-out)的子节点,会与图层上已绘制的所有内容混合,而不仅仅是与其兄弟节点混合。

设置 isolated: true 可以将组作为一个图像绘制。隔离组会先将子节点绘制到一个单独的透明 Canvas 上,然后一步将结果放到图层上,并只应用一次自身的 opacityglobalCompositeOperation。其效果类似于 CSS 中的 isolation: isolate 以及 SVG <g> 上的不透明度。

const group = new Konva.Group({
isolated: true,
opacity: 0.6,
});

启用隔离后:

  • 组的不透明度应用于整个图像。 重叠的子节点不会互相透出。
  • 组的混合模式应用于整个图像。 group.globalCompositeOperation('multiply') 会将合成后的组与其下方的内容混合。
  • 子节点的混合模式限制在组内。 设置了 destination-out 的子节点只会擦除组自身的内容,而不会擦除图层的其余部分。

隔离功能需要 Konva 10.7.0 或更高版本。有关完整的属性列表,请参阅 Konva.Group 文档

操作说明:将两个组拖到条纹背景上。左侧是普通组:其图形会互相透出,设置了 destination-out 的黑色圆形还会部分擦除条纹。右侧是隔离组:它作为一个整体图像变透明,孔洞中显示的是其后面的条纹。

import Konva from 'konva';

const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});

const layer = new Konva.Layer();
stage.add(layer);

// stripes, to see what the groups let through
for (let x = 0; x < stage.width(); x += 40) {
layer.add(
new Konva.Rect({
x: x,
width: 20,
height: stage.height(),
fill: '#8ecae6',
})
);
}

function createGroup(x, isolated) {
const group = new Konva.Group({
x: x,
y: 40,
opacity: 0.6,
isolated: isolated,
draggable: true,
});
group.add(
new Konva.Rect({
width: 120,
height: 120,
fill: '#e63946',
})
);
group.add(
new Konva.Circle({
x: 120,
y: 120,
radius: 70,
fill: '#1d3557',
})
);
// erases everything under it: the whole layer, or only the isolated group
group.add(
new Konva.Circle({
x: 60,
y: 60,
radius: 30,
fill: 'black',
globalCompositeOperation: 'destination-out',
})
);
group.add(
new Konva.Text({
y: 205,
text: isolated ? 'isolated: true' : 'default group',
fontSize: 18,
})
);
layer.add(group);
}

createGroup(40, false);
createGroup(300, true);

隔离还是缓存?

group.cache() 也会将组作为一个图像绘制,但它生成的是快照。更改子节点后,必须再次调用 cache()。隔离组则保持实时更新:更改、动画化或拖动任意子节点,下一次绘制就会显示结果。

代价是每次绘制图层时都要经过一个缓冲 Canvas。Konva 会让该缓冲区只与组的可见部分一样大,在多个隔离组之间复用它,并在不再使用时将其释放。对于会变化的内容,请使用隔离。对于很少变化的复杂组,或者需要使用滤镜时,请使用 cache()

注意事项

  • Konva 根据组的边界确定缓冲区的大小。如果自定义图形在其 widthheight 之外绘制内容,请使用 selfRectFunc 描述绘制区域,否则这部分内容可能会被裁剪。
  • 隔离只改变组的外观。命中检测保持不变:被兄弟节点擦除的图形仍然会接收事件。
  • 缓存组会保留其快照。如果对隔离组调用 cache(),在调用 clearCache() 之前,将一直绘制该快照。