UNPKG

react-native-reanimated-carousel

Version:

Simple carousel component.fully implemented using Reanimated 2.Infinitely scrolling, very smooth.

363 lines (330 loc) 8.13 kB
import { useMemo } from "react"; import type { TransformsStyle, ViewStyle } from "react-native"; import { Dimensions } from "react-native"; import { Extrapolate, interpolate } from "react-native-reanimated"; import type { IComputedDirectionTypes, CustomConfig } from "../types"; const screen = Dimensions.get("window"); export interface ILayoutConfig { showLength?: number moveSize?: number stackInterval?: number scaleInterval?: number opacityInterval?: number rotateZDeg?: number snapDirection?: "left" | "right" } export type TStackModeProps = IComputedDirectionTypes<{ /** * Carousel Animated transitions. */ mode?: "horizontal-stack" | "vertical-stack" /** * Stack animation style. * @default * mode: 'vertical', * snapDirection: 'right', * moveSize: window.width, * stackInterval: 30, * scaleInterval: 0.08, * rotateZDeg: 135, */ modeConfig?: ILayoutConfig }>; export function horizontalStackLayout(modeConfig: ILayoutConfig = {}) { return (_value: number) => { "worklet"; const { showLength, snapDirection = "left", moveSize = screen.width, stackInterval = 18, scaleInterval = 0.04, opacityInterval = 0.1, rotateZDeg = 30, } = modeConfig; const transform: TransformsStyle["transform"] = []; const { validLength, value, inputRange } = getCommonVariables({ showLength: showLength!, value: _value, snapDirection, }); const { zIndex, opacity } = getCommonStyles({ validLength, value, opacityInterval, snapDirection, }); const styles: ViewStyle = { transform, zIndex, opacity, }; let translateX: number; let scale: number; let rotateZ: string; if (snapDirection === "left") { translateX = interpolate( value, inputRange, [-moveSize, 0, validLength * stackInterval], Extrapolate.CLAMP, ); scale = interpolate( value, inputRange, [1, 1, 1 - validLength * scaleInterval], Extrapolate.CLAMP, ); rotateZ = `${interpolate( value, inputRange, [-rotateZDeg, 0, 0], Extrapolate.CLAMP, )}deg`; } else if (snapDirection === "right") { translateX = interpolate( value, inputRange, [-validLength * stackInterval, 0, moveSize], Extrapolate.CLAMP, ); scale = interpolate( value, inputRange, [1 - validLength * scaleInterval, 1, 1], Extrapolate.CLAMP, ); rotateZ = `${interpolate( value, inputRange, [0, 0, rotateZDeg], Extrapolate.CLAMP, )}deg`; } transform.push( { translateX: translateX!, }, { scale: scale!, }, { rotateZ: rotateZ!, }, ); return styles; }; } export function useHorizontalStackLayout( customAnimationConfig: ILayoutConfig = {}, customConfig: CustomConfig = {}, ) { const config = useMemo( () => ({ type: customAnimationConfig.snapDirection === "right" ? "negative" : "positive", viewCount: customAnimationConfig.showLength, ...customConfig, }), [customAnimationConfig, customConfig], ); return { layout: horizontalStackLayout(customAnimationConfig), config, }; } export function verticalStackLayout(modeConfig: ILayoutConfig = {}) { return (_value: number) => { "worklet"; const { showLength, snapDirection = "left", moveSize = screen.width, stackInterval = 18, scaleInterval = 0.04, opacityInterval = 0.1, rotateZDeg = 30, } = modeConfig; const transform: TransformsStyle["transform"] = []; const { validLength, value, inputRange } = getCommonVariables({ showLength: showLength!, value: _value, snapDirection, }); const { zIndex, opacity } = getCommonStyles({ validLength, value, opacityInterval, snapDirection, }); const styles: ViewStyle = { transform, zIndex, opacity, }; let translateX: number; let scale: number; let rotateZ: string; let translateY: number; if (snapDirection === "left") { translateX = interpolate( value, inputRange, [-moveSize, 0, 0], Extrapolate.CLAMP, ); scale = interpolate( value, inputRange, [1, 1, 1 - validLength * scaleInterval], Extrapolate.CLAMP, ); rotateZ = `${interpolate( value, inputRange, [-rotateZDeg, 0, 0], Extrapolate.CLAMP, )}deg`; translateY = interpolate( value, inputRange, [0, 0, validLength * stackInterval], Extrapolate.CLAMP, ); } else if (snapDirection === "right") { translateX = interpolate( value, inputRange, [0, 0, moveSize], Extrapolate.CLAMP, ); scale = interpolate( value, inputRange, [1 - validLength * scaleInterval, 1, 1], Extrapolate.CLAMP, ); rotateZ = `${interpolate( value, inputRange, [0, 0, rotateZDeg], Extrapolate.CLAMP, )}deg`; translateY = interpolate( value, inputRange, [validLength * stackInterval, 0, 0], Extrapolate.CLAMP, ); } transform.push( { translateX: translateX!, }, { scale: scale!, }, { rotateZ: rotateZ!, }, { translateY: translateY!, }, ); return styles; }; } function getCommonVariables(opts: { value: number showLength: number snapDirection: "left" | "right" }) { "worklet"; const { showLength, value: _value, snapDirection } = opts; function easeInOutCubic(v: number): number { return v < 0.5 ? 4 * v * v * v : 1 - (-2 * v + 2) ** 3 / 2; } const page = Math.floor(Math.abs(_value)); const diff = Math.abs(_value) % 1; const value = _value < 0 ? -(page + easeInOutCubic(diff)) : page + easeInOutCubic(diff); const validLength = showLength! - 1; let inputRange: [number, number, number]; if (snapDirection === "left") inputRange = [-1, 0, validLength]; else if (snapDirection === "right") inputRange = [-validLength, 0, 1]; else throw new Error("snapDirection must be set to either left or right"); return { inputRange, validLength, value, }; } function getCommonStyles(opts: { value: number validLength: number opacityInterval: number snapDirection: "left" | "right" }) { "worklet"; const { snapDirection, validLength, value, opacityInterval } = opts; let zIndex: number; let opacity: number; if (snapDirection === "left") { zIndex = Math.floor( interpolate( value, [-1.5, -1, -1 + Number.MIN_VALUE, 0, validLength], [ Number.MIN_VALUE, validLength, validLength, validLength - 1, -1, ], ) * 10000, ) / 100; opacity = interpolate( value, [-1, 0, validLength - 1, validLength], [0.25, 1, 1 - (validLength - 1) * opacityInterval, 0.25], ); } else if (snapDirection === "right") { zIndex = Math.floor( interpolate( value, [-validLength, 0, 1 - Number.MIN_VALUE, 1, 1.5], [ 1, validLength - 1, validLength, validLength, Number.MIN_VALUE, ], ) * 10000, ) / 100; opacity = interpolate( value, [-validLength, 1 - validLength, 0, 1], [0.25, 1 - (validLength - 1) * opacityInterval, 1, 0.25], ); } else { throw new Error("snapDirection must be set to either left or right"); } return { zIndex, opacity, }; }