React Rally 2016
what happens when you visit a website?
Creating Spatial Awareness
We already use this information
Helps with spatial awareness
From this CSS-Tricks Article
This pen.
This pen.
Viget did an experiment and found that despite some individual variation, novel loaders as a whole had a higher wait time and lower abandon rate than generic ones
Do you hate animation?
Right tool for the right job
Pros
Pros
Cons
Cons
This pen
This pen.
Small filesize + performance
Compare to using text with photos to illustrate an article.
Title and associative aria tags:
<svg aria-labelledby="title"
id="svg"
role="presentation"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 765 587">
<title id="title"
lang="en">
Icons that illustrate Global Warming Solutions
</title>
Title for elements in the SVG DOM
Role to let the screen reader know whether to traverse
This resource, with support charts.
This article by Heather Migliorisi.
Simplest example
//Icon Office
function IconOffice(props) {
//props and default props
const width = props.width || '100px'
const height = props.height || '200px'
const bookside = props.bookside || '#353f49'
const bookfront = props.bookfront || '#474f59'
const bookfill = props.bookfill || '#474f59'
return (
<svg className="office" xmlns="http://www.w3.org/2000/svg"
width={width}
height={height}
viewBox="0 0 188.5 188.5"
aria-labelledby="Office Icon"
>
<title>Office Icon</title>
<g className="cls-2">
<circle id="background" className="cls-3" cx="94.2" cy="94.2" r="94.2"/>
<path className="cls-4" d="M50.3 69.8h10.4v72.51H50.3z"/>
<path fill={bookside} d="M50.3 77.5h10.4v57.18H50.3z"/>
<path fill={bookfront} d="M60.7 77.5h38.9v57.19H60.7z"/>
.switcher .office {
#bulb { animation: switch 3s 4 ease both; }
#background { animation: fillChange 3s 4 ease both; }
}
@keyframes switch {
50% {
opacity: 1;
}
}
@keyframes fillChange {
50% {
fill: #FFDB79;
}
}
// App
function App() {
return (
<div>
<div className="switcher">
<IconOffice />
</div>
<IconOffice bookfill={200} bookside="#39B39B" bookfront="#76CEBD"/>
<IconOffice width="200" height="200"/>
</div>
)
}
Cheng-Lou React Europe 2015
<ReactCSSTransitionGroup
transitionName={ {
enter: 'enter',
enterActive: 'enterActive',
leave: 'leave',
leaveActive: 'leaveActive',
appear: 'appear',
appearActive: 'appearActive'
} }>
{item}
</ReactCSSTransitionGroup>
<ReactCSSTransitionGroup
transitionName={ {
enter: 'enter',
leave: 'leave',
appear: 'appear'
} }>
{item2}
</ReactCSSTransitionGroup>
CSS/SCSS
GSAP (GreenSock)
React-Motion
This pen.
<StaggeredMotion
defaultStyles = {arr}
styles={this.getStyles}>
{lines =>
<div className="demo">
{lines.map(({x, y, rotate}, i) =>
<div
key={i}
className={`playthings s${i}`}
// we have to subtract half the amount of $amt in the
//CSS panel so that the mouse stays in the center of
//the object we're creating
style={{
WebkitTransform: `translate3d(${x - amtHalf}px, ${y - amtHalf}px, 0) rotate(${rotate}deg)`,
transform: `translate3d(${x - amtHalf}px, ${y - amtHalf}px, 0) rotate(${rotate}deg)`
}} />
)}
</div>
}
</StaggeredMotion>
@mixin accelerate($name) {
will-change: $name;
transform: translateZ(0);
backface-visibility: hidden;
perspective: 1000px;
}
.foo {
@include accelerate(transform);
}
this pen.
src: Wealthfront
Case Study: Netflix
This pen.
const App = React.createClass({
getInitialState: function() {
return {
score: 500,
startTime: 0,
finalScore: 0
};
},
_startGame: function(setGameToStart) {
this.setState({ isPlaying: setGameToStart,
score: 500,
startTime: Date.now()
});
},
_updateScore: function(scoreDelta) {
let score = Math.min(Math.max(0, this.state.score + scoreDelta), 1270);
this.setState({ score: score });
if (score === 0 || score === 1270) {
this.setState({ isPlaying: false,
finalScore: 10000 - Math.round((Date.now() - this.state.startTime) / 30) });
}
},
...
render() {
return (
<div>
...
<HeartMeter score={this.state.score}/>
...
</div>
)
}
});
const HeartMeter = React.createClass({
render() {
return (
<div>
<svg className="heartmeter" xmlns="http://www.w3.org/2000/svg" width="250" height="50" viewBox="0 0 1741.8 395.6">
<path d="M1741.8 197.7c0 109.3-89 197.8-198.8 197.8a198.6 198.6 0 0 1-158.5-78.4H11.2A11.2 11.2 0 0 1 0 305.9V89.5a11.2 11.2 0 0 1 11.2-11.1h1373.4A198.8 198.8 0 0 1 1543 0c109.8 0 198.8 88.5 198.8 197.7z" fill="#000"/>
<path d="M1591.8 127c-18.3 0-34.1 14.8-41.4 30.3-7.3-15.5-23.1-30.3-41.4-30.3a45.7 45.7 0 0 0-45.7 45.5c0 51.1 51.8 64.5 87.1 115.1 33.4-50.2 87.1-65.6 87.1-115.1a45.7 45.7 0 0 0-45.7-45.5z" fill="#b29968"/>
<rect x="68.2" y="140.8" width={this.props.score} height="101.55" fill="#9391aa"/>
</svg>
</div>
)
}
});
Request Animation Frame
(function getCoords() {
let tCoords = tC1.getBoundingClientRect(),
txCoords = txC1.getBoundingClientRect(),
mCoords = mC1.getBoundingClientRect(),
elCoords = ele1.getBoundingClientRect();
function intersectRect(a, b) {
return Math.max(a.left, b.left + 40) < Math.min(a.right, b.right - 40) &&
Math.max(a.top, b.top + 40) < Math.min(a.bottom, b.bottom - 40);
}
// can't do if/else because sometimes they both come out at once
// and one of them will be ignored
if (intersectRect(tCoords, elCoords)) {
getHitTestIncrease();
}
if (intersectRect(txCoords, elCoords)) {
getHitTestDecrease();
}
if (intersectRect(mCoords, elCoords)) {
getHitTestMargarita();
}
requestAnimationFrame(getCoords);
}());
Bubble.prototype = {
init: function (x, y, vx, vy) {
this.x = x;
this.y = y;
this.vx = vx;
this.vy = vy;
},
update: function (dt) {
// friction opposes the direction of velocity
var acceleration = -Math.sign(this.vx) * friction;
// distance = velocity * time + 0.5 * acceleration * (time ^ 2)
this.x += this.vx * dt + 0.5 * acceleration * (dt ^ 2);
this.y += this.vy * dt + 0.5 * gravity * (dt ^ 2);
// velocity + acceleration * time
this.vy += gravity * dt;
this.vx += acceleration * dt;
this.circ.setAttribute("cx", this.x);
this.circ.setAttribute("cy", this.y);
this.circ.setAttribute("stroke", "rgba(1,146,190," + this.opacity + ")");
}
};
BT Dubs, this is d3 under the hood
(function animate(currentTime) {
var dt;
requestAnimationFrame(animate);
...
for (var i = 0; i < particles.length; i++) {
particles[i].update(dt);
...
}
}());
GSAP for Animation
_flyBy: function(el, amt, name, delay) {
if (this.props._isGameOver()) return;
...
TweenMax.fromTo(el, amt, {
rotation:0,
y:randY,
x:window.innerWidth + 200
}, {
x: -200,
y:randY,
rotation: 360,
delay: delay,
onComplete:this._flyBy,
onCompleteParams:[el, amt, name, delay],
ease: Power1.easeInOut
});
},
Bad transform origin bug on rotation, soon to be solved in Firefox.
More in this CSS-Tricks article.
Chrome
IE
Firefox
Safari (zoomed)
The issue with longer CSS animations:
This pen.
_hitTestIncrease: function() {
if (!this.state.tacoPassed && this.props.isPlaying) {
//animation for wowie
let inWow = this.refs.inWow,
tl = new TimelineLite();
audioIncrease.play();
tl.fromTo(inWow, 0.4, {
autoAlpha: 0,
scale: 0.5
}, {
autoAlpha: 1,
scale: 1,
ease: Power4.easeOut
});
tl.to(inWow, 0.2, {
autoAlpha: 0,
scale: 0.5,
ease: Power2.easeIn
}, "+=0.3");
this.setState({ tacoPassed: true });
this.props._updateScore(75);
}
},
This pen.
getStyles(prevStyles) {
// we're using the previous style to update the next ones placement
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;
},
const pathData = ["M48.8,0A48.8,48.8,0,1,0,97.6,48.8,48.8,48.8,0,0,0,48.8,0Zm0,88.6A39.8,39.8,0,1,1,88.6,48.8,39.8,39.8,0,0,1,48.8,88.6Z",
"M48.8,9.1A39.8,39.8,0,1,0,88.6,48.8,39.8,39.8,0,0,0,48.8,9.1Zm0,67.9A28.1,28.1,0,1,1,77,48.8,28.1,28.1,0,0,1,48.8,77Z",
"M48.8,20.7A28.1,28.1,0,1,0,77,48.8,28.1,28.1,0,0,0,48.8,20.7Zm0,50.1a22,22,0,1,1,22-22A22,22,0,0,1,48.8,70.8Z",
"M48.8,26.8a22,22,0,1,0,22,22A22,22,0,0,0,48.8,26.8Zm0,39.1A17.1,17.1,0,1,1,66,48.8,17.1,17.1,0,0,1,48.8,66Z",
"M48.8,31.7A17.1,17.1,0,1,0,66,48.8,17.1,17.1,0,0,0,48.8,31.7Zm0,29A11.9,11.9,0,1,1,60.7,48.8,11.9,11.9,0,0,1,48.8,60.7Z",
"M48.8,36.9A11.9,11.9,0,1,0,60.7,48.8,11.9,11.9,0,0,0,48.8,36.9Zm0,19.9a8,8,0,1,1,8-8A8,8,0,0,1,48.8,56.8Z",
"M48.8,40.8a8,8,0,1,0,8,8A8,8,0,0,0,48.8,40.8Zm0,12.6a4.6,4.6,0,1,1,4.6-4.6A4.6,4.6,0,0,1,48.8,53.5Z",
"M48.8,44.2a4.6,4.6,0,1,0,4.6,4.6A4.6,4.6,0,0,0,48.8,44.2Zm0,7.1a2.5,2.5,0,1,1,2.5-2.5A2.5,2.5,0,0,1,48.8,51.3Z",
"M51.3 48.8c0 1.38-1.12 2.5-2.5 2.5s-2.5-1.12-2.5-2.5 1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5z"]
<svg className="circled circle-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 97.6 97.6">
<StaggeredMotion
defaultStyles = {arr}
styles={this.getStyles}>
{circ =>
<g fill={pathColor} className="cPath">
{circ.map(({opacity}, i) =>
<path
key={i}
d={pathData[i]}
className={`things s${i}`}
style={{ opacity: opacity }} />
)}
</g>}
</StaggeredMotion>
</svg>
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>
This pen.
This repo
This demo
class App extends React.Component {
render () {
...
for (let i = 0; i < 30; i++) {
...
items.push(<Entity geometry="primitive: box; depth: 1.5; height: 1.5; width: 6"
material={{color: updateColor}}
position={randoPos}
pivot="0 0.5 0"
key={i}>
<Animation attribute="rotation"
dur="12000"
to={updateRot}/>
<Animation attribute="scale"
from="0 0 0" to="1 1 1"
dur="12000"
fill="both"
easing="ease-out"/>
</Entity>);
}
return (
<Scene>
<Camera><Cursor/></Camera>
<Sky/>
<Entity light={{type: 'ambient', color: '#888'}}/>
<Entity light={{type: 'directional', intensity: 0.5}} position={[-1, 1, 0]}/>
<Entity light={{type: 'directional', intensity: 1}} position={[1, 1, 0]}/>
{items}
</Scene>
);
}
}
Fun. Remember fun?
(I don't work for them and they don't pay me)
@sarah_edo on twitter
@sdras on codepen
These Slides: