29
头图

Let’s look at a very common interaction today: tooltips . Usually the prompt box is solid color, such as the following

image

The implementation of this type of layout is not too complicated. It can be formed by splicing a rounded rectangle and a small triangle, and set the same color.

image

This is not the focus of this article. who are interested can visit 160beeb522f115 css-tips (codepen.io)

Sometimes, in order to emphasize the characteristics of the product or to follow the trend of the design, the design will use a gradient background, such as the Tips component in the lulu UI Edge

image-20210607185438690

"splicing" method is still used, there will inevitably be problems with the connection. There is an obvious "fragmented" , and the visual restoration will be greatly reduced.

image

So, how to achieve this kind of effect? Let's take a look

One, clip-path clipping

clip-path may be a way that many people can think of right away. But after actual operation, there will still be a lot of trouble

  1. clip-path: path can support any shape, but it cannot achieve adaptive width and height
  2. clip-path: polygon can achieve small sharp corners, but cannot achieve rounded corners
  3. clip-path: inset can achieve adaptive rounded rectangles, but cannot achieve the small sharp corners below

How to solve this problem? Actually just combine 2 and 3

Two containers of the same size are needed here, which can be replaced by ::before and ::after , and then set the same background color, which can be defined by custom attributes

.tips{
   position: relative;
   --bg: linear-gradient(45deg, #ff3c41, #ff9800);
}
.tips::before,.tips::after{
  content:'';
  position: absolute;
  width: 100%;
  height: 100%;
  left: 0;
  top: 0;
  background: var(--bg);/*完全相同的背景*/
  z-index: -1;
}

Why use two containers of the same size? This is to ensure that the gradient background is completely consistent when cropping.

image-20210607234448368

Then one of them is cut into a rounded rectangle, the other is cut into a small triangle, and then overlaps .

.tips::before{
  clip-path: inset(0 0 5px 0 round 5px);
  /*round 可以设置圆角*/
}
.tips::after{
  clip-path: polygon(calc(50% - 5px) calc(100% - 5px), calc(50% + 5px) calc(100% - 5px), 50% 100%);
  /* 实现小三角,只需要3个点的坐标就可以了 */
}

You can see that the prompt box is completely adaptive, and the real-time effect is as follows

image-20210606003706857

The complete code can be accessed tooltips-clip-path (codepen.io)

Two, mask mask

In addition to clip-path , mask is also an idea. If you are not familiar with mask , you can refer to this one wonderful MASK CSS (juejin.im) . The principle here is as follows

image-20210607234301475

Using mask , the question now becomes: draw such a graphic through CSS?

image-20210605181646410

1. Universal gradient

There are no graphics that CSS gradient cannot draw, and this one is no exception. First of all, we decompose this graphic, here can be divided into a rounded rectangle and a triangle , the triangle is easier, can be -gradient or linear-gradient

image-20210605190716575

The rounded rectangle is a bit troublesome, but it can be broken down, as follows

Kapture 2021-06-05 at 20.08.32

It can be synthesized by 4 radial gradients and 2 linear gradients, which is realized by code

tips{
  -webkit-mask-image: 
    /*4个径向渐变和2个线性渐变*/
    radial-gradient(circle at 5px 5px, green 5px,transparent 0),
    radial-gradient(circle at 5px 5px, green 5px,transparent 0),
    radial-gradient(circle at 5px 5px, green 5px,transparent 0),
    radial-gradient(circle at 5px 5px, green 5px,transparent 0),
    linear-gradient(red,red),
    linear-gradient(blue,blue);
  -webkit-mask-size:
    10px 10px,
      10px 10px,
    10px 10px,
    10px 10px,
    100% calc(100% - 15px),
    calc(100% - 10px) calc(100% - 5px)
  -webkit-mask-position:
    left top,
    right top,
    left 0 bottom 5px,
    right 0 bottom 5px,
    left 5px,
    5px top;
  -webkit-mask-repeat: no-repeat
}

As long as you have a little patience, you can write it smoothly

but...

It's too long, there are a lot of duplicates (4 radial-gradient), very verbose, is there any way to optimize it? Here is a trick. When you encounter something that is repeated regularly, you can think more about repeat . Use the tiled characteristics of the background and set the background size reasonably, as follows

Kapture 2021-06-05 at 21.19.57

As you can see, the background size is set to calc(100% ) 160beeb522f79b to achieve the tiling effect. The code implementation is

tips{
  -webkit-mask-image: 
    /*只需要一个径向渐变即可*/
    radial-gradient(circle at 5px 5px, green 5px,transparent 0),
    linear-gradient(red,red),
    linear-gradient(blue,blue);
  -webkit-mask-size:
    calc(100% - 10px) calc(100% - 15px),/*圆角的尺寸,高度由于还需要减去三角形尺寸,所以多了5px*/
    100% calc(100% - 15px),
    calc(100% - 10px) calc(100% - 5px);
  -webkit-mask-position:
    left top,
    left 5px,
    5px top;
  -webkit-mask-repeat: repeat,no-repeat,no-repeat;
}

Has it been streamlined a lot? Then just join the triangles together, you can get the following effects

image-20210606003846115

The complete code can be accessed tooltips-mask-gradient (codepen.io)

2. Adaptive svg

Despite some optimizations, the amount of code above is still considerable. Is there an easier way?

Thought of svg...

Under normal circumstances, the svg path is of a fixed size, and only can be scaled to . It cannot be adaptive. But the basic graphics support self-adaptation, you can set the percentage size, such as <rect>

<svg xmlns='http://www.w3.org/2000/svg' width='100%' height='100%'>
   <rect rx="5" width='100%' height='100%'/>
</svg>
rx can set the rounded corners of the rectangle. When ry is not set, the default is the same as rx

Such an svg can be adaptive and will not deform when the size is changed (pay attention to the rounded corners), as follows

image-20210605221224181

The triangle is easy, you can use <polygon> achieve

<svg xmlns='http://www.w3.org/2000/svg'>
  <polygon points='0 0,10 0,5 5' />
</svg>

can be used directly as the mask background. You can use mask-size and mask-position to set 160beeb5230b1b size and position

tips{
  -webkit-mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><polygon points='0 0,10 0,5 5' /></svg>"),url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><rect rx='6' width='100%' height='100%'/></svg>");
  -webkit-mask-size: 10px 5px, 100% calc(100% - 5px);
  -webkit-mask-repeat: no-repeat;
  -webkit-mask-position: center bottom, 0 0;
}
svg used as a background need to add data:image/svg+xml in front, and the content needs to be escaped, please refer to this article for details: learned, inline SVG images in CSS have a better form than Base64

