Modern web frameworks like React and Next.js have popularized impressive visual effects and interactions that make websites feel alive and engaging. However, you don't always need a complex JavaScript framework to achieve similar results. In this post, I'll show you how to implement some of these eye-catching effects using just HTML, CSS, and vanilla JavaScript.

Why Go Vanilla?

While frameworks offer tremendous benefits for complex applications, there are compelling reasons to consider implementing certain effects with vanilla web technologies:

  • Performance: No framework overhead means faster load times
  • Simplicity: Fewer dependencies and build steps
  • Accessibility: Works on legacy systems and low-bandwidth connections
  • Learning: Better understanding of the underlying web technologies

Let's dive into some popular effects and how to implement them without React or Next.js!

1. Smooth Scroll Animations

React libraries like Framer Motion make it easy to trigger animations as elements scroll into view. Here's how to recreate this with the Intersection Observer API:

class="animate-on-scroll">This will animate when scrolled into view


  .animate-on-scroll {
    opacity: 0;
    transform: translateY(30px);
    transition: opacity 0.8s ease, transform 0.8s ease;
  }

  .animate-on-scroll.visible {
    opacity: 1;
    transform: translateY(0);
  }



  document.addEventListener('DOMContentLoaded', () => {
    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          entry.target.classList.add('visible');
        }
      });
    }, { threshold: 0.1 });

    document.querySelectorAll('.animate-on-scroll').forEach(element => {
      observer.observe(element);
    });
  });

2. Parallax Scrolling Effect

Parallax scrolling creates depth by moving background elements slower than foreground elements. React libraries like react-parallax make this easy, but we can create a similar effect with vanilla JS:

class="parallax-container">
   class="parallax-bg">
   class="content">
    Parallax Effect
    Scroll down to see the magic happen!
  



  .parallax-container {
    position: relative;
    height: 500px;
    overflow: hidden;
  }

  .parallax-bg {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 150%;
    background-image: url('your-background-image.jpg');
    background-size: cover;
    background-position: center;
    z-index: -1;
  }

  .content {
    position: relative;
    padding: 100px 20px;
    color: white;
    text-shadow: 1px 1px 3px rgba(0,0,0,0.8);
    text-align: center;
  }



  window.addEventListener('scroll', () => {
    const scrollPosition = window.pageYOffset;
    const parallaxElements = document.querySelectorAll('.parallax-bg');

    parallaxElements.forEach(element => {
      const speed = 0.5; // Adjust for faster/slower effect
      element.style.transform = `translateY(${scrollPosition * speed}px)`;
    });
  });

3. Animated Page Transitions

Next.js makes page transitions seamless, but we can achieve similar effects with the History API and CSS animations:

href="/" class="nav-link" data-link>Home
   href="/about" class="nav-link" data-link>About
   href="/contact" class="nav-link" data-link>Contact


 id="content">
  



  @keyframes fadeIn {
    from { opacity: 0; transform: translateY(20px); }
    to { opacity: 1; transform: translateY(0); }
  }

  @keyframes fadeOut {
    from { opacity: 1; transform: translateY(0); }
    to { opacity: 0; transform: translateY(-20px); }
  }

  .page-transition-in {
    animation: fadeIn 0.5s forwards;
  }

  .page-transition-out {
    animation: fadeOut 0.3s forwards;
  }



  document.addEventListener('DOMContentLoaded', () => {
    const contentDiv = document.getElementById('content');

    document.querySelectorAll('[data-link]').forEach(link => {
      link.addEventListener('click', e => {
        e.preventDefault();
        const href = link.getAttribute('href');

        // Animate out
        contentDiv.classList.add('page-transition-out');

        setTimeout(() => {
          // Load new content (in a real app, you'd fetch the new page content)
          fetch(href)
            .then(response => response.text())
            .then(html => {
              const parser = new DOMParser();
              const doc = parser.parseFromString(html, 'text/html');
              const newContent = doc.querySelector('#content').innerHTML;

              // Update page content
              contentDiv.innerHTML = newContent;
              history.pushState(null, '', href);

              // Animate in
              contentDiv.classList.remove('page-transition-out');
              contentDiv.classList.add('page-transition-in');

              setTimeout(() => {
                contentDiv.classList.remove('page-transition-in');
              }, 500);
            });
        }, 300);
      });
    });

    // Handle browser back/forward buttons
    window.addEventListener('popstate', () => {
      // Similar logic to reload content based on current URL
    });
  });

4. Custom Cursor Effects

Many modern React sites feature custom cursors that follow your mouse. Here's how to create this effect with vanilla JS:

