演示 / Live Component

无限滚动 - 2/2

无限滚动系列的第二部分也是最后一部分,带来一系列全新(可爱)的 T 恤!现在有了 滚动时自动加载、新技巧和惊艳的 加载动画

ux.symfony.com
🐻
0.99
🐨
1.99
🐼
2.99
🦥
3.99
🦦
4.99
🦨
5.99
🦘
6.99
🐾
7.99
🐓
8.99

那么... 它如何工作呢?

让我们关注两个方面。但首先,你滚动到最后了吗?
您可能错过了一些关于性能的重要细节... 🍿

Morphin' 技巧

这个技巧与 1/2 部分 的 "无限滚动" LiveComponent 演示中使用的技巧非常相似。

之前的结果

🐯 对于上一页的结果,我们添加一个与上一页最后一个项目对应的伪造项目,具有相同的 id。这允许在现有元素之后添加新元素

1<div class="ProductGrid" {{ attributes.defaults(stimulus_controller('appear')) }}>
2
3    <div id="results" style="display: flex; gap: 1rem; flex-direction: column;" class="p-4">
4        <div class="ProductGrid_items">
5
6            {# 🐯- Last result from previous page #}
7            {% if page > 1 %}
8                <article id="item--{{ page - 1 }}-{{ per_page }}"></article>
9            {% endif %}

当前页面的结果

🦊 对于当前页面的结果,我们同时使用 iddata-live-ignore。这允许保留 DOM 中之前的元素

11            {# 🦊 - Current page #}
12            {% for item in this.items %}
13                <article
14                    id="item--{{ page }}-{{ loop.index }}"
15                    class="ProductGrid_item"
16                    data-live-ignore
17                    style="--i: {{ item.id }};"
18                >
19                    <div class="ProductGrid_media">

下一页

🐼 最后,对于下一页的结果,我们添加带有 id 的占位符。

这与 "上一页" 技巧相结合,强制执行下一个元素的插入顺序

... 中间没有 div!

31                {# 🐼 - Next page #}
32                {% for i in 1..per_page %}
33                    <article id="item--{{ page + 1 }}-{{ i }}"
34                         class="ProductGrid_item"
35                         style="--i: {{ (page * per_page) + i - 1 }};"

Intersection Observer

我们希望在用户滚动到页面底部时自动加载下一个结果。

我们创建一个小的 Stimulus 控制器,利用 IntersectionObserver API 在其目标元素在视口中变为可见时触发自定义的 appear 事件。

通过创建一个专用的控制器,我们可以使逻辑与产品网格分离。并且我们可以在其他组件中重用它!

import { Controller } from '@hotwired/stimulus';

/* stimulusFetch: 'lazy' */
export default class extends Controller {
    static targets = ['loader'];

    loaderTargetConnected(element) {
        this.observer ??= new IntersectionObserver((entries) => {
            entries.forEach((entry) => {
                if (entry.isIntersecting) {
                    entry.target.dispatchEvent(new CustomEvent('appear', {detail: {entry}}));
                }
            });
        });
        this.observer?.observe(element);
    }

    loaderTargetDisconnected(element) {
        this.observer?.unobserve(element);
    }
}

LiveComponent + Stimulus

现在我们将 appear 控制器添加到我们的 ProductGrid 组件中,这要归功于 stimulus_controller 方法。

1<div class="ProductGrid" {{ attributes.defaults(stimulus_controller('appear')) }}>
2
3    <div id="results" style="display: flex; gap: 1rem; flex-direction: column;" class="p-4">

事件 -> LiveAction

最后,我们将 loader 目标添加到我们为下一页添加的第一个 "item" 占位符中。

🦁 当 appear 事件被触发时,我们配置该事件调用 live controllermore action。

这样做,我们可以在下一页的第一个项目可见时加载下一页

无需 "加载更多" 按钮!

32                {% for i in 1..per_page %}
33                    <article id="item--{{ page + 1 }}-{{ i }}"
34                         class="ProductGrid_item"
35                         style="--i: {{ (page * per_page) + i - 1 }};"
36
37                        {# 🦁 - The trigger #}
38                        {% if loop.first %}
39                            data-appear-target="loader"
40                            data-action="appear->live#action"
41                            data-live-action-param="debounce(750)|more"
42                        {% endif %}
43
44                    >
45                        <div class="ProductGrid_media">
作者 smnandre
发布于 2024-06-07