Shopify 滚动与产品叙事
按信息顺序组织层次、章节和图文,手机端回到自然阅读路径。
分层视差场景
背景图以小于内容的速度移动,突出一句产品或品牌信息。
已暂停,点击播放后体验交互。
演示使用抽象图形和示例文案。滚动效果在局部预览区内体验,接入页面时应使用 Section 自身的滚动区间。
适合放在 Shopify 的哪个位置?
品牌故事或产品介绍 Section;文字与背景图片分开输出,图片只承担辅助视觉。
交互和代码如何运作?
在演示内部滚动容器中读取 scrollTop,用 requestAnimationFrame 合并更新,并把位移限制为 ±30px。接入页面时改为该 Section 相对视口的进度,不复制预览内滚动容器。
触摸、键盘和减少动态效果如何处理?
减少动态效果时背景静止;手机减小位移或关闭。正文始终在普通文档流中,不锁住页面滚动。
上线前要检查什么?
图片移动不露底;正文可读;暂停不继续调帧;移除 Section 后回收监听。
示例源码 · parallax-scene.html
<section class="fx" data-effect="parallax-scene"><small>09 / LAYERS WITH A PURPOSE</small><div class="scroller" tabindex="0" aria-label="Scroll the layered scene"><div class="spacer">Scroll inside this preview ↓</div><div class="scene"><div class="art scene-art" aria-hidden="true"></div><h2>LESS, BUT<br>BETTER.</h2></div><div class="spacer">The message remains in normal document flow.</div></div></section>
示例源码 · parallax-scene.css
*{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}
.scene{min-height:260px;position:relative;display:grid;place-items:center;overflow:hidden}.scene-art{position:absolute;inset:-35px 0;min-height:0;will-change:transform}.scene h2{position:relative;text-align:center;text-shadow:0 2px 20px #0009}.scroller{height:290px}
示例源码 · parallax-scene.js
// 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 scroller=root.querySelector('.scroller'),art=root.querySelector('.scene-art');let frame=0;
function render(){frame=0;if(active&&!reduced)art.style.transform=`translateY(${Math.max(-30,Math.min(30,(scroller.scrollTop-80)*.18))}px)`;}
on(scroller,'scroll',()=>{if(active&&!frame)frame=requestAnimationFrame(render);},{passive:true});start=render;stop=()=>{cancelAnimationFrame(frame);frame=0;};cleanup=()=>{cancelAnimationFrame(frame);art.style.transform='';};
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 案例站中的参考效果这是根据同类交互重新编写的轻量实现,不复刻参考站主题及其整包源代码。
WESWOO 文章:对应文章经核对匹配后再补充。
章节叠层卡片
按先后顺序叠放短章节,用于介绍材料、工艺或设计过程。
已暂停,点击播放后体验交互。
演示使用抽象图形和示例文案。滚动效果在局部预览区内体验,接入页面时应使用 Section 自身的滚动区间。
适合放在 Shopify 的哪个位置?
产品故事 Section;每个 block 是一个短章节,图文保持同一源顺序,不把购买表单放进叠层。
交互和代码如何运作?
使用局部 position:sticky 和递增 top,容器控制叠层结束位置;不需要滚动锁定或额外 pin 库。预览暂停后回到普通卡片排列。
触摸、键盘和减少动态效果如何处理?
减少动态效果恢复普通文档流;手机内容过长时同样应改为纵向阅读,或复用主题已有卡片滑动方案。
上线前要检查什么?
卡片文字完整可读;最终章节不遮挡下一节;键盘顺序与视觉顺序一致;手机不被叠层困住。
示例源码 · pinned-stack.html
<section class="fx" data-effect="pinned-stack"><small>10 / A STORY IN LAYERS</small><div class="scroller" tabindex="0" aria-label="Scroll the stacked story"><div class="spacer">Scroll to stack the chapters ↓</div><div class="stack"><article class="stack-card"><div class="art" data-art="1"></div><strong>01 — The idea</strong></article><article class="stack-card"><div class="art" data-art="2"></div><strong>02 — The material</strong></article><article class="stack-card"><div class="art" data-art="3"></div><strong>03 — The detail</strong></article></div><div class="spacer">Keep each chapter short enough to read.</div></div></section>
示例源码 · pinned-stack.css
*{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}
.stack{padding:0 15px 65px}.stack-card{position:sticky;top:8px;background:#254336;padding:10px;border-radius:12px;margin-bottom:90px;box-shadow:0 8px 24px #0003}.stack-card:nth-child(2){top:19px}.stack-card:nth-child(3){top:30px}.stack-card .art{min-height:160px}.stack-card strong{display:block;padding:10px}.fx[data-playing=false] .stack-card{position:relative;top:0}@media(prefers-reduced-motion:reduce){.stack-card{position:relative;top:0!important}}
示例源码 · pinned-stack.js
// 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=()=>{};
// Sticky positioning supplies the effect. No scroll listener or pinning library.
// The shared lifecycle toggles data-playing; the reduced-motion CSS returns normal flow.
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 案例站中的参考效果这是根据同类交互重新编写的轻量实现,不复刻参考站主题及其整包源代码。
WESWOO 文章:对应文章经核对匹配后再补充。
纵向驱动横向图库
把连续三个视觉章节放在横向轨道中,通过有限的一段纵向进度浏览。
已暂停,点击播放后体验交互。
演示使用抽象图形和示例文案。滚动效果在局部预览区内体验,接入页面时应使用 Section 自身的滚动区间。
适合放在 Shopify 的哪个位置?
品牌专题 Section;只用于视觉章节,不替代商品列表筛选和购买流程。
交互和代码如何运作?
桌面在局部 sticky 区间,把纵向进度归一到 0—1,再乘轨道超出容器的宽度。resize 重新测量;实际页面使用 Section 自身滚动区间。
触摸、键盘和减少动态效果如何处理?
手机与减少动态效果使用可直接横向滑动的普通图库,不强制横向滚轮映射。预览内部滚动用于说明计算关系。
上线前要检查什么?
首张、末张都完整到达;调整窗口后没有空白尾部;页面纵向滚动仍正常;暂停清理未执行帧。
示例源码 · horizontal-gallery.html
<section class="fx" data-effect="horizontal-gallery"><small>11 / A HORIZONTAL CHAPTER</small><div class="scroller" tabindex="0" aria-label="Scroll vertically to move the gallery"><div class="horizontal-story"><div class="horizontal-pin"><div class="horizontal-track"><div class="art" data-art="1"><strong>01 / Explore</strong></div><div class="art" data-art="2"><strong>02 / Compare</strong></div><div class="art" data-art="3"><strong>03 / Decide</strong></div></div></div></div></div><p class="hint">On small screens or reduced motion, swipe the gallery directly.</p></section>
示例源码 · horizontal-gallery.css
*{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}
.horizontal-story{height:850px}.horizontal-pin{position:sticky;top:0;height:270px;overflow:hidden}.horizontal-track{display:flex;gap:15px;padding:10px;width:max-content;will-change:transform}.horizontal-track .art{width:min(440px,70vw);height:240px;flex:none}.horizontal-track strong{position:absolute;bottom:20px;left:20px;z-index:1}.fx[data-playing=false] .horizontal-story{height:270px}.fx[data-playing=false] .horizontal-pin{overflow-x:auto}.fx[data-playing=false] .horizontal-track{transform:none!important}@media(max-width:600px),(prefers-reduced-motion:reduce){.horizontal-story{height:270px}.horizontal-pin{overflow-x:auto;scroll-snap-type:x mandatory}.horizontal-track{transform:none!important}.horizontal-track .art{scroll-snap-align:start}}
示例源码 · horizontal-gallery.js
// 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 scroller=root.querySelector('.scroller'),story=root.querySelector('.horizontal-story'),track=root.querySelector('.horizontal-track'),pin=root.querySelector('.horizontal-pin'),small=matchMedia('(max-width:600px)');let frame=0;
function render(){frame=0;if(!active||reduced||small.matches){track.style.transform='';return;}const max=Math.max(1,story.offsetHeight-pin.clientHeight),p=Math.max(0,Math.min(1,scroller.scrollTop/max));track.style.transform=`translateX(${-p*Math.max(0,track.scrollWidth-pin.clientWidth)}px)`;}
on(scroller,'scroll',()=>{if(active&&!frame)frame=requestAnimationFrame(render);},{passive:true});on(window,'resize',render);start=render;stop=()=>{cancelAnimationFrame(frame);frame=0;track.style.transform='';};cleanup=stop;
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 案例站中的参考效果这是根据同类交互重新编写的轻量实现,不复刻参考站主题及其整包源代码。
WESWOO 文章:对应文章经核对匹配后再补充。
固定图与分段说明
图像保持在视野内,右侧依次解释结构、材料和使用价值。
已暂停,点击播放后体验交互。
演示使用抽象图形和示例文案。滚动效果在局部预览区内体验,接入页面时应使用 Section 自身的滚动区间。
适合放在 Shopify 的哪个位置?
商品详情的介绍 Section;把规格事实放在文本中,不将关键参数做进图片。
交互和代码如何运作?
两列共享同一父容器,图像列 sticky 且 align-self:start,说明列按正常顺序输出;父容器边界自然结束固定状态。
触摸、键盘和减少动态效果如何处理?
窄屏和减少动态效果改为单列图文,避免固定图占满屏幕。内容无需脚本即可读取。
上线前要检查什么?
短屏能看到图下说明;段落源顺序合理;固定图在 Section 末端结束;手机无覆盖和截断。
示例源码 · sticky-story.html
<section class="fx" data-effect="sticky-story"><small>12 / EXPLAIN THEN SHOW</small><div class="scroller" tabindex="0" aria-label="Read the two-column product story"><div class="story-grid"><div class="story-image"><div class="art" role="img" aria-label="Abstract product construction illustration"></div><p>One visual. Three meaningful details.</p></div><div class="story-copy"><article><h3>01 / Structure</h3><p>Start with the job this part has to do.</p></article><article><h3>02 / Material</h3><p>Explain the choice, its source and its limits.</p></article><article><h3>03 / Everyday use</h3><p>Connect the detail to a real usage decision.</p></article></div></div></div></section>
示例源码 · sticky-story.css
*{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}
.story-grid{display:grid;grid-template-columns:1fr 1fr;gap:25px;padding:10px}.story-image{position:sticky;top:10px;align-self:start}.story-image p{font-size:12px;margin-top:12px}.story-copy article{min-height:200px;padding:15px 0}.story-copy h3{color:#d5f875}.fx[data-playing=false] .story-image{position:static}@media(max-width:600px),(prefers-reduced-motion:reduce){.story-grid{grid-template-columns:1fr}.story-image{position:static}.story-copy article{min-height:120px}}
示例源码 · sticky-story.js
// 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=()=>{};
// CSS sticky is bounded by .story-grid, never by a transformed global scroller.
// Small screens and reduced-motion preferences use an ordinary reading order.
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 案例站中的参考效果这是根据同类交互重新编写的轻量实现,不复刻参考站主题及其整包源代码。
WESWOO 文章:对应文章经核对匹配后再补充。
内容确定后,再选择特效
先在知识库确定页面逻辑,再在开发知识库连接真实数据。只在有助理解信息的位置增加动效,并在接入后检查性能。
接入 Shopify Section
预览由普通 HTML、CSS 和 JavaScript 组成。先在复制主题中逐个接入,再由 Section 设置提供真实文案和图片。
- 将所选 CSS 保存为 assets/ww-effects.css,JavaScript 保存为 assets/ww-selected-effect.js;与主题整合前,把 CSS 限定在该 Section 内。
- 用所选 .fx 元素替换下方壳层占位,将示例文字、抽象图形改为 Section 设置及有授权的 image_tag 输出,控件标签接入语言文件。
- 用生命周期脚本防止重复挂载,离屏暂停、shopify:section:unload 时销毁;把局部滚动演示改成实际 Section 的区间计算,在主题编辑器和真实设备验收。
接入壳层:先插入所选效果 HTML 才能使用,不是可直接安装的主题或应用。
sections/ww-effect.liquid
{% 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
// 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 平台使用许可,上述示例未打包该仓库代码。
- Shopify/dawn · modal / slider base classes
- Shopify/dawn · product media gallery
- Shopify/dawn · license
依据核对 2026-11-12