Today we’ll look at a very common interaction: Tooltips. Usually the prompt box is a solid color, such as the one below

This type of layout is not too complicated to implement. It can be formed by combining a rounded rectangle with a small triangle and setting the same color

This is not the focus of this article. For those interested, visit CSS-Tips (codepen.io).

Sometimes a gradient background is used to highlight a feature or to follow a design trend, such as the Tips component in the Lulu UI Edge version

If the way of “splicing” is still used, it is inevitable that there will be problems of cohesion, and there will be an obvious sense of “fragmentation”, and the visual reduction will be greatly reduced

So how do you achieve this effect? Take a look

1. Clip-path clipping

Clip-path is probably the one that many people immediately think of. But in practice, there will be a lot of trouble

  1. Clip-path: Path can support any shape, but it does not have adaptive width and height
  2. Clip-path: Polygon can achieve small sharp corners, but not rounded corners
  3. Clip-path: Inset can implement adaptive rounded rectangles, but not the smaller sharp corners below

How to solve this problem? You can actually combine 2 and 3

You need two containers of the same size, which can be replaced with ::before and ::after, and then set the same background color, which can be defined by custom properties

.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);/* Exactly the same background */
  z-index: -1;
}
Copy the code

Why use two containers of the same size? This is to ensure that the background of the next gradient is exactly the same when cropping

Then cut one into a rounded rectangle and the other into a small triangle, and then overlap

.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%);
  /* to implement a small triangle, you only need three coordinates */
}
Copy the code

As you can see, the prompt box is completely adaptive, and the real-time effects are as follows

The full code is available at tooltips-clip-path (codepen.io)

2. Mask

In addition to clip-path, mask is also an idea. If you are not familiar with masks, you can refer to this wonderful CSS mask (juejin.cn). Here’s how it works

With masks, the question now becomes: How do YOU draw such a figure with CSS?

1. The omnipotent gradient

There is no shape that can’t be drawn with CSS gradients, and this one is no exception. First, we decompose this graph, which can be divided into a rounded rectangle and a triangle. The triangle is easier to draw by conic-gradient or linear-gradient

The rounded rectangle is a little bit more complicated, but it can be decomposed, as follows

Can be composed of 4 radial gradients and 2 linear gradients, with the code is achieved

tips{
  -webkit-mask-image: 
    /*4Radial gradient sum2Linear gradient */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
}
Copy the code

With a little patience, it can be written smoothly

But…

It’s too long, there are a lot of repeats (4 radial-gradient), it’s very verbose, is there any way to optimize it? Here is a tip, encounter repetition of regular things, you can think more repeat, use the tiling characteristics of the background, reasonable setting of background size can be, as follows

As you can see, set the background size to calc(100%-10px) to achieve the tiling effect, which is the code implementation

tips{
  -webkit-mask-image: /* Only need a radial gradient */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),/* The size of the fillet and the height of the fillet are increased by subtracting the triangle size5px* /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;
}
Copy the code

Is it much more streamlined? And then you just close the triangle, and you get this

For the full code, go to Tooltips-mask-gradient (codepen.io)

Adaptive SVG

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

Thought of SVG…

In general, SVG paths are fixed size and can only be scaled proportionally, not adaptive. However, the basic graphics are adaptive and can be set to percentage size, such as

<svg xmlns='http://www.w3.org/2000/svg' width='100%' height='100%'>
   <rect rx="5" width='100%' height='100%'/>
</svg>
Copy the code

Rx can set the rounded corners of the rectangle. If ry is not set, the rectangle is the same as rx by default

Such an SVG is adaptive and can change size without deformation (note the rounded corners), as shown below

Triangles are easy to do with

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

Then, use the two sections of SVG directly as the mask background, using mask-size and mask-position to set the size and position, respectively

tips{
  -webkit-mask-image: url("Data: image/SVG + XML, < SVG XMLNS =" http://www.w3.org/2000/svg "> < polygon points = 0, 0, 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;
}
Copy the code

SVG as a background needs to be preceded by data: Image/SVG + XML, and the content needs to be escaped. See this article for more details: There are better forms of inline SVG images in CSS than Base64

Still pretty good, the amount of code is not much, but also relatively easy to understand, real-time effect is as follows

The full code is available for tooltips-mask-svg (codepen.io)

Paint

Let’s take a look at a future solution, CSS Paint. CSS Paint, also known as “the drawing board of CSS”, is simply a way to draw a background using a Canvas. A 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, but currently only support Chrome, compatibility is as follows

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

  1. First, JS registers the module registerPaint
// paint-tips.js
registerPaint('tips-bg'.class {
    paint(ctx, size, properties) {
       // Draw the background here. The syntax is similar to canvas}});Copy the code
  1. Then, JS add module CSS. PaintWorklet. AddModule
if (window.CSS) {
    CSS.paintWorklet.addModule('paint-tips.js');
}
Copy the code
  1. Finally, use paint(tips-bg) in CSS
tips{
  -webkit-mask-image: paint(tips-bg); /* Use */ here as the mask background
}
Copy the code

