1
头图
This article was first published in Nuggets, and it is strictly forbidden to reprint without permission

One, a simple case to understand CSS Paint

For example, we want to simulate a Google material style ripple button. As follows:

1.gif

The complete CSS code and JS code are as follows:

    .ripple {
      width: 100px;
      height: 50px;
      display: flex;
      justify-content: center;
      align-items: center;
      color: #fff;
      border: none;
      font-size: 16px;
      border-radius: 6px;
      background-color: rgb(64 179 255);

      --ripple-x: 0;
      --ripple-y: 0;
      --animation-tick: 0;
    }
    /* 点击后增加的动画效果 */
    .ripple.animating {
      background-image: paint(ripple);
    }

The HTML code is as follows:

  <button class="ripple"> Click me! </button>
  
  <script>
    CSS.paintWorklet.addModule('ripple.js')
  </script>

Drawing graphics JS needs to be imported as a module. CSS.paintWorklet.addModule allows the web to import the script of ripple.js and open up additional threads for execution. It will not affect the main thread, which is an important "selling point" worklet

The ripple.js code is as follows:

registerPaint('ripple', class {
  paint(ctx, geom, properties) {
    // 像写canvas一样绘制动画
  }
});

The above is the general usage of the CSS Paint API, let's summarize it first:

  1. Use paint method in CSS
  2. JS add drawing code script
  3. Draw graphics like Canvas in ripple.js

So far, the following static buttons have to be poured:

image.png

2. How to achieve dynamic effects

Let's first think about how to realize the animation. I believe everyone has seen the animation. In fact, it is composed of multiple static images.
image.png
When there are more static images, the animation effect is smoother.
image.png

Then we can disassemble the water ripple dynamic effect:

  1. Draw a circle with a certain point as a circle (a circle with a background)
  2. The radius of the circle gradually increases until it disappears from the button boundary
    Simply draw it, the horizontal axis is the time line, and the circle gradually grows larger with the time.
    image.png
    So how to make an animation coherently? The above said that the more static pictures, the better, how many static pictures are the most suitable for such an animation on a computer? Some people will say, then use the timer SetInterval directly to draw a circle continuously while the diameter gradually increases. return executed when the radius is large enough?

This is a way of thinking, but SetInterval is a macro-task. Like SetTimeout , the execution time of cb will be affected by other tasks of the thread, and the animation effect is not ideal.

When it comes to timers, some people may think of requestAnimationFrame . Yes, it has two advantages over setTimeout and setInterval:

  1. requestAnimationFrame will gather all the DOM operations in each frame and complete it in one redraw or reflow, and the time interval of redraw or reflow closely follows the refresh frequency of the browser. Generally speaking, this frequency is per second 60 frames.
  2. In hidden or invisible elements, requestAnimationFrame will not be redrawn or reflowed, which of course means less cpu, gpu and memory usage.

On the code:

document.querySelector('button').addEventListener('click', evt => {
    requestAnimationFrame(function raf(now) {
      // 1. 不断刷新
      // 2. 不断画圆
      // 3. 满足某条件,return 出去!停止画圆
      requestAnimationFrame(raf);
    })
  })

When we click on the button, requestAnimationFrame will be executed 16.7ms/time, and the visual ripple animation effect can be realized by continuously changing the size of the circle. requestAnimationFrame we do three things:

  1. Keep refreshing
  2. Keep drawing circles
  3. Meet a certain condition, return out! Stop drawing circle

So how to keep refreshing, we solved it, and then think about how to keep drawing circles?

Here we can add animation to a button when clicked class , for the button to add a background-image ! If you've seen on an article , that see background-image should immediately think of Houdini in CSS Paint API . It can dynamically change the CSS variables, then directly on the code:

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

  // Canvas 画圆
  paint(ctx, geom, properties) {
    // 点击事件的坐标,作为画圆的圆形
    const x = parseFloat(properties.get('--ripple-x').toString());
    const y = parseFloat(properties.get('--ripple-y').toString());
    // 当前倒计时剩余时间
    let tick = parseFloat(properties.get('--animation-tick').toString());

    // 倒计时在1秒内,超出,Canvas 画圆动作结束
    if(tick < 0) tick = 0;
    if(tick > 1000) tick = 1000;

    ctx.fillStyle = '#ddd'; // 圆形背景颜色
    ctx.globalAlpha = 0.5; // 背景透明度

    // 画圆
    ctx.arc(
      x, y, // 圆心坐标
      geom.width * tick/1000, // 半径
      0, // 起始角
      2 * Math.PI, // 结束角
    );

    ctx.fill();
  }
});

Summarize:

  1. [Continuously refreshing]: requestAnimationFrame
  2. [Continuously draw circles]: requestAnimationFrame + CSS Paint API
  3. [Satisfy certain conditions, return out! Stop drawing circle]: After 1s, the circle drawing action will stop

The above three points are the main ideas for implementing a material style button based on CSS Houdini!

Welcome to download the source code for experience, click to jump to .

Well, this is the content of this article, thanks for reading, and welcome to share.


Allan91
2.3k 声望2.6k 粉丝