Recreate jQuery fadeIn with CSS and Vanilla JS
Transitioning away from jQuery often requires finding native
alternatives to built-in animation methods like .fadeIn().
This guide demonstrates how to replicate jQuery's .fadeIn()
functionality using modern vanilla JavaScript and CSS transitions. By
combining native DOM manipulation with hardware-accelerated CSS
properties, you can achieve smoother, more performant fade-in animations
without relying on external dependencies.
The Challenge with Display and Opacity
In jQuery, .fadeIn() handles both setting the element's
CSS display property and animating its
opacity. In standard CSS, the display property
cannot be animated. If you change an element from
display: none to display: block and
simultaneously alter its opacity, the browser executes both
at once, resulting in an abrupt appearance rather than a smooth
fade.
To solve this natively, you must reveal the element in the document
flow, trigger a browser reflow or defer the opacity change by a frame,
and then transition the opacity to 1.
1. The CSS Setup
Define the base transition styles and utility classes. The base
element starts hidden with an opacity of 0, and
transitioning the opacity handles the fade effect.
.fade-element {
opacity: 0;
display: none;
transition: opacity 400ms ease-in-out;
}
.fade-element.is-visible {
opacity: 1;
}2. The Vanilla JavaScript Implementation
To make the transition work, switch display to a visible
value (such as block or inline-block), force
the browser to compute the updated styles, and then apply the class that
increases the opacity.
function fadeIn(element, displayType = 'block') {
// 1. Make the element part of the layout
element.style.display = displayType;
// 2. Force a reflow to ensure the browser registers the initial opacity of 0
void element.offsetWidth;
// 3. Trigger the transition by adding the active class
element.classList.add('is-visible');
}Reading element.offsetWidth forces the browser to
recalculate styles (a reflow), guaranteeing that the transition from
0 to 1 takes place smoothly instead of
instantly jumping to the final state.
3. Creating a Dynamic, Standalone Function
If you need a flexible function that accepts custom
durations—mimicking the duration argument in jQuery's
.fadeIn(duration)—you can control the inline styles
entirely via JavaScript:
function fadeIn(element, duration = 400, displayType = 'block') {
element.style.opacity = '0';
element.style.display = displayType;
element.style.transition = `opacity ${duration}ms ease-in-out`;
// Use requestAnimationFrame to apply the opacity change on the next paint
requestAnimationFrame(() => {
requestAnimationFrame(() => {
element.style.opacity = '1';
});
});
}Complete Example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Native fadeIn Example</title>
<style>
#box {
width: 200px;
height: 200px;
background-color: #007acc;
display: none;
opacity: 0;
transition: opacity 500ms ease;
}
#box.visible {
opacity: 1;
}
</style>
</head>
<body>
<button id="trigger">Fade In</button>
<div id="box"></div>
<script>
const button = document.getElementById('trigger');
const box = document.getElementById('box');
function fadeIn(el) {
el.style.display = 'block';
void el.offsetWidth; // Trigger reflow
el.classList.add('visible');
}
button.addEventListener('click', () => {
fadeIn(box);
});
</script>
</body>
</html>This pattern provides the same visual result as jQuery's
.fadeIn() method while running natively on the browser's
compositor thread for optimal rendering performance.