Introduction
CSS functions mod()
, rem()
, and round()
, which were previously supported only in the Safari browser, are now supported in Chrome as well.
The mod() Function
The mod()
function in CSS calculates the remainder of dividing the first parameter by the second parameter, similar to the remainder operator (%) in JavaScript.
Example usage:
/* Examples of the CSS mod() function */
/* Numbers without units */
line-height: mod(7, 2); /* Results in 1 */
line-height: mod(14, 5); /* Results in 4 */
line-height: mod(3.5, 2); /* Results in 1.5 */
/* Percentages or dimensions with units */
margin: mod(15%, 2%); /* Results in 1% */
margin: mod(18px, 4px); /* Results in 2px */
margin: mod(19rem, 5rem); /* Results in 4rem */
margin: mod(29vmin, 6vmin); /* Results in 5vmin */
margin: mod(1000px, 29rem); /* Results in 72px (assuming root font size is 16px) */
/* Positive and negative values */
rotate: mod(100deg, 30deg); /* Results in 10deg */
rotate: mod(135deg, -90deg); /* Results in -45deg */
rotate: mod(-70deg, 20deg); /* Results in 10deg */
rotate: mod(-70deg, -15deg); /* Results in -10deg */
/* Calculations */
scale: mod(10 * 2, 1.7); /* Results in 1.3 */
rotate: mod(10turn, 18turn / 3); /* Results in 4turn */
transition-duration: mod(20s / 2, 3000ms * 2); /* Results in 4s */
The rem() Function
The rem()
function in CSS calculates the division remainder of the first parameter divided by the second parameter, similar to the remainder operator (%) in JavaScript.
Example usage:
/* Values without units */
line-height: rem(21, 2); /* Returns 1 */
line-height: rem(14, 5); /* Returns 4 */
line-height: rem(5.5, 2); /* Returns 1.5 */
/* Percentages or other dimensional units */
margin: rem(14%, 3%); /* Returns 2% */
margin: rem(18px, 5px); /* Returns 3px */
margin: rem(10rem, 6rem); /* Returns 4rem */
margin: rem(26vmin, 7vmin); /* Returns 5vmin */
margin: rem(1000px, 29rem); /* Returns 72px (assuming root font size is 16px) */
/* Positive and negative values */
rotate: rem(200deg, 30deg); /* Returns 20deg */
rotate: rem(140deg, -90deg); /* Returns 50deg */
rotate: rem(-90deg, 20deg); /* Returns -10deg */
rotate: rem(-55deg, -15deg); /* Returns -10deg */
/* Calculations */
scale: rem(10 * 2, 1.7); /* Returns 1.3 */
rotate: rem(10turn, 18turn / 3); /* Returns 4turn */
transition-duration: rem(20s / 2, 3000ms * 2); /* Returns 4s */
Difference Between rem and mod
The mod function generates a result that is either zero or has the same sign as the divisor.
The rem function generates a result that is either zero or has the same sign as the numerator.
These two mathematical functions are seldom used in everyday development.
However, there is one exception: the round()
function. This function has a more complex syntax, but it is much more practical.
Why the round() Function is Essential and Practical
Many people believe that the round()
function is just for rounding. In fact, it is designed with great flexibility, allowing you to implement various rounding methods through different parameter settings. For example, using the JS API:
- Math.ceil()
- Math.floor()
- Math.round()
- Math.trunc()
So how are these implemented? Let’s take a look at an example.
Emulating Math.ceil() Rounding Up in JavaScript
The Math.ceil()
function rounds a number up. In CSS, it can be represented as follows:
canvas {
border: round(up, 1.01px, 1px) solid;
}
The final rendered border width will be 2px.
Here, “up” represents rounding up, and the third parameter ensures the final value is divisible by this number. For example, the result of round(up, 2.01px, 2px)
is 4px.
Emulating Math.floor() Rounding Down to the Nearest Whole Number
Math.floor()
rounds down to the nearest whole number, so the border size in the following CSS code is set to 1px.
canvas {
border: round(down, 1.99px, 1px) solid;
}
Emulating Math.round() Rounding to the Nearest Whole Number
Syntax:
round( nearest, <valueToRound> , <roundingInterval> )
Alternatively:
round( <valueToRound> , <roundingInterval> )
This is what we commonly call rounding.
Emulating Math.trunc() (Truncating the Decimal Part)
Syntax:
round( to-zero, <valueToRound> , <roundingInterval> )
This means rounding valueToRound
to the closest integer multiple of roundingInterval
, moving towards zero.
for example:
canvas {
border: round(to-zero, 5.5px, 2px) solid;
}
The border width is 4px, because 5.5px rounds towards zero, and the nearest multiple of 2px is 4px.
The calculated value of round(to-zero, 5.5px, 3px)
is 3px, as the largest multiple of 3px smaller than 5.5px is 3px.
Practical Applications of the round() Function in CSS
Here are two practical examples.
Ensure the responsive font-size is always an integer
In mobile development, the rem unit is often adjusted based on screen size, for example:
html {
font-size: clamp(16px, calc(100% + 4 * (100vw - 375px) / 105), 20px);
}
At this point, the size corresponding to 1rem could become a decimal.
Decimal values can cause issues, such as when the size of some SVG icons is expressed in rem. Due to the decimal, tiny visual gaps can appear along the edges of the icons.
The same problem can occur with rounded corners or box-shadow edges, where small gaps may appear.
In such cases, you can use the round()
function to round the size to the nearest integer, thus avoiding these issues.
Example usage:
<ul>
<li>line 1</li>
<li>line 2</li>
<li>line 3</li>
</ul>
ul {
font-size: round(1rem, 1px);
}
Here, the font size of the <ul>
list will always be an integer.
Without using the round()
function, the font size could be a decimal.
Refer to the GIF below for a dynamic demonstration (without the round()
function, the font size is a decimal 18.2476px; with the round()
function, it becomes the integer 18px):
Simulating the effect of the steps() function in an animation.
The last parameter of the round() function represents the smallest unit value for rounding. Imagine this: if you have an animation ranging from 0 to 100, and you set the rounding unit to 20, the final computed values after applying the round() function would only be 0, 20, 40, 60, 80, or 100. This behaves similarly to the steps() function in CSS animations.
For example, consider a static loading icon:
<img src="loading.png" class="spin" />
The actual rendered effect is as follows, it’s a static one: (Provide a description or an example of what the static effect looks like for better context)
The following CSS code can create a loading rotation effect for this icon:
.spin {
transform: rotate(round(calc(var(--seed) * 3.6deg), 45deg));
animation: seed 1s linear infinite;
}
@property --seed {
syntax: "<integer>";
inherits: false;
initial-value: 0;
}
@keyframes seed {
from { --seed: 0; }
to { --seed: 100; }
}
Isn’t it cleverly implemented!
Although the steps()
function can achieve the same effect, it requires a higher learning and understanding cost compared to the round()
function. Its only real advantage is compatibility.
However, this example also highlights the potential applications of the round()
function.
Finally
Currently, most of the mathematical functions mentioned in the specification are already supported.
The first group of supported functions included min()
, max()
, and clamp()
.
Next, support for functions like sin()
and cos()
was introduced last year.
Other mathematical functions, such as sqrt()
for square roots, pow()
for exponents, exp()
for the exponential function, and log()
for logarithms, were supported at the end of last year, marking the third wave.
The value rounding functions discussed in this article belong to the fourth batch.
The final batch is expected to include functions like abs()
for absolute value and sign()
for determining positive, negative, or zero.