milvinae

web & product design.

  • 変わったGalleryを力技で作る

    変わったGalleryを力技で作る

    横並びのGallery、グリッド表示のGallery…
    ……もう飽き飽きですわ!

    そんなあなたにおすすめしたい、自分で張った写真のようなGalleryの作り方を紹介します。
    まずは達成したい目標を明らかにしましょう。

    <目標>
    ・任意の範囲内に画像をばらまく感じのギャラリーを作りたい
    ・ばらまくアニメーションがあると尚良し
    ・Javascriptで画像要素を生成して動的に処理したい


    あんまり複雑な処理は必要なさそうですね。
    結果から見てみましょう。

    ※Codepenで使用している画像はすべてPhotockさんのフリー素材です。
    https://photock.jp/

    See the Pen picture board by keiyashi (@keiyashi) on CodePen.


    htmlはBackboardだけの超シンプル構成です。

    画像たちはJavascriptで生成するのですが、具体的にはFigure要素(picture-frameクラス)の中にimg要素が入れる感じです。
    次はそれらのcss(scss)を記しましょう。
    .backboard{
      width: 90dvw;
      height: 90dvh;
      position:relative;
      left:5dvw;
      top:5dvh;
      background-image:  url("https://raw.githubusercontent.com/keiyashi/codepen-example/refs/heads/main/CorkBoard.jpg");
      background-repeat: no-repeat;
      background-position: center;
      background-size: cover;
      padding: 15px;
      box-shadow: 2dvw 2dvw 2dvw rgba(64,64,64,.3);
      border-image-source: linear-gradient(-30deg, rgba(64,64,64,.9) 0%, rgba(128,64,32,.9) 60%);
      border-image-slice: 1;
      border-width: 13px;
      border: outset;
    }
    
    .picture-frame{
      position:absolute;
      width:15dvw;
      aspect-ratio:1.5;
      background-color:white;
      transform-origin:center;
      box-shadow:0px 0px 3px rgba(64, 64, 64, .8);
      img{
        position:relative;
        width:92%;
        height:92%;
        top:4%;
        left:4%;
        color:lightblue;
      }
      
    }


    縦長画像と横長画像を区別して表示したいという欲求はありました。
    でもなんかうまくいかなくてムシャクシャしたので横長表示で統一してしまいました。
    とにかく画像は横長Width15vwで縦横比はとにかく1.5だ!

    cssに重要な部分は特にないですね。
    グラデーションの枠や影など装飾的要素がいくつか入っています。



    つぎはJavascriptです。

    画像は扱いやすいように”pic-x.webp”という名前で統一、Githubにおいておきます。
    こうすることでJSで画像パスを引っ張りやすくしました。
    すでにフォーマットが違う画像たちを使ってギャラリーを作る場合は、名前を変えて再アップロードするかJS側でどうにか呼び出す方法を自分で考えるか諦めてプラグインを使ってください。

    const picNumber = 17;
    const picIdPrefix = "pic";
    var   gitpass = "https://raw.githubusercontent.com/keiyashi/codepen-example/refs/heads/main/pic-";
    var   fileSuffix = ".webp";
    
    function initialize(){
      var back = document.querySelector(".backboard");
      
      for(i = 0; i < picNumber; i++){
        var picture = document.createElement("figure",{id: picIdPrefix + i}); 
        picture.classList.add("picture-frame");
        var pictureInner = document.createElement("img");
        pictureInner.src = gitpass + (i+1) + fileSuffix;
        picture.appendChild(pictureInner);
        back.appendChild(picture);
      }
    }
    
    function randomSet(){
      var back = document.querySelector(".backboard");
      var backRect = back.getBoundingClientRect();
      var innerAreaX = backRect.width * 1.2;
      var innerAreaY = backRect.height * 1.2;
      anime.set(".picture-frame", {
        // delay: 1000,
        translateX: function(){return anime.random(0, innerAreaX) + "px";},
        translateY: function(){return anime.random(0, innerAreaY) + "px";},
        rotate: function(){ return anime.random(-180, 180)},
      });
    }
    
    function randomMove(){
      var back = document.querySelector(".backboard");
      var backRect = back.getBoundingClientRect();
      var innerAreaX = backRect.width * 0.8;
      var innerAreaY = backRect.height * 0.8;
      anime({
        targets: ".picture-frame",
        delay: 500,
        duration: function(){ return anime.random(0, 1000)},
        easing: "easeOutSine",
        translateX: function(){return anime.random(0, innerAreaX) + "px";},
        translateY: function(){return anime.random(0, innerAreaY) + "px";},
        rotate: function(){ return anime.random(-180, 180)},
      });
    }
    
    document.addEventListener("DOMContentLoaded", (e) =>{
      initialize();
      randomSet();
      randomMove();
    })

    画像をばらまくアニメーションをつけたいのでanime.jsを使っています。
    initializeで画像要素を生成してBack-boardに入れ込む ⇒ 画像たちの初期位置をSetする ⇒ 画像たちが初期位置からランダムに動く という流れになっています。


    シンプルではありますが画像たちをバラバラに配置することが出来ました。
    こうなってくると、一度に見せる画像を少なくして視認性を上げたり、選択した画像をアップで表示したりなど機能を追加したくなりますね。
    後日、改良方法をアップしたいと思います。




    <注意>
    勘のいい方は画像が偏りがちなことに気付くでしょう。
    これはanime.randomの中で使われているMath.randomが実装依存の擬似乱数であることが原因のようです。
    ※乱数生成の種となるシードが実装時点で決まってしまっているためユーザが変更することが出来ず、生成される乱数には偏りが出るみたいです。
    https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Math/random

    これは見た目上よろしくないので後日記事で解決方法を記載します。

  • anime.jsで変化し続けるグラデーションを作る2

    anime.jsで変化し続けるグラデーションを作る2

    See the Pen change keycolor in marble gradient with animejs by keiyashi (@keiyashi) on CodePen.

    先の記事では自動でランダムに変化するグラデーションを作ることが出来ました。
    次はインタラクティブに色相を変化させる処理を追加してみましょう。

    方針は次の通りです。
    ・ラジオボタンを作って色を割り振る
    ・ラジオボタンを押すと、割り振った色相角度を中心に一定の範囲内で色を変化させる
    ・ランダムグラデーションに戻るボタンも作る

    まずはHTMLから
    <div class="grad-board">
      <div id="marble-ball-1" class="marble-ball"></div>
      <div id="marble-ball-2" class="marble-ball"></div>
      <div id="marble-ball-3" class="marble-ball"></div>
      <div id="marble-ball-4" class="marble-ball"></div>
      <div id="marble-ball-5" class="marble-ball"></div>
      
      <div class="radios">
        <div class="radio-case">
          <input id="red" name="color-btn" type="radio" class="button">
          <label for="red" class="color-name">red</label>
        </div>
        <div class="radio-case">
          <input id="yellow" name="color-btn" type="radio" class="button">
          <label for="yellow" class="color-name">yellow</label>
        </div>
        <div class="radio-case">
          <input id="green" name="color-btn" type="radio" class="button">
          <label for="green" class="color-name">green</label>
        </div>
        <div class="radio-case">
          <input id="blue" name="color-btn" type="radio" class="button">
          <label for="blue" class="color-name">blue</label>
        </div>
        <div class="radio-case">
          <input id="purple" name="color-btn" type="radio" class="button">
          <label for="purple" class="color-name">purple</label>
        </div>
        <div class="radio-case">
          <input id="gradient" name="color-btn" type="radio" class="button">
          <label for="gradient" class="color-name">gradient</label>
        </div>
      </div>
    </div>

    <div class="radios">以下が新しく追加した部分になります。
    <input>と<label>を<div>でくくったものを1つの色セットとして、必要分準備します。
    今回は赤・黄色・緑・青・紫、そしてランダムカラーの7セットを準備しています。

    inputをラジオボタンとして使うには、属性にtype="radio"・name="任意の名前”を指定すればOKです。
    nameはラジオボタンのグループ名だと思ってください。
    labelのfor属性にセットになってるラジオボタンのidを指定すると、ボタンと連携できます。
    ラベルをクリックしてもラジオボタンの選択状態を変えることが出来るようになります。

    .grad-board{
      width :100dvw;
      height:100dvh;  
      display: block;
      position: relative;
      background-color:white;
      padding:0;
      margin:0;
      overflow:hidden;
      
      @media screen and (orientation:portrait)  { --dot-size : 100dvh;}
      @media screen and (orientation:landscape) { --dot-size : 100dvw;}
      
      .marble-ball{
        position: absolute;
        z-index: 0;
        width: var( --dot-size);
        height: var(--dot-size);
        border-radius: calc(var(--dot-size) / 4);
        transform-origin: center;
        background: radial-gradient(rgb(0,128,128) 0px, transparent 70%);
      }
    
      #marble-ball-1{
        left: calc(50dvw - var(--dot-size)/2);
        top:  calc(50dvh - var(--dot-size)/2);
      }
      #marble-ball-2{
        left: calc(25dvw - var(--dot-size)/2);
        top:  calc(25dvh - var(--dot-size)/2);
      }
      #marble-ball-3{
        left: calc(25dvw - var(--dot-size)/2);
        top:  calc(75dvh - var(--dot-size)/2);
      }
      #marble-ball-4{
        left: calc(75dvw - var(--dot-size)/2);
        top:  calc(25dvh - var(--dot-size)/2);
      }
      #marble-ball-5{
        left: calc(75dvw - var(--dot-size)/2);
        top:  calc(75dvh - var(--dot-size)/2);
      }
      
      .radios{
        z-index:10;
        display:flex;
        gap:1;
        position:absolute;
        flex-direction:column;
        margin-top:2em;
        
        .radio-case{      
          .button{
            visibility:hidden;
          }
          .button:checked+label {
              color: white;
          }
          
          .color-name{
            font-size:2em;
          }
          .color-name:hover {
            color: white;
          }
        }
      }
    }
    

    次はCSS(scss)です。
    こちらは特にいうことないです。
    // inspired by "Layered animations with anime.js"
    // https://codepen.io/juliangarnier/pen/LMrddV
    // and it needs to change anime.js about dealing colors.
    // please check my github. 
    // https://github.com/keiyashi/animejs_for_marbleGradient
    
    var currentHue = 0;
    var angleSetting = 20;
    var angleDefault = 180;
    var hueAngle = 180;
    var colorMap ;
    
    document.addEventListener("DOMContentLoaded", (event) => {
      layeredAnimation();
    
      colorMap ={        // hue angle
        'red':      {hue: 0   , angle: angleSetting } ,  
        'yellow':   {hue: 60  , angle: angleSetting } ,
        'green':    {hue: 120 , angle: angleSetting } , 
        'blue':     {hue: 200 , angle: angleSetting } ,
        'purple':   {hue: 300 , angle: angleSetting } ,
        'gradient': {hue: 180 , angle: angleDefault }
      };
      
      radioSwitchInit();
    });
    
    function layeredAnimation(){
        var layeredAnimationEl = document.querySelector('.grad-board');
        var colorPints = layeredAnimationEl.querySelectorAll('.marble-ball');
    
        for (var i = 0; i < colorPints.length; i++) {
            animateShape(colorPints[i]);
        }
    }
    
    function animateShape(el) {
        var easings = ['easeInOutQuad', 'easeInOutCirc', 'easeInOutSine'];
        var animation = anime.timeline({
            targets: el,
            duration: function() { return anime.random(3000, 4000); },
            easing: function() { return easings[anime.random(0, easings.length - 1)]; },
            complete: function(anim) { animateShape(anim.animatables[0].target); },
        })
        .add({
            translateX: anime.random(-500, 500),
            translateY: anime.random(-400, 400),
            scale: anime.random(100, 200) / 100,
            background: makeColorContext(),
        }, 0);
    }
    
    function makeColorContext(){
        var angle = hueAngle;
        var angleBand = anime.random(-angle, angle);
        var hue   = currentHue + angleBand;
        if(hue >= 360){
            hue = 720 - hue;
        }else if(hue < 0){
            hue = 360 + hue;
        }
      
       var colorText = 'hsl(' + Math.round(hue) + ', ' + 80 + '%, ' + 50 + '%)';
        var outputContext = 'radial-gradient(' + anime.hsl2Rgb(colorText) + ' 0px, transparent 50%)';
        return outputContext;
    }
    
    function radioSwitchInit(){
        var radios = document.querySelectorAll('.button');
        radios.forEach(curRadio => {
            curRadio.addEventListener("change", (e) =>{
                currentHue = colorMap[curRadio.id].hue;
                hueAngle   = colorMap[curRadio.id].angle;
            })
         })
    }

    今回、ラジオボタンのIDをキーにしたカラーマップを準備しています。
    バリューには色相角度と、取り得る角度の範囲を持たせています。

    変数として選択中の色相角度と変化可能角度があり、任意のラジオボタンが押下されるとカラーマップから該当するidの色相角度と範囲を取得します。

    上記変数はアニメーション中に参照しているので、ラジオボタンを押すことでグラデーションカラーが変わります。

    無事、インタラクティブな色の変化を起こすことが出来ました。
  • anime.jsで変化し続けるグラデーションを作る

    anime.jsで変化し続けるグラデーションを作る
    関連項目…
     HTML、SCSS、javascript、anime.js

    web上でいろんなエフェクトをつけたい…
    でもプラグインを利用すると干渉しあったり変な挙動になったりする…
    ReactやVueで作ってもWordpressのようなCMSに適用するのはなんか複雑…

    そんな貴方に使っていただきたい知識たちを紹介します。
    当サイトでも使われている、グラデーションで変化し続けるエフェクトを、anime.jsで作っていきます。
    使うライブラリはanime.jsのみです。

      ~~~~~~~~~~~ anime.js とは? ~~~~~~~~~~~~~~

      シンプルでありながら強力なAPIを備えた軽量Javascriptアニメーションライブラリ(本文まま)
       https://animejs.com/
      何がいいって、NpmとかBuildとかカスタムフィールド作って無理やり追加するとかなしに、シンプルなJavaScriptLibraryであること。
      CMS使ってウェブサイト作っている最中に、エフェクト追加したくなっちゃった、そんな時に簡単に導入できる。
      クラス追加を強制しないし、既存コードにも追加しやすいところも素敵。
      歴史も古いので参考資料がたくさんあるものよいところ。

      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    CodePenをお見せしますので、こちらを見ながら解説していきます。
    見づらい場合はCodePenのサイトに移ってみてください。
     https://codepen.io/keiyashi/pen/RwXjamN
    気に入ったらLoveボタン、Pin留めよろしくお願いします。

    See the Pen transition marble gradient with animejs by keiyashi (@keiyashi) on CodePen.


    まず、やりたいこと明らかにしていきましょう。


    <要求>

    ・色が時間変化
     → リニアな変化でなく、2次元的にランダムな変化をさせたい。
    ・色調は固定
     → 色調(トーン:tone)は色の調子であり、PCCS(日本色研配色体系)ではビビッド・ソフト・ペールなど12の色調群に分けられている。
       色調も変化してしまうと統一感が失われるので、彩度と明度を維持して色相だけ変化させたい。
    ・滑らかに変化する
     → 色の変化が途切れない。


    <情報収集>

    ・cssと簡単なjsでどうにかならないか?
     → Linear-gradient及びRadial-gradientの指定はできるが、うねうね動き続けるのは難しい。
       mesherで生成するような複数のグラデーションを混ぜて、そのパラメータを時間変化させようとしても切り替わりは滑らかにならない。
    ・簡単に導入できるライブラリで解決できないか?
     → ReactやVue、Three.jsなどは多彩なことが出来るが導入コストが高い。
       anime.jsはjavascriptライブラリで、Wordpressに追加しやすく応用が利く。サンプルが多い。
       またanime.jsは色の変化もサポートしており、色から色への変化を滑らかにつないでくれる。
       これは使えそうな気がします。
    ・cssの色空間は?
     → 加法色空間(色を加えていくと白くなる)なので、色を重ねても汚くならない。
       透過をもつhtml要素を重ねることでグラデーションが表現できる。


    <方向性>

    以上のことから以下の方向での実装を探ります。

    ・大きい丸(ウィンドウをカバーするくらい大きい丸)を複数準備する
    ・それぞれの丸にはキーとなる色とTransparent(透明)の放射グラデーションを指定する
    ・透過グラデーションのある大きい丸を重ねることで、複雑なグラデーションを表現
    ・丸の位置や大きさを変化させてさらに複雑なグラデーションに!


    <実装>

     html…
    <div class="grad-board">
      <div id="marble-ball-1" class="marble-ball"></div>
      <div id="marble-ball-2" class="marble-ball"></div>
      <div id="marble-ball-3" class="marble-ball"></div>
      <div id="marble-ball-4" class="marble-ball"></div>
      <div id="marble-ball-5" class="marble-ball"></div>
    </div>
    シンプルですね。
    背景になるgrad-boardと、動く丸1~5があります。

    ではcss(scss)をつけていきましょう。
    .grad-board{
      width :100dvw;
      height:100dvh;  
      display: block;
      position: relative;
      background-color:white;
      padding:0;
      margin:0;
      overflow:hidden;
      
      @media screen and (orientation:portrait)  { --dot-size : 100dvh;}
      @media screen and (orientation:landscape) { --dot-size : 100dvw;}
      
      .marble-ball{
        position: absolute;
        z-index: 1;
        width: var( --dot-size);
        height: var(--dot-size);
        border-radius: calc(var(--dot-size) / 4);
        transform-origin: center;
        background: radial-gradient(rgb(0,128,128) 0px, transparent 70%);
      }
    
      #marble-ball-1{
        left: calc(50dvw - var(--dot-size)/2);
        top:  calc(50dvh - var(--dot-size)/2);
      }
      #marble-ball-2{
        left: calc(25dvw - var(--dot-size)/2);
        top:  calc(25dvh - var(--dot-size)/2);
      }
      #marble-ball-3{
        left: calc(25dvw - var(--dot-size)/2);
        top:  calc(75dvh - var(--dot-size)/2);
      }
      #marble-ball-4{
        left: calc(75dvw - var(--dot-size)/2);
        top:  calc(25dvh - var(--dot-size)/2);
      }
      #marble-ball-5{
        left: calc(75dvw - var(--dot-size)/2);
        top:  calc(75dvh - var(--dot-size)/2);
      }
    }
    
    
    
    grad-boardに、サイズやはみ出た部分の非表示を設定していきます。

    丸が重ならないとうまくグラデーションが表現できないので、丸のサイズは描画領域の縦横の大きいほうと同じ直径にしています。
    丸のBackgroundColorはJavascriptで変化させるので、初期値は入れなくてもいいかもしれませんが、間違いがあった時に差異がわかりやすいように入れておきます。

    また、animationではTranslateXとTranslateYで丸の位置を変化させるため、基準となる位置を指定しておきます。
    それぞれの丸にPosition:absoluteを指定し、TopとLeftで初期位置を渡しておきます。
    // inspired by "Layered animations with anime.js"
    // https://codepen.io/juliangarnier/pen/LMrddV
    
    document.addEventListener("DOMContentLoaded", (event) => {
        layeredAnimation();
    });
    
    function layeredAnimation(){
        var layeredAnimationEl = document.querySelector('.grad-board');
        var colorPints = layeredAnimationEl.querySelectorAll('.marble-ball');
    
        for (var i = 0; i < colorPints.length; i++) {
            animateShape(colorPints[i]);
        }
    }
    
    function animateShape(el) {
        var easings = ['easeInOutQuad', 'easeInOutCirc', 'easeInOutSine'];
        var animation = anime.timeline({
            targets: el,
            duration: function() { return anime.random(3000, 4000); },
            easing: function() { return easings[anime.random(0, easings.length - 1)]; },
            complete: function(anim) { animateShape(anim.animatables[0].target); },
        })
        .add({
            translateX: anime.random(-500, 500),
            translateY: anime.random(-400, 400),
            scale: anime.random(100, 200) / 100,
            background: makeColorContext(),
        }, 0);
    }
    
    このコードは、anime.jsのサンプル ”Layered animations with anime.js"にインスパイアされて作っています。
    丸の動きはサンプルのモノを簡略化して使っています。

    このスクリプトの核はanimateShape関数です。
    anime.timeline({条件}).add({遷移先})でアニメーションが定義されており、completeで再帰的に同じ関数が呼び出されます。
    条件に記載されているのは、
    ・Targets : アニメーションさせたい対象
    ・Duration : アニメーションが行われる時間
    ・Easing : 変化の仕方
    ・Complete: アニメーション終了後に呼び出される処理
    となっています。
    {条件}にはLoopやらDelayやらDirectionやら指定ができますが、詳しく知りたい方は本家ドキュメントを参照ください。
    時間にかかわる要素はすべてミリ秒での指定です。

    {遷移先}では、Targetsで指定された要素がどのように変化してほしいか、が記述されています。
    今回はTranslateX、TranslateY、Scale、Backgroundが変化対象として指定されています。

    サンプルに従えばBackgroundに色を指定することでアニメートできるようです。
    早速、グラデーションを当てて確認してみましょう。
    radial-gradient(at 50% 50%, hsl(60, 80%,60%) 0%, transparent 70%)


    …色が変わりません。
    グラデーションでないhslでは適用しました。
    ちょっとanime.jsの中身をのぞいてみます。

    …Backgroundで検索をかけてもヒットしません。
    ではcolorはどうでしょうか?
    …色であるかをチェックする関数、色であればrgbaに変換する関数がありました。
    どうやらここを通すことでanime.jsが扱える形に変えているようです。
    is.col関数にradial-gradientを色として認識できるように変更します。
    変更を加えたanime.jsはこちらに置いておきます。

     https://github.com/keiyashi/animejs_for_marbleGradient

    これで行けそうな気がします。
    再チャレンジです。
    いい感じにグラデーションがかかってますね。

    RGBで色を指定すると色調の維持が難しいので、HSLで色を指定しました。
    HSLはHue(色相)、Saturation(彩度)、Lightness(明度)の3要素で構成されており、彩度と明度を維持することで色調を維持することが出来ます。
    hue : 0~360
    saturation/lightness: 0~100%

    cssでグラデーションを指定する方法はいくつかありますが、今回は放射グラデーションを使っています。
    記述形式はmesherで出力される形式を使ってみます。

    ちょっとRadial-gradientの解説しましょう。
    放射グラデーションの指定方法はいくつもありますが、基本は同じです。
    要素の縦横何%の位置からグラデーションを開始するか、色の指定と基準点からの距離の情報で指定できます。
    上の例で行くと、html要素の縦50%・横50%を基準にして、基準点から0%の位置からhslで指定した色が始まり、基準点から70%の位置に向かってTransparentになっていく、という感じです。
    3色でも指定できますし、基準点の指定がなくても行けます。
    先ほどのグラデーションは次のグラデーションと等価です。
    radial-gradient(hsl(60, 80% 60%) 0%, transparent 70%)

    じゃあアニメーションに適用してみましょう。
    分かりやすくするため、丸は1つだけにしています。
    animeのBackgroundにmakeColorContext関数を通して上のグラデーションを渡してみます。
    
    function makeColorContext(){
      return 'radial-gradient(at 50% 50%, hsl(60, 80%,60%) 0%, transparent 70%)';
    }
    
    
    ぉ、ぉおん…?
    なんか変ですね…。
    at 50% 50% を at 0% 50%に変えてみましょうか。
    もっと不可解になってしまいました。
    ただ、動きに関していうと終着点は指定通りの位置に来ているように見えます。
    もしかするとat X% Y%もアニメーション対象として認識されてしまっているのかもしれません。

    一旦at~~の部分を取り外してみます。
    位置は望ましいですが色が勝手に変わってしまいます。
    確かに時間変化するグラデーションを作りたいですが、勝手に変化してしまうのはいただけません。

    hslでの指定がよくないのでしょうか?
    rgbでグラデーションを作ってみます。

    いい感じですね。
    ただし、色調はコントロールしたいので、hslで色を指定してからrgb変換してradial-gradientに渡すようにしましょう。
    hslからrgbに変換する関数はanime.jsに記述されているのでanime.js内に下記を追加して呼び出せるようにしておきます。
    anime.hsl2Rgb = hslToRgb;

    さて、放射グラデーションを時間変化させてみましょう
    Backgroundcolorに、ランダムに色を与えるmakeColorContextを実装します。
    
    function makeColorContext(){
        var angleBand = anime.random(-180, 180);
        var hue   = 180 + angleBand;
        var colorText = 'hsl(' + Math.round(hue) + ', ' + 80 + '%, ' + 50 + '%)';
        var outputContext = 'radial-gradient(' + anime.hsl2Rgb(colorText) + ' 0px, transparent 50%)';
        return outputContext;
    }
    色相は0~360度の色相環で表現されています。
    それを踏まえてランダムなRadial-gradientを作りましょう。

    180を基準に、-180~180の範囲のランダムな値を合成して、ランダムな色相を作ります。
    彩度と明度を固定したhsl形式に色相を指定します。
    hslをrgbに変換してradial-gradientを作ります。
    以上。

    では丸1個の状態で試してみましょう。
    とてもいい感じです。

    丸5つで動かしながらやってみましょう。
    Gifでみると重なっている部分のシャギーが気になりますが、実物はもっとなめらかでいい感じです。
    これでランダムに時間変化する2次元グラデーションが出来ました。
    きっともっといい作り方があると思います。
    いい方法があれば教えてください。

    <注意点>
    大きい丸がabsolute指定されているので要素の上下関係で上のほうに来てしまうため、下の要素にタッチできなくなる場合があります。
    その場合は丸にpointer-events:noneを持たせたりして回避して下さい。