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.
- package/.eslintrc.js +5 -0
- package/README.ja.md +335 -0
- package/README.md +335 -0
- package/README.zh-TW.md +335 -0
- package/android/build.gradle +43 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/expo/modules/gradientmask/GradientMaskModule.kt +33 -0
- package/android/src/main/java/expo/modules/gradientmask/GradientMaskView.kt +198 -0
- package/build/AnimatedGradientMaskView.d.ts +36 -0
- package/build/AnimatedGradientMaskView.d.ts.map +1 -0
- package/build/AnimatedGradientMaskView.js +37 -0
- package/build/AnimatedGradientMaskView.js.map +1 -0
- package/build/AnimatedGradientMaskView.web.d.ts +17 -0
- package/build/AnimatedGradientMaskView.web.d.ts.map +1 -0
- package/build/AnimatedGradientMaskView.web.js +100 -0
- package/build/AnimatedGradientMaskView.web.js.map +1 -0
- package/build/GradientMask.types.d.ts +38 -0
- package/build/GradientMask.types.d.ts.map +1 -0
- package/build/GradientMask.types.js +2 -0
- package/build/GradientMask.types.js.map +1 -0
- package/build/GradientMaskModule.d.ts +3 -0
- package/build/GradientMaskModule.d.ts.map +1 -0
- package/build/GradientMaskModule.js +4 -0
- package/build/GradientMaskModule.js.map +1 -0
- package/build/GradientMaskModule.web.d.ts +3 -0
- package/build/GradientMaskModule.web.d.ts.map +1 -0
- package/build/GradientMaskModule.web.js +3 -0
- package/build/GradientMaskModule.web.js.map +1 -0
- package/build/GradientMaskView.d.ts +4 -0
- package/build/GradientMaskView.d.ts.map +1 -0
- package/build/GradientMaskView.js +7 -0
- package/build/GradientMaskView.js.map +1 -0
- package/build/GradientMaskView.web.d.ts +8 -0
- package/build/GradientMaskView.web.d.ts.map +1 -0
- package/build/GradientMaskView.web.js +99 -0
- package/build/GradientMaskView.web.js.map +1 -0
- package/build/index.d.ts +6 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +7 -0
- package/build/index.js.map +1 -0
- package/expo-module.config.json +9 -0
- package/images/android.mp4 +0 -0
- package/images/android.png +0 -0
- package/images/demo.mov +0 -0
- package/images/ios Demo Video.webm +0 -0
- package/images/ios.png +0 -0
- package/ios/GradientMask.podspec +29 -0
- package/ios/GradientMaskModule.swift +29 -0
- package/ios/GradientMaskView.swift +133 -0
- package/package.json +83 -0
- package/src/AnimatedGradientMaskView.tsx +60 -0
- package/src/AnimatedGradientMaskView.web.tsx +149 -0
- package/src/GradientMask.types.ts +43 -0
- package/src/GradientMaskModule.ts +4 -0
- package/src/GradientMaskModule.web.ts +2 -0
- package/src/GradientMaskView.tsx +11 -0
- package/src/GradientMaskView.web.tsx +129 -0
- package/src/index.ts +7 -0
- package/tsconfig.json +9 -0
package/.eslintrc.js
ADDED
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>
|