ข้ามไปเนื้อหาหลัก

Category: reference

Browser Web APIs — ResizeObserver, Clipboard, Broadcast Channel, Web Workers

Web APIs ที่ใช้บ่อยและมักถูกมองข้าม: ResizeObserver, MutationObserver, Clipboard, Broadcast Channel, Web Workers

· อ่านประมาณ 4 นาที

สารบัญ

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

APIChromeFirefoxSafari
ResizeObserver64+69+13.1+
MutationObserver26+14+7+
Clipboard API66+63+13.1+
Broadcast Channel54+38+15.4+
Web Workers4+3.5+4+
Page Visibility33+18+7+