CATEGORIES:CSS JAVA / TAGS:
CSSとJavaScriptでローディングアニメーションを作る#01
A:ドット1つ・線なし
B:ドット1つ・線あり
C:ドット5つ・線あり

幅300pxの中を、10pxのドットが左右にランダムに行き来するローディングアニメーションを作る方法。HTMLの data- 属性を変えるだけで、ドットの数と背景の線の有無を切り替えられる。

動きは次のとおり。

  • ドットは行き先も移動時間もランダムに決まり、止まらず動き続ける
  • 動いている間だけ、ドットが上下に圧縮されて平らになる
  • 左右の端に着いたときだけ、ドットがオレンジ色になる
  • ドットが複数のときは、それぞれ別のタイミング・場所に動く

パターンは次の3つ。

パターンHTML
A:ドット1つ・線なしdata-dots="1" data-line="off"
B:ドット1つ・線ありdata-dots="1" data-line="on"
C:ドット5つ・線ありdata-dots="5" data-line="on"

HTML(ドットはJavaScriptが自動で作るので、div は空のままにする)

<div class="walker" data-dots="1" data-line="off" role="status" aria-label="読み込み中"></div>
.walker {
  position: relative;
  width: 300px;                    /* 動く範囲の幅。JSがこの幅を自動で読み取る */
  height: 10px;                    /* ドットの高さと同じにする */
}

/* 背景の線(data-line="on" のときだけ表示) */
.walker.line::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 4.5px;                      /* ドットの縦中央 */
  height: 1px;
  background: #d9d9d9;             /* 線の色 */
}

/* ドット */
.walker .dot {
  position: absolute;
  left: 0;
  top: 0;
  width: 10px;                     /* ドットの大きさ(直径) */
  height: 10px;
  border-radius: 50%;
  background: #555555;             /* ふだんの色 */
  transition-property: translate, background-color;
  transition-timing-function: cubic-bezier(.45, 0, .55, 1), ease-out;
  transition-duration: 0s, .12s;   /* 移動の時間はJSが毎回ランダムに決める */
}

/* 端に着いたときの色 */
.walker .dot.edge {
  background: #ff8a00;
}
(function () {
  var DUR_MIN = 250;       // 1回の移動の最短時間(ミリ秒)。小さいほど速い
  var DUR_MAX = 700;       // 1回の移動の最長時間(ミリ秒)
  var EDGE_CHANCE = 0.35;  // 端へ向かう確率(0〜1)
  var EDGE_HOLD = 160;     // 端に着いたあと、オレンジのまま止まる時間(ミリ秒)
  var MIN_DISTANCE = 40;   // 1回の移動の最短距離(px)
  var PAUSE_MAX = 60;      // 端以外で着いたあと、次へ動くまでの最大の間(ミリ秒)
  var SQUASH = '1.35 0.6'; // 移動の真ん中での形(横の倍率 縦の倍率)

  var reduce = window.matchMedia &&
    window.matchMedia('(prefers-reduced-motion: reduce)').matches;

  function run(el, dot) {
    var size = dot.offsetWidth;
    var x = Math.random() * (el.clientWidth - size);
    dot.style.translate = x + 'px 0';
    if (reduce) { return; }

    function go() {
      var maxX = el.clientWidth - size;
      dot.classList.remove('edge');

      var nx;
      if (Math.random() < EDGE_CHANCE) {
        nx = x > maxX / 2 ? 0 : maxX;
      } else {
        do {
          nx = Math.random() * maxX;
        } while (Math.abs(nx - x) < MIN_DISTANCE || nx < 1 || nx > maxX - 1);
      }

      var dur = DUR_MIN + Math.random() * (DUR_MAX - DUR_MIN);
      dot.style.transitionDuration = dur + 'ms, .12s';
      dot.style.translate = nx + 'px 0';

      dot.animate(
        [{ scale: '1 1' }, { scale: SQUASH }, { scale: '1 1' }],
        { duration: dur, easing: 'ease-in-out' }
      );

      x = nx;
      var atEdge = (nx === 0 || nx === maxX);
      setTimeout(function () {
        if (atEdge) {
          dot.classList.add('edge');
          setTimeout(go, EDGE_HOLD);
        } else {
          setTimeout(go, Math.random() * PAUSE_MAX);
        }
      }, dur);
    }

    setTimeout(go, 100 + Math.random() * 400);
  }

  var list = document.querySelectorAll('.walker');
  for (var i = 0; i < list.length; i++) {
    var el = list[i];
    var n = parseInt(el.getAttribute('data-dots'), 10) || 1;
    if (el.getAttribute('data-line') === 'on') { el.classList.add('line'); }
    for (var j = 0; j < n; j++) {
      var dot = document.createElement('div');
      dot.className = 'dot';
      el.appendChild(dot);
      run(el, dot);
    }
  }
})();

調整するときの変更箇所は次のとおり。

変えたいもの変更する場所
ドットの数HTML data-dots(好きな数でよい)
線の表示HTML data-line(on / off)
動く範囲の幅CSS .walker の width(JSの変更は不要)
ドットの色CSS .walker .dot の background
端に着いたときの色CSS .walker .dot.edge の background
全体の速さJS DUR_MIN / DUR_MAX
オレンジになる頻度JS EDGE_CHANCE
ドットの平らさJS SQUASH(縦の数字を小さくするほど平ら)

ドットの大きさを変えるときは、CSSの .walker の height と、線の top も合わせて直す。1ページに複数置いても、それぞれ別の設定で動く。

translate / scale プロパティを使うため、2022年以降のChrome・Edge・Safari・Firefoxが必要。表示環境によって動きの見え方が異なる場合があるため、本番に反映する前に確認を推奨。

NOTICES

  • 記事内容は実装させたものがほとんどですが自己責任で参考にしてください。

TO HEADER