<footer>
<p>This demo is part of <a href="https://goo.gle/css-wrapped-2023" target="_top">#CSSWrapped2023</a></p>
</footer>
<div class="demo">
<div>
<h2>Custom Easing</h2>
<p>Custom bounce easing, implemented via JavaScript.</p>
<div class="bounce" data-method="js">
<img src="https://assets.codepen.io/89905/matroshka-01.svg" alt="" title="" width="222" height="184" draggable="false">
</div>
</div>
<div>
<h2>linear()</h2>
<p>Custom bounce easing, simplified via <code>linear()</code>.</p>
<div class="bounce" data-method="css">
<img src="https://assets.codepen.io/89905/matroshka-02.svg" alt="" title="" width="222" height="184" draggable="false">
</div>
</div>
</div>
:root {
--custom-easing: linear(
0,
0.06,
0.25 18%,
1 36%,
0.81,
0.75,
0.81,
1,
0.94,
1,
1
);
}
.bounce[data-method="css"] {
animation: move var(--custom-easing) 2s infinite both;
}
@keyframes move {
to {
translate: 0% 200%;
}
}
.demo {
display: flex;
flex-direction: row;
width: 100%;
gap: 2em;
justify-content: space-between;
text-align: center;
}
@layer reset {
*,
*:after,
*:before {
box-sizing: border-box;
}
* {
margin: 0;
padding: 0;
}
ul[class] {
list-style: none;
}
label {
cursor: pointer;
max-width: max-content;
user-select: none;
}
}
@layer baselayout {
html {
margin: auto;
line-height: 1.5;
font-size: 1.25em;
font-family: "Syne", sans-serif;
min-height: 100%;
background: white;
}
body {
max-width: 75ch;
margin: 0 auto;
min-height: 100dvh;
padding: 2em;
}
footer {
text-align: center;
font-style: italic;
margin-bottom: 1rem;
width: 100%;
}
h2 {
font-family: "Anybody", sans-serif;
text-decoration: underline;
text-decoration-color: hsl(156deg 100% 50% / 50%);
text-decoration-thickness: 0.2rem;
text-decoration-style: wavy;
text-decoration-skip-ink: none;
}
h2,
.demo p {
margin-bottom: 1em;
text-wrap: balance;
}
}
// Bounce easing function
const bounce = function (pos) {
const n1 = 7.5625;
const d1 = 2.75;
if (pos < 1 / d1) {
return n1 * pos * pos;
} else if (pos < 2 / d1) {
return n1 * (pos -= 1.5 / d1) * pos + 0.75;
} else if (pos < 2.5 / d1) {
return n1 * (pos -= 2.25 / d1) * pos + 0.9375;
} else {
return n1 * (pos -= 2.625 / d1) * pos + 0.984375;
}
};
// Elastic easing function
const elastic = function (x) {
if (x === 0 || x === 1) return x;
const c4 = (2 * Math.PI) / 3;
return Math.pow(2, -10 * x) * Math.sin((x * 10 - 0.75) * c4) + 1;
};
// Need more easings? Go check out https://easings.net/
// Config
const waapi_precision = 100;
const linear_precision = 25;
const easing = bounce;
// Build keyframes using custom easing function
const values = [...Array(waapi_precision).keys()].map((i) =>
easing(i / waapi_precision)
);
const keyframes = values.map((value) => ({ translate: `0% ${value * 200}%` }));
// Set CSS animation to use this custom easing too
const toLinear = (fn, points = 50) => {
// Source: https://developer.mozilla.org/en-US/blog/custom-easing-in-css-with-linear/#recreating_popular_eases_with_javascript
const result = [...new Array(points)]
.map((d, i) => {
const x = i * (1 / points);
return fn(x);
})
.join(",");
return `linear(${result})`;
};
document.documentElement.style.setProperty(
"--custom-easing",
toLinear(easing, linear_precision)
);
// Animate element via JS
const anim = document
.querySelector('.bounce[data-method="js"]')
.animate(keyframes, {
duration: 2000,
easing: "linear",
fill: "both",
iterations: Infinity
});
// Sync up JS animation to CSS version that’s already playing
setTimeout(() => {
anim.startTime = document
.querySelector('.bounce[data-method="css"]')
.getAnimations()[0].startTime;
}, 100);
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.