Animation blob CSS Javascript

Pour toutes les discussions javascript, jQuery et autres frameworks
Répondre
Malys
Messages : 1
Enregistré le : 08 déc. 2022, 15:42

Animation blob CSS Javascript

Message par Malys » 08 déc. 2022, 15:52

Bonjour à tous,

Je tente de personnaliser une animation effet "blobs" ou "lampe à lave" CSS Javascript (voici le code d'origine https://codepen.io/Chandrakant/pen/eYBBxPQ).
J'ai réussi à supprimer le dégradé de fond des "blobs" et leur mettre un contour blanc, seulement maintenant le scontours des "blobs" ont un problème d'affichage, une sorte de "trou" qui oscille.
Voici le code :

HTML:

Code : Tout sélectionner

<!doctype html>
<html lang="fr">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" type="text/css" href="style.css">
    <style>
        @import url('https://fonts.googleapis.com/css2?family=Nunito:wght@700;800;900&family=Open+Sans&display=swap');
    </style>  
</head>

<body>

    <div class="bubblewrap">

        <div class="wrap">
            <div class="text-wrap">
                <div class="bubblewrapText ">
                    <h1>Bonjour !</h1>
                </div>
                <div class="header-text">
                    <p>Je blablablabla blablabla bla bla<br>
                        Et blablabla bla blabla</p>
                </div>

            </div>
            <canvas id="bubble"></canvas>
        </div>

    </div>

    <script src="bubblewrap.js"> </script>

</body>

</html>
CSS :

Code : Tout sélectionner

body{
    margin: 0;
    padding: 0;
}

h1{
    color: #F8F7F7;
    font-size: 80px;
    font-family: 'Nunito', sans-serif;
    font-weight: 900;
    margin-bottom: 20px;
    margin-top: 30px;
}

p{
    
}

.text-wrap{
    position: absolute;
    left: 10em;
    z-index:1;
    width: 100%;
}

.bubblewrap{
    height: 650px;
    position: relative;
}

.bubblewrapText,
.header-text{
    width: 80%;
    z-index: 1;
}

.header-text p{
    color:#F8F7F7;
    font-size: 30px;
    font-family: 'Open Sans', sans-serif;
    line-height:40px;
    font-weight: 400;
}

.wrap {
  overflow: hidden;
  position: relative;
  top: 0;
  left: 0;
  width: 100%;
  height: 94%;
  background-color: #262226;
z-index: -1;
}

.wrap canvas {
  position: absolute;
  top: -2%;
  left: -2%;
  width: 104%;
  height: 104%;
}

.page-center{
    max-width:1160px;
    margin-left: auto;
    margin-right: auto;
}
Javascript :

Code : Tout sélectionner

(function () {
    "use strict";
  
    var lava0;
    var ge1doot = {
      screen: {
        elem: null,
        callback: null,
        ctx: null,
        width: 100,
        height: 100,
        left: 0,
        top: 0,
        init: function (id, callback, initRes) {
          this.elem = document.getElementById(id);
          this.callback = callback || null;
          if (this.elem.tagName == "CANVAS")
            this.ctx = this.elem.getContext("2d");
          window.addEventListener(
            "resize",
            function () {
              this.resize();
            }.bind(this),
            false
          );
          this.elem.onselectstart = function () {
            return false;
          };
          this.elem.ondrag = function () {
            return false;
          };
          initRes && this.resize();
          return this;
        },
        resize: function () {
          var o = this.elem;
          this.width = o.offsetWidth;
          this.height = o.offsetHeight;
          for (this.left = 0, this.top = 0; o != null; o = o.offsetParent) {
            this.left += o.offsetLeft;
            this.top += o.offsetTop;
          }
          if (this.ctx) {
            this.elem.width = this.width;
            this.elem.height = this.height;
          }
          this.callback && this.callback();
        }
      }
    };
  
    // Point constructor
    var Point = function (x, y) {
      this.x = x;
      this.y = y;
      this.magnitude = x * x + y * y;
      this.computed = 0;
      this.force = 0;
    };
    Point.prototype.add = function (p) {
      return new Point(this.x + p.x, this.y + p.y);
    };
  
    // Ball constructor
    var Ball = function (parent) {
      var min = 0.5;
      var max = 3.0;
      this.vel = new Point(
        (Math.random() > 0.5 ? 1 : -1) * (0.2 + Math.random() * 0.25),
        (Math.random() > 0.5 ? 1 : -1) * (0.2 + Math.random())
      );
      this.pos = new Point(
        parent.width * 0.2 + Math.random() * parent.width * 0.6,
        parent.height * 0.2 + Math.random() * parent.height * 0.6
      );
      this.size =
        parent.wh / 15 + (Math.random() * (max - min) + min) * (parent.wh / 15);
      this.width = parent.width;
      this.height = parent.height;
    };
  
    // move balls
    Ball.prototype.move = function () {
      // bounce borders
      if (this.pos.x >= this.width - this.size) {
        if (this.vel.x > 0) this.vel.x = -this.vel.x;
        this.pos.x = this.width - this.size;
      } else if (this.pos.x <= this.size) {
        if (this.vel.x < 0) this.vel.x = -this.vel.x;
        this.pos.x = this.size;
      }
  
      if (this.pos.y >= this.height - this.size) {
        if (this.vel.y > 0) this.vel.y = -this.vel.y;
        this.pos.y = this.height - this.size;
      } else if (this.pos.y <= this.size) {
        if (this.vel.y < 0) this.vel.y = -this.vel.y;
        this.pos.y = this.size;
      }
  
      // velocity
      this.pos = this.pos.add(this.vel);
    };
  
    // lavalamp constructor
    var LavaLamp = function (width, height, numBalls, c0, c1) {
      this.step = 5;
      this.width = width;
      this.height = height;
      this.wh = Math.min(width, height);
      this.sx = Math.floor(this.width / this.step);
      this.sy = Math.floor(this.height / this.step);
      this.paint = false;
      this.plx = [0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0];
      this.ply = [0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1];
      this.mscases = [0, 3, 0, 3, 1, 3, 0, 3, 2, 2, 0, 2, 1, 1, 0];
      this.ix = [1, 0, -1, 0, 0, 1, 0, -1, -1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1];
      this.grid = [];
      this.balls = [];
      this.iter = 0;
      this.sign = 1;
  
      // init grid
      for (var i = 0; i < (this.sx + 2) * (this.sy + 2); i++) {
        this.grid[i] = new Point(
          (i % (this.sx + 2)) * this.step,
          Math.floor(i / (this.sx + 2)) * this.step
        );
      }
  
      // create metaballs
      for (var k = 0; k < numBalls; k++) {
        this.balls[k] = new Ball(this);
      }
    };
    // compute cell force
    LavaLamp.prototype.computeForce = function (x, y, idx) {
      var force;
      var id = idx || x + y * (this.sx + 2);
  
      if (x === 0 || y === 0 || x === this.sx || y === this.sy) {
        force = 0.6 * this.sign;
      } else {
        force = 0;
        var cell = this.grid[id];
        var i = 0;
        var ball;
        while ((ball = this.balls[i++])) {
          force +=
            (ball.size * ball.size) /
            (-2 * cell.x * ball.pos.x -
              2 * cell.y * ball.pos.y +
              ball.pos.magnitude +
              cell.magnitude);
        }
        force *= this.sign;
      }
      this.grid[id].force = force;
      return force;
    };
    // compute cell
    LavaLamp.prototype.marchingSquares = function (next) {
      var x = next[0];
      var y = next[1];
      var pdir = next[2];
      var id = x + y * (this.sx + 2);
      if (this.grid[id].computed === this.iter) {
        return false;
      }
      var dir,
        mscase = 0;
  
      // neighbors force
      for (var i = 0; i < 4; i++) {
        var idn = x + this.ix[i + 12] + (y + this.ix[i + 16]) * (this.sx + 2);
        var force = this.grid[idn].force;
        if (
          (force > 0 && this.sign < 0) ||
          (force < 0 && this.sign > 0) ||
          !force
        ) {
          // compute force if not in buffer
          force = this.computeForce(
            x + this.ix[i + 12],
            y + this.ix[i + 16],
            idn
          );
        }
        if (Math.abs(force) > 1) mscase += Math.pow(2, i);
      }
      if (mscase === 15) {
        // inside
        return [x, y - 1, false];
      } else {
        // ambiguous cases
        if (mscase === 5) dir = pdir === 2 ? 3 : 1;
        else if (mscase === 10) dir = pdir === 3 ? 0 : 2;
        else {
          // lookup
          dir = this.mscases[mscase];
          this.grid[id].computed = this.iter;
        }
        // draw line
        var ix =
          this.step /
          (Math.abs(
            Math.abs(
              this.grid[
                x +
                  this.plx[4 * dir + 2] +
                  (y + this.ply[4 * dir + 2]) * (this.sx + 2)
              ].force
            ) - 1
          ) /
            Math.abs(
              Math.abs(
                this.grid[
                  x +
                    this.plx[4 * dir + 3] +
                    (y + this.ply[4 * dir + 3]) * (this.sx + 2)
                ].force
              ) - 1
            ) +
            1);
        ctx.lineTo(
          this.grid[
            x + this.plx[4 * dir] + (y + this.ply[4 * dir]) * (this.sx + 2)
          ].x +
            this.ix[dir] * ix,
          this.grid[
            x +
              this.plx[4 * dir + 1] +
              (y + this.ply[4 * dir + 1]) * (this.sx + 2)
          ].y +
            this.ix[dir + 4] * ix
        );
        this.paint = true;
        // next
        return [x + this.ix[dir + 4], y + this.ix[dir + 8], dir];
      }
    };
  
    LavaLamp.prototype.renderMetaballs = function () {
      var i = 0,
        ball;
      while ((ball = this.balls[i++])) ball.move();
      // reset grid
      this.iter++;
      this.sign = -this.sign;
      this.paint = false;
      ctx.beginPath();
      // compute metaballs
      i = 0;
      //ctx.shadowBlur = 50;
      //ctx.shadowColor = "green";
      while ((ball = this.balls[i++])) {
        // first cell
        var next = [
          Math.round(ball.pos.x / this.step),
          Math.round(ball.pos.y / this.step),
          false
        ];
        // marching squares
        do {
          next = this.marchingSquares(next);
        } while (next);
        // fill and close path
        if (this.paint) {
          ctx.strokeStyle = "#FFFFFF";
          ctx.stroke();
          ctx.closePath();
          ctx.beginPath();
          this.paint = false;

        }
      }
    };

  
    // main loop
    var run = function () {
      requestAnimationFrame(run);
      ctx.clearRect(0, 0, screen.width, screen.height);
      lava0.renderMetaballs();
    };
    // canvas
    var screen = ge1doot.screen.init("bubble", null, true),
      ctx = screen.ctx;
    screen.resize();
    // create LavaLamps
    lava0 = new LavaLamp(
      screen.width + 20,
      screen.height + 20,
      4,
    );
  
    run();
  })();
  
Je vous remercie d'avance pour votre aide.

Avatar du membre
webmaster
Administrateur du site
Messages : 563
Enregistré le : 28 févr. 2017, 15:19

Re: Animation blob CSS Javascript

Message par webmaster » 08 déc. 2022, 16:08

Bonjour,

L'effet est impressionnant.

Pour le contour avec un morceau de trou, je le constate aussi.
J'imagine qu'il s'agit des arrondis de calcul de coordonnées.
Mais réussir à trouver l'origine peut être fastidieux...
TJS : 25 ans et mon livre Tout JavaScript chez Dunod
https://www.toutjavascript.com/livre/index.php

Vinch
Messages : 6
Enregistré le : 11 déc. 2022, 04:02

Re: Animation blob CSS Javascript

Message par Vinch » 11 déc. 2022, 19:25

Ce n'est pas tout à fait comme vous sembler le désirer mais qui sait...

Code : Tout sélectionner

   ;(function() {

  "use strict";

  var lava0;
  var ge1doot = {
    screen: {
      elem:     null,
      callback: null,
      ctx:      null,
      width:    100,
      height:   100,
      left:     0,
      top:      0,
      init: function (id, callback, initRes) {
        this.elem = document.getElementById(id);
        this.callback = callback || null;
        if (this.elem.tagName == "CANVAS") this.ctx = this.elem.getContext("2d");
        window.addEventListener('resize', function () {
          this.resize();
        }.bind(this), false);
        this.elem.onselectstart = function () { return false; }
        this.elem.ondrag        = function () { return false; }
        initRes && this.resize();
        return this;
      },
      resize: function () {
        var o = this.elem;
        this.width  = o.offsetWidth;
        this.height = o.offsetHeight;
        for (this.left = 0, this.top = 0; o != null; o = o.offsetParent) {
          this.left += o.offsetLeft;
          this.top  += o.offsetTop;
        }
        if (this.ctx) {
          this.elem.width  = this.width;
          this.elem.height = this.height;
        }
        this.callback && this.callback();
      }
    }
  }

  // Point constructor
  var Point = function(x, y) {
    this.x = x;
    this.y = y;
    this.magnitude = x * x + y * y;
    this.computed = 0;
    this.force = 0;
  };
  Point.prototype.add = function(p) {
    return new Point(this.x + p.x, this.y + p.y);
  };

  // Ball constructor
  var Ball = function(parent) {
    var min = .5;
    var max = 3.0;
    this.vel = new Point(
      (Math.random() > 0.5 ? 1 : -1) * (0.2 + Math.random() * 0.25), (Math.random() > 0.5 ? 1 : -1) * (0.2 + Math.random())
    );
    this.pos = new Point(
      parent.width * 0.2 + Math.random() * parent.width * 0.6,
      parent.height * 0.2 + Math.random() * parent.height * 0.6
    );
    this.size = (parent.wh / 15) + ( Math.random() * (max - min) + min ) * (parent.wh / 15);
    this.width = parent.width;
    this.height = parent.height;
  };

  // move balls
  Ball.prototype.move = function() {

    // bounce borders
    if (this.pos.x >= this.width - this.size) {
      if (this.vel.x > 0) this.vel.x = -this.vel.x;
      this.pos.x = this.width - this.size;
    } else if (this.pos.x <= this.size) {
      if (this.vel.x < 0) this.vel.x = -this.vel.x;
      this.pos.x = this.size;
    }

    if (this.pos.y >= this.height - this.size) {
      if (this.vel.y > 0) this.vel.y = -this.vel.y;
      this.pos.y = this.height - this.size;
    } else if (this.pos.y <= this.size) {
      if (this.vel.y < 0) this.vel.y = -this.vel.y;
      this.pos.y = this.size;
    }

    // velocity
    this.pos = this.pos.add(this.vel);

  };

  // lavalamp constructor
  var LavaLamp = function(width, height, numBalls, c0, c1) {
    this.step = 5;
    this.width = width;
    this.height = height;
    this.wh = Math.min(width, height);
    this.sx = Math.floor(this.width / this.step);
    this.sy = Math.floor(this.height / this.step);
    this.paint = false;
    this.metaFill = createRadialGradient(width, height, width, c0, c1);
    this.plx = [0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0];
    this.ply = [0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1];
    this.mscases = [0, 3, 0, 3, 1, 3, 0, 3, 2, 2, 0, 2, 1, 1, 0];
    this.ix = [1, 0, -1, 0, 0, 1, 0, -1, -1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1];
    this.grid = [];
    this.balls = [];
    this.iter = 0;
    this.sign = 1;

    // init grid
    for (var i = 0; i < (this.sx + 2) * (this.sy + 2); i++) {
      this.grid[i] = new Point(
        (i % (this.sx + 2)) * this.step, (Math.floor(i / (this.sx + 2))) * this.step
      )
    }

    // create metaballs
    for (var k = 0; k < numBalls; k++) {
      this.balls[k] = new Ball(this);
    }
  };
  // compute cell force
  LavaLamp.prototype.computeForce = function(x, y, idx) {

    var force;
    var id = idx || x + y * (this.sx + 2);

    if (x === 0 || y === 0 || x === this.sx || y === this.sy) {
      force = 0.6 * this.sign;
    } else {
      force = 0;
      var cell = this.grid[id];
      var i = 0;
      var ball;
      while (ball = this.balls[i++]) {
        force += ball.size * ball.size / (-2 * cell.x * ball.pos.x - 2 * cell.y * ball.pos.y + ball.pos.magnitude + cell.magnitude);
      }
      force *= this.sign
    }
    this.grid[id].force = force;
    return force;
  };
  // compute cell
  LavaLamp.prototype.marchingSquares = function(next) {
    var x = next[0];
    var y = next[1];
    var pdir = next[2];
    var id = x + y * (this.sx + 2);
    if (this.grid[id].computed === this.iter) {
      return false;
    }
    var dir, mscase = 0;

    // neighbors force
    for (var i = 0; i < 4; i++) {
      var idn = (x + this.ix[i + 12]) + (y + this.ix[i + 16]) * (this.sx + 2);
      var force = this.grid[idn].force;
      if ((force > 0 && this.sign < 0) || (force < 0 && this.sign > 0) || !force) {
        // compute force if not in buffer
        force = this.computeForce(
          x + this.ix[i + 12],
          y + this.ix[i + 16],
          idn
        );
      }
      if (Math.abs(force) > 1) mscase += Math.pow(2, i);
    }
    if (mscase === 15) {
      // inside
      return [x, y - 1, false];
    } else {
      // ambiguous cases
      if (mscase === 5) dir = (pdir === 2) ? 3 : 1;
      else if (mscase === 10) dir = (pdir === 3) ? 0 : 2;
      else {
        // lookup
        dir = this.mscases[mscase];
        this.grid[id].computed = this.iter;
      }
      // draw line
      var ix = this.step / (
          Math.abs(Math.abs(this.grid[(x + this.plx[4 * dir + 2]) + (y + this.ply[4 * dir + 2]) * (this.sx + 2)].force) - 1) /
          Math.abs(Math.abs(this.grid[(x + this.plx[4 * dir + 3]) + (y + this.ply[4 * dir + 3]) * (this.sx + 2)].force) - 1) + 1
        );
      ctx.lineTo(
        this.grid[(x + this.plx[4 * dir]) + (y + this.ply[4 * dir]) * (this.sx + 2)].x + this.ix[dir] * ix,
        this.grid[(x + this.plx[4 * dir + 1]) + (y + this.ply[4 * dir + 1]) * (this.sx + 2)].y + this.ix[dir + 4] * ix
      );
      this.paint = true;
      // next
      return [
        x + this.ix[dir + 4],
        y + this.ix[dir + 8],
        dir
      ];
    }
  };

  LavaLamp.prototype.renderMetaballs = function() {
    var i = 0, ball;
    while (ball = this.balls[i++]) ball.move();
    // reset grid
    this.iter++;
    this.sign = -this.sign;
    this.paint = false;
    ctx.fillStyle = '#262226';
    ctx.beginPath();    
    // compute metaballs
    i = 0;
    //ctx.shadowBlur = 50;
    //ctx.shadowColor = "green";
    ctx.shadowBlur = 6*2;
  ctx.shadowOffsetX =3; ctx.shadowOffsetY = 0;
  ctx.shadowColor = "#ffffff";
    while (ball = this.balls[i++]) {
      // first cell
      var next = [
        Math.round(ball.pos.x / this.step),
        Math.round(ball.pos.y / this.step), false
      ];
      // marching squares
      do {
        next = this.marchingSquares(next);
      } while (next);
      // fill and close path
      if (this.paint) {
        ctx.fill();
        ctx.closePath();
        ctx.beginPath();
        this.paint = false;
      }
    }
  };

  // gradients
  var createRadialGradient = function(w, h, r, c0, c1) {
    var gradient = ctx.createRadialGradient(
      w / 1, h / 1, 0,
      w / 1, h / 1, r
    );
    gradient.addColorStop(0, c0);
    gradient.addColorStop(1, c1);
    return gradient;
  };

  // main loop
  var run = function() {
    requestAnimationFrame(run);
    ctx.clearRect(0, 0, screen.width, screen.height);
    lava0.renderMetaballs();
  };
  // canvas
  var screen = ge1doot.screen.init("bubble", null, true),
      ctx = screen.ctx;
  screen.resize();
  // create LavaLamps
  lava0 = new LavaLamp(screen.width+20, screen.height+20, 4, "#262226", "#262226");

  run();

})();
Bon je n'y connais rien donc je suis peut-être complètement à coté de la plaque mais en jouant avec le shadow et intégrer une partie de ce code :

Code : Tout sélectionner

ontext.beginPath();
context.arc(cw/2,ch/2,75,0,Math.PI*2);
context.fillStyle='lightcyan';
context.fill();

context.globalCompositeOperation='source-atop';

context.shadowOffsetX = 500;
context.shadowOffsetY = 0;
context.shadowBlur = 15;
context.shadowColor = 'rgba(30,30,30,1)';

context.beginPath();
context.arc(cw/2-500,ch/2,75,0,Math.PI*2);
context.stroke();
context.stroke();
context.stroke();

Bien à vous

Répondre