Skip to content

Instantly share code, notes, and snippets.

@Kcko
Last active September 30, 2025 10:30
Show Gist options
  • Save Kcko/17685dff01ff40b04b7bbc3f05c8a10e to your computer and use it in GitHub Desktop.
Save Kcko/17685dff01ff40b04b7bbc3f05c8a10e to your computer and use it in GitHub Desktop.
// https://medium.com/@ThinkingLoop/12-dom-tricks-no-libraries-required-61268e398c50
// 1) Query once, act many
const $ = (sel, root = document) => root.querySelector(sel);
const $$ = (sel, root = document) => [...root.querySelectorAll(sel)];
const app = $('#app');
const buttons = $$('.action', app);
buttons.forEach(btn => btn.addEventListener('click', onAction));
// 2) Event delegation (one listener, infinite items)
$('#list').addEventListener('click', e => {
const btn = e.target.closest('[data-action="remove"]');
if (!btn) return;
e.preventDefault();
btn.closest('li')?.remove();
});
// when we dont want use e.preventDeafult() -> pasive: true, better optimalization
window.addEventListener('scroll', onScroll, { passive: true });
// 3) Build with DocumentFragment (no layout thrash)
const frag = document.createDocumentFragment();
for (const item of data) {
const li = document.createElement('li');
li.textContent = item.name;
frag.appendChild(li);
}
$('#list').appendChild(frag); // one commit
// 4) Render HTML safely with <template>
<template id="row-tpl">
<li class="row">
<span class="name"></span>
<button data-action="remove">×</button>
</li>
</template>
function renderRow(user) {
const tpl = $('#row-tpl').content.cloneNode(true);
tpl.querySelector('.name').textContent = user.name;
return tpl;
}
const frag = document.createDocumentFragment();
users.forEach(u => frag.appendChild(renderRow(u)));
$('#list').appendChild(frag);
// 5) Class toggles beat inline styles
.card { transition: transform .2s ease; }
.card.is-active { transform: scale(1.02); }
$('.card').classList.toggle('is-active', shouldHighlight);
// 6) dataset for behavior, not style
<button data-action="copy" data-copy-text="Hello!">Copy</button>
document.addEventListener('click', e => {
const btn = e.target.closest('[data-action="copy"]');
if (!btn) return;
navigator.clipboard.writeText(btn.dataset.copyText);
});
// 7) Observe, don’t poll (Intersection/Resize/Mutation)
// IntersectionObserver: lazy-load images
const io = new IntersectionObserver(entries => {
entries.forEach(({ isIntersecting, target }) => {
if (!isIntersecting) return;
target.src = target.dataset.src;
io.unobserve(target);
});
}, { rootMargin: '200px' });
$$('img[data-src]').forEach(img => io.observe(img));
// ResizeObserver: reflow grids on size change
const ro = new ResizeObserver(entries => {
for (const { target } of entries) layout(target);
});
ro.observe($('#grid'));
// 8) Batch mutations with requestAnimationFrame
function highlightRow(row) {
// READ
const { top } = row.getBoundingClientRect();
// Schedule WRITE
requestAnimationFrame(() => {
row.style.setProperty('--start', `${top}px`);
row.classList.add('highlight');
});
}
// 9) Virtualize long lists with windowing
const viewport = $('#viewport');
const rowHeight = 32;
let start = 0, end = 0;
viewport.addEventListener('scroll', render);
window.addEventListener('resize', render);
function render() {
const { scrollTop, clientHeight } = viewport;
start = Math.floor(scrollTop / rowHeight);
end = start + Math.ceil(clientHeight / rowHeight) + 5;
const frag = document.createDocumentFragment();
viewport.innerHTML = ''; // clear (or recycle nodes for extra perf)
for (let i = start; i < Math.min(end, data.length); i++) {
const div = document.createElement('div');
div.className = 'row';
div.style.top = `${i * rowHeight}px`;
div.textContent = data[i].name;
frag.appendChild(div);
}
viewport.appendChild(frag);
}
#viewport { position: relative; overflow: auto; height: 400px; }
.row { position: absolute; height: 32px; line-height: 32px; left: 0; right: 0; }
// 10) Custom events for clean boundaries
/ producer
function notifySaved(node, payload) {
node.dispatchEvent(new CustomEvent('saved', { detail: payload, bubbles: true }));
}
// consumer
document.addEventListener('saved', e => {
const { id } = e.detail;
toast(`Saved #${id}`);
});
// 11) Progressive enhancement with feature checks
const supportsClipboard = !!navigator.clipboard?.writeText;
if (supportsClipboard) {
document.body.classList.add('has-clipboard');
// enable fancy copy button
} else {
// use <input> select() + execCommand fallback or plain text
}
// 12) Micro-router: data-route + history.pushState
<nav>
<a href="/settings" data-route>Settings</a>
<a href="/reports" data-route>Reports</a>
</nav>
<main id="view"></main>
const routes = {
'/settings': () => $('#view').textContent = 'Settings ⚙️',
'/reports': () => $('#view').textContent = 'Reports 📈',
};
function go(path) {
history.pushState({}, '', path);
routes[path]?.();
}
document.addEventListener('click', e => {
const link = e.target.closest('a[data-route]');
if (!link) return;
e.preventDefault();
go(new URL(link.href).pathname);
});
window.addEventListener('popstate', () => routes[location.pathname]?.());
routes[location.pathname]?.(); // initial
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment