我试着用纯JS写一个虚拟滚动,但是当我向下滚动时,滚动并没有停止.我不知道是什么导致了无限滚动.

为了模拟滚动,我使用了两个容器,顶部和底部.在滚动过程中,我按比例改变它们的高度,同时重新呈现内部内容.

class VirtualScroll {
  constructor(selector, visibleRows = 0, amount, rowHeight) {
    this.scrollContainer = document.querySelector(selector);

    if (!this.scrollContainer) {
      return false;
    }

    this.rowHeight = rowHeight;
    this.visibleRows = visibleRows;
    this.start = 0;

    this.data = this.createTableData(amount);

    // Верхнее пространство для скролла
    this.topSpace = document.createElement("div");
    this.topSpace.classList.add('hidden-top');
    this.topSpace.style = `height: ${this.getTopHeight()}px;`;
    this.scrollContainer.appendChild(this.topSpace);

    // Создание пространство видимых блоков
    this.container = document.createElement("div");
    this.container.classList.add('scroll-container');
    this.fillContainer();

    this.scrollContainer.appendChild(this.container);

    // Верхнее пространство для скролла
    this.bottomSpace = document.createElement("div");
    this.bottomSpace.classList.add('hidden-bottom');
    this.bottomSpace.style = `height: ${this.getBottomHeight()}px`
    this.scrollContainer.appendChild(this.bottomSpace);

    this.scrollContainer.addEventListener('scroll', (e) => {
      const oldStart = this.start + 0;
      this.start = Math.min(
        this.data.length - this.visibleRows - 1,
        Math.floor(e.target.scrollTop / this.rowHeight)
      )

      if (oldStart === this.start) {
        return false;
      }

      this.bottomSpace.style = `height: ${this.getBottomHeight()}px`;
      this.topSpace.style = `height: ${this.getTopHeight()}px;`;
      this.fillContainer();
    });
  }

  getTopHeight() {
    return this.rowHeight * this.start;
  }

  getBottomHeight() {
    return this.rowHeight * (this.data.length - (this.start + this.visibleRows + 1));
  }

  createTableData(h) {
    return new Array(h).fill(0).map((_, row) => {
      return row;
    });
  }

  // Заполнение контейнера видимыми блоками 
  fillContainer() {
    this.container.innerHTML = "";
    this.data.slice(this.start, this.start + this.visibleRows + 1).map((row, rowIndex) => {
      const item = document.createElement("div");
      item.classList.add('scroll-item');
      item.innerText = row;
      item.id = this.start + ' ' + rowIndex + row;
      this.container.appendChild(item);
    })
  }
}

new VirtualScroll(".container", 4, 100, 30);
* {
  box-sizing: border-box;
}

.container {
  height: 120px;
  overflow-y: auto;
}

.scroll-container {
  width: 100%;
}

.scroll-item {
  height: 30px;
  border: 1px solid black;
  padding: 10px;
  color: black;
}
<div class="container"> </div>

CodePen Example

我希望更多细心和熟练的开发人员能告诉我哪里出了问题.

推荐答案

使用动态改变大小的顶部和底部元素的方法可能不会可靠地工作.这样做的原因是当顶部div的高度改变时,浏览器会在父滚动容器上触发一个layout操作,这反过来又会改变滚动位置.

然后,您会遇到在循环中调用滚动处理程序的情况,因为处理程序中的逻辑最终会触发滚动容器更改其滚动位置.顺理成章地说,这也是开发"聊天"界面复杂得令人惊讶的原因之一,因为上面添加了新的内容("消息").

这个数字周围的quick Google search表示这是一个常见的问题.

相反,您可以做的是丢弃顶部/底部容器,并使用absolute定位的组合,其中top用于顶部间距,padding-bottom用于底部间距.因为绝对定位将容器带出布局,所以当top改变时它不会触发滚动事件,所以它不会陷入循环.

你对每一面所需空间的计算是准确的,所以我们只需使用你现有的计算,但插入到这些css属性.额外的好处是,完成整个工作的工作量/复杂性略低,并且不会对您已有的主要实现进行重大更改.

注意CSS和JS都需要修改.这是一个codepen.

class VirtualScroll {
  constructor(selector, visibleRows = 0, amount, rowHeight) {
    this.scrollContainer = document.querySelector(selector);

    if (!this.scrollContainer) {
      return false;
    }

    this.rowHeight = rowHeight;
    this.visibleRows = visibleRows;
    this.start = 0;

    this.data = this.createTableData(amount);

    // Создание простанство видимых блоков
    this.container = document.createElement("div");
    this.container.classList.add("scroll-container");
    this.fillContainer();

    this.scrollContainer.appendChild(this.container);
    this.container.style = `top: ${this.getTopHeight()}px; padding-bottom: ${this.getBottomHeight()}px;`;

    this.scrollContainer.addEventListener("scroll", (e) => {
      const oldStart = this.start + 0;
      this.start = Math.min(
        this.data.length - this.visibleRows - 1,
        Math.floor(e.target.scrollTop / this.rowHeight),
      );

      if (oldStart === this.start) {
        return false;
      }

      this.container.style = `top: ${this.getTopHeight()}px; padding-bottom: ${this.getBottomHeight()}px;`;
      this.fillContainer();
    });
  }

  getTopHeight() {
    return this.rowHeight * this.start;
  }

  getBottomHeight() {
    return (
      this.rowHeight * (this.data.length - (this.start + this.visibleRows + 1))
    );
  }

  createTableData(h) {
    return new Array(h).fill(0).map((_, row) => {
      return row;
    });
  }

  // Заполнение контейнера видимыми блоками
  fillContainer() {
    this.container.innerHTML = "";
    this.data
      .slice(this.start, this.start + this.visibleRows + 1)
      .map((row, rowIndex) => {
        const item = document.createElement("div");
        item.classList.add("scroll-item");
        item.innerText = row;
        item.id = this.start + " " + rowIndex + row;
        this.container.appendChild(item);
      });
  }
}

new VirtualScroll(".container", 4, 100, 30);
* {
  box-sizing:border-box;
}
.container {
  height: 120px;
  overflow-y: auto;
  position: relative;
}
.scroll-container{
  width: 100%;
  position: absolute;
}
.scroll-item {
  height: 30px;
  border: 1px solid black;
  padding: 10px;
  color: black;
}

Javascript相关问答推荐

react 路由加载程序行为

警告!合同执行期间遇到错误[执行已恢复](Base Layer 2)

使用JavaScript重新排序行

用JavaScript复制C#CRC 32生成器

当Redux提供程序访问Reduxstore 时,可以安全地从Redux提供程序外部调用钩子?

我的角模板订阅后不刷新'

优化Google Sheet脚本以将下拉菜单和公式添加到多行

当使用';字母而不是与';var#39;一起使用时,访问窗口为什么返回未定义的?

TypeError:无法分解';React2.useContext(...)';的属性';basename';,因为它为空

在浏览器中触发插入事件时检索编码值的能力

WP Bootstrap NavWaker:下拉菜单一次打开所有下拉菜单

为什么这个.add.group({})在教程中运行得很好,但在我的游戏中就不行了?

JavaScript不重定向配置的PATH

自定义确认组件未在vue.js的v菜单内打开

使用自动识别发出信号(&Q)

如何在Jest中模拟函数

如何在每隔2分钟刷新OKTA令牌后停止页面刷新

Plotly.js栏为x轴栏添加辅助文本

MAT-TREE更多文本边框对齐问题

带有JS模块模式的Rails的Importmap错误:";Net::ERR_ABORTED 404(未找到)&Quot;