the

Animating

Virtual DOM

Sarah Drasner

@sarah_edo

Sarah Drasner

@sarah_edo

Consultant

CSS-Tricks, IBM, Microsoft, Salesforce
Smashing Magazine, NetMag, Zillow, Workflo,
O’Reilly, Frontend Masters, & Mule Design

      Microsoft

Sr. Cloud
Developer Advocate

Fiber

  • Priority-based scheduling of updates
  • Animation update preferred to data update
  • Animation becomes a first-class citizen

“We’ve evolved to perform actions that flow more or less seamlessly.

 

"We aren’t wired to deal with the fits and starts of human-computer interaction.”

Sensory memory: Your occipital lobe (AKA “the memory store”) works in 100ms bursts.

-Tammy Everts

React encapsulates state that's changing

Animation ties these two states together

Users can then follow the trajectory of the change as if it were a

Story

The "so what" factor

User attention span is short.

2 seconds

until dropoff

Amazon has discovered that for every one second delay, conversions dropped by 7%. If you sell $100k per day, that’s an annual loss of $2.5m.

Walmart has found that it gains 1% revenue increase for every 100ms of improvement.

Start with the end

Also helps track what validation looks like:

Google PMs are encouraged to find ONE goal.

Fill in the space between

Aligns the entire team to the same goal

Reduces friction

Without Transitions

Gain understanding

Spatial or otherwise

Workfloapp.com - @workflohq

Percieved Performance

  • Humans over-estimate passive waits by 36% - Eli Fitch and Richard Larson, MIT
  • Viget study: wait twice as long for custom loader
  • Over 4 seconds: HORROR
  • Uncertain waits are longer than known, finite waits
  • Disney world/Airports- entertainment while waiting
  • People want to get started- doctor exam rooms

Entire filesize: 6KB!

This actually goes back to IE9

What does the "Scalable" mean?

You don't have to care about positioning, even for responsive

Declarative ANimations

Timing

Workfloapp.com - @workflohq

Timing

<TransitionGroup>
  { this.state.shouldShowSoundwaves && 
    <Soundwaves 
      outTime={0.5} 
      drawTiming={5} 
      elTime={1} 
      easing="Circ"
    /> 
  }
</TransitionGroup>

Timing

Consider how things look to your user

Create Beautiful Defaults

Keep animation consistent and reusable the way you do with text, layout

const Eases = {
  entrance: {
    animationTimingFunction: `cubic-bezier(0.39, 0.575, 0.565, 1)`,
  },
  entranceEmphasis: {
    animationTimingFunction: `cubic-bezier(0.175, 0.885, 0.32, 1.275)`,
  },
  exit: {
    animationTimingFunction: `cubic-bezier(0.47, 0, 0.745, 0.715)`,
  },
  exitEmphasis: {
    animationTimingFunction: `cubic-bezier(0.6, -0.28, 0.735, 0.045)`,
  },
}
const Timing = {
  t1: {
    animationDuration: 0.1,
  },
  t2: {
    animationDuration: 0.15,
  },
  t3: {
    animationDuration: 0.2,
  }...
}

h1, h2, h3, h4, h5 is body copy

t1, t2, t3, t4, t5 is typical timing

Animation iteration

Workfloapp.com - @workflohq

Composite components, including animation

mo.js

mo.js

const burst = new mojs.Burst({
  left: 0, top: 0,
  zIndex:   10000,
  radius:   { 0: 100 },
  count:    5,
  children: {
    shape:     'cross',
    stroke:    { 'cyan' : 'yellow' },
    angle:     { 360: 0 },
    duration:  2000,
    delay:     'stagger(0, 100)'
  }
});

mo.js

This pen.

class ButtonGroup extends React.Component {
  render() {
    return (<div></div>);
  },
  
  shouldComponentUpdate() {
    this.props.isPlaying && this._burst.replay();
    return false;
  },
  
