htmlcssmarginviewportviewport-units

How to make a margin smoothly transition between two percentages when the window size is adjusted


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.


Solution

  • Following up on my comment

    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)

    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:

    You want to limit the final result between 5% and 15%, which will require CSS clamp leading to the final result:

    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>