Topics

トピックス

開発ブログ 2021-05-24

【コピペでOK】JSとCSSで背景にランダムで斜線が降り続けるアニメーションを作ってみた

\シェアする/

【コピペでOK】JSとCSSで背景にランダムで斜線が降り続けるアニメーションを作ってみた

こんにちは、ライターのみなせしゅんです。

 

先日、当サイトをリニューアルしました。

リニューアルしたサイトのトップページにて、背景にランダムで斜線が降り続けるアニメーションを使用しています。

その斜線のアニメーションについて、調べてもなかなか「これだ!」という作り方が出てこなかったので今回自作しました。

せっかく作ったので、作り方をご紹介いたします。皆さんにもぜひお使いいただけたらうれしいです。

Contents

    デモはこちら

    まずはこちらのデモをご覧ください。

    今回作り方をご紹介するのはこのアニメーションです。

     

    HTMLとCSS自体はシンプルです。jQuery等の外部ライブラリも使用していないので、おそらくコピペで動くと思われます。

    ある程度カスタマイズができるコードになっているので、お好みに合わせてご使用いただけるよう少しだけ解説をさせていただきます。

     

    コードの解説

    全体の仕組みについて

    HTMLで斜線描画部分のdivを定義し、JSでSVGのナナメ線を挿入し、CSSで流れるアニメーションを付与する、という流れです。

     

    使用しているCSS(SCSS)

    使用しているCSS(SCSS形式)はこちらです。

    .slash_line_outer {
      position: fixed;
      height: 100vh;
      width: 100vw;
      max-width: 100%;
      top: 0;
      left: 0;
      overflow: hidden;
      .slash_position {
        position: absolute;
        stroke: #666; //線の色はここで調整
      }
      svg {
        opacity: 1;
        stroke-width: 1px; //線幅はここで調整
        stroke-dasharray: 800;
        stroke-dashoffset: 800;
        animation: slash_line 3s; //線が流れる速さはここで調整
        animation-timing-function: ease-in-out;
        animation-direction: reverse;
      }
      //アニメーションの定義
      @keyframes slash_line {
        50% {
          stroke-dashoffset: 0;
        }
        100% {
          stroke-dashoffset: -800;
        }
      }
    }

     

    こちらについては特に解説不要だと思われます。

    「線の色はここで調整」「線幅はここで調整」「線が流れる速さはここで調整」とコメントが書いてある部分の値を変更することでカスタマイズができます。

     

    使用しているJS

    使用しているJSはこちらです。

    window.onload = function() {
      
      /*=======================================
        パラメーター設定ここから
     =======================================*/
      
      // 実行間隔(ms)
      const intervalMs = 300;
      
      // 描画を許可する座標の範囲(max)
      const randomRangeMax = 110;
      
      // 描画を許可する座標の範囲(min)
      const randomRangeMin = -10;
      
      // 直近x個の線と座標がかぶらないようにする
      const disallowedAxisNum = 4;
      
      // 縦横この範囲に既存の線があったら生成しない
      const disallowedRange = 8;
      
      // 線の長さ上限(vh)
      const lineLengthMax = 30;
      
      // 線の長さ下限(vh)
      const lineLengthMin = 20;
      
      // 線生成数上限
      const counterMax = 15;
      
      /*=======================================
        パラメーター設定ここまで
      =======================================*/
       
      // 変数の初期化
      let arrayY = [];
      let arrayX = [];
      let interval = null;
      let i = 1;
      
      // 描画エリアを取得
      const drawArea = document.querySelector('.slash_line_outer');
      
      // 処理本体
      const drawSlashLine = () => {
        
        // svg描画用のdivを定義
        let positionHtml = (top, left, n) => `<div class="slash_position n${n}" style="top: ${top}%; left: ${left}%;"></div>`;
        
        // 生成するsvgを定義
        let line = height => `<svg style="height: ${height}vh;" xmlns="http://www.w3.org/2000/svg"><line class="cls-1" x1="0.48" y1="320.5" x2="99.34" y2="0.15"/></svg>`;
        
        // 変数の初期化
        let y = 0;
        let x = 0;
        let randomNumY = 0;
        let randomNumX = 0;
        
        // Y軸座標乱数生成
        randomNumY = Math.floor( Math.random() * (randomRangeMax + 1 - randomRangeMin) ) + randomRangeMin; 
        
        // 生成されたY軸の座標が直近で生成された線のY座標と近すぎないか判定
        let rangeY = (element) => (randomNumY - disallowedRange) < element && element < (randomNumY + disallowedRange);
        if (arrayY.some(rangeY)) {
          return; // 近すぎたらNG
        }
        
        // X軸座標乱数生成
        randomNumX = Math.floor( Math.random() * (randomRangeMax + 1 - randomRangeMin) ) + randomRangeMin;
        
        // 生成されたX軸の座標が直近で生成された線のX座標と近すぎないか判定
        let rangeX = (element) => (randomNumX - disallowedRange) < element && element < (randomNumX + disallowedRange);
        if (arrayX.some(rangeX)) {
          return; // 近すぎたらNG
        }
        
        // 座標を保持している数が設定値の上限に達していたら一番古いものを削除
        if (arrayY.length >= disallowedAxisNum) {
          arrayY.shift();
          arrayX.shift();
        }
        
        // 新しい座標を保持
        arrayY.push(randomNumY);
        arrayX.push(randomNumX);
        
        // 古くなった既存の線を削除
        let positionSelector = document.querySelector(`.slash_position.n${i}`);
        if (positionSelector) {
          positionSelector.remove();
        }
        
        // 生成したY軸X軸の乱数を基に新しい線を生成、線の長さもここで決める
        drawArea.insertAdjacentHTML("beforeend", positionHtml(randomNumY, randomNumX, i));
        let parent = document.querySelector(`.slash_position.n${i}`);
        let lineHeight = Math.floor( Math.random() * (lineLengthMax + 1 - lineLengthMin) ) + lineLengthMin;
        parent.insertAdjacentHTML("beforeend", line(lineHeight));
      
        // 線の生成数が設定値の上限に達していたらカウンターをリセット
        if (i >= counterMax) {
          i = 0;
        }
        i++;
      };
      
      // 処理本体を実行する間隔を設定
      interval = setInterval(drawSlashLine, intervalMs);
      
      // ブラウザのタブが非アクティブになったら動作を停止し初期化、アクティブになったら再実行
      document.addEventListener("visibilitychange", function() {
        if(document.visibilityState == "hidden") {
          clearInterval(interval);
          document.querySelectorAll(".slash_position").forEach(el => el.remove());
          let arrayY = [];
          let arrayX = [];
          let i = 1;
        } else {
          interval = setInterval(drawSlashLine, intervalMs);
        }
      });
    };

    JSを扱い慣れていない方が見ると長くて面食らうかもしれませんが、コード上部のパラメーターをいじるだけでカスタマイズができるようになっていますのでご安心ください。

     

    各種パラメーターの説明

     

    //実行間隔(ms)
    const intervalMs = 300;

    こちらを変えると、線が生成される時間の間隔が変わります。単位はミリ秒です。

    間隔を短くしたい場合は数値を小さく、間隔を長くしたい場合は数値を大きくします。

    間隔を短くしすぎるとブラウザに負荷がかかるので、小さくしすぎないことをおすすめします。

     

    //描画を許可する座標の範囲(max)
    const randomRangeMax = 110;
    
    //描画を許可する座標の範囲(min)
    const randomRangeMin = -10;

    描画を許可する座標の範囲です。

    この設定の場合、Y軸は110vh ~ -10vh、X軸は110vw ~ -10vwの範囲に線が描画されます。

    この数値同士の範囲を狭くすると描画範囲が狭まり、広くすると描画範囲が広がります。

     

    //直近x個の線と座標がかぶらないようにする
    const disallowedAxisNum = 4;

    線同士が近くで描画されないようにする設定です。

    直近で生成された線の座標をいくつか保存しておき、近かったら描画しないという処理を組み込んでいます。その座標をいくつまで保持するかという設定です。

    この数値を小さくしていくと、線同士が近くで描画される確率が上がります。また、「既存の線と距離が近いためNG」と判定される確率も低くなるため描画される頻度も上がります。

    この数値を大きくしていくと、線同士が近くで描画される確率が下がりますが、「既存の線と距離が近いためNG」と判定される確率が高くなるためなかなか描画されなくなります。

     

    //縦横この範囲に既存の線があったら生成しない
    const disallowedRange = 8;

    こちらは、既存の線と距離が近いかどうか判定する処理に使用する、「どの程度の距離の近さをNGとするか」という設定です。NGと判定された場合は線が描画されません。

    この数値を小さくしていくと、線同士が近くで描画される確率が上がります。また、「既存の線と距離が近いためNG」と判定される確率も低くなるため描画される頻度も上がります。

    この数値を大きくしていくと、線同士が近くで描画される確率が下がりますが、「既存の線と距離が近いためNG」と判定される確率が高くなるためなかなか描画されなくなります。

     

    // 線の長さ上限(vh)
    const lineLengthMax = 30;
    
    // 線の長さ下限(vh)
    const lineLengthMin = 20;

    線の長さの上限と下限を設定します。単位はvhです。これらの数値の範囲でランダムな長さの線が生成されます。

    線の長さを固定したい場合は、lineLengthMaxとlineLengthMinを同じ値にします。

     

    //線生成数上限
    const counterMax = 15;

    線の生成数の上限です。

    線の流れるスピードを遅くしている場合にこの値を小さくすると、流れるアニメーションが最後まで再生されないまま消える可能性が高くなります。

    この値を大きくすると、線の流れるスピードを遅くしている場合でもアニメーションが最後まで再生される確率が高くなりますが、ブラウザに負荷がかかりやすくなります。

     

    (1000 ÷ 処理の実行間隔(intervalMs) × 線のアニメーションを再生しきるのにかかる秒数) + 5

    ぐらいの値にするのがちょうどよいです。たぶん。

     

    今回のコードの場合、intervalMsが300、線のアニメーションを再生しきるのにかかる秒数が3なので、以下のような式になります。

    (1000 ÷ 300 × 3) + 5 ≒ 15

     

    もし、アニメーションが再生しきる前に線が消える事象が頻発した場合は、この設定値を上げてみてください。

     

    以上、各種パラメーターの説明でした。

    基本コピペでそのまま使えるようになっていますが、もしカスタマイズをされる場合はパラメーターをいろいろいじってお好みのバランスに調整してみてください。

     

    ちなみに以下のコードによって、ブラウザのタブが非アクティブになったら動作を停止するようになっているため、裏で動き続けることもありません。

    タブがアクティブになったら動作を再開するようにもなっています。

    // ブラウザのタブが非アクティブになったら動作を停止し初期化、アクティブになったら再実行
    document.addEventListener("visibilitychange", function() {
      if(document.visibilityState == "hidden") {
        clearInterval(interval);
        document.querySelectorAll(".slash_position").forEach(el => el.remove());
        let arrayY = [];
        let arrayX = [];
        let i = 1;
      } else {
        interval = setInterval(drawSlashLine, intervalMs);
      }
    });

     

    おわりに

    以上、背景にランダムで斜線が降り続けるアニメーションの作り方をご紹介させていただきました。

     

    ちなみに、線の流れる間隔が一定でも問題なければ、直近で生成された線の座標と近すぎないか判定する処理を行う部分にて、NGだったらリトライする旨のwhile文を書くのもありだと思います。

    ただしリトライが繰り返されすぎると、ブラウザ側から無限ループと判断されて処理が強制的に止まる場合があるのでご注意ください。

     

    ぜひ皆さんのWEBサイトにも取り入れていただけたら嬉しいです。

    有益そうだな~~と思ってくださった方がいらっしゃいましたら、ぜひ下部のリンクよりシェアをお願いいたします!

    Share シェアしてほしい

    \シェアする/

    Writer この記事をかいたひと

    みなせしゅん |ディレクター/エンジニア/デザイナー

    RYOZEN Scratch Creations代表。

    1994年生まれ。千葉育ち。
    2019年よりフリーランスで活動を開始。
    ディレクションやフロントエンド・バックエンドのコーディング・プログラミング、グラフィックデザインからWEBデザインまで、わりとなんでもやる人。

    座右の銘は「レベルを上げて実績で殴れ」。

    Other Topics

    Recommends