案例作品集 浏览精选项目

WESWOO 提供 Shopify Plus 项目开发费抵扣,至多 US$4,800

Shopify 特效与定制模块库 / 指针、按钮与菜单

Shopify 指针、按钮与菜单

从可点击目标到预览与菜单,先保证触摸和键盘,再增强鼠标反馈。

列表悬停预览

先在列表中辨认条目,再查看相应图片;预览不遮挡条目和下一步操作。

已暂停,点击播放后体验交互。

演示使用抽象图形和示例文案。滚动效果在局部预览区内体验,接入页面时应使用 Section 自身的滚动区间。

适合放在 Shopify 的哪个位置?

案例、系列导览或品牌内容 Section;标题、目标链接与预览图片使用同一个 block 数据。

交互和代码如何运作?

鼠标进入、键盘聚焦和点击共用 select 函数,更新选中状态、图片标识和替代文本。真实图片使用 image_url / image_tag,并预留尺寸。

触摸、键盘和减少动态效果如何处理?

手机用点击选择,不依赖悬停;有减少动态效果偏好时直接替换内容。预览只是辅助,真实跳转另有明确链接。

上线前要检查什么?

每个输入方式选中相同条目;替代文本随图变化;图片不盖住目标;缺图仍有文字可用。

示例源码 · hover-preview.html
HTML · WESWOO
<section class="fx" data-effect="hover-preview"><small>06 / PREVIEW BEFORE OPENING</small><div class="preview-layout"><div class="preview-list" aria-label="Choose a concept"><button type="button" data-preview="1" aria-pressed="true">01 — Product story</button><button type="button" data-preview="2" aria-pressed="false">02 — Collection guide</button><button type="button" data-preview="3" aria-pressed="false">03 — Brand journal</button></div><div class="art preview-art" data-art="1" role="img" aria-label="Abstract product story illustration"></div></div><p class="hint">Hover, focus or tap to select a preview.</p></section>

示例源码 · hover-preview.css
CSS · WESWOO
*{box-sizing:border-box}body{margin:0;background:#11251f;color:#eef5e6;font:15px/1.5 system-ui,sans-serif}button,input{font:inherit}button{cursor:pointer}button:focus-visible,a:focus-visible,input:focus-visible,[tabindex]:focus-visible{outline:3px solid #d5f875;outline-offset:4px}button{border:1px solid #819a8a;border-radius:30px;padding:10px 18px;color:inherit;background:transparent}button:hover{background:#294638}.fx{position:relative;min-height:350px;padding:30px;overflow:hidden;display:grid;align-content:center;gap:20px}.fx h2,.fx p{margin:0}.fx h2{font-size:clamp(32px,7vw,68px);letter-spacing:-.05em;line-height:1.08}.fx small{letter-spacing:.12em;color:#bed0c1}.art{position:relative;min-height:180px;border-radius:14px;overflow:hidden;background:radial-gradient(circle at 70% 25%,#d5f875 0 14%,transparent 14.5%),linear-gradient(135deg,#087e75,#183c30)}.art:before{content:'';position:absolute;width:130px;height:130px;border:25px solid #cde4d0;border-radius:30px;left:20%;top:25%;transform:rotate(-25deg)}.art:after{content:'';position:absolute;inset:16px;border:1px solid #cde4d044;border-radius:8px}.art[data-art="2"]{background:radial-gradient(circle at 25% 70%,#a7b7ff 0 20%,transparent 20.5%),linear-gradient(45deg,#102943,#687ab1)}.art[data-art="3"]{background:radial-gradient(circle at 60% 50%,#ffd9a9 0 24%,transparent 24.5%),linear-gradient(135deg,#492e30,#a97652)}.hint{font-size:12px;color:#b9cbbd}.controls{display:flex;gap:8px;flex-wrap:wrap}.scroller{height:270px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:#b5cc91 #18382d;border-radius:10px;position:relative;outline-offset:2px}.spacer{height:100px;display:grid;place-items:center;color:#b9cbbd;font-size:12px}.fx [hidden]{display:none!important}.fx[data-playing="false"] *{animation-play-state:paused!important}.fx button[aria-pressed="true"]{background:#d5f875;color:#11251f}.fx a{color:inherit;text-decoration:underline;text-underline-offset:4px}
@media(max-width:500px){.fx{padding:20px;min-height:330px}.fx h2{font-size:36px}.art{min-height:160px}}
@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation:none!important;transition:none!important;scroll-behavior:auto!important}}
.fx[data-reduced-motion=true] *,.fx[data-reduced-motion=true] *:before,.fx[data-reduced-motion=true] *:after{animation:none!important;transition:none!important;scroll-behavior:auto!important}
.fx[data-reduced-motion=true] .stack-card{position:relative;top:0!important}.fx[data-reduced-motion=true] .horizontal-story{height:270px}.fx[data-reduced-motion=true] .horizontal-pin{overflow-x:auto}.fx[data-reduced-motion=true] .horizontal-track{transform:none!important}.fx[data-reduced-motion=true] .story-grid{grid-template-columns:1fr}.fx[data-reduced-motion=true] .story-image{position:static}.fx[data-reduced-motion=true] .loop-window{overflow-x:auto}.fx[data-reduced-motion=true] .loop-set[aria-hidden=true]{display:none}

