diff options
Diffstat (limited to 'src/client/views/nodes/trails/SlideEffect.tsx')
| -rw-r--r-- | src/client/views/nodes/trails/SlideEffect.tsx | 371 |
1 files changed, 371 insertions, 0 deletions
diff --git a/src/client/views/nodes/trails/SlideEffect.tsx b/src/client/views/nodes/trails/SlideEffect.tsx new file mode 100644 index 000000000..bad1fd8fa --- /dev/null +++ b/src/client/views/nodes/trails/SlideEffect.tsx @@ -0,0 +1,371 @@ +import { useSpring, animated, easings, to, useInView } from '@react-spring/web'; +import React, { useEffect, useState } from 'react'; +import { PresEffect, PresEffectDirection } from './PresEnums'; +import './SlideEffect.scss'; +import { Doc } from '../../../../fields/Doc'; +import { NumCast } from '../../../../fields/Types'; + +interface SlideEffectProps { + // pass in doc to extract width, height, bg + doc?: Doc; + dir: PresEffectDirection; + presEffect: PresEffect; + // stiffness (figma) = tension (react-spring) + tension: number; + // damping (figma) = friction (react-spring) + friction: number; + mass: number; + children: React.ReactNode; + infinite?: boolean; +} + +const DEFAULT_WIDTH = 40; +const PREVIEW_OFFSET = 60; +const ACTUAL_OFFSET = 200; +const infiniteOptions = { + loop: true, + delay: 500, +}; + +/** + * 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, friction, tension, mass, presEffect, children, infinite }: SlideEffectProps) { + const [springs, api] = useSpring( + () => ({ + from: { + x: 0, + y: 0, + opacity: 0, + scale: 1, + }, + config: { + tension, + friction, + mass, + }, + onStart: () => {}, + onRest: () => {}, + }), + [tension, friction, mass] + ); + const [ref, inView] = useInView({ + once: true, + }); + + // Whether the animation is currently playing + const [animating, setAnimating] = useState(false); + + const zoomConfig = { + from: { + scale: 0, + x: 0, + y: 0, + opacity: 1, + }, + to: { + scale: 1, + x: 0, + y: 0, + opacity: 1, + config: { + tension: tension, + friction: friction, + mass: mass, + }, + }, + }; + + const fadeConfig = { + from: { + opacity: 0, + scale: 1, + x: 0, + y: 0, + }, + to: { + opacity: 1, + scale: 1, + x: 0, + y: 0, + config: { + tension: tension, + friction: friction, + mass: mass, + }, + }, + }; + + const rotateConfig = { + from: { + x: 0, + }, + to: { + x: 360, + config: { + tension: tension, + friction: friction, + mass: mass, + }, + }, + }; + + const getBounceConfigFrom = () => { + switch (dir) { + case PresEffectDirection.Left: + return { + from: { + opacity: 0, + x: infinite ? -PREVIEW_OFFSET : -ACTUAL_OFFSET, + y: 0, + }, + }; + case PresEffectDirection.Right: + return { + from: { + opacity: 0, + x: infinite ? PREVIEW_OFFSET : ACTUAL_OFFSET, + y: 0, + }, + }; + case PresEffectDirection.Top: + return { + from: { + opacity: 0, + x: 0, + y: infinite ? -PREVIEW_OFFSET : -ACTUAL_OFFSET, + }, + }; + case PresEffectDirection.Bottom: + return { + from: { + opacity: 0, + x: 0, + y: infinite ? PREVIEW_OFFSET : ACTUAL_OFFSET, + }, + }; + default: + // no movement for center + return { + from: { + opacity: 0, + x: 0, + y: 0, + }, + }; + } + }; + + const bounceConfig = { + ...getBounceConfigFrom(), + to: [ + { + opacity: 1, + x: 0, + y: 0, + config: { + tension: tension, + friction: friction, + mass: mass, + }, + }, + ], + }; + + const flipConfig = { + from: { + x: 0, + }, + to: { + x: 180, + config: { + tension: tension, + friction: friction, + mass: mass, + }, + }, + }; + + // only left and right for now + const getRollConfigFrom = () => { + switch (dir) { + case PresEffectDirection.Left: + return { + from: { + opacity: 0, + x: -100, + y: -120, + }, + }; + case PresEffectDirection.Right: + return { + from: { + opacity: 0, + x: 100, + y: 120, + }, + }; + case PresEffectDirection.Top: + return { + from: { + opacity: 0, + x: -100, + y: -120, + }, + }; + case PresEffectDirection.Bottom: + return { + from: { + opacity: 0, + x: -100, + y: -120, + }, + }; + default: + // no movement for center + return { + from: { + opacity: 0, + x: 0, + y: 0, + }, + }; + } + }; + + const rollConfig = { + ...getRollConfigFrom(), + to: { + opacity: 1, + x: 0, + y: 0, + config: { + tension: tension, + friction: friction, + mass: mass, + }, + }, + }; + + const lightspeedConfig = { + from: { + opacity: 0, + }, + to: [], + }; + + // Switch animation depending on slide effect + const startAnimation = () => { + api.stop(); + let config: any = zoomConfig; + switch (presEffect) { + case PresEffect.Bounce: + config = bounceConfig; + break; + case PresEffect.Zoom: + config = zoomConfig; + break; + case PresEffect.Rotate: + config = rotateConfig; + break; + case PresEffect.Fade: + config = fadeConfig; + break; + case PresEffect.Flip: + config = flipConfig; + break; + case PresEffect.Roll: + config = rollConfig; + break; + case PresEffect.Lightspeed: + break; + default: + break; + } + + if (infinite) { + config = { ...config, ...infiniteOptions }; + } + + api.start(config); + }; + + const getRenderDoc = () => { + switch (presEffect) { + case PresEffect.Rotate: + return ( + <animated.div ref={ref} style={{ transform: to(springs.x, val => `rotate(${val}deg)`) }}> + {children} + </animated.div> + ); + case PresEffect.Flip: + return ( + // Pass in doc dimensions + <div className="flip-container" ref={ref}> + {dir === PresEffectDirection.Bottom || dir === PresEffectDirection.Top ? ( + <> + <animated.div + className={'flip-side flip-back'} + style={{ + transform: to(springs.x, val => `perspective(600px) rotateX(${val}deg)`), + width: doc ? NumCast(doc.width) : DEFAULT_WIDTH, + height: doc ? NumCast(doc.height) : DEFAULT_WIDTH, + backgroundColor: infinite ? '#a825ff' : 'rgb(223, 223, 223);', + }} + /> + <animated.div + className={'flip-side flip-front'} + style={{ transform: to(springs.x, val => `perspective(600px) rotateX(${val}deg)`), rotateX: '180deg', width: doc ? NumCast(doc.width) : DEFAULT_WIDTH, height: doc ? NumCast(doc.height) : DEFAULT_WIDTH }}> + {children} + </animated.div> + </> + ) : ( + <> + <animated.div + className={'flip-side flip-back'} + style={{ transform: to(springs.x, val => `perspective(600px) rotateY(${val}deg)`), width: doc ? NumCast(doc.width) : DEFAULT_WIDTH, height: doc ? NumCast(doc.height) : DEFAULT_WIDTH }} + /> + <animated.div + className={'flip-side flip-front'} + style={{ transform: to(springs.x, val => `perspective(600px) rotateY(${val}deg)`), rotateY: '180deg', width: doc ? NumCast(doc.width) : DEFAULT_WIDTH, height: doc ? NumCast(doc.height) : DEFAULT_WIDTH }}> + {children} + </animated.div> + </> + )} + </div> + ); + case PresEffect.Roll: + return ( + <animated.div ref={ref} style={{ opacity: springs.opacity, transform: to([springs.x, springs.y], (val, val2) => `translate3d(${val}%, 0, 0) rotate3d(0, 0, 1, ${val2}deg)`) }}> + {children} + </animated.div> + ); + default: + return ( + <animated.div + ref={ref} + style={{ + ...springs, + }}> + {children} + </animated.div> + ); + } + }; + + useEffect(() => { + if (infinite || !inView) return; + setTimeout(() => { + startAnimation(); + }, 100); + }, [inView]); + + useEffect(() => { + if (infinite) { + startAnimation(); + } + }, [presEffect, tension, friction, mass]); + + return <div>{getRenderDoc()}</div>; +} |
