DEV Community

Cover image for Bringing Dynamic React-like Effects to Plain HTML Websites
Jojo
Jojo

Posted on

Bringing Dynamic React-like Effects to Plain HTML Websites

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:

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

<style>
  .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);
  }
</style>

<script>
  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);
    });
  });
</script>
Enter fullscreen mode Exit fullscreen mode

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:

<div class="parallax-container">
  <div class="parallax-bg"></div>
  <div class="content">
    <h2>Parallax Effect</h2>
    <p>Scroll down to see the magic happen!</p>
  </div>
</div>

<style>
  .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;
  }
</style>

<script>
  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)`;
    });
  });
</script>
Enter fullscreen mode Exit fullscreen mode

3. Animated Page Transitions

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

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

<main id="content">
  <!-- Page content here -->
</main>

<style>
  @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;
  }
</style>

<script>
  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
    });
  });
</script>
Enter fullscreen mode Exit fullscreen mode

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:

<div class="custom-cursor"></div>

<style>
  .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;
  }
</style>

<script>
  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');
      });
    });
  });
</script>
Enter fullscreen mode Exit fullscreen mode

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:

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

<style>
  .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;
  }
</style>

<script>
  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);
    });
  });
</script>
Enter fullscreen mode Exit fullscreen mode

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:

<button class="hamburger-menu" aria-label="Menu">
  <div class="hamburger-line"></div>
  <div class="hamburger-line"></div>
  <div class="hamburger-line"></div>
</button>

<nav class="mobile-nav">
  <ul>
    <li><a href="/">Home</a></li>
    <li><a href="/about">About</a></li>
    <li><a href="/contact">Contact</a></li>
  </ul>
</nav>

<style>
  .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;
  }
</style>

<script>
  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');
    });
  });
</script>
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay