Search code examples
javascriptlazy-loadingcustom-data-attributeintersection-observer

Intersection Observer with lazy loading


i want to achieve this behaviour from this example with images https://codepen.io/ryanfinni/pen/jONBEdX. the only difference it's that instead on toggling the img visibility, for every div that it's in view, i want to make an call to an endpoint to retrieve the data and ppulate the html by sendind the corresponding data-id as a parameter.

what i have so far it's working only for the first item. how can i make it work for all the items that i have and target them by the data-id, similar to this example with data.src

function handleIntersection(entries) {
  entries.map((entry) => {
    if (entry.isIntersecting) {
      entry.target.src = entry.target.dataset.src;
      entry.target.classList.add('loaded')
      observer.unobserve(entry.target);
    }
  });
}

here is my code

const LIST = document.querySelector('.city-list');
async function getData() {
  try {
    let response = await fetch('https://raw.githubusercontent.com/ashes27/infinite/master/ids.json');
    if (response.status == 200) {
      let data = await response.json();
      return data;
    } else {
      throw new Error(response.status);
    }
  } catch (error) {
    console.log(error);
  }
}

getData().then((data) => {
  for (const details in data.data.items) {
    const LI = document.createElement('li');
    const TITLE = document.createElement('h2');
    TITLE.className = 'city-title';
    TITLE.innerText = data.data.items[details].title;
    LI.appendChild(TITLE);
    LIST.appendChild(LI);
    for (const id in data.data.items[details].ids) {
      const DIV = document.createElement('div');
      DIV.setAttribute('data-id', data.data.items[details].ids[id]);
      DIV.className = 'wrapper';
      const markup = `
       <div class="city-wrapper" >
          <div class="result-wrapper">
            <div class="image-wrapper">
              <img src=""/>
            </div>
            <div class="content-wrapper">
              <div class="content-info">
                <div class="info-wrapper">
                  <h2></h2>
                  <span class="address"></span>
                </div>
              </div>
              <p class="description"></p>
              </div>
            </div>
          </div>
        </div>
      `;
      DIV.innerHTML = markup;
      LI.appendChild(DIV);
    }
  }
});

var observer = new IntersectionObserver(
  function (entries) {
    if (entries[0].isIntersecting === true) {
      observer.unobserve(document.querySelector('.city-list'));
      const getInfo = async function (post) {
        let infoResponse = await fetch('https://raw.githubusercontent.com/ashes27/infinite/master/single-item.json');
        let info = await infoResponse.json();
        return info;
      };
      getInfo().then((info) => {
        console.log(info);
        let itemsInfo = info.data.items;
        const DIV = LIST.querySelector('.wrapper');
        console.log(DIV);
        const markup = `
        <div class="city-wrapper" >
          <div class="result-wrapper">
            <div class="image-wrapper">
              <img src="${itemsInfo.mainImgUrl}"/>
            </div>
            <div class="content-wrapper">
              <div class="content-info">
                <div class="info-wrapper">
                  <h2>${itemsInfo.visitTitle}</h2>
                  <span class="address">${itemsInfo.address}</span>
                </div>
              </div>
              <p class="description">${itemsInfo.mainDescription}</p>
              </div>
            </div>
          </div>
        </div>
        `;
        DIV.innerHTML = markup;
      });
    }
  },
  { threshold: [0] }
);

observer.observe(document.querySelector('.city-list'));
.city-wrapper {
  height: 440px;
}

img {
  width: 400px;
}
 <div style="height: 400px;"></div>
  <div style="height: 400px;"></div>
  <div style="height: 400px;"></div>
  <div style="height: 400px;"></div>
  <div style="height: 400px;"></div>
  <div style="height: 400px;"></div>
  <div style="height: 400px;"></div>
  <div style="height: 400px;"></div>

  <ul class="city-list">
  </ul>

thanks in advance!


Solution

  • You've told your observer to watch for an intersection of .city-list with the viewport. But you want to know when any of the .wrapper elements (the ones with the data-id) in the list gets (or is about to get) visible.

    Therefor you have to .observe() these div.wrapper elements and not the complete list:

    observer.observe(DIV);
    

    To remove an element that has just become visible you have to use the target property of the current IntersectionObserverEntry:

    observer.unobserve(entries[0].target);
    

    The data-id you're looking for is also available through the same property:

    const dataId = entries[0].target.dataset.id;
    

    As we're going to "observe" multiple elements we have to adjust the logic accordingly:

    entries.forEach(entry => {
      // ...
    });
    

    A complete, but slightly reduced (dummy data, no getInfo()) example:

    const observer = new IntersectionObserver(
      function (entries) {
        entries.forEach(entry => {
          if (entry.isIntersecting === true) {
            observer.unobserve(entry.target); // remove the current item from the "watchlist"
            console.log(entry.target.dataset.id); // the id you're looking for
          }
        });
      },
      { threshold: [0] }
    );
    
    async function getData() {
      // dummy data
      return Promise.resolve({
        data: {
          items: {
            city1: { title: "City 1", ids: [1, 2, 3] },
            city2: { title: "City 2", ids: [4, 5, 6] }
          }
        }
      })
    };
    
    getData().then((data) => {
      const LIST = document.querySelector('.city-list');
      
      for (const details in data.data.items) {
        const LI = document.createElement('li');
        
        const TITLE = document.createElement('h2');
        TITLE.className = 'city-title';
        TITLE.innerText = data.data.items[details].title;
        LI.appendChild(TITLE);
        
        LIST.appendChild(LI);
        
        for (const id in data.data.items[details].ids) {
          const DIV = document.createElement('div');
          DIV.setAttribute('data-id', data.data.items[details].ids[id]);
          DIV.className = 'wrapper';
          
          const markup = `<div class="city-wrapper" >...</div>`;
          
          DIV.innerHTML = data.data.items[details].ids[id] + "<br />" /* so the ID is directly visible */ + markup;
          LI.appendChild(DIV);
          
          observer.observe(DIV); // add the current DIV to the "watchlist"
        }
      }
    });
    .city-wrapper { height: 440px; }
    img { width: 400px; }
    <ul class="city-list"></ul>

    Or here: https://jsfiddle.net/sDSJrPqw/L0wd6kja/5/