55
头图

In CSS, there are actually all kinds of functions. Specifically divided into:

In this article, I will specifically introduce four of the CSS Math functions that have been supported on a large scale by browsers:

  • calc()
  • min()
  • max()
  • clamp()

Why is it said to be supported on a large scale by browsers? Because in addition to these 4 mathematical functions that have been supported on a large scale, the standard CSS Values ​​and Units Module Level 4 has defined trigonometric functions such as sin() , cos() , tan() etc., exponential functions are related to pow() , sqrt() and other mathematical functions, but they are currently in the laboratory stage, and there is no browser to support them, it takes a little time .

Calc()

calc() This CSS function allows to perform some calculations when declaring CSS property values.

The syntax is similar to

 {
    width: calc(100% - 80px);
}

Some points to note:

  • + and - must be surrounded by whitespace characters. For example, calc(50% -8px) would be parsed as an invalid expression and must be written as calc(8px + -50%)
  • * and / these two operators do not need whitespace before and after, but if uniformity is considered, it is still recommended to add whitespace
  • Dividing by 0 will cause the HTML parser to throw an exception
  • Mathematical expressions involving the width and height percentages of table columns, table column groups, table rows, table row groups, and table cells in both Auto Layout and Fixed Layout tables, where auto is considered specified.
  • The calc() function supports nesting, but the supported way is to treat the nested calc() function as normal parentheses. (So, just use parentheses directly inside the function.)
  • calc() supports mixing with CSS variables

Looking at one of the most common examples, the page structure is as follows:

 <div class="g-container">
    <div class="g-content">Content</div>
    <div class="g-footer">Footer</div>
</div>

The g-footer height of the page is 80px. We hope that no matter how long the page is, the g-content part can fill the remaining space, like this:

This layout can be easily implemented using flex's elastic layout, of course, it can also be implemented using calc() :

 .g-container {
    height: 100vh;
}
.g-content {
    height: calc(100vh - 80px);
}
.g-footer {
    height: 80px;
}

Listed below are some advanced techniques for Calc().

Difference Between Addition and Subtraction and Multiplication and Division in Calc

Note the difference between addition and subtraction in calc() and multiplication and division:

 {
    font-size: calc(1rem + 10px);
    width: calc(100px + 10%);
}

It can be seen that the operands on both sides of addition and subtraction require units, while multiplication and division require a unitless number, which only represents a multiplier:

 {
    width: calc(100% / 7);
    animation-delay: calc(1s * 3);
}

Nesting of Calc

The calc() function can be nested, like this:

 {
  width: calc(100vw - calc(100% - 64px));
}

At this point, the internal calc() function can be degenerate into a parenthesis (), so the above code is equivalent to:

 {
  width: calc(100vw - (100% - 64px));
}

That is, calc() inside the nest, and several function characters of calc can be omitted .

Mixed operations of different units in Calc

calc() supports mixed operations of different units. For length, as long as it is a unit related to length, mixed operations can be performed, including these:

  • px
  • %
  • em
  • rem
  • in
  • mm
  • cm
  • pt
  • pc
  • ex
  • ch
  • vh
  • vw
  • vmin
  • vmax

There is an interesting point here. The operation must consume performance. In the early years, there was such a piece of CSS code that could directly crash the Chrome browser:

 <div></div>