  componentDidMount () {
    const dog1 = new mojs.Burst({
      ...dog_opts,
      children: {
        ...dog_child_opts,
        shape:      'dogbase',
        fill:       '#E89221'
      }
    });
    
  ...

React-Motion

This pen

This pen.

getStyles(prevStyles) {
    const endValue = prevStyles.map((_, i) => {
      let staggerStiff = 100, staggerDamp = 19;
      return i === 0
        ? { opacity: spring(this.state.open ? 0 : 1, {stiffness: staggerStiff, damping: staggerDamp}) }
        : { opacity: spring(this.state.open ? 0 : 1, {stiffness: (staggerStiff - (i * 7)), damping: staggerDamp + (i * 0.2)}) }
    });
    return endValue;
  },
<StaggeredMotion
  defaultStyles = {arr} 
  styles={this.getStyles}>
  {circ =>
    <g fill={pathColor} className="cPath">
      {circ.map(({opacity}, i) =>
        <path
          key={i}
          d={pathData[i]}
          style={{
            opacity: opacity
          }} />
      )}
    </g>
  }
</StaggeredMotion>

This pen.

Drawn SVG

Done with stroke-dasharray and stroke-dashoffset

  • Path or shape has a stroke
  • The stroke is dashed
  • Use JS to .getTotalLength()
  • Dasharray the whole length of the shape
  • Animate dashoffset 
@keyframes dash {
  50% {
    stroke-dashoffset: 0;
  }
  100% {
    stroke-dashoffset: -274;
  }
}
<Motion style={{
    //designate all of the differences in interpolated values in these ternary operators
    ...
    dash: spring(this.state.compact ? 0 : 200), 
    ...
    }}>
  
    {/* make sure the values are passed below*/}
    {({..., dash, ...}) =>
    <svg viewBox="0 0 803.9 738.1" aria-labelledby="title">
    <title>React-Motion</title>
  
        ...
        <g style={{ strokeDashoffset: `${dash}` }}
           className="react-letters" data-name="react motion letters">
           <path className="cls-5" d="M178.4,247a2.2,2.2,0,1,1-3.5,2.6l-6.5-8.7h-8.6v7.4a2.2,2.2,0,0,1-4.4,0V220.1a2.2,2.2,0,0,1,2.2-2.2h10.8a11.5,11.5,0,0,1,4.8,22Zm-18.6-10.3h8.6a7.3,7.3,0,0,0,0-14.7h-8.6v14.7Z" transform="translate(3.1 1.5)"/>
           ...
        </g>
    </svg>
    }
</Motion>

correct tools for the job

my  recommendations:

CSS-in-JS/Styles

  • Small sequences and simple interactions
  • Once you get more than 3... switch to:

GSAP (GreenSock)

  • Great for sequencing and complex movement
  • Cross-browser consistency

React-Motion

  • Great for single movements that you'd like to look realistic

correct tools for the job

GSAP (GreenSock)

It's very tough to find something another tool can do that GSAP can't do...

 

but there are plenty of things GSAP can do that other tools can't.

🏆

  • Transforms
  • Cross-browser inconsistencies
  • Morphing
  • Sequencing and chaining

Not all are created equal

  • Opacity
  • Transforms
  • Hardware Acceleration
const accelerate = {
 transform: 'translateZ(0)',
 backface-visibility: 'hidden',
 perspective: '1000px',
}
 /*         3rd           2nd              1st      */
 transform: rotate(90deg) translateX(30px) scale(1.5);

Stacking Transforms

@keyframes foo {
  30% {
    transform: rotateY(360deg) scale(1.23) translateY(-14px)
  }
  65% {
    transform: rotateY(-360deg) scale(1.5) translateY(-30px)
  }
  90% {
    transform:  rotateY(-102deg) scale(0.75) translateY(10px)
  }
}

Stacking Transforms

womp womp :(

TweenMax.to('.thing', 2, {
  x: 20,
  y: 100,
  scale: 20,
  skew: 2
})

GreenSock

Don't have to write transform: translateX... etc either.

Or hardware accelerate.

Not beholden to the spec

🔥

*React-Move/React-Motion/React-Tween can handle matrix transforms, it's just not very intuitive

Animate the unanimatible, like SVG filters

Solves Cross-Browser Inconsistencies

Bad transform origin bug on rotation, soon to be solved in Firefox.

More in this CSS-Tricks article.

Chrome

IE

Firefox

Safari (zoomed)

Solves Other Transform-Origin Issues

This pen.

MorphSVG

Sequences

& Chaining

Easily spaghetti:

Delays for chaining,

Callbacks

Timeline

  • stack tweens
  • change their placement in time
  • group them into scenes
  • add relative labels
  • animate the scenes!
  • reuse the scenes, play backwards, pause
//instantiate a TimelineLite    
const tl = new TimelineMax();

//adds a tween at the beginning
tl.to(el1, 0.5, {x:100, opacity:0})

//adds another tween immediately after
tl.to(el2, 0.5, {y:-100, opacity:0})

//schedule next tween 0.5 seconds after
tl.to(el3, 0.5, {scale:.5}, "+=0.5")

End to end

Leo Leung

React transition Group Plus
&&
Greensock

💥

===

Transitions

Don't reinvent the wheel / Avoid callback hell

Without in-out modes

End to end

State-driven ANIMAtions

  toggleShape() {
    if (this.state.screen === 0) {
      this.animFire(this.state.splitText)
    } ...
    this.setState({
      screen: (this.state.screen + 1) % 3
    })
  }

State-driven Animations

  animFire(splitText) {
    const tl = new TimelineMax,
          stA = 'start';
    
    TweenMax.set([this.g1.childNodes, this.g2.childNodes], {
      clearProps:'svgOrigin'
    })

    tl.add('start')
    tl.staggerFromTo(this.g1.childNodes, dur, {
      drawSVG: '68% 100%'
    }, {
      drawSVG: '27.75% 0%',
      ease: Back.easeOut
    }, stD, stA)
    ...
  }
componentWillEnter(callback) {
    const { drawTiming, elTime, easing } = this.props,
          elTime2 = elTime * 2;
    
    TweenMax.fromTo(this.box, elTime2, {
      opacity: 0,
      drawSVG: '50% 50%'
    }, {
      opacity: 1,
      drawSVG: '100%',
      ease: `${easing}.easeOut`
    })
    ...
}

the sky's the limit!

More!

Draggable

Motion Along a Path

Custom Easing

Physics/ThrowProps

React encapsulates state that's changing

Animation ties these two states together

💥

SVG Animations

with Val Head

🎉

Thank you!

@sarah_edo on twitter

Animating the Virtual DOM

By sdrasner

Animating the Virtual DOM

  • 28,629