删除与视口相交的元素

Delete elements that have intersected the viewport

我正在与 intersection observer 一起玩弄以创建一个无限滚动狗网站。当您滚动并出现 6 条狗时,api 会再发射 6 次以抓取更多狗以添加到 DOM。我希望狗在用户滚动时加载,但当一只已经查看过的狗离开视口并在页面上上升时,该元素然后从页面上删除。所以狗总是在向下滚动时加载,但向上滚动时你总是在页面顶部。我当前在名为 lastFunc 的函数中的实现导致它表现得非常奇怪。我怎样才能达到预期的效果。

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>

删除元素时,容器的全部内容都会移动,观察者开始开火。为了删除元素时不触发观察者,需要在删除元素前将滚动条移动到该元素的高度。

示例如下:

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

    this.observer = new IntersectionObserver(
      (entries) => {
        entries.forEach((entry) => {
          entry.target.classList.add('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) {
          const scrollTop = this.$cardContainer.scrollTop;
          const height = item.offsetHeight;
          this.$cardContainer.scrollTo(0, Math.max(0, scrollTop - height));
          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>

希望这对你有所帮助。

<!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  */
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() {
        /*   , 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>