The CSS styles are as follows:

 div {
  --initial-level-0: calc(1vh + 1% + 1px + 1em + 1vw + 1cm);

  --level-1: calc(var(--initial-level-0) + var(--initial-level-0));
  --level-2: calc(var(--level-1) + var(--level-1));
  --level-3: calc(var(--level-2) + var(--level-2));
  --level-4: calc(var(--level-3) + var(--level-3));
  --level-5: calc(var(--level-4) + var(--level-4));
  --level-6: calc(var(--level-5) + var(--level-5));
  --level-7: calc(var(--level-6) + var(--level-6));
  --level-8: calc(var(--level-7) + var(--level-7));
  --level-9: calc(var(--level-8) + var(--level-8));
  --level-10: calc(var(--level-9) + var(--level-9));
  --level-11: calc(var(--level-10) + var(--level-10));
  --level-12: calc(var(--level-11) + var(--level-11));
  --level-13: calc(var(--level-12) + var(--level-12));
  --level-14: calc(var(--level-13) + var(--level-13));
  --level-15: calc(var(--level-14) + var(--level-14));
  --level-16: calc(var(--level-15) + var(--level-15));
  --level-17: calc(var(--level-16) + var(--level-16));
  --level-18: calc(var(--level-17) + var(--level-17));
  --level-19: calc(var(--level-18) + var(--level-18));
  --level-20: calc(var(--level-19) + var(--level-19));
  --level-21: calc(var(--level-20) + var(--level-20));
  --level-22: calc(var(--level-21) + var(--level-21));
  --level-23: calc(var(--level-22) + var(--level-22));
  --level-24: calc(var(--level-23) + var(--level-23));
  --level-25: calc(var(--level-24) + var(--level-24));
  --level-26: calc(var(--level-25) + var(--level-25));
  --level-27: calc(var(--level-26) + var(--level-26));
  --level-28: calc(var(--level-27) + var(--level-27));
  --level-29: calc(var(--level-28) + var(--level-28));
  --level-30: calc(var(--level-29) + var(--level-29));

  --level-final: calc(var(--level-30) + 1px);

    border-width: var(--level-final);                                 
    border-style: solid;
}

It can be seen that from --level-1 to --level-30 , the amount of each operation is doubled, and finally to --level-final variable, the expansion will have 2^30 = 1073741824 --initial-level-0 content of the expression.

Moreover, the content of each --initial-level-0 expression calc(1vh + 1% + 1px + 1em + 1vw + 1cm) , when the browser parses it, is already complex enough.

Mixed together, it leads to the browser's BOOM (version before Chrome 70). In order to see the effect, we assign the above style to an element when it is hovered, and the following effect is obtained:

css-crash

Of course, this bug has been fixed now. We can also learn from this small demo. One is that calc can perform mixed operations of different units. The other is to pay attention to the specific use. If the amount of calculation is huge, it may lead to performance higher consumption.

Of course, don't mix length units with non-length units, like this:

 {
    animation-delay: calc(1s + 1px);
}

Calc works with CSS custom variables

A very important feature of the calc() function is that it can be used with CSS customizations and CSS @property variables.

The simplest demo:

 :root {
    --width: 10px;
}
div {
    width: calc(var(--width));
}

Of course, in this way, the function of such a spelling cannot be seen at all, and it seems to be meaningless. In practical application scenarios, it will be slightly more complicated than the above DEMO.

Suppose we want to implement such a loading animation, with only 3 balls at the beginning:

The possible way of writing is this, we add the same rotation animation to all 3 balls, and then control their animation-delay respectively:

 <div class="g-container">
    <div class="g-item"></div>
    <div class="g-item"></div>
    <div class="g-item"></div>
</div>
 .item:nth-child(1) {
    animation: rotate 3s infinite linear;
}
.item:nth-child(2) {
    animation: rotate 3s infinite -1s linear;
}
.item:nth-child(3) {
    animation: rotate 3s infinite -2s linear;
}

If one day, this animation needs to expand to 5 balls, like this:

As a last resort, we have to add HTML and modify CSS. With the help of Calc and CSS variables, this scenario can be simplified a bit.

Assuming there are only 3 balls:

 <div class="g-container">
    <div class="g-item" style="--delay: 0"></div>
    <div class="g-item" style="--delay: 1"></div>
    <div class="g-item" style="--delay: 2"></div>
</div>

We pass in the --delay variables through the HTML Style tag and use them directly in CSS:

 .g-item {
    animation: rotate 3s infinite linear;
    animation-delay: calc(var(--delay) * -1s);
}
@keyframes rotate {
    to {
        transform: rotate(360deg);
    }
}

And when the animation is modified to 5 balls, we don't need to modify the CSS, just modify the HTML directly, like this:

 <div class="g-container">
    <div class="g-item" style="--delay: 0"></div>
    <div class="g-item" style="--delay: 0.6"></div>
    <div class="g-item" style="--delay: 1.2"></div>
    <div class="g-item" style="--delay: 1.8"></div>
    <div class="g-item" style="--delay: 2.4"></div>
</div>

The core CSS is still this sentence, without any modification:

 {
    animation-delay: calc(var(--delay) * -1s);
}

For the complete demo, you can click here: CodePen Demo -- Calc & CSS Variable Demo

The default value of calc with custom variables

Or the above Loading animation effect, if one of my HTML tags forgets to fill the value of --delay , what will happen?

