Skip to content

Commit 662cdcd

Browse files
authored
Merge pull request #53 from dohooo/fix46
perf: reduce the calculation of animation values
2 parents 137cb90 + 1f240e5 commit 662cdcd

File tree

8 files changed

+124
-68
lines changed

8 files changed

+124
-68
lines changed

README.md

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,15 @@ English | [简体中文](./README.zh-CN.md)
1616

1717
<br/>
1818

19-
## Here comes the official edition!
19+
## ReactNative community's best use of the carousel component! 🎉🎉🎉
2020

21-
`v1` has been born, now the carousel will be more natural, and fixed various bugs in the 0.x version, this library will continue to maintain, rest assured to use! [come and experience](https://snack.expo.dev/@zhaodonghao586/simple-carousel) 🎉🎉🎉
22-
23-
Updates:
24-
25-
- Reconstructed some logic, sliding animation more smooth, natural
26-
- timingConfig -> springConfig (The configuration of the 'duration' property is no longer supported by this configuration)
27-
- [...](https://github.com/dohooo/react-native-reanimated-carousel/releases/tag/v1.0.0)
21+
- It completely solves this [problem](https://github.com/meliorence/react-native-snap-carousel/issues/632) for `react-native-snap-carousel`! More styles and apis in development...
22+
- **Simple****Infinitely scrolling very smooth****Fully implemented using Reanimated 2!**
2823

2924
## Reason
3025

31-
🎉 **It completely solves [this problem](https://github.com/meliorence/react-native-snap-carousel/issues/632) for `react-native-snap-carousel`! Simple、Infinitely scrolling very smooth、Fully implemented using Reanimated 2!**
32-
33-
> The common RN infinite scroll component. It's common to get stuck on a fast slide. Wait for the next element to appear. This component will not have similar problems. That's why this library was created.
26+
The common RN infinite scroll component. It's common to get stuck on a fast slide. Wait for the next element to appear. This component will not have similar problems. That's why this library was created.[Try it with SNACK](https://snack.expo.dev/@zhaodonghao586/simple-carousel)
3427

35-
> At present, it only meets the needs of my work. Welcome to raise PR/ISSUES.[Try it with snack](https://snack.expo.dev/@zhaodonghao586/simple-carousel)
3628

3729
<p align="center">
3830
Use react-native-snap-carousel for quick swiping,you can see caton clearly when you reach the junction.(gif 4.6mb)
@@ -72,28 +64,21 @@ If use EXPO managed workflow please ensure that the version is greater than 41.B
7264

7365
## Usage
7466

75-
```typescript
67+
```tsx
7668
import Carousel from 'react-native-reanimated-carousel';
7769

78-
// ...
79-
8070
<Carousel<{ color: string }>
81-
width={width}
71+
width={ width }
8272
data={[{ color: 'red' }, { color: 'purple' }, { color: 'yellow' }]}
8373
renderItem={({ color }) => {
84-
return (
85-
<View
86-
style={{
87-
backgroundColor: color,
88-
justifyContent: 'center',
89-
flex: 1,
90-
}}
91-
/>
92-
);
74+
return <View style={{ backgroundColor: color, flex: 1}}/>
9375
}}
9476
/>;
9577
```
9678

79+
## Optimizing
80+
- When rendering a large number of elements, you can use the 'windowSize' property to control how many items of the current element are rendered. The default is full rendering. After testing without this property, frames will drop when rendering 200 empty views. After setting this property, rendering 1000 empty views is still smooth. (The specific number depends on the phone model tested)
81+
9782
## Props
9883

9984
| name | required | default | types | description |
@@ -116,6 +101,7 @@ import Carousel from 'react-native-reanimated-carousel';
116101
| onScrollBegin || | () => void | Callback fired when scroll begin |
117102
| onScrollEnd || | (previous: number, current: number) => void | Callback fired when scroll end |
118103
| panGestureHandlerProps || {} | Omit<Partial\<PanGestureHandlerProps\>,'onHandlerStateChange'> | PanGestureHandler props |
104+
| windowSize | ❌ | 0 | number | The maximum number of items that can respond to pan gesture events, `0` means all items will respond to pan gesture events
119105
| onProgressChange || | onProgressChange?: (offsetProgress: number,absoluteProgress: number) => void | On progress change. `offsetProgress`:Total of offset distance (0 390 780 ...); `absoluteProgress`:Convert to index (0 1 2 ...) |
120106

121107
## Ref

README.zh-CN.md

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,17 @@
1616

1717
<br/>
1818

19-
## 正式版来了!
19+
## ReactNative社区最好用的轮播图组件! 🎉🎉🎉
2020

21-
`v1`已经诞生,现在轮播图的滚动将会更加自然,并且修复了 0.x 版本中出现的各种 bug,此库将会持续维护,放心使用! [快来体验](https://snack.expo.dev/@zhaodonghao586/simple-carousel) 🎉🎉🎉
21+
- 完全解决了`react-native-snap-carousel`[问题](https://github.com/meliorence/react-native-snap-carousel/issues/632)! 更多样式与API正在开发中...
22+
- **易用****无限滚动****完全使用 Reanimated2 实现**
2223

23-
更新:
2424

25-
- 重构了部分逻辑,滑动动画更加流畅、自然
26-
- timingConfig -> springConfig (此配置不再支持对`duration`属性的配置)
27-
- [...](https://github.com/dohooo/react-native-reanimated-carousel/releases/tag/v1.0.0)
2825

26+
2927
## 原因
28+
常见的无限滚动轮播图,在快速滑动时会出现卡住的情况,这是因为实现方式而导致的问题。这个组件用了不同的方式来实现,解决了这个问题,这就是创建这个库的原因。[在 SNACK 上尝试](https://snack.expo.dev/@zhaodonghao586/simple-carousel)
3029

31-
🎉 **完全解决了`react-native-snap-carousel`[这个问题](https://github.com/meliorence/react-native-snap-carousel/issues/632)! 易用、无限滚动、完全使用 Reanimated2 实现**
32-
33-
> 常见的无限滚动轮播图,在快速滑动时会出现卡住的情况,这是因为实现方式而导致的问题。这个组件用了不同的方式来实现,解决了这个问题,这就是创建这个库的原因。
34-
35-
> 目前他只满足了我工作上的需要,欢迎大家的 ISSUES/PR。[在 SNACK 上尝试](https://snack.expo.dev/@zhaodonghao586/simple-carousel)
3630

3731
<p align="center">
3832
使用react-native-snap-carousel快速滑动,当到连接处时可以看清楚的看到卡顿。(gif 4.6mb)
@@ -72,28 +66,22 @@ npm install react-native-reanimated-carousel
7266

7367
## 使用
7468

75-
```typescript
69+
```tsx
7670
import Carousel from 'react-native-reanimated-carousel';
7771

78-
// ...
79-
8072
<Carousel<{ color: string }>
81-
width={width}
73+
width={ width }
8274
data={[{ color: 'red' }, { color: 'purple' }, { color: 'yellow' }]}
8375
renderItem={({ color }) => {
84-
return (
85-
<View
86-
style={{
87-
backgroundColor: color,
88-
justifyContent: 'center',
89-
flex: 1,
90-
}}
91-
/>
92-
);
76+
return <View style={{ backgroundColor: color, flex: 1}}/>
9377
}}
9478
/>;
9579
```
9680

81+
## 优化
82+
- 当渲染大量元素时,可使用`windowSize`属性,来控制当前元素的两侧渲染数量,默认为全量渲染。经测试不加此属性,渲染200个空view时会出现掉帧情况,设置此属性后渲染1000个空view依旧流畅。(具体数量与测试的手机型号相关)
83+
84+
9785
## Props
9886

9987
| name | required | default | types | description |
@@ -115,7 +103,8 @@ import Carousel from 'react-native-reanimated-carousel';
115103
| onSnapToItem || | (index: number) => void | 切换至另一张轮播图时触发 |
116104
| onScrollBegin || | () => void | 切换动画开始时触发 |
117105
| onScrollEnd || | (previous: number, current: number) => void | 切换动画结束时触发 |
118-
| panGestureHandlerProps || {} | Omit<Partial\<PanGestureHandlerProps\>,'onHandlerStateChange'> | PanGestureHandler props |
106+
| panGestureHandlerProps | ❌ | {} | Omit<Partial\<PanGestureHandlerProps\>,'onHandlerStateChange'> | PanGestureHandler props
107+
| windowSize || 0 | number | 能响应平移手势事件的最大item数量,0表示所有元素都会先响应 |
119108
| onProgressChange || | onProgressChange?: (offsetProgress: number,absoluteProgress: number) => void | 当滚动进度发生变化时触发 `offsetProgress`:总的偏移值 (0 390 780 ...); `absoluteProgress`:转化为 index 的进度变化 (0 1 2 ...) |
120109

121110
## Ref

src/Carousel.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { useCarouselController } from './hooks/useCarouselController';
2121
import { useAutoPlay } from './hooks/useAutoPlay';
2222
import { useIndexController } from './hooks/useIndexController';
2323
import { usePropsErrorBoundary } from './hooks/usePropsErrorBoundary';
24+
import { useVisibleRanges } from './hooks/useVisibleRanges';
2425

2526
const defaultSpringConfig: Animated.WithSpringConfig = {
2627
damping: 100,
@@ -94,6 +95,13 @@ export interface ICarouselProps<T extends unknown> {
9495
Partial<PanGestureHandlerProps>,
9596
'onHandlerStateChange'
9697
>;
98+
/**
99+
* Determines the maximum number of items will respond to pan gesture events,
100+
* windowSize={11} will active visible item plus up to 5 items above and 5 below the viewpor,
101+
* Reducing this number will reduce the calculation of the animation value and may improve performance.
102+
* @default 0 all items will respond to pan gesture events.
103+
*/
104+
windowSize?: number;
97105
/**
98106
* Render carousel item.
99107
*/
@@ -160,6 +168,7 @@ function Carousel<T extends unknown = any>(
160168
renderItem,
161169
onSnapToItem,
162170
onProgressChange,
171+
windowSize,
163172
} = props;
164173

165174
usePropsErrorBoundary({
@@ -379,6 +388,13 @@ function Carousel<T extends unknown = any>(
379388
[getCurrentIndex, goToIndex, next, prev]
380389
);
381390

391+
const visibleRanges = useVisibleRanges({
392+
total: data.length,
393+
viewSize: width,
394+
translation: handlerOffsetX,
395+
windowSize,
396+
});
397+
382398
const renderLayout = React.useCallback(
383399
(item: T, i: number) => {
384400
switch (mode) {
@@ -393,6 +409,7 @@ function Carousel<T extends unknown = any>(
393409
index={i}
394410
key={i}
395411
loop={loop}
412+
visibleRanges={visibleRanges}
396413
>
397414
{renderItem(item, i)}
398415
</ParallaxLayout>
@@ -407,6 +424,7 @@ function Carousel<T extends unknown = any>(
407424
index={i}
408425
key={i}
409426
loop={loop}
427+
visibleRanges={visibleRanges}
410428
>
411429
{renderItem(item, i)}
412430
</CarouselItem>
@@ -423,6 +441,7 @@ function Carousel<T extends unknown = any>(
423441
parallaxScrollingScale,
424442
width,
425443
renderItem,
444+
visibleRanges,
426445
]
427446
);
428447

src/CarouselItem.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from 'react';
22
import { FlexStyle, View } from 'react-native';
33
import Animated, { useAnimatedStyle } from 'react-native-reanimated';
44
import { useOffsetX } from './hooks/useOffsetX';
5+
import type { IVisibleRanges } from './hooks/useVisibleRanges';
56

67
export const CarouselItem: React.FC<{
78
loop?: boolean;
@@ -10,6 +11,7 @@ export const CarouselItem: React.FC<{
1011
width: number;
1112
data: unknown[];
1213
height?: FlexStyle['height'];
14+
visibleRanges: IVisibleRanges;
1315
}> = (props) => {
1416
const {
1517
handlerOffsetX,
@@ -19,15 +21,19 @@ export const CarouselItem: React.FC<{
1921
height = '100%',
2022
loop,
2123
data,
24+
visibleRanges,
2225
} = props;
2326

24-
const x = useOffsetX({
25-
handlerOffsetX,
26-
index,
27-
width,
28-
data,
29-
loop,
30-
});
27+
const x = useOffsetX(
28+
{
29+
handlerOffsetX,
30+
index,
31+
width,
32+
data,
33+
loop,
34+
},
35+
visibleRanges
36+
);
3137

3238
const offsetXStyle = useAnimatedStyle(() => {
3339
return {

src/hooks/useOffsetX.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import Animated, {
33
interpolate,
44
useDerivedValue,
55
} from 'react-native-reanimated';
6+
import type { IVisibleRanges } from './useVisibleRanges';
67

78
interface IOpts {
89
index: number;
@@ -14,7 +15,7 @@ interface IOpts {
1415
loop?: boolean;
1516
}
1617

17-
export const useOffsetX = (opts: IOpts) => {
18+
export const useOffsetX = (opts: IOpts, visibleRanges: IVisibleRanges) => {
1819
const {
1920
handlerOffsetX,
2021
index,
@@ -30,6 +31,13 @@ export const useOffsetX = (opts: IOpts) => {
3031
const HALF_WIDTH = 0.5 * width;
3132

3233
const x = useDerivedValue(() => {
34+
const { negativeRange, positiveRange } = visibleRanges.value;
35+
if (
36+
(index < negativeRange[0] || index > negativeRange[1]) &&
37+
(index < positiveRange[0] || index > positiveRange[1])
38+
) {
39+
return Number.MAX_SAFE_INTEGER;
40+
}
3341
if (loop) {
3442
const positiveCount =
3543
type === 'positive' ? viewCount : VALID_LENGTH - viewCount;
@@ -71,7 +79,7 @@ export const useOffsetX = (opts: IOpts) => {
7179
}
7280

7381
return handlerOffsetX.value + width * index;
74-
}, [loop, data, viewCount, type]);
82+
}, [loop, data, viewCount, type, visibleRanges]);
7583

7684
return x;
7785
};

src/hooks/useVisibleRanges.tsx

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import type Animated from 'react-native-reanimated';
2+
import { useDerivedValue } from 'react-native-reanimated';
3+
4+
export type IVisibleRanges = Animated.SharedValue<{
5+
negativeRange: number[];
6+
positiveRange: number[];
7+
}>;
8+
9+
export function useVisibleRanges(options: {
10+
total: number;
11+
viewSize: number;
12+
windowSize?: number;
13+
translation: Animated.SharedValue<number>;
14+
}): IVisibleRanges {
15+
const { total, viewSize, windowSize = 0, translation } = options;
16+
17+
const ranges = useDerivedValue(() => {
18+
const positiveCount = Math.round(windowSize / 2);
19+
const negativeCount = windowSize - positiveCount;
20+
let curIndex = Math.round(-translation.value / viewSize);
21+
curIndex = curIndex < 0 ? (curIndex % total) + total : curIndex;
22+
const negativeRange = [
23+
(curIndex - negativeCount + total) % total,
24+
(curIndex - 1 + total) % total,
25+
];
26+
const positiveRange = [
27+
(curIndex + total) % total,
28+
(curIndex + positiveCount + total) % total,
29+
];
30+
if (negativeRange[0] < total && negativeRange[0] > negativeRange[1]) {
31+
negativeRange[1] = total - 1;
32+
positiveRange[0] = 0;
33+
}
34+
if (positiveRange[0] > positiveRange[1]) {
35+
negativeRange[1] = total - 1;
36+
positiveRange[0] = 0;
37+
}
38+
return { negativeRange, positiveRange };
39+
}, [total, windowSize, translation]);
40+
41+
return ranges;
42+
}

src/layouts/ParallaxLayout.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import Animated, {
77
useAnimatedStyle,
88
} from 'react-native-reanimated';
99
import { useOffsetX } from '../hooks/useOffsetX';
10+
import type { IVisibleRanges } from '../hooks/useVisibleRanges';
1011

1112
export const ParallaxLayout: React.FC<{
1213
loop?: boolean;
@@ -16,6 +17,7 @@ export const ParallaxLayout: React.FC<{
1617
index: number;
1718
width: number;
1819
data: unknown[];
20+
visibleRanges: IVisibleRanges;
1921
}> = (props) => {
2022
const {
2123
handlerOffsetX,
@@ -26,15 +28,19 @@ export const ParallaxLayout: React.FC<{
2628
loop,
2729
data,
2830
children,
31+
visibleRanges,
2932
} = props;
3033

31-
const x = useOffsetX({
32-
handlerOffsetX,
33-
index,
34-
width,
35-
data,
36-
loop,
37-
});
34+
const x = useOffsetX(
35+
{
36+
handlerOffsetX,
37+
index,
38+
width,
39+
data,
40+
loop,
41+
},
42+
visibleRanges
43+
);
3844

3945
const offsetXStyle = useAnimatedStyle(() => {
4046
const baseTranslateX = x.value - index * width;

src/utils/log.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
* In worklet
33
* e.g. runOnJS(lop)(...);
44
*/
5-
export function log(msg: any) {
6-
console.log(msg);
5+
export function log(...msg: any) {
6+
console.log(...msg);
77
}

0 commit comments

Comments
 (0)