14
头图

In normal times, I really like to use CSS to build some interesting graphics.

Let's first look at a simple example. First, suppose we implement a 10x10 grid:

At this point, we can use some random effects to optimize this pattern. For example, we randomly add different colors to it:

Although random is used to fill the color of each grid randomly, it looks a little interesting, but this is just a messy graphic, and there is no artistic sense.

Why is this? Because the randomness here is completely random and belongs to a kind of white noise.

What is white noise?

Noise is actually a random number generator.

So, what is white noise ? If you understand it from the programmer's point of view, it can be understood that the function we use in JavaScript random() , the generated number is completely random within the range of 0~1.

The basis of noise is random numbers. For example, we add a random color to each grid of the above graph, and what we get is a chaotic graph block, which does not have much beauty at all.

White noise, or white noise, is a random signal with a constant power spectral density . In other words, the power spectral density of this signal in each frequency band is the same. Since white light is composed of monochromatic light of various frequencies (colors), the property of this signal with a flat power spectrum is called The noise is "white", and this signal is also called white noise.

Because the graphics generated by using white noise look unnatural and not very aesthetic.

Look at natural noises in real life, they don't grow like the above. For example, the texture of wood and the ups and downs of mountains, their shapes tend to be fractal, that is, they contain different levels of details. These random components are not completely independent, but there is a certain relationship between them. And apparently, white noise doesn't do that.

Perlin noise

In this way, we naturally introduce Perlin noise .

Perlin noise (Perlin noise) refers to the natural noise generation algorithm invented by Ken Perlin.

Before introducing it, let's take a look at what the above graph would look like if we didn't use white noise (completely random), but Perlin noise?

It might look like this:

Here I made a moving picture, you can feel that each click is a result of using Perlin noise random, giving each grid a different random color:

It can be seen that the graphics generated by the random effect of Perlin noise are not unrelated to each other, the changes between them are continuous, and there is no jump between them. This random effect is similar to random effects in nature, such as the changes in wood texture and mountain undulations, as mentioned above.

As mentioned above, noise is actually a random number generator. And here:

  1. The problem with white noise is that it's so random that there's no pattern to it
  2. The Perlin noise is based on randomness, and on this basis, the easing curve is used for smooth interpolation, so that the final noise effect tends to be more natural.

The specific implementation is here Improved Noise reference implementation , you can see that the source code is not very much:

 // This code implements the algorithm I describe in a corresponding SIGGRAPH 2002 paper.
// JAVA REFERENCE IMPLEMENTATION OF IMPROVED NOISE - COPYRIGHT 2002 KEN PERLIN.

public final class ImprovedNoise {
   static public double noise(double x, double y, double z) {
      int X = (int)Math.floor(x) & 255,                  // FIND UNIT CUBE THAT
          Y = (int)Math.floor(y) & 255,                  // CONTAINS POINT.
          Z = (int)Math.floor(z) & 255;
      x -= Math.floor(x);                                // FIND RELATIVE X,Y,Z
      y -= Math.floor(y);                                // OF POINT IN CUBE.
      z -= Math.floor(z);
      double u = fade(x),                                // COMPUTE FADE CURVES
             v = fade(y),                                // FOR EACH OF X,Y,Z.
             w = fade(z);
      int A = p[X  ]+Y, AA = p[A]+Z, AB = p[A+1]+Z,      // HASH COORDINATES OF
          B = p[X+1]+Y, BA = p[B]+Z, BB = p[B+1]+Z;      // THE 8 CUBE CORNERS,

      return lerp(w, lerp(v, lerp(u, grad(p[AA  ], x  , y  , z   ),  // AND ADD
                                     grad(p[BA  ], x-1, y  , z   )), // BLENDED
                             lerp(u, grad(p[AB  ], x  , y-1, z   ),  // RESULTS
                                     grad(p[BB  ], x-1, y-1, z   ))),// FROM  8
                     lerp(v, lerp(u, grad(p[AA+1], x  , y  , z-1 ),  // CORNERS
                                     grad(p[BA+1], x-1, y  , z-1 )), // OF CUBE
                             lerp(u, grad(p[AB+1], x  , y-1, z-1 ),
                                     grad(p[BB+1], x-1, y-1, z-1 ))));
   }
   static double fade(double t) { return t * t * t * (t * (t * 6 - 15) + 10); }
   static double lerp(double t, double a, double b) { return a + t * (b - a); }
   static double grad(int hash, double x, double y, double z) {
      int h = hash & 15;                      // CONVERT LO 4 BITS OF HASH CODE
      double u = h<8 ? x : y,                 // INTO 12 GRADIENT DIRECTIONS.
             v = h<4 ? y : h==12||h==14 ? x : z;
      return ((h&1) == 0 ? u : -u) + ((h&2) == 0 ? v : -v);
   }
   static final int p[] = new int[512], permutation[] = { 151,160,137,91,90,15,
   131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
   190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
   88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
   77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
   102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
   135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
   5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
   223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
   129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
   251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
   49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
   138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180
   };
   static { for (int i=0; i < 256 ; i++) p[256+i] = p[i] = permutation[i]; }
}