Like this:

 <div class="g-container">
    <div class="g-item" style="--delay: 0"></div>
    <div class="g-item" style="--delay: 0.6"></div>
    <div class="g-item"></div>
    <div class="g-item" style="--delay: 1.8"></div>
    <div class="g-item" style="--delay: 2.4"></div>
</div>
 {
    animation-delay: calc(var(--delay) * -1s);
}

Since the HTML tag did not pass in the value of --delay , and the corresponding value was not found in the CSS, at this time, animation-delay: calc(var(--delay) * -1s) This sentence is actually invalid, which is equivalent to animation-delay: 0 , the effect is the effect of one less ball:

So, based on this situation, you can use the fallback mechanism of CSS custom variables var() :

 {
    // (--delay, 1) 中的 1 是个容错机制
    animation-delay: calc(var(--delay, 1) * -1s);
}

At this point, if no --delay value is read, the default 1 and -1s will be used for operation.

Calc String Concatenation

When many people use CSS, they will try to concatenate strings, like this:

 <div style="--url: 'bsBD1I.png'"></div>
 :root {
    --urlA: 'url(https://s1.ax1x.com/2022/03/07/';
    --urlB: ')';
}
div {
    width: 400px;
    height: 400px;
    background-image: calc(var(--urlA) + var(--url) + var(--urlB));
}

