Rendimiento y listas de imágenes
Cuando se renderiza una lista o cuadrícula de imágenes interactivas OIM (por ejemplo, en un catálogo de productos, una galería de escaparate o un feed), optimizar el rendimiento es fundamental. Instanciar decenas de componentes <oim-player> de forma simultánea puede consumir mucha memoria y bloquear el hilo principal de la UI durante el parseo de los metadatos de los PNG.
Esta guía cubre buenas prácticas y patrones de código para mostrar imágenes interactivas en listas de forma eficiente.
1. Estrategias principales
1.1 Hidratación progresiva (Hover/Click para cargar)
En lugar de renderizar un <oim-player> completo por cada elemento al cargar la página, renderiza un <img> estático que represente la miniatura de vista previa. Cuando el usuario pase el cursor o haga clic sobre la tarjeta, sustituye o promociona el elemento a un <oim-player> totalmente interactivo.
1.2 Lazy loading (basado en viewport)
Usa IntersectionObserver para detectar cuándo una tarjeta entra en el viewport. Carga los metadatos OIM e instancia el custom element solo cuando se hace visible.
1.3 Listas con metadatos preextraídos
En catálogos a gran escala, no fuerces al cliente a extraer los metadatos de los PNG al vuelo en una cuadrícula. En su lugar:
- Ejecuta una tarea en segundo plano en el servidor para extraer los metadatos una única vez.
- Sirve un índice JSON que contenga los elementos y sus metadatos de layout.
- Pasa el objeto de metadatos ya cargado directamente al método
.setMetadata(data)del player, o mediante un property bind, en lugar de que cada player vuelva a descargar y leer los chunks binarios.
2. Ejemplos de implementación
Cuadrícula en JavaScript puro (IntersectionObserver + Hover Upgrade)
Este ejemplo mantiene ligeras las celdas de la lista usando un wrapper <img> estándar y las promociona a <oim-player> solo cuando entran en el viewport y el usuario pasa el cursor sobre ellas.
<div class="oim-grid">
<!-- Card template -->
<div class="oim-card" data-src="/images/product1.oim.png" data-lang="en">
<div class="player-container">
<!-- Static fallback thumbnail -->
<img src="/images/product1-thumb.png" alt="Product 1" class="thumbnail" />
</div>
<h3>Product Title</h3>
</div>
</div>const cards = document.querySelectorAll('.oim-card');
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const card = entry.target;
setupInteractiveTriggers(card);
observer.unobserve(card); // Only setup triggers once
}
});
}, { rootMargin: '100px' });
cards.forEach(card => observer.observe(card));
function setupInteractiveTriggers(card) {
const container = card.querySelector('.player-container');
const src = card.dataset.src;
const lang = card.dataset.lang;
// Upgrade to player on mouse hover (or click)
card.addEventListener('mouseenter', () => {
if (container.querySelector('oim-player')) return; // Already upgraded
const player = document.createElement('oim-player');
player.setAttribute('src', src);
player.setAttribute('lang', lang);
player.className = 'interactive-player';
// Swap thumbnail with active player
const thumb = container.querySelector('.thumbnail');
if (thumb) thumb.style.display = 'none';
container.appendChild(player);
}, { once: true }); // Trigger once
}Integración de lista en React con lazy loading
Así puedes implementar una tarjeta de imagen interactiva de alto rendimiento en React:
import React, { useState, useRef, useEffect } from 'react';
interface OimCardProps {
id: string;
thumbnailUrl: string;
oimUrl: string;
title: string;
}
export function OimCard({ id, thumbnailUrl, oimUrl, title }: OimCardProps) {
const [isNearScreen, setIsNearScreen] = useState(false);
const [isInteractive, setIsInteractive] = useState(false);
const cardRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsNearScreen(true);
observer.disconnect();
}
},
{ rootMargin: '100px' }
);
if (cardRef.current) {
observer.observe(cardRef.current);
}
return () => observer.disconnect();
}, []);
return (
<div
ref={cardRef}
className="oim-grid-card border border-neutral-800 rounded-xl overflow-hidden bg-neutral-900"
onMouseEnter={() => isNearScreen && setIsInteractive(true)}
onTouchStart={() => isNearScreen && setIsInteractive(true)}
>
<div className="relative aspect-video flex items-center justify-center bg-black">
{!isInteractive ? (
<img
src={thumbnailUrl}
alt={title}
className="w-full h-full object-cover transition-opacity duration-300"
/>
) : (
<oim-player
src={oimUrl}
lang="en"
class="w-full h-full"
id={`player-${id}`}
/>
)}
</div>
<div className="p-4">
<h4 className="text-sm font-semibold text-white">{title}</h4>
</div>
</div>
);
}3. Checklist de rendimiento
| Métrica | Objetivo | Solución |
|---|---|---|
| Tamaño inicial del bundle | <15KB | Importa dinámicamente el módulo del player solo cuando sea necesario. |
| Máximo de players concurrentes | ≤ 3 activos | Hidrata solo el elemento enfocado; destruye o desactiva los players que salgan del viewport. |
| Overhead de parseo | 0ms en el hilo de UI | Carga los metadatos JSON por adelantado si vas a renderizar muchos elementos. |
| Miniaturas | WebP/AVIF optimizados | Muestra siempre vistas previas estáticas muy comprimidas para la imagen horneada (capa −1) antes de cargar el player. |