I want to have a percentage margin around an element of 5% of the viewport's width when the viewport is small, and 15% of the width of the viewport when the viewport is larger.
I could use a media query to swap out the margin amount like this:
.element {
margin: 5%;
}
@media (min-width: 800px) {
.element {
margin: 15%;
}
}
However, I would like it to smoothly transition between the percentages when the window changes size, instead of snapping from one value to the other.
I think thought something like this could work:
@media (min-width: 800px) {
.image-grid {
margin: 1em
calc(
(var(--max-margin) - var(--min-margin)) * ((100vw - 800px) / (1920 - 800) +
var(--min-margin))
);
}
}
This should theoretically change the margin value smoothly by setting it to a percentage of the amount of change between the margins.
e.g. when viewport is small: min-margin + [change of margin] * 0
, and when viewport is medium size: min-margin + [change of margin] * 0.5
, and when viewport is large: min-margin + [change of margin] * 1
But it doesn't because the calc function doesn't return things like 10px / 20px
as a straight decimal eg. 0.5. Instead it would return 0.5 px. And 15% * 0.5 px is illogical and causes the margin to go back to a default of 0.
Maybe I'm going about this the wrong way. Any help would be appreciated.
Following up on my comment
calc(..)
you will have to make sure that the terms in the calculation are legal. Be especially meticulous with division and multiplification!%
) will yield a pixel (px
) result, as do any of the viewport size units (vh,vw,vmin,vmax
) and their derivations.As such your first term (var(--max-margin) - var(--min-margin))
will yield pixel units because %
of a size translates to a pixel result.
As the second term ((100vw - 800px) / (1920 - 800) + var(--min-margin))
also yields a value in pixel units you are multiplying pixels with pixels.
One just cannot multiply cookies with cookies.
The point slope form of linear equation y=mx+b is a perfect choice to calculate any size relative to the current viewport size. It is like drawing a line on an XY-graph using a low and high coordinate being (x1,y1) and (x2,y2).
The math for the point slope form: y - y1 = m(x - x1)
y = y1 + (y2 − y1) / (x2 − x1) × (x − x1)
In your case
For the low point we can select any convenient viewport size and use 5%
to calculate the margin at that size. I used 400px
viewport width which yields 0.05 * 400 = 20
. Now we get low point (x1,y1) with value (400,20)
The high point is easily found as you want a margin of 15%
on viewports wider than 800px
. As 0.15 * 800 = 120
the high point (x2,y2) will be (800,120)
All we now have to do is enter the values into the above equation (with proper px
conversion) and simplify:
margin = 20 * 1px + (120 - 20) / (800 - 400) * (100vw - 400 * 1px)
margin = 20px + 100 / 400 * (100vw - 400px)
margin = 20px + 0.25 * (100vw - 400px)
You want to limit the final result between 5%
and 15%
, which will require CSS clamp
leading to the final result:
clamp(5%, 20px + 0.25 * (100vw - 400px), 15%)
The snippet shows the effect on both a clamped and unclamped version (go full page and resize the browser to see the difference).
BTW the slope intercept form (y = mx + b) of the final result is 25vw - 80px
.
/* point slope form */
.test1 { margin: 0 clamp(5%, 20px + 0.25 * (100vw - 400px), 15%) }
.test2 { margin: 0 calc(20px + 0.25 * (100vw - 400px)) }
/* slope intercept form */
.test3 { margin: 0 clamp(5%, 25vw - 80px, 15%) }
.test4 { margin: 0 calc(25vw - 80px) }
/* just eye-candy */
* { box-sizing: border-box; outline: 1px dashed }
body { margin: 0; text-align: center }
.test { background-color: Linen }
<h3>point slope form</h3>
<div class="test test1">clamped</div>
<div class="test test2">unclampled</div>
<h3>slope intercept form</h3>
<div class="test test3">clamped</div>
<div class="test test4">unclampled</div>