当前位置: 首页 > news >正文

Shopify 集合页改造:增加 Banner 图片 + 点击加载更多功能

在做 Shopify 商城二开的时候,我们常常会遇到集合页(Collection Page)用户体验不够流畅的问题:

  • 默认的 分页 需要用户不断点击「下一页」才能看完所有商品,流程比较割裂。
  • 集合页顶部有时需要插入 活动 Banner 图,但 Shopify 默认没有直接的插槽。

于是,我改造了 Collection 模板,增加了两个功能:

  • 在第一页商品列表前插入 Banner 图片
  • 将传统分页替换为「点击加载更多」按钮

在原来的结构中增加banner图和按钮的结构 {% if section.settings.img != blank and paginate.current_page == 1 %}保证了只会加载一次,防止重复加载图

<ul id="product-grid" data-id="{{ section.id }}" class=" grid product-grid grid--{{ section.settings.columns_mobile }}-col-tablet-down grid--{{ section.settings.columns_desktop }}-col-desktop {% if section.settings.quick_add == 'bulk' %} collection-quick-add-bulk{% endif %} ">{%- assign cols = section.settings.columns_desktop | plus: 0 -%}{%- assign gutter = 'var(--grid-desktop-horizontal-spacing)' -%}{% if section.settings.img != blank and paginate.current_page == 1 %}<li class="grid__item grid__item--banner pc" style=" width: calc({{ 200 | divided_by: cols }}% - (({{ cols }} - 2) / {{ cols }}) * {{ gutter }}); max-width: calc({{ 200 | divided_by: cols }}% - (({{ cols }} - 2) / {{ cols }}) * {{ gutter }});"><img src="{{ section.settings.img | image_url: width: '1920x' }}" width="{{ section.settings.img.width }}"height="{{ section.settings.img.height }}" loading="lazy" alt="{{ section.settings.img.alt }}"></li>{% endif %}{% assign skip_card_product_styles = false %}{%- for product in collection.products -%}{% assign lazy_load = false %}{%- if forloop.index > 2 -%}{%- assign lazy_load = true -%}{%- endif -%}<li class="grid__item{% if settings.animations_reveal_on_scroll %} scroll-trigger animate--slide-in{% endif %}" {% ifsettings.animations_reveal_on_scroll %} data-cascade style="--animation-order: {{ forloop.index }};" {% endif %}>{% render 'card-product'%} <!--产品卡组件 --></li>{%- assign skip_card_product_styles = true -%}{%- endfor -%}
</ul>
<div class="Load-More-inner"><!--使用 paginate 提供“筛选后”的真实总数与每页条数 --><div class="Load-More-number text-text" id="LoadMoreNumber" data-total="{{ paginate.items }}"data-per-page="{{ paginate.page_size }}"><!-- 左侧显示“当前已加载数”,初始为当前页已渲染的产品数量 --><span id="LoadedCount">{{ collection.products | size }}</span>of<span id="TotalCount">{{ paginate.items }}</span></div>{% if paginate.pages > 1 %}<!-- 由 JS 动态计算下一页 --><div class="Load-More btn_4" id="LoadMoreBtn">Load More</div>{% endif %}</div>

Shopify 默认的集合页分页机制,用户每次都要跳页,体验割裂。JS 逻辑优化:初始化 → 监听 → 点击加载 → 状态更新。

  • UI 与数据同步:点击加载、筛选、Section 重渲染,都会自动更新按钮和计数。
  • 兼容 Shopify:监听 shopify:section:load / facets:updated / MutationObserver。
  • 避免重复加载:通过 Math.ceil(totalLoaded / perPage) 动态计算下一页。
  • 用户体验优化:按钮有 Loading 状态,加载完毕后自动隐藏。

Ajax 异步加载 + DOM 解析:点击按钮后,用 fetch 拉取下一页 HTML,再用 DOMParser 抽取 #product-grid 里的 <li> 追加。每次加载后,调用 updateLoadMoreUI(),判断按钮显示隐藏。

fetch(url.href).then(r => r.text()).then(html => {const doc = new DOMParser().parseFromString(html, 'text/html');const nextGrid = doc.querySelector('#ProductGridContainer #product-grid');const nextProducts = nextGrid.querySelectorAll('li.grid__item:not(.grid__item--banner)');nextProducts.forEach(item => productGrid.appendChild(item));updateLoadMoreUI();});

由于 Shopify 的 Facet Filter 筛选会刷新整个产品区块,如果不处理,Load More 状态会错乱。我加了一个 MutationObserver,监听 #product-grid 子元素变化:

const observer = new MutationObserver(() => updateLoadMoreUI());
observer.observe(productGrid, { childList: true, subtree: true });

完整的js代码参考