It's still pretty good, the amount of code is not much, and it is easier to understand, the real-time effect is as follows

image-20210606004126668

The complete code can be accessed tooltips-mask-svg (codepen.io)

Three, paint drawing

Let's introduce a future solution, CSS paint . About CSS paint , also known as the "drawing board in the CSS world", in simple terms, it is to use canvas drawing to draw the background, canvas can draw almost anything, so this is a more general solution. If you want to quickly learn about CSS paint , you can refer to this introductory article: CSS Paint API Introduction to CSS Paint API , but currently only supports Chrome, the compatibility is as follows

截屏2021-06-07 23.23.28

But it does not affect our study, after all, it is the solution for the future, first look at the general grammar, as follows

  1. First, the JS registration module registerPaint
// paint-tips.js
registerPaint('tips-bg', class {
    paint(ctx, size, properties) {
       // 在这里绘制背景,语法和canvas类似
    }
});
  1. Next, JS add module CSS.paintWorklet.addModule
if (window.CSS) {
    CSS.paintWorklet.addModule('paint-tips.js');
}
  1. Finally, use paint(tips-bg)
tips{
  -webkit-mask-image: paint(tips-bg); /*这里作为遮罩背景使用*/
}

Now let’s draw the prompt box. If the mask is still used, then the question becomes: How to draw such a figure through canvas?

image-20210606205856074

In canvas, compared to CSS, this kind of graphics is simply pediatric. You only need to use lineTo and arc to draw them. The most important point is that the size here is rendered in real time and can be obtained by size

Learning about the canvas, where it is recommended Zhangxin Xu teacher of Canvas API Chinese document , API and case documents clearly much better than mdn

The drawing code is as follows (the following is a very common canvas code, in fact, it is just a few segments connected, and then filled with a solid color)

registerPaint('tips-bg', class {
    paint(ctx, size) { // ctx为绘制上下文,size为容器尺寸
      const { width,height } = size; // 容器尺寸
      const radius = 5; // 圆角大小
      const deg = Math.PI / 2;
      const edge = 5; // 三角形大小
      const pos = width / 2; // 三角形位置
      ctx.beginPath();
      ctx.moveTo(radius,0);
      ctx.lineTo(width-2*radius,0);
      ctx.arc(width-radius,radius,radius,-deg,0);
      ctx.lineTo(width,height-2*radius-edge);
      ctx.arc(width-radius,height-radius-edge,radius,0,deg);
      ctx.lineTo(pos+edge,height-edge);
      ctx.lineTo(pos,height);
      ctx.lineTo(pos-edge,height-edge);
      ctx.lineTo(radius,height-edge);
      ctx.arc(radius,height-radius-edge,radius,deg,2*deg);
      ctx.lineTo(0,radius-edge);
      ctx.arc(radius,radius,radius,-2*deg,-deg);
      ctx.closePath();
      ctx.fillStyle = '#000';
      ctx.fill();
   }
});

