【问题标题】:Delete elements that have intersected the viewport删除与视口相交的元素
【发布时间】:2021-12-25 20:37:02
【问题描述】:

我正在与交叉点观察者一起玩,以创建一个无限滚动的狗网站。当您滚动并出现 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>

【问题讨论】:

    标签: javascript intersection-observer


    【解决方案1】:

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

    下面的例子:

    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>

    我希望这会对你有所帮助。

    【讨论】:

    • 代码改进: if (this.$allCards.length % 6 === 0) this.lastFunc(), console.log(".length % 6 === 0");只在最后一张卡片上调用 thelastfunc。最后一张卡片充当哨兵,它位于卡片组的底部,您必须滚动到底部才能看到最后一张卡片,当它完全可见时,您删除前六张,我也有 if(index= =5) console.log("观察卡"),this.observer.observe(card);在 loadnewcards 中,所以我们只观察最后一项。
    • intersectionobserver 调用 loadnewcards,所以当最后一张卡片完全可见时,我们会得到新卡片。
    【解决方案2】:
    <!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>
    

    【讨论】:

    • 使用单个哨兵来观察卡片堆栈的结尾,并使用 Promise.all 进行异步 url 加载。简化了装饰过渡。
    猜你喜欢
    • 1970-01-01
    • 2022-07-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-21
    • 2013-05-24
    • 1970-01-01
    相关资源
    最近更新 更多