SMASHINGMAGAZINE.COM
CSS min() All The Things
Did you see this post that Chris Coyier published back in August? He experimented with CSS container query units, going all in and using them for every single numeric value in a demo he put together. And the result was not too bad, actually.See the Pen Container Units for All Units [forked] by Chris Coyier. What I found interesting about this is how it demonstrates the complexity of sizing things. Were constrained to absolute and relative units in CSS, so were either stuck at a specific size (e.g., px) or computing the size based on sizing declared on another element (e.g., %, em, rem, vw, vh, and so on). Both come with compromises, so its not like there is a correct way to go about things its about the elements context and leaning heavily in any one direction doesnt remedy that.I thought Id try my own experiment but with the CSS min() function instead of container query units. Why? Well, first off, we can supply the function with any type of length unit we want, which makes the approach a little more flexible than working with one type of unit. But the real reason I wanted to do this is personal interest more than anything else.The DemoI wont make you wait for the end to see how my min() experiment went:Taking website responsiveness to a whole new level pic.twitter.com/pKmHl5d0Dy Vayo (@vayospot) March 1, 2023 Well talk about that more after we walk through the details.A Little About min()The min() function takes two values and applies the smallest one, whichever one happens to be in the elements context. For example, we can say we want an element to be as wide as 50% of whatever container it is in. And if 50% is greater than, say 200px, cap the width there instead.See the Pen [forked] by Geoff Graham.So, min() is sort of like container query units in the sense that it is aware of how much available space it has in its container. But its different in that min() isnt querying its container dimensions to compute the final value. We supply it with two acceptable lengths, and it determines which is best given the context. That makes min() (and max() for that matter) a useful tool for responsive layouts that adapt to the viewports size. It uses conditional logic to determine the best match, which means it can help adapt layouts without needing to reach for CSS media queries..element { width: min(200px, 50%);}/* Close to this: */.element { width: 200px; @media (min-width: 600px) { width: 50%; }}The difference between min() and @media in that example is that were telling the browser to set the elements width to 50% at a specific breakpoint of 600px. With min(), it switches things up automatically as the amount of available space changes, whatever viewport size that happens to be.When I use the min(), I think of it as having the ability to make smart decisions based on context. We dont have to do the thinking or calculations to determine which value is used. However, using min() coupled with just any CSS unit isnt enough. For instance, relative units work better for responsiveness than absolute units. You might even think of min() as setting a maximum value in that it never goes below the first value but also caps itself at the second value.I mentioned earlier that we could use any type of unit in min(). Lets take the same approach that Chris did and lean heavily into a type of unit to see how min() behaves when it is used exclusively for a responsive layout. Specifically, well use viewport units as they are directly relative to the size of the viewport.Now, there are different flavors of viewport units. We can use the viewports width (vw) and height (vh). We also have the vmin and vmax units that are slightly more intelligent in that they evaluate an elements width and height and apply either the smaller (vmin) or larger (vmax) of the two. So, if we declare 100vmax on an element, and that element is 500px wide by 250px tall, the unit computes to 500px.That is how I am approaching this experiment. What happens if we eschew media queries in favor of only using min() to establish a responsive layout and lean into viewport units to make it happen? Well take it one piece at a time.Font SizingThere are various approaches for responsive type. Media queries are quickly becoming the old school way of doing it:p { font-size: 1.1rem; }@media (min-width: 1200px) { p { font-size: 1.2rem; }}@media (max-width: 350px) { p { font-size: 0.9rem; }}Sure, this works, but what happens when the user uses a 4K monitor? Or a foldable phone? There are other tried and true approaches; in fact, clamp() is the prevailing go-to. But were leaning all-in on min(). As it happens, just one line of code is all we need to wipe out all of those media queries, substantially reducing our code:p { font-size: min(6vmin, calc(1rem + 0.23vmax)); }Ill walk you through those values6vmin is essentially 6% of the browsers width or height, whichever is smallest. This allows the font size to shrink as much as needed for smaller contexts.For calc(1rem + 0.23vmax), 1rem is the base font size, and 0.23vmax is a tiny fraction of the viewports width or height, whichever happens to be the largest.The calc() function adds those two values together. Since 0.23vmax is evaluated differently depending on which viewport edge is the largest, its crucial when it comes to scaling the font size between the two arguments. Ive tweaked it into something that scales gradually one way or the other rather than blowing things up as the viewport size increases.Finally, the min() returns the smallest value suitable for the font size of the current screen size.And speaking of how flexible the min() approach is, it can restrict how far the text grows. For example, we can cap this at a maximum font-size equal to 2rem as a third function parameter:p { font-size: min(6vmin, calc(1rem + 0.23vmax), 2rem); }This isnt a silver bullet tactic. Id say its probably best used for body text, like paragraphs. We might want to adjust things a smidge for headings, e.g., <h1>:h1 { font-size: min(7.5vmin, calc(2rem + 1.2vmax)); }Weve bumped up the minimum size from 6vmin to 7.5vmin so that it stays larger than the body text at any viewport size. Also, in the calc(), the base size is now 2rem, which is smaller than the default UA styles for <h1>. Were using 1.2vmax as the multiplier this time, meaning it grows more than the body text, which is multiplied by a smaller value, .023vmax.This works for me. You can always tweak these values and see which works best for your use. Whatever the case, the font-size for this experiment is completely fluid and completely based on the min() function, adhering to my self-imposed constraint.Margin And PaddingSpacing is a big part of layout, responsive or not. We need margin and padding to properly situate elements alongside other elements and give them breathing room, both inside and outside their box.Were going all-in with min() for this, too. We could use absolute units, like pixels, but those arent exactly responsive.min() can combine relative and absolute units so they are more effective. Lets pair vmin with px this time:div { margin: min(10vmin, 30px); }10vmin is likely to be smaller than 30px when viewed on a small viewport. Thats why Im allowing the margin to shrink dynamically this time around. As the viewport size increases, whereby 10vmin exceeds 30px, min() caps the value at 30px, going no higher than that.Notice, too, that I didnt reach for calc() this time. Margins dont really need to grow indefinitely with screen size, as too much spacing between containers or elements generally looks awkward on larger screens. This concept also works extremely well for padding, but we dont have to go there. Instead, it might be better to stick with a single unit, preferably em, since it is relative to the elements font-size. We can essentially pass the work that min() is doing on the font-size to the margin and padding properties because of that..card-info { font-size: min(6vmin, calc(1rem + 0.12vmax)); padding: 1.2em;}Now, padding scales with the font-size, which is powered by min().WidthsSetting width for a responsive design doesnt have to be complicated, right? We could simply use a single percentage or viewport unit value to specify how much available horizontal space we want to take up, and the element will adjust accordingly. Though, container query units could be a happy path outside of this experiment.But were min() all the way!min() comes in handy when setting constraints on how much an element responds to changes. We can set an upper limit of 650px and, if the computed width tries to go larger, have the element settle at a full width of 100%:.container { width: min(100%, 650px); }Things get interesting with text width. When the width of a text box is too long, it becomes uncomfortable to read through the texts. There are competing theories about how many characters per line of text is best for an optimal reading experience. For the sake of argument, lets say that number should be between 50-75 characters. In other words, we ought to pack no more than 75 characters on a line, and we can do that with the ch unit, which is based on the 0 characters size for whatever font is in use.p { width: min(100%, 75ch);}This code basically says: get as wide as needed but never wider than 75 characters.Sizing Recipes Based On min()Over time, with a lot of tweaking and modifying of values, I have drafted a list of pre-defined values that I find work well for responsively styling different properties::root { --font-size-6x: min(7.5vmin, calc(2rem + 1.2vmax)); --font-size-5x: min(6.5vmin, calc(1.1rem + 1.2vmax)); --font-size-4x: min(4vmin, calc(0.8rem + 1.2vmax)); --font-size-3x: min(6vmin, calc(1rem + 0.12vmax)); --font-size-2x: min(4vmin, calc(0.85rem + 0.12vmax)); --font-size-1x: min(2vmin, calc(0.65rem + 0.12vmax)); --width-2x: min(100vw, 1300px); --width-1x: min(100%, 1200px); --gap-3x: min(5vmin, 1.5rem); --gap-2x: min(4.5vmin, 1rem); --size-10x: min(15vmin, 5.5rem); --size-9x: min(10vmin, 5rem); --size-8x: min(10vmin, 4rem); --size-7x: min(10vmin, 3rem); --size-6x: min(8.5vmin, 2.5rem); --size-5x: min(8vmin, 2rem); --size-4x: min(8vmin, 1.5rem); --size-3x: min(7vmin, 1rem); --size-2x: min(5vmin, 1rem); --size-1x: min(2.5vmin, 0.5rem);}This is how I approached my experiment because it helps me know what to reach for in a given situation:h1 { font-size: var(--font-size-6x); }.container { width: var(--width-2x); margin: var(--size-2x);}.card-grid { gap: var(--gap-3x); }There we go! We have a heading that scales flawlessly, a container thats responsive and never too wide, and a grid with dynamic spacing all without a single media query. The --size- properties declared in the variable list are the most versatile, as they can be used for properties that require scaling, e.g., margins, paddings, and so on.The Final Result, AgainI shared a video of the result, but heres a link to the demo.See the Pen min() website [forked] by Vayo. So, is min() the be-all, end-all for responsiveness? Absolutely not. Neither is a diet consisting entirely of container query units. I mean, its cool that we can scale an entire webpage like this, but the web is never a one-size-fits-all beanie.If anything, I think this and what Chris demoed are warnings against dogmatic approaches to web design as a whole, not solely unique to responsive design. CSS features, including length units and functions, are tools in a larger virtual toolshed. Rather than getting too cozy with one feature or technique, explore the shed because you might find a better tool for the job.
0 التعليقات
0 المشاركات
155 مشاهدة