DEMO
この枠の中でマウスを動かしてください
CODE
<div class="demo-stage smc-stage" id="smc-stage">
<p class="smc-hint">この枠の中でマウスを動かしてください</p>
<div class="smc-cursor" id="smc-cursor" aria-hidden="true">
<svg viewBox="0 0 24 24" width="28" height="28"><path d="M12 2l8 19-8-5-8 5z" fill="#E91B89" stroke="#fff" stroke-width="1.5" stroke-linejoin="round"/></svg>
</div>
</div>
.smc-stage { position:relative; overflow:hidden; display:flex; align-items:center; justify-content:center; padding:2rem; background:#0B0B10; min-height:300px; }
.smc-hint { margin:0; color:#A1A1AA; font-size:.82rem; text-align:center; }
.smc-cursor { position:absolute; top:0; left:0; width:28px; height:28px; margin:-2px 0 0 -14px; transform-origin:14px 2px; pointer-events:none; opacity:0; transition: opacity .2s ease; will-change:transform; }
.smc-cursor svg { display:block; filter: drop-shadow(0 2px 6px rgba(0,0,0,.5)); }
.smc-stage.is-active .smc-cursor { opacity:1; }
@media (hover: hover) and (pointer: fine) {
.smc-stage.is-active { cursor:none; }
}
@media (prefers-reduced-motion: reduce) {
.smc-cursor { transition-duration:.01ms; }
}
(function(){
var stage = document.getElementById('smc-stage');
var cur = document.getElementById('smc-cursor');
if (!stage || !cur) return;
var reduce = window.matchMedia('(prefers-reduced-motion: reduce)');
var STIFF = 0.16, DAMP = 0.72;
var x = 0, y = 0, vx = 0, vy = 0, tx = 0, ty = 0, angle = 0, raf = 0;
function norm(a){ return ((a % 360) + 540) % 360 - 180; }
function turn(to, rate){ angle = norm(angle + norm(to - angle) * rate); }
function draw(){
var sp = Math.hypot(vx, vy);
cur.style.transform = 'translate(' + x + 'px,' + y + 'px) rotate(' + angle + 'deg) scale(' + (1 + Math.min(sp / 60, .25)) + ')';
}
function step(){
vx = (vx + (tx - x) * STIFF) * DAMP;
vy = (vy + (ty - y) * STIFF) * DAMP;
x += vx; y += vy;
var sp = Math.hypot(vx, vy);
if (sp > .6) turn(Math.atan2(vy, vx) * 180 / Math.PI + 90, .25);
else turn(0, .12);
draw();
if (Math.abs(tx - x) > .1 || Math.abs(ty - y) > .1 || sp > .1 || Math.abs(angle) > .5) raf = requestAnimationFrame(step);
else { angle = 0; draw(); raf = 0; }
}
stage.addEventListener('pointermove', function(e){
if (e.pointerType !== 'mouse') return;
var r = stage.getBoundingClientRect();
tx = e.clientX - r.left; ty = e.clientY - r.top;
if (!stage.classList.contains('is-active')) { stage.classList.add('is-active'); x = tx; y = ty; vx = vy = 0; }
if (reduce.matches) { x = tx; y = ty; vx = vy = 0; angle = 0; draw(); return; }
if (!raf) raf = requestAnimationFrame(step);
});
stage.addEventListener('pointerleave', function(){ stage.classList.remove('is-active'); });
window.addEventListener('scroll', function(){ stage.classList.remove('is-active'); }, { passive: true });
})();
AI PROMPT
マウスの位置をばねのような動きで少し遅れて追いかける矢印カーソル(Smooth Cursor)を作って。デモの枠自体に position:relative と overflow:hidden を付け、その中に aria-hidden="true"・pointer-events:none の矢印(上向きの SVG)を position:absolute で置く(position:fixed は使わず、枠の中だけで動かす)。矢印の先端が実際のマウス位置に来るよう、margin で先端の分だけずらし、transform-origin も先端にして回転・拡大しても先端が動かないようにする(標準のカーソルを隠すので、見た目の先端とクリック位置がずれると押しにくくなる)。pointermove で pointerType が mouse の時だけ、枠の getBoundingClientRect からの相対座標を目標位置にする。requestAnimationFrame の毎フレームで、速度に「目標までの差 × 0.16」を足してから 0.72 を掛け(減衰)、位置に速度を足す=ばねの動き。速度が一定以上の時は Math.atan2 で進む向きの角度を求め、矢印の先端がその向きを向くように回転させる(角度の差と角度そのものを毎回 -180〜180 度に丸めて、遠回りに回らないようにし、止まった時に 0 度と判定できるようにする)。止まったら角度を0度へゆっくり戻し、目標に着いて静止したらループを止める。速く動くほど少しだけ拡大する(最大1.25倍)。枠に入った時だけ表示し、@media (hover: hover) and (pointer: fine) の時だけ枠内の標準カーソルを cursor:none で隠す(タッチ端末では何もしない)。ページがスクロールされたら独自カーソルを隠し(枠が動くと本当の位置とずれるため)、次に動かした時にその位置から出し直す。prefers-reduced-motion: reduce の時は、ばねと回転をやめて遅れなしで追従させる。 Acceptance criteria: works with prefers-reduced-motion (animation disabled), zero console errors, zero layout shift
AIエージェントでの実装ガイド → Claude Code でWebアニメーションを実装する
BOOKS · PR · AMAZON
※当サイトはAmazonアソシエイトプログラムの参加者です。リンクから商品をご購入いただくと、当方に紹介料が発生します。
学びを止めない · AMAZONサブスク
※当サイトはAmazonアソシエイトプログラムの参加者です。リンクから商品をご購入いただくと、当方に紹介料が発生します。
標準のマウスカーソルの代わりに、ばねのような動きで少し遅れて追いかける独自のカーソルを表示する演出です。Magic UI にも同じ名前のコンポーネント(Smooth Cursor)があり、公式の説明では「物理ベースのなめらかなカーソルのアニメーション」とされています。
当サイトの Custom Cursor は独自カーソルの基本形、Cursor Trail はカーソルの後ろに軌跡を残す演出、Cursor Blob Follow はグラデーションの塊が慣性をもって追いかける演出です。本ページは矢印の形を保ったまま、ばねの遅れと進む向きへの傾きを加える別テーマです。
カーソルは操作の要なので、遅れすぎると押したい場所と見た目がずれて使いにくくなります。係数を調整して「少し遅れる」程度にとどめ、動きを減らす設定の利用者には遅れなしで追従させてください。
ばねの動きはどう計算していますか?
毎フレーム、目標位置までの差に係数(本実装は0.16)を掛けて速度に足し、その速度に減衰の係数(0.72)を掛けてから位置に足しています。差が大きいほど強く引っ張られ、減衰で少しずつ勢いが落ちるため、行き過ぎて戻るような自然な動きになります。係数はフレームごとに掛けているので、画面のリフレッシュレートが高い端末では少し速く追いつきます。気になる場合は経過時間を掛けて計算してください。
標準のカーソルを隠しても問題ありませんか?
カーソルを隠すと、クリックできる場所の手がかり(指の形になる等)も失われます。本実装はデモの枠の中だけで隠し、タッチ端末やマウスのない環境では隠さないよう (hover: hover) and (pointer: fine) の条件を付けています。サイト全体で使う場合も、リンクやボタンの上では見た目を変えるなど、押せる場所が分かる工夫を加えてください。
コードは無料で使えますか?
はい。Motion LabのコードスニペットとAIプロンプトは、クレジット表記なしで自由に使えます。コピーしてそのままプロジェクトに利用できます。