class="custom-cursor">


  .custom-cursor {
    position: fixed;
    width: 40px;
    height: 40px;
    border: 2px solid #333;
    border-radius: 50%;
    transform: translate(-50%, -50%);
    pointer-events: none;
    transition: width 0.3s, height 0.3s, background-color 0.3s;
    z-index: 9999;
  }

  .custom-cursor.hover {
    width: 60px;
    height: 60px;
    background-color: rgba(51, 51, 51, 0.1);
  }

  /* Hide cursor on interactive elements */
  a, button {
    cursor: none;
  }



  document.addEventListener('DOMContentLoaded', () => {
    const cursor = document.querySelector('.custom-cursor');

    document.addEventListener('mousemove', e => {
      cursor.style.left = `${e.clientX}px`;
      cursor.style.top = `${e.clientY}px`;
    });

    // Change cursor appearance when hovering over links
    document.querySelectorAll('a, button').forEach(el => {
      el.addEventListener('mouseenter', () => {
        cursor.classList.add('hover');
      });

      el.addEventListener('mouseleave', () => {
        cursor.classList.remove('hover');
      });
    });
  });

5. Image Lazy Loading with Blur Effect

Next.js offers built-in image optimization with blur placeholders. Here's how to create a similar effect:

class="lazy-image-container">
   class="lazy-image-placeholder" style="background-image: url('tiny-placeholder.jpg')">
   class="lazy-image" data-src="full-image.jpg" alt="Description">



  .lazy-image-container {
    position: relative;
    overflow: hidden;
    aspect-ratio: 16/9;
    background-color: #f0f0f0;
  }

  .lazy-image-placeholder {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-size: cover;
    background-position: center;
    filter: blur(20px);
    transform: scale(1.1);
    transition: opacity 0.5s ease;
  }

  .lazy-image {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: 0;
    transition: opacity 0.5s ease;
  }

  .lazy-image.loaded {
    opacity: 1;
  }

  .lazy-image.loaded + .lazy-image-placeholder {
    opacity: 0;
  }



  document.addEventListener('DOMContentLoaded', () => {
    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          const img = entry.target;
          const src = img.getAttribute('data-src');

          img.setAttribute('src', src);
          img.addEventListener('load', () => {
            img.classList.add('loaded');
          });

          observer.unobserve(img);
        }
      });
    }, { threshold: 0.1 });

    document.querySelectorAll('.lazy-image').forEach(img => {
      observer.observe(img);
    });
  });

6. Animated Hamburger Menu

Creating a smooth, animated hamburger menu is a common task in React apps. Here's how to create one with CSS and minimal JS:

class="hamburger-menu" aria-label="Menu">
   class="hamburger-line">
   class="hamburger-line">
   class="hamburger-line">


 class="mobile-nav">
  
     href="/">Home
     href="/about">About
     href="/contact">Contact
  



  .hamburger-menu {
    background: none;
    border: none;
    cursor: pointer;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    height: 24px;
    padding: 0;
    position: relative;
    width: 30px;
    z-index: 2;
  }

  .hamburger-line {
    background-color: #333;
    height: 3px;
    transition: transform 0.3s, opacity 0.3s;
    width: 100%;
  }

  /* Animated X shape when active */
  .hamburger-menu.active .hamburger-line:nth-child(1) {
    transform: translateY(10px) rotate(45deg);
  }

  .hamburger-menu.active .hamburger-line:nth-child(2) {
    opacity: 0;
  }

  .hamburger-menu.active .hamburger-line:nth-child(3) {
    transform: translateY(-10px) rotate(-45deg);
  }

  .mobile-nav {
    background-color: white;
    height: 100vh;
    left: 0;
    opacity: 0;
    position: fixed;
    top: 0;
    transform: translateX(-100%);
    transition: transform 0.3s ease, opacity 0.3s ease;
    width: 250px;
    z-index: 1;
  }

  .mobile-nav.active {
    opacity: 1;
    transform: translateX(0);
    box-shadow: 3px 0 10px rgba(0,0,0,0.1);
  }

  .mobile-nav ul {
    list-style: none;
    margin-top: 80px;
    padding: 0 20px;
  }

  .mobile-nav li {
    margin-bottom: 20px;
  }

  .mobile-nav a {
    color: #333;
    font-size: 18px;
    text-decoration: none;
  }



  document.addEventListener('DOMContentLoaded', () => {
    const hamburger = document.querySelector('.hamburger-menu');
    const nav = document.querySelector('.mobile-nav');

    hamburger.addEventListener('click', () => {
      hamburger.classList.toggle('active');
      nav.classList.toggle('active');
      document.body.classList.toggle('no-scroll');
    });
  });

Conclusion

Modern web effects don't have to be limited to complex JavaScript frameworks. With vanilla HTML, CSS, and JavaScript, you can implement many of the same impressive effects that make React and Next.js applications stand out.

These techniques not only make your website more engaging but can also lead to better performance since you're not loading heavy libraries. Plus, understanding how these effects work at a fundamental level will make you a better developer overall, even when you do use frameworks.

What other React/Next.js effects would you like to see implemented with vanilla web technologies? Let me know in the comments!


Happy coding!

BTW you can view/test the stuff from this post on Codepen: https://codepen.io/jojocraftde-dev/pen/emYeQZR