javascript 如何实现一个垂直的新闻提要或时间轴,在到达终点时动态加载新项目,并支持跳转到特定位置?

h7appiyu  于 2023-06-20  发布在  Java
关注(0)|答案(1)|浏览(120)

我有数据,其中包括数以千计的项目,我希望能够显示。但是,同时显示所有项目会显著降低性能。而且,我一次只需要查看几个项目。
所以基本上,我想要的是一个像Facebook新闻提要,Twitter时间线或任何即时通讯聊天历史视图一样的东西。在所有这些例子中,最初,我只显示了几个项目,通过滚动到列表的末尾,我可以动态加载更多。
我还可以跳到提要的特定位置,比如聊天记录的某个日期和时间。提要通常不会加载和显示从现在到指定的历史位置的每一条消息,但它会跳转到该位置并显示周围的一些消息。再次,通过到达列表的(任一)末端,动态地添加更多项。
这个功能叫什么?我可以使用哪个库来实现它?

nnsrf1az

nnsrf1az1#

这里有一个不需要任何第三方库的小型概念验证。
在这个例子中,数据库由从0到500的501个项目组成。FeedEngine负责从列表中添加/删除项目的机制。实际的站点/项目内容由itemCallback函数管理。在这里,它被称为customItemBuilder,并且非常简单:它只显示项目索引并改变背景颜色。这是您实际从数据库中提取特定itemIndex的内容并相应地动态调整itemElement的地方。
在移动的上,您可以在项目中滑动。在桌面上,最好使用鼠标滚轮滚动提要。

<!DOCTYPE html>
<html lang="en">

<head>
<script>
/**
 * FeedEngine
 *
 * FeedEngine is a vertical news feed or timeline implementation. Initially,
 * only a certain, small amount of items are displayed. If the user reaches
 * either end of the container, for example by scrolling, more and more items
 * are dynamically added to the feed as required. It's also possible to jump
 * to a specific item, i.e. feed position.
 *
 * For each item, an empty, blank DIV element will be added to the container
 * element. Afterwards, a function is called which receives two parameters:
 * `itemElement`, the new element, and `itemIndex`, the index of the new
 * item. This callback function allows you to customize the presentation of
 * the feed items.
 *
 * Options:
 *     containerElement - The element which will contain all DIV elements for
 *         the items. For best results, you should probably choose a DIV
 *         element for the container as well. Furthermore, its CSS should
 *         contain something like `overflow: scroll`. Note: Its attributes
 *         `innerHTML` and `onscroll` will be overwritten.
 *     itemCallback - This function will be called after a new item has been
 *         added to the container. If the callback doesn't return `true`, the
 *         item will immediately be removed again.
 *     moreItemsCount -  The number of new items that will be added above and
 *         below the first item, the target item of a jump or the outermost
 *         item in the feed, respectively.
 *     moreItemsTrigger - The threshold distance to the outermost item which
 *         triggers more items to be added to the feed. For example, if this
 *         option is set to `0`, new items will only be added once the
 *         outermost item is fully in view. Furthermore, a value greater than
 *         or equal to `moreItemsCount` doesn't make sense.
 *     inverseOrder - Use bottom-to-top instead of top-to-bottom order.
 *
 * @constructor
 * @param {Object} options - Options object.
 */
function FeedEngine(options) {
    'use strict';
    this.itemCallback = (itemElement, itemIndex) => {};
    this.moreItemsCount = 20;
    this.moreItemsTrigger = 5;
    this.inverseOrder = false;
    Object.assign(this, options);
    if (this.containerElement === undefined) {
        throw new Error('container element must be specified');
    }
    this.jumpToItem = (itemIndex) => {
        this.containerElement.innerHTML = '';
        this.topItemIndex = itemIndex;
        this.bottomItemIndex = itemIndex;
        var initialItem = this.insertItemBelow(true);
        for (var i = 0; i < this.moreItemsCount; i++) {
            this.insertItemAbove();
            this.insertItemBelow();
        }
        this.containerElement.scrollTop = initialItem.offsetTop - this.containerElement.offsetTop + (this.inverseOrder ? initialItem.clientHeight - this.containerElement.clientHeight : 0);
    };
    this.insertItemAbove = () => {
        this.topItemIndex += this.inverseOrder ? 1 : -1;
        var itemElement = document.createElement('div');
        this.containerElement.insertBefore(itemElement, this.containerElement.children[0]);
        if (!this.itemCallback(itemElement, this.topItemIndex)) {
            itemElement.remove();
        }
        return itemElement;
    };
    this.insertItemBelow = (isInitialItem) => {
        if (isInitialItem === undefined || !isInitialItem) {
            this.bottomItemIndex += this.inverseOrder ? -1 : 1;
        }
        var itemElement = document.createElement('div');
        this.containerElement.appendChild(itemElement);
        if (!this.itemCallback(itemElement, this.bottomItemIndex)) {
            itemElement.remove();
        }
        return itemElement;
    };
    this.itemVisible = (itemElement) => {
        var containerTop = this.containerElement.scrollTop;
        var containerBottom = containerTop + this.containerElement.clientHeight;
        var elementTop = itemElement.offsetTop - this.containerElement.offsetTop;
        var elementBottom = elementTop + itemElement.clientHeight;
        return elementTop >= containerTop && elementBottom <= containerBottom
    };
    this.containerElement.onscroll = (event) => {
        var topTriggerIndex = this.moreItemsTrigger;
        var bottomTriggerIndex = event.target.children.length - this.moreItemsTrigger - 1;
        var topTriggerElement = event.target.children[topTriggerIndex];
        var bottomTriggerElement = event.target.children[bottomTriggerIndex];
        var topTriggerVisible = this.itemVisible(topTriggerElement);
        var bottomTriggerVisible = this.itemVisible(bottomTriggerElement);
        for (var i = 0; i < this.moreItemsCount; i++) {
            if (topTriggerVisible) {
                this.insertItemAbove();
            }
            if (bottomTriggerVisible) {
                this.insertItemBelow();
            }
        }
    };
    this.jumpToItem(0);
}
</script>
</head>

<body>
Feed:
<button onclick="feed = new FeedEngine({containerElement: document.getElementById('container'), itemCallback: customItemBuilder})">top-to-bottom</button>
<button onclick="feed = new FeedEngine({containerElement: document.getElementById('container'), itemCallback: customItemBuilder, inverseOrder: true})">bottom-to-top</button>
<input type="text" id="jump" value="250">
<button onclick="feed.jumpToItem(parseInt(document.getElementById('jump').value))">jump</button>
<div id="container" style="overflow: scroll; width: 300px; height: 100px; resize: both;"></div>
<script>
function customItemBuilder(itemElement, itemIndex) {
    if (0 <= itemIndex && itemIndex <= 500) {
        /* customize the item DIV element here */
        itemElement.innerHTML = 'Content for item index ' + itemIndex;
        itemElement.style.backgroundColor = itemIndex % 2 ? 'LightCyan' : 'LightGray';
        return true;
    }
}
window.onload = () => {
    document.getElementsByTagName('button')[0].click();
}
</script>
</body>

</html>

相关问题