INTERACTION / 169

Pointer spotlight

指先を追うスポットライト
ポインターやタップ位置に合わせて、光の中心が移動します。

見本の中を操作して変化を試してください。

ラボで詳しく試す ↗

ひとつ試すなら

光を指先で動かす

こんなときに

領域の中を探索するような表現。

仕組みと使い方

タップでも位置を変えられます。フォーカス後の矢印キーにも対応します。

ポインターspotlight追う光タップ
実装コードを見る
<!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 .spot-zone{width:100%;height:130px;border-radius:10px;background:radial-gradient(circle 75px at var(--x,50%) var(--y,50%),#f9f8ec 0%,#a7a69b 40%,#252623 100%);display:grid;place-items:center;overflow:hidden;touch-action:pan-y}.sample .spot-zone b{font-size:35px;font-weight:900;letter-spacing:-.07em;line-height:.95;mix-blend-mode:overlay;color:white;text-align:center}</style><div class="sample"><div class="adv-wrap"><div class="spot-zone" tabindex="0" role="img" aria-label="タップ、ポインター、矢印キーで光の中心を変えられる見本"><b>find<br>the good.</b></div><small>なぞる、タップする、矢印キーで動かす</small></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 spotlight(root,options){const c=lifecycle(root,options),zone=root.querySelector('.spot-zone');const move=e=>{const r=zone.getBoundingClientRect();zone.style.setProperty('--x',`${Math.max(0,Math.min(r.width,e.clientX-r.left))}px`);zone.style.setProperty('--y',`${Math.max(0,Math.min(r.height,e.clientY-r.top))}px`)};c.on(zone,'pointermove',move,{passive:true});c.on(zone,'pointerdown',move,{passive:true});c.on(zone,'pointerleave',()=>{zone.style.removeProperty('--x');zone.style.removeProperty('--y')});c.on(zone,'keydown',e=>{const pos={ArrowLeft:['25%','50%'],ArrowRight:['75%','50%'],ArrowUp:['50%','25%'],ArrowDown:['50%','75%']}[e.key];if(pos){e.preventDefault();zone.style.setProperty('--x',pos[0]);zone.style.setProperty('--y',pos[1])}});return c})(document,{reducedMotion:matchMedia('(prefers-reduced-motion:reduce)').matches});addEventListener('pagehide',()=>control.destroy(),{once:true});</script></html>