CSS Houdini for dynamic wavy effect

Time:2019-12-5

CSS Houdini is known as the most exciting innovation in the field of CSS. CSS itself is short of syntax features for a long time, and its scalability is almost zero. Houdini directly exposed the CSS API to developers. In the past, the completely black box browser parsing flow began to open to the public. Developers can customize their own CSS properties.

background

We know that when rendering a page, the browser first parses the HTML and CSS of the page, generates a rendering tree, and then presents the entire page content through layout and painting. Before Houdini, there was very little space for us to operate in this process, especially the layout and painting links, which can be said to be completely closed, greatly limiting the flexibility of CSS. The emergence of CSS preprocessing technologies such as sass, less, stylus in the community mostly stems from this reason. They all hope to break through the limitations of CSS through precompiling, so that CSS has a stronger organization and writing ability. So slowly, we are no longer handwritten CSS, more convenient and flexible CSS extension language has become the main role of web development. Seeing this, CSS Houdini can’t sit down.

What is CSS Houdini?

CSS Houdini has opened a series of APIs for browser parsing process. These APIs allow developers to intervene in the operation of browser CSS engine, bringing more CSS solutions.

CSS Houdini mainly provides the following APIs:

CSS Properties and Values API

Allowing to define and use variables in CSS is the most compatible API at present;

Layout API

Allows developers to write their own layout module and customize layout attributes such as display;

Painting API

Allows developers to write their own paint module and customize painting attributes such as background image.

Basic: using the painting API in three steps

1. Load the custom code of the style through worklets in HTML:


<div class="rect"></div>
<script>
  if ("paintWorklet" in CSS) {
    CSS.paintWorklet.addModule("paintworklet.js");
  }
</script>

Worklets is also one of the APIs provided by Houdini, which is responsible for loading and executing custom JS code of styles. It is similar to the web worker, which is a independent working process running outside the main code, but it is lighter than the worker, and is most suitable for CSS rendering tasks.

2. Create a paintworklet.js, register a paint class rect with registerpaint method, and define the paint logic of the paint attribute:


registerPaint(
  "rect",
  class {
    static get inputProperties() {
      return ["--rect-color"];
    }
    paint(ctx, geom, properties) {
      const color = properties.get("--rect-color")[0];
      ctx.fillStyle = color;
      ctx.fillRect(0, 0, geom.width, geom.height);
    }
  }
);

A paint attribute class named rect is defined above. When rect is used, it will instantiate rect and automatically trigger the paint method to perform rendering. In the paint method, we get the — rect color variable defined by the node CSS and fill the background of the element with the specified color. The CTX parameter is a canvas context object, so the logic of paint is the same as that of canvas.

3. When using CSS, you only need to call the paint method:


.rect {
  width: 100vw;
  height: 100vh;
  background-image: paint(rect);
  --rect-color: rgb(255, 64, 129);
}

This is a simple implementation of custom CSS background color attributes. It can be seen that we can use CSS Houdini to flexibly and freely implement the style functions we want like canvas.

Advanced: dynamic ripple

According to the above steps, let’s demonstrate how to use CSS painting API to achieve a dynamic wave effect:


<!-- index.html -->
<div id="wave"></div>

<style>
  #wave {
    width: 20%;
    height: 70vh;
    margin: 10vh auto;
    background-color: #ff3e81;
    background-image: paint(wave);
  }
</style>

<script>
  if ("paintWorklet" in CSS) {
    CSS.paintWorklet.addModule("paintworklet.js");

    const wave = document.querySelector("#wave");
    let tick = 0;  
    requestAnimationFrame(function raf(now) {
      tick += 1;
      wave.style.cssText = `--animation-tick: ${tick};`;
      requestAnimationFrame(raf);
    });
  }
</script>

