We all know responsive design, right? We use media to query. No, now we've used containers to query, aren't we? Sometimes we use flexbox or automatic streaming mesh cleverly. If we want to be bolder, we can also try fluid typography.
I'm a little unaccustomed to responsive designs being often split into discrete blocks, such as "layout A to this size, then layout B until there is enough space for layout C". This is OK, it works and is suitable for the workflow of designing the screen as a static layout in PhotoFigVa (disclaimer, I made up the software name). But this process seems to me to be a compromise. I have always believed that responsive design should be nearly invisible to users. When they were lined up to buy K-Pop tickets and visiting my website on mobile, they shouldn't notice how different it was from the experience of sitting on the huge curved gaming monitor they convinced their boss to need an hour ago.
Consider this simple hero banner and its mobile equivalent. Sorry, the design is rough. Images are generated by AI, but this is the only AI generated part of this article.
The location and size of the mongoose and text are different. The traditional way to achieve this is to choose two layouts using <del>媒体</del>
(sorry, container) query. Each layout may have some flexibility, such as centering content, and a small amount of fluid typography on the font size, but we will select a point where we will switch the layout into and out of the stacked version. So, at the width near the breakpoint, the layout may look a little empty or a little crowded.
Is there any other way?
Facts have proved that have . We can apply the concept of fluid typesetting to almost anything. This way, we can have a layout that changes smoothly with the size of its parent container. Few users will see the transition, but they will appreciate the results. To be honest, they will.
The first step, let's style the layout separately, a bit like when we use width query and breakpoints. In fact, let's use container queries and breakpoints together so that we can easily see which properties need to be changed.
This is the mark of our hero banner, it will not change:
<div> <div> <h1>LookOut</h1> <p>Eagle Defense System</p> </div> </div>
This is the wide version related CSS:
#hero { container-type: inline-size; max-width: 1200px; min-width: 360px; .details { position: absolute; z-index: 2; top: 220px; left: 565px; h1 { font-size: 5rem; } p { font-size: 2.5rem; } } &::before { content: ''; position: absolute; z-index: 1; top: 0; left: 0; right: 0; bottom: 0; background-image: url(../meerkat.jpg); background-origin: content-box; background-repeat: no-repeat; background-position-x: 0; background-position-y: 0; background-size: auto 589px; } }
I append the background image to a ::before
pseudo-element so that I can use container queries on it (because the container cannot query itself). We will keep this later so that we can query (cqi) units using inline containers. Now, here is the container query that only shows the value we are going to make it smooth:
@container (max-width: 800px) { #hero { .details { top: 50px; left: 20px; h1 { font-size: 3.5rem; } p { font-size: 2rem; } } &::before { background-position-x: -310px; background-position-y: -25px; background-size: auto 710px; } } }
You can see the code running in a live demo - it is completely static to show the limitations of a typical approach.
Now we can get the start and end points of the size and position of the text and background and make it smooth. Text size uses fluid typography in a way you are already familiar with. Here is the result - I will explain these expressions after you look at the code.
First is the change in text position and size:
<div> <div> <h1>LookOut</h1> <p>Eagle Defense System</p> </div> </div>
This is the background position and size of the mongolia image:
#hero { container-type: inline-size; max-width: 1200px; min-width: 360px; .details { position: absolute; z-index: 2; top: 220px; left: 565px; h1 { font-size: 5rem; } p { font-size: 2.5rem; } } &::before { content: ''; position: absolute; z-index: 1; top: 0; left: 0; right: 0; bottom: 0; background-image: url(../meerkat.jpg); background-origin: content-box; background-repeat: no-repeat; background-position-x: 0; background-position-y: 0; background-size: auto 589px; } }
Now we can completely delete the container query.
Let's explain these clamp()
expressions. We will start with the expression of the top
attribute.
@container (max-width: 800px) { #hero { .details { top: 50px; left: 20px; h1 { font-size: 3.5rem; } p { font-size: 2rem; } } &::before { background-position-x: -310px; background-position-y: -25px; background-size: auto 710px; } } }
You will notice there is a comment there. These expressions are a good idea of why magic numbers are bad. But we can't avoid them here because they are solutions to some simultaneous equations - CSS can't do that!
The upper and lower limits passed to are clear enough, but the expression in the middle comes from these simultaneous equations: clamp()
/* 行更改 * -12,27 +12,32 */ .details { /* ... 行 14-16 未更改 */ /* 对 360px 宽的容器计算结果为 50px,对 1200px 宽的容器计算结果为 220px */ top: clamp(50px, 20.238cqi - 22.857px, 220px); /* 对 360px 宽的容器计算结果为 20px,对 1200px 宽的容器计算结果为 565px */ left: clamp(20px, 64.881cqi - 213.571px, 565px); /* ... 行 20-25 未更改 */ h1 { /* 对 360px 宽的容器计算结果为 3.5rem,对 1200px 宽的容器计算结果为 5rem */ font-size: clamp(3.5rem, 2.857rem + 2.857cqi, 5rem); /* ... 字体粗细未更改 */ } p { /* 对 360px 宽的容器计算结果为 2rem,对 1200px 宽的容器计算结果为 2.5rem */ font-size: clamp(2rem, 1.786rem + 0.952cqi, 2.5rem); } }
/* 行更改 * -50,3 +55,8 */ /* 对 360px 宽的容器计算结果为 -310px,对 1200px 宽的容器计算结果为 0px */ background-position-x: clamp(-310px, 36.905cqi - 442.857px, 0px); /* 对 360px 宽的容器计算结果为 -25px,对 1200px 宽的容器计算结果为 0px */ background-position-y: clamp(-25px, 2.976cqi); /* 对 360px 宽的容器计算结果为 710px,对 1200px 宽的容器计算结果为 589px */ background-size: auto clamp(589px, 761.857px - 14.405cqi, 710px);
. 20.238cqi – 22.857px
element, we have; <h1></h1>
/* 对 360px 宽的容器计算结果为 50px,对 1200px 宽的容器计算结果为 220px */ top: clamp(50px, 20.238cqi - 22.857px, 220px);
This is solving these equations, because 1cqi is the same as 0.75rem when the container width is 1200px (my rem relative to the default UA stylesheet, 16px), while 1cqi is 0.225rem when 360px wide.
<code>f + 12v = 220 f + 3.6v = 50</code>
Honestly, it's boring to do this boring math every time, so I made a calculator you can use. Not only does it solve equations for you (exactly up to three decimal places to keep your CSS tidy), it also provides useful comments to use with expressions so you can see where they come from and avoid magic numbers. Feel free to use it. Yes, there are a lot of similar calculators out there, but they focus on typography, so (correctly) on rem units. If you use a CSS preprocessor, you may be able to port JavaScript.
The
clamp()
function is not strictly necessary at this time. In each case, the boundary of clamp()
is set to a value when the container width is 360px or 1200px. Since the container itself is limited to these restrictions - by setting the min-width
and max-width
values - the clamp()
expression should never call any bounds. However, if we change our mind (we are about to do so), I prefer to keep clamp()
because implicit boundaries like this are hard to find and maintain.
We can think our work is done, but we haven't. The layout is still not very useful. The text is directly over the head of the mongoose. While I'm guaranteed that this won't hurt the mongoose, I don't like the look of it. So let's make some changes to keep the text from hitting the mongoose.
The first one is very simple. We move the mongoose to the left faster so that it makes way. The easiest way is to change the lower end of the interpolation to a wider container. We will set it so that the mongoose moves completely to the left at 450px, rather than descending to 360px. There is no reason the start and end points of all our fluid expressions need to be aligned with the same width, so we can smoothly lower the other expressions to 360px.
With my reliable calculator, we just need to change the clamp()
expression of the background position property:
<div> <div> <h1>LookOut</h1> <p>Eagle Defense System</p> </div> </div>
This improves the situation, but not completely. I don't want to move it faster, so next we'll look at the path to the text. Currently it moves in a straight line, as shown below:
But can we bend it? Yes, we can.
One way we can do this is to define two different interpolations for the top coordinates, place the line at different angles, and then choose the smallest one. This way, it allows steeper straight lines to "win" at larger container widths, while shallower straight lines become the winning value when the container width is less than about 780px. The result is a straight line with a curve, which misses the mongoose.
We only change the top
value, but we have to first calculate two intermediate values:
#hero { container-type: inline-size; max-width: 1200px; min-width: 360px; .details { position: absolute; z-index: 2; top: 220px; left: 565px; h1 { font-size: 5rem; } p { font-size: 2.5rem; } } &::before { content: ''; position: absolute; z-index: 1; top: 0; left: 0; right: 0; bottom: 0; background-image: url(../meerkat.jpg); background-origin: content-box; background-repeat: no-repeat; background-position-x: 0; background-position-y: 0; background-size: auto 589px; } }
For these values, instead of formally computing them using carefully selected midpoints, I tried the endpoints until I got the result I wanted. Experiments are as effective as calculations, and can achieve the results you need. In this case, I start with repeated interpolation in a custom variable. I could have split the path into explicit parts using a container query, but this does not reduce math overhead, and using the min()
function is simpler in my eyes. Also, this article is not strictly about container queries, right?
The text now moves along this path. Open a live demo to see how it works.
The last point of explanation about calculation is that there are limitations in terms of what we can and cannot do. The first one, we have already alleviated slightly, that is, these interpolations are linear. This means that fading or other complex behavior is impossible.
Another major limitation is that CSS can only generate length values in this way, so that fluid opacity or rotation angles based on container or viewport size cannot be applied in pure CSS. The preprocessor can't help us here, either, because the limitation is how calc()
works in the browser.
If you are ready to rely on a small amount of JavaScript, you can undo both restrictions. It is enough to observe the width of the container and set a unitless CSS custom property. I will use this to make the text follow a quadratic Bezier curve as follows:
There are too many codes to be listed, and there are too many mathematical explanations of Bezier curves, but please check it in the real-time demonstration.
If an expression like calc(1vw / 1px)
does not fail in CSS, we don't even need JavaScript. They have no reason to fail because they represent the ratio between two lengths. Just like there are 2.54 cm in 1 inch, when the viewport width is 800px, there are 8px in 1vw, so calc(1vw / 1px)
should be calculated as 8 values without units.
But they do fail, so all we can do is state our point of view and move on.
Of course, there are always some layouts that require size query; some designs only need to be changed at fixed breakpoints. If appropriate, there is no reason to avoid doing so. There is also no reason to avoid mixing the two, for example, by smoothly adjusting the background size and position while using queries to switch between grid definitions of text placement. My mongoose example is deliberately designed to be simple for demonstration purposes.
I want to add that I am very excited about the possibility of fluid positioning using the new anchor positioning API. It is possible to use anchor positioning to define how two elements flow together on the screen, but this is left to be discussed later.
The above is the detailed content of Fluid Everything Else. For more information, please follow other related articles on the PHP Chinese website!