The real-time effect is as follows

image-20210606215036666

The complete code can be accessed tooltips-mask-paint (codepen.io)

In addition, you can also customize it through CSS variables, for example, define a --r as the rounded corner size, and --t as the triangle size

<tips style="--r:5;--t:5"></tips>
registerPaint('tips-bg', class {
  static get inputProperties() { // 定义允许的自定义属性
    return [
      '--r',
      '--t'
    ]
  }
  paint(ctx, size, properties) { // properties为自定义属性
    const radius = Number(properties.get('--r'));
    const edge = Number(properties.get('--t'));
    // ...
  }
})

You can see that the drawing is updated in real time (changing the rounded corners), without JS additional processing, the real-time effect is as follows

Kapture 2021-06-07 at 00.20.01

The complete code can be accessed tooltips-mask-paint-var (codepen.io)

Fourth, the stroke effect

Sometimes the prompt box may also have a stroke effect, such as this

image-20210607190931300

In fact, none of the above methods are suitable for this kind of stroked type. clip-path and mask can not achieve stroke, but there is a border generation scheme for reference: interesting! Irregular border generation scheme (juejin.cn) , unfortunately the effect is not perfect (slightly fuzzy)

If the size is fixed, you can directly use the svg method, refer to this article: Use SVG to implement an elegant prompt box (juejin.cn)

For now, there is really no better implementation (there is a better implementation, welcome to add 😂, I can't think of it temporarily), but if you use CSS paint , then everything is possible! Just draw the border and background in the paint

The drawing code is as follows

registerPaint('tips-bg', class {
    paint(ctx, size) {
      const { width,height } = size; // 容器尺寸
      const radius = 5; // 圆角大小
      const deg = Math.PI / 2;
      const edge = 5; // 三角形大小
      const pos = width / 2; // 三角形位置
      const lineWidth = 2; // 描边宽度
      ctx.beginPath();
      ctx.moveTo(radius+lineWidth,lineWidth);
      ctx.lineTo(width-2*radius-lineWidth,lineWidth);
      ctx.arc(width-radius-lineWidth,radius+lineWidth,radius,-deg,0);
      ctx.lineTo(width-lineWidth,height-2*radius-edge-lineWidth);
      ctx.arc(width-radius-lineWidth,height-radius-edge-lineWidth,radius,0,deg);
      ctx.lineTo(pos+edge,height-edge-lineWidth);
      ctx.lineTo(pos,height-lineWidth);
      ctx.lineTo(pos-edge,height-edge-lineWidth);
      ctx.lineTo(radius+lineWidth,height-edge-lineWidth);
      ctx.arc(radius+lineWidth,height-radius-edge-lineWidth,radius,deg,2*deg);
      ctx.lineTo(lineWidth,radius+lineWidth);
      ctx.arc(radius+lineWidth,radius+lineWidth,radius,-2*deg,-deg);
      ctx.closePath();
      const gradient = ctx.createLinearGradient(0, 0, width, 0); // 渐变背景
      gradient.addColorStop(0, '#F57853');
      gradient.addColorStop(1, '#F8B578');
      ctx.fillStyle = gradient; 
      ctx.fill();
      ctx.strokeStyle = '#FBF8F8'; // 绘制边框
      ctx.lineWidth = lineWidth;
      ctx.lineCap = 'round';
      ctx.stroke();
   }
});
tips{
  /* -webkit-mask-image: paint(tips-bg); */
  background: paint(tips-bg); /*不再借助mask,纯js绘制背景,包括渐变*/
}

The real-time effect is as follows

image-20210607200501837

The complete code can be accessed at tooltips-paint-stroke (codepen.io)

Five, summary and explanation

For the tooltips layout above, three different types of implementations are introduced, which are clip-path , mask , CSS paint . Among them, the implementation focus of mask is actually the drawing of CSS graphics. There are mainly gradient and svg . Although gradient a bit more complicated, it is the most common, and other methods may not be suitable for another layout. Now to summarize the main points:

  1. Multiple containers can be overlapped with clip-path to achieve complex adaptive effects
  2. When using CSS gradients to draw graphics, the same shape makes full use of the tiling feature
  3. The basic shape of svg supports percentage size, and it is also effective as a background. You can use multiple backgrounds to combine
  4. CSS paint is the best solution in the future, and it can also easily achieve the stroke effect
  5. The only flaw of CSS paint is that the compatibility is not good enough (now only supports Chrome 65+), but it is worth learning

Of course, these methods are not only to implement the layout of this article, but to provide an idea. Next time you encounter other "alien layout" you can immediately think of the corresponding solution instead of choosing "cut" " 160beeb5231a7e. If you think it's not bad, if it is helpful to you, please like, bookmark, and forward ❤❤❤


XboxYan
18.1k 声望14.1k 粉丝