Creating Custom JavaScript Animations from Scratch: A Hands-On Guide

Creating Custom JavaScript Animations from Scratch: A Hands-On Guide

Why Bother Creating Custom JavaScript Animations?

Alright, picture this: you’re scrolling through a site, and suddenly, a button gently pulses under your cursor, or a menu slides in with a slick bounce that just feels… right. You might not think about it, but those little flourishes are the secret sauce that turns a decent experience into a memorable one.

Now, you could lean on CSS animations or libraries like GSAP, which are great—don’t get me wrong—but there’s something deeply satisfying about crafting animations from scratch with vanilla JavaScript. It’s like painting your own masterpiece instead of coloring inside the lines. Plus, it forces you to understand timing, easing, and frame rates in a way no shortcut can teach.

Trust me, I’ve been there—early on, I thought it was overkill or too fiddly. But once I got the hang of the fundamentals, it opened a whole new playground for interactivity. And that’s what I want to share with you today, no fluff, just the real deal from my own toolkit.

Getting Your Hands Dirty: The Basics of Animation in JS

At its core, animation is about updating the properties of an element over time. Whether it’s position, opacity, scale, or color, you’re essentially telling the browser: “Hey, move this thing here, now, smoothly.”

Here’s the kicker: the web runs animations in frames—basically snapshots—usually 60 times per second. So your job is to update your element’s state before each frame is painted.

Enter requestAnimationFrame, your best friend for smooth animations. Unlike timers like setTimeout or setInterval, which can get janky, requestAnimationFrame syncs perfectly with the browser’s repaint cycle, giving you buttery smooth motion.

Here’s a quick example of moving a box from left to right:

const box = document.querySelector('.box');
let start = null;
const duration = 2000; // 2 seconds
const distance = 300; // pixels

function step(timestamp) {
  if (!start) start = timestamp;
  const elapsed = timestamp - start;
  const progress = Math.min(elapsed / duration, 1);
  box.style.transform = `translateX(${progress * distance}px)`;
  if (progress < 1) {
    requestAnimationFrame(step);
  }
}
requestAnimationFrame(step);

Simple, right? But wait—let’s unpack this:

  • timestamp: Passed automatically by requestAnimationFrame, it represents the current time in milliseconds.
  • start: We capture the starting timestamp to calculate elapsed time.
  • progress: A normalized value between 0 and 1 that tells us how far along we are.
  • transform: We move the box based on progress.

This pattern—capturing time, calculating progress, and updating the element—is the heartbeat of JS animations.

Adding Easing: Because Linear is Boring

Ever notice how a car doesn’t just slam the gas pedal flat to get going? It eases in, picks up speed, then slows gently to stop. That’s easing in animations, and it’s crucial for natural-feeling motion.

Without easing, things look robotic. Luckily, easing functions are just math, and you can plug them right into your progress calculation.

Here’s a simple ease-out cubic function:

function easeOutCubic(t) {
  return 1 - Math.pow(1 - t, 3);
}

Modify the earlier code like so:

const easedProgress = easeOutCubic(progress);
box.style.transform = `translateX(${easedProgress * distance}px)`;

That little tweak makes a world of difference. Suddenly, the box slows down smoothly as it reaches the end of its journey.

Pro tip: There’s a whole treasure trove of easing functions out there. Robert Penner’s easing equations are a classic starting point (https://easings.net/).

Building a Reusable Animation Function

Writing this stuff inline is fine for one-offs, but real projects demand flexibility. Here’s a pattern I swear by: a generic animate function that takes an object describing what to animate, how long, and with what easing.

function animate({duration, draw, easing = t => t, onComplete}) {
  let start = null;

  function frame(time) {
    if (!start) start = time;
    let timeFraction = (time - start) / duration;
    if (timeFraction > 1) timeFraction = 1;

    const progress = easing(timeFraction);

    draw(progress);

    if (timeFraction < 1) {
      requestAnimationFrame(frame);
    } else if (onComplete) {
      onComplete();
    }
  }

  requestAnimationFrame(frame);
}

Use it like this:

animate({
  duration: 1500,
  easing: easeOutCubic,
  draw(progress) {
    box.style.transform = `translateX(${progress * distance}px)`;
  },
  onComplete() {
    console.log('Animation done!');
  }
});

This approach brings clarity and reusability. You can animate anything, not just position—opacity, color, size… you name it.

A Real-World Example: Creating a Bouncy Button

Let me tell you about this one time I needed a button to bounce when clicked. Not just a quick scale-up, but a playful little jiggle that made the interaction feel alive.

Here’s a simplified version of how I did it:

const button = document.querySelector('.bounce-btn');

function bounce(progress) {
  // Using a sine wave for a smooth bounce effect
  return Math.sin(progress * Math.PI * 2) * (1 - progress);
}

button.addEventListener('click', () => {
  animate({
    duration: 600,
    easing: t => t, // linear here because bounce function handles easing
    draw(progress) {
      const scale = 1 + 0.2 * bounce(progress);
      button.style.transform = `scale(${scale})`;
    }
  });
});

What’s neat is that the sine function creates that natural up-and-down bounce, while multiplying by (1 - progress) fades it out smoothly. The result? A button that feels like it’s got a heartbeat.

Honestly, I wasn’t convinced at first that hand-rolling this was worth the effort. But after seeing how intuitive it felt to tweak the bounce amplitude, duration, and timing, it was clear—sometimes you just gotta get under the hood.

Performance Tips for Smooth Animations

Because — let’s be real — nothing kills vibes like a choppy animation. Here are a few nuggets I picked up on the job:

  • Manipulate transform and opacity only: These properties are GPU-accelerated and won’t trigger layout recalculations. Avoid animating width, height, top, etc., if you can.
  • Batch DOM reads and writes: Reading and writing DOM properties back-to-back can cause forced synchronous layouts. Try to separate reads and writes where possible.
  • Throttle heavy calculations: If your animation involves heavy math or data fetching, debounce or throttle those parts.
  • Use will-change sparingly: Adding will-change: transform; hints to the browser to optimize, but overusing it can backfire.

Going Beyond: Combining with CSS and Libraries

Here’s a little secret: custom JS animations don’t have to replace CSS or libraries—they can complement them. For example, you might handle complex timing and easing in JS, but let CSS handle hover states or keyframe sequences for simpler animations.

And if you want to level up without reinventing the wheel, libraries like GSAP or Anime.js provide powerful APIs that still let you dig deep when needed.

But if you want to truly understand what’s going on behind the scenes—and maybe even fix things when they go sideways—starting with scratch-made animations is pure gold.

Wrapping Up: Your Own Animation Playground

So… what’s the takeaway? If you’re itching to make your apps pop or just flex your JavaScript muscles, building custom animations from the ground up is a rewarding way to go. It’s not just about flashy effects—it’s about understanding timing, easing, and the subtle magic that makes digital experiences feel human.

Next time you need an animation, try this: sketch out what you want, then break it down frame-by-frame. Don’t rush. Play with easing curves. Watch your frames like a hawk. And don’t sweat the mess-ups—each glitch is just a lesson waiting to happen.

Give it a try, and see how it changes not just your projects, but the way you think about front-end craft. And hey, if you want to share your experiments or ask questions, I’m all ears. Animation is a wild ride, but it’s one worth taking.

Written by

Related Articles

Creating Custom JavaScript Animations from Scratch