Here I want to use calc(var(--urlA) + var(--url) + var(--urlB)) to spell out the complete URL ---5d10fa3b51e8e58d324c9e04e144511a background-image that can be used in url(https://s1.ax1x.com/2022/03/07/bsBD1I.png) .

However, this is not allowed (cannot be achieved). calc has no string concatenation capabilities .

The only possible string concatenation is in the content attribute of the element's pseudo-element. But it's not using calc either.

Let's look at this example, which is wrong :

 :root {
    --stringA: '123';
    --stringB: '456';
    --stringC: '789';
}

div::before {
    content: calc(var(--stringA) + var(--stringB) + var(--stringC));
}

At this point, you don't need calc, you can directly use custom variables to add them.

Therefore, the correct way to write:

 :root {
    --stringA: '123';
    --stringB: '456';
    --stringC: '789';
}
div::before {
    content: var(--stringA) + var(--stringB) + var(--stringC);
}

At this point, the content can be displayed normally:

Again, calc does not have the ability to concatenate strings . The following usages are all unrecognized incorrect syntax:

 .el::before {
  // 不支持字符串拼接
  content: calc("My " + "counter");
}
.el::before {
  // 更不支持字符串乘法
  content: calc("String Repeat 3 times" * 3);
}

min(), max(), clamp()

min(), max(), clamp() are suitable for speaking together. Their roles are related to each other.

  • max(): Selects the largest (positive direction) value from a comma-separated list of expressions as the property's value
  • min(): Selects the smallest value from a comma-separated list of expressions as the property's value
  • clamp(): Limit a value between an upper limit and a lower limit. When the value exceeds the range of the minimum and maximum values, select a value between the minimum and maximum values ​​to use

Because in reality, the attributes of many elements are not static, but will change according to the context and environment.

For example a layout like this:

 <div class="container"></div>
 .container {
    height: 100px;
    background: #000;
}

The effect is as follows, .container block It will grow as the screen grows, always occupying the entire screen:

For a responsive item, we definitely don't want its width to keep getting bigger, but when a certain threshold is reached, the width changes from relative units to absolute units, which applies to min() , simply transform the code:

 .container {
    width: min(100%, 500px);
    height: 100px;
    background: #000;
}

The width value of the container will choose between width: 100% and width: 500px , whichever is smaller.

When the screen width is less than 500px, it will appear as width: 100% , and vice versa, it will appear as width: 500px :

Similarly, in similar scenarios, we can also use max() to select a relatively larger value from multiple values.

min(), max() supports lists of multiple values

min(), max() support lists of multiple values, such as width: max(1px, 2px, 3px, 50px) .

Of course, for the above expression:

width: max(1px, 2px, 3px, 50px) is actually equal to width: 50px . Therefore, for the specific use of min() and max(), at most one specific absolute unit should be included. Otherwise, such a code like the above, although the syntax supports it, in any case, the calculated value is deterministic, which is actually meaningless.

with calc

min(), max(), clamp() can all be used with calc.

for example:

 div {
    width: max(50vw, calc(300px + 10%));
}

In this case, calc and the corresponding surrounding parentheses can be omitted, so the above code can be written again as:

 div {
    width: max(50vw, 300px + 10%);
}

Simulate clamp based on max and min

Now, there is such a scenario, if we need to limit the maximum value and the minimum value, what should we do?

In a scene like this, the minimum font size is 12px. As the screen gets bigger, it gradually gets bigger, but in order to avoid the phenomenon of old-fashioned machines (as the screen gets bigger, it gets bigger without limit), we also need to limit A maximum value of 20px.

We can use vw to assign dynamic values ​​to fonts. Assuming that on the mobile side, when the CSS pixel of the device width is 320px, the minimum font width of the page is 12px. Converted to vw, it is 320 / 100 = 3.2 , which is 1vw When the screen width is 320px, the performance is 3.2px, and 12px is approximately equal to 3.75 vw.

At the same time, we need to limit the maximum font value to 20px, the corresponding CSS is as follows:

 p {
    font-size: max(12px, min(3.75vw, 20px));
}

Take a look at the effect:

Through the combined use of max() , min() , and a relative unit vw, we successfully set the upper and lower limits for the font, and realized dynamic changes between the upper and lower limits.

Of course, the core paragraph above max(12px, min(3.75vw, 20px)) looks a bit confusing. Therefore, CSS introduced clamp() simplifies this syntax, and the following two writings are equivalent:

 p {
    font-size: max(12px, min(3.75vw, 20px));
    // 等价于
    font-size: clamp(12px, 3.75vw, 20px);
}

clamp()

clamp() The function of the function is to limit a value between an upper limit and a lower limit. When the value exceeds the range of the minimum and maximum values, select a value between the minimum and maximum values ​​to use. It accepts three parameters: minimum value, preferred value, maximum value.

Interestingly, clamp(MIN, VAL, MAX) actually means max(MIN, min(VAL, MAX)) .

Use vw with clamp to achieve responsive layout

We continue the above topic.

In the near past, in terms of mobile terminal adaptation, more rem adaptation schemes may be used, and some ready-made libraries may be used, such as flexible.js, hotcss.js and other libraries. A big problem with the rem solution is that it requires a piece of JavaScript to respond to viewport changes, reset the root element's font-size , and using rem is a bit of a hack.

Now, for mobile adaptation, we prefer the vw pure CSS scheme, which is similar to the rem scheme, and its essence is the proportional scaling of the page. One of its problems is that if you only use vw, as the screen keeps getting bigger or smaller, the content elements will keep getting bigger and smaller, which also causes many elements to look too big on the big screen!

Therefore, we need a way to control the maximum and minimum thresholds, like this:

At this point, clamp can come in very handy. In the above example, this piece of code font-size: clamp(12px, 3.75vw, 20px) can limit the font to the range of 12px - 20px .

Therefore, for mobile pages, all units involving length can be set using vw. And things such as fonts, inner and outer margins, widths, etc. should not be completely scaled, use clamp() control the maximum and minimum thresholds .

In the article Modern Fluid Typography Using CSS Clamp , there is a more in-depth discussion on using clamp() for fluid responsive layout. If you are interested, you can read it in depth.

To sum up, for mobile pages, we can use vw with clamp() to complete the adaptation of the entire mobile layout. Its advantages are:

  • No extra JavaScript code introduced, pure CSS solution
  • Able to control the boundary threshold well, and reasonably zoom in and out

Reverse responsive change

There is also a trick, using clamp() with negative values, we can also reverse the operation to get a reverse responsive effect with larger screen and smaller font:

 p {
    font-size: clamp(20px, -5vw + 96px, 60px);
}

Take a look at the effect:

This trick is quite interesting, because the calculated value of -5vw + 96px will increase as the screen becomes smaller, realizing a reverse font responsive change.

Summarize

To sum up, the rational use of min(), max(), and clamp() is the key to building a modern responsive layout. We can say goodbye to some traditional solutions that require JavaScript assistance, and can complete all the demands based on CSS mathematical functions.

Some very good articles for advanced reading:

At last

Well, this is the end of this article, I hope this article will help you :)

If you want to get the most interesting CSS information, don't miss my official account -- iCSS front-end anecdotes 😄

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 粉丝