MOTION / 171
FLIP layout transition
配置換えをなめらかに
並べ替え前後の座標を測り、その差を打ち消してから動かします。
見本の中を操作して変化を試してください。
ひとつ試すなら
どこが変わるかを観察して、似た表現と見比べてみよう。
こんなときに
カードの並べ替えや、レイアウト変更。
仕組みと使い方
FLIPはFirst・Last・Invert・Playの略です。この例は位置の変化を扱います。
対応状況は使用する機能と端末により異なります。公式資料も参照してください。
表現名には通称や説明のための名称を含みます。デモと掲載コードはこの図鑑のオリジナルです。作者について
実装コードを見る
<!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 .flip-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:7px;width:100%;padding:17px 0}.sample .flip-grid i{height:74px;border:1px solid #333;border-radius:7px;display:grid;place-items:center;font:600 18px monospace;font-style:normal;background:#e4e3db}.sample .flip-grid i:nth-of-type(2n){background:#333;color:#fff}</style><div class="sample"><div class="adv-wrap"><div class="flip-grid"><i>01</i><i>02</i><i>03</i><i>04</i></div><button type="button" class="flip-order">順番を入れ替える ↗</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 flip(root,options){const c=lifecycle(root,options),grid=root.querySelector('.flip-grid'),button=root.querySelector('.flip-order');c.on(button,'click',()=>{c.stop();const nodes=[...grid.children],before=new Map(nodes.map(n=>[n,n.getBoundingClientRect()]));grid.append(nodes[0]);if(c.reduced)return;nodes.forEach(n=>{const old=before.get(n),now=n.getBoundingClientRect(),dx=old.left-now.left,dy=old.top-now.top;c.track(n.animate([{transform:`translate(${dx}px,${dy}px)`},{transform:'translate(0,0)'}],{duration:650,easing:'cubic-bezier(.22,1,.36,1)'}))})});return c})(document,{reducedMotion:matchMedia('(prefers-reduced-motion:reduce)').matches});addEventListener('pagehide',()=>control.destroy(),{once:true});</script></html>