JS拡張サンプル集

CSSだけでは届かない領域へ。JavaScriptを少し足すと、アニメーションは「触れる体験」に変わる。
15種類の実用パターンを、コード付きで。

IntersectionObserver requestAnimationFrame Canvas 2D SVG path Web Animations API Pointer Events
01

スクロールリビール

IntersectionObserver

画面に入った瞬間に要素をフェードアップ。一度だけ発火する、最も使われるスクロール演出。

↓ ページをスクロールすると各カードが順次フェード:

🎨デザイン
パフォーマンス
🔧ツーリング
🎯ターゲット
📐レイアウト
🎮UX
🌈カラー
🔮魔法
// 画面に入ったら .show を付けるだけ
const io = new IntersectionObserver((entries) => {
  entries.forEach(e => {
    if (e.isIntersecting) {
      e.target.classList.add('show');
      io.unobserve(e.target);  // 一度だけ
    }
  });
}, { threshold: 0.15 });
document.querySelectorAll('.js-reveal').forEach(el => io.observe(el));
02

マウスパララックス

mousemove + layered transform

複数のレイヤーが異なる速度で動く、奥行きの錯覚。

// 各レイヤーに data-depth を持たせ、マウス位置で平行移動
stage.addEventListener('mousemove', e => {
  const r = stage.getBoundingClientRect();
  const mx = (e.clientX - r.left) / r.width - 0.5;
  const my = (e.clientY - r.top)  / r.height - 0.5;
  stage.querySelectorAll('[data-depth]').forEach(el => {
    const d = parseFloat(el.dataset.depth);
    el.style.transform = `translate(${mx * d * 6}px, ${my * d * 6}px)`;
  });
});
03

カーソルトレイル

requestAnimationFrame

マウスを追いかける光の粒。RAFループで滑らかに、過去N点を残光させる。

マウスやタッチを動かしてみて
// 50個のドットを並べ、それぞれ前のドットに追従させる
const dots = Array.from({length: 30}, () => ...);
const tick = () => {
  let x = mouseX, y = mouseY;
  dots.forEach((d, i) => {
    d.x += (x - d.x) * 0.3;
    d.y += (y - d.y) * 0.3;
    d.el.style.transform = `translate(${d.x}px,${d.y}px) scale(${1 - i/30})`;
    x = d.x; y = d.y;
  });
  requestAnimationFrame(tick);
};
04

マグネティックボタン

pointermove

マウスが近づくとボタンが引き寄せられる。さりげない物理感がUIに「気配」を与える。

// ボタンの中心と相対座標の30%だけ動かす
btn.addEventListener('mousemove', e => {
  const r = btn.getBoundingClientRect();
  const x = e.clientX - r.left - r.width  / 2;
  const y = e.clientY - r.top  - r.height / 2;
  btn.style.transform = `translate(${x * 0.3}px, ${y * 0.3}px)`;
});
btn.addEventListener('mouseleave', () => btn.style.transform = '');
05

テキストスクランブル

setInterval / random chars

ランダムな文字がじわじわ正解に収束していく、エージェント映画の暗号復号風。

Hello, World
// 進行状況に応じて、未確定文字をランダム置換
const chars = '!<>-_\\/[]{}—=+*^?#________';
function scramble(target) {
  const queue = buildQueue(current, target);
  let frame = 0;
  const tick = () => {
    const out = queue.map(({from, to, start, end}, i) => {
      if (frame >= end) return to;
      if (frame < start) return from;
      return chars[Math.floor(Math.random() * chars.length)];
    });
    el.textContent = out.join('');
    frame++;
    if (frame <= maxFrame) requestAnimationFrame(tick);
  };
  tick();
}
06

数値カウントアップ

requestAnimationFrame + easing

「0 → 目標値」へとイージングしながら駆け上がる数字。実績ページの定番。

ユーザー
0
プロジェクト
0
公開ファイル
0
成功率
0%
// ease-out で滑らかに到達
function count(el, to, duration = 1800) {
  const start = performance.now();
  const step = (now) => {
    const t = Math.min((now - start) / duration, 1);
    const eased = 1 - Math.pow(1 - t, 3); // easeOutCubic
    el.textContent = (eased * to).toFixed(decimals);
    if (t < 1) requestAnimationFrame(step);
  };
  requestAnimationFrame(step);
}
07

マルチライン・タイピング

setTimeout chain

複数行を順番に打ち、カラー区別や遅延を効かせるターミナル風タイピング。

// 1行ずつ、文字を1個ずつ追加。HTMLも解釈可
async function typeLine(html, speed = 30) {
  for (let i = 0; i <= html.length; i++) {
    el.innerHTML = collected + html.slice(0, i) + '<span class="caret"></span>';
    await sleep(speed);
  }
  collected += html + '\n';
}
08

パーティクル・ネットワーク

Canvas 2D

点と点を距離に応じて線で結ぶ古典的Canvas表現。マウスにも反応します。

