No.164
CURSOR CSSJS

Smooth Cursor

マウスの位置をばねのような動きで少し遅れて追いかけ、進む向きに先端を傾ける矢印カーソル。止まると向きが元に戻り、動きを減らす設定では遅れなしで追従します。

DEMO

この枠の中でマウスを動かしてください

CODE

HTML
<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>
CSS
.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; }
}
JS
(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

Claude Code Cursor v0
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

📖 なかしまぁ先生のCSSアニメーション入門 📖 マイクロインタラクション ―UI/UXデザインの神が宿る細部 📖 Fundamentals of Web Animation with GSAP

※当サイトはAmazonアソシエイトプログラムの参加者です。リンクから商品をご購入いただくと、当方に紹介料が発生します。

学びを止めない · AMAZONサブスク

📚 Kindle Unlimited — 技術書・デザイン書が読み放題。30日無料体験 → 🎧 Audible — 移動中に耳で学ぶオーディオブック。30日無料体験 → 📦 Amazon Prime — Prime Reading・配送特典つき。30日無料体験 → 🎵 Music Unlimited — 作業用BGMに。1億曲が聴き放題・30日無料体験 →

※当サイトはAmazonアソシエイトプログラムの参加者です。リンクから商品をご購入いただくと、当方に紹介料が発生します。

RELATED EFFECTS

↗ MORE
→ CUSTOM CURSOR → CURSOR TRAIL → CURSOR BLOB FOLLOW

Smooth Cursor(スムーズカーソル)とは

標準のマウスカーソルの代わりに、ばねのような動きで少し遅れて追いかける独自のカーソルを表示する演出です。Magic UI にも同じ名前のコンポーネント(Smooth Cursor)があり、公式の説明では「物理ベースのなめらかなカーソルのアニメーション」とされています。

当サイトの Custom Cursor は独自カーソルの基本形、Cursor Trail はカーソルの後ろに軌跡を残す演出、Cursor Blob Follow はグラデーションの塊が慣性をもって追いかける演出です。本ページは矢印の形を保ったまま、ばねの遅れと進む向きへの傾きを加える別テーマです。

カーソルは操作の要なので、遅れすぎると押したい場所と見た目がずれて使いにくくなります。係数を調整して「少し遅れる」程度にとどめ、動きを減らす設定の利用者には遅れなしで追従させてください。

よくある質問

ばねの動きはどう計算していますか?

毎フレーム、目標位置までの差に係数(本実装は0.16)を掛けて速度に足し、その速度に減衰の係数(0.72)を掛けてから位置に足しています。差が大きいほど強く引っ張られ、減衰で少しずつ勢いが落ちるため、行き過ぎて戻るような自然な動きになります。係数はフレームごとに掛けているので、画面のリフレッシュレートが高い端末では少し速く追いつきます。気になる場合は経過時間を掛けて計算してください。

標準のカーソルを隠しても問題ありませんか?

カーソルを隠すと、クリックできる場所の手がかり(指の形になる等)も失われます。本実装はデモの枠の中だけで隠し、タッチ端末やマウスのない環境では隠さないよう (hover: hover) and (pointer: fine) の条件を付けています。サイト全体で使う場合も、リンクやボタンの上では見た目を変えるなど、押せる場所が分かる工夫を加えてください。

コードは無料で使えますか?

はい。Motion LabのコードスニペットとAIプロンプトは、クレジット表記なしで自由に使えます。コピーしてそのままプロジェクトに利用できます。