Question
I have a script that uses $(document).ready(...), but it does not use any other jQuery features. I would like to remove the jQuery dependency to make the script lighter.
How can I implement the equivalent of $(document).ready() using plain JavaScript?
I know that window.onload is not the same, because window.onload fires only after images, frames, and other external resources have fully loaded.
Short Answer
By the end of this page, you will understand what jQuery's $(document).ready() actually does, how to replace it with native JavaScript, and when to use DOMContentLoaded, document.readyState, or a deferred script instead.
Concept
$(document).ready() exists to run JavaScript as soon as the HTML document has been parsed and the DOM is ready to use.
In plain JavaScript, the native event for this is usually DOMContentLoaded.
document.addEventListener('DOMContentLoaded', function () {
console.log('DOM is ready');
});
This matters because many scripts need the DOM to exist before they can safely query elements, attach event listeners, or modify content.
For example, this code fails if it runs too early:
const button = document.getElementById('save');
button.addEventListener('click', handleSave);
If the browser has not parsed the <button id="save"> yet, button will be null.
DOMContentLoaded solves that by waiting until the document structure is loaded.
Important distinction
Mental Model
Think of the browser as preparing a room before you enter it.
- While the HTML is still being parsed, the furniture is still being placed.
- When
DOMContentLoadedfires, the room layout is finished and you can walk in and use it. - When
window.onloadfires, not only is the room ready, but all deliveries have also arrived: paintings, decorations, and extra equipment.
If your script only needs the room layout, you do not need to wait for every delivery. That is why DOMContentLoaded is usually the better choice.
Syntax and Examples
Basic native replacement
document.addEventListener('DOMContentLoaded', function () {
console.log('The DOM is ready');
});
This runs once the browser has finished parsing the HTML.
Reusable ready helper
function ready(fn) {
if (document.readyState !== 'loading') {
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
}
}
ready(function () {
const message = document.getElementById('message');
if (message) {
message.textContent = 'Page is ready';
}
});
Why this helper is useful
If your script runs before the DOM is ready, it waits. If your script runs after the DOM is ready, it runs immediately.
Step by Step Execution
Consider this code:
function ready(fn) {
if (document.readyState !== 'loading') {
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
}
}
ready(function () {
const title = document.getElementById('title');
console.log(title.textContent);
});
Step by step
- The browser starts executing the script.
- The
readyfunction is defined. ready(...)is called with a callback function.- The code checks
document.readyState.
Possible paths:
Case 1: DOM is still loading
document.readyStateis'loading'- The callback is attached to
Real World Use Cases
Native DOM-ready logic is useful in many common situations:
- Form setup: attach submit handlers after the form exists
- UI initialization: open tabs, modals, menus, or accordions once elements are available
- Client-side validation: register input listeners after fields are in the DOM
- Dashboard widgets: render charts or counters into existing containers
- Progressive enhancement: start with working HTML, then add JavaScript behavior when the DOM is ready
- Third-party script cleanup: remove jQuery from older code that only used
$(document).ready()
Example:
function ready(fn) {
if (document.readyState !== 'loading') {
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
}
}
ready(function () {
const menuButton = document.getElementById('menu-button');
const menu = document.getElementById('menu');
if (!menuButton || !menu) return;
menuButton.(, () {
menu..();
});
});
Real Codebase Usage
In real projects, developers usually use one of these patterns:
1. Deferred scripts
This is common in modern codebases.
<script src="app.js" defer></script>
This often removes the need for a manual ready helper.
2. Guard clauses after DOM access
Developers often combine DOM-ready logic with safe checks:
document.addEventListener('DOMContentLoaded', function () {
const modal = document.getElementById('modal');
if (!modal) return;
// Initialize modal here
});
This prevents errors on pages where an element does not exist.
3. Small init functions
Large applications often wrap startup code in named functions:
function initSearch() {
const input = document.();
(!input) ;
input.(, () {
.();
});
}
.(, initSearch);
Common Mistakes
1. Using window.onload when you only need the DOM
Broken approach for most DOM setup tasks:
window.onload = function () {
console.log('Runs later than necessary');
};
Why it is a problem:
- waits for images and other resources
- can make initialization feel slower
Prefer:
document.addEventListener('DOMContentLoaded', function () {
console.log('Runs as soon as the DOM is ready');
});
2. Forgetting that the event may have already fired
Broken code:
function runApp() {
console.log('Start');
}
document.addEventListener('DOMContentLoaded', runApp);
If this script is loaded late, may never run.
Comparisons
| Approach | Fires when | Best for | Notes |
|---|---|---|---|
$(document).ready() | When DOM is ready | Older jQuery code | jQuery abstraction |
DOMContentLoaded | When DOM is parsed | Native DOM setup | Main replacement |
window.onload | After all resources load | Cases that depend on images or full page assets | Usually later than needed |
<script defer> | Script runs after HTML parsing | Modern external scripts | Often the cleanest option |
Script at end of <body> | Usually after DOM elements are parsed |
Cheat Sheet
// Basic replacement
document.addEventListener('DOMContentLoaded', function () {
// DOM is ready
});
// Robust helper
function ready(fn) {
if (document.readyState !== 'loading') {
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
}
}
ready(function () {
// Safe DOM code here
});
Quick rules
- Use
DOMContentLoadedto replace$(document).ready() - Use
window.onloadonly if you need images or full assets loaded - Use
document.readyStateif the script might run after DOM readiness - Prefer
<script defer>for modern external scripts - Check for missing elements with guard clauses
Common values
FAQ
What is the native equivalent of $(document).ready()?
Use document.addEventListener('DOMContentLoaded', callback).
Is DOMContentLoaded the same as window.onload?
No. DOMContentLoaded waits for the DOM. window.onload waits for all page resources.
Why might DOMContentLoaded not run in my script?
If your script loads after the event already fired, the listener will not run. Use a document.readyState check.
Can I avoid a ready handler completely?
Yes. If you load your script with defer, your code usually runs after the DOM is parsed.
Should I still use jQuery for document ready?
Usually no. Modern browsers support native DOM-ready patterns well.
When should I use window.onload instead?
Use it when your code depends on fully loaded images, iframes, or other external assets.
Is placing the script at the end of <body> enough?
Often yes for simple pages, because the DOM elements above it already exist. But is generally a cleaner modern approach.
Mini Project
Description
Build a small page initializer that sets up a button click handler and updates a status message as soon as the DOM is ready, without using jQuery. This demonstrates how to safely work with DOM elements using a reusable native ready helper.
Goal
Create a plain JavaScript startup function that works whether the DOM is still loading or has already finished loading.
Requirements
- Create a reusable
readyfunction using native JavaScript. - Wait until the DOM is available before selecting elements.
- Update a status element when initialization completes.
- Add a click event listener to a button.
- Handle missing elements safely without throwing errors.
Keep learning
Related questions
Deep Cloning Objects in JavaScript: Methods, Trade-offs, and Best Practices
Learn how to deep clone objects in JavaScript, compare structuredClone, JSON methods, and recursive approaches with examples.
Get Screen, Page, and Browser Window Size in JavaScript
Learn how to get screen size, viewport size, page size, and scroll position in JavaScript across major browsers with clear examples.
How JavaScript Closures Work: A Beginner-Friendly Guide
Learn how JavaScript closures work with simple explanations, examples, common mistakes, and practical use cases for real code.