preview

You can first experience the effect online ↓(please use PC to browse)

moayuisuda.github.io/moa-line/.

noise

An overview of the noise

Noise, as I understand it, is “continuous randomness”, such as noise(0.01), noise(0.02), noise(0.03)…… If the parameters are close, the resulting values will be random, but also close. If you want to further understand the principle of noise, recommend this article blog.csdn.net/candycat199…

Imagine an undulating sea. Is the height of the adjacent position consistent with this “continuous and random”?

Intuitive impression

The 3d noise simplex3 is used this time. You can imagine it as a large cube containing continuous random numbers in the range of -1 to 1. We input the coordinates of a cube (x, y, z) to pick out a random value, I use three.js to generate a cube to visually display the characteristics of noise ↓

  const span = 100; // Cube length
  // To get continuous random values, the parameters passed in must also differ very closely, usually within the order of 0.1.
  const scale = 100; 
  /* scale = 100 represents a three.js unit length map of 0.01(1/100) noisy cube unit length. In other words, the cube generated in three.js is 100*100*100, but it only maps the situation of 1*1*1 in the noisy cube. * /
  
  
  for (let z = 0; z < span; z++) {
    for (let y = 0; y < span; y++) {
      for (let x = 0; x < span; x++) {
        let point = new THREE.Vector3(x, y, z); // Each point is a point that makes up the cube
        // for example, when x,y,z are (4,5,5), the mapping noise cube coordinates are (0.04, 0.05, 0.05).
        // noise.simplex3() has the range -1 to 1, and the seed range is 0 to 1
        let seed = 0.5 * (noise.simplex3(x / scale, y / scale, z / scale) + 1);
        
        // * Color will reflect the seed directly, where the three parameters range from 0 to 1(1 equals 255). The darker the color, the smaller the value
        let color = newTHREE.Color(seed, seed, seed); Geometry.vertices.push(point); Geometry.colors.push(color); }}}Copy the code

let seed = 0.5 * (noise.simplex3(x / scale, y / scale, z / scale) + 1);
let color = new THREE.Color(seed, seed, seed); // The darker the color, the smaller the value
Copy the code

And you can see that the color change is not that big anymore, because scale is 10 times bigger, and x/scale, y/scale, z/scale is 10 times smaller between x, y, and z next to each other, Simplex3 (x/scale, y/scale, z/scale) is also very small, and color is also very small.

Began to

Let’s draw the circle first

<! DOCTYPE html><html>
  <head>
    <style>
      body {
        margin: 0;
      }

      .main {
        background: # 000;
        height: 100vh;
      }
    </style>
  </head>

  <body>
    <div class="main"></div>
  </body>
  <script>
    const wave = function({
      dom, //Which DOM is mounted on span =50.//The size of a single element zIndex = -999 //Canvas z - index}) {
      // Change the parent DOM to a cascading context
      dom.style.position = "relative";
      dom.style.zIndex = 0;
      dom.style.overflow = "hidden";

      const canvas = document.createElement("canvas");
      const context = canvas.getContext("2d");

      If the parent element is not a cascading context, the canvas will be directly covered by the parent element.
      canvas.style.position = "absolute";
      canvas.style.zIndex = zIndex;
      canvas.height = parseInt(getComputedStyle(dom)["height"]);
      canvas.width = parseInt(getComputedStyle(dom)["width"]);

      dom.appendChild(canvas);

      let r = span / 2; // The radius of a single element

      // Single registration point
      function Point({ cx, cy }) {
        this.cx = cx;
        this.cy = cy;
      }

      // The draw method of the anchor point, which is used to generate shapes, is now simply used to draw circles by arc
      Point.prototype.draw = function() {
        context.beginPath();
        context.strokeStyle = "#ffffff";
        context.arc(this.cx, this.cy, r, 0.2 * Math.PI);
        context.stroke();
      };

      // Initialize all registration points in the points array
      let points = [];
      function initPoints() {
        for (let y = 0; y < canvas.height; y += span) {
          for (let x = 0; x < canvas.width; x += span) {
            X < canvas.height is true even on the penultimate line of the canvas and continues downward
            points.push(
              new Point({
                cx: x + r,
                cy: y + r }) ); }}}// Each time requestAnimationFrame calls the method to redraw the cloth
      function draw() {
        context.clearRect(0.0, canvas.width, canvas.height); // Empty the canvas
        
        // Call the draw method on each point
        for (let i ofpoints) { i.draw(); }}let id; / / animation id
      function animate() {
        draw();

        id = requestAnimationFrame(animate);
      }

      initPoints();
      animate();

      // Return the canvas and stop methods to stop the animation
      return {
        canvas,
        stop(){ cancelAnimationFrame(id); }}; };/ / run
    wave({
      dom: document.querySelector(".main")});</script>
</html>
Copy the code

After running, you should see the following ↓

The introduction of noise

First of all, we need to introduce noise library, please copy and bring raw.githubusercontent.com/josephg/noi script…

For each point, we need to use its Cx and cy properties to map to the coordinates in the noise cube, but one is planar and the other is three-dimensional. Simplex3 (x, y, 0) is a two-dimensional noise. Simplex3 (x, y, 0) is a two-dimensional noise.

First add a scale to the parameters of the function, that is, the mapping ratio with the noise cube mentioned above

const wave = function({
      dom, //Which DOM is mounted on span =50.//Size of a single element scale =1000.//----------------- the previous mapping ratio with the noise cube zIndex = -999 //Canvas z - index})
Copy the code

Modify the above point.prototype.draw

  Point.prototype.draw = function() {
    context.beginPath();
    let seed = 0.5 * (noise.simplex3(this.cx / scale, this.cy / scale, 0) + 1); // Seed range 0-1
    context.strokeStyle = `rgba(255, 255, 255, ${seed}) `; // Use seed as alpha
    context.arc(this.cx, this.cy, r, 0.2 * Math.PI);
    context.stroke();
  }
Copy the code

Re-run it and you should see something like ↓ below

The opacity of the circle becomes “random and continuous”.

Let’s get it moving

We wrote animate() above, but since the animate() function does not change the properties of the graph, it looks static.

Remember that we set the third parameter of the noise function to zero ↓

noise.simplex3(this.cx / scale, this.cy / scale, 0)

And now that we’ve enabled it again, what’s the value for it? Time.

Let seed = 0.5 * (noise.simplex3(this.cx/scale, this.cy/scale, 0) + 1); let seed = 0.5 * (noise.simplex3(this.cx/scale, this.cy/scale, 0) + 1); Let seed = 0.5 * (noise.simplex3(this.cx/scale, this.cy/scale, time) + 1); let seed = 0.5 * (noise.simplex3(this.cx/scale, this.cy/scale, time) + 1);

We need to define a global variabletime, and a parameter that controls the speed of timespeedAnd in animatetime += speed;

const wave = function({
      dom, //Which DOM is mounted on span =50.//Size of a single element scale =1000.//The mapping ratio to the noisy cube is speed =0.01.//-----------------speed parameter zIndex = -999 //Canvas z - index})
Copy the code
  let id; / / animation id
  let time = 0; // ------------- initializes time to 0
  function animate() {
    draw();
    time += speed;
    id = requestAnimationFrame(animate);
  }
Copy the code

Then you will notice your picture moving down

Now that all the basic code is done, the next step is yesPoint.prototype.drawWith various modifications, let’s implement a line animation

  Point.prototype.draw = function() {
    context.beginPath();
    let s = noise.simplex3(this.cx / scale, this.cy / scale, time);
    let sa = Math.abs(s); // The absolute value of noise, used to generate alpha and lineWidth
    context.strokeStyle = `rgba(255, 255, 255, ${sa}) `;
    context.lineWidth = Math.abs(s) * 8;
    let a = Math.PI * 2 * s; / / Angle
    let ap = Math.PI + a; // Angle + 180 degrees
    
    context.moveTo(this.cx + Math.cos(a) * r, this.cy);
    context.lineTo(this.cx + Math.cos(ap) * r, this.cy + Math.sin(ap) * r);

    context.stroke();
  };
Copy the code

context.moveTo(this.cx + Math.cos(a) * r, this.cy); Context.moveto (this.cx + math.cos (a) * r, this.cy + math.sin (a)); And you could try it out and replace it with the latter, and it would be a lot less interesting, because you would just be connecting one end of the circle to the other end.

By adjusting speed, scale, SPAN and other parameters, you can get many interesting effects ↓

Then use your imagination to transformPoint.prototype.draw!

For the complete code for this example, remember to change the SRC ↓ of the noise library

<! DOCTYPEhtml>
<html>
  <head>
    <style>
      body {
        margin: 0;
      }

      .main {
        background: # 000;
        height: 100vh;
      }
    </style>
  </head>

  <body>
    <div class="main"></div>
  </body>
  <script src="./perlin.js"></script>
  <script>
    const wave = function({
      dom,
      span = 50,
      scale = 1000,
      speed = 0.01,
      zIndex = -999
    }) {
      dom.style.position = "relative";
      dom.style.zIndex = 0;
      dom.style.overflow = "hidden";

      const canvas = document.createElement("canvas");
      const context = canvas.getContext("2d");

      canvas.style.position = "absolute";
      canvas.style.zIndex = zIndex;
      canvas.height = parseInt(getComputedStyle(dom)["height"]);
      canvas.width = parseInt(getComputedStyle(dom)["width"]);

      dom.appendChild(canvas);

      let r = span / 2;

      function Point({ cx, cy }) {
        this.cx = cx;
        this.cy = cy;
      }

      Point.prototype.draw = function() {
        context.beginPath();
        let s = noise.simplex3(this.cx / scale, this.cy / scale, time);
        let sa = Math.abs(s);
        context.strokeStyle = `rgba(255, 255, 255, ${sa}) `;
        context.lineWidth = Math.abs(s) * 8;
        let a = Math.PI * 2 * s;
        let ap = Math.PI + a;

        context.moveTo(this.cx, this.cx);
        context.lineTo(this.cx + Math.cos(ap) * r, this.cy + Math.sin(ap) * r);

        context.stroke();
      };

      let points = [];
      function initPoints() {
        for (let y = 0; y < canvas.height; y += span) {
          for (let x = 0; x < canvas.width; x += span) {
            points.push(
              new Point({
                cx: x + r,
                cy: y + r }) ); }}}function draw() {
        context.clearRect(0.0, canvas.width, canvas.height);

        for (let i ofpoints) { i.draw(); }}let id;
      let time = 0;
      function animate() {
        draw();
        time += speed;
        id = requestAnimationFrame(animate);
      }

      initPoints();
      animate();

      return {
        canvas,
        stop(){ cancelAnimationFrame(id); }}; }; wave({dom: document.querySelector(".main"),
      scale: 5000.speed: 0.002.span: 100
    });
  </script>
</html>
Copy the code

Color gradient

Next, we will implement the DOM color transformation by clicking on it, and can customize the color transformation array, color conversion time. Let’s start with the new parameters:

 const wave = function({
      dom,
      span = 50,
      scale = 1000,
      speed = 0.01,
      zIndex = -999,
      duration = 2000.//-------------- color conversion time colors = [[212.192.255],
        [192.255.244],
        [255.192.203]]//------------- color array})
Copy the code

The gradient

First we need to achieve the gradient, and also need to be able to customize the gradient time, write a general linear gradient function ↓

const copy = target= > {
  let re = [];
  for (let i in target) {
    re[i] = target[i];
  }

  return re;
};

const move = (
  origin,
  target,
  duration,
  after, // Callback after each value change
  fn = pro => {
    return Math.sqrt(pro, 2);
  } // slow function
) = > {
  if (fn(1) != 1) throw '[moaline-move] The fn must satisfy "fn (1) == 1"'; // When the parameter is 1, the corresponding value must also be 1

  let st, sp;
  st = performance.now(); // Save the start time
  sp = copy(origin); // Save the source attributes
  let d = {}; // Distance of each term between source and target
  for (let i in origin) {
    d[i] = target[i] - origin[i];
  }

  let frame = t= > {
    let pro = (t - st) / duration; // The current process
    if (pro >= 1) {
      return;
    }

    for (let i in origin) {
      origin[i] = sp[i] + fn(pro) * d[i]; // fn(pro) calculates the distance percentage of the current time corresponding to the easing function, and then multiplies the total distance
    }

    if(after) after(copy(origin), pro);
    requestAnimationFrame(frame);
  };

  frame(st);
};
Copy the code

The binding event

Then we bind an event to the DOM and let it do the tweening with one click.

  let ci = 0;
  const color = [...colors[ci]];
  function clickE() {
    let target = [...colors[++ci % colors.length]];
    move(color, target, duration);
  }
  dom.addEventListener("click", clickE);
Copy the code

Numerical binding

Now that color can be tweaked, bind it to the color related part of the point.prototype. draw.

  Point.prototype.draw = function() {
    context.beginPath();
    let s = noise.simplex3(this.cx / scale, this.cy / scale, time);
    let sa = Math.abs(s);
    context.strokeStyle = `rgba(${color[0]}.${color[1]}.${color[2]}.${sa}) `;
    context.lineWidth = Math.abs(s) * 8;
    let a = Math.PI * 2 * s;
    let ap = Math.PI + a;

    context.moveTo(this.cx + Math.sin(a) * r, this.cy + Math.cos(a) * r);
    context.lineTo(this.cx + Math.cos(ap) * r, this.cy + Math.sin(ap) * r);

    context.stroke();
  };
Copy the code

Then remove the background color of the body

  .main {
    /* background: #000; */
    height: 100vh;
  }
