Skip to content

Commit

Permalink
feat: improve sliding experience
Browse files Browse the repository at this point in the history
BREAKING CHANGE: remove props "timingConfig", add "springConfig" with no support of duration
  • Loading branch information
gxxgcn committed Oct 30, 2021
1 parent 20ff7e1 commit 14b62ee
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 137 deletions.
2 changes: 0 additions & 2 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ export default function App() {
<View style={{ height: 300 }}>
<Carousel<ImageSourcePropType>
autoPlay
timingConfig={{ duration: 500 }}
autoPlayInterval={2000}
ref={r}
width={width}
Expand All @@ -48,7 +47,6 @@ export default function App() {
<View style={{ height: 300 }}>
<Carousel<ImageSourcePropType>
autoPlay
timingConfig={{ duration: 500 }}
autoPlayInterval={2000}
ref={r}
mode="parallax"
Expand Down
146 changes: 59 additions & 87 deletions src/Carousel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import {
PanGestureHandlerGestureEvent,
} from 'react-native-gesture-handler';
import Animated, {
cancelAnimation,
runOnJS,
useAnimatedGestureHandler,
useDerivedValue,
useSharedValue,
withTiming,
withSpring,
} from 'react-native-reanimated';
import { CarouselItem } from './CarouselItem';
import type { TMode } from './layouts';
Expand All @@ -19,12 +20,10 @@ import { useCarouselController } from './useCarouselController';
import { useComputedAnim } from './useComputedAnim';
import { useAutoPlay } from './useAutoPlay';
import { useIndexController } from './useIndexController';
import { useLockController } from './useLock';

const defaultTimingConfig: Animated.WithTimingConfig = {
duration: 250,
const defaultSpringConfig: Animated.WithSpringConfig = {
damping: 40,
};

export interface ICarouselProps<T extends unknown> {
ref?: React.Ref<ICarouselInstance>;
/**
Expand Down Expand Up @@ -87,9 +86,9 @@ export interface ICarouselProps<T extends unknown> {
*/
onSnapToItem?: (index: number) => void;
/**
* Timing config of translation animated
* Sping config of translation animated
*/
timingConfig?: Animated.WithTimingConfig;
springConfig?: Animated.WithSpringConfig;
/**
* On scroll begin
*/
Expand Down Expand Up @@ -143,21 +142,14 @@ function Carousel<T extends unknown = any>(
parallaxScrollingScale,
onSnapToItem,
style,
timingConfig = defaultTimingConfig,
panGestureHandlerProps = {},
} = props;

if (
typeof timingConfig.duration === 'number' &&
timingConfig.duration > autoPlayInterval
) {
throw Error(
'The during time of animation must less than autoplay interval.'
);
}

const timingConfig = {
...defaultSpringConfig,
...props.springConfig,
};
const width = Math.round(props.width);
const lockController = useLockController();
const handlerOffsetX = useSharedValue<number>(0);
const data = React.useMemo<T[]>(() => {
if (!loop) return _data;
Expand Down Expand Up @@ -189,8 +181,6 @@ function Carousel<T extends unknown = any>(
width,
handlerOffsetX,
indexController,
lockController,
timingConfig,
disable: !data.length,
onScrollBegin: () => runOnJS(onScrollBegin)(),
onScrollEnd: () => runOnJS(onScrollEnd)(),
Expand Down Expand Up @@ -245,93 +235,75 @@ function Carousel<T extends unknown = any>(
useAnimatedGestureHandler<PanGestureHandlerGestureEvent>(
{
onStart: (_, ctx: any) => {
if (lockController.isLock()) return;
runOnJS(pause)();
runOnJS(onScrollBegin)();
ctx.startContentOffsetX = handlerOffsetX.value;
cancelAnimation(handlerOffsetX);
ctx.currentContentOffsetX = handlerOffsetX.value;
ctx.start = true;
},
onActive: (e, ctx: any) => {
if (lockController.isLock() || !ctx.start) return;
/**
* `onActive` and `onEnd` return different values of translationX!So that creates a bias!TAT
* */
ctx.translationX = e.translationX;
if (loop) {
const { translationX } = e;
if (
!loop &&
(handlerOffsetX.value >= 0 ||
handlerOffsetX.value <= -(data.length - 1) * width)
) {
handlerOffsetX.value =
ctx.currentContentOffsetX + e.translationX;
ctx.currentContentOffsetX + translationX / 2;
return;
}
handlerOffsetX.value = Math.max(
Math.min(ctx.currentContentOffsetX + e.translationX, 0),
-(data.length - 1) * width
);
handlerOffsetX.value =
ctx.currentContentOffsetX + translationX;
},
onEnd: (e, ctx: any) => {
if (lockController.isLock() || !ctx.start) return;
const translationX = ctx.translationX;
function _withTimingCallback(num: number) {
return withTiming(num, timingConfig, (isFinished) => {
if (isFinished) {
ctx.start = false;
lockController.unLock();
runOnJS(onScrollEnd)();
onEnd: (e) => {
function _withAnimationCallback(num: number) {
return withSpring(
num,
{
...timingConfig,
velocity: e.velocityX,
},
(isFinished) => {
if (isFinished) {
runOnJS(onScrollEnd)();
}
}
});
);
}

if (translationX > 0) {
/**
* If not loop no , longer scroll when sliding to the start.
* */
if (!loop && handlerOffsetX.value >= 0) {
return;
}
lockController.lock();
if (
Math.abs(translationX) + Math.abs(e.velocityX) >
width / 2
) {
handlerOffsetX.value = _withTimingCallback(
handlerOffsetX.value + width - translationX
);
} else {
handlerOffsetX.value = _withTimingCallback(
handlerOffsetX.value - translationX
);
}
const page = Math.round(handlerOffsetX.value / width);
const velocityPage = Math.round(
(handlerOffsetX.value + e.velocityX) / width
);
const pageWithVelocity = Math.min(
page + 1,
Math.max(page - 1, velocityPage)
);

if (loop) {
handlerOffsetX.value = _withAnimationCallback(
pageWithVelocity * width
);
return;
}
if (handlerOffsetX.value >= 0) {
handlerOffsetX.value = _withAnimationCallback(0);
return;
}

if (translationX < 0) {
/**
* If not loop , no longer scroll when sliding to the end.
* */
if (
!loop &&
handlerOffsetX.value <= -(data.length - 1) * width
) {
return;
}
lockController.lock();
if (
Math.abs(translationX) + Math.abs(e.velocityX) >
width / 2
) {
handlerOffsetX.value = _withTimingCallback(
handlerOffsetX.value - width - translationX
);
} else {
handlerOffsetX.value = _withTimingCallback(
handlerOffsetX.value - translationX
);
}
if (handlerOffsetX.value <= -(data.length - 1) * width) {
handlerOffsetX.value = _withAnimationCallback(
-(data.length - 1) * width
);
return;
}

handlerOffsetX.value = _withAnimationCallback(
pageWithVelocity * width
);
},
},
[loop, data, lockController, onScrollBegin, onScrollEnd]
[loop, data, onScrollBegin, onScrollEnd]
);

React.useImperativeHandle(ref, () => {
Expand Down
28 changes: 11 additions & 17 deletions src/useCarouselController.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ import React from 'react';
import type Animated from 'react-native-reanimated';
import { runOnJS, withTiming } from 'react-native-reanimated';
import type { IIndexController } from './useIndexController';
import type { ILockController } from './useLock';

const defaultTimingConfig: Animated.WithTimingConfig = {
duration: 250,
};

interface IOpts {
loop: boolean;
width: number;
handlerOffsetX: Animated.SharedValue<number>;
lockController: ILockController;
indexController: IIndexController;
timingConfig: Animated.WithTimingConfig;
disable?: boolean;
onScrollBegin?: () => void;
onScrollEnd?: () => void;
Expand All @@ -27,25 +28,21 @@ export function useCarouselController(opts: IOpts): ICarouselController {
width,
loop,
handlerOffsetX,
timingConfig,
lockController,
indexController,
disable = false,
} = opts;

const canSliding = React.useCallback(() => {
return !disable && !lockController.isLock();
}, [lockController, disable]);
return !disable;
}, [disable]);

const onScrollEnd = React.useCallback(() => {
lockController.unLock();
opts.onScrollEnd?.();
}, [lockController, opts]);
}, [opts]);

const onScrollBegin = React.useCallback(() => {
opts.onScrollBegin?.();
lockController.lock();
}, [lockController, opts]);
}, [opts]);

const next = React.useCallback(() => {
if (
Expand All @@ -59,7 +56,7 @@ export function useCarouselController(opts: IOpts): ICarouselController {

handlerOffsetX.value = withTiming(
handlerOffsetX.value - width,
timingConfig,
defaultTimingConfig,
(isFinished: boolean) => {
if (isFinished) {
runOnJS(onScrollEnd)();
Expand All @@ -71,7 +68,6 @@ export function useCarouselController(opts: IOpts): ICarouselController {
canSliding,
onScrollBegin,
width,
timingConfig,
handlerOffsetX,
indexController,
loop,
Expand All @@ -85,7 +81,7 @@ export function useCarouselController(opts: IOpts): ICarouselController {

handlerOffsetX.value = withTiming(
handlerOffsetX.value + width,
timingConfig,
defaultTimingConfig,
(isFinished: boolean) => {
if (isFinished) {
runOnJS(onScrollEnd)();
Expand All @@ -97,7 +93,6 @@ export function useCarouselController(opts: IOpts): ICarouselController {
canSliding,
onScrollBegin,
width,
timingConfig,
handlerOffsetX,
indexController,
loop,
Expand All @@ -117,7 +112,7 @@ export function useCarouselController(opts: IOpts): ICarouselController {
if (animated) {
handlerOffsetX.value = withTiming(
offset,
timingConfig,
defaultTimingConfig,
(isFinished: boolean) => {
indexController.index.value = index;
if (isFinished) {
Expand All @@ -136,7 +131,6 @@ export function useCarouselController(opts: IOpts): ICarouselController {
onScrollBegin,
onScrollEnd,
width,
timingConfig,
indexController,
handlerOffsetX,
]
Expand Down
31 changes: 0 additions & 31 deletions src/useLock.ts

This file was deleted.

0 comments on commit 14b62ee

Please sign in to comment.