No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Infinite Scrolling: evento de scroll

11/20
Recursos

Aportes 39

Preguntas 7

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

Passive lo que hace es evitar el llamado de preventDefault() en el caso de que este existiese en la función llamada por el Listener. En los navegadores que usa la gente normal el valor por defecto es false por lo que no se aplica, pero en el caso de Safari e Internet Explorer el valor por defecto es true. Por lo que supongo que es recomendable ponerle un valor para que el código se ejecute igual en todos los navegadores.

esta clase la vi mas veces de lo que me gustaría admitir

No habia entendido como funcionan y se relacionan los atributos clientHeight, scrollTop y scrollHeight. Leyendo los entendi asi que este es un resumen de como funcionan y se relacionan:
.
1- document.documentElement.clientHeight: Devuelve la altura VISIBLE del elemento documentElement en pixeles incluyendo el padding del elemento pero no el border, ni el margin del elemento ni la barra de dezplazamiento horizontal del navegador.
.
Como clientHeight NO devuelve la altura total de un elemento sino la altura visible del elemento, entonces si el tamaño de pantalla del dispositivo se reduce o si el tamaño de la ventana del navegador se reduce entonces la altura visible del elemento tambien se reducira por lo tanto el valor de .clientHeight se reducira y visceversa.

.
2- document.documentElement.scrollTop: Devuelve el numero de pixeles desplazados al hacer scroll vertical en el elemento documentElement.
.
TENER EN CUENTA QUE SI EL TAMAÑO DE PANTALLA DEL DISPOSITIVO SE REDUCE O SI EL TAMAÑO DE LA VENTANA DEL NAVEGADOR SE REDUCE ENTONCES EL VALOR DE clientHeight SE REDUCIRA Y EL VALOR MAXIMO DE scrollTop AUMENTARA YA QUE AL REDUCIRSE LA VENTANA TOCA HACER MAS SCROLL PARA RECORRER EL ELEMENTO documentElement. SI EL TAMAÑO DE LA VENTANA DEL NAVEGADOR AUMENTA ENTONCES OCURRE LO CONTRARIO
.

3- document.documentElement.scrollHeight: Devuelve la altura total del elemento documentElement en pixeles incluyendo el padding del elemento pero no el border, ni el margin del elemento ni la barra de dezplazamiento horizontal del navegador.
.
Si analizamos nos damos cuenta que la altura total del elemento documentElement es igual a la altura visible de dicho elemento mas el maximo de pixeles desplazados al hacer scroll vertical en dicho elemento, osea, documentElement.scrollHeight es igual a documentElement.clientHeight + documentElement.scrollTop cuando el valor de scrollTop es el maximo

Por lo que entendí al leer la documentación, el tercer parámetro que se le envía al EventListener es opcional y especifica las condiciones en la que se ejecuta el mismo.

addEventListener(type, listener, options);

Existen varias opciones, como use, signal, passive y capture, este último es el que se usa por defecto al colocar un valor booleano, como es el caso. Lo que determina capture es el orden en el que se ejecutan los eventos cuando existen varios que responden a la misma acción. Al estar en false el orden de ejecución es desde el padre del evento(por ejemplo un botón) a la raiz del árbol (DOM), y en el caso de ser true el orden se invierte, por lo que se priorizan los elementos más cercanos al DOM en orden jerárquico. Primero se ejecutan los eventos que tengan capture true(del más grande al más chico) y luego los false (del más chico al más grande).
.
Esta imágen me ayudo a entenderlo.

Capture phase = true
Bubbling phase = false (por defecto)
Target Phase = elemento “mas pequeño” que escucha al evento

documentación EventTarget.addEventListener()

A esto se refiere el tercer parámetro del EventListener: Bubbling y Capturing
En el siguiente link hay una explicación detalla con ejemplos interactivos LINK

Mi solución al reto del botón fue agregarle un “genericSection.removeChild(‘Element’)” al evento del botón en la función de TrendingMovies y “Paginación

Me costo entender como implementar Infine Scroll en la navegación, pero ahí les va mi explicación:

  1. declaramos la variable page = 1 e inicializamos infiteScroll como undefined.
let page = 1;
let infiniteScroll;
  1. cuando navegemos por la aplicación, si existe algún valor a infiteScroll será removido.
