Crow tips for CSS container query: closer to container query
We emphasize again: container query is required in CSS! It looks like we are heading in this direction.
When building a website component, you don't always know how the components will be used. It may be as wide as the browser window. Maybe two components are placed side by side. Maybe it's in a narrow column. The width of a component is not always related to the width of the browser window.
Typically, you will find it very convenient to query using container-based CSS components. If you search for solutions online, you may find several JavaScript-based solutions. But these solutions come at a cost: additional dependencies, JavaScript-required styles, and contaminated application logic and design logic.
I firmly believe in separation of concerns, and layout is the focus of CSS. For example, while the IntersectionObserver API is good, I would like to have something like :in-viewport
in CSS! So I continued to look for a CSS-only solution and found Heydon Pickering’s “Flexbox Holy Albatross”. This is a good solution for columns, but I want more. The original albatross has some improvements (like the "Unsacred Albatross"), but they are still a little clumsy, and everything that happens is just a row-to-column switch.
I still want more! I want to be closer to the actual container query! So, what does CSS provide that I can use? I have a math background, so functions like calc()
, min()
, max()
and clamp()
are things I like and understand.
Next: Use them to build a container query-like solution.
Table of contents:
- Why the "crow"?
- Mathematical functions in CSS
- Step 1: Create a configuration variable
- Step 2: Create indicator variable
- Step 3: Use indicator variables to select interval values
- Step 4: Use
min()
and a huge integer to select a value of any length - Step 5: Put Everything Together
- other?
- Where is the height?
- What about showing and hiding content?
- Key points
- Additional content
- The final thought
Want to see what might be achieved before continuing to read? This is a CodePen collection that shows what the ideas discussed in this article can achieve.
Why the "crow"?
This work was inspired by Heydon's albatross, but the technique could do more tricks, so I chose a crow because the crow is a very smart bird.
Review: Mathematical Functions in CSS
The calc()
function allows mathematical operations in CSS. Additionally, units can be combined, so operations like calc(100vw - 300px)
are possible.
min()
and max()
functions take two or more parameters and return the minimum or maximum parameters (respectively).
clamp()
function is very useful, it is similar to the combination of min()
and max()
. The function clamp(a, x, b)
will return:
- If x is less than a, return a
- If x is greater than b, return b
- If x is between a and b, then x is returned
So it's kind of like clamp(最小值, 相对值, 最大值)
. It can be regarded as the abbreviation of min(max(a,x),b)
. If you want to learn more, here is more information about this.
We will also use another CSS tool extensively in this article: CSS Custom Properties. These are like --color: red;
or --distance: 20px;
It is essentially a variable. We will use them to make CSS more concise, such as avoiding excessive duplication of ourselves.
Let's start using this crow trick.
Step 1: Create a configuration variable
Let's create some CSS custom properties to set.
What basic size do we want the query to be based on? Since we are looking for container query behavior, this will be 100% - using 100vw will make it behave like a media query, because that's the width of the browser window, not the container!
<code>--base_size: 100%;</code>
Now let's consider breakpoints. Literally means container width, we want to break here to apply new styles.
<code>--breakpoint_wide: 1500px; /* 大于1500px 将被视为宽*/ --breakpoint_medium: 800px; /* 从801px 到1500px 将被视为中等*/ /* 小于或等于800px 将被视为小*/</code>
In the running example, we will use three intervals, but this technique has no limitations.
Now let's define some (CSS length) values that we want to return for the intervals we define for the breakpoint. These are the literal values:
<code>--length_4_small: calc((100% / 1) - 10px); /* 根据你的需求更改*/ --length_4_medium: calc((100% / 2) - 10px); /* 根据你的需求更改*/ --length_4_wide: calc((100% / 3) - 10px); /* 根据你的需求更改*/</code>
This is the configuration. Let's use it!
Step 2: Create indicator variable
We will create some indicator variables for the interval. They are a bit like booleans, but with units of length (0px and 1px). If we limit these lengths as minimum and maximum values, they act as a kind of "true" and "false" indicator.
So, if and only if --base_size is greater than --breakpoint_wide, we want a variable with a value of 1px. Otherwise, we want 0px. This can be done using clamp()
:
<code>--is_wide: clamp(0px, var(--base_size) - var(--breakpoint_wide), 1px );</code>
If var(--base_size) - var(--breakpoint_wide)
is negative, then --base_size is less than --breakpoint_wide, so clamp()
will return 0px in this case.
Conversely, if --base_size is greater than --breakpoint_wide, the calculation will give a positive length that is greater than or equal to 1px. This means clamp()
will return 1px.
bingo! We get a "width" indicator variable.
Let's do this for the "medium" interval:
<code>--is_medium: clamp(0px, var(--base_size) - var(--breakpoint_medium), 1px ); /* 不要使用,见下文! */</code>
This will give 0px for the cell interval, but 1px for the medium and wide intervals. However, what we want is a wide interval of 0px and a medium interval of only 1px.
We can solve this problem by subtracting the --is_wide value. In a wide interval, 1px - 1px is 0px; in a medium interval, 1px - 0px is 1px; for a small interval, 0px - 0px gives 0px. Perfect.
So we get:
<code>--is_medium: calc( clamp(0px, var(--base_size) - var(--breakpoint_medium), 1px) - var(--is_wide) );</code>
Do you understand? To calculate indicator variables, use clamp()
with 0px and 1px as boundaries and the difference between --base_width and --breakpoint_whatever as limiting values. Then subtract the sum of all the larger interval indicators. For the minimum interval indicator, this logic produces the following results:
<code>--is_small: calc( clamp(0px, (var(--base_size) - 0px, 1px) - (var(--is_medium) var(--is_wide)) );</code>
We can skip clamp()
here because the breakpoint between cells is 0px and --base_size is positive, so --base_size - 0px is always greater than 1px, clamp()
will always return 1px. Therefore, the calculation of --is_small can be simplified to:
<code>--is_small: calc(1px - (var(--is_medium) var(--is_wide)));</code>
Step 3: Use indicator variables to select interval values
Now we need to move from these "indicator variables" to something useful. Let's assume we are using a pixel-based layout. Don't worry, we'll deal with other units later.
This is a problem. What does this return?
<code>calc(var(--is_small) * 100);</code>
If --is_small is 1px, it will return 100px; if --is_small is 0px, it will return 0px.
What's the use of this? Check out this:
<code>calc( (var(--is_small) * 100) (var(--is_medium) * 200) );</code>
This will return 100px 0px = 100px in the cell interval (where --is_small is 1px and --is_medium is 0px). In the medium interval (where --is_medium is 1px and --is_small is 0px), it will return 0px 200px = 200px.
Do you understand? See Roman Komarov's article for a deeper look at what's going on here, as it can be difficult to understand.
You multiply a pixel value (units) by the corresponding indicator variable and add all of these items together. So for pixel-based layouts, something like this is enough:
<code>width: calc( (var(--is_small) * 100) (var(--is_medium) * 200) (var(--is_wide) * 500) );</code>
But most of the time, we don't want pixel-based values. We want concepts such as "full width" or "third width" or even other units such as 2rem, 65ch, etc. For these we have to continue.
Step 4: Use min()
and a huge integer to select a value of any length
In the first step, we define something like this, instead of static pixel values:
<code>--length_4_medium: calc((100% / 2) - 10px);</code>
So how do we use them? min()
function is to rescue!
Let's define a helper variable:
<code>--very_big_int: 9999; /* 纯粹的无单位数字。必须大于其他地方出现的任何长度。 */</code>
Multiplying this value by the indicator variable will give 0px or 9999px. How big this value should be depends on your browser. Chrome will accept 999999, but Firefox won't accept such a large number, so 9999 is a value that works in both. There are almost no viewports larger than 9999px around, so we should be fine.
So what happens when we use it with any value smaller than 9999px but larger than 0px min()
?
<code>min( var(--length_4_small), var(--is_small) * var(--very_big_int) );</code>
It returns 0px if and only if --is_small is 0px. If --is_small is 1px, multiplication will return 9999px (greater than --length_4_small), and min
will return: --length_4_small.
This is how we can choose any length (i.e. less than 9999px but greater than 0px) based on the indicator variable.
If you are dealing with a viewport larger than 9999px, then you need to adjust the --very_big_int variable. This is a bit ugly, but we can fix this once pure CSS can remove the units in the value to get rid of the units in the indicator variable (and directly multiply it by any length). Currently, this works.
We will now combine all the pieces and let the crows fly!
Step 5: Put Everything Together
We can now calculate our breakpoint-based values based on dynamic container width like this:
<code>--dyn_length: calc( min(var(--is_wide) * var(--very_big_int), var(--length_4_wide)) min(var(--is_medium) * var(--very_big_int), var(--length_4_medium)) min(var(--is_small) * var(--very_big_int), var(--length_4_small)) );</code>
Each line is min()
in step 4. All the lines add up like in step 3, the indicator variables come from step 2, all based on the configuration we did in step 1 – they work together in one big formula!
Want to try it? This is a Pen that can be used (see comments in CSS).
This Pen does not use flexbox, grid, or float. Just some divs. This is to show that auxiliary programs are unnecessary in this layout. But feel free to use Crow with these layouts as it will help you create more complex layouts.
other?
So far we have used fixed pixel values as breakpoints, but do we want to change the layout if the container is larger or smaller than half of the viewport minus 10px? no problem:
<code>--breakpoint_wide: calc(50vw - 10px);</code>
This works! Other formulas also apply. To avoid strange behavior, we want to use something like:
<code>--breakpoint_medium: min(var(--breakpoint_wide), 500px);</code>
...Set the second breakpoint at a 500px width. The calculation in step 2 depends on the fact that --breakpoint_wide is not less than --breakpoint_medium. Just keep the breakpoints in the correct order: min()
and/or max()
are very useful here!
Where is the height?
All calculation evaluations are performed delayed. That is, when --dyn_length is assigned to any attribute, the calculation will be based on the calculation result of --base_size in this position. Therefore, if --base_size is 100%, the setting height will be based on 100%.
I haven't found a way to set the height based on the container width. So you can use padding-top
because 100% is equivalent to width for padding.
What about showing and hiding content?
The easiest way to show and hide content using the Crow trick is to set the width to 100px (or any other suitable width) at the appropriate indicator variable:
<code>.show_if_small { width: calc(var(--is_small) * 100); } .show_if_medium { width: calc(var(--is_medium) * 100); } .show_if_wide { width: calc(var(--is_wide) * 100); }</code>
You need to set:
<code>overflow: hidden; display: inline-block; /* 避免难看的空行*/</code>
...or some other way to hide the contents in a box with a width of 0px. To completely hide the box requires setting other box model properties (including margins, fills, and border widths) to 0px. Crow can do this on certain properties, but fixing it to 0px is equally effective.
Another way is to use position: absolute;
and draw the element off-screen via left: calc(var(--is_???) * 9999);
Key points
We probably don't need JavaScript at all, even for container query behavior! Of course, we hope that if we actually get container queries in CSS syntax, it will be easier to use and understand – but it is also very cool to be able to implement these things in CSS today.
While dealing with this, I have some thoughts about other things that CSS can use:
- Container-based units such as conW and conH are used to set height based on width. These units can be based on the root element of the current stacking context.
- Some kind of "evaluation as value" function to overcome the problem of delayed evaluation. This will work very well with the "remove unit" function that works when rendering.
Note: In earlier versions, I used cw and ch as units, but some people pointed out that these are easily confused with CSS units of the same name. Thanks to Mikko Tapionlinna and Gilson Nunes Filho for the tips in the comments! )
If we have a second one, it will allow us to set the color (in a clean way), borders, box shadows, flex-grow, background position, z-index, scale() and other things using the crow.
Combined with component-based units, it is even possible to set the child size to the same aspect ratio as the parent. Cannot be divided by the value with units; otherwise --indicator / 1px will work as the "removal unit" of the crow.
Additional content: Boolean logic
The indicator variable looks like a boolean, right? The only difference is that they have a "px" unit. So what about these logical combinations? Imagine something like "the container is larger than half the width of the screen" and "the layout is in two columns mode". The CSS function comes to rescue again!
For the OR operator, we can use max()
for all indicators:
<code>--a_OR_b: max( var(--indicator_a) , var(--indicator_b) );</code>
For the NOT operator, we can subtract the indicator from 1px:
<code>--NOT_a: calc(1px - var(--indicator_a));</code>
Logical purists might stop here because NOR(a,b) = NOT(OR(a,b)) is a complete Boolean algebra. But hey, just for fun, here are some more:
AND:
<code>--a_AND_b: min(var(--indicator_a), var(--indicator_b));</code>
This will evaluate to 1px if and only if both indicators are 1px.
Note that min()
and max()
accept more than two parameters. They still work as AND and OR of (more than two) indicator variables.
XOR:
<code>--a_XOR_b: max( var(--indicator_a) - var(--indicator_b), var(--indicator_b) - var(--indicator_a) );</code>
If (and only if) both indicators have the same value, both differences are 0px, max()
will return this value. If the indicator has a different value, one term will give -1px and the other will give 1px. In this case, max()
returns 1px.
If anyone is interested in situations where two indicators are equal, use the following method:
<code>--a_EQ_b: calc(1px - max( var(--indicator_a) - var(--indicator_b), var(--indicator_b) - var(--indicator_a) ) );</code>
Yes, this is NOT(a XOR b) . I can't find a "better" solution to this.
Equality may be more interesting for general CSS length variables than just for indicator variables. This might help by using clamp()
again:
<code>--a_EQUALS_b_general: calc( 1px - clamp(0px, max( var(--var_a) - var(--var_b), var(--var_b) - var(--var_a) ), 1px) );</code>
Remove px units to obtain general equality of unitless variables (integrals).
I think this is enough Boolean logic for most layouts!
Additional content 2: Set the number of columns in the grid layout
Since the Crow is limited to returning CSS length values only, it cannot directly select the number of columns for the grid (because this is a value without units). But there is a way to make it work (assuming we declare the indicator variable like above):
<code>--number_of_cols_4_wide: 4; --number_of_cols_4_medium: 2; --number_of_cols_4_small: 1; --grid_gap: 0px; --grid_columns_width_4_wide: calc( (100% - (var(--number_of_cols_4_wide) - 1) * var(--grid_gap) ) / var(--number_of_cols_4_wide)); --grid_columns_width_4_medium: calc( (100% - (var(--number_of_cols_4_medium) - 1) * var(--grid_gap) ) / var(--number_of_cols_4_medium)); --grid_columns_width_4_small: calc( (100% - (var(--number_of_cols_4_small) - 1) * var(--grid_gap) ) / var(--number_of_cols_4_small)); --raven_grid_columns_width: calc( /* 使用乌鸦组合值*/ min(var(--is_wide) * var(--very_big_int),var(--grid_columns_width_4_wide)) min(var(--is_medium) * var(--very_big_int),var(--grid_columns_width_4_medium)) min(var(--is_small) * var(--very_big_int),var(--grid_columns_width_4_small)) );</code>
And set up your grid using the following methods:
<code>.grid_container{ display: grid; grid-template-columns: repeat(auto-fit, var(--raven_grid_columns_width)); gap: var(--grid_gap) };</code>
How does this work?
- Define the number of columns we want for each interval (rows 1, 2, 3)
- Calculate the perfect column width for each interval (rows 5, 6, 7). What's going on here?
First, we calculate the available space for the column. This is 100%, minus the space the gap will take up. For column n, there are (n-1) gaps. Then divide this space by the number of columns we want. 3. Use the crow to calculate the correct column width for the actual --base_size.
In the grid container, this line:
<code>grid-template-columns: repeat(auto-fit, var(--raven_grid_columns_width));</code>
...and then select the number of columns to fit the value provided by the crow (this will result in our --number_of_cols 4 ??? variable above).
The crow may not be able to give the column number directly, but it can give the length, making repeat
and autofit
calculate the number we want for us.
But auto-fit
does the same thing as minmax()
, right? No! The above solution never gives three columns (or five columns), and the number of columns does not need to increase with the width of the container. Try setting the following values in this Pen to see the crows fly at full speed:
<code>--number_of_cols_4_wide: 1; --number_of_cols_4_medium: 2; --number_of_cols_4_small: 4;</code>
Additional Content 3: Use linear-gradient()
to change background color
This is a bit more brain-intensive. The crow is all about length values, so how do we get the color from these values? Well, linear gradients deal with both at the same time. They define colors in specific areas defined by length values. Let's discuss this concept in more detail before entering the code.
To solve the actual gradient part, a well-known technique is to double the color stop point, effectively causing the gradient part to occur within 0px. Check out this code to learn how to do this:
<code>background-image:linear-gradient( to right, red 0%, red 50%, blue 50%, blue 100% );</code>
This will make your background half red on the left and blue on the right. Note the first parameter "to right". This means that the percentage values are evaluated from the left to right level.
Controlling a value of 50% through the Crow variable allows you to move the color stop point at will. We can add more color stop points. In the running example, we need three colors, resulting in two (double) internal color stop points.
Add some variables for color and color stop points, and this is what we get:
<code>background-image: linear-gradient( to right, var(--color_small) 0px, var(--color_small) var(--first_lgbreak_value), var(--color_medium) var(--first_lgbreak_value), var(--color_medium) var(--second_lgbreak_value), var(--color_wide) var(--second_lgbreak_value), var(--color_wide) 100% );</code>
But how do we calculate the values of --first_lgbreak_value and --second_lgbreak_value? Let's take a look.
The first value controls the visible position of color_small. In the cell interval, it should be 100%, and in other intervals it should be 0px. We've learned how to do this with a crow. The second variable controls the visibility of color_medium. For a small interval, it should be 100%; for a medium interval, it should be 100%; but for a wide interval, it should be 0px. If the container width is in a small or medium interval, the corresponding indicator must be 1px.
Since we can do Boolean logic operations on the indicator, it is:
<code>max(--is_small, --is_medium)</code>
…to get the correct indicator. This gives:
<code>--first_lgbreak_value: min(var(--is_small) * var(--very_big_int), 100%); --second_lgbreak_value: min( max(var(--is_small), var(--is_medium)) * var(--very_big_int), 100%);</code>
Putting everything together produces the following CSS code that changes the background color based on the width (the interval indicator is calculated as shown above):
<code>--first_lgbreak_value: min( var(--is_small) * var(--very_big_int), 100%); --second_lgbreak_value: min( max(var(--is_small), var(--is_medium)) * var(--very_big_int), 100%); --color_wide: red;/* 根据你的需求更改*/ --color_medium: green;/* 根据你的需求更改*/ --color_small: lightblue;/* 根据你的需求更改*/ background-image: linear-gradient( to right, var(--color_small) 0px, var(--color_small) var(--first_lgbreak_value), var(--color_medium) var(--first_lgbreak_value), var(--color_medium) var(--second_lgbreak_value), var(--color_wide) var(--second_lgbreak_value), var(--color_wide) 100% );</code>
This is a Pen that can see how it works.
Additional Content 4: Get rid of nested variables
I'm having a strange problem when using Crow: There is a limited number of nested variables that can be used in calc()
. This can cause some problems when using too many breakpoints. As far as I understand, this limitation is to prevent blocking the page when calculating the style and allow for faster circular reference checking.
In my opinion, something like "evaluation as value" would be a good way to overcome this problem. However, this limit can cause you a headache when breaking through the limits of CSS. Hopefully this problem can be solved in the future.
There is a way to calculate the indicator variable for the crow without using (depth) nested variables. Let's see the original calculation of the --is_medium value:
<code>--is_medium:calc( clamp(0px, var(--base_size) - var(--breakpoint_medium), 1px) - var(--is_wide) );</code>
The problem occurs where minus --is_wide. This causes the CSS parser to paste the definition of the complete formula of --is_wide. The --is_small calculation even has more references of this kind. (The definition of --is_wide will even be pasted twice, because it is hidden in the definition of --is_medium and is used directly.)
Fortunately, there is a way to calculate indicators without referencing indicators with larger breakpoints.
The indicator is true if and only if --base_size is greater than the lower limit breakpoint of the interval and is less than or equal to the upper limit breakpoint of the interval. This definition provides us with the following code:
<code>--is_medium: min( clamp(0px, var(--base_size) - var(--breakpoint_medium), 1px), clamp(0px, 1px var(--breakpoint_wide) - var(--base_size), 1px) );</code>
-
min()
is used as a logical AND operator - The first
clamp()
is "--base_size greater than --breakpoint_medium" - The second
clamp()
means "--base_size is less than or equal to --breakpoint_wide." - Adding 1px will switch from "less than" to "less than or equal to ". This works because we are processing integer (pixel) numbers (a
The complete calculation of indicator variables can be done in this way:
<code>--is_wide: clamp(0px, var(--base_size) - var(--breakpoint_wide), 1px); --is_medium: min(clamp(0px, var(--base_size) - var(--breakpoint_medium), 1px), clamp(0px, 1px var(--breakpoint_wide) - var(--base_size), 1px) ); --is_small: clamp(0px,1px var(--breakpoint_medium) - var(--base_size), 1px);</code>
--is_wide and --is_small are simpler to calculate, because each requires only a given breakpoint to be checked.
This applies to everything we have seen so far. Here is a Pen that combines examples.
The final thought
Crow can't do everything that media queries can do. But we don't need it to do that, because we have media queries in CSS. For "large" design changes, such as the position of the sidebar or the reconfiguration of the menu, they are OK. These things happen in the context of the entire viewport (the size of the browser window).
But for components, media queries are a bit wrong because we never know the size of the component.
Heydon Pickering demonstrates this issue using this image:
I hope Raven will help you overcome the problem of creating responsive layouts for components and push the limit of "what can CSS do" even higher.
By showing what might be possible today, it may be possible to complete a "real" container query by adding some syntax sugar and some very small new functions such as conW, conH, "removal units" or "evaluating as pixels". If there is a function in CSS that allows '1px' to be rewrited as a space and '0px' to be "initial", Crow can be used in conjunction with custom attribute switching tricks and change each CSS attribute, not just the length value.
By avoiding JavaScript to achieve this, your layout renders faster because it does not depend on JavaScript download or run. Even if JavaScript is disabled, it doesn't matter. These calculations won't block your main thread, and your application logic won't get messed up with design logic.
Thanks to Chris, Andrés Galante, Cathy Dutton, Marko Ilic and David Atanda for their wonderful CSS-Tricks articles. They really helped me explore what Crows can achieve.
The above is the detailed content of The Raven Technique: One Step Closer to Container Queries. For more information, please follow other related articles on the PHP Chinese website!

I got this question the other day. My first thought is: weird question! Specificity is about selectors, and at-rules are not selectors, so... irrelevant?

Yes, you can, and it doesn't really matter in what order. A CSS preprocessor is not required. It works in regular CSS.

You should for sure be setting far-out cache headers on your assets like CSS and JavaScript (and images and fonts and whatever else). That tells the browser

Many developers write about how to maintain a CSS codebase, yet not a lot of them write about how they measure the quality of that codebase. Sure, we have

Have you ever had a form that needed to accept a short, arbitrary bit of text? Like a name or whatever. That's exactly what is for. There are lots of

I'm so excited to be heading to Zürich, Switzerland for Front Conference (Love that name and URL!). I've never been to Switzerland before, so I'm excited

One of my favorite developments in software development has been the advent of serverless. As a developer who has a tendency to get bogged down in the details

In this post, we’ll be using an ecommerce store demo I built and deployed to Netlify to show how we can make dynamic routes for incoming data. It’s a fairly


Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

AI Hentai Generator
Generate AI Hentai for free.

Hot Article

Hot Tools

DVWA
Damn Vulnerable Web App (DVWA) is a PHP/MySQL web application that is very vulnerable. Its main goals are to be an aid for security professionals to test their skills and tools in a legal environment, to help web developers better understand the process of securing web applications, and to help teachers/students teach/learn in a classroom environment Web application security. The goal of DVWA is to practice some of the most common web vulnerabilities through a simple and straightforward interface, with varying degrees of difficulty. Please note that this software

SublimeText3 Chinese version
Chinese version, very easy to use

SublimeText3 English version
Recommended: Win version, supports code prompts!

ZendStudio 13.5.1 Mac
Powerful PHP integrated development environment

PhpStorm Mac version
The latest (2018.2.1) professional PHP integrated development tool