Skip to content

Instantly share code, notes, and snippets.

@ra-kesh
Forked from FlorianRappl/useCarousel.ts
Created June 10, 2022 11:34
Show Gist options
  • Save ra-kesh/bdd2b8f6eb50bec3b3ac935714e2b0c6 to your computer and use it in GitHub Desktop.
Save ra-kesh/bdd2b8f6eb50bec3b3ac935714e2b0c6 to your computer and use it in GitHub Desktop.

Revisions

  1. @FlorianRappl FlorianRappl revised this gist Jul 29, 2019. 1 changed file with 4 additions and 4 deletions.
    8 changes: 4 additions & 4 deletions useCarousel.ts
    Original file line number Diff line number Diff line change
    @@ -94,13 +94,13 @@ function carouselReducer(state: CarouselState, action: CarouselAction): Carousel
    }
    }

    function swiped(e: EventData, dispatch: React.Dispatch<CarouselAction>, dir: 1 | -1) {
    function swiped(e: EventData, dispatch: React.Dispatch<CarouselAction>, length: number, dir: 1 | -1) {
    const t = threshold(e.event.target);
    const d = dir * e.deltaX;

    if (d >= t) {
    dispatch({
    type: d > 0 ? 'next' : 'prev',
    type: dir > 0 ? 'next' : 'prev',
    length,
    });
    } else {
    @@ -124,10 +124,10 @@ export function useCarousel(
    });
    },
    onSwipedLeft(e) {
    swiped(e, dispatch, 1);
    swiped(e, dispatch, length, 1);
    },
    onSwipedRight(e) {
    swiped(e, dispatch, -1);
    swiped(e, dispatch, length, -1);
    },
    trackMouse: true,
    trackTouch: true,
  2. @FlorianRappl FlorianRappl created this gist Jul 23, 2019.
    168 changes: 168 additions & 0 deletions useCarousel.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,168 @@
    import { useReducer, useEffect } from 'react';
    import { useSwipeable, SwipeableHandlers, EventData } from 'react-swipeable';

    function previous(length: number, current: number) {
    return (current - 1 + length) % length;
    }

    function next(length: number, current: number) {
    return (current + 1) % length;
    }

    function threshold(target: EventTarget) {
    const width = (target as HTMLElement).clientWidth;
    return width / 3;
    }

    const transitionTime = 400;
    const elastic = `transform ${transitionTime}ms cubic-bezier(0.68, -0.55, 0.265, 1.55)`;
    const smooth = `transform ${transitionTime}ms ease`;

    interface CarouselState {
    offset: number;
    desired: number;
    active: number;
    }

    const initialCarouselState: CarouselState = {
    offset: 0,
    desired: 0,
    active: 0,
    };

    interface CarouselNextAction {
    type: 'next';
    length: number;
    }

    interface CarouselPrevAction {
    type: 'prev';
    length: number;
    }

    interface CarouselJumpAction {
    type: 'jump';
    desired: number;
    }

    interface CarouselDoneAction {
    type: 'done';
    }

    interface CarouselDragAction {
    type: 'drag';
    offset: number;
    }

    type CarouselAction =
    | CarouselJumpAction
    | CarouselNextAction
    | CarouselPrevAction
    | CarouselDragAction
    | CarouselDoneAction;

    function carouselReducer(state: CarouselState, action: CarouselAction): CarouselState {
    switch (action.type) {
    case 'jump':
    return {
    ...state,
    desired: action.desired,
    };
    case 'next':
    return {
    ...state,
    desired: next(action.length, state.active),
    };
    case 'prev':
    return {
    ...state,
    desired: previous(action.length, state.active),
    };
    case 'done':
    return {
    ...state,
    offset: NaN,
    active: state.desired,
    };
    case 'drag':
    return {
    ...state,
    offset: action.offset,
    };
    default:
    return state;
    }
    }

    function swiped(e: EventData, dispatch: React.Dispatch<CarouselAction>, dir: 1 | -1) {
    const t = threshold(e.event.target);
    const d = dir * e.deltaX;

    if (d >= t) {
    dispatch({
    type: d > 0 ? 'next' : 'prev',
    length,
    });
    } else {
    dispatch({
    type: 'drag',
    offset: 0,
    });
    }
    }

    export function useCarousel(
    length: number,
    interval: number,
    ): [number, (n: number) => void, SwipeableHandlers, React.CSSProperties] {
    const [state, dispatch] = useReducer(carouselReducer, initialCarouselState);
    const handlers = useSwipeable({
    onSwiping(e) {
    dispatch({
    type: 'drag',
    offset: -e.deltaX,
    });
    },
    onSwipedLeft(e) {
    swiped(e, dispatch, 1);
    },
    onSwipedRight(e) {
    swiped(e, dispatch, -1);
    },
    trackMouse: true,
    trackTouch: true,
    });

    useEffect(() => {
    const id = setTimeout(() => dispatch({ type: 'next', length }), interval);
    return () => clearTimeout(id);
    }, [state.offset, state.active]);

    useEffect(() => {
    const id = setTimeout(() => dispatch({ type: 'done' }), transitionTime);
    return () => clearTimeout(id);
    }, [state.desired]);

    const style: React.CSSProperties = {
    transform: 'translateX(0)',
    width: `${100 * (length + 2)}%`,
    left: `-${(state.active + 1) * 100}%`,
    };

    if (state.desired !== state.active) {
    const dist = Math.abs(state.active - state.desired);
    const pref = Math.sign(state.offset || 0);
    const dir = (dist > length / 2 ? 1 : -1) * Math.sign(state.desired - state.active);
    const shift = (100 * (pref || dir)) / (length + 2);
    style.transition = smooth;
    style.transform = `translateX(${shift}%)`;
    } else if (!isNaN(state.offset)) {
    if (state.offset !== 0) {
    style.transform = `translateX(${state.offset}px)`;
    } else {
    style.transition = elastic;
    }
    }

    return [state.active, n => dispatch({ type: 'jump', desired: n }), handlers, style];
    }