Skip to content

Commit 942a8fe

Browse files
authored
Merge pull request #427 from dohooo/develop
feat: added maxScrollDistancePerSwipe prop
2 parents 7e0049c + 8ea24ca commit 942a8fe

File tree

5 files changed

+84
-33
lines changed

5 files changed

+84
-33
lines changed

.changeset/fluffy-carrots-agree.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'react-native-reanimated-carousel': minor
3+
---
4+
5+
Added `maxScrollDistancePerSwipe` props.

docs/props.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
| enabled || true | boolean | when false, Carousel will not respond to any gestures |
3434
| customConfig || | () => {type?: 'negative' \| 'positive';viewCount?: number;} | Custom carousel config |
3535
| customAnimation || | (value: number) => Animated.AnimatedStyleProp<ViewStyle> | Custom animations. For details, see below[custom animation](./custom-animation.md) |
36+
| maxScrollDistancePerSwipe || | number | Maximum offset value for one scroll. If `props.vertical = true`, this will be `maxScrollDistancePerSwipeY`. If `props.vertical = false`, this will be `maxScrollDistancePerSwipeX`. |
3637

3738
## `modeConfig` Props
3839

docs/props.zh-CN.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
| enabled || true | boolean | 当值为 false 时,轮播图将不会响应任何手势行为 |
3434
| customConfig || | () => {type?: 'negative' \| 'positive';viewCount?: number;} | 自定义轮播图内部配置 |
3535
| customAnimation || | (value: number) => Animated.AnimatedStyleProp<ViewStyle> | 自定动画,详情见[自定义动画](./custom-animation.zh-CN.md) |
36+
| maxScrollDistancePerSwipe || | number | 单次滚动的最大偏移值。如果`props.vertical = true`,则表示`maxScrollDistancePerSwipeY`;如果`props.vertical = false`,则表示`maxScrollDistancePerSwipeX`|
3637

3738
## `modeConfig` Props
3839

src/ScrollViewGesture.tsx

Lines changed: 71 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ const IScrollViewGesture: React.FC<Props> = (props) => {
5252
enabled,
5353
dataLength,
5454
overscrollEnabled,
55+
maxScrollDistancePerSwipe,
5556
},
5657
} = React.useContext(CTX);
5758