function navigator() {
  console.log({ location });

  if (infiniteScroll) {
    window.removeEventListener('scroll', infiniteScroll, { passive: false });
    infiniteScroll = undefined;
  }

  if (location.hash.startsWith('#trends')) {
    trendsPage();
  1. Cuando ingresemos a la pagina de trendsPage, infiniteScroll tendrá el valor de getPaginatedTrendingMovies. (dependiendo de hacia donde navegues el valor de infinite Scroll va a cambiar para cargar el api de su pagina)
function trendsPage() {
  infiniteScroll = getPaginatedTrendingMovies;
}
  1. termina de cargar la función navigation y ejecuta window.addEventListener(‘scroll’, infiniteScroll,{ passive: false });
function navigator() {
  console.log({ location });

  if (infiniteScroll) {
    window.removeEventListener('scroll', infiniteScroll, { passive: false });
    infiniteScroll = undefined;
  }
  
  if (location.hash.startsWith('#trends')) {
    trendsPage();

  if (infiniteScroll) {
    window.addEventListener('scroll', infiniteScroll, { passive: false });
  }
}

Así fue como entendí como aplicar infiniteScroll en la navegación. Espero que le haya servido a alguien esta explicación.

Yo solucioné todo el problema del evento scroll usando

    document.onscroll = () => getPaginatedMovies('trending/movie/day')

Por cada página, se llama a getPaginatedMovies con un endpoint diferente. Después se podrían hacer cosas como poner todos los endpoints en variables, llevar a document.onscroll a navigator, etc. pero no me compliqué mucho.

A mi en lugar de hacer tantos cambios para el infiniteScroll, me funcionó simplemente envolverla en una función flecha:

window.addEventListener('scroll',()=>infiniteScroll());

con eso me ahorré la lógica de remover el listener

yo lo hice un poco distinto, espero que no este mal, use otro intersection observer que vea el footer, funciona perfecto, pero a lo mejor juan no lo uso por algun motivo

passive
Un valor booleano que, si es true , indica que la función especificada por el listener nunca llamará a preventDefault() . Si un oyente pasivo llama a preventDefault() , el agente de usuario no hará nada más que generar una advertencia en la consola. Si no se especifica, el valor predeterminado es false , excepto que en navegadores que no sean Safari e Internet Explorer, el valor predeterminado es true para los eventos wheel , rueda del mousewheel , touchstart y touchmove . Consulte Mejorar el rendimiento del desplazamiento con oyentes pasivos para obtener más información.

en mi reto mi app no es para moviles y todas las peliculas cargan dentro de un div con tamaño fijo… estuve pensando como hacer hasta que recorde la herencia, y el div tiene los mismos parametros scrollTop, ScrollHeight (el clientHeight es el tamaño del div que le pones en css), y funciona igual y es mas exacto, y si quieres usar las misma logica dle profe usa height:auto en tu css

  • document.documentElement.scrollTop indica la cantidad de desplazamiento vertical desde la parte superior del documento hasta la posición actual del borde superior de la ventana del navegador.
    .
  • document.documentElement.clientHeight representa la altura del área visible del navegador en ese momento. Esta altura incluye todo el contenido visible en la ventana del navegador, sin incluir barras de desplazamiento u otros elementos de la interfaz del navegador.
    .

Al sumar estas dos propiedades, obtienes la posición vertical del borde inferior de la ventana del navegador en relación con el documento completo. Cuando esta suma es igual al valor de document.documentElement.scrollHeight (la altura total del documento), significa que has alcanzado el final del documento, es decir, el usuario se encuentra en la parte inferior del documento y no hay más contenido por desplazar hacia abajo.

document.documentElement.scrollTop + document.documentElement.clientHeight
yo implementaría el scroll infinito como una proporción de scrollTop + clientHeight sobre scrollHeight. Es decir porejemplo cuando represente un 85% o 90%, ahi disparar la api para la próxima página
Esta clase fue magistral, soprendido con cada segundo, el nivel es muy profesional

Y si como otra posible solucion habriamos agregado un "div " invisible como lastChild sea cual fuere la seccion que estemos, y un intersection observer que si intersecta al div hacemos el llamado de la funcion?

dentro de la función trendsPage() en el archivo de Navigation.js construí esto al final:

        let counter = 1

        window.addEventListener('scroll', () => {

        const { 
            scrollTop,
            clientHeight,
            scrollHeight } = document.documentElement;
        
        const scrollIsBottom = (scrollTop + clientHeight) >= scrollHeight - 15;

            if(scrollIsBottom){
                counter++
                getTrendingMovies(page = 1 + counter)
            } //this way we make sure we're loading new content only when the scrollIsBottom results TRUE and at the same time we're at the bottom of the page
        })

Así cada vez que se haga scroll crearemos esas variables y la validación para llamar a getTrendingMovies con el counter (que es un número que aumentará en 1 cada vez que llamemos esta función). Además cada vez que nos remitamos a esa pagina de tendencias no cargará todo completamente sino que volverá a cargar solamente las 8 películas iniciales

El ‘false’ al final de un addEventListener siginifica que el evento será capturado durante la Bubbling phase.
.
.

¿Qué significa esto de Bubbling phase?

.
.
Cuando le asignamos un evento a un nodo HTML, existen dos fases o etapas a la hora de procesar eventos.
La fase de Capturing y la fase de Bubbling. Es decir, primero el evento viaja desde todo el documento html pasando por todos los nodos hasta el nodo que queremos, luego desde ese nodo el evento se ejecuta con todos los nodos padres.
La captura viaja de “afuera” hacia “adentro”, y luego la burbuja va de “adentro” hacia “afuera”.
.
.

.
.
El evento va de arriba hacia abajo (Capturing down) y luego de abajo hacia arriba (Bubbling up).
El setear el useCapture como TRUE o FALSE, lo que le dice al evento es en cuál fase quieres que sea ejecutado.
.
useCapture = true
.
El handler estará seteado en la fase Capturing. El evento llegará hasta el nodo antes que a sus hijos. Se ejecutará primero al tocar ese nodo y luego en la fase de burbuja se ejecutarán los eventos de los hijos a los padres.
.
useCapture = false
.
El handler estará seteado en la fase de Bubbling. El evento llegará hasta el nodo después que sus hijos. Es decir, el evento irá hasta el fondo, rebotará en el último hijo y regresará hasta el nodo que señalamos en la fase de bubbling, en el camino de vuelta.
.
Eso quiere decir que si haces esto:

child.addEventListener("click", second);
parent.addEventListener("click", first, true);

.
Cuando le des click a ‘child’ se ejecutará el método ‘first’ antes que ‘second’, porque está useCapture como false (que es su forma por defecto). Es decir, primero el evento se detendrá en el “parent” y luego irá hasta el “child”, rebota el evento y ejecutará ‘second’.

Para mi fortuna ya habia usado el clientHeight y el scrollHeight para mis carruseles de mi pagina de incio (hice un indicador de progreso de scroll), asi que al nombrarlo entendi de lo que hablaban… sin embargo me parece que se complico un poco todo XD pero de nuevo, para mi fortuna yo estoy haciendo mi pagina diferente, solo tengo en cuenta lo que hace el profe pero a estas alturas del proyecto no puedo aplicar muchas de las cosas tal cual el las propone, lo que en cierta forma significa un reto para mi cada nuevo “feature” y eso me agrada.

Mucha suerte compañeros y animo… les dejo un screenshoot de mi pagina de incio :3

Para el infinity scroll yo use otro intersection observer en la última película cargada, y de esta forma volvia a ejecutar la función getTrendingMovies() aunmentando page y luego hacia la validaci’on:

if(lastMovieInterception){
    observadorMoreMovies.unobserve(lastMovieInterception)
  }

Aquí la función para detectar si el usuario alcanzó el límite de scrolling inferior:

const scrollBottomReached = () => {
    const {scrollTop, scrollHeight, clientHeight } = document.documentElement;
    return (scrollTop + clientHeight) >= (scrollHeight - 15);
}

Que se puede usar elegantemente:

window.addEventListener('scroll', () => {
	if(scrollBottomReached()){
		// Aquí tu lógica
	}
})

Hola, muchas gracias por los aportes anteriores. Excelente clase, he tenido que verla algunas veces. Comparto mi código, con el fin de saber si tiene alguna contra hacerlo de este modo, los resultados en la app son los mismos.

function getTrendingMovies() {    
    createMovies(ENDPOINT_TRENDING, genericSection, {}, { lazyLoad: true, clean: true,});    
};

window.addEventListener('scroll', () => {
    const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
    const scrollIsBottom = ( scrollTop + clientHeight) >= (scrollHeight - 1);
    if (scrollIsBottom) {
        createMovies(ENDPOINT_TRENDING, genericSection, {
            params: {
                page: page++,
            },
        }, {
                lazyLoad: true,
                clean: false,
            },
        );
    };
}); 

Amigos por favor tener en cuenta que en esta parte del codigo:

const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
  const scrollIsBottom = (scrollTop + clientHeight) >= (scrollHeight - 15);

se suma el scrollTop y el clientHeight, yo los tenia restando y dure un buen rato tratando de ver cual era el error, era un signo. Atencion a los pequenios detalles

Yo lo hice con React

import InfiniteSroll from './InfiniteSroll'

function MyComponent() {
	 const onInfiniteScroll = () => console.log('onInfiniteScroll')
	return <InfiniteScroll onInfiniteScroll={onInfiniteScroll}>
		// ... algo como una lista
	</InfiniteScroll>
}
// InfiniteScroll.js
import PropTypes from 'prop-types'
import { useEffect } from 'react'

const InfiniteScroll = ({ children, onInfiniteScroll }) => {
  useEffect(function setupListener() {
    function infiniteScroll() {
      const { scrollTop, scrollHeight, clientHeight } = document.documentElement
      const scrollIsBottom = scrollTop + clientHeight >= scrollHeight - 10
      if (scrollIsBottom) {
        onInfiniteScroll()
      }
    }
    window.addEventListener('scroll', infiniteScroll)

    return function cleanupListener() {
      window.removeEventListener('scroll', infiniteScroll)
    }
  })

  return (
    <div id="wrapper">
      <div id="content">{children}</div>
      <div id="last" />
    </div>
  )
}

InfiniteScroll.propTypes = {
  children: PropTypes.node.isRequired,
  onInfiniteScroll: PropTypes.func.isRequired,
}

export default InfiniteScroll

Mi propuesta propuesta es reutilizar las funciones de petiiciones de datos agregando un parámetro por defecto de página 1 cuando se ejecuta la primera vez, y luego con el evento scroll allí sí colocamos la página que es el page de la variable global. para esto necesitaremos una función para determinar si el scroll está en el umbral

const scrollIsOnThreshold = () => {
    const { scrollTop, clientHeight, scrollHeight } = document.documentElement;
    const scrollDiff = scrollHeight - (scrollTop + clientHeight);
    const scrollIsBottom = scrollDiff <= 30;
    if (scrollIsBottom) page += 1;
    return scrollIsBottom;
};

Si está en el umbral para hacer el trigger entonces aumentamos de página y retornamos el resultado (true), luego creamos el callback infiniteScroll basándonos en esta función y la función promesa reutilizada.

const trendsPage = () => {
    	.
	.
	.
    // Obteniendo los datos a renderizar
    getData.getTrendingMovies();
    // Scroll infinito
    infiniteScroll = () => {
        if (scrollIsOnThreshold()) getTrendingMovies(page);
    };
};

.
.
Mi propuesta en el reposiitorio, commit actual : Click aquíi

Creo que estos datos pueden ayudar para comprender la clase \[Apuntes...]\(https://github.com/aleroses/Platzi/blob/master/DW/2-intermedio/014.api-rest-js/3.api-rest-js-performance/3.api-rest-js-performance.md#11-infinite-scrolling-evento-de-scroll)
\### `addEventListener` y `removeEventListener`: Parámetros y Comportamiento \#### Introducción a los Parámetros En JavaScript, `addEventListener` y `removeEventListener` se utilizan para gestionar eventos en los elementos del DOM. Ambos métodos aceptan un tercer parámetro que puede ser un booleano (`true` o `false`) o un objeto de opciones que especifica detalles adicionales sobre cómo se debe manejar el evento. \#### Booleano como Tercer Parámetro El tercer parámetro puede ser: \- \*\*`true`\*\*: Indica la fase de captura. En esta fase, el evento se captura en el orden ascendente desde el elemento raíz del DOM hasta el elemento objetivo. \- \*\*`false`\*\*: Indica la fase de burbuja, que es la fase por defecto. El evento se maneja en el orden descendente desde el elemento objetivo hacia el elemento raíz del DOM. \#### Objeto de Opciones como Tercer Parámetro El tercer parámetro también puede ser un objeto que permite una configuración más detallada: \- \*\*`capture`\*\* (booleano): Equivalente a usar `true` o `false` directamente. \- \*\*`once`\*\* (booleano): Si es `true`, el evento se ejecuta solo una vez y luego se elimina automáticamente. \- \*\*`passive`\*\* (booleano): Si es `true`, indica que el manejador de eventos no llamará a `preventDefault`, mejorando el rendimiento en algunos casos como el desplazamiento (scrolling). \#### Ejemplo de Uso ```javascript // Definimos una función de manejo de eventos function infiniteScroll() { console.log('Scrolled'); } // Añadimos el evento usando un objeto de opciones window.addEventListener("scroll", infiniteScroll, { capture: true, once: false, passive: true }); // Más tarde, removemos el evento usando los mismos parámetros window.removeEventListener("scroll", infiniteScroll, { capture: true, once: false, passive: true }); ``` \#### Fase de Captura y Fase de Burbuja \- \*\*Fase de captura (true)\*\*: El evento se propaga desde el elemento raíz del DOM hacia el elemento objetivo. Este orden ascendente permite capturar eventos antes de que lleguen al objetivo. \- \*\*Fase de burbuja (false)\*\*: El evento se maneja después de que alcanza su objetivo, propagándose de regreso hacia el elemento raíz del DOM en un orden descendente. \### `preventDefault` y el Parámetro `passive` \#### `preventDefault` El método `preventDefault` se usa dentro de los manejadores de eventos para prevenir el comportamiento predeterminado del navegador asociado con el evento, como la navegación de un enlace o el envío de un formulario. ```javascript document.getElementById('myLink').addEventListener('click', function(event) { event.preventDefault(); // Evita que el enlace navegue console.log('Enlace clickeado, pero navegación evitada.'); }); ``` \#### Parámetro `passive` El parámetro `passive` en `addEventListener` es una optimización de rendimiento. Indica al navegador que el manejador de eventos no llamará a `preventDefault`, permitiendo al navegador optimizar el rendimiento en algunos casos, como el desplazamiento (scrolling). \- \*\*`passive: true`\*\*: No se puede usar `preventDefault` en el manejador de eventos, lo que permite al navegador proceder con el comportamiento predeterminado sin retrasos. \- \*\*`passive: false`\*\*: Se puede usar `preventDefault`, pero puede resultar en un rendimiento menos óptimo en ciertos casos. \#### Ejemplo de `passive` ```javascript window.addEventListener('scroll', function(event) { console.log('Scrolled'); }, { passive: true }); // Intentar llamar a preventDefault lanzará un error: // window.addEventListener('scroll', function(event) { // event.preventDefault(); // Esto lanzará un error // }, { passive: true }); ``` ```javascript window.addEventListener('scroll', function(event) { event.preventDefault(); // Esto es permitido console.log('Scrolled and default behavior prevented'); }, { passive: false }); ``` \### Resumen \- \*\*`addEventListener` y `removeEventListener`\*\*: El tercer parámetro puede ser un booleano para especificar la fase de captura (`true`) o burbuja (`false`), o un objeto de opciones para mayor control. \- \*\*`preventDefault`\*\*: Método para evitar el comportamiento predeterminado del navegador. Se relaciona con `passive`, ya que este último indica si `preventDefault` puede ser usado. \- \*\*`passive`\*\*: Mejora el rendimiento al informar al navegador que `preventDefault` no será llamado. Utilizar `passive: true` es recomendado para eventos de alta frecuencia como el desplazamiento cuando no se necesita prevenir el comportamiento predeterminado.
Quiero saber que opinan de mi solución, en la clase anterior decidi no crear una función para las paginas, en vez de eso en la misma función validar la página, algo asi. ![](https://static.platzi.com/media/user_upload/image-0019f5c0-9ada-4e01-a7b2-e4dd342664f9.jpg) Por tal motivo la solución del profe no me servía, así que decidí hacer lo siguiente, en el archivo de navigatión agregue el mismo evento de escucha, con la excepción de que en el mismo evento realizo la validación del scroll, y valido que la variable infityScroll no sea undefined: ![](https://static.platzi.com/media/user_upload/image-85730362-7241-4bc5-b9db-41a105ff59a5.jpg) Lo que hago es que en la función de navigation dejo undefined la variable infityScroll cuando recien entra ![](https://static.platzi.com/media/user_upload/image-04414926-2660-47c4-9ee3-b39f38919bbd.jpg) Y después cada "URL" se encarga de agregarle la función que va a utilizar: ![](https://static.platzi.com/media/user_upload/image-770ef1aa-536b-4620-a0b2-e21501128d22.jpg)
Quiero saber que opinan de mi solución, en la clase anterior decidi no crear una función para las paginas, en vez de eso en la misma función validar la página, algo asi. ```js async function getPopularMovies(page=1){ const {data} = await api(`trending/movie/day`,{ params:{ page: page } }); const movies = data.results; createMovies( movies, genericSection, { lazyload:true, clean: page == 1 }); console.log({data, movies}); } ```Por tal motivo la solución del profe no me servía, así que decidí hacer lo siguiente, en el archivo de navigatión agregue el mismo evento de escucha, con la excepción de que en el mismo evento realizo la validación del scroll, y valido que la variable infityScroll no sea undefined, lo que hago es que en la función de navigation dejo undefined la variable infityScroll cuando recien entra ```js function navigation(){ console.log({location}) if(infityScroll){ infityScroll=undefined; } if(location.hash.startsWith('#trends')){ trendsPage(); }else if(location.hash.startsWith('#search=')){ searchPage(); }else if(location.hash.startsWith('#movie=')){ moviePage(); }else if(location.hash.startsWith('#category=')){ categoryPage(); }else{ homePage(); } document.body.scrolltop = 0; document.documentElement.scrolltop = 0; } ```
No se por que, restandole los 15 pixeles, cada vez que se ejecutaba la función para cargar mas películas en el scroll, cargaban de golpe 5 páginas, lo comprobé imprimiendo en consola la página cada vez que se llamara a la función getPaginatedTrendingMovies.
En mi caso utilice una única función para implementar el infinite scroll en cualquier pagina que lo necesite, implementando clousures de la siguiente manera: ```js function getNextPage( url, parent, { params = { with_genres: null, query: null, } } = {} ) { let page = 1 return async function getInfiniteScroll() { if(loading) return; const {scrollHeight, clientHeight, scrollTop} = document.documentElement; const srcollIsBottom = (scrollTop + clientHeight) >= (scrollHeight - 15) const {with_genres, query} = params if (srcollIsBottom) { loading = true; page++; try { const { data } = await api(url, { params: { page, with_genres, query, } }) const movies = data.results; console.log(data); createMoviesContainer(movies, parent, {clean: false}); } catch (error) { console.error(error) } finally { loading = false } } } } ```Para esta función utilice una variable auxiliar que es `loading` que arranca en false cuando es asignada al principio de `main.js` para hacer validaciones y que la función no se ejecute varias veces, ya que tuve ese problema, cuando llegaba al final del scroll traía más de una pagina adicional, para que se ejecutara la función asincora en cada pagina requeríaasync function getTrendingMovies() {    const {data, status} = await api('/trending/movie/day');    const movies = data.results;     createMoviesContainer(movies, genericSection);            const clousure = getNextPage('/trending/movie/day', genericSection)    scrollInfinite = clousure    } lo hice de la siguiente forma: ```js async function getTrendingMovies() { const {data, status} = await api('/trending/movie/day'); const movies = data.results; createMoviesContainer(movies, genericSection); const clousure = getNextPage('/trending/movie/day', genericSection) scrollInfinite = clousure } ```En este caso se crea una instancia del clousure en donde se le envía los argumento necesarios y después con otra variable auxiliar `scrollInfinite` se almacena la respuesta y el `addEventListener` escucha esa variable auxiliar que se reasigna cada vez que se ejecuta una petición a la API, en caso de requerir otro parámetro, como en las categorías se haría así: ```js async function getMoviesByCategory(categoryId) { const {data, status} = await api('/discover/movie', { params: { with_genres: categoryId, }, }); const movies = data.results; createMoviesContainer(movies, genericSection); const clousure = getNextPage('/discover/movie', genericSection, { params: { with_genres: categoryId, } }) scrollInfinite = clousure } ```Por ultimo el `addEventListener` lo ejecuto al final de la función `navigator` : ```js function navigator() { scrollTo(top) if (location.hash.startsWith('#trends')) { trendsPage() } else if (location.hash.startsWith('#search=')) { searchPage() } else if (location.hash.startsWith('#movie=')) { movieDetailsPage() }else if (location.hash.startsWith('#category=')) { categoriesPage() } else { homePage() } window.addEventListener('scroll', () => scrollInfinite(), { passive: false }) } ```

yo simplemente hice una validacion en el addEventListener y si se cumple la condicion ejecuto la funcion

window.addEventListener('scroll', function() {
    const view = document.documentElement.scrollTop + document.documentElement.clientHeight >= document.documentElement.scrollHeight - 15
    if (view) {
        get_paginated_trending()
    }
})

Mi solución fue implementar un observer al final de la pagina con eso cuando sea capturado ejecute la api y sume mas películas ![](https://static.platzi.com/media/user_upload/image-aef7277f-23c8-4e84-9f7f-4300ac49d2a4.jpg)![](https://static.platzi.com/media/user_upload/image-a17415e4-69e7-48f9-a178-a21743031f4e.jpg) ![](https://static.platzi.com/media/user_upload/image-5db539a3-e812-4f00-a62d-ba7cb34aa4d0.jpg)![](https://static.platzi.com/media/user_upload/image-92ac589f-1f72-4a19-ab69-dfc4349755a8.jpg)

aqui si creo que le falto un poco mas de logica a esta clase, ademas que acabamos de crear un loop de infiniteScroll en toda nuestra SPA pudiendo terminar en un bug…
Copio mi solucion de como lo resolví de manera sencilla:

let page = 1;
let infiniteScroll = getPaginatedTrendingMovies;
// let infiniteScroll;
const navigator = () => {

    window.removeEventListener('scroll', infiniteScroll);

    if (location.hash.startsWith('#trends')) {
        trendsPage();
    } else if (location.hash.startsWith('#search=')) {
        searchPage();
    } else if (location.hash.startsWith('#movie=')) {
        movieDetailsPage();
    } else if (location.hash.startsWith('#category=')) {
        categoriesPage();
    } else {
        homePage();
    }

    document.body.scrollTop = 0;
    document.documentElement.scrollTop = 0;
}

const searchClick = () => {
    location.hash = '#search=' + searchFormInput.value;
}
const trendClick = () => {
    location.hash = '#trends';
}
const backClick = () => {
    history.back();
    // location.hash = '#home';
}

searchFormBtn.addEventListener('click', searchClick);

trendingBtn.addEventListener('click', trendClick);

arrowBtn.addEventListener('click', backClick);

window.addEventListener('DOMContentLoaded', navigator, false);
window.addEventListener('hashchange', navigator, false);
// window.addEventListener('scroll', infiniteScroll);


function homePage() {

    headerSection.classList.remove('header-container--long');
    headerSection.style.background = '';
    arrowBtn.classList.add('inactive');
    arrowBtn.classList.remove('header-arrow--white');
    headerTitle.classList.remove('inactive');
    headerCategoryTitle.classList.add('inactive');
    searchForm.classList.remove('inactive');

    trendingPreviewSection.classList.remove('inactive');
    categoriesPreviewSection.classList.remove('inactive');
    genericSection.classList.add('inactive');
    movieDetailSection.classList.add('inactive');

    getTrendingPreview(TREND);
    getCategoriesPreview(GENRE);
}

function categoriesPage() {

    headerSection.classList.remove('header-container--long');
    headerSection.style.background = '';
    arrowBtn.classList.remove('inactive');
    arrowBtn.classList.remove('header-arrow--white');
    headerTitle.classList.add('inactive');
    headerCategoryTitle.classList.remove('inactive');
    searchForm.classList.add('inactive');

    trendingPreviewSection.classList.add('inactive');
    categoriesPreviewSection.classList.add('inactive');
    genericSection.classList.remove('inactive');
    movieDetailSection.classList.add('inactive');

    const [categoryId, categoryName] = location.hash.split('=')[1].split("-");
    getMoviesByCategory(categoryId);
    headerCategoryTitle.innerHTML = categoryName;
}

function movieDetailsPage() {

    headerSection.classList.add('header-container--long');
    // headerSection.style.background = '';
    arrowBtn.classList.remove('inactive');
    arrowBtn.classList.add('header-arrow--white');
    headerTitle.classList.add('inactive');
    headerCategoryTitle.classList.add('inactive');
    searchForm.classList.add('inactive');

    trendingPreviewSection.classList.add('inactive');
    categoriesPreviewSection.classList.add('inactive');
    genericSection.classList.add('inactive');
    movieDetailSection.classList.remove('inactive');

    const [_, movieId] = location.hash.split('=');
    getMovieById(movieId);
}

function searchPage() {

    headerSection.classList.remove('header-container--long');
    headerSection.style.background = '';
    arrowBtn.classList.remove('inactive');
    arrowBtn.classList.remove('header-arrow--white');
    headerTitle.classList.add('inactive');
    headerCategoryTitle.classList.add('inactive');
    searchForm.classList.remove('inactive');

    trendingPreviewSection.classList.add('inactive');
    categoriesPreviewSection.classList.add('inactive');
    genericSection.classList.remove('inactive');
    movieDetailSection.classList.add('inactive');

    const [_, query] = location.hash.split('=');
    getMoviesBySearch(query)
}

function trendsPage() {

    headerSection.classList.remove('header-container--long');
    headerSection.style.background = '';
    arrowBtn.classList.remove('inactive');
    arrowBtn.classList.remove('header-arrow--white');
    headerTitle.classList.add('inactive');
    headerCategoryTitle.classList.remove('inactive');
    searchForm.classList.add('inactive');

    trendingPreviewSection.classList.add('inactive');
    categoriesPreviewSection.classList.add('inactive');
    genericSection.classList.remove('inactive');
    movieDetailSection.classList.add('inactive');
    headerCategoryTitle.innerHTML = 'TENDENCIAS';

    getTrendingMovies();

    window.addEventListener('scroll', infiniteScroll);
}

Para poder reutilizar la funcion getPaginatedTrendingMovies, para el resto de rutas, la hacemos mas dinamica, ingresando la url especifica de cada sección como parámetro:

async function getPaginatedMovies(url) {
    const { scrollTop, scrollHeight, clientHeight } = document.documentElement   //Desestructuración 
    const scrollBottom = (scrollTop + clientHeight) >= (scrollHeight - 15)

    if (scrollBottom) {
        page++
        const {data} = await api(url, {
            params: {
                page
            }
        }); 
        const movies = data.results;
        renderMovies(movies, genericListSection, {lazyLoad : true, clean: false})
    }
}

Cambiando el addEventListener de scroll, por ‘onscroll’ podemos mandar como argumento a la funcion ‘getPaginatedMovies’ la url de la que obtener mas páginas:

document.onscroll = () => getPaginatedMovies('trending/movie/day')

Mencionar que para la solución que propone el profesor se tiene que crear una función de más películas para cada vista, sea de búsqueda, de tendencias o de categorías, porque tienen distintos parámetros.

Así quedó mi proyecto.

https://rica999.github.io/BROWSER-MOVIES-MOVIEDB/

Repositorio: https://github.com/rica999/BROWSER-MOVIES-MOVIEDB

Se me hizo infinite scroll absolutamente toda mi app y ni siquiera se porque 😂 solo aplique la lógica de los scrollTop, scrollHeigth, y clientHeigth con el if de validación y nada mas y ahora en cualquier página tengo infinite scroll, ni siquiera use la parte donde el profe declara la variable infiniteScroll ni el eventListener, sin embargo entro en los botones de c/categoria y tengo scroll infinito.
Lo gracioso es que es de las primeras veces que me sale algo y nisiquiera se el porque 😂😱, claramente mi app esta diseñada diferente a la del profe desde el inicio en cuestión de maquetación y eso

Usar un Intersection Observer haria la misma funcion que escuchar el evento scroll en todo el body, no? El nodo a observar ya quedaria en la imaginacion de cada quien… ( a mi se me ocurriria observar el footer o la ultima pelicula…)
Segun un estudio en este articulo, llamar una funcion por cada scroll puede darle demasiada carga al hilo principal. En esta app pequena a lo mejor no hace tanto efecto, pero hay que tomarlo en cuenta. 😃

A mi me funciono todo bien sin usar el false del tercer parámetro del addEventListener.