Skip to content

Instantly share code, notes, and snippets.

@matt-daniel-brown
Created December 26, 2020 16:28
Show Gist options
  • Save matt-daniel-brown/19dfdfedd5328d265730f647ae88dea9 to your computer and use it in GitHub Desktop.
Save matt-daniel-brown/19dfdfedd5328d265730f647ae88dea9 to your computer and use it in GitHub Desktop.
🚦Toggle animation with CSS & State Machines! Live collaborative coding with the @keyframers 2.32.0
<a href="https://youtu.be/0Wx9QbRQYuI" target="_blank" data-keyframers-credit style="color: #000"></a>
<script src="https://codepen.io/shshaw/pen/QmZYMG.js"></script>
<form id="app" data-state="off">
<div class="toggle-wrapper">
<input id="toggle" name="toggle" type="checkbox">
<label for="toggle" class="fancy-toggle">
<div class="bg"></div>
<div class="circle">
<svg viewBox="0 0 100 100">
<circle class="loader" cx="50" cy="50" r="45" pathLength="1" />
</svg>
</div>
</label>
<output class="status">
<div class="status-text" data-for="off">Devices Off</div>
<div class="status-text" data-for="pending">Please Wait...</div>
<div class="status-text" data-for="on">Devices On</div>
</output>
</div>
</form>
console.clear();
const elToggle = document.querySelector('#toggle');
const elApp = document.querySelector('#app');
const elBody = document.body;
// const machine = {
// initial: 'off',
// states: {
// off: {},
// pending: {},
// on: {}
// }
// }
const TIMEOUT = 1000;
elBody.style.setProperty('--timeout', TIMEOUT);
const transition = (state, event) => {
switch (state) {
case 'off':
return 'pending';
case 'pending':
switch (event.type) {
case 'ALL_ON':
return 'on';
case 'ALL_OFF':
return 'off';
default:
return state;
}
case 'on':
return 'pending';
default:
return 'off';
}
}
let current = elApp.dataset.state;
function activate(state) {
elApp.dataset.state = current;
elBody.dataset.toggleState = current;
document.querySelectorAll(`[data-active]`)
.forEach(el => delete el.dataset.active);
document.querySelectorAll(`[data-for="${current}"]`)
.forEach(el => el.dataset.active = true);
}
activate(current);
function send(event) {
const prevState = current;
elApp.dataset.prevState = prevState;
current = transition(current, event);
if (prevState === current) {
return;
}
activate(current);
switch (current) {
case 'off':
elToggle.indeterminate = false;
elToggle.checked = false;
elToggle.readOnly = false;
break;
case 'pending':
elToggle.readOnly = true;
elToggle.indeterminate = true;
setTimeout(() => {
if (prevState === 'on') {
send({ type: 'ALL_OFF' })
} else {
send({ type: 'ALL_ON' })
}
}, TIMEOUT);
break;
case 'on':
elToggle.readOnly = false;
elToggle.checked = true;
elToggle.indeterminate = false;
break;
default:
break;
}
}
elToggle.addEventListener('click', e => {
e.preventDefault();
send({ type: 'TOGGLE' });
});
@import url('https://fonts.googleapis.com/css?family=Open+Sans&display=swap');
*, *:before, *:after {
box-sizing: border-box;
position: relative;
}
:root {
--easing: cubic-bezier(.5, 0, .5, 1);
--duration: .3s;
--blue: #6C80F5;
}
html, body {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
font-family: 'Open Sans', sans-serif;
}
body {
display: flex;
justify-content: center;
align-items: center;
}
/* ---------------------------------- */
.toggle-wrapper {
font-size: 2rem;
display: flex;
align-items: center;
}
#toggle {
width: 2em;
height: 2em;
visibility: hidden;
position: absolute;
}
#app {
display: flex;
flex-direction: row;
color: #404843;
}
.fancy-toggle {
font-size: 3rem;
width: 1em;
height: 1em;
// border: solid 5px blue;
padding: 0.2em;
box-sizing: content-box;
margin-right: 1.5em;
transition: transform 0.4s var(--easing);
transition-property: transform, width, background-color;
.bg {
background: grey;
position: absolute;
top: 0;
left: 0;
width: 175%;
height: 100%;
border-radius: 2em;
box-shadow: 0 .25em .5em rgba(0,0,0,0.1);
transition: inherit;
}
.circle {
width: 1em;
height: 1em;
background: white;
box-shadow: 0 .25em .5em rgba(0,0,0,0.1);
border-radius: 50%;
transition: inherit;
> svg {
transform: rotate(-90deg);
margin: -.1em;
}
}
.loader {
stroke: var(--blue);
stroke-width: 6;
fill: none;
stroke-dasharray: 1 1;
stroke-dashoffset: -1;
transition:
stroke-dasharray calc(var(--timeout, 0) * 0.5ms) linear,
stroke-dashoffset 1000ms linear;
}
}
.status {
overflow: hidden;
display: grid;
user-select: none;
> div {
grid-area: 1 / 1;
}
}
.status-text {
transition: transform var(--duration) var(--easing);
&[data-for="off"],
&[data-for="pending"] {
transform: translateY(-100%);
}
&[data-for="on"] {
transform: translateY(100%);
}
&[data-active] ~ [data-for="pending"] {
transform: translateY(100%);
}
&[data-active] {
transform: translateY(0);
}
}
[data-state="off"] {
.fancy-toggle .bg {
background: #E44B45;
}
}
[data-state="pending"] {
.fancy-toggle {
transform: translateX(50%);
.bg {
width: 100%;
background: #b5b5b5;
}
.loader {
stroke-dashoffset: 0;
}
}
}
[data-state="on"] {
.fancy-toggle {
.bg {
background: #1BCE6F;
}
.circle {
transform: translateX(100%);
}
.loader {
stroke-dashoffset: 1;
}
}
}
body {
transition: background-color .3s var(--easing);
background-color: var(--bg-color);
&[data-toggle-state="off"] {
--bg-color: #FFE3E2;
}
&[data-toggle-state="pending"] {
--bg-color: #DCE1FF;
}
&[data-toggle-state="on"] {
--bg-color: #E1FCED;
}
}

🚦Toggle animation with CSS & State Machines! Live collaborative coding with the @keyframers 2.32.0

State machines for a toggle? You bet! David Khourshid & Stephen Shaw recreate this perfect example of a state machine powered animation using CSS & JavaScript.

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:

  • State Machines
  • CSS Animation
  • Data Attributes

A Pen by Matt Daniel Brown on CodePen.

License.

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