Skip to content

frankluongo/lut-react-spring

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

17 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Animating React with React Spring

LevelUpTutorials

1 Why React Spring & Physics Based Animations

React Spring Documentation

  • Also check out popmotion

2 Getting Started

Course Starter Files

3 A Basic Spring

import { useSpring, animated } from 'react-spring';

const Application = () => {
  const fade = useSpring({ from: { opacity: 0 }, opacity: 1 });

  return (
    <animated.div className="App" style={fade}>

4 Toggling a Spring

import React, { useState } from 'react';
import { useSpring, animated } from 'react-spring';

const Toggle = () => {
  const [isToggled, setToggle] = useState(false);
  const fade = useSpring({
    opacity: isToggled ? 1 : 0
  });

  return (
    <div>
      <animated.h1 style={fade}>Hello</animated.h1>
      <button onClick={() => setToggle(!isToggled)}>Toggle</button>
    </div>
  )
}

export default Toggle;

5 Animating Properties

  const fade = useSpring({
    // opacity: isToggled ? 1 : 0,
    color: isToggled ? '#000' : 'rgba(255,255,255,0.5)',
    fontSize: isToggled ? '1rem' : '3em',
    transform: isToggled ? 'translate3d(0,0,0)' : 'translate3d(0, -50px, 0)'
  });

11: Transition With Multiple Components

const Toggle = () => {
  const [items, setItems] = useState([
    {letter: "S",key: 1 },
    {letter: "c",key: 2},
    {letter: "o",key: 3},
    {letter: "t",key: 4},
    {letter: "t",key: 5},
  ]);
  const transition = useTransition(items, item => item.key, {
    from: { opacity: 0 },
    enter: { opacity: 1},
    leave: { opacity: 0}
  });

  return (
    <div>
      {transition.map(({ item, key, props }) => (
        <animated.h1 key={key} style={props}>
          {item.letter}
        </animated.h1>
      ))}
      <button onClick={() => setItems([
        {
          letter: "S",
          key: 1
        }
      ])}>Toggle</button>
    </div>
  )
}

12: Transition With React Router

import React, { useContext } from 'react';
import { BrowserRouter as Router, Switch, Route, Link, __RouterContext } from 'react-router-dom';
import { animated, useTransition } from 'react-spring';


function useRouter() {
  return useContext(__RouterContext);
}

const Routes = () => {
  return (
    <Router>
      <ul className="router-nav">
        <NavLink to="/">One</NavLink>
        <NavLink to="/two">Two</NavLink>
        <NavLink to="/three">Three</NavLink>
      </ul>

      <Main />
    </Router>
  );
};


const Main = () => {
  const { location } = useRouter();

  const transitions = useTransition(location, location => location.key, {
    from: { opacity: 0, position: 'absolute', width: '100%', transform: 'translate3d(100%,0,0)' },
    enter: { opacity: 1, transform: 'translate3d(0,0,0)'},
    leave: { opacity: 0, transform: 'translate3d(-50%,0,0)' },
  });

  return transitions.map(({ item, props: transition, key }) => (
    <animated.div key={key} style={transition}>
      <Switch location={item}>
        <Route exact path="/" component={One} />
        <Route exact path="/two" component={Two} />
        <Route exact path="/three" component={Three} />
      </Switch>
    </animated.div>
  ));
}


function NavLink(props) {
  return (
    <li>
      <Link {...props} />
    </li>
  );
}

const One = () => {
  return (
    <div className="page-route">
      <h1>One</h1>
    </div>
  );
};
const Two = () => {
  return (
    <div className="page-route two">
      <h1>Two</h1>
    </div>
  );
};
const Three = () => {
  return (
    <div className="page-route three">
      <h1>Three</h1>
    </div>
  );
};

export default Routes;

13: Modals

import React, { useState }  from 'react'
import { animated, useTransition } from 'react-spring'


const Modal = ({closeModal, animation, pointerEvents}) => {
  return (
    <div className="modal" style={{ pointerEvents }}>
      <animated.div className="modal-card" style={animation}>
        <h1>Hi</h1>
        <button onClick={closeModal}>Close</button>
      </animated.div>
    </div>
  )
}

const ModalWrapper = () => {
  const [ on, toggle ] = useState(false);
  const transition = useTransition(on, null, {
      from: {opacity: 0, transform: 'translate3d(0, -40px, 0)'},
      enter: {opacity: 1, transform: 'translate3d(0, 0, 0)'},
      leave: {opacity: 0, transform: 'translate3d(0, -40px, 0)'},
    }
  );
  const pointerEvents = on ? 'all' : 'none';

  function toggleOn () {
    toggle(true)
  }

  function toggleOff () {
    toggle(false)
  }

  return (
    <div>
      {
        transition.map(({ item, props }) => item && <Modal pointerEvents={pointerEvents} animation={props} closeModal={toggleOff} />)
      }
      <button onClick={toggleOn}>Open</button>
    </div>
  )
}

export default ModalWrapper

14: Configuration Options

Common API

import React from 'react'
import { useSpring, animated, config } from 'react-spring';

const Checkout = ({ isOpen }) => {
  const { x } = useSpring({
    x: isOpen ? 0 : 100,
    config: config.molasses
  });

  return (
    <div
      className="checkout"
      style={{
        pointerEvents: isOpen ? 'all' : 'none'
      }}>
      <animated.div style={{
        transform: x.interpolate(x => `translate3d(-${x}%,0,0)`)
      }} className="checkout-left" />
      <animated.div style={{
        transform: x.interpolate(x => `translate3d(${x}%,0,0)`)
      }} className="checkout-right" />
    </div>
  )
}

export default Checkout

/*
    config: {
      tension: 500,
      friction: 1,
    }
*/

15: Animating Height Auto

import React, { useState } from 'react'
import { animated, useSpring } from 'react-spring';
import useMeasure from '../Hooks/useMeasure';

const Accordion = () => {
  const [on, toggle] = useState(false);
  const [bind, {height: accordHeight, top}] = useMeasure();

  const spring = useSpring({
    overflow: 'hidden',
    height: on ? accordionDimensions() : 0,
  });

  function accordionDimensions () {
    return accordHeight + top * 2;
  }

  function toggleAccordion () {
    toggle(!on);
  }


  return (
    <div>
       <button onClick={toggleAccordion}>Toggle</button>
       <animated.div style={spring}>
        <div {...bind} className="accordion">
          <p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Molestiae qui iusto officia dignissimos excepturi cumque unde sit suscipit, eveniet, eos assumenda non quas. Minus nobis repellendus qui, earum hic dolorum!</p>
        </div>
       </animated.div>
    </div>
  )
}

export default Accordion

16: Animation on Scroll

import React, { useState } from 'react'
import { animated, useSpring } from 'react-spring'
import { Waypoint } from 'react-waypoint';


const Waypoints = () => {
  const [ on, toggle ] = useState(false);
  const animation = useSpring({
    opacity: (on ? 1 : 0),
    transform: (on ? 'translate3d(0,0,0)' : 'translate3d(-50%,0,0)'),
  });


  return (
    <div className="waypoints">
      <p>
        Lorem ipsum dolor amet poutine pitchfork tattooed venmo, heirloom cliche chartreuse gentrify mumblecore hammock single-origin coffee banh mi. Sartorial unicorn 90's edison bulb iPhone. Leggings pickled brunch neutra tousled. Occupy fixie affogato pinterest vaporware aesthetic, tbh subway tile hammock next level prism vape lomo taiyaki kale chips. Jianbing knausgaard taxidermy squid artisan thundercats, gochujang subway tile air plant taiyaki master cleanse cray.
      </p>
      <p>
        Pug godard pour-over 90's direct trade, PBR&B +1 next level organic edison bulb quinoa DIY. Taiyaki sriracha unicorn, cronut taxidermy chicharrones four dollar toast keytar cold-pressed raclette yuccie cray iceland. Roof party knausgaard neutra plaid, pork belly chambray banh mi chia. Blue bottle narwhal iceland health goth cornhole fam humblebrag flannel pitchfork pickled.
      </p>
      <p>
        Jianbing lomo lumbersexual put a bird on it fixie next level pitchfork gentrify, disrupt echo park. Hot chicken subway tile drinking vinegar fixie. YOLO keytar gluten-free artisan live-edge four loko cred man braid food truck leggings. Health goth semiotics kogi heirloom authentic hell of. Pork belly helvetica cornhole gentrify microdosing austin chillwave pitchfork paleo cred raclette venmo vegan fashion axe +1.
      </p>
      <p>
        Craft beer tousled ennui ugh, williamsburg stumptown flexitarian plaid activated charcoal. Taxidermy letterpress glossier 8-bit, organic bitters coloring book. Selvage lo-fi typewriter wolf ugh, lyft four loko chillwave bitters mustache tumblr copper mug subway tile. Fanny pack aesthetic taiyaki vice sustainable mustache. Asymmetrical shabby chic DIY authentic normcore man braid you probably haven't heard of them. Mustache humblebrag umami beard williamsburg. Prism hexagon VHS, paleo tacos narwhal etsy fashion axe ennui schlitz ethical echo park vinyl.
      </p>
      <div>
        <Waypoint
          bottomOffset="30%"
          onEnter={() => toggle(true)}
          onLeave={() => toggle(false)}
        />
      <animated.p style={animation}>
        Health goth af scenester irony, farm-to-table austin intelligentsia man bun celiac flexitarian yuccie marfa kickstarter banh mi gluten-free. Pabst vape bespoke banjo umami next level tumblr offal wolf kombucha. Jean shorts direct trade distillery yr glossier dreamcatcher, before they sold out butcher. Fixie unicorn leggings pabst forage neutra. 3 wolf moon microdosing food truck hell of keytar, bitters disrupt flannel chartreuse knausgaard affogato ethical pickled. Humblebrag small batch meh glossier mixtape.
      </animated.p>
      </div>
    </div>
  )
}

export default Waypoints

17: Set Function and Gestures

import React from 'react'
import { useGesture } from 'react-with-gesture'
import { animated, useSpring } from 'react-spring'



const Box = () => {
  const [{xy}, set] = useSpring(() => ({
      xy: [0, 0]
    }));

  const bind = useGesture ( ({ down, delta }) => {
    set({ xy: down ? delta : [0,0] });
  })

  return (
    <animated.div
      style={{
        transform: xy.interpolate((x,y) => `translate3d(${x}px, ${y}px, 0)`)
      }}
      {...bind()}
      className="box" />
  )
}

export default Box

18: Gestures With Events

import React from 'react'
import { useGesture } from 'react-with-gesture'
import { animated, useSpring } from 'react-spring'



const Box = () => {
  const [{x}, set] = useSpring(() => ({
      x: 0
    }));

  const bind = useGesture ( ({ down, delta }) => {

    if (down) {
      set({ x: down ? delta[0] : 0});
    } else {
      if (delta[0] > 400) {
        set({ x: 500})
      } else {
        set({ x: 0})
      }
    }

  })

  return (
    <animated.div
      style={{
        opacity: x.interpolate({
          map: Math.abs,
          range: [0, 400],
          output: [1, 0.5]}
        ),
        transform: x.interpolate((x) => `translate3d(${x}px, 0, 0)`)
      }}
      {...bind()}
      className="box" />
  )
}

export default Box

19: UseSprings

import React from 'react'
import { animated, useSprings } from 'react-spring';

const items = [0,1,2,3,4];

const Boxes = () => {
  const springs = useSprings(
    items.length,
    items.map(
      item => ({
        from: {opacity: 0},
        to: {opacity: 1}
      }))
  )

  return (
    <div className="boxes-grid">
      {springs.map(animation => <animated.div className="box" style={animation} />)}
    </div>
  )
}

export default Boxes

20: useTrail

import React, { useState } from 'react'
import { animated, useTrail } from 'react-spring';

const items = [0,1,2,3,4];

const Boxes = () => {
  const [on, toggle] = useState(false);

  const trail = useTrail(
    items.length,
    {
      // from: { opacity: 0 },
      // to: { opacity: 1 }
      opacity: on ? 1 : 0,
      transform: on ? 'scale(1)' : 'scale(0.3)'
    }
  )

  return (
    <div className="boxes-grid">
      <button onClick={() => {toggle(!on)}}>Toggler</button>
      {trail.map(animation => <animated.div className="box" style={animation} />)}
    </div>
  )
}

export default Boxes

21: useChain

import React, { useState, useRef } from 'react'
import { animated, useTrail, useSpring, useChain, useTransition } from 'react-spring';

const items = [0,1,2,3,4];

const Boxes = () => {
  const [on, toggle] = useState(false);

  const springRef = useRef();

  const { size } = useSpring({
    ref: springRef,
    from: { size: '20%' },
    to: { size: on ? '100%' : '20%' }
  });

  const transitionRef = useRef();

  const transition = useTransition(on ? items : [], item => item, {
      ref: transitionRef,
      trail: 100 / items.length,
      from: { opacity: 0, transform: 'scale(0)' },
      enter: { opacity: 1, transform: 'scale(1)' },
      leave: { opacity: 0, transform: 'scale(0)' }
    }
  )

  useChain(on ? [springRef, transitionRef] : [transitionRef, springRef]);

  return (
    <div className="full-height">
      <animated.div
        style={{width: size, height: size}}
        className="boxes-grid-two"
        onClick={() => {toggle(!on)}}>
        {/* <button onClick={() => {toggle(!on)}}>Toggler</button> */}
        {transition.map(({ item, key, props: animation }) => (
          <animated.div className="box-two" key={key} style={animation} />
        ))}
      </animated.div>
    </div>
  )
}

export default Boxes

About

Level Up Tutorials React Spring Tutorial

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published