@@ -122,33 +123,39 @@ const IScrollViewGesture: React.FC<Props> = (props) => {
122123
// Default to scroll in the direction of the slide (with deceleration)
123124
let finalTranslation: number = withDecay({ velocity, deceleration: 0.999 });
124125

126+
// If the distance of the swipe exceeds the max scroll distance, keep the view at the current position
127+
if (typeof maxScrollDistancePerSwipe === "number" && Math.abs(scrollEndTranslation.value) > maxScrollDistancePerSwipe) {
128+
finalTranslation = origin;
129+
}
130+
else {
125131
/**
126132
* The page size is the same as the item size.
127133
* If direction is vertical, the page size is the height of the item.
128134
* If direction is horizontal, the page size is the width of the item.
129-
*
130-
* `page size` equals to `size` variable.
131-
* */
132-
if (pagingEnabled) {
133-
// distance with direction
134-
const offset = -(scrollEndTranslation.value >= 0 ? 1 : -1); // 1 or -1
135-
const computed = offset < 0 ? Math.ceil : Math.floor;
136-
const page = computed(-translation.value / size);
137-
138-
if (infinite) {
139-
const finalPage = page + offset;
140-
finalTranslation = withSpring(withProcessTranslation(-finalPage * size), onFinished);
141-
}
142-
else {
143-
const finalPage = Math.min(maxPage - 1, Math.max(0, page + offset));
144-
finalTranslation = withSpring(withProcessTranslation(-finalPage * size), onFinished);
135+
*
136+
* `page size` equals to `size` variable.
137+
* */
138+
if (pagingEnabled) {
139+
// distance with direction
140+
const offset = -(scrollEndTranslation.value >= 0 ? 1 : -1); // 1 or -1
141+
const computed = offset < 0 ? Math.ceil : Math.floor;
142+
const page = computed(-translation.value / size);
143+
144+
if (infinite) {
145+
const finalPage = page + offset;
146+
finalTranslation = withSpring(withProcessTranslation(-finalPage * size), onFinished);
147+
}
148+
else {
149+
const finalPage = Math.min(maxPage - 1, Math.max(0, page + offset));
150+
finalTranslation = withSpring(withProcessTranslation(-finalPage * size), onFinished);
151+
}
145152
}
146-
}
147153

148-
if (!pagingEnabled && snapEnabled) {
149-
// scroll to the nearest item
150-
const nextPage = Math.round((origin + velocity * 0.4) / size) * size;
151-
finalTranslation = withSpring(withProcessTranslation(nextPage), onFinished);
154+
if (!pagingEnabled && snapEnabled) {
155+
// scroll to the nearest item
156+
const nextPage = Math.round((origin + velocity * 0.4) / size) * size;
157+
finalTranslation = withSpring(withProcessTranslation(nextPage), onFinished);
158+
}
152159
}
153160

154161
translation.value = finalTranslation;
@@ -164,15 +171,16 @@ const IScrollViewGesture: React.FC<Props> = (props) => {
164171
}
165172
},
166173
[
167-
translation,
168-
scrollEndVelocity.value,
169-
pagingEnabled,
174+
withSpring,
170175
size,
171-
scrollEndTranslation.value,
176+
maxPage,
172177
infinite,
173-
withSpring,
174178
snapEnabled,
175-
maxPage,
179+
translation,
180+
pagingEnabled,
181+
scrollEndVelocity.value,
182+
maxScrollDistancePerSwipe,
183+
scrollEndTranslation.value,
176184
],
177185
);
178186

@@ -240,6 +248,18 @@ const IScrollViewGesture: React.FC<Props> = (props) => {
240248
[pagingEnabled, resetBoundary],
241249
);
242250

251+
function withProcessTranslation(translation: number) {
252+
"worklet";
253+
254+
if (!infinite && !overscrollEnabled) {
255+
const limit = getLimit();
256+
const sign = Math.sign(translation);
257+
return sign * Math.max(0, Math.min(limit, Math.abs(translation)));
258+
}
259+
260+
return translation;
261+
}
262+
243263
const panGestureEventHandler = useAnimatedGestureHandler<
244264
PanGestureHandlerGestureEvent,
245265
GestureContext
@@ -262,10 +282,19 @@ const IScrollViewGesture: React.FC<Props> = (props) => {
262282
cancelAnimation(translation);
263283
}
264284
touching.value = true;
265-
const { translationX, translationY } = e;
266-
const panTranslation = isHorizontal.value
267-
? translationX
268-
: translationY;
285+
let { translationX, translationY } = e;
286+
287+
const totalTranslation = isHorizontal.value ? translationX : translationY;
288+
289+
if (typeof maxScrollDistancePerSwipe === "number" && Math.abs(totalTranslation) > maxScrollDistancePerSwipe) {
290+
const overSwipe = Math.abs(totalTranslation) - maxScrollDistancePerSwipe;
291+
const dampedTranslation = maxScrollDistancePerSwipe + overSwipe * 0.5;
292+
293+
translationX = isHorizontal.value ? dampedTranslation * Math.sign(translationX) : translationX;
294+
translationY = !isHorizontal.value ? dampedTranslation * Math.sign(translationY) : translationY;
295+
}
296+
297+
const panTranslation = isHorizontal.value ? translationX : translationY;
269298
if (!infinite) {
270299
if ((translation.value > 0 || translation.value < -ctx.max)) {
271300
const boundary = translation.value > 0 ? 0 : -ctx.max;
@@ -277,9 +306,10 @@ const IScrollViewGesture: React.FC<Props> = (props) => {
277306
}
278307

279308
const translationValue = ctx.panOffset + panTranslation;
309+
280310
translation.value = translationValue;
281311
},
282-
onEnd: (e) => {
312+
onEnd: (e, ctx) => {
283313
const { velocityX, velocityY, translationX, translationY } = e;
284314
scrollEndVelocity.value = isHorizontal.value
285315
? velocityX
@@ -288,7 +318,15 @@ const IScrollViewGesture: React.FC<Props> = (props) => {
288318
? translationX
289319
: translationY;
290320

291-
endWithSpring(onScrollEnd);
321+
const totalTranslation = isHorizontal.value ? translationX : translationY;
322+
323+
if (typeof maxScrollDistancePerSwipe === "number" && Math.abs(totalTranslation) > maxScrollDistancePerSwipe) {
324+
const nextPage = Math.round((ctx.panOffset + maxScrollDistancePerSwipe * Math.sign(totalTranslation)) / size) * size;
325+
translation.value = withSpring(withProcessTranslation(nextPage), onScrollEnd);
326+
}
327+
else {
328+
endWithSpring(onScrollEnd);
329+
}
292330

293331
if (!infinite)
294332
touching.value = false;

src/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,12 @@ export type TCarouselProps<T = any> = {
153153
* Used to locate this view in end-to-end tests.
154154
*/
155155
testID?: string
156+
/**
157+
* Maximum offset value for once scroll.
158+
* props.vertical = true => maxScrollDistancePerSwipeY
159+
* props.vertical = false => maxScrollDistancePerSwipeX
160+
* */
161+
maxScrollDistancePerSwipe?: number
156162
/**
157163
* Custom carousel config.
158164
*/

0 commit comments

Comments
 (0)