TYPOGRAPHY / 256

Kinetic typography

文字ごとに波を渡す
各文字の位相をずらし、文字列全体に波のような動きをつくります。

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

ラボで詳しく試す ↗

ひとつ試すなら

振幅0と大きな振幅を比較し、読める範囲を探してください。

こんなときに

見出し、タイポグラフィ、文字の表情。

仕組みと使い方

振幅0と大きな振幅を比較し、読める範囲を探してください。

文字キネティック波時間差
実装コードを見る
<!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:330px}.sample{width:100%;height:100%;min-height:330px;padding:12px!important;display:flex!important;flex-direction:column;justify-content:center;align-items:center;gap:8px!important;background:#f0efeb;color:#232323;font-family:Arial,Meiryo,sans-serif;overflow:hidden;box-sizing:border-box}.sample *{box-sizing:border-box}.lab-scene{width:100%;max-width:430px;height:140px;position:relative;overflow:hidden;border-bottom:1px solid #ccc}.lab-scene:has(canvas){max-width:320px;height:auto;aspect-ratio:16/9;flex-shrink:0}.lab-canvas{display:block;width:100%;height:100%!important;outline-offset:-3px}.lab-canvas:focus-visible{outline:2px solid #222}.lab-controls{width:100%;max-width:390px;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px 14px}.lab-controls label{display:grid;grid-template-columns:1fr auto;gap:2px;font-size:11px;min-width:0}.lab-controls input{grid-column:1/-1;width:100%;min-width:0;margin:0;accent-color:#333;height:30px}.lab-controls output{font-variant-numeric:tabular-nums}.lab-actions{display:flex;gap:8px;align-items:center;flex-wrap:wrap;justify-content:center}.sample .lab-actions button{color:inherit;font:11px Arial,Meiryo,sans-serif;border:1px solid #aaa;border-radius:99px;background:transparent;min-height:44px;padding:8px 12px;cursor:pointer}.lab-status{font-size:10px;line-height:1.4;min-height:14px;margin:0;text-align:center}.lab-note{font-size:9px;line-height:1.3;color:#666;margin:0;text-align:center}.lab-actions button:focus-visible{outline:2px solid #222;outline-offset:2px}@media(prefers-reduced-motion:reduce){.sample *{animation-play-state:paused!important;transition:none!important}}.lab-scene{display:grid;place-items:center}.editorial-word{font:bold 58px Arial;letter-spacing:-4px;display:flex;align-items:center;justify-content:center;width:100%;height:100%;text-align:center}.editorial-word i{font-style:normal;display:inline-block}.editorial-word small{font-size:12px;letter-spacing:.1em}.editorial-word{font-size:clamp(22px,7vw,38px);letter-spacing:-.055em}.editorial-word i{animation:kinetic calc(var(--speed,2)*1s) ease-in-out infinite;animation-delay:calc(var(--i)*-.15s)}.changed i{animation-direction:reverse}@keyframes kinetic{50%{transform:translateY(calc(var(--height,15)*-1px))}}</style><div class="sample"><div class="lab-scene"><div class="editorial-word" aria-label="NAOYAMAN"><i aria-hidden="true" style="--i:0">N</i><i aria-hidden="true" style="--i:1">A</i><i aria-hidden="true" style="--i:2">O</i><i aria-hidden="true" style="--i:3">Y</i><i aria-hidden="true" style="--i:4">A</i><i aria-hidden="true" style="--i:5">M</i><i aria-hidden="true" style="--i:6">A</i><i aria-hidden="true" style="--i:7">N</i></div></div><div class="lab-controls"><label>振幅 px<output>15</output><input aria-label="振幅 px" type="range" min="0" max="25" step="1" value="15" data-key="height"></label><label>周期 秒<output>2</output><input aria-label="周期 秒" type="range" min="1" max="5" step="0.1" value="2" data-key="speed"></label></div><div class="lab-actions"><button data-play>停止</button><button data-reset>動きを反転</button></div><p class="lab-status" role="status"></p></div><script>const lab=(function runDomLab(root,cfg,setup){
 const abort=new AbortController(),values=Object.fromEntries(cfg.controls.map(c=>[c.key,c.value])),mq=matchMedia('(prefers-reduced-motion:reduce)');let auto=!mq.matches;
 const on=(el,event,fn)=>el.addEventListener(event,fn,{signal:abort.signal});
 const api=setup({root,values,kind:cfg.kind,on,reduced:mq.matches})||{};
 for(const input of root.querySelectorAll('[data-key]'))on(input,'input',()=>{values[input.dataset.key]=Number(input.value);input.closest('label').querySelector('output').textContent=input.value;api.update?.()});
 const play=root.querySelector('[data-play]');on(play,'click',()=>{auto=!auto;api.setAuto?.(auto);for(const a of root.querySelector('.lab-scene').getAnimations({subtree:true}))auto?a.play():a.pause();play.textContent=auto?'停止':'再生';play.setAttribute('aria-pressed',String(!auto))});
 const reset=root.querySelector('[data-reset]');on(reset,'click',()=>api.action?.());
 on(document,'visibilitychange',()=>api.setAuto?.(auto&&!document.hidden));
 api.update?.();if(cfg.static)play.hidden=true;
 return {setAuto(on){auto=on&&!mq.matches;api.setAuto?.(auto);for(const a of root.querySelector('.lab-scene').getAnimations({subtree:true}))auto?a.play():a.pause();play.textContent=auto?'停止':'再生'},destroy(){abort.abort();api.destroy?.()}};
})(document,{"kind":"type-kinetic","controls":[{"key":"height","label":"振幅 px","min":0,"max":25,"value":15,"step":1},{"key":"speed","label":"周期 秒","min":1,"max":5,"value":2,"step":0.1}],"action":"動きを反転"},function editorial({root,values:v,kind}){const stage=root.querySelector('.lab-scene'),status=root.querySelector('.lab-status');let changed=false;return {update(){for(const[k,x]of Object.entries(v))stage.style.setProperty('--'+k,String(x));if(kind==='type-variable'){root.querySelector('.editorial-word').style.fontVariationSettings=`"wght" ${v.weight}`;}if(kind==='texture-grain')root.querySelector('feTurbulence').setAttribute('baseFrequency',String(v.frequency));if(kind==='layout-intrinsic')root.querySelector('.layout-board').style.width=v.width+'%';status.textContent=kind==='type-variable'?'Inter variable / wght '+v.weight:'';},action(){changed=!changed;stage.classList.toggle('changed',changed);if(kind==='type-variable'){root.querySelector('.editorial-word').style.fontVariationSettings=`"wght" ${changed?400:v.weight}`;status.textContent=changed?'比較 / wght 400':'選んだ太さ / wght '+v.weight;}else status.textContent=changed?'変化後':'元の状態';}}});addEventListener('pagehide',()=>lab.destroy(),{once:true});</script></html>