Category: reference
Browser Web APIs — ResizeObserver, Clipboard, Broadcast Channel, Web Workers
Web APIs ที่ใช้บ่อยและมักถูกมองข้าม: ResizeObserver, MutationObserver, Clipboard, Broadcast Channel, Web Workers
สารบัญ
ResizeObserver
ตรวจ size change ของ element (ไม่ใช่แค่ window):
const observer = new ResizeObserver((entries) => {
for (const entry of entries) {
const { width, height } = entry.contentRect;
console.log(`${entry.target.id}: ${width}×${height}`);
// Border box dimensions (padding + border รวมด้วย)
const [borderBox] = entry.borderBoxSize;
console.log('border box:', borderBox.inlineSize, borderBox.blockSize);
}
});
observer.observe(document.querySelector('.chart-container'));
observer.unobserve(element);
observer.disconnect();
Use case: Chart ที่ต้อง re-render เมื่อ container เปลี่ยน size, responsive component ที่ไม่ขึ้นกับ media query
MutationObserver
ตรวจ DOM changes:
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type === 'childList') {
console.log('nodes added:', mutation.addedNodes);
console.log('nodes removed:', mutation.removedNodes);
}
if (mutation.type === 'attributes') {
console.log(`attr "${mutation.attributeName}" changed to:`, mutation.target.getAttribute(mutation.attributeName));
}
}
});
observer.observe(document.body, {
childList: true, // ตรวจ add/remove children
attributes: true, // ตรวจ attribute changes
subtree: true, // ตรวจ descendants ด้วย
characterData: true, // ตรวจ text node changes
attributeOldValue: true, // เก็บค่าเก่าของ attribute
characterDataOldValue: true,
});
observer.disconnect();
Use case: เพิ่ม behavior ให้ dynamic content ที่ inject โดย third-party, detect DOM changes จาก CMS preview
Clipboard API
// อ่าน clipboard (ต้องขอ permission)
async function readClipboard() {
try {
const text = await navigator.clipboard.readText();
console.log('Clipboard:', text);
} catch (err) {
console.error('Cannot read clipboard:', err);
}
}
// เขียน clipboard (ไม่ต้องขอ permission)
async function copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text);
console.log('Copied!');
} catch {
// Fallback สำหรับ browser เก่า
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
}
}
// Copy รูปภาพ
async function copyImage(imageElement) {
const response = await fetch(imageElement.src);
const blob = await response.blob();
await navigator.clipboard.write([
new ClipboardItem({ [blob.type]: blob }),
]);
}
Broadcast Channel
สื่อสารระหว่าง tabs/windows ของ domain เดียวกัน:
// Tab A
const channel = new BroadcastChannel('app-events');
channel.postMessage({ type: 'USER_LOGIN', userId: '123' });
// Tab B — รับข้อความจาก Tab A
const channel = new BroadcastChannel('app-events');
channel.onmessage = (event) => {
if (event.data.type === 'USER_LOGIN') {
updateUserUI(event.data.userId);
}
};
// ปิด channel
channel.close();
Use case: sync dark mode toggle ระหว่าง tabs, logout จาก tab หนึ่งแล้ว logout ทุก tab, shopping cart sync
Web Workers
รัน JavaScript แบบ background thread ไม่บล็อก main thread:
// main.js
const worker = new Worker('/worker.js');
worker.postMessage({ data: largeArray, operation: 'sort' });
worker.onmessage = (event) => {
console.log('Result:', event.data.result);
};
worker.onerror = (error) => {
console.error('Worker error:', error);
};
// ปิด worker
worker.terminate();
// worker.js (แยกไฟล์)
self.onmessage = (event) => {
const { data, operation } = event.data;
if (operation === 'sort') {
const result = [...data].sort((a, b) => a - b);
self.postMessage({ result });
}
};
Inline Worker (ไม่ต้องแยกไฟล์):
const workerCode = `
self.onmessage = (event) => {
const result = heavyComputation(event.data);
self.postMessage(result);
};
function heavyComputation(n) { return n * n; }
`;
const blob = new Blob([workerCode], { type: 'text/javascript' });
const worker = new Worker(URL.createObjectURL(blob));
Web Storage
// localStorage — persist ข้าม session
localStorage.setItem('theme', 'dark');
localStorage.getItem('theme'); // 'dark'
localStorage.removeItem('theme');
localStorage.clear();
// sessionStorage — เฉพาะ session ปัจจุบัน
sessionStorage.setItem('draft', JSON.stringify(formData));
const draft = JSON.parse(sessionStorage.getItem('draft') ?? 'null');
// ทั้งคู่เก็บได้เฉพาะ string
localStorage.setItem('user', JSON.stringify({ id: 1, name: 'Alice' }));
const user = JSON.parse(localStorage.getItem('user') ?? 'null');
// Storage event — sync ระหว่าง tabs
window.addEventListener('storage', (event) => {
if (event.key === 'theme') {
document.documentElement.dataset.theme = event.newValue ?? 'light';
}
});
Page Visibility API
ตรวจว่า user กำลังดู tab นี้อยู่ไหม:
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
// tab ซ่อนอยู่ — หยุด animation, polling, video
pauseVideo();
clearInterval(pollingInterval);
} else {
// tab กลับมา visible — เริ่มใหม่
resumeVideo();
startPolling();
}
});
Performance API
// ตรวจ navigation timing
const nav = performance.getEntriesByType('navigation')[0];
console.log('TTFB:', nav.responseStart - nav.fetchStart, 'ms');
console.log('DOMContentLoaded:', nav.domContentLoadedEventEnd - nav.fetchStart, 'ms');
console.log('Load:', nav.loadEventEnd - nav.fetchStart, 'ms');
// Custom marks
performance.mark('myFeature-start');
await loadMyFeature();
performance.mark('myFeature-end');
performance.measure('myFeature', 'myFeature-start', 'myFeature-end');
const [measure] = performance.getEntriesByName('myFeature');
console.log('Duration:', measure.duration, 'ms');
// Long tasks (> 50ms)
new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
if (entry.duration > 50) {
console.warn('Long task:', entry.duration, 'ms');
}
});
}).observe({ type: 'longtask', buffered: true });
Quick Reference: Browser Support
| API | Chrome | Firefox | Safari |
|---|---|---|---|
| ResizeObserver | 64+ | 69+ | 13.1+ |
| MutationObserver | 26+ | 14+ | 7+ |
| Clipboard API | 66+ | 63+ | 13.1+ |
| Broadcast Channel | 54+ | 38+ | 15.4+ |
| Web Workers | 4+ | 3.5+ | 4+ |
| Page Visibility | 33+ | 18+ | 7+ |