Modern Web API'leri
Tarayıcının Gizli Yetenekleri
Tarayıcılar artık sadece HTML gösteren kutular değil — birer işletim sistemi gibi çalışıyorlar. Konum bilgisi alabilir, bildirim gönderebilir, pano'ya erişebilir, DOM değişikliklerini izleyebilir, elementin ekranda görünüp görünmediğini anlayabilirsin. Bu güçlü API'ler, eskiden sadece native uygulamaların yapabildiği şeyleri web'de mümkün kılıyor.
Bu derste beş güçlü Web API'sini derinlemesine keşfedeceğiz: IntersectionObserver ile görünürlük izleme, ResizeObserver ile boyut değişiklikleri, MutationObserver ile DOM değişiklikleri, Geolocation ile konum, Notification ile bildirimler ve Clipboard API ile pano erişimi. Her biri gerçek dünya senaryolarıyla — lazy loading, responsive tasarım, canlı editörler, harita entegrasyonu gibi.
IntersectionObserver: Bir Element Ekranda mı?
Bir elementin viewport'ta (ekranda görünen alan) olup olmadığını anlamak eskiden zor ve pahalıydı. scroll event'ine listener ekler, getBoundingClientRect() ile hesaplama yapardık — ve bu, her scroll'da tetiklenerek performansı çökertiirdi.
IntersectionObserver bu sorunu verimli ve asenkron olarak çözer. Tarayıcı optimizasyonu ile çalışır, ana thread'i bloklamaz.
Bir güvenlik kamerası gibi düşün: sürekli etrafı taramak yerine, belirli bir alan hareket algıladığında seni uyarıyor.
// Temel kullanım
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
console.log(`${entry.target.id} ekrana girdi!`);
} else {
console.log(`${entry.target.id} ekrandan çıktı`);
}
});
});
// İzlenecek elementleri belirle
const sections = document.querySelectorAll(".section");
sections.forEach(section => observer.observe(section));
// İzlemeyi durdur
// observer.unobserve(element); // Tek element
// observer.disconnect(); // TümüLazy Loading: Görselleri Gerektiğinde Yükle
Lazy loading'in en yaygın kullanımı — görselleri sadece ekrana yaklaştığında yükle:
function createLazyLoader() {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
// data-src'deki gerçek URL'yi src'ye aktar
img.src = img.dataset.src;
img.classList.add("loaded");
// Bu element artık yüklendi, izlemeye gerek yok
observer.unobserve(img);
}
});
},
{
rootMargin: "200px", // Ekrana 200px kala yüklemeye başla
threshold: 0, // Piksel kadar görünürse yeterli
}
);
// Tüm lazy görselleri izle
document.querySelectorAll("img[data-src]").forEach(img => {
observer.observe(img);
});
return observer;
}
// HTML:
// <img data-src="heavy-image.jpg" alt="..." class="lazy" />
createLazyLoader();Sonsuz Scroll (Infinite Scroll)
function setupInfiniteScroll(loadMore) {
// Sayfanın en altındaki "sentinel" elementi izle
const sentinel = document.getElementById("scroll-sentinel");
const observer = new IntersectionObserver(
async (entries) => {
if (entries[0].isIntersecting) {
await loadMore(); // Yeni veri yükle
}
},
{ threshold: 1.0 } // Element tamamen görünür olduğunda
);
observer.observe(sentinel);
}
// HTML: <div id="scroll-sentinel"></div> (listenin altında)
setupInfiniteScroll(async () => {
const newItems = await fetch("/api/items?page=next").then(r => r.json());
renderItems(newItems);
});Animasyon Tetikleme
// Scroll ile animasyon — element ekrana girdiğinde animasyon başlasın
const animationObserver = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add("animate-in");
animationObserver.unobserve(entry.target); // Bir kez çalışsın
}
});
},
{ threshold: 0.2 } // %20'si görünürse
);
document.querySelectorAll(".animate-on-scroll").forEach(el => {
animationObserver.observe(el);
});💡 İpucu:
rootMarginnegatif değer alabilir — bu sayede element ekranın belirli bir iç bölgesine girdiğinde tetikleme yapabilirsin. Sticky header tespiti gibi senaryolarda kullanışlı.
ResizeObserver: Boyut Değişikliklerini İzle
Bir elementin boyutu değiştiğinde haberdar olmak istediğinde — grafikler, dashboard widget'ları, responsive bileşenler — ResizeObserver devreye girer.
window.onresize sadece pencere boyutu değiştiğinde çalışır. Ama bir <div>'in boyutu CSS animasyonu, içerik değişikliği veya komşu elementin değişmesiyle de değişebilir. ResizeObserver herhangi bir elementin boyut değişikliğini yakalar.
const observer = new ResizeObserver((entries) => {
for (const entry of entries) {
const { width, height } = entry.contentRect;
console.log(`${entry.target.id}: ${width}x${height}`);
}
});
const container = document.getElementById("chart-container");
observer.observe(container);
// Grafik container'ı yeniden boyutlandığında grafik de güncellensin
const chartObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
const { width, height } = entry.contentRect;
// Grafik kütüphanesine yeni boyutu bildir
chart.resize(width, height);
chart.render();
}
});
chartObserver.observe(document.getElementById("chart-wrapper"));Container Queries Benzeri Davranış
// Element boyutuna göre class ekle/çıkar
// CSS Container Queries olmadan benzer sonuç
const responsiveObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
const el = entry.target;
const width = entry.contentRect.width;
el.classList.toggle("compact", width < 400);
el.classList.toggle("medium", width >= 400 && width < 800);
el.classList.toggle("wide", width >= 800);
}
});
document.querySelectorAll(".responsive-widget").forEach(widget => {
responsiveObserver.observe(widget);
});MutationObserver: DOM Değişikliklerini Yakalama
MutationObserver, DOM ağacındaki değişiklikleri izler: element ekleme/silme, attribute değişikliği, metin içeriği değişikliği. Canlı editörler, extension'lar, analitik araçlar bu API'yi yoğun kullanır.
Bir güvenlik nöbetçisi gibi düşün: belirli bir odada herhangi bir değişiklik olduğunda (eşya taşınması, yeni eşya gelmesi) seni bilgilendiriyor.
// DOM değişikliklerini izle
const observer = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
switch (mutation.type) {
case "childList":
// Alt elementler eklendi veya silindi
mutation.addedNodes.forEach(node => {
console.log("Eklendi:", node);
});
mutation.removedNodes.forEach(node => {
console.log("Silindi:", node);
});
break;
case "attributes":
// Attribute değişti
console.log(
`${mutation.attributeName} değişti:`,
mutation.target.getAttribute(mutation.attributeName)
);
break;
case "characterData":
// Metin içeriği değişti
console.log("Metin değişti:", mutation.target.textContent);
break;
}
});
});
// İzleme seçenekleri
observer.observe(document.getElementById("editor"), {
childList: true, // Alt element ekleme/silme
attributes: true, // Attribute değişiklikleri
characterData: true, // Metin değişiklikleri
subtree: true, // Alt ağaçtaki değişiklikler de
attributeOldValue: true, // Eski attribute değerini de kaydet
});
// İzlemeyi durdur
// observer.disconnect();Pratik Örnek: Otomatik Tablo İçeriği
// Tabloya yeni satır eklendiğinde otomatik numara ver
function autoNumberTable(table) {
const observer = new MutationObserver((mutations) => {
const tbody = table.querySelector("tbody");
if (!tbody) return;
const rows = tbody.querySelectorAll("tr");
rows.forEach((row, index) => {
const numberCell = row.querySelector(".row-number");
if (numberCell) {
numberCell.textContent = String(index + 1);
}
});
});
observer.observe(table.querySelector("tbody"), {
childList: true, // Satır ekleme/silme
});
return observer;
}Dark Mode Algılama
// <html> tag'ındaki class değişikliğini izle — tema değişimi
const themeObserver = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
if (mutation.attributeName === "class") {
const isDark = document.documentElement.classList.contains("dark");
console.log(`Tema değişti: ${isDark ? "dark" : "light"}`);
// Grafikleri, haritayı vb. güncelle
}
});
});
themeObserver.observe(document.documentElement, {
attributes: true,
attributeFilter: ["class"], // Sadece class değişikliklerini izle
});⚠️ Dikkat: MutationObserver çok sık tetiklenebilir. Ağır işlemler yapıyorsan
requestAnimationFrameveya debounce ile birleştir. Aksi halde performans sorunu yaşarsın.
Geolocation API: Konum Bilgisi
Geolocation API, kullanıcının coğrafi konumunu almana olanak tanır. Harita uygulamaları, yakındaki restoranlar, teslimat uygulamaları — konum bilgisi gerektiren her senaryoda kullanılır.
// Tek seferlik konum al
navigator.geolocation.getCurrentPosition(
(position) => {
// Başarılı
console.log("Enlem:", position.coords.latitude);
console.log("Boylam:", position.coords.longitude);
console.log("Doğruluk (metre):", position.coords.accuracy);
console.log("Yükseklik:", position.coords.altitude); // null olabilir
console.log("Hız:", position.coords.speed); // null olabilir
},
(error) => {
// Hata
switch (error.code) {
case error.PERMISSION_DENIED:
console.error("Kullanıcı konum iznini reddetti");
break;
case error.POSITION_UNAVAILABLE:
console.error("Konum bilgisi mevcut değil");
break;
case error.TIMEOUT:
console.error("Konum isteği zaman aşımına uğradı");
break;
}
},
{
enableHighAccuracy: true, // GPS kullan (daha yavaş ama hassas)
timeout: 10000, // 10 saniye zaman aşımı
maximumAge: 60000, // 1 dakika eski konum kabul edilir
}
);Sürekli Konum İzleme
// Konum değiştikçe güncelle — navigasyon uygulamalarında
const watchId = navigator.geolocation.watchPosition(
(position) => {
updateMapMarker(position.coords.latitude, position.coords.longitude);
},
(error) => {
console.error("Konum hatası:", error.message);
},
{
enableHighAccuracy: true,
maximumAge: 0, // Her zaman taze konum
}
);
// İzlemeyi durdur
// navigator.geolocation.clearWatch(watchId);Mesafe Hesaplama (Haversine)
// İki nokta arasındaki mesafeyi hesapla (km)
function calculateDistance(lat1, lon1, lat2, lon2) {
const R = 6371; // Dünya yarıçapı (km)
const dLat = toRad(lat2 - lat1);
const dLon = toRad(lon2 - lon1);
const a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) *
Math.sin(dLon / 2) * Math.sin(dLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c;
}
function toRad(deg) {
return deg * (Math.PI / 180);
}
// Kullanım
const istanbul = { lat: 41.0082, lon: 28.9784 };
const ankara = { lat: 39.9334, lon: 32.8597 };
const dist = calculateDistance(istanbul.lat, istanbul.lon, ankara.lat, ankara.lon);
console.log(`Mesafe: ${dist.toFixed(1)} km`); // ~351.5 km⚠️ Dikkat: Geolocation API HTTPS gerektirir (localhost hariç). HTTP üzerinde çalışmaz. Ayrıca kullanıcı izni gerekir — izin verilmezse graceful degradation yap.
Notification API: Masaüstü Bildirimleri
Web uygulamalarından masaüstü bildirimi gönderebilirsin — mesajlaşma uygulamaları, e-posta bildirimleri, zamanlayıcılar gibi senaryolarda.
// İzin iste (bir kez)
async function requestNotificationPermission() {
if (!("Notification" in window)) {
console.log("Bu tarayıcı bildirimleri desteklemiyor");
return false;
}
if (Notification.permission === "granted") {
return true; // Zaten izin verilmiş
}
if (Notification.permission !== "denied") {
const permission = await Notification.requestPermission();
return permission === "granted";
}
return false; // Daha önce reddedilmiş
}
// Bildirim gönder
async function sendNotification(title, body, icon) {
const hasPermission = await requestNotificationPermission();
if (!hasPermission) return;
const notification = new Notification(title, {
body,
icon: icon || "/favicon.png",
badge: "/badge.png",
tag: "my-notification", // Aynı tag'lı bildirim güncellenir
requireInteraction: false, // Otomatik kapansın mı
});
// Tıklama olayı
notification.onclick = (event) => {
event.preventDefault();
window.focus(); // Uygulamayı ön plana getir
notification.close();
};
// Otomatik kapat
setTimeout(() => notification.close(), 5000);
}
// Kullanım
sendNotification(
"Yeni Mesaj",
"Ali: Merhaba, nasılsın?",
"/avatars/ali.jpg"
);Bildirim Stratejisi
// Akıllı bildirim yöneticisi — spam önleme
class NotificationManager {
#lastNotification = 0;
#minInterval = 5000; // En az 5 saniye arayla
async notify(title, options = {}) {
const now = Date.now();
if (now - this.#lastNotification < this.#minInterval) {
console.log("Bildirim çok sık, atlanıyor");
return;
}
// Sayfa odakta ise bildirim gönderme
if (document.hasFocus()) {
console.log("Sayfa zaten aktif, bildirim gerekmiyor");
return;
}
const hasPermission = await requestNotificationPermission();
if (!hasPermission) return;
this.#lastNotification = now;
return new Notification(title, options);
}
}
const notifier = new NotificationManager();Clipboard API: Pano Erişimi
Modern Clipboard API, metin, HTML ve hatta dosyaları pano'ya kopyalama ve yapıştırma imkanı sunar:
// Metin kopyala — modern async API
async function copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text);
console.log("Kopyalandı!");
return true;
} catch (error) {
console.error("Kopyalama başarısız:", error);
return false;
}
}
// Metni panodan oku
async function pasteFromClipboard() {
try {
const text = await navigator.clipboard.readText();
console.log("Yapıştırılan:", text);
return text;
} catch (error) {
console.error("Yapıştırma başarısız:", error);
return null;
}
}
// "Kopyala" butonu
document.getElementById("copy-btn").addEventListener("click", async () => {
const code = document.getElementById("code-block").textContent;
const success = await copyToClipboard(code);
if (success) {
// Butonu geçici olarak "Kopyalandı!" yap
const btn = document.getElementById("copy-btn");
const original = btn.textContent;
btn.textContent = "✅ Kopyalandı!";
setTimeout(() => btn.textContent = original, 2000);
}
});Zengin İçerik Kopyalama
// HTML ve düz metin olarak kopyala
async function copyRichText(html, plainText) {
try {
const blob = new Blob([html], { type: "text/html" });
const textBlob = new Blob([plainText], { type: "text/plain" });
const clipboardItem = new ClipboardItem({
"text/html": blob,
"text/plain": textBlob,
});
await navigator.clipboard.write([clipboardItem]);
console.log("Zengin içerik kopyalandı!");
} catch (error) {
console.error("Kopyalama hatası:", error);
}
}
// Kullanım: tablo verisi kopyala
copyRichText(
"<table><tr><td>İsim</td><td>Yaş</td></tr></table>",
"İsim\tYaş"
);⚠️ Dikkat: Clipboard API güvenli bağlam (HTTPS veya localhost) ve çoğu durumda kullanıcı etkileşimi (tıklama) gerektirir. Arka planda sessizce pano'ya erişemezsin — bu bir güvenlik önlemi.
Gerçek Dünya Örneği: Akıllı Dashboard Widget
Birden fazla API'yi birleştiren bir dashboard widget'ı:
class DashboardWidget {
constructor(container) {
this.container = container;
this.chart = null;
this.setupObservers();
}
setupObservers() {
// 1. Element ekrana girince veri yükle (lazy load)
this.visibilityObserver = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) {
this.loadData();
this.visibilityObserver.unobserve(this.container);
}
},
{ threshold: 0.1 }
);
this.visibilityObserver.observe(this.container);
// 2. Boyut değişince grafiği yeniden çiz
this.resizeObserver = new ResizeObserver((entries) => {
const { width, height } = entries[0].contentRect;
if (this.chart) {
this.chart.resize(width, height - 40); // Header çıkar
}
});
this.resizeObserver.observe(this.container);
// 3. Container'a veri attribute'u eklenirse güncelle
this.mutationObserver = new MutationObserver((mutations) => {
mutations.forEach(m => {
if (m.attributeName === "data-refresh") {
this.loadData();
}
});
});
this.mutationObserver.observe(this.container, {
attributes: true,
attributeFilter: ["data-refresh"],
});
}
async loadData() {
const endpoint = this.container.dataset.endpoint;
const data = await fetch(endpoint).then(r => r.json());
this.render(data);
}
render(data) {
// Grafik çizim mantığı...
this.chart = { resize: (w, h) => console.log(`Resize: ${w}x${h}`) };
this.container.innerHTML = `<div class="chart">${data.length} veri noktası</div>`;
}
destroy() {
this.visibilityObserver.disconnect();
this.resizeObserver.disconnect();
this.mutationObserver.disconnect();
}
}Özet
IntersectionObserver: Element ekranda mı? Lazy loading, infinite scroll, scroll animasyonları.
rootMarginvethresholdile ince ayar.ResizeObserver: Element boyutu değişti mi? Grafik yeniden çizimi, responsive widget'lar. Window resize değil, element resize.
MutationObserver: DOM değişti mi? Element ekleme/silme, attribute değişikliği, metin değişikliği. Canlı editörler, tema algılama.
Geolocation: Kullanıcı konumu (HTTPS + izin gerekli).
getCurrentPositiontek seferlik,watchPositionsürekli izleme.Notification: Masaüstü bildirimi (izin gerekli).
tagile güncelleme,document.hasFocus()ile gereksiz bildirimi engelle.Clipboard API: Kopyala/yapıştır (HTTPS + kullanıcı etkileşimi).
writeTextbasit metin,writezengin içerik.Tüm Observer'larda
disconnect()çağırmayı unutma — memory leak'e yol açar.
AI Asistan
Sorularını yanıtlamaya hazır