The next step is to draw the prompt box. If you are still using the mask, the question becomes: How do I draw such a graph on the Canvas?

On canvas, these graphics are almost trivial compared to CSS. You only need to use lineTo and ARC to draw. Crucially, the dimensions here are rendered in real time and can be obtained by size

As for Canvas learning, I recommend Mr. Zhang Xinxu’s Canvas API Chinese document here. The API and case are much clearer than the MDN document

The drawing code is as follows (here is the normal Canvas code, which is just a few line segments joined together and filled with a solid color)

registerPaint('tips-bg'.class {
    paint(ctx, size) { // CTX is the drawing context and size is the container size
      const { width,height } = size; // The size of the container
      const radius = 5; // Fillet size
      const deg = Math.PI / 2;
      const edge = 5; // The size of the triangle
      const pos = width / 2; // Triangle position
      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(); }});Copy the code

The real-time effect is as follows

For the full code, go to Tooltips-mask-paint (codepen.io)

Alternatively, you can customize it using CSS variables such as –r for rounded corners and –t for triangles

<tips style="--r:5; --t:5"></tips>
Copy the code
registerPaint('tips-bg'.class {
  static get inputProperties() { // Define the allowed custom attributes
    return [
      '--r'.'--t']}paint(ctx, size, properties) { // Properties are custom properties
     const radius = Number(properties.get('--r'));
     const edge = Number(properties.get('--t'));
     // ...}})Copy the code

As you can see, the drawing is updated in real time (changing the rounded corners) without any additional JS processing. The real-time effect is as follows

For the full code, go to tooltips-mask-paint-var (codepen.io)

Four, stroke effect

Sometimes the prompt may also have a stroke effect, like this

< span style = “box-width: border-width: 0px; border-width: 0px; border-width: 0px; border-width: 0px; Irregular border generation scheme (juejin.cn), but the effect is not perfect (slightly fuzzy)

If the size is fixed, you can use SVG directly. See this article: Implementing an elegant prompt in SVG (juejin.cn).

As of right now, there is no good way to do it (there is a better way to do it, please add 😂, I can’t think of one), but with CSS paint, everything is possible! Just draw the border and background in the paint function

The drawing code is as follows

registerPaint('tips-bg'.class {
    paint(ctx, size) {
      const { width,height } = size; // The size of the container
      const radius = 5; // Fillet size
      const deg = Math.PI / 2;
      const edge = 5; // The size of the triangle
      const pos = width / 2; // Triangle position
      const lineWidth = 2; // Stroke width
      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 background
      gradient.addColorStop(0.'#F57853');
      gradient.addColorStop(1.'#F8B578');
      ctx.fillStyle = gradient; 
      ctx.fill();
      ctx.strokeStyle = '#FBF8F8'; // Draw the border
      ctx.lineWidth = lineWidth;
      ctx.lineCap = 'round'; ctx.stroke(); }});Copy the code
tips{
  /* -webkit-mask-image: paint(tips-bg); * /
  background: paint(tips-bg); /* No mask, pure JS background, including gradient */
}
Copy the code

The real-time effect is as follows

The full code is available at tooltips-paint-stroke (codepen.io)

5. Summary and explanation

There are three different implementation types for tooltips: Clip-path, Mask, and CSS Paint. Mask of the implementation of the focus is actually CSS graphics drawing, mainly gradient and SVG two, although the gradient writing a little more complex, but the most general, other ways may be changed to a layout is not applicable. Now to summarize the main points:

  1. Multiple containers can be overlapped with clip-path to achieve complex adaptive effects
  2. When drawing graphics with CSS gradients, the same shape takes advantage of the tiling feature
  3. SVG basic shapes support percentage sizes that are equally effective as backgrounds and can be combined using multiple backgrounds
  4. CSS Paint is the best solution for the future and makes it easy to achieve stroke effects as well
  5. The only downside to CSS Paint is that it’s not very compatible (Chrome 65+ is currently only supported), but it’s worth learning

Of course, these methods are not only to achieve the layout of this article, but more to provide a way of thinking, the next time you encounter other “special-shaped layout” can immediately think of the corresponding solution, rather than choose “cut diagram. PNG”. If it looks good and helps you, please like, favorit, and retweet ❤❤❤