// paintworklet.js
registerPaint('wave', class {
  static get inputProperties() {
    return ['--animation-tick'];
  }
  paint(ctx, geom, properties) {
    let tick = Number(properties.get('--animation-tick'));
    const {
      width,
      height
    } = geom;
    const initY = height * 0.4;
    tick = tick * 2;

    ctx.beginPath();
    ctx.moveTo(0, initY + Math.sin(tick / 20) * 10);
    for (let i = 1; i <= width; i++) {
      ctx.lineTo(i, initY + Math.sin((i + tick) / 20) * 10);
    }
    ctx.lineTo(width, height);
    ctx.lineTo(0, height);
    ctx.lineTo(0, initY + Math.sin(tick / 20) * 10);
    ctx.closePath();

    ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
    ctx.fill();
  }
})

In the paintworklet, the sin function is used to draw wave lines. Because the animationworklets are still in the experimental stage and less open, here we use the requestanimationframe API to drive the animation outside the worklet to make the wave lines move. When finished, you can see the effect below.


 

However, in fact, the effect is a little stiff. The sin function is too regular. In reality, the waves should be irregular, which is mainly reflected in two aspects:

1) corrugation height (y) varies irregularly with position (x)


 

After decomposing the graph according to X-Y orthogonal, we hope that the irregularity can be considered as a fixed moment, with the change of X axis, the height of corrugation y presents irregular change;

2) fix a point (x is fixed), and the ripple height (y) changes irregularly with time

The dynamic process needs to consider the time dimension, and the irregularity we want also needs to be reflected in the influence of time. Compared with the second before and the second after the wind blows, the wave height at the same position must be irregular.

When it comes to irregularity, some friends may think of using math.random method. However, the irregularity here is not suitable to be realized by random numbers, because the random numbers taken twice before and after are discontinuous, and the waves at the two points before and after are continuous. It’s not hard to understand. Have you ever seen waves that grow into serrations? Or have you ever seen a wave that is 10 meters high in the last quarter and 2 meters high in the next?

In order to realize this kind of continuous and irregular feature, we discard sin function and introduce a package simplex noise. Because there are two dimensions that affect the wave height, position X and time t, the noise2d method is needed here. It constructs a continuous irregular surface in a three-dimensional space in advance:

// paintworklet.js
import SimplexNoise from 'simplex-noise';
const sim = new SimplexNoise(() => 1);

registerPaint('wave', class {
  static get inputProperties() {
    return ['--animation-tick'];
  }

  paint(ctx, geom, properties) {
    const tick = Number(properties.get('--animation-tick'));

    this.drawWave(ctx, geom, 'rgba(255, 255, 255, 0.4)', 0.004, tick, 15, 0.4);
    this.drawWave(ctx, geom, 'rgba(255, 255, 255, 0.5)', 0.006, tick, 12, 0.4);
  }
  
  /**
   *Draw ripples
   */
  drawWave(ctx, geom, fillColor, ratio, tick, amp, ih) {
    const {
      width,
      height
    } = geom;
    const initY = height * ih;
    const speedT = tick * ratio;

    ctx.beginPath();
    for (let x = 0, speedX = 0; x <= width; x++) {
      speedX += ratio * 1;
      var y = initY + sim.noise2D(speedX, speedT) * amp;
      ctx[x === 0 ? 'moveTo' : 'lineTo'](x, y);
    }
    ctx.lineTo(width, height);
    ctx.lineTo(0, height);
    ctx.lineTo(0, initY + sim.noise2D(0, speedT) * amp);
    ctx.closePath();

    ctx.fillStyle = fillColor;
    ctx.fill();
  }
})

 

Modify the parameters such as peak value and offset term, and then draw a different wave pattern. The effect is as follows. Finish!

summary

The above is the CSS Houdini introduced by Xiaobian to realize the dynamic wavy effect. I hope it can help you. If you have any questions, please leave a message to me, and Xiaobian will reply you in time. Thank you very much for your support of the developepaer website!
If you think this article is helpful to you, welcome to reprint, please indicate the source, thank you!