react-native-gradient-mask 0.0.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/.eslintrc.js +5 -0
  2. package/README.ja.md +335 -0
  3. package/README.md +335 -0
  4. package/README.zh-TW.md +335 -0
  5. package/android/build.gradle +43 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/java/expo/modules/gradientmask/GradientMaskModule.kt +33 -0
  8. package/android/src/main/java/expo/modules/gradientmask/GradientMaskView.kt +198 -0
  9. package/build/AnimatedGradientMaskView.d.ts +36 -0
  10. package/build/AnimatedGradientMaskView.d.ts.map +1 -0
  11. package/build/AnimatedGradientMaskView.js +37 -0
  12. package/build/AnimatedGradientMaskView.js.map +1 -0
  13. package/build/AnimatedGradientMaskView.web.d.ts +17 -0
  14. package/build/AnimatedGradientMaskView.web.d.ts.map +1 -0
  15. package/build/AnimatedGradientMaskView.web.js +100 -0
  16. package/build/AnimatedGradientMaskView.web.js.map +1 -0
  17. package/build/GradientMask.types.d.ts +38 -0
  18. package/build/GradientMask.types.d.ts.map +1 -0
  19. package/build/GradientMask.types.js +2 -0
  20. package/build/GradientMask.types.js.map +1 -0
  21. package/build/GradientMaskModule.d.ts +3 -0
  22. package/build/GradientMaskModule.d.ts.map +1 -0
  23. package/build/GradientMaskModule.js +4 -0
  24. package/build/GradientMaskModule.js.map +1 -0
  25. package/build/GradientMaskModule.web.d.ts +3 -0
  26. package/build/GradientMaskModule.web.d.ts.map +1 -0
  27. package/build/GradientMaskModule.web.js +3 -0
  28. package/build/GradientMaskModule.web.js.map +1 -0
  29. package/build/GradientMaskView.d.ts +4 -0
  30. package/build/GradientMaskView.d.ts.map +1 -0
  31. package/build/GradientMaskView.js +7 -0
  32. package/build/GradientMaskView.js.map +1 -0
  33. package/build/GradientMaskView.web.d.ts +8 -0
  34. package/build/GradientMaskView.web.d.ts.map +1 -0
  35. package/build/GradientMaskView.web.js +99 -0
  36. package/build/GradientMaskView.web.js.map +1 -0
  37. package/build/index.d.ts +6 -0
  38. package/build/index.d.ts.map +1 -0
  39. package/build/index.js +7 -0
  40. package/build/index.js.map +1 -0
  41. package/expo-module.config.json +9 -0
  42. package/images/android.mp4 +0 -0
  43. package/images/android.png +0 -0
  44. package/images/demo.mov +0 -0
  45. package/images/ios Demo Video.webm +0 -0
  46. package/images/ios.png +0 -0
  47. package/ios/GradientMask.podspec +29 -0
  48. package/ios/GradientMaskModule.swift +29 -0
  49. package/ios/GradientMaskView.swift +133 -0
  50. package/package.json +83 -0
  51. package/src/AnimatedGradientMaskView.tsx +60 -0
  52. package/src/AnimatedGradientMaskView.web.tsx +149 -0
  53. package/src/GradientMask.types.ts +43 -0
  54. package/src/GradientMaskModule.ts +4 -0
  55. package/src/GradientMaskModule.web.ts +2 -0
  56. package/src/GradientMaskView.tsx +11 -0
  57. package/src/GradientMaskView.web.tsx +129 -0
  58. package/src/index.ts +7 -0
  59. package/tsconfig.json +9 -0
@@ -0,0 +1,17 @@
1
+ import * as React from 'react';
2
+ import type { SharedValue } from 'react-native-reanimated';
3
+ import { GradientMaskViewProps } from './GradientMask.types';
4
+ export type AnimatedGradientMaskViewProps = Omit<GradientMaskViewProps, 'maskOpacity'> & {
5
+ /**
6
+ * Mask effect intensity (0-1) as a Reanimated SharedValue
7
+ * 0 = no gradient effect (content fully visible)
8
+ * 1 = full gradient effect
9
+ */
10
+ maskOpacity: SharedValue<number>;
11
+ };
12
+ /**
13
+ * AnimatedGradientMaskView - Web 實作
14
+ * 使用 CSS mask-image 搭配 useAnimatedReaction 監聽 SharedValue 變化
15
+ */
16
+ export default function AnimatedGradientMaskView(props: AnimatedGradientMaskViewProps): React.JSX.Element;
17
+ //# sourceMappingURL=AnimatedGradientMaskView.web.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnimatedGradientMaskView.web.d.ts","sourceRoot":"","sources":["../src/AnimatedGradientMaskView.web.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE3D,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,MAAM,MAAM,6BAA6B,GAAG,IAAI,CAC9C,qBAAqB,EACrB,aAAa,CACd,GAAG;IACF;;;;OAIG;IACH,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CAClC,CAAC;AAgFF;;;GAGG;AACH,MAAM,CAAC,OAAO,UAAU,wBAAwB,CAAC,KAAK,EAAE,6BAA6B,qBAyCpF"}
@@ -0,0 +1,100 @@
1
+ import * as React from 'react';
2
+ import { View, StyleSheet } from 'react-native';
3
+ import { useAnimatedReaction, runOnJS } from 'react-native-reanimated';
4
+ /**
5
+ * 將 processColor 處理過的顏色值轉回 rgba 字串
6
+ */
7
+ function colorToRgba(color) {
8
+ if (color === null || color === undefined) {
9
+ return 'rgba(0, 0, 0, 0)';
10
+ }
11
+ const intValue = color >>> 0;
12
+ const a = ((intValue >> 24) & 0xff) / 255;
13
+ const r = (intValue >> 16) & 0xff;
14
+ const g = (intValue >> 8) & 0xff;
15
+ const b = intValue & 0xff;
16
+ return `rgba(${r}, ${g}, ${b}, ${a})`;
17
+ }
18
+ /**
19
+ * 根據 direction 取得 CSS linear-gradient 的方向
20
+ */
21
+ function getGradientDirection(direction) {
22
+ switch (direction) {
23
+ case 'top':
24
+ return 'to bottom';
25
+ case 'bottom':
26
+ return 'to top';
27
+ case 'left':
28
+ return 'to right';
29
+ case 'right':
30
+ return 'to left';
31
+ default:
32
+ return 'to bottom';
33
+ }
34
+ }
35
+ /**
36
+ * 建立 CSS linear-gradient 字串
37
+ */
38
+ function buildGradientString(colors, locations, direction) {
39
+ const gradientDirection = getGradientDirection(direction);
40
+ const colorStops = colors.map((color, index) => {
41
+ const rgba = colorToRgba(color);
42
+ const location = locations[index] !== undefined ? locations[index] * 100 : (index / (colors.length - 1)) * 100;
43
+ return `${rgba} ${location}%`;
44
+ });
45
+ return `linear-gradient(${gradientDirection}, ${colorStops.join(', ')})`;
46
+ }
47
+ /**
48
+ * 根據 maskOpacity 調整顏色的 alpha 值
49
+ */
50
+ function adjustColorsForOpacity(colors, maskOpacity) {
51
+ if (maskOpacity >= 1)
52
+ return colors;
53
+ if (maskOpacity <= 0) {
54
+ return colors.map(() => 0xff000000);
55
+ }
56
+ return colors.map((color) => {
57
+ if (color === null || color === undefined)
58
+ return color;
59
+ const intValue = color >>> 0;
60
+ const a = ((intValue >> 24) & 0xff) / 255;
61
+ const r = (intValue >> 16) & 0xff;
62
+ const g = (intValue >> 8) & 0xff;
63
+ const b = intValue & 0xff;
64
+ const adjustedAlpha = a + (1 - a) * (1 - maskOpacity);
65
+ return ((Math.round(adjustedAlpha * 255) << 24) | (r << 16) | (g << 8) | b) >>> 0;
66
+ });
67
+ }
68
+ /**
69
+ * AnimatedGradientMaskView - Web 實作
70
+ * 使用 CSS mask-image 搭配 useAnimatedReaction 監聽 SharedValue 變化
71
+ */
72
+ export default function AnimatedGradientMaskView(props) {
73
+ const { colors, locations, direction = 'top', maskOpacity, style, children, } = props;
74
+ const [currentOpacity, setCurrentOpacity] = React.useState(maskOpacity.value);
75
+ // 監聽 SharedValue 變化並更新 state
76
+ useAnimatedReaction(() => maskOpacity.value, (value) => {
77
+ runOnJS(setCurrentOpacity)(value);
78
+ }, [maskOpacity]);
79
+ const maskStyle = React.useMemo(() => {
80
+ if (currentOpacity <= 0) {
81
+ return {};
82
+ }
83
+ const adjustedColors = adjustColorsForOpacity(colors, currentOpacity);
84
+ const gradientString = buildGradientString(adjustedColors, locations, direction);
85
+ return {
86
+ WebkitMaskImage: gradientString,
87
+ maskImage: gradientString,
88
+ transition: 'mask-image 0.1s ease-out, -webkit-mask-image 0.1s ease-out',
89
+ };
90
+ }, [colors, locations, direction, currentOpacity]);
91
+ return (<View style={[styles.container, style, maskStyle]}>
92
+ {children}
93
+ </View>);
94
+ }
95
+ const styles = StyleSheet.create({
96
+ container: {
97
+ overflow: 'hidden',
98
+ },
99
+ });
100
+ //# sourceMappingURL=AnimatedGradientMaskView.web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnimatedGradientMaskView.web.js","sourceRoot":"","sources":["../src/AnimatedGradientMaskView.web.tsx"],"names":[],"mappings":"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","sourcesContent":["import * as React from 'react';\nimport { View, StyleSheet } from 'react-native';\nimport { useAnimatedReaction, runOnJS } from 'react-native-reanimated';\nimport type { SharedValue } from 'react-native-reanimated';\n\nimport { GradientMaskViewProps } from './GradientMask.types';\n\nexport type AnimatedGradientMaskViewProps = Omit<\n GradientMaskViewProps,\n 'maskOpacity'\n> & {\n /**\n * Mask effect intensity (0-1) as a Reanimated SharedValue\n * 0 = no gradient effect (content fully visible)\n * 1 = full gradient effect\n */\n maskOpacity: SharedValue<number>;\n};\n\n/**\n * 將 processColor 處理過的顏色值轉回 rgba 字串\n */\nfunction colorToRgba(color: number | null): string {\n if (color === null || color === undefined) {\n return 'rgba(0, 0, 0, 0)';\n }\n\n const intValue = color >>> 0;\n const a = ((intValue >> 24) & 0xff) / 255;\n const r = (intValue >> 16) & 0xff;\n const g = (intValue >> 8) & 0xff;\n const b = intValue & 0xff;\n\n return `rgba(${r}, ${g}, ${b}, ${a})`;\n}\n\n/**\n * 根據 direction 取得 CSS linear-gradient 的方向\n */\nfunction getGradientDirection(direction: GradientMaskViewProps['direction']): string {\n switch (direction) {\n case 'top':\n return 'to bottom';\n case 'bottom':\n return 'to top';\n case 'left':\n return 'to right';\n case 'right':\n return 'to left';\n default:\n return 'to bottom';\n }\n}\n\n/**\n * 建立 CSS linear-gradient 字串\n */\nfunction buildGradientString(\n colors: (number | null)[],\n locations: number[],\n direction: GradientMaskViewProps['direction']\n): string {\n const gradientDirection = getGradientDirection(direction);\n\n const colorStops = colors.map((color, index) => {\n const rgba = colorToRgba(color);\n const location = locations[index] !== undefined ? locations[index] * 100 : (index / (colors.length - 1)) * 100;\n return `${rgba} ${location}%`;\n });\n\n return `linear-gradient(${gradientDirection}, ${colorStops.join(', ')})`;\n}\n\n/**\n * 根據 maskOpacity 調整顏色的 alpha 值\n */\nfunction adjustColorsForOpacity(\n colors: (number | null)[],\n maskOpacity: number\n): (number | null)[] {\n if (maskOpacity >= 1) return colors;\n if (maskOpacity <= 0) {\n return colors.map(() => 0xff000000);\n }\n\n return colors.map((color) => {\n if (color === null || color === undefined) return color;\n const intValue = color >>> 0;\n const a = ((intValue >> 24) & 0xff) / 255;\n const r = (intValue >> 16) & 0xff;\n const g = (intValue >> 8) & 0xff;\n const b = intValue & 0xff;\n const adjustedAlpha = a + (1 - a) * (1 - maskOpacity);\n return ((Math.round(adjustedAlpha * 255) << 24) | (r << 16) | (g << 8) | b) >>> 0;\n });\n}\n\n/**\n * AnimatedGradientMaskView - Web 實作\n * 使用 CSS mask-image 搭配 useAnimatedReaction 監聽 SharedValue 變化\n */\nexport default function AnimatedGradientMaskView(props: AnimatedGradientMaskViewProps) {\n const {\n colors,\n locations,\n direction = 'top',\n maskOpacity,\n style,\n children,\n } = props;\n\n const [currentOpacity, setCurrentOpacity] = React.useState(maskOpacity.value);\n\n // 監聽 SharedValue 變化並更新 state\n useAnimatedReaction(\n () => maskOpacity.value,\n (value) => {\n runOnJS(setCurrentOpacity)(value);\n },\n [maskOpacity]\n );\n\n const maskStyle = React.useMemo(() => {\n if (currentOpacity <= 0) {\n return {};\n }\n\n const adjustedColors = adjustColorsForOpacity(colors, currentOpacity);\n const gradientString = buildGradientString(adjustedColors, locations, direction);\n\n return {\n WebkitMaskImage: gradientString,\n maskImage: gradientString,\n transition: 'mask-image 0.1s ease-out, -webkit-mask-image 0.1s ease-out',\n };\n }, [colors, locations, direction, currentOpacity]);\n\n return (\n <View style={[styles.container, style, maskStyle as any]}>\n {children}\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n container: {\n overflow: 'hidden',\n },\n});\n"]}
@@ -0,0 +1,38 @@
1
+ import type { StyleProp, ViewStyle } from 'react-native';
2
+ export type GradientMaskViewProps = {
3
+ /**
4
+ * Gradient colors array (processed colors from processColor)
5
+ * Use alpha values to control opacity
6
+ * e.g., ['rgba(0,0,0,0)', 'rgba(0,0,0,1)'] = transparent to opaque
7
+ */
8
+ colors: (number | null)[];
9
+ /**
10
+ * Position of each color (0-1)
11
+ * e.g., [0, 0.3, 1] means first color at 0%, second at 30%, third at 100%
12
+ */
13
+ locations: number[];
14
+ /**
15
+ * Gradient direction
16
+ * 'top' = top transparent, bottom opaque
17
+ * 'bottom' = bottom transparent, top opaque
18
+ * 'left' = left transparent, right opaque
19
+ * 'right' = right transparent, left opaque
20
+ */
21
+ direction?: 'top' | 'bottom' | 'left' | 'right';
22
+ /**
23
+ * Mask effect intensity (0-1)
24
+ * 0 = no gradient effect (content fully visible)
25
+ * 1 = full gradient effect
26
+ * @default 1
27
+ */
28
+ maskOpacity?: number;
29
+ /**
30
+ * Style
31
+ */
32
+ style?: StyleProp<ViewStyle>;
33
+ /**
34
+ * Children
35
+ */
36
+ children?: React.ReactNode;
37
+ };
38
+ //# sourceMappingURL=GradientMask.types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GradientMask.types.d.ts","sourceRoot":"","sources":["../src/GradientMask.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzD,MAAM,MAAM,qBAAqB,GAAG;IAClC;;;;OAIG;IACH,MAAM,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;IAE1B;;;OAGG;IACH,SAAS,EAAE,MAAM,EAAE,CAAC;IAEpB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;IAEhD;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAE7B;;OAEG;IACH,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=GradientMask.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GradientMask.types.js","sourceRoot":"","sources":["../src/GradientMask.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { StyleProp, ViewStyle } from 'react-native';\n\nexport type GradientMaskViewProps = {\n /**\n * Gradient colors array (processed colors from processColor)\n * Use alpha values to control opacity\n * e.g., ['rgba(0,0,0,0)', 'rgba(0,0,0,1)'] = transparent to opaque\n */\n colors: (number | null)[];\n\n /**\n * Position of each color (0-1)\n * e.g., [0, 0.3, 1] means first color at 0%, second at 30%, third at 100%\n */\n locations: number[];\n\n /**\n * Gradient direction\n * 'top' = top transparent, bottom opaque\n * 'bottom' = bottom transparent, top opaque\n * 'left' = left transparent, right opaque\n * 'right' = right transparent, left opaque\n */\n direction?: 'top' | 'bottom' | 'left' | 'right';\n\n /**\n * Mask effect intensity (0-1)\n * 0 = no gradient effect (content fully visible)\n * 1 = full gradient effect\n * @default 1\n */\n maskOpacity?: number;\n\n /**\n * Style\n */\n style?: StyleProp<ViewStyle>;\n\n /**\n * Children\n */\n children?: React.ReactNode;\n};\n"]}
@@ -0,0 +1,3 @@
1
+ declare const _default: any;
2
+ export default _default;
3
+ //# sourceMappingURL=GradientMaskModule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GradientMaskModule.d.ts","sourceRoot":"","sources":["../src/GradientMaskModule.ts"],"names":[],"mappings":";AAGA,wBAAmD"}
@@ -0,0 +1,4 @@
1
+ import { requireNativeModule } from 'expo';
2
+ // We only use the View, no module functions needed
3
+ export default requireNativeModule('GradientMask');
4
+ //# sourceMappingURL=GradientMaskModule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GradientMaskModule.js","sourceRoot":"","sources":["../src/GradientMaskModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAE3C,mDAAmD;AACnD,eAAe,mBAAmB,CAAC,cAAc,CAAC,CAAC","sourcesContent":["import { requireNativeModule } from 'expo';\n\n// We only use the View, no module functions needed\nexport default requireNativeModule('GradientMask');\n"]}
@@ -0,0 +1,3 @@
1
+ declare const _default: {};
2
+ export default _default;
3
+ //# sourceMappingURL=GradientMaskModule.web.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GradientMaskModule.web.d.ts","sourceRoot":"","sources":["../src/GradientMaskModule.web.ts"],"names":[],"mappings":";AACA,wBAAkB"}
@@ -0,0 +1,3 @@
1
+ // Web module stub - GradientMask is primarily for native platforms
2
+ export default {};
3
+ //# sourceMappingURL=GradientMaskModule.web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GradientMaskModule.web.js","sourceRoot":"","sources":["../src/GradientMaskModule.web.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,eAAe,EAAE,CAAC","sourcesContent":["// Web module stub - GradientMask is primarily for native platforms\nexport default {};\n"]}
@@ -0,0 +1,4 @@
1
+ import * as React from 'react';
2
+ import { GradientMaskViewProps } from './GradientMask.types';
3
+ export default function GradientMaskView(props: GradientMaskViewProps): React.JSX.Element;
4
+ //# sourceMappingURL=GradientMaskView.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GradientMaskView.d.ts","sourceRoot":"","sources":["../src/GradientMaskView.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAK7D,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,qBAEpE"}
@@ -0,0 +1,7 @@
1
+ import { requireNativeView } from 'expo';
2
+ import * as React from 'react';
3
+ const NativeView = requireNativeView('GradientMask');
4
+ export default function GradientMaskView(props) {
5
+ return <NativeView {...props}/>;
6
+ }
7
+ //# sourceMappingURL=GradientMaskView.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GradientMaskView.js","sourceRoot":"","sources":["../src/GradientMaskView.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AACzC,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,MAAM,UAAU,GACd,iBAAiB,CAAC,cAAc,CAAC,CAAC;AAEpC,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,KAA4B;IACnE,OAAO,CAAC,UAAU,CAAC,IAAI,KAAK,CAAC,EAAG,CAAC;AACnC,CAAC","sourcesContent":["import { requireNativeView } from 'expo';\nimport * as React from 'react';\n\nimport { GradientMaskViewProps } from './GradientMask.types';\n\nconst NativeView: React.ComponentType<GradientMaskViewProps> =\n requireNativeView('GradientMask');\n\nexport default function GradientMaskView(props: GradientMaskViewProps) {\n return <NativeView {...props} />;\n}\n"]}
@@ -0,0 +1,8 @@
1
+ import * as React from 'react';
2
+ import { GradientMaskViewProps } from './GradientMask.types';
3
+ /**
4
+ * GradientMaskView - Web 實作
5
+ * 使用 CSS mask-image 搭配 linear-gradient 實現漸層遮罩效果
6
+ */
7
+ export default function GradientMaskView(props: GradientMaskViewProps): React.JSX.Element;
8
+ //# sourceMappingURL=GradientMaskView.web.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GradientMaskView.web.d.ts","sourceRoot":"","sources":["../src/GradientMaskView.web.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAqF7D;;;GAGG;AACH,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,qBA8BpE"}
@@ -0,0 +1,99 @@
1
+ import * as React from 'react';
2
+ import { View, StyleSheet } from 'react-native';
3
+ /**
4
+ * 將 processColor 處理過的顏色值轉回 rgba 字串
5
+ */
6
+ function colorToRgba(color) {
7
+ if (color === null || color === undefined) {
8
+ return 'rgba(0, 0, 0, 0)';
9
+ }
10
+ // processColor 在 web 上回傳的格式是 AARRGGBB (32-bit integer)
11
+ const intValue = color >>> 0; // 確保是 unsigned
12
+ const a = ((intValue >> 24) & 0xff) / 255;
13
+ const r = (intValue >> 16) & 0xff;
14
+ const g = (intValue >> 8) & 0xff;
15
+ const b = intValue & 0xff;
16
+ return `rgba(${r}, ${g}, ${b}, ${a})`;
17
+ }
18
+ /**
19
+ * 根據 direction 取得 CSS linear-gradient 的方向
20
+ */
21
+ function getGradientDirection(direction) {
22
+ switch (direction) {
23
+ case 'top':
24
+ return 'to bottom'; // 頂部透明 → 底部不透明
25
+ case 'bottom':
26
+ return 'to top'; // 底部透明 → 頂部不透明
27
+ case 'left':
28
+ return 'to right'; // 左側透明 → 右側不透明
29
+ case 'right':
30
+ return 'to left'; // 右側透明 → 左側不透明
31
+ default:
32
+ return 'to bottom';
33
+ }
34
+ }
35
+ /**
36
+ * 建立 CSS linear-gradient 字串
37
+ */
38
+ function buildGradientString(colors, locations, direction) {
39
+ const gradientDirection = getGradientDirection(direction);
40
+ const colorStops = colors.map((color, index) => {
41
+ const rgba = colorToRgba(color);
42
+ const location = locations[index] !== undefined ? locations[index] * 100 : (index / (colors.length - 1)) * 100;
43
+ return `${rgba} ${location}%`;
44
+ });
45
+ return `linear-gradient(${gradientDirection}, ${colorStops.join(', ')})`;
46
+ }
47
+ /**
48
+ * 根據 maskOpacity 調整顏色的 alpha 值
49
+ * maskOpacity = 0 時,所有顏色變為完全不透明(無遮罩效果)
50
+ * maskOpacity = 1 時,保持原本的 alpha 值
51
+ */
52
+ function adjustColorsForOpacity(colors, maskOpacity) {
53
+ if (maskOpacity >= 1)
54
+ return colors;
55
+ if (maskOpacity <= 0) {
56
+ // 全部變成不透明黑色,表示內容完全可見
57
+ return colors.map(() => 0xff000000);
58
+ }
59
+ return colors.map((color) => {
60
+ if (color === null || color === undefined)
61
+ return color;
62
+ const intValue = color >>> 0;
63
+ const a = ((intValue >> 24) & 0xff) / 255;
64
+ const r = (intValue >> 16) & 0xff;
65
+ const g = (intValue >> 8) & 0xff;
66
+ const b = intValue & 0xff;
67
+ // 根據 maskOpacity 調整 alpha:當 maskOpacity = 0 時 alpha 趨近於 1(完全可見)
68
+ const adjustedAlpha = a + (1 - a) * (1 - maskOpacity);
69
+ return ((Math.round(adjustedAlpha * 255) << 24) | (r << 16) | (g << 8) | b) >>> 0;
70
+ });
71
+ }
72
+ /**
73
+ * GradientMaskView - Web 實作
74
+ * 使用 CSS mask-image 搭配 linear-gradient 實現漸層遮罩效果
75
+ */
76
+ export default function GradientMaskView(props) {
77
+ const { colors, locations, direction = 'top', maskOpacity = 1, style, children, } = props;
78
+ const maskStyle = React.useMemo(() => {
79
+ if (maskOpacity <= 0) {
80
+ // 無遮罩效果
81
+ return {};
82
+ }
83
+ const adjustedColors = adjustColorsForOpacity(colors, maskOpacity);
84
+ const gradientString = buildGradientString(adjustedColors, locations, direction);
85
+ return {
86
+ WebkitMaskImage: gradientString,
87
+ maskImage: gradientString,
88
+ };
89
+ }, [colors, locations, direction, maskOpacity]);
90
+ return (<View style={[styles.container, style, maskStyle]}>
91
+ {children}
92
+ </View>);
93
+ }
94
+ const styles = StyleSheet.create({
95
+ container: {
96
+ overflow: 'hidden',
97
+ },
98
+ });
99
+ //# sourceMappingURL=GradientMaskView.web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GradientMaskView.web.js","sourceRoot":"","sources":["../src/GradientMaskView.web.tsx"],"names":[],"mappings":"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","sourcesContent":["import * as React from 'react';\nimport { View, StyleSheet } from 'react-native';\n\nimport { GradientMaskViewProps } from './GradientMask.types';\n\n/**\n * 將 processColor 處理過的顏色值轉回 rgba 字串\n */\nfunction colorToRgba(color: number | null): string {\n if (color === null || color === undefined) {\n return 'rgba(0, 0, 0, 0)';\n }\n\n // processColor 在 web 上回傳的格式是 AARRGGBB (32-bit integer)\n const intValue = color >>> 0; // 確保是 unsigned\n const a = ((intValue >> 24) & 0xff) / 255;\n const r = (intValue >> 16) & 0xff;\n const g = (intValue >> 8) & 0xff;\n const b = intValue & 0xff;\n\n return `rgba(${r}, ${g}, ${b}, ${a})`;\n}\n\n/**\n * 根據 direction 取得 CSS linear-gradient 的方向\n */\nfunction getGradientDirection(direction: GradientMaskViewProps['direction']): string {\n switch (direction) {\n case 'top':\n return 'to bottom'; // 頂部透明 → 底部不透明\n case 'bottom':\n return 'to top'; // 底部透明 → 頂部不透明\n case 'left':\n return 'to right'; // 左側透明 → 右側不透明\n case 'right':\n return 'to left'; // 右側透明 → 左側不透明\n default:\n return 'to bottom';\n }\n}\n\n/**\n * 建立 CSS linear-gradient 字串\n */\nfunction buildGradientString(\n colors: (number | null)[],\n locations: number[],\n direction: GradientMaskViewProps['direction']\n): string {\n const gradientDirection = getGradientDirection(direction);\n\n const colorStops = colors.map((color, index) => {\n const rgba = colorToRgba(color);\n const location = locations[index] !== undefined ? locations[index] * 100 : (index / (colors.length - 1)) * 100;\n return `${rgba} ${location}%`;\n });\n\n return `linear-gradient(${gradientDirection}, ${colorStops.join(', ')})`;\n}\n\n/**\n * 根據 maskOpacity 調整顏色的 alpha 值\n * maskOpacity = 0 時,所有顏色變為完全不透明(無遮罩效果)\n * maskOpacity = 1 時,保持原本的 alpha 值\n */\nfunction adjustColorsForOpacity(\n colors: (number | null)[],\n maskOpacity: number\n): (number | null)[] {\n if (maskOpacity >= 1) return colors;\n if (maskOpacity <= 0) {\n // 全部變成不透明黑色,表示內容完全可見\n return colors.map(() => 0xff000000);\n }\n\n return colors.map((color) => {\n if (color === null || color === undefined) return color;\n const intValue = color >>> 0;\n const a = ((intValue >> 24) & 0xff) / 255;\n const r = (intValue >> 16) & 0xff;\n const g = (intValue >> 8) & 0xff;\n const b = intValue & 0xff;\n // 根據 maskOpacity 調整 alpha:當 maskOpacity = 0 時 alpha 趨近於 1(完全可見)\n const adjustedAlpha = a + (1 - a) * (1 - maskOpacity);\n return ((Math.round(adjustedAlpha * 255) << 24) | (r << 16) | (g << 8) | b) >>> 0;\n });\n}\n\n/**\n * GradientMaskView - Web 實作\n * 使用 CSS mask-image 搭配 linear-gradient 實現漸層遮罩效果\n */\nexport default function GradientMaskView(props: GradientMaskViewProps) {\n const {\n colors,\n locations,\n direction = 'top',\n maskOpacity = 1,\n style,\n children,\n } = props;\n\n const maskStyle = React.useMemo(() => {\n if (maskOpacity <= 0) {\n // 無遮罩效果\n return {};\n }\n\n const adjustedColors = adjustColorsForOpacity(colors, maskOpacity);\n const gradientString = buildGradientString(adjustedColors, locations, direction);\n\n return {\n WebkitMaskImage: gradientString,\n maskImage: gradientString,\n };\n }, [colors, locations, direction, maskOpacity]);\n\n return (\n <View style={[styles.container, style, maskStyle as any]}>\n {children}\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n container: {\n overflow: 'hidden',\n },\n});\n"]}
@@ -0,0 +1,6 @@
1
+ export { default } from './GradientMaskModule';
2
+ export { default as GradientMaskView } from './GradientMaskView';
3
+ export { default as AnimatedGradientMaskView } from './AnimatedGradientMaskView';
4
+ export type { AnimatedGradientMaskViewProps } from './AnimatedGradientMaskView';
5
+ export * from './GradientMask.types';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,OAAO,IAAI,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AACjF,YAAY,EAAE,6BAA6B,EAAE,MAAM,4BAA4B,CAAC;AAChF,cAAc,sBAAsB,CAAC"}
package/build/index.js ADDED
@@ -0,0 +1,7 @@
1
+ // Reexport the native module. On web, it will be resolved to GradientMaskModule.web.ts
2
+ // and on native platforms to GradientMaskModule.ts
3
+ export { default } from './GradientMaskModule';
4
+ export { default as GradientMaskView } from './GradientMaskView';
5
+ export { default as AnimatedGradientMaskView } from './AnimatedGradientMaskView';
6
+ export * from './GradientMask.types';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,mDAAmD;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,OAAO,IAAI,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAEjF,cAAc,sBAAsB,CAAC","sourcesContent":["// Reexport the native module. On web, it will be resolved to GradientMaskModule.web.ts\n// and on native platforms to GradientMaskModule.ts\nexport { default } from './GradientMaskModule';\nexport { default as GradientMaskView } from './GradientMaskView';\nexport { default as AnimatedGradientMaskView } from './AnimatedGradientMaskView';\nexport type { AnimatedGradientMaskViewProps } from './AnimatedGradientMaskView';\nexport * from './GradientMask.types';\n"]}
@@ -0,0 +1,9 @@
1
+ {
2
+ "platforms": ["apple", "android", "web"],
3
+ "apple": {
4
+ "modules": ["GradientMaskModule"]
5
+ },
6
+ "android": {
7
+ "modules": ["expo.modules.gradientmask.GradientMaskModule"]
8
+ }
9
+ }
Binary file
Binary file
Binary file
Binary file
package/images/ios.png ADDED
Binary file
@@ -0,0 +1,29 @@
1
+ require 'json'
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = 'GradientMask'
7
+ s.version = package['version']
8
+ s.summary = package['description']
9
+ s.description = package['description']
10
+ s.license = package['license']
11
+ s.author = package['author']
12
+ s.homepage = package['homepage']
13
+ s.platforms = {
14
+ :ios => '15.1',
15
+ :tvos => '15.1'
16
+ }
17
+ s.swift_version = '5.9'
18
+ s.source = { git: 'https://github.com/CS6/s33' }
19
+ s.static_framework = true
20
+
21
+ s.dependency 'ExpoModulesCore'
22
+
23
+ # Swift/Objective-C compatibility
24
+ s.pod_target_xcconfig = {
25
+ 'DEFINES_MODULE' => 'YES',
26
+ }
27
+
28
+ s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}"
29
+ end
@@ -0,0 +1,29 @@
1
+ import ExpoModulesCore
2
+
3
+ public class GradientMaskModule: Module {
4
+ public func definition() -> ModuleDefinition {
5
+ Name("GradientMask")
6
+
7
+ View(GradientMaskView.self) {
8
+ // colors: List of processed colors (from processColor in JS)
9
+ Prop("colors") { (view: GradientMaskView, colors: [Int]?) in
10
+ view.setColors(colors)
11
+ }
12
+
13
+ // locations: Array of floats (0-1) for gradient stops
14
+ Prop("locations") { (view: GradientMaskView, locations: [Double]?) in
15
+ view.setLocations(locations)
16
+ }
17
+
18
+ // direction: "top" | "bottom" | "left" | "right"
19
+ Prop("direction") { (view: GradientMaskView, direction: String?) in
20
+ view.setDirection(direction ?? "top")
21
+ }
22
+
23
+ // maskOpacity: 0 = no mask effect, 1 = full gradient mask
24
+ Prop("maskOpacity") { (view: GradientMaskView, opacity: Double?) in
25
+ view.setMaskOpacity(opacity ?? 1.0)
26
+ }
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,133 @@
1
+ import ExpoModulesCore
2
+ import UIKit
3
+
4
+ class GradientMaskView: ExpoView {
5
+
6
+ // MARK: - Properties
7
+
8
+ private let gradientMaskLayer = CAGradientLayer()
9
+ /// Used to overlay gradient when maskOpacity = 0, making content fully visible
10
+ private let solidMaskLayer = CALayer()
11
+
12
+ private var _colors: [CGColor] = [
13
+ UIColor.clear.cgColor,
14
+ UIColor.black.cgColor
15
+ ]
16
+
17
+ private var _locations: [NSNumber] = [0, 1]
18
+ private var _direction: String = "top"
19
+ private var _maskOpacity: CGFloat = 1.0
20
+
21
+ // MARK: - Initialization
22
+
23
+ required init(appContext: AppContext? = nil) {
24
+ super.init(appContext: appContext)
25
+ setupView()
26
+ }
27
+
28
+ private func setupView() {
29
+ backgroundColor = .clear
30
+ clipsToBounds = true
31
+
32
+ // Initialize gradient mask layer
33
+ gradientMaskLayer.colors = _colors
34
+ gradientMaskLayer.locations = _locations
35
+
36
+ // Initialize solid mask layer (opaque black, used to overlay gradient)
37
+ solidMaskLayer.backgroundColor = UIColor.black.cgColor
38
+ // Initially solidMaskLayer is invisible (opacity = 1 - maskOpacity = 0)
39
+ // So mask effect shows full gradient
40
+ solidMaskLayer.opacity = 0.0
41
+
42
+ updateGradientDirection()
43
+ }
44
+
45
+ // MARK: - Props Setters
46
+
47
+ func setColors(_ colors: [Int]?) {
48
+ guard let colors = colors else { return }
49
+ _colors = colors.map { colorValue -> CGColor in
50
+ let intValue = UInt32(bitPattern: Int32(truncatingIfNeeded: colorValue))
51
+ return UIColor(
52
+ red: CGFloat((intValue >> 16) & 0xFF) / 255.0,
53
+ green: CGFloat((intValue >> 8) & 0xFF) / 255.0,
54
+ blue: CGFloat(intValue & 0xFF) / 255.0,
55
+ alpha: CGFloat((intValue >> 24) & 0xFF) / 255.0
56
+ ).cgColor
57
+ }
58
+ updateGradientMask()
59
+ }
60
+
61
+ func setLocations(_ locations: [Double]?) {
62
+ guard let locations = locations else { return }
63
+ _locations = locations.map { NSNumber(value: $0) }
64
+ updateGradientMask()
65
+ }
66
+
67
+ func setDirection(_ direction: String) {
68
+ _direction = direction
69
+ updateGradientDirection()
70
+ updateGradientMask()
71
+ }
72
+
73
+ func setMaskOpacity(_ opacity: Double) {
74
+ _maskOpacity = CGFloat(opacity)
75
+ updateMaskOpacity()
76
+ }
77
+
78
+ // MARK: - Layout
79
+
80
+ override func layoutSubviews() {
81
+ super.layoutSubviews()
82
+ updateGradientMask()
83
+ }
84
+
85
+ // MARK: - Gradient Mask
86
+
87
+ private func updateGradientMask() {
88
+ gradientMaskLayer.frame = bounds
89
+ solidMaskLayer.frame = bounds
90
+
91
+ gradientMaskLayer.colors = _colors
92
+ gradientMaskLayer.locations = _locations
93
+
94
+ updateGradientDirection()
95
+
96
+ // Create composite mask: gradient + solid overlay
97
+ let compositeMask = CALayer()
98
+ compositeMask.frame = bounds
99
+ compositeMask.addSublayer(gradientMaskLayer)
100
+ compositeMask.addSublayer(solidMaskLayer)
101
+
102
+ layer.mask = compositeMask
103
+ }
104
+
105
+ private func updateMaskOpacity() {
106
+ CATransaction.begin()
107
+ CATransaction.setDisableActions(true)
108
+ // maskOpacity = 0 → solidMaskLayer.opacity = 1 → content fully visible
109
+ // maskOpacity = 1 → solidMaskLayer.opacity = 0 → full gradient effect
110
+ solidMaskLayer.opacity = Float(1.0 - _maskOpacity)
111
+ CATransaction.commit()
112
+ }
113
+
114
+ private func updateGradientDirection() {
115
+ switch _direction {
116
+ case "top":
117
+ gradientMaskLayer.startPoint = CGPoint(x: 0.5, y: 0)
118
+ gradientMaskLayer.endPoint = CGPoint(x: 0.5, y: 1)
119
+ case "bottom":
120
+ gradientMaskLayer.startPoint = CGPoint(x: 0.5, y: 1)
121
+ gradientMaskLayer.endPoint = CGPoint(x: 0.5, y: 0)
122
+ case "left":
123
+ gradientMaskLayer.startPoint = CGPoint(x: 0, y: 0.5)
124
+ gradientMaskLayer.endPoint = CGPoint(x: 1, y: 0.5)
125
+ case "right":
126
+ gradientMaskLayer.startPoint = CGPoint(x: 1, y: 0.5)
127
+ gradientMaskLayer.endPoint = CGPoint(x: 0, y: 0.5)
128
+ default:
129
+ gradientMaskLayer.startPoint = CGPoint(x: 0.5, y: 0)
130
+ gradientMaskLayer.endPoint = CGPoint(x: 0.5, y: 1)
131
+ }
132
+ }
133
+ }