Web APIs Modernas: 10 APIs del Navegador Que Todo Desarrollador JavaScript Debe Conocer en 2025
Hola HaWkers, los navegadores modernos son plataformas increíblemente poderosas. Muchos desarrolladores utilizan frameworks sin conocer las APIs nativas que resolverían sus problemas de forma más eficiente.
En 2025, conocer estas APIs es diferencial. Ellas permiten crear experiencias ricas sin dependencias externas, con mejor performance y soporte nativo.
Por Qué Conocer Web APIs Nativas
Beneficios:
- Zero dependencias externas
- Performance optimizada por el navegador
- APIs estables y bien documentadas
- Mejor compatibilidad a largo plazo
- Menor bundle size
Cuándo usar:
- Cuando la API nativa resuelve el problema
- Cuando performance es crítica
- Cuando quieres reducir dependencias
- Para features específicas del navegador
1. Intersection Observer API
Detecta cuándo elementos entran o salen del viewport. Esencial para lazy loading, infinite scroll y animaciones.
// Lazy loading de imágenes
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.add('loaded');
observer.unobserve(img);
}
});
}, {
rootMargin: '50px', // Carga 50px antes de entrar en el viewport
threshold: 0.1
});
// Aplicar a todas las imágenes con lazy loading
document.querySelectorAll('img[data-src]').forEach(img => {
imageObserver.observe(img);
});// Infinite scroll
const loadMoreObserver = new IntersectionObserver(async (entries) => {
const lastEntry = entries[0];
if (lastEntry.isIntersecting) {
await loadMoreItems();
}
}, {
threshold: 1.0
});
// Observar el elemento sentinela al final de la lista
const sentinel = document.querySelector('#load-more-trigger');
loadMoreObserver.observe(sentinel);// Animaciones al scroll
const animateOnScroll = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('animate-in');
}
});
}, {
threshold: 0.2
});
document.querySelectorAll('.animate-on-scroll').forEach(el => {
animateOnScroll.observe(el);
});2. Web Workers API
Ejecuta JavaScript en background thread, evitando bloquear la UI.
// worker.js
self.onmessage = function(e) {
const { type, data } = e.data;
switch(type) {
case 'HEAVY_CALCULATION':
const result = performHeavyCalculation(data);
self.postMessage({ type: 'RESULT', result });
break;
case 'PROCESS_DATA':
const processed = processLargeDataset(data);
self.postMessage({ type: 'PROCESSED', processed });
break;
}
};
function performHeavyCalculation(numbers) {
// Simulación de cálculo pesado
return numbers.reduce((acc, n) => {
for (let i = 0; i < 1000000; i++) {
acc += Math.sqrt(n * i);
}
return acc;
}, 0);
}
function processLargeDataset(data) {
return data.map(item => ({
...item,
processed: true,
timestamp: Date.now()
}));
}// main.js
class WorkerPool {
constructor(workerScript, poolSize = navigator.hardwareConcurrency || 4) {
this.workers = [];
this.queue = [];
this.activeWorkers = 0;
for (let i = 0; i < poolSize; i++) {
this.workers.push(new Worker(workerScript));
}
}
execute(task) {
return new Promise((resolve, reject) => {
this.queue.push({ task, resolve, reject });
this.processQueue();
});
}
processQueue() {
if (this.queue.length === 0) return;
const availableWorker = this.workers.find((w, i) =>
i >= this.activeWorkers
);
if (availableWorker) {
const { task, resolve, reject } = this.queue.shift();
this.activeWorkers++;
availableWorker.onmessage = (e) => {
this.activeWorkers--;
resolve(e.data);
this.processQueue();
};
availableWorker.onerror = (e) => {
this.activeWorkers--;
reject(e);
this.processQueue();
};
availableWorker.postMessage(task);
}
}
}
// Uso
const pool = new WorkerPool('./worker.js');
// Procesar sin bloquear UI
const result = await pool.execute({
type: 'HEAVY_CALCULATION',
data: [1, 2, 3, 4, 5]
});
3. Broadcast Channel API
Comunicación entre tabs/windows del mismo origen.
// En cualquier tab
const channel = new BroadcastChannel('app-sync');
// Enviar mensaje
channel.postMessage({
type: 'USER_LOGGED_OUT',
timestamp: Date.now()
});
// Recibir mensajes
channel.onmessage = (event) => {
const { type, data } = event.data;
switch(type) {
case 'USER_LOGGED_OUT':
// Hacer logout en esta tab también
window.location.href = '/login';
break;
case 'CART_UPDATED':
// Sincronizar carrito
updateCartUI(data);
break;
case 'THEME_CHANGED':
// Sincronizar tema
document.documentElement.setAttribute('data-theme', data.theme);
break;
}
};
// Cerrar el canal cuando no sea necesario
// channel.close();// Caso de uso: Sincronizar autenticación entre tabs
class AuthSync {
constructor() {
this.channel = new BroadcastChannel('auth-sync');
this.setupListeners();
}
setupListeners() {
this.channel.onmessage = (event) => {
const { type, user } = event.data;
if (type === 'LOGIN') {
// Usuario hizo login en otra tab
this.handleRemoteLogin(user);
} else if (type === 'LOGOUT') {
// Usuario hizo logout en otra tab
this.handleRemoteLogout();
}
};
}
notifyLogin(user) {
this.channel.postMessage({ type: 'LOGIN', user });
}
notifyLogout() {
this.channel.postMessage({ type: 'LOGOUT' });
}
handleRemoteLogin(user) {
// Actualizar estado local
store.dispatch({ type: 'SET_USER', user });
// Actualizar UI
window.location.reload();
}
handleRemoteLogout() {
store.dispatch({ type: 'CLEAR_USER' });
window.location.href = '/login';
}
}4. Clipboard API
Acceso moderno a la clipboard (copiar/pegar).
// Copiar texto
async function copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text);
showToast('Copiado para la clipboard!');
} catch (err) {
console.error('Error al copiar:', err);
// Fallback para navegadores más antiguos
fallbackCopy(text);
}
}
// Leer texto de la clipboard
async function pasteFromClipboard() {
try {
const text = await navigator.clipboard.readText();
return text;
} catch (err) {
console.error('Error al pegar:', err);
}
}
// Copiar imagen
async function copyImageToClipboard(imageBlob) {
try {
const item = new ClipboardItem({
'image/png': imageBlob
});
await navigator.clipboard.write([item]);
showToast('Imagen copiada!');
} catch (err) {
console.error('Error al copiar imagen:', err);
}
}
// Componente de botón "copiar"
function CopyButton({ text }) {
const [copied, setCopied] = useState(false);
const handleCopy = async () => {
await copyToClipboard(text);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
return (
<button onClick={handleCopy}>
{copied ? '✓ Copiado!' : 'Copiar'}
</button>
);
}
5. ResizeObserver API
Observa cambios de tamaño en elementos.
// Responsive component
const resizeObserver = new ResizeObserver(entries => {
for (const entry of entries) {
const { width, height } = entry.contentRect;
// Ajustar layout basado en tamaño del elemento
if (width < 400) {
entry.target.classList.add('compact');
entry.target.classList.remove('expanded');
} else {
entry.target.classList.remove('compact');
entry.target.classList.add('expanded');
}
}
});
// Observar container
const container = document.querySelector('.responsive-container');
resizeObserver.observe(container);// Hook React para usar ResizeObserver
function useResizeObserver(ref) {
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
useEffect(() => {
if (!ref.current) return;
const observer = new ResizeObserver(entries => {
const { width, height } = entries[0].contentRect;
setDimensions({ width, height });
});
observer.observe(ref.current);
return () => observer.disconnect();
}, [ref]);
return dimensions;
}
// Uso
function ResponsiveChart() {
const containerRef = useRef(null);
const { width, height } = useResizeObserver(containerRef);
return (
<div ref={containerRef} style={{ width: '100%', height: '400px' }}>
<Chart width={width} height={height} data={data} />
</div>
);
}6. Web Storage API (con Storage Events)
LocalStorage y SessionStorage con sincronización entre tabs.
// Clase para gerenciar storage con sincronización
class SyncedStorage {
constructor(key) {
this.key = key;
this.listeners = new Set();
this.setupSync();
}
get() {
try {
const item = localStorage.getItem(this.key);
return item ? JSON.parse(item) : null;
} catch (e) {
console.error('Error al leer storage:', e);
return null;
}
}
set(value) {
try {
localStorage.setItem(this.key, JSON.stringify(value));
this.notify(value);
} catch (e) {
console.error('Error al escribir storage:', e);
}
}
remove() {
localStorage.removeItem(this.key);
this.notify(null);
}
subscribe(callback) {
this.listeners.add(callback);
return () => this.listeners.delete(callback);
}
notify(value) {
this.listeners.forEach(cb => cb(value));
}
setupSync() {
// Escuchar cambios de otras tabs
window.addEventListener('storage', (e) => {
if (e.key === this.key) {
const value = e.newValue ? JSON.parse(e.newValue) : null;
this.notify(value);
}
});
}
}
// Uso
const userStorage = new SyncedStorage('user');
// En cualquier parte del app
userStorage.subscribe((user) => {
console.log('Usuario cambió:', user);
updateUI(user);
});
// Guardar usuario
userStorage.set({ id: 1, name: 'Juan' });
// Leer usuario
const user = userStorage.get();
7. AbortController API
Control sobre requests asíncronos (cancela fetch, etc).
// Cancelar fetch
const controller = new AbortController();
const signal = controller.signal;
fetch('/api/data', { signal })
.then(response => response.json())
.then(data => console.log(data))
.catch(err => {
if (err.name === 'AbortError') {
console.log('Request cancelado');
} else {
console.error('Error:', err);
}
});
// Cancelar después de 5 segundos
setTimeout(() => controller.abort(), 5000);
// O cancelar manualmente
// controller.abort();// Hook React con abort automático
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
async function fetchData() {
try {
setLoading(true);
const response = await fetch(url, {
signal: controller.signal
});
const json = await response.json();
setData(json);
setError(null);
} catch (err) {
if (err.name !== 'AbortError') {
setError(err.message);
}
} finally {
setLoading(false);
}
}
fetchData();
// Cleanup: abortar request cuando componente desmonta
return () => controller.abort();
}, [url]);
return { data, loading, error };
}// Race condition protection
class SearchService {
constructor() {
this.currentController = null;
}
async search(query) {
// Cancelar búsqueda anterior
if (this.currentController) {
this.currentController.abort();
}
this.currentController = new AbortController();
try {
const response = await fetch(`/api/search?q=${query}`, {
signal: this.currentController.signal
});
return await response.json();
} catch (err) {
if (err.name === 'AbortError') {
return null; // Ignorar requests cancelados
}
throw err;
}
}
}
const searchService = new SearchService();
// Input de búsqueda con debounce
let debounceTimer;
input.addEventListener('input', (e) => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(async () => {
const results = await searchService.search(e.target.value);
if (results) {
displayResults(results);
}
}, 300);
});8. Navigator API (Geolocation, Online Status, etc)
Acceso a información y features del dispositivo.
// Geolocation
async function getCurrentLocation() {
return new Promise((resolve, reject) => {
if (!navigator.geolocation) {
reject(new Error('Geolocation no soportado'));
return;
}
navigator.geolocation.getCurrentPosition(
(position) => {
resolve({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
accuracy: position.coords.accuracy
});
},
(error) => {
reject(error);
},
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 60000
}
);
});
}
// Online/Offline status
function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
const handleOnline = () => setIsOnline(true);
const handleOffline = () => setIsOnline(false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
return isOnline;
}
// Vibration API (mobile)
function vibrate(pattern = [100]) {
if ('vibrate' in navigator) {
navigator.vibrate(pattern);
}
}
// Share API
async function shareContent(data) {
if (navigator.share) {
try {
await navigator.share({
title: data.title,
text: data.text,
url: data.url
});
} catch (err) {
if (err.name !== 'AbortError') {
console.error('Error al compartir:', err);
}
}
} else {
// Fallback: copiar link
await copyToClipboard(data.url);
}
}
9. Page Visibility API
Detecta cuándo la página está visible o oculta.
// Pausar/resumir operaciones basado en visibilidad
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
// Página está oculta
pauseVideo();
pauseAnimations();
reducePollFrequency();
} else {
// Página está visible
resumeVideo();
resumeAnimations();
normalPollFrequency();
}
});
// Hook React
function usePageVisibility() {
const [isVisible, setIsVisible] = useState(!document.hidden);
useEffect(() => {
const handleVisibilityChange = () => {
setIsVisible(!document.hidden);
};
document.addEventListener('visibilitychange', handleVisibilityChange);
return () => {
document.removeEventListener('visibilitychange', handleVisibilityChange);
};
}, []);
return isVisible;
}
// Uso práctico: pausar polling cuando tab está inactiva
function usePolling(url, interval = 5000) {
const isVisible = usePageVisibility();
const [data, setData] = useState(null);
useEffect(() => {
if (!isVisible) return; // No hacer polling si página está oculta
const poll = async () => {
const response = await fetch(url);
const json = await response.json();
setData(json);
};
poll();
const timer = setInterval(poll, interval);
return () => clearInterval(timer);
}, [url, interval, isVisible]);
return data;
}10. Performance API
Medición de performance del app.
// Medir tiempo de operación
performance.mark('inicio-operacion');
// ... operación pesada ...
performance.mark('fin-operacion');
performance.measure('duracion-operacion', 'inicio-operacion', 'fin-operacion');
const medidas = performance.getEntriesByName('duracion-operacion');
console.log(`Operación tomó: ${medidas[0].duration}ms`);// Performance Observer para monitoreo continuo
const performanceObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(`${entry.name}: ${entry.duration}ms`);
// Enviar a analytics
analytics.track('performance', {
name: entry.name,
duration: entry.duration,
entryType: entry.entryType
});
}
});
// Observar diferentes tipos de métricas
performanceObserver.observe({
entryTypes: ['measure', 'navigation', 'resource', 'longtask']
});
// Web Vitals (LCP, FID, CLS)
function measureWebVitals() {
// Largest Contentful Paint
new PerformanceObserver((list) => {
const entries = list.getEntries();
const lcp = entries[entries.length - 1];
console.log('LCP:', lcp.startTime);
}).observe({ type: 'largest-contentful-paint', buffered: true });
// First Input Delay
new PerformanceObserver((list) => {
const fid = list.getEntries()[0];
console.log('FID:', fid.processingStart - fid.startTime);
}).observe({ type: 'first-input', buffered: true });
// Cumulative Layout Shift
let clsValue = 0;
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
clsValue += entry.value;
}
}
console.log('CLS:', clsValue);
}).observe({ type: 'layout-shift', buffered: true });
}Conclusión
Estas 10 Web APIs cubren una amplia gama de necesidades comunes en desarrollo frontend. Conocerlas te permite:
- Reducir dependencias de bibliotecas
- Mejorar performance
- Crear experiencias más ricas
- Escribir código más mantenible
Antes de agregar una biblioteca, verifica si existe una API nativa que resuelva tu problema. Frecuentemente la solución ya está en el navegador.
Si quieres profundizar en JavaScript moderno, recomiendo que veas otro artículo: ECMAScript 2025: Nuevos Recursos de JavaScript donde vas a descubrir las novedades más recientes del lenguaje.

