MOTION / 166
Scroll reveal once
見えたら一度だけ再生
枠の中に入った要素が、一度だけ浮かび上がります。
見本の中を操作して変化を試してください。
ひとつ試すなら
どこが変わるかを観察して、似た表現と見比べてみよう。
こんなときに
読み進める順序に沿った、要素の登場。
仕組みと使い方
連動型とは違い、表示位置をきっかけに時間ベースのアニメーションが始まります。
対応状況は使用する機能と端末により異なります。公式資料も参照してください。
表現名には通称や説明のための名称を含みます。デモと掲載コードはこの図鑑のオリジナルです。作者について
実装コードを見る
<!doctype html><html lang="ja"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><style>body{margin:0;min-height:300px}.sample{width:100%;height:100%;min-height:190px;position:relative;box-sizing:border-box;display:grid;place-items:center;overflow:hidden;padding:18px;background:#f4f3ee;color:#242424;font-family:Arial,Meiryo,sans-serif}.sample *{box-sizing:border-box}.sample button{font:600 11px Arial,Meiryo,sans-serif;border:1px solid #333;border-radius:999px;min-height:36px;padding:8px 14px;background:#fff;color:#222;cursor:pointer;touch-action:manipulation}.sample :focus-visible{outline:2px solid #555;outline-offset:4px}.sample small{font-size:10px;color:#666;line-height:1.5}.sample .adv-wrap{width:min(100%,260px);display:flex;flex-direction:column;align-items:center;gap:10px}.sample .adv-status{min-height:13px;text-align:center;font-size:10px;color:#666}.sample svg{max-width:100%}@media(prefers-reduced-motion:reduce){.sample *{transition:none!important;animation:none!important}}.sample .reveal-port{width:100%;height:135px;overflow:auto;overscroll-behavior:contain;border:1px solid #bbb;border-radius:10px;scrollbar-width:thin;padding:12px}.sample .reveal-intro{height:145px;display:flex;flex-direction:column;gap:15px}.sample .reveal-intro b{font-size:30px;line-height:.95;letter-spacing:-.06em}.sample .reveal-item{margin:15px 0;padding:27px 12px;background:#deddd4;border-radius:7px;font-size:12px}.sample .reveal-item[data-pending]{opacity:0}</style><div class="sample"><div class="adv-wrap"><div class="reveal-port" tabindex="0" aria-label="スクロールするとカードが登場"><div class="reveal-intro"><b>hello,<br>scroll.</b><small>この枠の中をスクロール ↓</small></div><div class="reveal-item">01 — 見つける。</div><div class="reveal-item">02 — 名前を知る。</div><div class="reveal-item">03 — 使ってみる。</div></div><button type="button" class="reveal-reset">最初から</button></div></div><script>const lifecycle=function lifecycle(root,options={}){const abort=new AbortController(),animations=new Set(),cleanup=[],pause=[];const os=globalThis.matchMedia?.('(prefers-reduced-motion:reduce)').matches||false;let reduced=options.reducedMotion??os,live=true;return{get reduced(){return reduced},get live(){return live},on(node,event,fn,opts={}){node.addEventListener(event,fn,{...opts,signal:abort.signal})},track(a){animations.add(a);a.finished.then(()=>animations.delete(a),()=>animations.delete(a));return a},stop(){animations.forEach(a=>a.cancel());animations.clear()},cleanup(fn){cleanup.push(fn)},onPause(fn){pause.push(fn)},destroy(){if(!live)return;live=false;abort.abort();this.stop();cleanup.forEach(fn=>fn())},setAuto(value){reduced=!value||os;if(reduced){this.stop();pause.forEach(fn=>fn())}}}};const control=(function reveal(root,options){const c=lifecycle(root,options),port=root.querySelector('.reveal-port'),items=[...root.querySelectorAll('.reveal-item')],reset=root.querySelector('.reveal-reset');let observer;function arm(){observer?.disconnect();c.stop();items.forEach(n=>n.dataset.pending='true');port.scrollTop=0;if(!('IntersectionObserver'in globalThis)){items.forEach(n=>delete n.dataset.pending);return}observer=new IntersectionObserver(entries=>entries.forEach(({target,isIntersecting})=>{if(!isIntersecting||!c.live)return;delete target.dataset.pending;if(!c.reduced)c.track(target.animate([{opacity:0,transform:'translateY(20px)'},{opacity:1,transform:'translateY(0)'}],{duration:500,easing:'cubic-bezier(.2,.7,.2,1)'}));observer.unobserve(target)}),{root:port,threshold:.4});items.forEach(n=>observer.observe(n))}c.on(reset,'click',arm);c.cleanup(()=>observer?.disconnect());c.onPause(()=>items.forEach(n=>delete n.dataset.pending));arm();return c})(document,{reducedMotion:matchMedia('(prefers-reduced-motion:reduce)').matches});addEventListener('pagehide',()=>control.destroy(),{once:true});</script></html>