react-native-skia icon indicating copy to clipboard operation
react-native-skia copied to clipboard

Using Group opacity with Mask child also applies the opacity on other outside nodes

Open albinhubsch opened this issue 5 months ago • 6 comments

Description

When using a <Mask> inside a <Group> and then also applying opacity to the group it somehow applies that same opacity even to nodes not part of the group that comes after the group. This happens only if I place a Mask inside the group.

My use case is a bit more advanced than the repro. I have some charts that I apply some gradient opacity effects through a mask and then control the chart visibility through the group opacity. This has previously worked, but seems to have stopped working in recent versions. Not sure which one.

I can think of some workarounds, but I think the use case is strong enough to have it behave like expected, not applying the opacity to other nodes than the group.

https://github.com/user-attachments/assets/85e048b7-9211-4760-9143-7ad0d7d6da58

React Native Skia Version

2.2.11

React Native Version

0.79.6

Using New Architecture

  • [x] Enabled

Steps to Reproduce

The following is a simple repro. I've also attached a git repo that can be cloned to test this.

import { Canvas, Group, Mask, Rect } from "@shopify/react-native-skia"
import { Button, StyleSheet, View } from "react-native"
import { useSharedValue } from "react-native-reanimated"

export default function App() {
  const opacity1 = useSharedValue(1)
  const opacity2 = useSharedValue(1)

  return (
    <View style={styles.container}>
      <Canvas style={{ height: 250, width: 250, borderWidth: 1, borderColor: "red", marginBottom: 40 }}>
        <Group opacity={opacity1}>
          <Mask mask={<Rect x={0} y={0} width={100} height={100} color="white" />} mode="luminance">
            <Rect x={0} y={0} width={100} height={100} color="red" />
          </Mask>
        </Group>

        <Rect x={100} y={0} width={100} height={100} color="orange" />

        <Group opacity={opacity2}>
          <Rect x={0} y={100} width={100} height={100} color="blue" />
        </Group>
      </Canvas>

      <View style={{ flexDirection: "row", gap: 10 }}>
        <Button title="Square 1 Opacity = 1" onPress={() => opacity1.set(1)} />
        <Button title="Square 1 Opacity = 0" onPress={() => opacity1.set(0)} />
      </View>

      <View style={{ flexDirection: "row", gap: 10 }}>
        <Button title="Square 2 Opacity = 1" onPress={() => opacity2.set(1)} />
        <Button title="Square 2 Opacity = 0" onPress={() => opacity2.set(0)} />
      </View>
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center",
  },
})

Snack, Code Example, Screenshot, or Link to Repository

https://github.com/albinhubsch/skia-group-opacity

albinhubsch avatar Sep 08 '25 12:09 albinhubsch

@albinhubsch good to see you here 👋 While I haven't tried it yet, I could see how this bug could happen, the issue is that the opacity of the group is used in whatever is inmask=? It sounds fair to add tests for it and fix it.

wcandillon avatar Sep 12 '25 19:09 wcandillon

I was just looking at another <Mask /> bug report (3254), maybe a good way to work on these bugs is to build the expected result on figma (we match the semantic there, or at least try to) and we use it in our test suite.

wcandillon avatar Sep 12 '25 19:09 wcandillon

👋 @wcandillon ,,great to hear! I do like the sound of trying to get it into figma. Our designers figma files are usually a great source of inspiration for me on how to layer and implement stuff in rnskia, so I guess matching the semantics and catch it early in tests sounds great!

Let me know if I can assist in any way.

albinhubsch avatar Sep 12 '25 20:09 albinhubsch

haha sry about that close, reopen 👆 cmd+enter 😅

albinhubsch avatar Sep 12 '25 20:09 albinhubsch

I'm facing the same bug. It happens even when no mask is used, for example, when wrapping <Text /> inside <Group opacity={0.85}>.

RodolfoSilva avatar Oct 11 '25 10:10 RodolfoSilva

I am also seeing this same problem where opacity is being applied. The opacity is applying to all siblings and even subsequent draws from higher in the react tree. This is just trying to wrap a <RoundedRect><Paint /></RoundedRect> in a single Groupcomponent. If I don't render thisRoundedRect` component, then the fades of all other draws is not impacted.

JakeAlewel avatar Nov 11 '25 08:11 JakeAlewel