Of course, this article is not specifically to discuss how Perlin noise is implemented, the above code is a big deal for anyone who reads it. We just need to know that we can use Perlin noise to build more regular graphics effects. Let's make our graphics more aesthetic.

Leverage Perlin Noise in CSS with CSS-doodle

So, how do we use Perlin noise in CSS?

One way is to find some ready-made libraries, such as the noise function in p5.js.

Of course, here, I'm used to CSS-doodle , the CSS graphics building library I've covered in several articles.

Simply put, CSS-doodle is a Web-Component based library. Allows us to quickly create pages based on CSS Grid layout, and provides various convenient instructions and functions (random, loop, etc.), allowing us to obtain different CSS effects through a set of rules. You can simply take a look at its home page -- Home Page of CSS-doodle , and it only takes 5 minutes to get started quickly.

For example, the above graph, its entire code :

 <css-doodle grid="10x10">
    :doodle {
        @size: 50vmin;
        gap: 1px;
    }
   
    background: hsl(@rn(255, 1, 2), @rn(10%, 90%), @rn(10%, 90%));
</css-doodle>

That's right, with just a few sentences, you can outline such a pattern:

CSS Pattern -- CSS Doodle

Briefly explain:

  1. css-doodle is based on Web-Component package, basically all the code is written in the <css-doodle> tag, of course, you can also write some native CSS/JavaScript assistance
  2. Use grid="10x10" to generate a 10x10 Grid grid, and then cooperate with @size: 50vmin , which means to generate a Grid layout with a width and height of 50vmin , of which 10x10 gap: 1px represents the gap of grid layout
  3. Finally, the core part of the entire code is background: hsl(@rn(255, 1, 2), @rn(10%, 90%), @rn(10%, 90%)) , which means to give a background color to each grid item, of which @rn() is the core part, using the Perlin noise algorithm, Regularly map the background color to each grid

Of course, the usage of @rn() function cannot be found in the latest CSS-doodle document. For this reason, I specially asked Mr. Yuan Chuan , the author of the library.

The reply was that the official website will be refactored in the near future, so the latest syntax has not been updated. Meanwhile, the implementation of @rn() uses the implementation of Perlin noise . At the same time, the function is equivalent to doing a map similar to the noise function in p5.js, and the map is in the range from from to to set by the previous function parameters.

Here @rn() noise will randomly map to each grid according to the grid, so that there is a certain correlation between the values of adjacent grid items.

For example, we have a 10x10 Grid layout. For each Grid item, add a pseudo-element, the content of the pseudo-element, and fill it with @r(100) . Note, @r() function It is completely random without regularity, then the generated numbers are probably like this:

It can be seen that the numbers between each of them are completely random and unrelated.

What if we use correlated Perlin noise randomness? If you use @rn(100) to fill each grid, it will look like this:

Looking at it, it is easy to find that there is a certain correlation between adjacent boxes or between multiple continuous grids, which makes the graphics we create with it have certain rules.

You can simply look at the implementation of the source code. Currently, the premise is that you need to have a certain understanding of the usage of CSS-doodle:

 rn({ x, y, context, position, grid, extra, shuffle }) {
      let counter = 'noise-2d' + position;
      let [ni, nx, ny, nm, NX, NY] = last(extra) || [];
      let isSeqContext = (ni && nm);
      return (...args) => {
        let {from = 0, to = from, frequency = 1, amplitude = 1} = get_named_arguments(args, [
          'from', 'to', 'frequency', 'amplitude'
        ]);

        if (args.length == 1) {
          [from, to] = [0, from];
        }
        if (!context[counter]) {
          context[counter] = new Perlin(shuffle);
        }
        frequency = clamp(frequency, 0, Infinity);
        amplitude = clamp(amplitude, 0, Infinity);
        let transform = [from, to].every(is_letter) ? by_charcode : by_unit;
        let t = isSeqContext
          ? context[counter].noise((nx - 1)/NX * frequency, (ny - 1)/NY * frequency, 0)
          : context[counter].noise((x - 1)/grid.x * frequency, (y - 1)/grid.y * frequency, 0);
        let fn = transform((from, to) => map2d(t * amplitude, from, to, amplitude));
        let value = fn(from, to);
        return push_stack(context, 'last_rand', value);
      };
    },

