CATEGORIES:CSS JAVA / TAGS:
CSSとJavaScriptでローディングアニメーションを作る#02
INITIALIZING

幅300pxの中をドットが左右にランダムに行き来し、中央の「INITIALIZING」の文字と重なると、その部分だけ文字が消えて見えるローディングアニメーションを作る方法。文字とドットを同じ色にして、背景を無地にするのがポイント。

動きは次のとおり。

  • ドットは行き先も移動時間もランダムに決まり、止まらず動き続ける
  • 動いている間だけ、ドットが上下に圧縮されて平らになる
  • 左右の端に着いたときだけ、ドットがオレンジ色になる(文字は変わらない)
  • ドットが文字の上を通るとき、同じ色なので文字が見えなくなる
<div class="walker" role="status">
  <span class="loader-text">INITIALIZING</span>
</div>
/* ドットが動く範囲(文字もここに重ねる) */
.walker {
  --loader-color: #555555;         /* 文字とドットの色。ここだけ変えれば両方そろう */
  position: relative;
  width: 300px;                    /* 動く範囲の幅。JSがこの幅を自動で読み取る */
  height: 16px;                    /* 文字の行の高さと同じにする */
}

/* ドット */
.walker .dot {
  position: absolute;
  left: 0;
  top: 3px;                        /* 縦中央 = (16px − 10px) ÷ 2。ドットの大きさを変えたら直す */
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--loader-color);
  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;
}

/* 文字。ドットの上に重ねて中央に置く */
.loader-text {
  position: absolute;
  left: 50%;
  top: 0;
  transform: translateX(-50%);
  white-space: nowrap;
  line-height: 16px;
  pointer-events: none;
  font-family: 'Montserrat', sans-serif;
  font-size: 12px;
  letter-spacing: .2em;            /* 広いほどクールに見える */
  padding-left: .2em;              /* letter-spacing で左に寄る分の補正 */
  color: var(--loader-color);
}
(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 dot = document.createElement('div');
    dot.className = 'dot';
    list[i].insertBefore(dot, list[i].firstChild);
    run(list[i], dot);
  }
})();

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

変えたいもの変更する場所
文字とドットの色CSS .walker の --loader-color(1か所だけ)
端に着いたときの色CSS .walker .dot.edge の background
動く範囲の幅CSS .walker の width(JSの変更は不要)
全体の速さJS DUR_MIN / DUR_MAX
オレンジになる頻度JS EDGE_CHANCE
文言<span class="loader-text"> の中身(例: STANDBY / PROCESSING)
ドットの平らさJS SQUASH(縦の数字を小さくするほど平ら)

文字とドットの色がそろっていないと、重なったときに文字が消えて見える効果が出ない。背景は無地にすること(画像やグラデーションの上だと、消えた部分が目立つ)。

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

NOTICES

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

TO HEADER