jQuery / .animate()でアニメーション効果を作る

.animate()は現在のCSS値から目標まで時間をかけて変えます。.animate({ left: 100, width: 160, opacity: 0.4 })なら位置・幅・透明度が同時に変わります。時間とeasingを指定でき、完了は要素ごとに呼びます。

属性と時間を決める

.animate()の最初は目標属性オブジェクトです。数値left・widthはここではpx、opacityは0〜1の単位なしです。同じオブジェクトは同時で、別呼出しが基本列に並ぶのとは異なります。

leftの移動にはstatic以外の配置が必要です。relativeは元の場所からで文書x=100ではありません。widthは内容幅で、border・paddingを単純化しcontent-boxにしました。

durationはミリ秒、既定400です。easingは進行速度でswingとlinearを比べます。同じ目標でも途中が違います。任意の色やtransform文字列まで基本jQueryで補間すると考えず対応数値属性を調べます。

二要素の完全な例

全コードをUTF-8のanimate-demo.htmlに保存し開きます。インターネットから公式完全版4.0.0を読み、effectsが必要なのでslimを避けます。サーバーや導入は不要です。Windows Edge、3.7.1・4.0.0で中間・目標・停止・再生を確認しました。

両方は同じ目標です。Startは既知の初期値に戻し、Resetは時間とeasing選択を保って箱とカウンターを戻します。実行中は重複を防ぎます。

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Animate CSS properties and observe completion</title>
  <script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
  <style>
    body { font: 16px/1.6 sans-serif; max-width: 760px; margin: 20px; }
    label { display: block; margin: 8px 0; }
    select, button { font: inherit; margin: 4px; }
    #stage { background: #eef4f4; padding: 10px; overflow-x: auto; }
    .box { position: relative; left: 0; width: 80px; height: 45px; margin: 12px 0; background: teal; color: white; box-sizing: content-box; }
    pre { white-space: pre-wrap; overflow-wrap: anywhere; }
  </style>
</head>
<body>
  <label>Duration <select id="duration"><option value="600">600ms</option><option value="1200">1200ms</option><option value="0">0ms</option></select></label>
  <label>Easing <select id="easing"><option value="swing">swing</option><option value="linear">linear</option></select></label>
  <button type="button" id="start">Start</button>
  <button type="button" id="stop">Stop here</button>
  <button type="button" id="reset">Reset</button>
  <div id="stage"><div class="box" id="alpha">Alpha</div><div class="box" id="beta">Beta</div></div>
  <pre id="result" aria-live="polite"></pre>
  <script>
    jQuery(function ($) {
      const $boxes = $("#stage > .box");
      let generation = 0, busy = false, state = "ready", completed = 0, batches = 0;
      function report() {
        const values = $boxes.map(function () {
          const $box = $(this);
          return this.id + ": left=" + Math.round(parseFloat($box.css("left"))) + "; width=" + Math.round($box.width()) + "; opacity=" + Number(parseFloat($box.css("opacity")).toFixed(2));
        }).get();
        $("#result").text(["state=" + state, "completed=" + completed + "; batches=" + batches, ...values].join("\n"));
      }
      function reset() {
        generation++; $boxes.stop(true, false).css({ left: 0, width: 80, opacity: 1 });
        busy = false; state = "ready"; completed = batches = 0;
        $("#start").prop("disabled", false); report();
      }
      $("#start").on("click", function () {
        if (busy) return;
        reset(); busy = true; state = "running"; $(this).prop("disabled", true);
        const run = generation;
        const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
        const duration = reduce ? 0 : Number($("#duration").val());
        $boxes.animate({ left: 100, width: 160, opacity: 0.4 }, {
          duration: duration, easing: $("#easing").val(),
          complete: function () { if (run === generation) { completed++; report(); } }
        });
        $boxes.promise("fx").done(function () {
          if (run !== generation) return;
          batches++; busy = false; state = "done";
          $("#start").prop("disabled", false); report();
        });
        report();
      });
      $("#stop").on("click", function () {
        if (!busy) return;
        generation++; $boxes.stop(true, false);
        busy = false; state = "stopped"; $("#start").prop("disabled", false); report();
      });
      $("#reset").on("click", reset);
      reset();
    });
  </script>
</body>
</html>

初期幅80、相対left=0、opacity=1です。表示の丸めは計算を整数に限定しません。

state=ready
completed=0; batches=0
alpha: left=0; width=80; opacity=1
beta: left=0; width=80; opacity=1
  1. 600ms・swingでStart。三属性が同時に変わります。
  2. 完了はstate=done、completed=2; batches=1で、両方left=100; width=160; opacity=0.4です。
  3. Reset後easingだけlinearにして比較し、swingへ戻せます。目標は同じです。
  4. 1200msで開始しStop here。途中でstate=stoppedとなり、後からdoneへ変わりません。
  5. Reset後再生します。0msなら即目標と完了です。
  6. 動きを減らすreduceなら1200msを選んでも即時です。

要素別と全体完了

completeは二回でthisは各DOM要素です。最初の一回を全体完了としません。.animate()は終了までJavaScriptを止めません。

.promise("fx")は集合の効果を観察し、ここではbatchesを一度増やしStartを戻します。後に基本列へ追加した処理も待機範囲になり得ます。

列が空でも自然完了とは限りません。.stop(true, false)は途中で止め列を消してもfx promiseが解決し得ます。Stop・Resetは先にgenerationを変え、旧処理がdoneを上書きしないようにします。

連打は通常効果を積むためStart無効とbusyを使います。現在位置から別目標へ変更するUIなら別の中断方針を設計します。

停止・復元と実配置

jumpToEnd=falseは現在値を保ちます。Resetは三属性を明示復元します。中間値を残せば次の比較条件が変わります。

prefers-reduced-motion reduceは今回だけ時間0にし同じ目標と完了経路を使います。全体設定は変えません。

位置と幅は周囲や重なりに影響するため、狭い画面、拡大、長文、スクロール領域を確認します。opacity=0も自動でdisplay:noneやアクセシビリティ上の非表示にはならず別途設計します。

CSS transitionやプラグインが同じ属性を同時制御しないよう、対象と属性・列の所有を定め、選択子とイベントを機能内へ限定します。