'Delete elements that have intersected the viewport

I am playing around with intersection observer to create an infinite scroll dog website. As you scroll and 6 dogs appear, an api fires off 6 more times to grab more dogs to add to the DOM. I would like for the dogs to load in as a user scrolls but as an already viewed dog leaves the viewport and goes up on the page, that element is then deleted off the page. SO the dogs always load in scrolling down, but scrolling up you are always at the top of the page. My current implementation in the function called lastFunc is causing it to act really weird. How can I achieve the desired effect.

class CardGenerator {
  constructor() {
    this.$cardContainer = document.querySelector('.card-container');
    this.$allCards = undefined;

    this.observer = new IntersectionObserver(
      (entries) => {
        entries.forEach((entry) => {
          entry.target.classList.toggle('show', entry.isIntersecting);
          if (entry.isIntersecting) {
            this.observer.unobserve(entry.target);
          }
        });
      },
      {
        threshold: 1,
        rootMargin: '150px',
      }
    );
    this.loadNewCards();
  }

  cacheDOMElements() {
    this.$allCards = document.querySelectorAll('.card');
  }

  loadNewCards() {
    for (let index = 0; index < 6; index++) {
      fetch('https://dog.ceo/api/breeds/image/random', { method: 'GET' })
        .then((result) => {
          return result.json();
        })
        .then((r) => {
          console.log(r);
          const card = document.createElement('div');
          card.classList.add('card');

          const imageElement = document.createElement('img');
          imageElement.classList.add('forza-img');

          imageElement.setAttribute('src', r.message);
          card.appendChild(imageElement);
          this.observer.observe(card);
          this.$cardContainer.append(card);
          this.cacheDOMElements();
          if (this.$allCards.length % 6 === 0) this.lastFunc();
        });
    }
  }

  lastFunc() {
    console.log(this.$allCards);
    if (this.$allCards.length > 12) {
      this.$allCards.forEach((item, idx) => {
        if (idx < 6) {
          item.remove();
        }
      });
    }

    this.$allCards.forEach((card, idx) => {
      this.observer.observe(card);
    });

    const lastCardObserver = new IntersectionObserver((entries) => {
      const $lastCard = entries[0];
      if (!$lastCard.isIntersecting) return;
      this.loadNewCards();
      lastCardObserver.unobserve($lastCard.target);
    });

    lastCardObserver.observe(document.querySelector('.card:last-child'));
  }
}

const cardGenerator = new CardGenerator();
html,
body {
  height: 100%;
  width: 100%;
  box-sizing: border-box;
  padding: 0;
  margin: 0;
}

.card {
  float: left;
  width: 48vw;
  margin: 1%;
  transform: translateX(100px);
  opacity: 0;
  transition: 150ms;
}

.card.show {
  transform: translateY(0);
  opacity: 1;
}

.card img {
  width: 100%;
  border-radius: 15px;
  height: 30vh;
  object-fit: cover;
}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <h1>Dog Random Images</h1>
  <div class="card-container"></div>
</body>

<script src="app.js" ></script>
</html>


Solution 1:[1]

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title></title>

</head>
<body>


<style>
body {
  height: 100%;
  width: 100%;
  box-sizing: border-box;
  padding: 0;
  margin: 0;
}

.card {
  width: 48vw;
  margin: 1%;
}

.card.show {
//  opacity: 1;
}

.card img {
  width: 100%;
  border-radius: 15px;
  height: 30vh;
  object-fit: cover;
}


.card-container{
    border: solid 1px #00f;
    padding: 20px;
    overflow-y:scroll;
}
</style>


<style>
    #sentinel{
    height:0px;
}
</style>


<h1>Dog Random Images</h1>
<div class="card-container">
    <div id="sentinel"></div>
</div>


<script>
/* Question on https://stackoverflow.com/questions/70482606/delete-elements-that-have-intersected-the-viewport */
class CardGenerator {
    constructor() {
        this.$cardContainer = document.querySelector('.card-container');
        this.$allCards = undefined;
        this.mysentinel = document.querySelector('#sentinel');

        this.observer = new IntersectionObserver(
            (entries) => {

                let [entry] = entries; //destructure array, get first entry - should only be 1 - sentinel
                if (entry.isIntersecting) {
                    this.observer.unobserve(entry.target);
                    this.loadNewCards();
                }

            }, {
                threshold: 1,
                rootMargin: '150px' /*expanded root/viewport(due to null) by 150px*/,
            }
        );
        this.loadNewCards();

    } // end constructor;

    cacheDOMElements() {
        //The Document method querySelectorAll() returns a static (not live) NodeList
        this.$allCards = document.querySelectorAll('.card');
    }

    loadNewCards() {
        /* https://stackoverflow.com/questions/31710768/how-can-i-fetch-an-array-of-urls-with-promise-all  , from peirix*/
        this.mypromises = [];
        this.mymessages = [];
        this.urls = new Array(6).fill("https://dog.ceo/api/breeds/image/random", 0, 6);

        //create array of promises
        var promises = this.urls.map(url => fetch(url).then(y => y.json()));
        //Promise.all() method takes an iterable of promises
        //promise.all returns a single Promise that resolves to an array of the results of the input promises
        Promise.all(promises)
            .then(results => {
                //accumulate all the urls from message property
                results.forEach(v => this.mymessages.push(v.message));

            })
            .finally(() => {

                let idx = 0;
                for (let message of this.mymessages) {
                    const card = document.createElement('div');
                    card.classList.add('card');
                    const imageElement = document.createElement('img');
                    imageElement.setAttribute('src', message);
                    imageElement.setAttribute('title', `${idx++}:${message}`);
                    card.appendChild(imageElement);
                    this.$cardContainer.appendChild(card);

                }// end for
                
                this.cacheDOMElements();
                
                //stop this sentinel possibly hitting the observer to loadnewcards as we (re)move cards
                this.observer.unobserve(this.mysentinel);
                //if number of cards is>12 then takeoff the first 6
                if (this.$allCards.length > 12) {
                    for (let i = 0; i < 6; i++) {
                        this.$allCards[i].remove();
                    }

                }
                //already exists so move it to bottom of container div
                this.$cardContainer.appendChild(this.mysentinel);
                /*this should be outside the root so when it invokes observer it will not fire loadnewcards*/
                this.observer.observe(this.mysentinel);
            }); //end of finally end of Promise.all

    } //end loadnewcards



} //class CardGenerator


const cardGenerator = new CardGenerator();
</script>  


</body>
</html>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 JoePythonKing