✅ Page Load Events
Page load events tell JavaScript when it is safe to run code based on the loading state of the webpage.
❓ Why Page Load Events Are Important
Without load events:
JavaScript may execute before HTML elements exist
document.getElementById()may returnnullDOM manipulation may fail
Layout-dependent code (images, sizes) may break
📌 Page load events help synchronize JavaScript with page loading
<div class="infographic-pageload2">
<style>
.infographic-pageload2 {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
--good: #1a8a5f;
--good-soft: #e1f2ea;
--bad: #c8452e;
--bad-soft: #f7e3df;
box-sizing: border-box;
max-width: 880px;
margin: 0 auto;
font-family: 'Inter', system-ui, sans-serif;
color: var(--ink);
background: var(--paper);
overflow-x: hidden;
}
.infographic-pageload2[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
--good: #4fbf8f;
--good-soft: #16281f;
--bad: #e0715a;
--bad-soft: #2e211d;
}
.infographic-pageload2 * { box-sizing: border-box; }
.infographic-pageload2 h1 { font-family: 'Fraunces', Georgia, serif; font-weight: 600; margin: 0; }
.infographic-pageload2 p { margin: 0; }
.infographic-pageload2 button { font-family: inherit; }
.pl2-wrap { padding: clamp(1.25rem, 5vw, 2.5rem) clamp(1.1rem, 5vw, 3rem) 2rem; }
.pl2-hero { text-align: center; margin-bottom: 1.3rem; }
.pl2-hero h1 { font-size: clamp(1.3rem, 4.5vw, 1.8rem); }
.pl2-hero p { color: var(--ink-soft); margin-top: 0.45rem; font-size: 0.84rem; }
/* phase tracker, same pattern as event-flow.html */
.pl2-phases { display: flex; align-items: center; justify-content: center; margin-bottom: 1.6rem; flex-wrap: wrap; }
.pl2-phase { display: flex; flex-direction: column; align-items: center; gap: 0.3rem; width: 84px; }
.pl2-phase-num { width: 28px; height: 28px; border-radius: 50%; border: 1.5px solid var(--line); background: var(--paper-alt); display: flex; align-items: center; justify-content: center; font-family: 'IBM Plex Mono', monospace; font-size: 0.66rem; font-weight: 700; color: var(--ink-soft); transition: border-color 0.6s ease, background 0.6s ease, color 0.6s ease, transform 0.6s ease; }
.pl2-phase-label { font-size: 0.56rem; text-align: center; color: var(--ink-soft); font-weight: 600; line-height: 1.2; }
.pl2-phase.is-active .pl2-phase-num { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); transform: scale(1.15); }
.pl2-phase.is-active .pl2-phase-label { color: var(--accent); }
.pl2-phase.is-done .pl2-phase-num { border-color: var(--good); background: var(--good-soft); color: var(--good); transform: scale(1); }
.pl2-phase.is-done .pl2-phase-label { color: var(--good); }
.pl2-phase.is-warn .pl2-phase-num { border-color: var(--bad); background: var(--bad-soft); color: var(--bad); transform: scale(1.15); }
.pl2-phase.is-warn .pl2-phase-label { color: var(--bad); }
.pl2-phase-connect { width: 20px; height: 2px; background: var(--line); margin-bottom: 1.3rem; }
/* mock browser window, same nesting-box visual language */
.pl2-browser { border: 1.5px solid var(--line); border-radius: 8px; overflow: hidden; margin-bottom: 1rem; transition: border-color 0.7s ease; }
.pl2-browser.is-warn { border-color: var(--bad); opacity: 0.6; }
.pl2-browser-bar { background: #2b2b2b; padding: 0.45rem 0.7rem; display: flex; gap: 0.3rem; }
.pl2-browser-dot { width: 8px; height: 8px; border-radius: 50%; background: #555; }
.pl2-browser-body { padding: 1.2rem; background: var(--paper); min-height: 140px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.6rem; position: relative; }
.pl2-skeleton { display: flex; flex-direction: column; gap: 0.35rem; width: 100%; opacity: 0.25; transition: opacity 0.7s ease; }
.pl2-skeleton.is-shown { opacity: 1; }
.pl2-skel-line { height: 8px; background: var(--paper-alt); border: 1px solid var(--line); border-radius: 3px; }
.pl2-skel-line.s1 { width: 60%; }
.pl2-skel-line.s2 { width: 90%; }
.pl2-skel-line.s3 { width: 75%; }
.pl2-assets { display: flex; gap: 0.4rem; opacity: 0; transition: opacity 0.7s ease; }
.pl2-assets.is-shown { opacity: 1; }
.pl2-asset-chip { width: 26px; height: 26px; border-radius: 5px; border: 1.5px solid var(--line); background: var(--paper-alt); display: flex; align-items: center; justify-content: center; }
.pl2-asset-chip svg { width: 13px; height: 13px; stroke: var(--ink-soft); }
.pl2-loaded-badge { position: absolute; top: 8px; right: 8px; display: flex; align-items: center; gap: 0.25rem; font-family: 'IBM Plex Mono', monospace; font-size: 0.58rem; font-weight: 700; color: var(--good); border: 1.5px solid var(--good); background: var(--good-soft); padding: 0.15rem 0.4rem; border-radius: 4px; opacity: 0; transition: opacity 0.7s ease; }
.pl2-loaded-badge.is-shown { opacity: 1; }
.pl2-loaded-badge svg { width: 11px; height: 11px; stroke: var(--good); }
.pl2-unload-badge { display: flex; align-items: center; gap: 0.3rem; font-family: 'IBM Plex Mono', monospace; font-size: 0.66rem; font-weight: 700; color: var(--bad); opacity: 0; transition: opacity 0.7s ease; }
.pl2-unload-badge.is-shown { opacity: 1; }
.pl2-unload-badge svg { width: 15px; height: 15px; stroke: var(--bad); }
.pl2-play { display: block; margin: 0 auto; border: none; background: var(--accent); color: #fff; padding: 0.55rem 1.3rem; font-size: 0.8rem; font-weight: 600; border-radius: 6px; cursor: pointer; }
.pl2-play:active { transform: scale(0.96); }
.pl2-play:disabled { opacity: 0.45; cursor: default; }
.pl2-console { margin: 1.2rem auto 0; max-width: 380px; text-align: left; border: 1.5px solid var(--line); background: var(--paper-alt); padding: 0.55rem 0.8rem; font-family: 'IBM Plex Mono', monospace; font-size: 0.68rem; min-height: 7em; line-height: 1.6; }
.pl2-console .pl2-cline { display: block; opacity: 0; transition: opacity 0.25s ease; }
.pl2-console .pl2-cline.is-shown { opacity: 1; }
.pl2-console .pl2-cline.step { color: var(--accent); }
.pl2-console .pl2-cline.warn { color: var(--bad); }
.pl2-note { text-align: center; font-size: 0.8rem; color: var(--ink-soft); margin-top: 1.4rem; }
.pl2-note strong { color: var(--ink); }
@media (prefers-reduced-motion: reduce) {
.infographic-pageload2 *, .infographic-pageload2 *::before, .infographic-pageload2 *::after {
animation-duration: 0.001ms !important; transition-duration: 0.001ms !important;
}
}
</style>
<div class="pl2-wrap">
<div class="pl2-hero">
<h1>Page loading lifecycle</h1>
<p>Watch the page itself change as each stage completes.</p>
</div>
<div class="pl2-phases">
<div class="pl2-phase" id="phHtml">
<div class="pl2-phase-num">1</div>
<div class="pl2-phase-label">HTML loading</div>
</div>
<div class="pl2-phase-connect"></div>
<div class="pl2-phase" id="phDom">
<div class="pl2-phase-num">2</div>
<div class="pl2-phase-label">DOM ready</div>
</div>
<div class="pl2-phase-connect"></div>
<div class="pl2-phase" id="phAssets">
<div class="pl2-phase-num">3</div>
<div class="pl2-phase-label">assets</div>
</div>
<div class="pl2-phase-connect"></div>
<div class="pl2-phase" id="phLoad">
<div class="pl2-phase-num">4</div>
<div class="pl2-phase-label">load</div>
</div>
<div class="pl2-phase-connect"></div>
<div class="pl2-phase" id="phUnload">
<div class="pl2-phase-num">5</div>
<div class="pl2-phase-label">unload</div>
</div>
</div>
<div class="pl2-browser" id="browserWindow">
<div class="pl2-browser-bar"><div class="pl2-browser-dot"></div><div class="pl2-browser-dot"></div><div class="pl2-browser-dot"></div></div>
<div class="pl2-browser-body">
<div class="pl2-loaded-badge" id="loadedBadge">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>
loaded
</div>
<div class="pl2-skeleton" id="skeleton">
<div class="pl2-skel-line s1"></div>
<div class="pl2-skel-line s2"></div>
<div class="pl2-skel-line s3"></div>
</div>
<div class="pl2-assets" id="assets">
<div class="pl2-asset-chip"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></svg></div>
<div class="pl2-asset-chip"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="13.5" cy="6.5" r=".5" fill="currentColor"/><circle cx="17.5" cy="10.5" r=".5" fill="currentColor"/><circle cx="8.5" cy="7.5" r=".5" fill="currentColor"/><circle cx="6.5" cy="12.5" r=".5" fill="currentColor"/><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2z"/></svg></div>
<div class="pl2-asset-chip"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 20h16"/><path d="m6 16 6-12 6 12"/><path d="M8 12h8"/></svg></div>
</div>
<div class="pl2-unload-badge" id="unloadBadge">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 12h.01"/><path d="M18 20V6a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v14"/><path d="M2 20h20"/></svg>
page unloaded
</div>
</div>
</div>
<button class="pl2-play" id="plPlay" type="button">simulate a page visit</button>
<div class="pl2-console">
<span class="pl2-cline step" id="c1">HTML starts loading</span>
<span class="pl2-cline step" id="c2">"DOMContentLoaded" fired — DOM is ready</span>
<span class="pl2-cline step" id="c3">CSS, images, fonts finished loading</span>
<span class="pl2-cline step" id="c4">"load" fired — page fully loaded</span>
<span class="pl2-cline warn" id="c5">"unload" fired — page unloaded</span>
</div>
<p class="pl2-note">Five real events, in a <strong>fixed order</strong> — you can hook a handler onto any of them.</p>
</div>
</div>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-pageload2')
|| document.querySelector('.infographic-pageload2');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') { dark = true; }
else if (bodyAttr === 'false') { dark = false; }
else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
const phases = ['phHtml', 'phDom', 'phAssets', 'phLoad', 'phUnload'].map(id => wrapper.querySelector('#' + id));
const skeleton = wrapper.querySelector('#skeleton');
const assets = wrapper.querySelector('#assets');
const loadedBadge = wrapper.querySelector('#loadedBadge');
const unloadBadge = wrapper.querySelector('#unloadBadge');
const browserWindow = wrapper.querySelector('#browserWindow');
const lines = ['c1', 'c2', 'c3', 'c4', 'c5'].map(id => wrapper.querySelector('#' + id));
const playBtn = wrapper.querySelector('#plPlay');
function reset() {
phases.forEach(p => p.classList.remove('is-active', 'is-done', 'is-warn'));
skeleton.classList.remove('is-shown');
assets.classList.remove('is-shown');
loadedBadge.classList.remove('is-shown');
unloadBadge.classList.remove('is-shown');
browserWindow.classList.remove('is-warn');
lines.forEach(l => l.classList.remove('is-shown'));
}
playBtn.addEventListener('click', () => {
playBtn.disabled = true;
reset();
const STEP = 1300;
let t = 250;
setTimeout(() => { phases[0].classList.add('is-active'); skeleton.classList.add('is-shown'); lines[0].classList.add('is-shown'); }, t);
t += STEP;
setTimeout(() => { phases[0].classList.remove('is-active'); phases[0].classList.add('is-done'); phases[1].classList.add('is-active'); lines[1].classList.add('is-shown'); }, t);
t += STEP;
setTimeout(() => { phases[1].classList.remove('is-active'); phases[1].classList.add('is-done'); phases[2].classList.add('is-active'); assets.classList.add('is-shown'); lines[2].classList.add('is-shown'); }, t);
t += STEP;
setTimeout(() => { phases[2].classList.remove('is-active'); phases[2].classList.add('is-done'); phases[3].classList.add('is-active'); loadedBadge.classList.add('is-shown'); lines[3].classList.add('is-shown'); }, t);
t += STEP;
setTimeout(() => {
phases[3].classList.remove('is-active'); phases[3].classList.add('is-done');
phases[4].classList.add('is-warn');
browserWindow.classList.add('is-warn');
unloadBadge.classList.add('is-shown');
lines[4].classList.add('is-shown');
}, t);
t += STEP;
setTimeout(() => { playBtn.disabled = false; }, t);
});
})();
</script>🔹 1. DOMContentLoaded Event (DOM Ready)
📌 When it fires
HTML is fully parsed
DOM tree is created
External resources like images and videos may still be loading
👉 JavaScript can safely access and modify DOM elements
✅ Syntax
document.addEventListener("DOMContentLoaded", () => {
console.log("DOM is ready");
});DOM is ready🧠 Why DOMContentLoaded is Preferred
✔ Faster than load
✔ Does not wait for images
✔ Best for DOM manipulation
✔ Improves perceived performance
🧪 Practical Example
<div class="infographic-dcl">
<style>
.infographic-dcl {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
--good: #1a8a5f;
--good-soft: #e1f2ea;
box-sizing: border-box;
max-width: 880px;
margin: 0 auto;
font-family: 'Inter', system-ui, sans-serif;
color: var(--ink);
background: var(--paper);
overflow-x: hidden;
}
.infographic-dcl[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
--good: #4fbf8f;
--good-soft: #16281f;
}
.infographic-dcl * { box-sizing: border-box; }
.infographic-dcl h1 { font-family: 'Fraunces', Georgia, serif; font-weight: 600; margin: 0; }
.infographic-dcl p { margin: 0; }
.infographic-dcl button { font-family: inherit; }
.dcl-wrap { padding: clamp(1.25rem, 5vw, 2.5rem) clamp(1.1rem, 5vw, 3rem) 2rem; }
.dcl-hero { text-align: center; margin-bottom: 1.3rem; }
.dcl-hero h1 { font-size: clamp(1.3rem, 4.5vw, 1.8rem); }
.dcl-hero p { color: var(--ink-soft); margin-top: 0.45rem; font-size: 0.84rem; }
.dcl-code { background: #1e1e1e; color: #d4d4d4; border-radius: 5px; padding: 0.6rem 0.8rem; font-family: 'IBM Plex Mono', monospace; font-size: 0.68rem; line-height: 1.6; margin-bottom: 1.3rem; white-space: pre; overflow-x: auto; }
.tok-kw { color: #569cd6; padding: 0; margin: 0; }
.tok-fn { color: #dcdcaa; padding: 0; margin: 0; }
.tok-str { color: #ce9178; padding: 0; margin: 0; }
.tok-var { color: #9cdcfe; padding: 0; margin: 0; }
.tok-punc { color: #d4d4d4; padding: 0; margin: 0; }
/* race timeline */
.dcl-race { display: flex; flex-direction: column; gap: 0.9rem; margin-bottom: 1rem; padding-top: 1.8rem; }
.dcl-lane { display: flex; align-items: center; gap: 0.6rem; }
.dcl-lane-label { width: 96px; flex-shrink: 0; font-family: 'IBM Plex Mono', monospace; font-size: 0.66rem; color: var(--ink-soft); }
.dcl-lane-track { flex: 1; height: 18px; background: var(--paper-alt); border: 1px solid var(--line); position: relative; overflow: visible; }
.dcl-lane-bar { position: absolute; left: 0; top: 0; bottom: 0; width: 0%; background: var(--accent); transition: width linear; }
.dcl-lane.is-dcl .dcl-lane-bar { background: var(--good); }
.dcl-marker {
position: absolute; top: -8px; width: 2px; height: 34px; background: var(--line); opacity: 0; transition: opacity 0.3s ease, background 0.3s ease;
}
.dcl-marker.is-shown { opacity: 1; }
.dcl-marker.dcl-mark { background: var(--good); }
.dcl-marker.load-mark { background: var(--accent); }
.dcl-marker-tag {
position: absolute; top: -22px; left: 50%; transform: translateX(-50%); font-family: 'IBM Plex Mono', monospace; font-size: 0.56rem; font-weight: 700;
white-space: nowrap; opacity: 0; transition: opacity 0.3s ease;
}
.dcl-marker-tag.is-shown { opacity: 1; }
.dcl-marker-tag.dcl-tag { color: var(--good); }
.dcl-marker-tag.load-tag { color: var(--accent); }
/* mock button that reacts */
.dcl-page { border: 1.5px solid var(--line); background: var(--paper-alt); border-radius: 6px; overflow: hidden; margin-bottom: 1rem; }
.dcl-page-bar { background: #2b2b2b; padding: 0.4rem 0.6rem; display: flex; gap: 0.3rem; }
.dcl-page-dot { width: 8px; height: 8px; border-radius: 50%; background: #555; }
.dcl-page-body { padding: 1rem; background: var(--paper); text-align: center; }
.dcl-mock-btn { border: 1.5px solid var(--line); background: var(--paper); color: var(--ink-soft); padding: 0.45rem 1rem; font-size: 0.8rem; border-radius: 5px; transition: border-color 0.4s ease, background-color 0.4s ease, color 0.4s ease; }
.dcl-mock-btn.is-ready { border-color: var(--good); background-color: var(--good); color: #fff; }
.dcl-mock-caption { font-size: 0.62rem; color: var(--ink-soft); margin-top: 0.5rem; font-family: 'IBM Plex Mono', monospace; opacity: 0; transition: opacity 0.3s ease; }
.dcl-mock-caption.is-shown { opacity: 1; color: var(--good); }
.dcl-play { display: block; margin: 0 auto; border: none; background: var(--accent); color: #fff; padding: 0.55rem 1.3rem; font-size: 0.8rem; font-weight: 600; border-radius: 6px; cursor: pointer; }
.dcl-play:active { transform: scale(0.96); }
.dcl-play:disabled { opacity: 0.45; cursor: default; }
.dcl-note { text-align: center; font-size: 0.8rem; color: var(--ink-soft); padding-top: 1.4rem; }
.dcl-note strong { color: var(--ink); }
@media (prefers-reduced-motion: reduce) {
.infographic-dcl *, .infographic-dcl *::before, .infographic-dcl *::after {
animation-duration: 0.001ms !important; transition-duration: 0.001ms !important;
}
}
</style>
<div class="dcl-wrap">
<div class="dcl-code"><span class="tok-var">document</span><span class="tok-punc">.</span><span class="tok-fn">addEventListener</span><span class="tok-punc">(</span><span class="tok-str">"DOMContentLoaded"</span><span class="tok-punc">, () => {</span>
<span class="tok-kw">const</span> <span class="tok-var">btn</span> <span class="tok-punc">=</span> <span class="tok-var">document</span><span class="tok-punc">.</span><span class="tok-fn">getElementById</span><span class="tok-punc">(</span><span class="tok-str">"btn"</span><span class="tok-punc">);</span>
<span class="tok-var">btn</span><span class="tok-punc">.</span><span class="tok-var">style</span><span class="tok-punc">.</span><span class="tok-var">backgroundColor</span> <span class="tok-punc">=</span> <span class="tok-str">"green"</span><span class="tok-punc">;</span>
<span class="tok-punc">});</span></div>
<div class="dcl-race">
<div class="dcl-lane is-dcl">
<div class="dcl-lane-label">DOM parsing</div>
<div class="dcl-lane-track">
<div class="dcl-lane-bar" id="domBar"></div>
<div class="dcl-marker dcl-mark" id="dclMarker" style="left: 32%;"></div>
<div class="dcl-marker-tag dcl-tag" id="dclTag" style="left: 32%;">DOMContentLoaded</div>
</div>
</div>
<div class="dcl-lane">
<div class="dcl-lane-label">images loading</div>
<div class="dcl-lane-track">
<div class="dcl-lane-bar" id="imgBar"></div>
<div class="dcl-marker load-mark" id="loadMarker" style="left: 100%;"></div>
<div class="dcl-marker-tag load-tag" id="loadTag" style="left: 100%; transform: translateX(-90%);">load</div>
</div>
</div>
</div>
<div class="dcl-page">
<div class="dcl-page-bar"><div class="dcl-page-dot"></div><div class="dcl-page-dot"></div><div class="dcl-page-dot"></div></div>
<div class="dcl-page-body">
<button class="dcl-mock-btn" id="mockBtn" type="button">#btn</button>
<div class="dcl-mock-caption" id="mockCaption">styled — while images are still loading</div>
</div>
</div>
<button class="dcl-play" id="dclPlay" type="button">simulate a page visit</button>
<p class="dcl-note">Waiting for <strong>load</strong> instead would leave the button unstyled for the whole extra stretch — DOMContentLoaded is what makes the page feel ready sooner.</p>
</div>
</div>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-dcl')
|| document.querySelector('.infographic-dcl');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') { dark = true; }
else if (bodyAttr === 'false') { dark = false; }
else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
const domBar = wrapper.querySelector('#domBar');
const imgBar = wrapper.querySelector('#imgBar');
const dclMarker = wrapper.querySelector('#dclMarker');
const dclTag = wrapper.querySelector('#dclTag');
const loadMarker = wrapper.querySelector('#loadMarker');
const loadTag = wrapper.querySelector('#loadTag');
const mockBtn = wrapper.querySelector('#mockBtn');
const mockCaption = wrapper.querySelector('#mockCaption');
const playBtn = wrapper.querySelector('#dclPlay');
const DOM_DUR = 900; // DOMContentLoaded fires here
const IMG_DUR = 2800; // load fires here
function resetBar(bar) {
bar.style.transition = 'none';
bar.style.width = '0%';
void bar.offsetWidth;
}
function growBar(bar, ms, toWidth) {
bar.style.transition = 'width ' + ms + 'ms linear';
requestAnimationFrame(() => { bar.style.width = toWidth + '%'; });
}
playBtn.addEventListener('click', () => {
playBtn.disabled = true;
resetBar(domBar);
resetBar(imgBar);
[dclMarker, dclTag, loadMarker, loadTag].forEach(el => el.classList.remove('is-shown'));
mockBtn.classList.remove('is-ready');
mockCaption.classList.remove('is-shown');
// dom bar fills fully to the DCL point (scaled relative to the full race length)
const domWidthPct = (DOM_DUR / IMG_DUR) * 100;
growBar(domBar, DOM_DUR, domWidthPct);
growBar(imgBar, IMG_DUR, 100);
setTimeout(() => {
dclMarker.classList.add('is-shown');
dclTag.classList.add('is-shown');
mockBtn.classList.add('is-ready');
mockCaption.classList.add('is-shown');
}, DOM_DUR);
setTimeout(() => {
loadMarker.classList.add('is-shown');
loadTag.classList.add('is-shown');
mockCaption.textContent = 'images finished too — "load" fires now';
playBtn.disabled = false;
}, IMG_DUR);
});
})();
</script>📌 Ensures the button exists before accessing it
🔹 2. load Event (Fully Loaded Page)
📌 When it fires
HTML loaded
CSS loaded
Images loaded
Fonts loaded
External scripts loaded
👉 Fires after everything is completely ready
✅ Syntax
window.addEventListener("load", () => {
console.log("Everything loaded");
});Everything loaded🧠 When to Use load
✔ When image dimensions matter
✔ For sliders, canvas, charts
✔ When layout depends on fonts or media
🧪 Practical Example
<div class="infographic-loaduse">
<style>
.infographic-loaduse {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
--good: #1a8a5f;
--good-soft: #e1f2ea;
--bad: #c8452e;
--bad-soft: #f7e3df;
box-sizing: border-box;
max-width: 880px;
margin: 0 auto;
font-family: 'Inter', system-ui, sans-serif;
color: var(--ink);
background: var(--paper);
overflow-x: hidden;
}
.infographic-loaduse[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
--good: #4fbf8f;
--good-soft: #16281f;
--bad: #e0715a;
--bad-soft: #2e211d;
}
.infographic-loaduse * { box-sizing: border-box; }
.infographic-loaduse h1 { font-family: 'Fraunces', Georgia, serif; font-weight: 600; margin: 0; }
.infographic-loaduse p { margin: 0; }
.infographic-loaduse button { font-family: inherit; }
.ld-wrap { padding: clamp(1.25rem, 5vw, 2.5rem) clamp(1.1rem, 5vw, 3rem) 2rem; }
.ld-hero { text-align: center; margin-bottom: 1.3rem; }
.ld-hero h1 { font-size: clamp(1.3rem, 4.5vw, 1.8rem); }
.ld-hero p { color: var(--ink-soft); margin-top: 0.45rem; font-size: 0.84rem; }
.ld-code { background: #1e1e1e; color: #d4d4d4; border-radius: 5px; padding: 0.6rem 0.8rem; font-family: 'IBM Plex Mono', monospace; font-size: 0.68rem; line-height: 1.6; margin-bottom: 1.3rem; white-space: pre; overflow-x: auto; }
.tok-kw { color: #569cd6; padding: 0; margin: 0; }
.tok-fn { color: #dcdcaa; padding: 0; margin: 0; }
.tok-str { color: #ce9178; padding: 0; margin: 0; }
.tok-var { color: #9cdcfe; padding: 0; margin: 0; }
.tok-punc { color: #d4d4d4; padding: 0; margin: 0; }
/* race timeline, same pattern as the DOMContentLoaded piece */
.ld-race { display: flex; flex-direction: column; gap: 0.9rem; margin-bottom: 1rem; padding-top: 1.8rem; }
.ld-lane { display: flex; align-items: center; gap: 0.6rem; }
.ld-lane-label { width: 96px; flex-shrink: 0; font-family: 'IBM Plex Mono', monospace; font-size: 0.66rem; color: var(--ink-soft); }
.ld-lane-track { flex: 1; height: 18px; background: var(--paper-alt); border: 1px solid var(--line); position: relative; overflow: visible; }
.ld-lane-bar { position: absolute; left: 0; top: 0; bottom: 0; width: 0%; background: var(--accent); transition: width linear; }
.ld-lane.is-dcl .ld-lane-bar { background: var(--good); }
.ld-marker { position: absolute; top: -8px; width: 2px; height: 34px; background: var(--line); opacity: 0; transition: opacity 0.3s ease, background 0.3s ease; }
.ld-marker.is-shown { opacity: 1; }
.ld-marker.dcl-mark { background: var(--good); }
.ld-marker.load-mark { background: var(--accent); }
.ld-marker-tag { position: absolute; top: -22px; left: 50%; transform: translateX(-50%); font-family: 'IBM Plex Mono', monospace; font-size: 0.56rem; font-weight: 700; white-space: nowrap; opacity: 0; transition: opacity 0.3s ease; }
.ld-marker-tag.is-shown { opacity: 1; }
.ld-marker-tag.dcl-tag { color: var(--good); }
.ld-marker-tag.load-tag { color: var(--accent); }
/* mock banner image */
.ld-page { border: 1.5px solid var(--line); background: var(--paper-alt); border-radius: 6px; overflow: hidden; margin-bottom: 1rem; }
.ld-page-bar { background: #2b2b2b; padding: 0.4rem 0.6rem; display: flex; gap: 0.3rem; }
.ld-page-dot { width: 8px; height: 8px; border-radius: 50%; background: #555; }
.ld-page-body { padding: 1rem; background: var(--paper); text-align: center; }
.ld-banner {
width: 100%; height: 140px; border: 1.5px dashed var(--line); border-radius: 5px; background: var(--paper-alt);
display: flex; align-items: center; justify-content: center; transition: border-color 0.4s ease, background-color 0.4s ease, border-style 0.4s ease;
overflow: hidden; position: relative;
}
.ld-banner svg { width: 22px; height: 22px; stroke: var(--ink-soft); opacity: 1; transition: opacity 0.4s ease, stroke 0.4s ease; position: absolute; }
.ld-banner.is-loaded { border-style: solid; border-color: var(--good); }
.ld-banner.is-loaded svg { opacity: 0; }
.ld-banner-img { width: 100%; height: 100%; position: relative; overflow: hidden; opacity: 0; transition: opacity 0.4s ease; background: linear-gradient(180deg, #cfe8f7 0%, #eef7ee 70%, #eef7ee 100%); }
.ld-banner.is-loaded .ld-banner-img { opacity: 1; }
.ld-sun { position: absolute; top: 14px; right: 26px; width: 22px; height: 22px; border-radius: 50%; background: #f7d774; box-shadow: 0 0 0 6px rgba(247,215,116,0.35); }
.ld-mtn { position: absolute; bottom: 0; left: 0; width: 100%; }
.ld-mtn.m1 { height: 60%; background: #9fb8a8; clip-path: polygon(0% 100%, 18% 30%, 38% 70%, 55% 15%, 78% 65%, 100% 35%, 100% 100%); }
.ld-mtn.m2 { height: 42%; background: #7a9a86; clip-path: polygon(0% 100%, 12% 55%, 30% 90%, 48% 40%, 68% 85%, 88% 50%, 100% 75%, 100% 100%); }
.ld-mtn-cap { position: absolute; bottom: 0; left: 0; width: 100%; height: 60%; clip-path: polygon(0% 100%, 18% 30%, 22% 38%, 38% 70%, 42% 60%, 55% 15%, 60% 24%, 78% 65%, 82% 55%, 100% 35%, 100% 42%, 82% 62%, 78% 72%, 60% 32%, 55% 24%, 42% 68%, 38% 78%, 22% 46%, 18% 38%, 4% 96%); background: transparent; }
.ld-flowerrow { position: absolute; bottom: 6px; left: 0; width: 100%; display: flex; justify-content: space-evenly; }
.ld-flower { position: relative; width: 10px; height: 10px; }
.ld-flower .petal { position: absolute; width: 5px; height: 5px; border-radius: 50%; background: #f2a5c4; }
.ld-flower .petal:nth-child(1) { top: -3px; left: 2px; }
.ld-flower .petal:nth-child(2) { top: 3px; left: -3px; }
.ld-flower .petal:nth-child(3) { top: 3px; left: 7px; }
.ld-flower .petal:nth-child(4) { top: 8px; left: 2px; }
.ld-flower .center { position: absolute; top: 2.5px; left: 2.5px; width: 5px; height: 5px; border-radius: 50%; background: #f4c651; }
.ld-banner-dims { font-family: 'IBM Plex Mono', monospace; font-size: 0.6rem; color: var(--ink-soft); margin-top: 0.4rem; }
.ld-console { margin: 1rem auto 0; max-width: 320px; text-align: left; border: 1.5px solid var(--line); background: var(--paper-alt); padding: 0.55rem 0.8rem; font-family: 'IBM Plex Mono', monospace; font-size: 0.7rem; min-height: 3.2em; line-height: 1.6; }
.ld-console .ld-cline { display: block; opacity: 0; transition: opacity 0.25s ease; }
.ld-console .ld-cline.is-shown { opacity: 1; }
.ld-console .ld-cline.bad { color: var(--bad); }
.ld-console .ld-cline.good { color: var(--good); }
.ld-play { display: block; margin: 0 auto; border: none; background: var(--accent); color: #fff; padding: 0.55rem 1.3rem; font-size: 0.8rem; font-weight: 600; border-radius: 6px; cursor: pointer; }
.ld-play:active { transform: scale(0.96); }
.ld-play:disabled { opacity: 0.45; cursor: default; }
.ld-usecases { display: flex; justify-content: center; gap: 0.5rem; margin-top: 1.3rem; flex-wrap: wrap; }
.ld-usecase { font-family: 'IBM Plex Mono', monospace; font-size: 0.62rem; padding: 0.22rem 0.55rem; border: 1px solid var(--line); color: var(--ink-soft); background: var(--paper-alt); }
@media (prefers-reduced-motion: reduce) {
.infographic-loaduse *, .infographic-loaduse *::before, .infographic-loaduse *::after {
animation-duration: 0.001ms !important; transition-duration: 0.001ms !important;
}
}
</style>
<div class="ld-wrap">
<div class="ld-code"><span class="tok-var">window</span><span class="tok-punc">.</span><span class="tok-fn">addEventListener</span><span class="tok-punc">(</span><span class="tok-str">"load"</span><span class="tok-punc">, () => {</span>
<span class="tok-kw">const</span> <span class="tok-var">img</span> <span class="tok-punc">=</span> <span class="tok-var">document</span><span class="tok-punc">.</span><span class="tok-fn">getElementById</span><span class="tok-punc">(</span><span class="tok-str">"banner"</span><span class="tok-punc">);</span>
<span class="tok-var">console</span><span class="tok-punc">.</span><span class="tok-fn">log</span><span class="tok-punc">(</span><span class="tok-var">img</span><span class="tok-punc">.</span><span class="tok-var">offsetWidth</span><span class="tok-punc">);</span>
<span class="tok-punc">});</span></div>
<div class="ld-race">
<div class="ld-lane is-dcl">
<div class="ld-lane-label">DOM parsing</div>
<div class="ld-lane-track">
<div class="ld-lane-bar" id="domBar"></div>
<div class="ld-marker dcl-mark" id="dclMarker" style="left: 32%;"></div>
<div class="ld-marker-tag dcl-tag" id="dclTag" style="left: 32%;">DOMContentLoaded</div>
</div>
</div>
<div class="ld-lane">
<div class="ld-lane-label">#banner loading</div>
<div class="ld-lane-track">
<div class="ld-lane-bar" id="imgBar"></div>
<div class="ld-marker load-mark" id="loadMarker" style="left: 100%;"></div>
<div class="ld-marker-tag load-tag" id="loadTag" style="left: 100%; transform: translateX(-90%);">load</div>
</div>
</div>
</div>
<div class="ld-page">
<div class="ld-page-bar"><div class="ld-page-dot"></div><div class="ld-page-dot"></div><div class="ld-page-dot"></div></div>
<div class="ld-page-body">
<div class="ld-banner" id="banner">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></svg>
<div class="ld-banner-img" id="bannerImg">
<div class="ld-sun"></div>
<div class="ld-mtn m1"></div>
<div class="ld-mtn m2"></div>
<div class="ld-flowerrow">
<div class="ld-flower"><div class="petal"></div><div class="petal"></div><div class="petal"></div><div class="petal"></div><div class="center"></div></div>
<div class="ld-flower"><div class="petal"></div><div class="petal"></div><div class="petal"></div><div class="petal"></div><div class="center"></div></div>
<div class="ld-flower"><div class="petal"></div><div class="petal"></div><div class="petal"></div><div class="petal"></div><div class="center"></div></div>
<div class="ld-flower"><div class="petal"></div><div class="petal"></div><div class="petal"></div><div class="petal"></div><div class="center"></div></div>
<div class="ld-flower"><div class="petal"></div><div class="petal"></div><div class="petal"></div><div class="petal"></div><div class="center"></div></div>
</div>
</div>
</div>
<div class="ld-banner-dims" id="bannerDims">#banner — not loaded yet</div>
</div>
</div>
<button class="ld-play" id="ldPlay" type="button">simulate a page visit</button>
<div class="ld-console">
<span class="ld-cline bad" id="c1">at DOMContentLoaded → img.offsetWidth = 0</span>
<span class="ld-cline good" id="c2">at load → img.offsetWidth = 600</span>
</div>
</div>
</div>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-loaduse')
|| document.querySelector('.infographic-loaduse');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') { dark = true; }
else if (bodyAttr === 'false') { dark = false; }
else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
const domBar = wrapper.querySelector('#domBar');
const imgBar = wrapper.querySelector('#imgBar');
const dclMarker = wrapper.querySelector('#dclMarker');
const dclTag = wrapper.querySelector('#dclTag');
const loadMarker = wrapper.querySelector('#loadMarker');
const loadTag = wrapper.querySelector('#loadTag');
const banner = wrapper.querySelector('#banner');
const bannerDims = wrapper.querySelector('#bannerDims');
const c1 = wrapper.querySelector('#c1');
const c2 = wrapper.querySelector('#c2');
const playBtn = wrapper.querySelector('#ldPlay');
const DOM_DUR = 900;
const IMG_DUR = 2800;
function resetBar(bar) {
bar.style.transition = 'none';
bar.style.width = '0%';
void bar.offsetWidth;
}
function growBar(bar, ms, toWidth) {
bar.style.transition = 'width ' + ms + 'ms linear';
requestAnimationFrame(() => { bar.style.width = toWidth + '%'; });
}
playBtn.addEventListener('click', () => {
playBtn.disabled = true;
resetBar(domBar);
resetBar(imgBar);
[dclMarker, dclTag, loadMarker, loadTag, c1, c2].forEach(el => el.classList.remove('is-shown'));
banner.classList.remove('is-loaded');
bannerDims.textContent = '#banner — not loaded yet';
const domWidthPct = (DOM_DUR / IMG_DUR) * 100;
growBar(domBar, DOM_DUR, domWidthPct);
growBar(imgBar, IMG_DUR, 100);
setTimeout(() => {
dclMarker.classList.add('is-shown');
dclTag.classList.add('is-shown');
c1.classList.add('is-shown');
}, DOM_DUR);
setTimeout(() => {
loadMarker.classList.add('is-shown');
loadTag.classList.add('is-shown');
banner.classList.add('is-loaded');
bannerDims.textContent = '#banner — 600 × 70';
c2.classList.add('is-shown');
playBtn.disabled = false;
}, IMG_DUR);
});
})();
</script>📌 Image size is available only after full load
⚔️ DOMContentLoaded vs load
Feature | DOMContentLoaded | load |
|---|---|---|
Waits for HTML | ✅ Yes | ✅ Yes |
Waits for images | ❌ No | ✅ Yes |
Speed | Faster | Slower |
Best use | DOM logic | Asset-dependent logic |
🔹 3. beforeunload Event
📌 Purpose: Warn users before they leave the page
📌 When it fires
Closing tab or browser
Reloading page
Navigating to another URL
✅ Syntax
window.addEventListener("beforeunload", (event) => {
event.preventDefault();
event.returnValue = ""; // Triggers the dialog
});🖨️ Result
➡️ Browser shows a confirmation dialog

🧠 Common Use Cases
✔ Unsaved form data
✔ Checkout/payment flows
✔ Long user inputs
🧪 Example: Form Protection
<div class="infographic-beforeunload">
<style>
.infographic-beforeunload {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
--good: #1a8a5f;
--good-soft: #e1f2ea;
--bad: #c8452e;
--bad-soft: #f7e3df;
box-sizing: border-box;
max-width: 880px;
margin: 0 auto;
font-family: 'Inter', system-ui, sans-serif;
color: var(--ink);
background: var(--paper);
overflow-x: hidden;
}
.infographic-beforeunload[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
--good: #4fbf8f;
--good-soft: #16281f;
--bad: #e0715a;
--bad-soft: #2e211d;
}
.infographic-beforeunload * { box-sizing: border-box; }
.infographic-beforeunload h1 { font-family: 'Fraunces', Georgia, serif; font-weight: 600; margin: 0; }
.infographic-beforeunload p { margin: 0; }
.infographic-beforeunload button { font-family: inherit; }
.bu-wrap { padding: clamp(1.25rem, 5vw, 2.5rem) clamp(1.1rem, 5vw, 3rem) 2rem; }
.bu-hero { text-align: center; margin-bottom: 1.2rem; }
.bu-hero h1 { font-size: clamp(1.3rem, 4.5vw, 1.8rem); }
.bu-hero p { color: var(--ink-soft); margin-top: 0.4rem; font-size: 0.84rem; }
.bu-code { background: #1e1e1e; color: #d4d4d4; border-radius: 5px; padding: 0.6rem 0.8rem; font-family: 'IBM Plex Mono', monospace; font-size: 0.68rem; line-height: 1.6; margin-bottom: 1.2rem; white-space: pre; overflow-x: auto; }
.tok-kw { color: #569cd6; padding: 0; margin: 0; }
.tok-fn { color: #dcdcaa; padding: 0; margin: 0; }
.tok-str { color: #ce9178; padding: 0; margin: 0; }
.tok-var { color: #9cdcfe; padding: 0; margin: 0; }
.tok-punc { color: #d4d4d4; padding: 0; margin: 0; }
.bu-toggle-row { display: flex; align-items: center; justify-content: center; gap: 0.6rem; margin-bottom: 1.2rem; }
.bu-toggle-label { font-family: 'IBM Plex Mono', monospace; font-size: 0.72rem; color: var(--ink-soft); }
.bu-toggle { display: flex; border: 1.5px solid var(--line); overflow: hidden; }
.bu-toggle button { border: none; background: var(--paper-alt); color: var(--ink-soft); padding: 0.32rem 0.7rem; font-size: 0.68rem; font-weight: 700; cursor: pointer; font-family: 'IBM Plex Mono', monospace; }
.bu-toggle button.on.is-active { background: var(--bad-soft); color: var(--bad); }
.bu-toggle button.off.is-active { background: var(--good-soft); color: var(--good); }
.bu-page { border: 1.5px solid var(--line); background: var(--paper-alt); border-radius: 8px; overflow: hidden; margin-bottom: 1rem; position: relative; transition: opacity 0.35s ease, transform 0.35s ease; }
.bu-page.is-closing { opacity: 0; transform: scale(0.94); }
.bu-page-bar { background: #2b2b2b; padding: 0.45rem 0.7rem; display: flex; align-items: center; gap: 0.5rem; }
.bu-page-dot { width: 8px; height: 8px; border-radius: 50%; background: #555; }
.bu-page-title { flex: 1; text-align: center; color: #9a9a9a; font-family: 'IBM Plex Mono', monospace; font-size: 0.66rem; margin-right: 2rem; }
.bu-page-body { padding: 1rem; background: var(--paper); }
.bu-field-label { font-family: 'IBM Plex Mono', monospace; font-size: 0.66rem; color: var(--ink-soft); margin-bottom: 0.3rem; display: flex; align-items: center; gap: 0.4rem; }
.bu-dirty-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--bad); opacity: 0; transition: opacity 0.3s ease; }
.bu-dirty-dot.is-shown { opacity: 1; }
.bu-input { width: 100%; border: 1.5px solid var(--line); border-radius: 5px; padding: 0.4rem 0.6rem; font-size: 0.8rem; color: var(--ink); background: var(--paper); font-family: inherit; }
.bu-input:focus { outline: none; border-color: var(--accent); }
.bu-close-btn { display: block; margin: 0 auto; border: none; background: var(--accent); color: #fff; padding: 0.5rem 1.2rem; font-size: 0.8rem; font-weight: 600; border-radius: 6px; cursor: pointer; }
.bu-close-btn:active { transform: scale(0.96); }
.bu-close-btn:disabled { opacity: 0.45; cursor: default; }
/* fake native dialog */
.bu-dialog-overlay {
position: absolute; inset: 0; background: rgba(0,0,0,0.35); display: flex; align-items: center; justify-content: center;
opacity: 0; pointer-events: none; transition: opacity 0.25s ease;
}
.bu-dialog-overlay.is-shown { opacity: 1; pointer-events: auto; }
.bu-dialog { background: var(--paper); border-radius: 8px; padding: 1rem 1.1rem; width: 240px; box-shadow: 0 10px 30px rgba(0,0,0,0.3); transform: scale(0.9); transition: transform 0.25s ease; }
.bu-dialog-overlay.is-shown .bu-dialog { transform: scale(1); }
.bu-dialog-title { font-size: 0.82rem; font-weight: 700; margin-bottom: 0.4rem; }
.bu-dialog-text { font-size: 0.72rem; color: var(--ink-soft); margin-bottom: 0.9rem; line-height: 1.4; }
.bu-dialog-btns { display: flex; gap: 0.5rem; justify-content: flex-end; }
.bu-dialog-btn { border: 1px solid var(--line); background: var(--paper-alt); color: var(--ink); padding: 0.32rem 0.7rem; font-size: 0.72rem; cursor: pointer; border-radius: 4px; }
.bu-dialog-btn.primary { border: none; background: var(--accent); color: #fff; font-weight: 600; }
.bu-status { text-align: center; font-family: 'IBM Plex Mono', monospace; font-size: 0.7rem; color: var(--ink-soft); padding-top: 0.9rem; min-height: 1.4em; }
.bu-status.good { color: var(--good); }
.bu-status.bad { color: var(--bad); }
@media (prefers-reduced-motion: reduce) {
.infographic-beforeunload *, .infographic-beforeunload *::before, .infographic-beforeunload *::after {
animation-duration: 0.001ms !important; transition-duration: 0.001ms !important;
}
}
</style>
<div class="bu-wrap">
<div class="bu-code"><span class="tok-kw">let</span> <span class="tok-var">isFormDirty</span> <span class="tok-punc">=</span> <span class="tok-kw">true</span><span class="tok-punc">;</span>
<span class="tok-var">window</span><span class="tok-punc">.</span><span class="tok-fn">addEventListener</span><span class="tok-punc">(</span><span class="tok-str">"beforeunload"</span><span class="tok-punc">, (</span><span class="tok-var">event</span><span class="tok-punc">) => {</span>
<span class="tok-kw">if</span> <span class="tok-punc">(</span><span class="tok-var">isFormDirty</span><span class="tok-punc">) {</span>
<span class="tok-var">event</span><span class="tok-punc">.</span><span class="tok-fn">preventDefault</span><span class="tok-punc">();</span>
<span class="tok-var">event</span><span class="tok-punc">.</span><span class="tok-var">returnValue</span> <span class="tok-punc">=</span> <span class="tok-str">""</span><span class="tok-punc">;</span>
<span class="tok-punc">}</span>
<span class="tok-punc">});</span></div>
<div class="bu-toggle-row">
<span class="bu-toggle-label">isFormDirty?</span>
<div class="bu-toggle" id="buToggle">
<button class="off" data-val="off" type="button">no</button>
<button class="on is-active" data-val="on" type="button">yes</button>
</div>
</div>
<div class="bu-page" id="buPage">
<div class="bu-page-bar">
<div class="bu-page-dot"></div><div class="bu-page-dot"></div><div class="bu-page-dot"></div>
<div class="bu-page-title">checkout form</div>
</div>
<div class="bu-page-body">
<div class="bu-field-label"><span class="bu-dirty-dot is-shown" id="dirtyDot"></span>Shipping address</div>
<input class="bu-input" id="buInput" type="text" value="221B Baker Street" readonly>
</div>
<div class="bu-dialog-overlay" id="dialogOverlay">
<div class="bu-dialog">
<div class="bu-dialog-title">Leave site?</div>
<div class="bu-dialog-text">Changes you made may not be saved.</div>
<div class="bu-dialog-btns">
<button class="bu-dialog-btn" id="cancelBtn" type="button">Cancel</button>
<button class="bu-dialog-btn primary" id="leaveBtn" type="button">Leave</button>
</div>
</div>
</div>
</div>
<button class="bu-close-btn" id="closeBtn" type="button">✕ close tab</button>
<p class="bu-status" id="buStatus"></p>
</div>
</div>
<script>
(function () {
const wrapper = document.currentScript.closest('.infographic-beforeunload')
|| document.querySelector('.infographic-beforeunload');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') { dark = true; }
else if (bodyAttr === 'false') { dark = false; }
else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
let isDirty = true;
const toggle = wrapper.querySelector('#buToggle');
const dirtyDot = wrapper.querySelector('#dirtyDot');
toggle.querySelectorAll('button').forEach(b => {
b.addEventListener('click', () => {
isDirty = b.dataset.val === 'on';
toggle.querySelectorAll('button').forEach(x => x.classList.remove('is-active'));
b.classList.add('is-active');
dirtyDot.classList.toggle('is-shown', isDirty);
});
});
const page = wrapper.querySelector('#buPage');
const closeBtn = wrapper.querySelector('#closeBtn');
const overlay = wrapper.querySelector('#dialogOverlay');
const cancelBtn = wrapper.querySelector('#cancelBtn');
const leaveBtn = wrapper.querySelector('#leaveBtn');
const status = wrapper.querySelector('#buStatus');
closeBtn.addEventListener('click', () => {
status.textContent = '';
status.className = 'bu-status';
if (isDirty) {
overlay.classList.add('is-shown');
} else {
page.classList.add('is-closing');
status.textContent = 'no unsaved changes — tab closed immediately';
status.classList.add('good');
setTimeout(() => { page.classList.remove('is-closing'); }, 700);
}
});
cancelBtn.addEventListener('click', () => {
overlay.classList.remove('is-shown');
status.textContent = '"Cancel" — stayed on the page';
status.className = 'bu-status bad';
});
leaveBtn.addEventListener('click', () => {
overlay.classList.remove('is-shown');
page.classList.add('is-closing');
status.textContent = '"Leave" — tab closed anyway';
status.className = 'bu-status good';
setTimeout(() => { page.classList.remove('is-closing'); }, 700);
});
})();
</script>📌 Dialog appears only if data is unsaved
🔹 4. unload Event
📌 When it fires
User leaves the page
Page is being destroyed
After
beforeunload
✅ Syntax
window.addEventListener("unload", () => {
console.log("Page unloaded");
});⚠️ Important Limitations
❌ Async code may not run
❌ Network requests may fail
❌ alert() won’t work
❌ Console logs may not appear
📌 Browser may terminate scripts immediately
🧠 Valid Use Cases
✔ Cleanup tasks
✔ Clearing timers
✔ Releasing resources
❌ Bad Use Case
window.addEventListener("unload", async () => {
await fetch("/save-data"); // ❌ unreliable
});🔄 beforeunload vs unload
Feature |
|
|
|---|---|---|
Fires | Before leaving page | When page unloads |
Can stop leaving | ✅ Yes | ❌ No |
Shows dialog | ✅ Yes | ❌ No |
User still present | ✅ Yes | ❌ No |
Main use | Warn user | Cleanup only |
Async code | ❌ Not reliable | ❌ Not allowed |
🧠 Memory Trick
beforeunload → “Wait, are you sure?”
unload → “Too late, I’m gone.”
🧠 Best Practices Summary
Scenario | Recommended Event |
|---|---|
DOM manipulation |
|
Image/layout dependent logic |
|
Warn before leaving |
|
Cleanup only |
|