alice in web animations api land
TRANSCRIPT
@RachelNabors .com
surgery.
Some years later…
HistoryGOSSIP
OMG!!!1!
Birth of SMIL, 1999
SMIL
Birth of CSS Anima5ons and Transi5ons, 2009
CSS Transi-ons
CSS Anima-ons
CSS Transi-ons
CSS Anima-ons SMIL
Seriously?
Internet Explorer’s Reac5on
One API to Rule Them All
CSS Transi-ons
CSS Anima-ons SMIL
?
Some years later…
?Web Anima-ons
API
CSS Transi-ons
CSS Anima-ons SMIL
CSS Transi-ons
CSS Anima-ons SMIL
?Web Anima-ons
API
la mort de SMIL, 2015
Fire
fox
Dev
elop
er E
di0o
n
with
@RachelNabors
& Alice
Core Concepts
The Timing & Anima5on Models
The When The What
Timing
the Cheshire Cat’s 5meline
there
0 seconds
not all there
8 seconds4 seconds2 61 3 5 7
“Time may be shi2ed backwards and forwards, scaled, reversed,
paused, and repeated.”
–Web Anima0ons API spec
Stateless Animation
Frame Rate Independent
Direc8on Agnos8c
Seek-able
Animation
0 seconds 8 seconds4 seconds2 61 3 5 7
there not all there
the Cheshire Cat’s 5meline
Keyframe Effect
Do you even remember me?
KeyframeEffect
Anima-on
Timing Anima-on
Web Anima-ons
API
CSS Transi-ons
CSS Anima-ons SMIL
Web Anima5ons API underlies both CSS Anima5ons and Transi5ons
CSS Animations & Transitions
WEB ANIMATIONS API
I’ve a CSS AnimaCons
& TransiCons course… rachelnabors.com/
css-animaCons-course
KeyframeEffect
Anima-on
KeyframeEffect Constructor
Interac5on Development
Tradi5onal Anima5on
tweenskeyframe keyframe
key keyin-betweens
0 seconds 8 seconds4 seconds2 61 3 5 7
there not all there
Keyframe Effect
#rabbit { transition: transform 3s;
}
#rabbit.interacted { transform: translateY(100%);}
new KeyframeEffect(
);
whiteRabbit, [ { transform: 'translateY(0%)' }, { transform: 'translateY(100%)' } ], { duration: 3000, fill: 'forwards' }
var rabbitDownKeyframes =
Familiar Keyframe timing options
duration = transition/animation-duration
delay = transition/animation-delay
fill = animation-fill-mode
iterations = animation-iteration-count
direction = animation-direction
easing = transition/animation-timing-function; Defaults to linear.
Shiny Keyframe timing options
endDelay Milliseconds to delay aDer the end of an anima8on.
iterationStart When the itera8on the anima8on should start.
composite, iteration-composite, spacing
Animation Constructor
var rabbitDownKeyframes = new KeyframeEffect( whiteRabbit, [ { transform: 'translateY(0%)' }, { transform: 'translateY(100%)' } ], { duration: 3000, fill: 'forwards' } );
KeyframeEffect
Anima-on
new Animation(rabbitDownKeyframes, document.timeline);
var rabbitDownAnimation = new Animation(rabbitDownKeyframes, document.timeline);new Animation(rabbitDownKeyframes, document.timeline);
Animation Methods
Animation.pause()
Animation.play()
Animation.reverse()
Animation.finish()
Animation.cancel()
whiteRabbit.removeEventListener( "click", downHeGoes);
function downHeGoes() {
}
whiteRabbit.addEventListener("click", downHeGoes);
rabbitDownAnimation.play();
Fortunately, we have a shortcut.
animate( ) Method
#rabbit { transition: transform 3s;
}
#rabbit.interacted { transform: translateY(100%);
}
#rabbit.interacted { animation: downHeGoes 3s forwards;
}
@keyframes downHeGoes { 0% { transform: translateY(0%);
} 100% { transform: translateY(100%);
} }
#alice { animation: aliceTumbling infinite 3s linear; }
@keyframes aliceTumbling { 0% { color: #000; transform: rotate(0) …; } 30% { color: #431236; } 100% { color: #000; transform: rotate(360deg) …; } }
element.animate( keyframes, timingOptions );
var aliceKeyframes = [ { transform: 'rotate(0) …', color: '#000' }, { color: '#431236',
}, { transform: 'rotate(360deg) …', color: '#000' } ];
offset: 0.3
var aliceTiming = { duration: 3000, iterations: Infinity }
element.animate( keyframes, timingOptions );
document.getElementById("alice") .animate( aliceTumbling, aliceTiming )
Timelines & Callbacks
Anima-on
Animation Attributes
onfinish promise
oncancel promise
ready promise
playState read-only, use methods
playbackRate more on you later…
effect points to…
KeyframeEffect
Anima-on
Animation Attributes
currentTime loca8on on 8meline
finished callback
var aliceChange = document.getElementById('alice').animate( [ { transform: 'scale(.5) …' }, { transform: 'scale(2) …' } ], aliceTimingOptions);
aliceChange.pause();
var aliceChange = document.getElementById('alice').animate( [ { transform: 'scale(.5) …' }, { transform: 'scale(2) …' } ], aliceTimingOptions);
aliceChange.pause();
aliceChange.currentTime = aliceChange.effect.timing.duration / 2;
Setting the Controls
var shrinkAlice = function() { aliceChange.playbackRate = -1; aliceChange.play();
// bottle’s animation drinking.play(); }
drinking.addEventListener("mousedown", shrinkAlice);
var shrinkAlice = function() { aliceChange.playbackRate = -1; aliceChange.play();
// bottle’s animation drinking.play(); }
drinking.addEventListener("mousedown", shrinkAlice);
var shrinkAlice = function() { aliceChange.playbackRate = -1; aliceChange.play();
// bottle’s animation drinking.play(); }
drinking.addEventListener("mousedown", shrinkAlice);
var shrinkAlice = function() { aliceChange.playbackRate = -1; aliceChange.play();
// bottle’s animation drinking.play(); }
drinking.addEventListener("mousedown", shrinkAlice);
var shrinkAlice = function() { aliceChange.playbackRate = -1; aliceChange.play();
// bottle’s animation drinking.play(); }
drinking.addEventListener("mousedown", shrinkAlice);
var shrinkAlice = function() {
aliceChange.reverse();
// bottle’s animation drinking.play(); }
drinking.addEventListener("mousedown", shrinkAlice);
var growAlice = function() { aliceChange.playbackRate = 1; aliceChange.play();
// play cake’s animation nommingCake.play(); }
cake.addEventListener("mousedown", growAlice);
var stopPlayingAlice = function() { aliceChange.pause(); nommingCake.pause(); drinking.pause(); };
bottle.addEventListener("mouseup", stopPlayingAlice); cake.addEventListener("mouseup", stopPlayingAlice);
var stopPlayingAlice = function() { aliceChange.pause(); nommingCake.pause(); drinking.pause(); };
bottle.addEventListener("mouseup", stopPlayingAlice); cake.addEventListener("mouseup", stopPlayingAlice);
var stopPlayingAlice = function() { aliceChange.pause(); nommingCake.pause(); drinking.pause(); };
bottle.addEventListener("mouseup", stopPlayingAlice); cake.addEventListener("mouseup", stopPlayingAlice);
Game Over: Two Endings
// When the cake or runs out... nommingCake.onfinish = endGame; drinking.onfinish = endGame;
// ...or Alice reaches the end of her animation aliceChange.onfinish = endGame;
// When the cake or runs out... animation.onfinish = endGame; drinking.onfinish = endGame;
// ...or Alice reaches the end of her animation aliceChange.onfinish = endGame;
// When the cake or runs out... nommingCake.onfinish = endGame; drinking.onfinish = endGame;
// ...or Alice reaches the end of her animation aliceChange.onfinish = endGame;
var endGame = function() {
var alicePlayhead = aliceChange.currentTime;
var aliceTimeline = aliceChange.effect.activeDuration;
var aliceHeight = alicePlayhead/aliceTimeline;
}
var endGame = function() {
var alicePlayhead = aliceChange.currentTime;
var aliceTimeline = aliceChange.effect.activeDuration;
var aliceHeight = alicePlayhead/aliceTimeline;
}
var endGame = function() {
var alicePlayhead = aliceChange.currentTime;
var aliceTimeline = aliceChange.effect.activeDuration;
var aliceHeight = alicePlayhead/aliceTimeline;
}
var endGame = function() {
var alicePlayhead = aliceChange.currentTime;
var aliceTimeline = aliceChange.effect.activeDuration;
var aliceHeight = alicePlayhead/aliceTimeline;
}
var aliceHeight = alicePlayhead/aliceTimeline;
if (aliceHeight <= .333){ // Alice got smaller! … } else if (aliceHeight >= .666) { // Alice got bigger! … } else { // Alice didn't change significantly … }
var aliceHeight = alicePlayhead/aliceTimeline;
if (aliceHeight <= .333){ // Alice got smaller! … } else if (aliceHeight >= .666) { // Alice got bigger! … } else { // Alice didn't change significantly … }
var endGame = function() { stopPlayingAlice();
var alicePlayhead = aliceChange.currentTime; var aliceTimeline = aliceChange.effect.activeDuration;
var aliceHeight = alicePlayhead/aliceTimeline;
if (aliceHeight <= .333){ // Alice got smaller! … } else if (aliceHeight >= .666) { // Alice got bigger! … } else { // Alice didn't change significantly … } }
var endGame = function() { stopPlayingAlice();
var alicePlayhead = aliceChange.currentTime; var aliceTimeline = aliceChange.effect.activeDuration;
var aliceHeight = alicePlayhead/aliceTimeline;
if (aliceHeight <= .333){ // Alice got smaller! … } else if (aliceHeight >= .666) { // Alice got bigger! … } else { // Alice didn't change significantly … } }
Bonus: Randomizing Animation
var getRandomMsRange = function(min, max) { return Math.random() * (max - min) + min; }
tears.forEach(function(tear) { tear.animate( tearsFalling, { delay: getRandomMsRange(-1000, 1000), duration: getRandomMsRange(2000, 6000), iterations: Infinity, easing: "cubic-bezier(0.6, 0.04, 0.98, 0.335)" }); });
var getRandomMsRange = function(min, max) { return Math.random() * (max - min) + min; }
tears.forEach(function(tear) { tear.animate( tearsFalling, { delay: getRandomMsRange(-1000, 1000), duration: getRandomMsRange(2000, 6000), iterations: Infinity, easing: "cubic-bezier(0.6, 0.04, 0.98, 0.335)" }); });
var getRandomMsRange = function(min, max) { return Math.random() * (max - min) + min; }
tears.forEach(function(tear) { tear.animate( tearsFalling, { delay: getRandomMsRange(-1000, 1000), duration: getRandomMsRange(2000, 6000), iterations: Infinity, easing: "cubic-bezier(0.6, 0.04, 0.98, 0.335)" }); });
var getRandomMsRange = function(min, max) { return Math.random() * (max - min) + min; }
tears.forEach(function(tear) { tear.animate( tearsFalling, { delay: getRandomMsRange(-1000, 1000), duration: getRandomMsRange(2000, 6000), iterations: Infinity, easing: "cubic-bezier(0.6, 0.04, 0.98, 0.335)" }); });
var getRandomMsRange = function(min, max) { return Math.random() * (max - min) + min; }
tears.forEach(function(tear) { tear.animate( tearsFalling, { delay: getRandomMsRange(-1000, 1000), duration: getRandomMsRange(2000, 6000), iterations: Infinity, easing: "cubic-bezier(0.6, 0.04, 0.98, 0.335)" }); });
Playback Rate
The Red Queen’s Race
var redQueen_alice = redQueen_alice_sprite.animate( spriteFrames, { easing: 'steps(6, end)', direction: "reverse", duration: 600, iterations: Infinity, playbackRate: 1 });
var redQueen_alice = redQueen_alice_sprite.animate( spriteFrames, { easing: 'steps(6, end)', direction: "reverse", duration: 600, iterations: Infinity, playbackRate: 1 });
setInterval( function() { if (redQueen_alice.playbackRate > .4) { redQueen_alice.playbackRate *= .9; } }, 3000);
setInterval( function() { if (redQueen_alice.playbackRate > .4) { redQueen_alice.playbackRate *= .9; } }, 3000);
setInterval( function() { if (redQueen_alice.playbackRate > .4) { redQueen_alice.playbackRate *= .9; } }, 3000);
var goFaster = function() { redQueen_alice.playbackRate *= 1.1; }
document.addEventListener("click", goFaster); document.addEventListener("touchstart", goFaster);
Running to Stay in Place
What’s Next
gith
ub.co
m/w
eb-a
nim
a0on
s/w
eb-
anim
a0on
s-js
Support for the Web Anima5ons API
Let’s do this.
I guess.
Spiri
t.js
Chro
me
Cana
ry
Timing Anima-on
Web Anima-ons
API
CSS Transi-ons
CSS Anima-ons
Web Anima5ons API opens the door to future anima5on specs
?
Ace Folks
Alex Miller Opal Essence
Chris Mills Brian Birtles
@RachelNabors .com
RachelNabors.com/waapi WebAnimaConWeekly.com
slack.AnimaConAtWork.com
Available for all your animaCon needs.