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
package/.eslintrc.js ADDED
@@ -0,0 +1,5 @@
1
+ module.exports = {
2
+ root: true,
3
+ extends: ['universe/native', 'universe/web'],
4
+ ignorePatterns: ['build'],
5
+ };
package/README.ja.md ADDED
@@ -0,0 +1,335 @@
1
+ <p align="center">
2
+ <h1 align="center">react-native-gradient-mask</h1>
3
+ </p>
4
+
5
+ <p align="center">
6
+ <b>React Native ネイティブグラデーションマスクコンポーネント</b>
7
+ </p>
8
+
9
+ <p align="center">
10
+ 美しいフェードエフェクト、リストマスク、スムーズなグラデーショントランジションを、ネイティブパフォーマンスと Reanimated アニメーションサポートで実現。
11
+ </p>
12
+
13
+ <p align="center">
14
+ <a href="https://www.npmjs.com/package/react-native-gradient-mask">
15
+ <img src="https://img.shields.io/npm/v/react-native-gradient-mask.svg" alt="npm version" />
16
+ </a>
17
+ <a href="https://www.npmjs.com/package/react-native-gradient-mask">
18
+ <img src="https://img.shields.io/npm/dm/react-native-gradient-mask.svg" alt="npm downloads" />
19
+ </a>
20
+ <img src="https://img.shields.io/badge/platforms-iOS%20%7C%20Android%20%7C%20Web-brightgreen.svg" alt="platforms" />
21
+ <img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="license" />
22
+ </p>
23
+
24
+ <p align="center">
25
+ <a href="./README.md">English</a> •
26
+ <a href="./README.zh-TW.md">繁體中文</a>
27
+ </p>
28
+
29
+ ---
30
+
31
+ ## デモ
32
+
33
+ <p align="center">
34
+ <table>
35
+ <tr>
36
+ <td align="center"><b>iOS</b></td>
37
+ <td align="center"><b>Android</b></td>
38
+ </tr>
39
+ <tr>
40
+ <td><a href="./images/ios Demo Video.webm"><img src="./images/ios.png" alt="iOS デモ" width="280" /></a></td>
41
+ <td><a href="./images/android.mp4"><img src="./images/android.png" alt="Android デモ" width="280" /></a></td>
42
+ </tr>
43
+ </table>
44
+ </p>
45
+
46
+ ## 特徴
47
+
48
+ | 特徴 | 説明 |
49
+ |------|------|
50
+ | **クロスプラットフォーム** | iOS、Android、Web 対応 |
51
+ | **ネイティブパフォーマンス** | iOS: `CAGradientLayer` • Android: `Bitmap` + `PorterDuff` • Web: CSS `mask-image` |
52
+ | **Reanimated 対応** | `AnimatedGradientMaskView` で 60fps のスムーズなマスクアニメーション |
53
+ | **柔軟な設定** | カスタムカラー、位置、方向、マスク強度 |
54
+ | **TypeScript** | 完全な型定義付き |
55
+
56
+ ## インストール
57
+
58
+ ```bash
59
+ npm install react-native-gradient-mask
60
+ ```
61
+
62
+ ```bash
63
+ yarn add react-native-gradient-mask
64
+ ```
65
+
66
+ ### 必要条件
67
+
68
+ | 依存関係 | バージョン |
69
+ |----------|-----------|
70
+ | Expo SDK | 50+ |
71
+ | React Native | 0.73+ |
72
+ | react-native-reanimated | >= 3.0.0 *(オプション)* |
73
+
74
+ ### セットアップ
75
+
76
+ <details>
77
+ <summary><b>iOS</b></summary>
78
+
79
+ ```bash
80
+ cd ios && pod install
81
+ ```
82
+ </details>
83
+
84
+ <details>
85
+ <summary><b>Android</b></summary>
86
+
87
+ 追加設定不要。オートリンクが有効です。
88
+ </details>
89
+
90
+ ---
91
+
92
+ ## クイックスタート
93
+
94
+ ```tsx
95
+ import { processColor } from 'react-native';
96
+ import { GradientMaskView } from 'react-native-gradient-mask';
97
+
98
+ const colors = [
99
+ processColor('rgba(0,0,0,0)'),
100
+ processColor('rgba(0,0,0,1)'),
101
+ ];
102
+
103
+ export default function App() {
104
+ return (
105
+ <GradientMaskView
106
+ colors={colors}
107
+ locations={[0, 1]}
108
+ direction="top"
109
+ style={{ flex: 1 }}
110
+ >
111
+ <YourContent />
112
+ </GradientMaskView>
113
+ );
114
+ }
115
+ ```
116
+
117
+ ---
118
+
119
+ ## API リファレンス
120
+
121
+ ### コンポーネント
122
+
123
+ | コンポーネント | 説明 |
124
+ |----------------|------|
125
+ | `GradientMaskView` | 基本グラデーションマスクコンポーネント |
126
+ | `AnimatedGradientMaskView` | Reanimated アニメーション対応グラデーションマスク |
127
+
128
+ ### Props
129
+
130
+ #### GradientMaskView
131
+
132
+ | プロパティ | 型 | 必須 | デフォルト | 説明 |
133
+ |------------|------|:----:|------------|------|
134
+ | `colors` | `(number \| null)[]` | はい | - | グラデーションカラー(`processColor()` を使用) |
135
+ | `locations` | `number[]` | はい | - | カラー位置 (0-1) |
136
+ | `direction` | `'top' \| 'bottom' \| 'left' \| 'right'` | いいえ | `'top'` | グラデーション方向 |
137
+ | `maskOpacity` | `number` | いいえ | `1` | マスク強度 (0-1) |
138
+ | `style` | `ViewStyle` | いいえ | - | コンテナスタイル |
139
+ | `children` | `ReactNode` | いいえ | - | マスクを適用するコンテンツ |
140
+
141
+ #### AnimatedGradientMaskView
142
+
143
+ `GradientMaskView` と同じですが、`maskOpacity` はアニメーション制御用の `SharedValue<number>` を受け付けます。
144
+
145
+ ### 方向ガイド
146
+
147
+ | 方向 | 効果 |
148
+ |------|------|
149
+ | `top` | 上部が透明 → 下部が不透明 |
150
+ | `bottom` | 下部が透明 → 上部が不透明 |
151
+ | `left` | 左側が透明 → 右側が不透明 |
152
+ | `right` | 右側が透明 → 左側が不透明 |
153
+
154
+ ---
155
+
156
+ ## 使用例
157
+
158
+ ### 基本的なフェードエフェクト
159
+
160
+ ```tsx
161
+ import { processColor } from 'react-native';
162
+ import { GradientMaskView } from 'react-native-gradient-mask';
163
+
164
+ const colors = [
165
+ processColor('rgba(0,0,0,0)'),
166
+ processColor('rgba(0,0,0,0.5)'),
167
+ processColor('rgba(0,0,0,1)'),
168
+ ];
169
+
170
+ function FadeExample() {
171
+ return (
172
+ <GradientMaskView
173
+ colors={colors}
174
+ locations={[0, 0.3, 1]}
175
+ direction="top"
176
+ style={{ flex: 1 }}
177
+ >
178
+ <ScrollView>
179
+ <Text>フェードエフェクト付きコンテンツ</Text>
180
+ </ScrollView>
181
+ </GradientMaskView>
182
+ );
183
+ }
184
+ ```
185
+
186
+ ### Reanimated アニメーションとの連携
187
+
188
+ ```tsx
189
+ import { processColor } from 'react-native';
190
+ import { AnimatedGradientMaskView } from 'react-native-gradient-mask';
191
+ import { useSharedValue, withTiming } from 'react-native-reanimated';
192
+
193
+ function AnimatedExample() {
194
+ const maskOpacity = useSharedValue(0);
195
+
196
+ const showMask = () => {
197
+ maskOpacity.value = withTiming(1, { duration: 600 });
198
+ };
199
+
200
+ const hideMask = () => {
201
+ maskOpacity.value = withTiming(0, { duration: 400 });
202
+ };
203
+
204
+ return (
205
+ <AnimatedGradientMaskView
206
+ colors={[
207
+ processColor('rgba(0,0,0,0)'),
208
+ processColor('rgba(0,0,0,1)'),
209
+ ]}
210
+ locations={[0, 1]}
211
+ maskOpacity={maskOpacity}
212
+ style={{ flex: 1 }}
213
+ >
214
+ <YourContent />
215
+ </AnimatedGradientMaskView>
216
+ );
217
+ }
218
+ ```
219
+
220
+ ### チャットリストの動的マスク
221
+
222
+ ```tsx
223
+ import { useMemo, useCallback, useRef } from 'react';
224
+ import { processColor } from 'react-native';
225
+ import { FlashList } from '@shopify/flash-list';
226
+ import { AnimatedGradientMaskView } from 'react-native-gradient-mask';
227
+ import { useSharedValue, withTiming, cancelAnimation, Easing } from 'react-native-reanimated';
228
+
229
+ function ChatList({ messages }) {
230
+ const maskOpacity = useSharedValue(0);
231
+ const isAtBottomRef = useRef(false);
232
+
233
+ const maskColors = useMemo(() => [
234
+ processColor('rgba(0,0,0,0)'),
235
+ processColor('rgba(0,0,0,0)'),
236
+ processColor('rgba(0,0,0,0.2)'),
237
+ processColor('rgba(0,0,0,0.6)'),
238
+ processColor('rgba(0,0,0,0.9)'),
239
+ processColor('rgba(0,0,0,1)'),
240
+ ], []);
241
+
242
+ const handleScroll = useCallback((e) => {
243
+ const { contentOffset, layoutMeasurement, contentSize } = e.nativeEvent;
244
+ const distanceFromBottom = contentSize.height - contentOffset.y - layoutMeasurement.height;
245
+ const isAtBottom = distanceFromBottom <= 30;
246
+
247
+ if (isAtBottom !== isAtBottomRef.current) {
248
+ isAtBottomRef.current = isAtBottom;
249
+ cancelAnimation(maskOpacity);
250
+ maskOpacity.value = withTiming(isAtBottom ? 1 : 0, {
251
+ duration: isAtBottom ? 600 : 400,
252
+ easing: isAtBottom ? Easing.in(Easing.quad) : Easing.out(Easing.quad),
253
+ });
254
+ }
255
+ }, []);
256
+
257
+ return (
258
+ <AnimatedGradientMaskView
259
+ colors={maskColors}
260
+ locations={[0, 0.42, 0.45, 0.48, 0.5, 1]}
261
+ direction="top"
262
+ maskOpacity={maskOpacity}
263
+ style={{ flex: 1 }}
264
+ >
265
+ <FlashList
266
+ data={messages}
267
+ renderItem={({ item }) => <MessageItem item={item} />}
268
+ onScroll={handleScroll}
269
+ scrollEventThrottle={16}
270
+ />
271
+ </AnimatedGradientMaskView>
272
+ );
273
+ }
274
+ ```
275
+
276
+ ---
277
+
278
+ ## ヒントとベストプラクティス
279
+
280
+ ### 必ず `processColor()` を使用
281
+
282
+ ```tsx
283
+ // ✅ 正しい
284
+ const colors = [
285
+ processColor('rgba(0,0,0,0)'),
286
+ processColor('rgba(0,0,0,1)'),
287
+ ];
288
+
289
+ // ❌ 間違い - 動作しません
290
+ const colors = [
291
+ 'rgba(0,0,0,0)',
292
+ 'rgba(0,0,0,1)',
293
+ ];
294
+ ```
295
+
296
+ ### `useMemo` で最適化
297
+
298
+ ```tsx
299
+ const maskColors = useMemo(() => [
300
+ processColor('rgba(0,0,0,0)'),
301
+ processColor('rgba(0,0,0,1)'),
302
+ ], []);
303
+ ```
304
+
305
+ ### ちらつきを防ぐ
306
+
307
+ ```tsx
308
+ import { cancelAnimation } from 'react-native-reanimated';
309
+
310
+ // 新しいアニメーションを開始する前に前のアニメーションをキャンセル
311
+ cancelAnimation(maskOpacity);
312
+ maskOpacity.value = withTiming(newValue, { duration: 300 });
313
+ ```
314
+
315
+ ---
316
+
317
+ ## プラットフォームサポート
318
+
319
+ | プラットフォーム | 実装 | 状態 |
320
+ |------------------|------|:----:|
321
+ | iOS | `CAGradientLayer` | ✅ |
322
+ | Android | `Bitmap` + `LinearGradient` + `PorterDuff` | ✅ |
323
+ | Web | CSS `mask-image` + `linear-gradient` | ✅ |
324
+
325
+ ---
326
+
327
+ ## ライセンス
328
+
329
+ MIT © [DaYuan Lin (CS6)](https://github.com/CS6)
330
+
331
+ ---
332
+
333
+ <p align="center">
334
+ <sub>React Native コミュニティのために ❤️ を込めて作りました</sub>
335
+ </p>
package/README.md ADDED
@@ -0,0 +1,335 @@
1
+ <p align="center">
2
+ <h1 align="center">react-native-gradient-mask</h1>
3
+ </p>
4
+
5
+ <p align="center">
6
+ <b>A native gradient mask component for React Native</b>
7
+ </p>
8
+
9
+ <p align="center">
10
+ Create beautiful fade effects, list masks, and smooth gradient transitions with native performance and Reanimated animation support.
11
+ </p>
12
+
13
+ <p align="center">
14
+ <a href="https://www.npmjs.com/package/react-native-gradient-mask">
15
+ <img src="https://img.shields.io/npm/v/react-native-gradient-mask.svg" alt="npm version" />
16
+ </a>
17
+ <a href="https://www.npmjs.com/package/react-native-gradient-mask">
18
+ <img src="https://img.shields.io/npm/dm/react-native-gradient-mask.svg" alt="npm downloads" />
19
+ </a>
20
+ <img src="https://img.shields.io/badge/platforms-iOS%20%7C%20Android%20%7C%20Web-brightgreen.svg" alt="platforms" />
21
+ <img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="license" />
22
+ </p>
23
+
24
+ <p align="center">
25
+ <a href="./README.zh-TW.md">繁體中文</a> •
26
+ <a href="./README.ja.md">日本語</a>
27
+ </p>
28
+
29
+ ---
30
+
31
+ ## Demo
32
+
33
+ <p align="center">
34
+ <table>
35
+ <tr>
36
+ <td align="center"><b>iOS</b></td>
37
+ <td align="center"><b>Android</b></td>
38
+ </tr>
39
+ <tr>
40
+ <td><a href="./images/ios Demo Video.webm"><img src="./images/ios.png" alt="iOS Demo" width="280" /></a></td>
41
+ <td><a href="./images/android.mp4"><img src="./images/android.png" alt="Android Demo" width="280" /></a></td>
42
+ </tr>
43
+ </table>
44
+ </p>
45
+
46
+ ## Features
47
+
48
+ | Feature | Description |
49
+ |---------|-------------|
50
+ | **Cross-platform** | iOS, Android, and Web support |
51
+ | **Native Performance** | iOS: `CAGradientLayer` • Android: `Bitmap` + `PorterDuff` • Web: CSS `mask-image` |
52
+ | **Reanimated Support** | Smooth 60fps mask animations with `AnimatedGradientMaskView` |
53
+ | **Flexible** | Custom colors, locations, directions, and mask intensity |
54
+ | **TypeScript** | Full type definitions included |
55
+
56
+ ## Installation
57
+
58
+ ```bash
59
+ npm install react-native-gradient-mask
60
+ ```
61
+
62
+ ```bash
63
+ yarn add react-native-gradient-mask
64
+ ```
65
+
66
+ ### Requirements
67
+
68
+ | Dependency | Version |
69
+ |------------|---------|
70
+ | Expo SDK | 50+ |
71
+ | React Native | 0.73+ |
72
+ | react-native-reanimated | >= 3.0.0 *(optional)* |
73
+
74
+ ### Setup
75
+
76
+ <details>
77
+ <summary><b>iOS</b></summary>
78
+
79
+ ```bash
80
+ cd ios && pod install
81
+ ```
82
+ </details>
83
+
84
+ <details>
85
+ <summary><b>Android</b></summary>
86
+
87
+ No additional setup required. Auto-linking enabled.
88
+ </details>
89
+
90
+ ---
91
+
92
+ ## Quick Start
93
+
94
+ ```tsx
95
+ import { processColor } from 'react-native';
96
+ import { GradientMaskView } from 'react-native-gradient-mask';
97
+
98
+ const colors = [
99
+ processColor('rgba(0,0,0,0)'),
100
+ processColor('rgba(0,0,0,1)'),
101
+ ];
102
+
103
+ export default function App() {
104
+ return (
105
+ <GradientMaskView
106
+ colors={colors}
107
+ locations={[0, 1]}
108
+ direction="top"
109
+ style={{ flex: 1 }}
110
+ >
111
+ <YourContent />
112
+ </GradientMaskView>
113
+ );
114
+ }
115
+ ```
116
+
117
+ ---
118
+
119
+ ## API Reference
120
+
121
+ ### Components
122
+
123
+ | Component | Description |
124
+ |-----------|-------------|
125
+ | `GradientMaskView` | Basic gradient mask component |
126
+ | `AnimatedGradientMaskView` | Animated gradient mask with Reanimated support |
127
+
128
+ ### Props
129
+
130
+ #### GradientMaskView
131
+
132
+ | Prop | Type | Required | Default | Description |
133
+ |------|------|:--------:|---------|-------------|
134
+ | `colors` | `(number \| null)[]` | Yes | - | Gradient colors (use `processColor()`) |
135
+ | `locations` | `number[]` | Yes | - | Color positions (0-1) |
136
+ | `direction` | `'top' \| 'bottom' \| 'left' \| 'right'` | No | `'top'` | Gradient direction |
137
+ | `maskOpacity` | `number` | No | `1` | Mask intensity (0-1) |
138
+ | `style` | `ViewStyle` | No | - | Container style |
139
+ | `children` | `ReactNode` | No | - | Content to mask |
140
+
141
+ #### AnimatedGradientMaskView
142
+
143
+ Same as `GradientMaskView`, but `maskOpacity` accepts `SharedValue<number>` for animations.
144
+
145
+ ### Direction Guide
146
+
147
+ | Direction | Effect |
148
+ |-----------|--------|
149
+ | `top` | Top transparent → Bottom opaque |
150
+ | `bottom` | Bottom transparent → Top opaque |
151
+ | `left` | Left transparent → Right opaque |
152
+ | `right` | Right transparent → Left opaque |
153
+
154
+ ---
155
+
156
+ ## Examples
157
+
158
+ ### Basic Fade Effect
159
+
160
+ ```tsx
161
+ import { processColor } from 'react-native';
162
+ import { GradientMaskView } from 'react-native-gradient-mask';
163
+
164
+ const colors = [
165
+ processColor('rgba(0,0,0,0)'),
166
+ processColor('rgba(0,0,0,0.5)'),
167
+ processColor('rgba(0,0,0,1)'),
168
+ ];
169
+
170
+ function FadeExample() {
171
+ return (
172
+ <GradientMaskView
173
+ colors={colors}
174
+ locations={[0, 0.3, 1]}
175
+ direction="top"
176
+ style={{ flex: 1 }}
177
+ >
178
+ <ScrollView>
179
+ <Text>Content with fade effect</Text>
180
+ </ScrollView>
181
+ </GradientMaskView>
182
+ );
183
+ }
184
+ ```
185
+
186
+ ### Animated Mask with Reanimated
187
+
188
+ ```tsx
189
+ import { processColor } from 'react-native';
190
+ import { AnimatedGradientMaskView } from 'react-native-gradient-mask';
191
+ import { useSharedValue, withTiming } from 'react-native-reanimated';
192
+
193
+ function AnimatedExample() {
194
+ const maskOpacity = useSharedValue(0);
195
+
196
+ const showMask = () => {
197
+ maskOpacity.value = withTiming(1, { duration: 600 });
198
+ };
199
+
200
+ const hideMask = () => {
201
+ maskOpacity.value = withTiming(0, { duration: 400 });
202
+ };
203
+
204
+ return (
205
+ <AnimatedGradientMaskView
206
+ colors={[
207
+ processColor('rgba(0,0,0,0)'),
208
+ processColor('rgba(0,0,0,1)'),
209
+ ]}
210
+ locations={[0, 1]}
211
+ maskOpacity={maskOpacity}
212
+ style={{ flex: 1 }}
213
+ >
214
+ <YourContent />
215
+ </AnimatedGradientMaskView>
216
+ );
217
+ }
218
+ ```
219
+
220
+ ### Chat List with Dynamic Mask
221
+
222
+ ```tsx
223
+ import { useMemo, useCallback, useRef } from 'react';
224
+ import { processColor } from 'react-native';
225
+ import { FlashList } from '@shopify/flash-list';
226
+ import { AnimatedGradientMaskView } from 'react-native-gradient-mask';
227
+ import { useSharedValue, withTiming, cancelAnimation, Easing } from 'react-native-reanimated';
228
+
229
+ function ChatList({ messages }) {
230
+ const maskOpacity = useSharedValue(0);
231
+ const isAtBottomRef = useRef(false);
232
+
233
+ const maskColors = useMemo(() => [
234
+ processColor('rgba(0,0,0,0)'),
235
+ processColor('rgba(0,0,0,0)'),
236
+ processColor('rgba(0,0,0,0.2)'),
237
+ processColor('rgba(0,0,0,0.6)'),
238
+ processColor('rgba(0,0,0,0.9)'),
239
+ processColor('rgba(0,0,0,1)'),
240
+ ], []);
241
+
242
+ const handleScroll = useCallback((e) => {
243
+ const { contentOffset, layoutMeasurement, contentSize } = e.nativeEvent;
244
+ const distanceFromBottom = contentSize.height - contentOffset.y - layoutMeasurement.height;
245
+ const isAtBottom = distanceFromBottom <= 30;
246
+
247
+ if (isAtBottom !== isAtBottomRef.current) {
248
+ isAtBottomRef.current = isAtBottom;
249
+ cancelAnimation(maskOpacity);
250
+ maskOpacity.value = withTiming(isAtBottom ? 1 : 0, {
251
+ duration: isAtBottom ? 600 : 400,
252
+ easing: isAtBottom ? Easing.in(Easing.quad) : Easing.out(Easing.quad),
253
+ });
254
+ }
255
+ }, []);
256
+
257
+ return (
258
+ <AnimatedGradientMaskView
259
+ colors={maskColors}
260
+ locations={[0, 0.42, 0.45, 0.48, 0.5, 1]}
261
+ direction="top"
262
+ maskOpacity={maskOpacity}
263
+ style={{ flex: 1 }}
264
+ >
265
+ <FlashList
266
+ data={messages}
267
+ renderItem={({ item }) => <MessageItem item={item} />}
268
+ onScroll={handleScroll}
269
+ scrollEventThrottle={16}
270
+ />
271
+ </AnimatedGradientMaskView>
272
+ );
273
+ }
274
+ ```
275
+
276
+ ---
277
+
278
+ ## Tips & Best Practices
279
+
280
+ ### Always use `processColor()`
281
+
282
+ ```tsx
283
+ // ✅ Correct
284
+ const colors = [
285
+ processColor('rgba(0,0,0,0)'),
286
+ processColor('rgba(0,0,0,1)'),
287
+ ];
288
+
289
+ // ❌ Wrong - won't work
290
+ const colors = [
291
+ 'rgba(0,0,0,0)',
292
+ 'rgba(0,0,0,1)',
293
+ ];
294
+ ```
295
+
296
+ ### Optimize with `useMemo`
297
+
298
+ ```tsx
299
+ const maskColors = useMemo(() => [
300
+ processColor('rgba(0,0,0,0)'),
301
+ processColor('rgba(0,0,0,1)'),
302
+ ], []);
303
+ ```
304
+
305
+ ### Avoid Flickering
306
+
307
+ ```tsx
308
+ import { cancelAnimation } from 'react-native-reanimated';
309
+
310
+ // Cancel previous animation before starting new one
311
+ cancelAnimation(maskOpacity);
312
+ maskOpacity.value = withTiming(newValue, { duration: 300 });
313
+ ```
314
+
315
+ ---
316
+
317
+ ## Platform Support
318
+
319
+ | Platform | Implementation | Status |
320
+ |----------|----------------|:------:|
321
+ | iOS | `CAGradientLayer` | ✅ |
322
+ | Android | `Bitmap` + `LinearGradient` + `PorterDuff` | ✅ |
323
+ | Web | CSS `mask-image` + `linear-gradient` | ✅ |
324
+
325
+ ---
326
+
327
+ ## License
328
+
329
+ MIT © [DaYuan Lin (CS6)](https://github.com/CS6)
330
+
331
+ ---
332
+
333
+ <p align="center">
334
+ <sub>Built with ❤️ for the React Native community</sub>
335
+ </p>