Horizon 商品媒体放大与热点:画廊、缩放对话框与 Shop the look
本文基于 Horizon 4.2.0,源码固定到 5acd1b6(核验于 2026-10-07)。结论限于此提交,不表示其他版本行为相同;本篇为静态源码分析,未完成店铺运行验证。Dawn 上游已发布 v16.0.0(2026-08-10,GitHub Releases 核对),本库 Dawn 正文统一以 v15.3.0 为基线,升级前需重新核对;本库 Horizon 正文统一以 4.2.0 为基线(固定提交中 config/settings_schema.json 的 theme_version 为 4.2.0)。
对应哪些通用模式
覆盖 Media Gallery、Modal(放大对话框)、Hotspot 与变体媒体模块。商品页只写到 _product-media-gallery 是媒体入口,变体选择写了选择器请求链;本篇补媒体排序、放大、视频与 3D,以及独立的热点 Section。
入口与文件
| 文件 | 职责 / 何时输出 |
|---|---|
| _product-media-gallery.liquid → product-media-gallery-content.liquid | 画廊设置与主体:排序、slideshow 与 grid 两套 DOM、zoom-dialog;商品模板的 static 块,精选商品轮播复用 |
| product-media.liquid、video.liquid、media.js | 单个媒体:图片、deferred-media 视频、product-model 3D |
| media-gallery.js、zoom-dialog.js、drag-zoom-wrapper.js | 变体切换换画廊、放大对话框(仅 zoom 开启时加载)、触屏缩放(页面有图片时加载) |
| product-hotspots.liquid、_hotspot-product.liquid、product-hotspot.js、quick-add.js | 热点 Section、热点块与对话框;触屏改走快速购买弹窗 |
数据从哪来
画廊取 closest.product.media(图片、视频、外部视频、3D 混排)。所选变体的 featured_media 置顶,其余保持商品顺序;hide_variants 开启时按 images | where: 'attached_to_variant?', true 剔除其他变体绑定的图片;变体没有 featured_media 时既不置顶也不剔除。Horizon 只表达“一张变体主图置顶”,没有“每变体一组专属媒体”的结构,与变体媒体模块所说平台只提供 variant.featured_media 一致。默认商品模板为 grid、两列、zoom 与 hide_variants 开启。
热点不读集合:Section 设置 image、section_height(auto、21 / 9、16 / 9、4 / 3),热点块设置 product、x-position、y-position(0–100 整数百分比)。价格与可售状态来自 closest.product 与 price snippet;schema 无 max_blocks。
交互怎样运行
- 两套 DOM:grid 时 ≥750px 显示
ul.media-gallery__grid,<750px 用 slideshow(见幻灯片篇)。 - 放大:grid 每张图覆盖一个
button,carousel 与移动端把on:click挂在slideshow-slide上。zoom-dialog.js调用dialog.showModal(),滚动时以 IntersectionObserver 找最可见项,同步缩略图aria-selected并让底层 slideshow 无动画跟随;View Transition 仅在支持、非低功耗、未开启减少动态时使用。drag-zoom-wrapper.js只在 <750px 绑定 touch(捏合 1–5 倍、双击 1↔1.5 倍),桌面“放大”只是100vw大图。 - 视频与 3D:视频经
video.liquid(disable_controls: true)输出deferred-media,有封面图时点击封面才克隆template里的video或iframe,随后静音、autoplay、无原生控件,仅一个播放/暂停切换钮;其他deferred-media在mediaStartedPlaying或dialog:close时暂停。3D 的product-model点击后载入model_viewer_tag。 - 变体切换:
media-gallery.js监听productSelect(忽略product-card内的),用返回 HTML 里的media-gallery整块replaceWith。 - 热点:≥750px 且非触屏,pointerenter 120ms 或点击后量对话框尺寸、选位写入
data-placement,以非模态dialog.show()打开;点击外部、Escape 或 keyup 时焦点不在其内即关闭。<750px 或触屏,点击改调quick-add-component的handleClick打开全局快速购买弹窗,热点内对话框display: none;无商品且非设计模式时,触屏与移动端隐藏该热点。
与通用契约的一致与差距
Media Gallery / Modal
- 一致:控件为真实
button,选中态用aria-selected;单项不输出控件;原生showModal()、带aria-label的关闭钮与 Escape;减少动态时缩略图滚动改即时;--media-preview-ratio按preview_image.aspect_ratio预留空间。 - 键盘放大入口只在 grid 桌面布局存在;carousel 与移动端的放大绑在非聚焦的
slideshow-slide上(静态阅读)。 - 放大
dialog无aria-label或aria-labelledby;缩放手势仅触屏。zoom 按钮由服务端输出,脚本失败即成无效按钮,与 Modal 页要求不符。 alt直取media.alt,无回退;按钮读作 “Zoom media N”、缩略图读作 “Slide N of M”,不含图片描述;视频无字幕与音量入口(未见track)。product-media.liquid读block.settings.video_loop,画廊调用未传block,该设置能否生效未核验。- 3D:无调用点向
product-media传first_3d_model与selected_product,按静态阅读ProductModelJSON-脚本不会输出,AR 是否可用未核验。zoom开启且为 model 时,product-media-gallery-content.liquid第 396 行的attributes以孤立的"开头并写入slideshow-slide标签,容错结果未核验。 scroll-lock属性写在放大dialog上,但ZoomDialog不调用lockScroll,在 assets、snippets、layout、sections、blocks 中未找到读取它的代码;焦点返回靠原生close(),触发元素为 slide 时落点未核验。
Hotspot
- 一致:触发器是
button,aria-label含商品名;点击区 44px(--button-size取--minimum-touch-target),视觉圆 36px;价格与可售状态取运行时;悬停内容可移入、Escape 可关;无脉冲动画。 - 无图旁文字列表。除 locales 外,
hotspot只出现在上表四个文件与cart-drawer.js;无 JavaScript 时商品链接在未打开的dialog内,不可达。 - 坐标是相对
.hotspots-container的百分比。section_height为auto且有图时容器比例取图片aspect_ratio;选 21/9、16/9、4/3 时图用object-fit: cover裁切而坐标不换算,也无 focal point 与分断点坐标,热点页所说的位置漂移在此成立。 - 售罄:
hotspot_product.available为假时显示 “Sold out” 并保留热点与链接;按静态阅读,触屏点击调用的#openQuickAddModal在售罄分支找不到quick-add-component而直接返回,点击无反应。下架、不在当前市场销售的分支未见,未核验。 - 键盘:Enter 打开的非模态对话框不接管焦点,Tab 才进入其中的链接与按钮;焦点仍在触发钮时的 keyup 会触发关闭(静态阅读)。触发钮依赖全局
*:focus-visible,对比度未核验。 - 无数量上限,零个热点仍输出图与空容器,一个热点不退化;对话框的模糊、缩放、回弹过渡未包
prefers-reduced-motion。
定制入口与风险
编辑器可改画廊的 media_presentation、media_columns、aspect_ratio、constrain_to_viewport、zoom、video_loop、hide_variants 等,热点的 image、section_height、hotspot_color 与各块的 product、坐标。耦合点:media-gallery.js 依赖 slideshow 与 zoomDialogComponent 两个 ref;热点依赖 QuickAddComponent.productPageUrl 经 closest('product-hotspot-component') 取链接,改结构要同时检查快速购买。
建议的验证范围
本篇未执行。grid 与 carousel 各测键盘能否放大、Escape 与焦点落点;带视频与 3D 的商品测暂停互斥;多变体商品测置顶与 hide_variants;热点在三档 section_height 下核对坐标;售罄、无商品、下架商品在桌面与触屏上的点击;关闭 JavaScript 与开启减少动态各测一次。