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