Skip to content

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:

  1. Ejecuta una tarea en segundo plano en el servidor para extraer los metadatos una única vez.
  2. Sirve un índice JSON que contenga los elementos y sus metadatos de layout.
  3. 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.

html
<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>
javascript
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:

tsx
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étricaObjetivoSolución
Tamaño inicial del bundle<15KBImporta dinámicamente el módulo del player solo cuando sea necesario.
Máximo de players concurrentes≤ 3 activosHidrata solo el elemento enfocado; destruye o desactiva los players que salgan del viewport.
Overhead de parseo0ms en el hilo de UICarga los metadatos JSON por adelantado si vas a renderizar muchos elementos.
MiniaturasWebP/AVIF optimizadosMuestra siempre vistas previas estáticas muy comprimidas para la imagen horneada (capa −1) antes de cargar el player.