How to apply transparency for several shapes at once?
Is it possible to use opacity for several shapes at the same time?
You can use the opacity
attribute to change the alpha channel of any Konva
node. Due to how canvas works, all shapes have their own independent opacity values.
That means if you have a group with several shapes inside and that group has group.opacity(0.5)
, it will look exactly the same as if each shape inside the group has shape.opacity(0.5)
and the group has group.opacity(1)
. This means you will see overlapping areas of those shapes.
What if we don't want to see overlapping areas of transparent shapes?
There is a way to fix such default behavior. You just need to cache the group with group.cache()
. Caching the group will convert it into a bitmap and draw it into an external canvas. On the next draw call, Konva
will use that resulted canvas to draw the whole group with opacity applied to the whole image.
So while Konva
is making a bitmap cache for such group, it will draw internal shapes ignoring transparency of the group.
Remember that if a group is cached, it has some limitations of cached nodes. If you are doing any internal changes (like changing shapes attributes), you have to recache the group. This is an expensive operation, so it is not recommended to do it frequently like inside animations or on every mousemove.
In the demo below, on the left you see the default behavior, on the right you see the fixed behavior with a cached group.
Try dragging both groups to see the difference in how transparency is applied. The left group shows the default behavior with visible overlapping areas, while the right group shows the cached behavior where the entire group is treated as a single transparent unit.
import Konva from 'konva';
const width = window.innerWidth;
const height = window.innerHeight;
const stage = new Konva.Stage({
container: 'container',
width: width,
height: height,
});
const layer = new Konva.Layer();
stage.add(layer);
const group1 = new Konva.Group({
opacity: 0.5,
x: 50,
y: 50,
draggable: true,
});
group1.add(
new Konva.Rect({
width: 100,
height: 100,
fill: 'red',
})
);
group1.add(
new Konva.Circle({
x: 100,
y: 100,
radius: 70,
fill: 'green',
})
);
layer.add(group1);
const group2 = group1.clone({ x: 250 });
layer.add(group2);
group2.cache();
import { Stage, Layer, Group, Rect, Circle } from 'react-konva';
import { useEffect, useRef } from 'react';
const App = () => {
const group2Ref = useRef(null);
useEffect(() => {
if (group2Ref.current) {
group2Ref.current.cache();
}
}, []);
const sharedGroupProps = {
opacity: 0.5,
draggable: true,
};
const renderGroup = (x, ref = null) => (
<Group {...sharedGroupProps} x={x} y={50} ref={ref}>
<Rect width={100} height={100} fill="red" />
<Circle x={100} y={100} radius={70} fill="green" />
</Group>
);
return (
<Stage width={window.innerWidth} height={400}>
<Layer>
{}
{renderGroup(50)}
{}
{renderGroup(250, group2Ref)}
</Layer>
</Stage>
);
};
export default App;
<template>
<v-stage :config="stageConfig">
<v-layer>
<v-group :config="{ ...groupConfig, x: 50 }">
<v-rect :config="rectConfig" />
<v-circle :config="circleConfig" />
</v-group>
<v-group ref="group2" :config="{ ...groupConfig, x: 250 }">
<v-rect :config="rectConfig" />
<v-circle :config="circleConfig" />
</v-group>
</v-layer>
</v-stage>
</template>
<script setup>
import { ref, onMounted } from 'vue';
const group2 = ref(null);
const stageConfig = {
width: window.innerWidth,
height: 400,
};
const groupConfig = {
opacity: 0.5,
y: 50,
draggable: true,
};
const rectConfig = {
width: 100,
height: 100,
fill: 'red',
};
const circleConfig = {
x: 100,
y: 100,
radius: 70,
fill: 'green',
};
onMounted(() => {
if (group2.value) {
group2.value.getNode().cache();
}
});
</script>