import { animated, to, useInView, useSpring } from '@react-spring/web'; import React, { useEffect } from 'react'; import { Doc } from '../../../../fields/Doc'; import { NumCast } from '../../../../fields/Types'; import { PresEffect, PresEffectDirection } from './PresEnums'; import './SlideEffect.scss'; import { emptyFunction } from '../../../../Utils'; interface SlideEffectProps { doc?: Doc; // pass in doc to extract width, height, bg dir: PresEffectDirection; presEffect: PresEffect; springSettings: { stiffness: number; damping: number; mass: number; }; children: React.ReactNode; infinite?: boolean; startOpacity?: number; // set to zero to linearly fade in while animating } const DEFAULT_WIDTH = 40; const PREVIEW_OFFSET = 60; const ACTUAL_OFFSET = 200; /** * This component wraps around the doc to create an effect animation, and also wraps the preview animations * for the effects as well. */ export default function SpringAnimation({ doc, dir, springSettings, presEffect, children, infinite, startOpacity }: SlideEffectProps) { const expandConfig = { to: { scale: 1, x: 0, y: 0 }, from: { scale: 0, x: 0, y: 0 }, }; const fadeConfig = { to: { x: 0, y: 0 }, from: { x: 0, y: 0 }, }; const rotateConfig = { to: { x: 360, y: 0 }, from: { x: 0, y: 0 }, }; const flipConfig = { to: { x: 180, y: 0 }, from: { x: 0, y: 0 }, }; const bounceConfig = { to: { x: 0, y: 0 }, from: (() => { const offset = infinite ? PREVIEW_OFFSET : ACTUAL_OFFSET; switch (dir) { case PresEffectDirection.Left: return { x: -offset, y: 0, }; case PresEffectDirection.Right: return { x: offset, y: 0, }; case PresEffectDirection.Top: return { x: 0, y: -offset, }; case PresEffectDirection.Bottom:return { x: 0, y: offset, }; default: return { x: 0, y: 0, }; // no movement for center }})(), // prettier-ignore }; const rollConfig = { to: { x: 0, y: 0 }, from: (() => { switch (dir) { case PresEffectDirection.Left: return { x: -100, y: -120, }; case PresEffectDirection.Right: return { x: 100, y: 120, }; case PresEffectDirection.Top: return { x: -100, y: -120, }; case PresEffectDirection.Bottom: return { x: -100, y: -120, }; default: return { x: 0, y: 0, }; // no movement for center }})(), // prettier-ignore }; // prettier-ignore const effectConfig = (() => { switch (presEffect) { case PresEffect.Fade: return fadeConfig; case PresEffect.Bounce: return bounceConfig; case PresEffect.Rotate: return rotateConfig; case PresEffect.Flip: return flipConfig; case PresEffect.Roll: return rollConfig; case PresEffect.Lightspeed: return { from: {}, to: {} }; case PresEffect.Expand: default: return expandConfig; } // prettier-ignore })(); const [springs, api] = useSpring( () => ({ to: { ...effectConfig.to, opacity: 1 }, from: { ...effectConfig.from, opacity: startOpacity ?? 1 }, config: { tension: springSettings.stiffness, friction: springSettings.damping, mass: springSettings.mass }, onStart: emptyFunction, onRest: emptyFunction, }), [springSettings] ); const [ref, inView] = useInView({ once: true, }); useEffect(() => { if (inView) { api.start({ loop: infinite, delay: infinite ? 500 : 0 }); } }, [inView]); const animatedDiv = (style: object) => ( `${val}`) }}> {children} ); const [width, height] = [NumCast(doc?.width, DEFAULT_WIDTH), NumCast(doc?.height, DEFAULT_WIDTH)]; const flipAxis = dir === PresEffectDirection.Bottom || dir === PresEffectDirection.Top ? 'X' : 'Y'; const [rotateX, rotateY] = flipAxis === 'X' ? ['180deg', undefined] : [undefined, '180deg']; switch (presEffect) { case PresEffect.Flip: return animatedDiv({ transform: to(springs.x, val => `perspective(600px) rotate${flipAxis}(${val}deg)`), width, height, rotateX, rotateY }) case PresEffect.Rotate:return animatedDiv({ transform: to(springs.x, val => `rotate(${val}deg)`) }); case PresEffect.Roll: return animatedDiv({ transform: to([springs.x, springs.y], (val, val2) => `translate3d(${val}%, 0, 0) rotate3d(0, 0, 1, ${val2}deg)`) }); default: return animatedDiv(springs); } // prettier-ignore }