语法@rn(from, to, frequency, amplitude) 58fda057fa840c7845e1477046a68df1--- ,其中fromto表示随机范围,而frequency表示噪声的频率, amplitude Indicates the amplitude of the noise. These two parameters can be understood as controlling the frequency and magnitude of random effects.

Among them new Perlin(shuffle) that is, the Perlin noise algorithm is used.

Show Time

OK, the above introduces a lot of knowledge related to noise and CSS-doodle, let's return to CSS, and return to the main body of this article.

On the basis of the above graph, we can add random scale() and skew() . If it was completely random, the code would be something like this:

 <css-doodle grid="20">
    :doodle {
        grid-gap: 1px;
        width: 600px; height: 600px;
    }
    background: hsl(@r(360), 80%, 80%);
    transform: 
        scale(@r(1.1, .3, 3)) 
        skew(@r(-45deg, 45deg, 3));
</css-doodle>
 html,
body {
    width: 100%;
    height: 100%;
    background-color: #000;
}

The above code represents a 20x20 Grid grid, each Grid item is set with a completely random background color, scale() and skew() . Of course, here we use @r() instead of @rn() , each attribute of each grid is random without any correlation, then we will get such a pattern:

Well, what the hell is this, there is no beauty at all. We only need to change the ordinary completely random to Perlin noise random @rn() on the basis of the above code:

 <css-doodle grid="20">
    :doodle {
        grid-gap: 1px;
        width: 600px; height: 600px;
    }
    background: hsl(@rn(360), 80%, 80%);
    transform: 
        scale(@rn(1.1, .3, 3)) 
        skew(@rn(-45deg, 45deg, 3));
</css-doodle>

At this point, you can get a completely different effect:

This is because the random effect of each Grid item is related to each other based on their position in the Grid layout, which is the random effect of Perlin noise.

I can add hue-rotate animation:

 html,
body {
    width: 100%;
    height: 100%;
    background-color: #000;
    animation: change 10s linear infinite;
}
@keyframes change {
    10% {
        filter: hue-rotate(360deg);
    }
}

Take a look at the effect, and, in CSS-doodle, due to random effects, you can get a different pattern every time you refresh:

CSS Doodle - CSS Pattern2

Of course, this style can also be paired with a variety of other ideas, like this:

CSS Doodle - CSS Pattern 3

Or this:

CSS Doodle - CSS Pattern 4

emmm, or this:

CSS Doodle - CSS Pattern 5

Yes, we can randomly apply Perlin noise to various properties, and we can let our imagination run wild and try various combinations. The following is the application of Perlin noise to lattice positioning:

 <css-doodle grid="30x30">
    :doodle {
        @size: 90vmin;
        perspective: 10px;
    }
    position: absolute;
    top: 0;
    left: 0;
    width: 2px;
    height: 2px;
    border-radius: 50%;
    top: @rn(1%, 100%, 1.5);
    left: @rn(1%, 100%, 1.5);
    transform: scale(@rn(.1, 5, 2));
    background: hsl(@rn(1, 255, 3), @rn(10%, 90%), @rn(10%, 90%));
</css-doodle>

CodePen Demo - CSS Doodle - CSS Pattern6

Or use it in conjunction with transform: rotate() :

 <css-doodle grid="20x5">
    @place-cell: center;
    @size: calc(@i * 1.5%);
    :doodle {
        width: 60vmin; 
        height: 60vmin;
    }
    z-index: calc(999 - @i);
    border-radius: 50%;
    border: 1px @p(dashed, solid, double) hsl(@rn(255), 70%, @rn(60, 90%));
    border-bottom-color: transparent;
    border-left-color: transparent;
    transform: 
        rotate(@rn(-720deg, 720deg))
        scale(@rn(.8, 1.2, 3));
</css-doodle>

The effect is as follows:

Of course, every random time, there will be a different result:

CodePen Demo -- CSS doodle - CSS Pattern7

Well, my personal imagination is limited, you can find any DEMO by yourself, and after Fork, try to collide with different sparks.

at last

This concludes this article, I hope it helps you :)

More wonderful CSS technical articles are summarized in my Github -- iCSS , which will be updated continuously. Welcome to click star to subscribe to the collection.

If you have any questions or suggestions, you can communicate more. Original articles are limited in writing and knowledge. If there are any inaccuracies in the article, please let me know.


chokcoco
12.3k 声望18.5k 粉丝