120 nodes · live connections
// 各ノードを更新 → 近接同士を線で結ぶ
function tick() {
  ctx.clearRect(0, 0, w, h);
  nodes.forEach(n => {
    n.x += n.vx; n.y += n.vy;
    if (n.x < 0 || n.x > w) n.vx *= -1;
    if (n.y < 0 || n.y > h) n.vy *= -1;
  });
  for (let i = 0; i < nodes.length; i++) {
    for (let j = i + 1; j < nodes.length; j++) {
      const d = dist(nodes[i], nodes[j]);
      if (d < 100) {
        ctx.strokeStyle = `rgba(124,92,255,${1 - d/100})`;
        ctx.beginPath(); ctx.moveTo(nodes[i].x, nodes[i].y); ctx.lineTo(nodes[j].x, nodes[j].y); ctx.stroke();
      }
    }
  }
  requestAnimationFrame(tick);
}
09

スプリングドラッグ

pointer + spring physics

引っ張って離すと、バネのように元へ戻る。バネの剛性と減衰をコードで指定。

// バネ方程式: x'' = -k(x - x₀) - c·x'
const k = 0.15;  // 剛性
const damp = 0.8; // 減衰
function tick() {
  if (!dragging) {
    vx += (originX - x) * k; vx *= damp; x += vx;
    vy += (originY - y) * k; vy *= damp; y += vy;
    box.style.transform = `translate(${x}px,${y}px) scale(${1 + Math.abs(vx + vy) * 0.01})`;
  }
  requestAnimationFrame(tick);
}
10

3Dティルトカード

perspective + rotateX/Y

マウス位置から回転角度を計算し、立体的に傾く。光沢のスイープも追従。

PREMIUM
CSS × JS
// マウスの相対位置から ±15° 回転
card.addEventListener('mousemove', e => {
  const r = card.getBoundingClientRect();
  const mx = (e.clientX - r.left) / r.width;
  const my = (e.clientY - r.top)  / r.height;
  const ry = (mx - 0.5) *  30;
  const rx = (my - 0.5) * -30;
  card.style.transform = `rotateX(${rx}deg) rotateY(${ry}deg)`;
  card.style.setProperty('--glare', `${mx * 200 - 100}%`);
});
11

音波ビジュアライザ

RAF + sine waves

Web Audio APIなしで、複数のサイン波を重ねた「それっぽい」リアルタイム視覚化。

// 各バーに対し、複数の正弦波を合成
function tick(t) {
  bars.forEach((bar, i) => {
    const v =
      Math.sin(t / 300 + i * 0.2) * 0.5 +
      Math.sin(t / 120 + i * 0.5) * 0.3 +
      Math.sin(t / 80  + i)        * 0.2;
    const h = (v + 1) / 2 * 140 + 20;
    bar.style.height = h + 'px';
  });
  requestAnimationFrame(tick);
}
12

SVGパスドロー

stroke-dasharray

パスの長さを getTotalLength() で取り、ダッシュ配列でアニメ。手描き風の線。

// 線の長さを取って、初期は全部ダッシュで隠す → 0にトリガ
const len = path.getTotalLength();
path.style.strokeDasharray = len;
path.style.strokeDashoffset = len;
path.getBoundingClientRect(); // reflow
path.style.transition = 'stroke-dashoffset 2s ease-out';
path.style.strokeDashoffset = 0;
13

コンフェッティ砲

Canvas + 2D physics

重力・空気抵抗・初速・回転、すべてをコードで指定したリアル目の紙吹雪。

// 各粒子は位置・速度・回転を持ち、毎フレーム重力で落下
const P = { x, y, vx, vy, rot, vrot, color, size, life };
P.vy += 0.25;       // gravity
P.vx *= 0.99;        // drag
P.x += P.vx; P.y += P.vy;
P.rot += P.vrot;
ctx.save(); ctx.translate(P.x, P.y); ctx.rotate(P.rot);
ctx.fillStyle = P.color;
ctx.fillRect(-P.size/2, -P.size/2, P.size, P.size * 2);
ctx.restore();
14

Web Animations API

element.animate()

スクラブで時間軸を巻き戻したり、playbackRateで早回し。CSSキーフレームより柔軟。

// element.animate() は Animation オブジェクトを返す
const anim = box.animate([
  { transform: 'translateX(-150px) rotate(0deg)',   borderRadius: '50%' },
  { transform: 'translateX(0)     rotate(180deg)', borderRadius: '8px' },
  { transform: 'translateX(150px) rotate(360deg)', borderRadius: '50%' }
], { duration: 2500, iterations: Infinity, direction: 'alternate' });
anim.pause();         anim.play();
anim.playbackRate = 0.3;
anim.currentTime = 1200;   // 任意の時点へジャンプ
15

スタッガーリスト

classList + animation-delay

リスト項目を順番に、わずかな遅延つきで登場。チーム表、メニュー、結果一覧に。

1はじめにJSを読み込みます
2各要素にdelayを動的に割り当て
3.goクラスを付与してアニメ開始
4CSSの@keyframesが順番に発火
5視線が自然に下へ移動する
6これで連続演出の完成
// 各要素のdelayをインデックスから計算
items.forEach((el, i) => {
  el.style.animationDelay = `${i * 80}ms`;
  el.classList.remove('go');
  void el.offsetWidth;  // reflow to restart
  el.classList.add('go');
});