DEMO
この枠の中でマウスを大きく動かしてください(タッチはタップした場所に1枚)
CODE
<div class="demo-stage imt-stage" id="imt-stage"> <p class="imt-hint">この枠の中でマウスを大きく動かしてください(タッチはタップした場所に1枚)</p> <div class="imt-layer" id="imt-layer" aria-hidden="true"></div> </div>
.imt-stage { position:relative; overflow:hidden; display:flex; align-items:center; justify-content:center; padding:2rem; background:#0B0B10; min-height:320px; }
.imt-hint { margin:0; max-width:260px; color:#A1A1AA; font-size:.82rem; line-height:1.6; text-align:center; }
.imt-layer { position:absolute; inset:0; pointer-events:none; }
.imt-item { position:absolute; top:0; left:0; width:96px; height:120px; margin:-60px 0 0 -48px; border-radius:10px; box-shadow:0 10px 24px rgba(0,0,0,.45); animation: imt-life 1s cubic-bezier(.22,1,.36,1) forwards; }
@keyframes imt-life {
0% { opacity:0; transform:scale(.6); }
15% { opacity:1; transform:scale(1); }
65% { opacity:1; transform:scale(1); }
100% { opacity:0; transform:translateY(24px) scale(.85); }
}
(function(){
var stage = document.getElementById('imt-stage');
var layer = document.getElementById('imt-layer');
if (!stage || !layer) return;
var reduce = window.matchMedia('(prefers-reduced-motion: reduce)');
var FILLS = ['linear-gradient(160deg,#7B37A8,#E91B89)', 'linear-gradient(160deg,#0369A1,#5BAADE)', 'linear-gradient(160deg,#92400E,#F59E0B)',
'linear-gradient(160deg,#047857,#34D399)', 'linear-gradient(160deg,#9D174D,#F472B6)', 'linear-gradient(160deg,#1E1B4B,#6366F1)'];
var GAP = 70, MAX = 10, lastX = null, lastY = 0, n = 0;
function spawn(x, y){
var el = document.createElement('div');
el.className = 'imt-item';
el.style.left = x + 'px'; el.style.top = y + 'px';
el.style.background = FILLS[n++ % FILLS.length];
el.addEventListener('animationend', function(){ el.remove(); });
layer.appendChild(el);
while (layer.children.length > MAX) layer.firstElementChild.remove();
}
function pos(e){ var r = stage.getBoundingClientRect(); return [e.clientX - r.left, e.clientY - r.top]; }
stage.addEventListener('pointermove', function(e){
if (e.pointerType !== 'mouse' || reduce.matches) return;
var p = pos(e);
if (lastX === null) { lastX = p[0]; lastY = p[1]; return; }
if (Math.hypot(p[0] - lastX, p[1] - lastY) < GAP) return;
lastX = p[0]; lastY = p[1];
spawn(p[0], p[1]);
});
stage.addEventListener('pointerleave', function(){ lastX = null; });
stage.addEventListener('pointerup', function(e){
if (e.pointerType === 'mouse' || reduce.matches) return;
var p = pos(e);
spawn(p[0], p[1]);
});
})();
AI PROMPT
マウスを一定の距離動かすたびに、その位置へ画像のタイルが現れて少し遅れて消えていく Image Trail を作って。デモの枠に position:relative と overflow:hidden を付け、中に pointer-events:none・aria-hidden="true" の層(position:absolute; inset:0)を置き、タイルはその層へ追加する(position:fixed は使わない)。pointermove で pointerType が mouse の時だけ、枠の getBoundingClientRect からの相対座標を取り、前回タイルを出した位置から70px以上離れた時だけ新しいタイルを出す(毎回出すと重なって見えないため)。タイルは幅96px・高さ120px・角丸10pxで、margin で中心をマウス位置に合わせ、left と top は出す時に1回だけ指定する。画像の代わりに6色のグラデーションを順番に使う(実際には作品の画像を入れる)。@keyframes で、0%=透明・scale(.6)、15%=不透明・scale(1)、65%まで保持、100%=translateY(24px) scale(.85)・透明 の1秒の動きを付け、animationend でタイルを取り除く。同時に残すタイルは10枚までにして、超えたら古い順に消す。枠から出たら距離の基準をリセットする。タッチやペンではなぞる動きを追わず、pointerup の時にタップした位置へ1枚だけ出す(ページのスクロールを妨げないため。スクロールになった操作は pointercancel で終わり pointerup が来ないので、スクロールでは出ない)。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アソシエイトプログラムの参加者です。リンクから商品をご購入いただくと、当方に紹介料が発生します。
CREATOR TOOLS · スクール(PR)
※本サイトはアフィリエイトリンクを含みます。リンク経由のご購入・お申し込みで運営者が紹介料を受け取る場合があります。
マウスの通り道に沿って画像が次々と現れ、少し遅れて消えていく演出です。Web制作の情報サイト Codrops は2019年の記事「Image Trail Effects」でこの動きを紹介しており、記事では「マウスを追いかけてランダムな画像の軌跡を見せる」アイデアとして説明されています。React Bits にも ImageTrail というコンポーネントがあり、説明は「カーソルに合わせた画像の軌跡(複数のバリエーション付き)」です。
当サイトの Cursor Trail はマウス追従の光の残像が複数フェードしながら消える演出、Cursor Blob Follow はグラデーションの塊が慣性をもって追いかける演出、Smooth Cursor はばねの動きで遅れて追いかけ先端を進む向きに傾ける矢印カーソルです。本ページはカーソルの形を変えず、通り道に大きな画像を並べて見せる別テーマです。
作品の数が多いポートフォリオやギャラリーのトップで、触るたびに違う作品が見える楽しさを作れます。一方で画面の上に画像が次々と重なるため、読ませたい文章の上では使わず、演出の範囲を枠の中に限るのが扱いやすい使い方です。
タイルを出す間隔はどう決めていますか?
前回タイルを出した位置から70px以上離れた時だけ、新しいタイルを出しています。マウスが動くたびに出すと、同じ場所に何枚も重なって1枚ずつの画像が見えなくなります。間隔はタイルの大きさに合わせて決め、画像を大きくする場合は間隔も広げてください。
本物の画像に差し替える時の注意点は?
タイルの background に画像を指定するか、img 要素を入れて object-fit: cover で切り抜きます。マウスを動かし始めてから画像を読み込むと表示が遅れるため、使う画像は先に読み込んでおきます。装飾として出す画像なので、層ごと aria-hidden にして読み上げの対象から外し、作品の一覧は別の場所に文字でも載せておいてください。
コードは無料で使えますか?
はい。Motion LabのコードスニペットとAIプロンプトは、クレジット表記なしで自由に使えます。コピーしてそのままプロジェクトに利用できます。