Shopify 知识库 · 指南
Dawn 筛选、排序与分页:分区请求、URL 与移动抽屉
基于 Dawn v15.3.0 固定源码,追踪集合页与搜索页的筛选表单、Section Rendering 局部刷新、pushState URL、已选条件移除、价格区间、移动筛选抽屉与分页,并对照通用契约列出差距。
历史资料
请结合文中的适用版本和来源阅读。
本文基于 Dawn v15.3.0,源码固定到 ecb06c3(核验于 2026-09-29)。结论限于此提交,不表示其他版本行为相同;本篇为静态源码分析,未完成店铺运行验证。Dawn 上游已发布 v16.0.0(2026-08-10,GitHub Releases 核对),本库 Dawn 正文统一以 v15.3.0 为基线,升级前需重新核对。
对应哪些通用模式
对应集合浏览与筛选模块、Pagination,移动端筛选对应Drawer。集合页与搜索结果页共用同一个 facets 片段和 facets.js;搜索框与建议见Dawn 预测搜索。
入口与文件
| 文件 | 职责 | 何时输出 |
|---|---|---|
| sections/main-collection-product-grid.liquid | 分页、渲染 facets、商品网格、pagination,含空结果分支 | 集合模板 |
| sections/main-search.liquid | 同构,paginate 每页 24;search.filters 非空才渲染 facets | 搜索模板 |
| snippets/facets.liquid | 桌面表单、menu-drawer 抽屉、已选条件、计数、排序 | 由上两者 render |
| snippets/price-facet.liquid | 价格区间两个文本输入 | 价格筛选 |
| assets/facets.js | facet-filters-form、price-range、facet-remove | 页面加载 |
| snippets/pagination.liquid | 页码链接 nav | paginate.pages > 1 |
数据从哪来
- 筛选与排序对象:
collection.filters/search.filters(filter.type为boolean、list、price_range,active_values、values、url_to_remove、param_name)、sort_options、sort_by、products_count/all_products_count。 - URL 参数:复选框
name取value.param_name,价格输入取filter.min_value.param_name/max_value.param_name;参数命名规则见通用模块页,本文未核对实际值。 - 商家设置(
main-collection-product-gridschema):products_per_page(8–36,步长 4,默认 16)、enable_filtering、filter_type(horizontal默认 /vertical/drawer)、enable_sorting、quick_add等;搜索页有同名筛选设置。
交互怎样运行
- 表单
input事件经 800ms 防抖触发onSubmitHandler,无提交按钮:用FormData生成查询串(桌面合并排序与筛选表单)。 renderPage请求${pathname}?section_id=<#product-grid 的 data-id>&<查询串>,即对当前 URL 做 Section Rendering,再替换#ProductGridContainer、各.js-filter组、.active-facets-*、.sorting与计数节点;响应缓存在内存数组filterData(以完整 URL 为键,页面会话内不失效)。history.pushState({searchParams}, '', pathname?查询串)写回地址栏;popstate读取 state 重新渲染。表单数据不含page,所以任何筛选、排序变化都回到第一页。- 已选条件是
<facet-remove><a href=url_to_remove>:JS 把链接设为role="button",点击时阻止跳转并按其查询串局部刷新;“全部移除”指向collection.url或搜索地址。 - 价格区间是两个
type="text" inputmode="decimal"输入,price-range在change时按data-min/data-max钳制,keydown只放行数字、分隔符与少数控制键;没有滑块,不依赖拖拽(money_without_currency输出与提交参数的小数格式是否一致,未核验)。 - 移动端:
<menu-drawer data-breakpoint="mobile">内的details,来自global.js的MenuDrawer(Escape 关闭、trapFocus、失焦关闭),每个筛选组是嵌套details。勾选立即请求,“Apply”只是点击summary关闭抽屉。horizontal/vertical时抽屉仅在 <750px 显示,drawer时各宽度都用。 - 分页不由 JS 接管:
pagination输出普通<a href>,换页是整页导航;layout/theme.liquid的<title>在current_page != 1时追加“Page N”。 - 无 JS:排序与筛选表单无
action、提交按钮或noscript回退(全仓库noscript搜索无命中),已选条件与页码仍是真实链接;Apply 依赖onclick。
与通用契约的一致与差距
| 契约(集合浏览与筛选) | Dawn v15.3.0 | 判断 |
|---|---|---|
| URL 可重建、可返回 | pushState + popstate 重渲染;地址即查询串 | 一致;canonical_url 是否含筛选参数、是否有 noindex 未核验(全仓库搜索 noindex、rel="next"、rel="prev" 无命中) |
| 结果计数通知 | .product-count 为 role="status",刷新后替换其中 #ProductCount 内容 | 一致 |
| 加载中旧结果保留 | 给 .collection 加 loading 类与图标,无 aria-busy(全仓库搜索无命中) | 部分一致 |
| 取消过期请求、错误处理 | fetch 无 AbortController、无 response.ok 检查、无 catch | 差距:乱序响应会互相覆盖,失败时 loading 状态可能不复位(静态推断) |
| 焦点保持 | 复选框所在 .facets-wrap 被整体替换,主题显式把焦点移到该组 summary(移动端为返回按钮);价格文本输入例外;facets.js 仅此一处 .focus() | 差距:不会回页首,但焦点从被操作的控件跳到组标题;排序下拉与被点击的已选标签随 innerHTML 被替换,未见恢复焦点代码,实际落点未核验 |
| 自动应用须事先告知(WCAG 3.2.2) | 800ms 后自动请求;排序下拉的 aria-describedby 指向 a11y-refresh-page-message,其默认英文文案表示选择会整页刷新 | 差距:文案与 JS 局部刷新不符;复选框无说明 |
| 筛选值为 0 | count == 0 且未选中时 disabled 并加 disabled 类,仍显示 | 一致(显示为不可选) |
| 分组与已选条件 | 每组 fieldset + 视觉隐藏 legend,summary 的 aria-label 含已选数量;标签文字为“筛选名: 值”并附视觉隐藏“Remove filter”,另有“全部移除” | 一致 |
| 零结果 | 提示文案加指向 collection.url 的“少用筛选”链接,整页跳转;已选标签仍可移除;已读分支中无客服入口,HTTP 状态码未核验 | 部分一致 |
| 计数文案 | 桌面按 value.count == 1 选单复数键,移动端写成 value.count == '1'(字符串) | 疑似不一致,Liquid 中二者是否等价未核验 |
| 抽屉作为模态 | menu-drawer 有焦点圈定与 Escape,但未见 role="dialog" / aria-modal(facets.liquid 中无命中) | 差距 |
分页链接与 aria-current | nav 带 aria-label,当前页 aria-current="page"(role="link" 无 href),上一页 / 下一页有 aria-label | 一致;经分区响应重渲染的页码链接是否带 section_id 未核验 |
定制入口与风险
商家可改上述 section 设置;筛选项本身来自 Search & Discovery,不在主题里。开发侧耦合点:#ProductGridContainer、#product-grid 的 data-id、#ProductCount、.js-filter、FacetFiltersFormMobile 等 id 被 facets.js 硬编码;renderAdditionalElements 每次重新调用 bindEvents(),会给未被替换的元素重复绑定监听,影响未核验;分区被 innerHTML 替换后,global.js 启动时给 [id^="Details-"] summary 设置的 role、aria-expanded 不会自动补到新节点(推断,未验证)。
建议的验证范围
本篇均未执行:多筛选组合的 URL 与后退前进;限速下连续勾选,确认乱序与失败复位;零结果 URL 的状态码与 canonical;键盘完成筛选、移除、排序并记录焦点落点;读屏软件核对计数与排序说明;<750px 抽屉的焦点循环;第二页页码链接是否保留筛选参数。