.filmstrip {
animation: slide 1s infinite steps(7);
}
@keyframes slide {
to { transform: translate(-700px); }
}
.filmstrip {
/* steps(7) = 7 "changes" including the last one */
animation: slide 1s infinite steps(7);
}
@keyframes slide {
to { transform: translate(-700px); }
}
.filmstrip {
animation: slide 1s infinite steps(6);
}
@keyframes slide {
to { transform: translate(-700px); }
}
.filmstrip {
/* steps(6) = steps(6, end) */
animation: slide 1s infinite steps(6);
}
@keyframes slide {
to { transform: translate(-700px); }
}
.filmstrip {
animation: slide 1s infinite steps(6, start);
}
@keyframes slide {
to { transform: translate(-700px); }
}
.filmstrip {
/* New! jump-none! */
animation: slide 1s infinite steps(7, jump-none);
}
@keyframes slide {
to { transform: translate(-700px); }
}
jump-none
, jump-both
transitionrun
→ a transition was created 🆕
→ ok to wait for transitionend
🙆♂️
transitioncancel
→ element vanished 🗑️
(dropped, re-“rendered”,
display:none
etc.)
→ don't wait for transitionend
❌
transitionstart
(to match animationstart
)
animationcancel
(as with transitioncancel
)
transitioncancel
and friendsanimationcancel
transitionstart
animationcancel
Element.animate
.
const uniqueKeyframesName = generateUuid();
document.styleSheets[0].insertRule(
`@keyframes ${uniqueKeyframesName} {
from { transform: scale(0) }
95% { transform: scale(${fullSize}) }
to { transform: scale(0) }
}`
);
star.style.animation =
`${uniqueKeyframesName} ${duration}ms ` +
`${delay}ms infinite`;
// Don't forget to clean up!
transitionrun
, transitioncancel
etc.
Element.animate()
star.animate(
[
{ transform: 'scale(0)' },
{ transform: `scale(${fullSize})`, offset: 0.95 },
{ transform: 'scale(0)' },
],
{ easing, duration, delay, iterations: Infinity }
);
Element.animate()
const starAnimation = star.animate(
[
{ transform: 'scale(0)' },
{ transform: `scale(${fullSize})`, offset: 0.95 },
{ transform: 'scale(0)' },
],
{ easing, duration, delay, iterations: Infinity }
);
// starAnimation.reverse();
// starAnimation.cancel();
// starAnimation.updatePlaybackRate(0.5);
starAnimation.finished.then(() => { ... });
Element.animate()
.body, .head {
animation-timing-function: steps(4);
}
.head {
animation-timing-function: steps(4);
}
@keyframes drop-head {
60% { transform: rotate(45deg) }
}
@keyframes drop-head {
0% {
transform: none;
animation-timing-function: steps(4);
}
50% {
transform: rotate(45deg);
animation-timing-function: steps(4);
}
100% { transform: none; }
}
head.animate(
{
// Implicit 0%/100% keyframes are not yet
// shipping in Chrome/Firefox
transform: ['none', 'rotate(60deg)', 'none'],
offset: [0, 0.6, 1],
},
{
duration: 2000,
iterations: Infinite,
easing: 'steps(4)'
}
);
Element.animate
.
head.animate(
{
transform: ['none', 'rotate(60deg)', 'none'],
offset: [0, 0.6, 1],
easing: 'ease-in',
},
{
duration: 2000,
iterations: Infinite,
easing: 'steps(4)'
}
);
Element.animate
.
Element.getAnimations()
document.addEventListener('transitionrun', evt => {
if (evt.propertyName !== 'transform') {
return;
}
const transition = evt.target
.getAnimations()
.find(animation => animation.transitionProperty === 'transform');
const keyframes = transition.effect.getKeyframes();
const distance = calculateDistance(
keyframes[0].transform,
keyframes[1].transform
);
// Move at 700px per second
const duration = (distance / 700) * 1000;
transition.effect.updateTiming({ duration });
// NOTE: `transition.updatePlaybackRate()` would be even better
});
Element.getAnimations
.
Element.getAnimations
to return
transform transition endpoints.
Element.getAnimations()
CSSTransition.setKeyframes()
document.addEventListener('transitionrun', evt => {
if (evt.propertyName !== 'fill') {
return;
}
const transition = evt.target
.getAnimations()
.find(animation => animation.transitionProperty === 'fill');
const keyframes = transition.effect.getKeyframes();
const hslKeyframes = generateHslKeyframes(
keyframes[0].fill,
keyframes[1].fill
);
transition.effect.setKeyframes(hslKeyframes);
});
Element.getAnimations
.
setKeyframes
on
transitions.
CSSTransition.setKeyframes() (Element.getAnimations)
offset-path
.
offset-path
.goose {
offset-path: path('M100 100...');
animation: fly 10s linear infinite;
}
@keyframes fly {
to { offset-distance: 100%; }
}
offset-path
offset-anchor
preffed off)
offset-anchor
preffed off)
transform
and opacity
offset-path
.
prefers-reduced-motion: reduce | no-preference
div {
animation: ...
transition: ...
}
@media (prefers-reduced-motion: reduce) {
* {
animation-name: none !important;
transition-property: none !important;
/* Or to ensure events still fire, e.g.
* transition-duration: 0.01s !important;
* transition-delay: 0s !important;
* ...
*/
}
}
// Check for browser support
if (!('animate' in elem)) {
return;
}
// Check for user support
if (matchMedia('(prefers-reduced-motion)').matches) {
return;
}
// Animate away...
prefers-reduced-motion
jump-none
transitioncancel
Element.animate
Element.animate
Element.getAnimations
Element.getAnimations
offset-path
prefers-reduced-motion