.preview-layout{display:grid;grid-template-columns:1fr 1fr;gap:24px;align-items:center}.preview-list{display:grid;gap:8px}.preview-list button{border-radius:0;text-align:left;border-width:0 0 1px;padding:15px 0}.preview-art{transition:opacity .25s}.preview-list button[aria-pressed=true]{background:transparent;color:#d5f875}@media(max-width:500px){.preview-layout{gap:15px;grid-template-columns:1fr}.preview-art{min-height:120px}.preview-list button{padding:8px 0}}

示例源码 · hover-preview.js
JavaScript · WESWOO
// WESWOO original educational example; no third-party runtime required.
export function mountEffect(root, {reducedMotion=false}={}) {
  const reduced=reducedMotion||matchMedia('(prefers-reduced-motion: reduce)').matches;
  root.dataset.reducedMotion=String(reduced);
  const life=new AbortController(), animations=[];
  let active=false, started=false;
  const on=(node,type,fn,options={})=>node.addEventListener(type,fn,{...options,signal:life.signal});
  const animate=(node,frames,options)=>{
    if(reduced)return null;
    const animation=node.animate(frames,options);animations.push(animation);return animation;
  };
  let start=()=>{}, stop=()=>{}, cleanup=()=>{};
const artwork=root.querySelector('.preview-art'),buttons=[...root.querySelectorAll('[data-preview]')];
function select(button){if(!active)return;buttons.forEach(item=>item.setAttribute('aria-pressed',String(item===button)));artwork.dataset.art=button.dataset.preview;artwork.setAttribute('aria-label','Abstract illustration: '+button.textContent.trim());}
buttons.forEach(button=>{on(button,'pointerenter',event=>{if(event.pointerType==='mouse')select(button);});on(button,'focus',()=>select(button));on(button,'click',()=>select(button));});
root.dataset.playing='false';
  return {
    resume(){active=true;root.dataset.playing='true';if(!started){started=true;start();}animations.forEach(a=>a.play());root.dispatchEvent(new Event('ww:resume'));},
    pause(){active=false;root.dataset.playing='false';animations.forEach(a=>a.pause());stop();},
    destroy(){active=false;life.abort();animations.forEach(a=>a.cancel());cleanup();}
  };
}

案例效果与官方依据

查看 WESWOO 案例站中的参考效果
.has-hover-image

这是根据同类交互重新编写的轻量实现,不复刻参考站主题及其整包源代码。

WESWOO 文章:对应文章经核对匹配后再补充。

内容确定后,再选择特效

先在知识库确定页面逻辑,再在开发知识库连接真实数据。只在有助理解信息的位置增加动效,并在接入后检查性能。

Shopify 知识库 · Shopify 开发知识库

接入 Shopify Section

预览由普通 HTML、CSS 和 JavaScript 组成。先在复制主题中逐个接入,再由 Section 设置提供真实文案和图片。

  1. 将所选 CSS 保存为 assets/ww-effects.css,JavaScript 保存为 assets/ww-selected-effect.js;与主题整合前,把 CSS 限定在该 Section 内。
  2. 用所选 .fx 元素替换下方壳层占位,将示例文字、抽象图形改为 Section 设置及有授权的 image_tag 输出,控件标签接入语言文件。
  3. 用生命周期脚本防止重复挂载,离屏暂停、shopify:section:unload 时销毁;把局部滚动演示改成实际 Section 的区间计算,在主题编辑器和真实设备验收。

接入壳层:先插入所选效果 HTML 才能使用,不是可直接安装的主题或应用。

sections/ww-effect.liquid
WESWOO
{% comment %} WESWOO example shell. Add ONE effect's HTML inside .fx-slot. {% endcomment %}
{{ 'ww-effects.css' | asset_url | stylesheet_tag }}
<section id="ww-effect-{{ section.id }}" data-ww-effect-section>
  <h2>{{ section.settings.heading | escape }}</h2>
  <button type="button" data-effect-toggle aria-pressed="false">Play / pause effect</button>
  <div class="fx-slot">
    {% comment %} Insert the chosen .fx element here; read copy/images from section settings. {% endcomment %}
  </div>
</section>
<script type="module" src="{{ 'ww-effect-section.js' | asset_url }}"></script>
{% schema %}
{"name":"WESWOO effect","settings":[{"type":"text","id":"heading","label":"Heading","default":"Our story"}],"presets":[{"name":"WESWOO effect"}]}
{% endschema %}

共用初始化:每个模块接入一种所选效果;多种效果需显式建立注册表和 Section 对应关系。

assets/ww-effect-section.js
WESWOO
// Put the chosen example JS in assets/ww-selected-effect.js (rename .mjs to .js).
import {mountEffect} from './ww-selected-effect.js';
const instances=new Map();
function mount(container=document){
  const roots=[...(container.matches?.('[data-ww-effect-section]')?[container]:[]),...container.querySelectorAll('[data-ww-effect-section]')];
  for(const section of roots){
    if(instances.has(section))continue;
    const root=section.querySelector('.fx'),button=section.querySelector('[data-effect-toggle]');
    if(!root||!button)continue;
    const effect=mountEffect(root),events=new AbortController();let playing=false;
    const pause=()=>{playing=false;effect.pause();button.setAttribute('aria-pressed','false');};
    button.addEventListener('click',()=>{playing=!playing;playing?effect.resume():effect.pause();button.setAttribute('aria-pressed',String(playing));},{signal:events.signal});
    const observer=new IntersectionObserver(entries=>{if(!entries[0].isIntersecting)pause();});observer.observe(section);
    document.addEventListener('visibilitychange',()=>{if(document.hidden)pause();},{signal:events.signal});
    instances.set(section,()=>{observer.disconnect();events.abort();effect.destroy();});
  }
}
document.addEventListener('shopify:section:load',event=>mount(event.target));
document.addEventListener('shopify:section:unload',event=>{for(const [section,destroy] of instances)if(section===event.target||event.target.contains(section)){destroy();instances.delete(section);}});
mount();

这些示例为 WESWOO 编写的教学实现,可查看、复制并保留 WESWOO 及本页来源说明。参考主题、第三方库、商标和案例图片仍适用各自权利,未包含在演示下载中。

Shopify Dawn 主题整合参考(固定版本)。Dawn 源码适用其 Shopify 平台使用许可,上述示例未打包该仓库代码。

依据核对 2026-11-12