<section id="flip" data-state="initial">
<button id="prev">‹</button>
<div class="window">
<div class="box"></div>
</div>
<div class="steps">
<p data-flip-key="first">
<strong>First:</strong> get the initial dimensions
</p>
<p data-flip-key="last">
<strong>Last:</strong> get dimensions after change
</p>
<p data-flip-key="invert">
<strong>Invert:</strong> calculate deltas to move element to its original position
</p>
<p data-flip-key="play">
<strong>Play:</strong> animate from original position to final position
</p>
</div>
<button id="next">›</button>
</section>
body, section {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
section {
width: 100vw;
> .steps, > .window {
flex-basis: 40%;
flex-grow: 0;
flex-shrink: 0;
}
}
[data-state="initial"] {
[data-flip-key] {
display: none;
}
.box {
outline-color: transparent;
}
}
[data-state="initial"], [data-state="play"] {
.box:before {
opacity: 1;
}
}
// First
[data-state="first"] {
[data-flip-key]:nth-child(1) ~ [data-flip-key] {
display: none
}
}
// Last
[data-state="last"] {
[data-flip-key]:nth-child(2) ~ [data-flip-key] {
display: none
}
.box:before {
transform:
translate(
calc(20vw - 5vmin - 1rem),
calc(25vh - 5vmin - 1rem)
);
}
.box:after {
opacity: 1;
}
}
// Invert
[data-state="invert"] {
[data-flip-key]:nth-child(3) ~ [data-flip-key] {
display: none
}
.box:after {
opacity: 1;
}
.box:before {
transform:
translate(0, 0);
}
}
// Play
[data-state="play"] {
.box:after {
opacity: 1;
}
.box:before {
opacity: 1;
transform:
translate(0, 0);
animation: play 2s ease-in-out both infinite;
@keyframes play {
from, 10% {
transform: none;
}
50%, to {
transform: translate(
calc(20vw - 5vmin - 1rem),
calc(25vh - 5vmin - 1rem)
);
}
75% {
opacity: 1;
}
to {
opacity: 0;
}
}
}
}
button {
appearance: none;
-webkit-appearance: none;
background: transparent;
border: none;
color: white;
font-size: 10vmin;
font-weight: normal;
}
.steps {
flex-basis: 50%;
padding: 0 2rem;
[data-state="initial"] &:before {
content: "Let's smoothly animate the blue box to the center. Press the right arrow to begin."
}
}
.box {
position: absolute;
top: 1rem;
left: 1rem;
height: 10vmin;
width: 10vmin;
outline: 2px dashed #16c72e;
outline-offset: 1px;
&:before, &:after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
transition: opacity .3s ease-out;
}
&:before {
opacity: 0.5;
background-color: #449dda;
border-radius: .25rem;
will-change: transform;
}
&:after {
outline: 2px dashed #E8747E;
outline-offset: inherit;
opacity: 0;
transform:
translate(
calc(20vw - 5vmin - 1rem),
calc(25vh - 5vmin - 1rem)
);
}
}
.window {
height: 70vh;
width: 40vw;
border-top: 2rem solid #C4C7C8;
border-radius: .5rem;
background-color: #fff;
box-shadow: 0 0 5vw 0 rgba(black, 0.2);
}
body {
background-image: linear-gradient(to bottom right, #636C91, #313A5C);
color: #FEFEFE;
}
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
*, *:before, *:after {
box-sizing: border-box;
position: relative;
}
View Compiled
const flipContainer = document.getElementById('flip');
const machine = {
initial: 'initial',
states: {
initial: {
on: { NEXT: 'first' }
},
first: {
on: { NEXT: 'last', PREV: 'initial' }
},
last: {
on: { NEXT: 'invert', PREV: 'first' }
},
invert: {
on: { NEXT: 'play', PREV: 'last' }
},
play: {
on: { PREV: 'invert', NEXT: 'initial' }
},
}
};
const flipping = new Flipping();
let state = machine.initial;
const transition = (state, action) => {
try {
return machine.states[state].on[action];
} catch (e) {
return undefined;
}
}
const update = flipping.wrap((nextState) => {
state = nextState;
flipContainer.setAttribute('data-state', nextState);
});
const emit = (action) => (e) => {
const nextState = transition(state, action);
console.log(action, state, nextState);
if (nextState) {
update(nextState);
}
}
const prev = document.getElementById('prev');
const next = document.getElementById('next');
prev.addEventListener('click', emit('PREV'));
next.addEventListener('click', emit('NEXT'));
This Pen doesn't use any external CSS resources.