先判断问题出现在哪里
在移动端时代,让用户用大拇指去点击页面底部小得可怜的“上一页”、“下一页”数字按钮(1, 2, 3...),是一种非常反人类的交互体验。

核心逻辑是:无缝加载(Seamless Loading)与滚动监听。
通过现代浏览器的 Intersection Observer API,我们可以非常高效地监听用户是否滚动到了页面底部。当用户快要看底时,在后台默默地通过 Ajax 请求下一页的商品数据,并将其无缝追加(Append)到当前商品列表的末尾,实现瀑布流般的无限滚动体验(Infinite Scroll)或“点击加载更多(Load More)”。
实战步骤
步骤 1:在 Liquid 中保留标准分页结构 (作为 Fallback)
操作路径:在 sections/main-collection-product-grid.liquid 中编写
- 即使我们要用 JS 做无限滚动,也需要在 Liquid 中写好标准的
{% paginate %}标签。这不仅是为了 SEO(搜索引擎爬虫需要通过<a href="?page=2">来抓取下一页),也是为了在用户的 JS 加载失败时提供降级方案。{% paginate collection.products by 24 %} <div class="product-grid" id="ProductGrid"> {% for product in collection.products %} {% render 'product-card', product: product %} {% endfor %} </div> {% if paginate.next %} <div class="pagination-wrapper"> <a href="{{ paginate.next.url }}" class="load-more-btn">加载更多</a> </div> {% endif %} {% endpaginate %}
步骤 2:使用 Intersection Observer 监听滚动
操作路径:在 assets/infinite-scroll.js 中编写监听逻辑
- 不要使用老旧且非常消耗性能的
window.addEventListener('scroll')。 - 创建一个 Intersection Observer 实例,去观察底部的
.pagination-wrapper元素。const loadMoreBtn = document.querySelector('.load-more-btn'); if (!loadMoreBtn) return; const observer = new IntersectionObserver((entries) => { if (entries[0].isIntersecting) { // 当按钮进入视口时,触发加载下一页的函数 fetchNextPage(loadMoreBtn.href); } }, { rootMargin: '0px 0px 500px 0px' }); // 提前 500px 触发,实现无缝感 observer.observe(loadMoreBtn);
步骤 3:Fetch 请求与 DOM 拼接 (Append)
操作路径:编写 fetchNextPage 函数
- 当触发加载时,向
paginate.next.url发送请求。 - 为了提高性能,我们可以加上
§ion_id=main-collection-product-grid参数,让 Shopify 只返回这个 Section 的 HTML,而不是整个完整的网页。 - 拿到返回的 HTML 字符串后,将其解析为 DOM 对象。
- 提取出新页面中的所有
.product-card元素,将它们append到当前页面的#ProductGrid容器末尾。 - 提取出新页面中的
.load-more-btn的href链接,更新当前页面按钮的链接。如果新页面没有下一页链接了,说明到底了,移除观察者(Observer)并隐藏加载按钮。
常见误区与处理方法
误区一:无限滚动导致用户永远无法到达 Footer (页脚)
规避方法:这是纯粹的无限滚动(Infinite Scroll)最臭名昭著的 UX 灾难。如果你的集合页有 1000 个商品,用户向下滚动时,商品会不断加载。但用户突然想查看网站底部的“退换货政策”或“联系我们”链接,他们绝望地发现,每次快要滑到底部时,页面又被拉长了,Footer 永远在逃跑。最佳实践是:采用混合模式(Hybrid Approach)。 前 2-3 页使用自动的无限滚动,当加载到第 4 页时,停止自动加载,显示一个明确的“点击加载更多(Load More)”按钮。这样既保证了前期的沉浸式浏览,又把控制权还给了用户,让他们有机会访问 Footer。
误区二:加载下一页后,浏览器的“后退”按钮行为错乱
规避方法:用户在集合页无限滚动加载到了第 5 页,看到一个喜欢的商品,点击进入了产品详情页。看完后,点击浏览器的“后退”按钮。糟糕的事情发生了:页面重新回到了集合页的第 1 页,用户刚才滚动的位置和加载的商品全没了!这种挫败感是毁灭性的。在每次成功加载下一页并追加 DOM 后,需要使用 history.replaceState 更新当前页面的 URL(如更新为 ?page=5)。 更理想的做法是结合 sessionStorage 缓存当前加载的所有商品 HTML 和滚动高度,当监听到用户后退返回时,直接从缓存中恢复整个长列表,实现真正的无缝体验。
误区三:没有处理加载状态 (Loading State),导致重复发送请求
规避方法:当用户网络较慢时,滚动到底部触发了加载请求,但商品还没渲染出来。用户以为没触发,又上下滚动了几次,导致 Intersection Observer 在短时间内连续触发了 5 次,向服务器发送了 5 个请求下一页的 Ajax 请求。最终渲染出来的商品列表出现了大量重复的商品。需要在 JS 中设置一个 isLoading 的布尔值锁(Lock)。 当开始请求时,将 isLoading 设为 true,并在按钮处显示一个旋转的 Loading 动画。在请求完成并渲染完毕之前,即使再次触发观察者,也直接 return 拦截掉请求。请求结束后,再将 isLoading 设为 false 释放锁。
常见问题
学习「Shopify 集合页无限滚动 (Infinite Scroll) 与分页加载开发」前需要什么基础?
建议先熟悉 HTML、CSS、基础 JavaScript 和 Shopify 后台结构。涉及 Liquid、Section、Schema 或主题工作流的内容,可以边读边在测试主题里练习,不要直接改线上主题。
可以直接在正在使用的线上主题里操作吗?
不建议。主题开发和结构调整应先在复制主题、开发主题或本地环境中完成,确认移动端、产品页、购物车和关键模板正常后,再发布到线上主题。
修改主题前最应该备份什么?
至少保留当前主题副本,并用 Git 记录代码变化。如果文章涉及主题编辑器配置,还要注意模板 JSON 和 settings_data.json 这类配置文件是否需要同步。
遇到教程和后台界面不一致怎么办?
优先以当前 Shopify 后台、主题代码和官方文档为准。Shopify 后台和 CLI 会持续更新,旧截图可用于理解路径,但不能替代当前界面提示。
这类主题开发内容适合什么时候上线到正式店铺?
当改动已经在测试主题中完成移动端、桌面端、产品页、集合页、购物车和速度检查后,再安排上线。影响结账、价格、库存或应用兼容的改动要单独回归。