Jonathan Alfonso
Juan Castro
ProfesorUffff. Esta propuesta me gusta muchísimo más que la mía. Muy bien ahí. :+1:
Jose Castillo
Cuando una API devuelve pósters rotos o vacíos, tu interfaz queda con huecos blancos y el atributo alt como única pista. La solución pasa por detectar el evento error de la imagen en JavaScript y reemplazar el src por una imagen de fallback, además de corregir un detalle de CSS que afecta al lazy loading.
Cómo detecto cuándo falla una imagen en JavaScript
El truco está en escuchar el evento error directamente sobre el elemento <img>. Cuando la URL del póster devuelve 404 o la propiedad llega como null desde la API de Movie DB, el navegador dispara ese evento y ahí es donde tú entras a actuar [02:10].
Dentro de la función que crea cada tarjeta de película, agregas un addEventListener sobre movieImage escuchando el evento error. Cuando ese error ocurre, llamas a setAttribute('src', ...) con la URL de tu imagen por defecto. Así el navegador reemplaza el póster roto sin que el usuario vea un espacio vacío.
¿Qué evento detecta cuando una imagen no carga en HTML? El evento
errordel elemento<img>. Se dispara cuando el recurso devuelve 404, está corrupto o el src es inválido, y permite ejecutar lógica de fallback.
Por qué el orden del setAttribute importa
Un detalle fácil de pasar por alto: el addEventListener del error debe ir después del setAttribute original que asigna el póster real. Si lo pones antes, corres el riesgo de que el orden de ejecución sobrescriba tu lógica de reemplazo o genere comportamientos confusos. Mantén el listener debajo y tu código queda más predecible [04:30].
Cómo agrego una imagen de fallback cuando el póster no existe
La imagen por defecto puede ser cualquier recurso accesible por URL. En la clase se usa una ilustración tipo astronauta de Platzi que dice "Algo falló", pero lo ideal es que crees tu propio asset o uses uno libre de derechos.
El flujo queda así:
src real de la API con setAttribute.addEventListener('error', ...) sobre la misma imagen.setAttribute('src', urlDeFallback).Al recargar y buscar películas como Vengadores o títulos con pocos resultados como Holly, las cartas que antes mostraban el alt sobre fondo blanco ahora muestran tu ilustración de error. La interfaz se siente completa.
Por qué el lazy loading carga todas las imágenes a la vez
Aquí viene lo interesante. Después de arreglar los pósters faltantes, aparece otro síntoma raro: en la sección de películas por categoría, todas las imágenes se cargan al mismo tiempo aunque tengas el lazy loading activo. ¿Cómo así?
El problema no es JavaScript, es CSS. Cuando una imagen no ha cargado, su height por defecto es prácticamente cero, porque el navegador todavía no sabe sus dimensiones. Si todas las tarjetas miden cero de alto, todas caben en el viewport al mismo tiempo, y el IntersectionObserver reporta isIntersecting: true para cada una. Resultado: tu lazy loader las dispara todas en bloque [09:15].
¿Qué es isIntersecting en el IntersectionObserver? Es una propiedad booleana que indica si el elemento observado está dentro del viewport o del root definido. Cuando es
true, tu callback se ejecuta y puedes cargar el recurso.
Cómo lo compruebas en consola
Una forma rápida de verificarlo es comentar la línea que asigna el src real dentro del lazy loader y dejar solo un console.log(entry.target) para los elementos con isIntersecting en true. Al recargar, ves en la consola que todas las películas se reportan como intersectando, confirmando la hipótesis del alto cero.
Cómo soluciono el lazy loading con min-height en CSS
La cura es darle a cada .movie-img un min-height que reserve espacio aunque la imagen aún no haya cargado. En la clase se prueban varios valores y se aterriza en 175 píxeles como un mínimo razonable que funciona para distintos tamaños de pantalla.
css .movie-img { min-height: 175px; }
Con ese estilo aplicado, cada contenedor ocupa altura desde el primer render. El navegador ahora sí puede calcular qué tarjetas están dentro del viewport y cuáles no, y el IntersectionObserver reporta isIntersecting: true solo para las primeras seis o cuatro visibles, según el ancho del navegador.
Después de aplicar el min-height, vuelves a activar la línea del setAttribute('src', url) dentro del lazy loader y desactivas el console.log. Al recargar con caché limpio y revisar la pestaña Network filtrando por imágenes, solo cargan las pocas tarjetas visibles, y el resto se va sumando conforme haces scroll.
Qué aprendiste sobre optimización de imágenes en el front-end
Este módulo cierra con dos arreglos que conviven en el mismo flujo:
addEventListener('error') sobre la imagen y reemplazas el src cuando la API no entrega póster.IntersectionObserver pueda discriminar qué está realmente en pantalla.No puedes hacer que las imágenes carguen más rápido, pero sí controlar cuándo y cómo se cargan según la interacción del usuario. ¿Tú qué imagen de fallback usarías en tu proyecto? Cuéntalo en los comentarios.
Jonathan Alfonso
Juan Castro
ProfesorUffff. Esta propuesta me gusta muchísimo más que la mía. Muy bien ahí. :+1:
Jose Castillo
Victor Andres Barilin
No soy muy bueno con la creatividad, pero esto fue lo que hice! jajaja
Jonathan Guidi
Yo en vez utilizar un "addEventListener" con el evento "Error", utilice una condicional if(movie.poster_path === null){...} nose si fue la solucion mas optima, pero me funciono!
Carlos Rodríguez
Las imágenes de tiipo place holder también ayudan mucho:
https://via.placeholder.com/300x450/5c218a/ffffff?text=${title}
.
.
Como la api nos devuelve un null al no tener imagen, podemos condicionar el src dentro e la función de agregar imagen:
const src = posterPath ? imgCorrecta : imgNoEncontrada
. Cambios en el commiit: Click aquí . . Referencia:
David Prado
Para los que aun no se les aplique el LazyLoading en la pagina de Categorías. Revisen si agregando min-height en la categoría .genericList-container .movie-img se soluciona .
Ameth Ordoñez Erazo
Juan José Verón
@amethgabriel acuerdate de pasarle el true a getMoviesByCategory(id)
Ameth Ordoñez Erazo
Yo no vi necesario cargar una imagen, solo cree una nueva clase en CSS para mostrar cuando no cargue la imagen: Queda así:
Primero que todo agrego en un dataset de la imagen, llamado data-img, el valor del path de la imagen, que si no existe, viene "null". Entonces en el observador agrego esta validación:
Básicamente lo que hago es agregarle una clase nueva si la imagen no existe.
La clase CSS es esta:
En el pseudoelemento ::before de la clase indicamos los estilos a mostrar cuando la imagen no cargue, además aprovechamos que el nombre de la pelicula está en el atributo "alt" y se muestra solo, sin hacer nada más.
Lisbeth Quintero
Le agregué una imagen y un span con el título de la película.
movieImg.addEventListener('error', ()=>{ movieImg.setAttribute('src', 'https://img.icons8.com/arcade/512/popcorn.png') let movieTitle = document.createElement('span') movieTitle.textContent = movie.title console.log(movieTitle) movieContainer.insertAdjacentElement("afterbegin", movieTitle) movieTitle.style.fontWeight = 'bold' movieContainer.style.display = 'flex' movieContainer.style.flexDirection = 'column' movieContainer.style.alignItems = 'center' })
Gilbert Ardila
lo que yo hago es verificar si la propiedad poster_path esta vacía, si es así no muestro la película, en la busqueda de cada pelicula verifico lo mismo pero si el backdrop_path está vacío muestro el poster_path, si los dos están vacíos muestro otra imagen por defecto https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRb-j5czZKCBg2Og1We4HoVkt-YBX8dwiz_kQ&usqp=CAU
Marcelo Jorge Bellizia
Le agregue el titulo y unos estilos para que quede con mejor accesibilidad.
css:
.movie-noLoad{ display: flex; align-items: center; justify-content: center; flex-direction: column; height: 237px; width: 47.5%; }
Sergio Javier Lopez Olivera
Yo lo solucioné detectando cuando era 'null' la direccion de la imagen al momento de cargar la película: . . .
Rigoberto Maldonado
Juan Fernando Yepes Muñoz
Le puse esta imagen para que se viera mas divertido que el icono feo del navegador. Espero haber usado correctamente mi inglés, si no lo usé bien pido perdón
. Este es el código que usé, ahora iré a ver como lo hizo el profe :)
movieImg.addEventListener('error', () => { movieContainer.classList.add('error-img-msg'); movieContainer.textContent= ''; const errorImg = document.createElement('img'); errorImg.classList.add('error-img'); errorImg.src = './assets/undraw_location_search_re_ttoj.svg' const errorText = document.createElement('p') errorText.innerText = `We searched all the galaxies, but we couldn't find the image from this movie. This is the name of the movie: ${movie.original_title}.` movieContainer.append(errorImg, errorText); })
Roger Fernandes
Jorge Garcia
mi solucion
Juan Castro
ProfesorMe gusta :clap:
Lautaro Els
esa es mi solucion
movie.poster_path !== null ? `https://image.tmdb.org/t/p/w300${movie.poster_path}` : `https://as01.epimg.net/meristation/imagenes/2021/04/26/reportajes/1619438192_264857_1619438392_sumario_normal.jpg` )
no se si esta bien pero me funciono
Daniel Alberto Méndez Pérez
hice algo sencillo
movieImg.addEventListener('error', () => { movieImg.style.paddingTop = "100px"; movieImg.style.fontSize = "1.5rem"; });
.movie-container{ cursor: pointer; display: flex; text-align: center; }
mars Dev
Dario Paladines
Problema resuelto. Simplemente le agregue un condicional cuando esté creando la película, si es que el hash de la pelicula es null entonces crea una img
if ((movie.poster_path)== null){ const movieImg = document.createElement("img"); movieImg.classList.add("imagen"); movieImg.setAttribute("alt", movie.title); movieImg.setAttribute("src", "https://i.ibb.co/dKGRLmx/fposter-small-wall-texture-product-750x1000.jpg"); container.appendChild(movieImg); }else{ const movieImg = document.createElement("img"); movieImg.classList.add("imagen"); movieImg.setAttribute("alt", movie.title); movieImg.setAttribute("src", "https://image.tmdb.org/t/p/w300" + movie.poster_path); container.appendChild(movieImg); container.addEventListener("click", ()=>{ location.hash = "#movie=" + movie.id }) } })
Gabriel Hernández Valdez
A mi me pasaba ese error de CSS en un proyecto react con los intersection observer, cargaba todo y no sabia porque, lo encontre debbugeando y mi solución fue colocar un skeleton y que se observara ese elemento para hacer la carga de mas datos.
Haciendo asi que el skeleton se fuera recorriendo con cada carga nueva de datos haciendo que el observe diera: false.
Me sirvio asi y se ve bien :D
Yo creé una imagen de fondo para cada género de película con el color identificatorio, y le agregué un span con el título en el centro para que se puedan identificar.
movieImg.addEventListener('error', () => { movieImg.setAttribute('src', `images/bg-default-${movie.genre_ids[0]}.png`) const movieTitleText = document.createTextNode(movieImg.getAttribute('alt')); const movieTitle = document.createElement('span'); movieContainer.appendChild(movieTitle); movieTitle.appendChild(movieTitleText); })
un shorthand para ese evento es:
movieImg.onerror = () =>{ movieImg.setAttribute('src', `${imgErrorUrl}`); movieImg.setAttribute('alt'), 'url-broke-img'; }
Nada, a mi no, y asi lo tengo:
.genericList-container .movie-img { height: 225px; min-height: 225px; max-height: 225px; width: 150px; min-width: 150px; max-width: 150px; }
Esta es mi solución, agregar una imagen de 1x1 px en base64 y luego agregar el nombre de la pelicula en un span:
movieImg.addEventListener('error', () => { movieImg.setAttribute('src', `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOMUez6DwAD8AIIOhah/wAAAABJRU5ErkJggg==`) const movieImgTitleSpan = document.createElement('span'); const movieImgTitle = document.createTextNode(movie.title); movieContainer.appendChild(movieImgTitleSpan); movieImgTitleSpan.appendChild(movieImgTitle); })
hay que agregar un poco de css:
.movie-container { cursor: pointer; position: relative; } .movie-container span { color: var(--purple-light-2); left: 0; margin: auto; padding: 10px; position: absolute; right: 0; text-align: center; top: 50%; transform: translate(0, -50%); width: 100%; }
Resultado búsqueda "demo":
Hola buenas tardes compañeros esta es mi solución :D
movies.map( movie => { //Creo una variable donde guardo toda la ruta donde le asigno el poster path de cada movie let posterImage = `https://image.tmdb.org/t/p/w300/${movie.poster_path}`; console.log("Pelicula creada!"); const $movieContainer = d.createElement('div'); $movieContainer.classList.add('movie-container'); const $img = d.createElement('img'); $img.classList.add('movie-img'); //Aqui sucede la magia //Indagando un poco en la pagina de placeholder se me ocurrio //ya que al no encontrarse con una image me devuelve null //con el metodo include verifico si existe "null" y si existe le agrego una placeholderImage sino le agrego el poster path $img.dataset.image = (!posterImage.includes('null')) ? posterImage : `https://via.placeholder.com/150/FFFF00/000000?text=${movie.title}`; $img.dataset.id = movie.id; $img.alt = `${movie.title}`; $movieContainer.appendChild($img); $fragment.appendChild($movieContainer); //Aquí está! addImage($img); });