<script>
/* 封装一个计算&展示状态的方法 */
function updateLoadMoreUI() {const productGrid = document.getElementById('product-grid');const loadMoreNumber = document.getElementById('LoadMoreNumber');const btn = document.getElementById('LoadMoreBtn');if (!productGrid || !loadMoreNumber || !btn) return;// 统计当前已加载(排除 banner)const totalLoaded = productGrid.querySelectorAll('li.grid__item:not(.grid__item--banner)').length;// 读取筛选后的“总数/每页”const totalProducts = parseInt(loadMoreNumber.dataset.total, 10) || 0;const perPage = parseInt(loadMoreNumber.dataset.perPage, 10) || 1;// 更新显示const loadedEl = document.getElementById('LoadedCount');const totalEl = document.getElementById('TotalCount');if (loadedEl) loadedEl.textContent = totalLoaded;if (totalEl) totalEl.textContent = totalProducts;// 如果已加载 >= 总数,则隐藏按钮if (totalLoaded >= totalProducts || perPage <= 0) {btn.style.display = 'none';} else {btn.style.display = 'inline-flex'; }
}/* 页面初次加载/筛选后(区块被替换)也确保状态正确 */
document.addEventListener('DOMContentLoaded', updateLoadMoreUI);
window.addEventListener('shopify:section:load', updateLoadMoreUI);
window.addEventListener('shopify:section:render', updateLoadMoreUI);
document.addEventListener('facets:updated', updateLoadMoreUI);//新增 MutationObserver,监听 product-grid 变化(筛选结果替换时触发)
document.addEventListener('DOMContentLoaded', () => {const productGrid = document.getElementById('product-grid');if (productGrid) {const observer = new MutationObserver(() => {updateLoadMoreUI();});observer.observe(productGrid, { childList: true, subtree: true });updateLoadMoreUI(); // 初始执行一次}
});document.addEventListener('click', function (e) {const btn = e.target.closest('#LoadMoreBtn');if (!btn) return;const productGrid = document.getElementById('product-grid');const loadMoreNumber = document.getElementById('LoadMoreNumber');if (!productGrid || !loadMoreNumber) return;// 基于已加载数量/每页动态算下一页(用 Math.ceil 避免重复加载)const perPage = parseInt(loadMoreNumber.dataset.perPage, 10) || 1;const totalProducts = parseInt(loadMoreNumber.dataset.total, 10) || 0;const totalLoadedBefore = productGrid.querySelectorAll('li.grid__item:not(.grid__item--banner)').length;const currentPage = Math.ceil(totalLoadedBefore / perPage);const nextPage = currentPage + 1;// 从现有 URL 保留所有筛选参数,只改 pageconst url = new URL(window.location.href);url.searchParams.set('page', nextPage);// 可选 loading 态const originalBtnText = btn.textContent;btn.textContent = 'Loading…';btn.disabled = true;fetch(url.href).then(response => response.text()).then(html => {const parser = new DOMParser();const doc = parser.parseFromString(html, 'text/html');// 取下一页对应的商品网格const nextGrid = doc.querySelector('#ProductGridContainer #product-grid');if (!nextGrid) {btn.style.display = 'none';return;}const nextProducts = nextGrid.querySelectorAll('li.grid__item:not(.grid__item--banner)');if (!nextProducts.length) {btn.style.display = 'none';return;}// 追加产品nextProducts.forEach(item => productGrid.appendChild(item));updateLoadMoreUI(); // 追加完产品后立刻更新 UI}).catch(err => {console.error('Load More Error:', err);}).finally(() => {btn.textContent = originalBtnText;btn.disabled = false;updateLoadMoreUI(); //兜底再更新一次});
});
</script>

http://www.dtcms.com/a/390024.html

相关文章:

  • 泛函 Φ(u) = ∫[(u″)² + u² + 2f(x)u]dx − (u′(0))² 在 u(0)=u(1) 下的驻点方程与边界条件
  • JAVA高频面试题汇总:Java+ 并发 +Spring+MySQL+ 分布式 +Redis+ 算法 +JVM 等
  • 构建与运营“爬虫 IP 代理池”的方法论
  • 【文献笔记】Point Transformer
  • Linux | i.MX6ULL Modbus 移植和使用(第二十一章)
  • 几种微前端框架的沙箱策略
  • 黑盒测试:测试用例设计之边界值设计方法(边界值分析法)(上点、离点、内点)健壮性测试、单缺陷假设理论
  • 【题解】P1548 [NOIP 1997 普及组] 棋盘问题
  • scala中for推导式详细讲解
  • React学习 ---- 基础知识学习
  • C语言实现MATLAB中的Fir1带通滤波器
  • 微信小程序开发教程(十七)
  • 9月18日星期四今日早报简报微语报早读
  • SqlSugar 问题记录
  • 记一次宝塔+nginx+php8+thinkphp8多应用下某个应用报错404的问题 - nginx、php日志全无 - 无法追踪
  • Windows Server远程桌面(RDP)安全优化
  • 工具链过于分散会导致哪些问题
  • 【RAG】Youtu-GraphRAG
  • 惠普LaserJet Pro M203dn黑白激光打印机双面卡纸维修一例
  • 专题二 二叉树中的深度优先搜索
  • Git 多人协作(1)
  • 设计模式第三章(迭代器模式)
  • 网络原理(4):HTTP协议 -- HTTP请求 -- 首行(请求方法)
  • 密钥下发服务中心:双重验证 + 实时监控的轻量级密钥管理解决方案
  • 硬件 - RK3588部分(4) - 原理图 - RK806
  • Sass开发【三】
  • 百度之星2025(第二场)
  • Ovis-U1:阿里巴巴推出的统一的多模态理解与生成模型
  • 深入剖析C++智能指针:unique_ptr与shared_ptr的资源管理哲学
  • 创建索引失败,表一直查询不了