the
Sarah Drasner
@sarah_edo
Sarah Drasner
@sarah_edo
CSS-Tricks,
IBM,
Microsoft, Salesforce
Smashing Magazine,
NetMag,
Zillow, Workflo,
O’Reilly, Frontend Masters, & Mule Design
Microsoft
“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
User attention span is short.
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.
Also helps track what validation looks like:
Google PMs are encouraged to find ONE goal.
Aligns the entire team to the same goal
Reduces friction
Spatial or otherwise
Workfloapp.com - @workflohq
Entire filesize: 6KB!
This actually goes back to IE9
You don't have to care about positioning, even for responsive
Workfloapp.com - @workflohq
<TransitionGroup>
{ this.state.shouldShowSoundwaves &&
<Soundwaves
outTime={0.5}
drawTiming={5}
elTime={1}
easing="Circ"
/>
}
</TransitionGroup>
Consider how things look to your user
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
Workfloapp.com - @workflohq
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)'
}
});
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'
}
});
...
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.
Done with stroke-dasharray and stroke-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>
CSS-in-JS/Styles
GSAP (GreenSock)
const accelerate = {
transform: 'translateZ(0)',
backface-visibility: 'hidden',
perspective: '1000px',
}
/* 3rd 2nd 1st */
transform: rotate(90deg) translateX(30px) scale(1.5);
@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)
}
}
womp womp :(
TweenMax.to('.thing', 2, {
x: 20,
y: 100,
scale: 20,
skew: 2
})
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
Bad transform origin bug on rotation, soon to be solved in Firefox.
More in this CSS-Tricks article.
Chrome
IE
Firefox
Safari (zoomed)
This pen.
Easily spaghetti:
Delays for chaining,
Callbacks
//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")
Leo Leung
===
Don't reinvent the wheel / Avoid callback hell
Without in-out modes
toggleShape() {
if (this.state.screen === 0) {
this.animFire(this.state.splitText)
} ...
this.setState({
screen: (this.state.screen + 1) % 3
})
}
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`
})
...
}
More!
Draggable
Motion Along a Path
Custom Easing
Physics/ThrowProps
with Val Head
@sarah_edo on twitter