Skip to content

Instantly share code, notes, and snippets.

@matt-daniel-brown
Created September 6, 2020 00:21
Show Gist options
  • Save matt-daniel-brown/13d86d0a9225ced7e4c764ab61d71bdc to your computer and use it in GitHub Desktop.
Save matt-daniel-brown/13d86d0a9225ced7e4c764ab61d71bdc to your computer and use it in GitHub Desktop.
✅ UX Animation Principles: Duration | @keyframers 1.22.1
<a href="https://youtu.be/WLYaEohJgxE" target="_blank" data-keyframers-credit style="color: #000"></a>
<script src="https://codepen.io/shshaw/pen/QmZYMG.js"></script>
<div class="app" data-state="Good">
<div class="device" data-view="list">
<ul class="layer" data-layer="list">
<li class="content">
<div class="bg"></div>
<div class="avatar"></div>
<div class="title">Some list item</div>
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit.</p>
</li>
<li class="content">
<div class="bg"></div>
<div class="avatar"></div>
<div class="title">Some list item</div>
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit.</p>
</li>
<li class="content clickable" id="article-1">
<div class="bg" data-flip-key="bg-1"></div>
<div class="avatar" data-flip-key="avatar-1"></div>
<div class="title" data-flip-key="title-1">@keyframers</div>
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit.</p>
</li>
<li class="content">
<div class="bg"></div>
<div class="avatar"></div>
<div class="title">Some list item</div>
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit.</p>
</li>
</ul>
<div class="layer article" data-layer="article">
<button class="exit">𝖃</button>
<div class="avatar" data-flip-key="avatar-1" id="back"></div>
<div class="content">
<div class="bg" data-flip-key="bg-1"></div>
<h1 data-flip-key="title-1">@keyframers</h1>
<div class="title">This is some article</div>
<p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Dolores odio iste, deserunt incidunt obcaecati explicabo fugit facere debitis repellat harum reprehenderit, nesciunt doloremque id aperiam minima sapiente similique amet tempora?</p>
</div>
</div>
</div>
</div>
<div class="app" data-state="Bad">
<div class="device" data-view="list">
<ul class="layer" data-layer="list">
<li class="content">
<div class="bg"></div>
<div class="avatar"></div>
<div class="title">Some list item</div>
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit.</p>
</li>
<li class="content">
<div class="bg"></div>
<div class="avatar"></div>
<div class="title">Some list item</div>
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit.</p>
</li>
<li class="content clickable" id="article-2">
<div class="bg" data-flip-key="bg-2"></div>
<div class="avatar" data-flip-key="avatar-2"></div>
<div class="title" data-flip-key="title-2">@keyframers</div>
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit.</p>
</li>
<li class="content">
<div class="bg"></div>
<div class="avatar"></div>
<div class="title">Some list item</div>
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit.</p>
</li>
</ul>
<div class="layer article" data-layer="article">
<button class="exit">𝖃</button>
<div class="avatar" data-flip-key="avatar-2" id="back"></div>
<div class="content">
<div class="bg" data-flip-key="bg-2"></div>
<h1 data-flip-key="title-2">@keyframers</h1>
<div class="title">This is some article</div>
<p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Dolores odio iste, deserunt incidunt obcaecati explicabo fugit facere debitis repellat harum reprehenderit, nesciunt doloremque id aperiam minima sapiente similique amet tempora?</p>
</div>
</div>
</div>
</div>
console.clear();
const elDevices = document.querySelectorAll('.device');
const machine = {
initial: 'list',
states: {
list: {
on: {
ARTICLE: 'article'
}
},
article: {
on: {
BACK: 'list'
}
}
}
};
let currentState = elDevices[0].dataset.view;
function readCssVar(element, varName){
const elementStyles = getComputedStyle(element);
return elementStyles.getPropertyValue('--'+varName).trim();
}
const flipping = new Flipping({
duration: +readCssVar(elDevices[0], 'duration').replace(/(s|ms)/,'') || 300,
activeSelector: el => {
return el.matches(`[data-layer="${elDevices[0].dataset.view}"] *`);
}
});
function send(event) {
const nextState = machine.states[currentState].on[event];
flipping.read();
currentState = nextState;
[...elDevices].forEach( d => {
d.dataset.view = nextState;
});
flipping.flip();
}
const elArticles = [...document.querySelectorAll('.clickable')];
const elBackos = [...document.querySelectorAll('.exit')];
elArticles.forEach( el => {
el.addEventListener('click', () => {
send('ARTICLE');
});
});
elBackos.forEach( el => {
el.addEventListener('click', () => {
send('BACK');
});
});
<script src="https://unpkg.com/[email protected]/dist/flipping.web.js"></script>
$color: #FAE5E5;
$easing: cubic-bezier(.5, 0, .2, 1);
[data-state="Bad"] {
--duration: 1000ms;
--delay: 400ms;
--color: #FAE5E5;
--faded-color: #{rgba(#EE9F9F, .5)};
}
[data-state="Good"] {
--duration: 400ms;
--delay: 100ms;
--color: #E7EEF8;
--faded-color: #{rgba(darken(#E7EEF8,20%), .5)};
}
.app {
width: 100%;
height: 100%;
padding: 2rem;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
background: var(--color);
&:before {
font-family: monospace;
content: attr(data-state);
margin-bottom: .5em;
}
}
.state {
font-size: 1.5rem;
margin: 0 0 1rem;
text-align: center;
}
/* ---------------------------------- */
.device {
max-width: 20rem;
display: grid;
overflow: hidden;
border-radius: 1vmin;
background-color: #fff;
box-shadow: 0 2vmin 3vmin var(--faded-color);
> * {
grid-area: 1 / 1;
padding: 1rem;
}
}
/* ---------------------------------- */
ul {
margin: 0;
padding: 0;
list-style-type: none;
color: #000;
margin: auto;
}
li {
margin-bottom: .5rem;
animation: enter var(--duration) $easing backwards;
display: grid;
grid-template-columns: 25% auto;
grid-template-rows: auto auto;
grid-column-gap: 1rem;
align-items: center;
> .title {
grid-column: 2 / 3;
grid-row: 1 / 2;
}
> p {
grid-column: 2 / 3;
grid-row: 2 / 3;
}
> .avatar {
grid-column: 1 / 2;
grid-row: 1 / -1;
}
@for $i from 1 through 5 {
&:nth-child(#{$i}) {
animation-delay: calc(#{$i} * var(--delay));
}
}
}
@keyframes enter {
from {
opacity: 0;
transform: scale(.7);
}
}
.content {
border-radius: .4rem;
padding: 1rem;
--bg: var(--color);
}
.clickable {
.bg { border: solid 5px var(--faded-color); }
cursor: pointer;
}
.bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: var(--bg);
border-radius: inherit;
}
.exit {
z-index: 1;
position: absolute;
top: 1rem;
right: 1rem;
padding: 1rem;
appearance: none;
background: transparent;
font: inherit;
color: white;
border: none;
appearance: none;
}
.avatar {
background-color: var(--faded-color);
border-radius: .35rem;
&:before {
content: '';
display: block;
padding-bottom: 100%;
}
}
p {
font-size: .65rem;
line-height: 1.5;
margin: 0;
font-weight: bold;
opacity: 0.3;
font-family: monospace;
}
.title {
font-weight: bold;
opacity: 0.5;
margin-bottom: .5rem;
font-family: monospace;
}
/* ---------------------------------- */
.article {
// background: inherit;
.avatar {
margin-bottom: .5em;
}
.avatar:before {
padding-bottom: 40%;
}
}
/* ---------------------------------- */
/* Layer Toggling */
[data-layer] {
opacity: 0;
visibility: hidden;
transition: opacity var(--duration) linear, visibility 0s linear var(--duration);
}
[data-view="list"] [data-layer="list"],
[data-view="article"] [data-layer="article"] {
opacity: 1;
transition-delay: 0s, 0s;
visibility: visible;
}
/* ---------------------------------- */
[data-layer="list"] {
li {
transform: translateY(0%);
transition: transform var(--duration) ease;
}
}
[data-view="article"] {
[data-layer="list"] {
> li { transform: translateY(-100%); }
.clickable { transform: none; }
.clickable ~ li { transform: translateY(100%); }
}
[data-layer="article"] {
.title, p {
animation: enter-content calc(var(--duration) * 1.5) calc(var(--delay) / 2) $easing both;
~ * {
animation-delay: calc(var(--delay) * 1.5);
}
}
}
}
@keyframes enter-content {
from {
transform: translateY(1rem);
opacity: 0;
}
}
/* ---------------------------------- */
*, *:before, *:after {
box-sizing: border-box;
position: relative;
}
body {
display: flex;
justify-content: center;
align-items: center;
}
body, html {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}

✅ UX Animation Principles: Duration | @keyframers 1.22.1

David Khourshid and Stephen Shaw bring examples from an outstanding article on UX animation to life, showcasing good and bad approaches to state animations. This week, they cover the importance of staggering & duration.

Additional Resources:

Like what we're doing? There are many ways you can support @keyframers so we can keep live coding awesome animations!

Topics covered:

  • CSS Animation
  • Staggering
  • Duration
  • State machines
  • CSS Grid
  • Layering

A Pen by Matthew Daniel Brown on CodePen.

License.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment