software-mansion / software-mansion/react-native-svg
Android Mask not working
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 8k
- Forks
- 1.2k
- Avg merge
- 10d 9h
- Merged PRs (30d)
- 2
Description
Description
Here is my component that renders this preloader.
Figma screenshots:
It works perfectly on iOS, but the mask doesn't work on Android.
iOS screenshots:
Android screenshots:
import React, { useEffect } from 'react'
import Svg, {
Circle,
Defs,
G,
Path,
Mask,
LinearGradient,
Stop,
} from 'react-native-svg'
import Animated, {
Easing,
useAnimatedStyle,
useSharedValue,
withRepeat,
withTiming,
} from 'react-native-reanimated'
import { LoaderProps } from '@/shared/ui/Loader/model'
import { rem } from '@/lib'
export function Loader({ size = 56, style }: LoaderProps) {
const outerRadius = rem(size)
const segments = rem(size)
const id = Date.now().toString()
const segmentsArray = []
const gradientsArray = []
for (let i = 0; i < segments; i++) {
const startAngle = (i / segments) * 360 - 90
const endAngle = ((i + 1) / segments) * 360 - 90
const largeArc = endAngle - startAngle >= 180 ? 1 : 0
const x1 =
outerRadius + outerRadius * Math.cos((Math.PI * startAngle) / 180)
const y1 =
outerRadius + outerRadius * Math.sin((Math.PI * startAngle) / 180)
const x2 =
outerRadius + outerRadius * Math.cos((Math.PI * endAngle) / 180)
const y2 =
outerRadius + outerRadius * Math.sin((Math.PI * endAngle) / 180)
segmentsArray.push(
<Path
key={i}
d={`M ${outerRadius} ${outerRadius} L ${x1} ${y1} A ${outerRadius} ${outerRadius} 0 ${largeArc} 1 ${x2} ${y2} Z`}
stroke="none"
fill={`url(#${id}-grad-${i})`}
/>,
)
const fromOpacity = i / segments
const toOpacity = (i + 1) / segments
gradientsArray.push(
<LinearGradient
key={i}
id={`${id}-grad-${i}`}
x1="0%"
y1="0%"
x2="0%"
y2="100%">
<Stop
offset="0%"
stopColor="black"
stopOpacity={fromOpacity}
/>
<Stop
offset="100%"
stopColor="black"
stopOpacity={toOpacity}
/>
</LinearGradient>,
)
}
const rotation = useSharedValue(0)
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ rotate: `${rotation.value}deg` }],
}))
useEffect(() => {
rotation.value = withRepeat(
withTiming(360, { duration: 1000, easing: Easing.linear }),
-1,
false,
)
})
return (
<Animated.View
style={[
style,
{
width: rem(size),
height: rem(size),
},
animatedStyle,
]}>
<Svg
width={rem(size)}
height={rem(size)}
viewBox={`0 0 ${rem(size)} ${rem(size)}`}>
<Defs>
{gradientsArray}
<Mask id={id}>
<Circle
cx={rem(size) / 2}
cy={rem(size) / 2}
r={outerRadius}
fill="white"
/>
<Circle
cx={rem(size) / 2}
cy={rem(size) / 2}
r={rem(size) / 2 - rem(size) * 0.9090909}
fill="black"
/>
</Mask>
</Defs>
<G mask={`url(#${id})`}>{segmentsArray}</G>
</Svg>
</Animated.View>
)
}
Steps to reproduce
- Just add in App layout
Snack or a link to a repository
- Nope
SVG version
15.12.1
React Native version
0.81.5
Platforms
Android, iOS
JavaScript runtime
None
Workflow
Expo Go
Architecture
Fabric (New Architecture)
Build type
Debug app & dev bundle
Device
Android emulator
Device model
Pixel 9 Pro XL
Acknowledgements
Yes
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the provided Loader component and reproduce it in Expo Go on the Android emulator using React Native 0.81.5 and react-native-svg 15.12.1. Compare the mask output with iOS and inspect the Android SVG mask behavior; done means the preloader renders the intended masked segments consistently on Android.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react-native, typescript
- Domain
- frontend, mobile
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100