<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);

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.