Copy the code

The complete code is as follows (remember to change the SRC of the Noise library yourself)

<! DOCTYPEhtml>
<html>
  <head>
    <style>
      body {
        margin: 0;
      }

      .main {
        /* background: #000; * /
        height: 100vh;
      }
    </style>
  </head>

  <body>
    <div class="wrapper"></div>
    <div class="main"></div>
  </body>
  <script src="./perlin.js"></script>
  <script>
    const wave = function({
      dom,
      span = 50,
      scale = 1000,
      speed = 0.01,
      zIndex = -999,
      duration = 2000,
      colors = [
        [212.192.255],
        [192.255.244],
        [255.192.203]]}) {
      dom.style.position = "relative";
      dom.style.zIndex = 0;
      dom.style.overflow = "hidden";

      const canvas = document.createElement("canvas");
      const context = canvas.getContext("2d");

      canvas.style.position = "absolute";
      canvas.style.zIndex = zIndex;
      canvas.height = parseInt(getComputedStyle(dom)["height"]);
      canvas.width = parseInt(getComputedStyle(dom)["width"]);

      dom.appendChild(canvas);

      let r = span / 2;

      function Point({ cx, cy }) {
        this.cx = cx;
        this.cy = cy;
      }

      Point.prototype.draw = function() {
        context.beginPath();
        let s = noise.simplex3(this.cx / scale, this.cy / scale, time);
        let sa = Math.abs(s);
        context.strokeStyle = `rgba(${color[0]}.${color[1]}.${color[2]}.${sa}) `;
        context.lineWidth = Math.abs(s) * 8;
        let a = Math.PI * 2 * s;
        let ap = Math.PI + a;

        context.moveTo(this.cx + Math.sin(a) * r, this.cy + Math.cos(a) * r);
        context.lineTo(this.cx + Math.cos(ap) * r, this.cy + Math.sin(ap) * r);

        context.stroke();
      };

      let points = [];
      function initPoints() {
        for (let y = 0; y < canvas.height; y += span) {
          for (let x = 0; x < canvas.width; x += span) {
            points.push(
              new Point({
                cx: x + r,
                cy: y + r }) ); }}}function draw() {
        context.clearRect(0.0, canvas.width, canvas.height);

        for (let i ofpoints) { i.draw(); }}let id;
      let time = 0;
      function animate() {
        draw();
        time += speed;
        id = requestAnimationFrame(animate);
      }

      let ci = 0;
      const color = [...colors[ci]];
      function clickE() {
        let target = [...colors[++ci % colors.length]];
        move(color, target, duration);
      }
      dom.addEventListener("click", clickE);

    const copy = target= > {
      let re = [];
      for (let i in target) {
        re[i] = target[i];
      }
    
      return re;
    };
    
    const move = (
      origin,
      target,
      duration,
      after, // Callback after each value change
      fn = pro => {
        return Math.sqrt(pro, 2);
      } // slow function
    ) = > {
      if (fn(1) != 1) throw '[moaline-move] The fn must satisfy "fn (1) == 1"'; // When the parameter is 1, the corresponding value must also be 1
    
      let st, sp;
      st = performance.now(); // Save the start time
      sp = copy(origin); // Save the source attributes
      let d = {}; // Distance of each term between source and target
      for (let i in origin) {
        d[i] = target[i] - origin[i];
      }
    
      let frame = t= > {
        let pro = (t - st) / duration; // The current process
        if (pro >= 1) {
          return;
        }
    
        for (let i in origin) {
          origin[i] = sp[i] + fn(pro) * d[i]; // fn(pro) calculates the distance percentage of the current time corresponding to the easing function, and then multiplies the total distance
        }
    
        if(after) after(copy(origin), pro);
        requestAnimationFrame(frame);
      };
    
      frame(st);
    };


      initPoints();
      animate();

      return {
        canvas,
        stop() {
          cancelAnimationFrame(id);
          dom.removeEventListener('click', clickE); }}; }; wave({dom: document.querySelector(".main"),
      scale: 1000.speed: 0.0006.span: 200.duration: 1000
    });
  </script>
</html>
Copy the code

The last

Use your imagination, use the generated value of the noise flexibly, or add some interaction, and the effect will be more powerful. But be sure to call the return stop() method to stop the animation when you don’t need it.

Can also be multiple canvas overlay.