From 13b010d0b518b04a15ae28215516a0832bc43857 Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Wed, 18 Sep 2024 13:06:25 +0100 Subject: [PATCH 1/3] Add interpolate-size and calc-size() --- files/en-us/web/css/interpolate-size/index.md | 195 ++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 files/en-us/web/css/interpolate-size/index.md diff --git a/files/en-us/web/css/interpolate-size/index.md b/files/en-us/web/css/interpolate-size/index.md new file mode 100644 index 000000000000000..b1b910021a1222f --- /dev/null +++ b/files/en-us/web/css/interpolate-size/index.md @@ -0,0 +1,195 @@ +--- +title: interpolate-size +slug: Web/CSS/interpolate-size +page-type: css-property +status: + - experimental +browser-compat: css.properties.interpolate-size +--- + +{{CSSRef}}{{seecompattable}} + +The **`interpolate-size`** [CSS](/en-US/docs/Web/CSS) property enables you to enable [animations](/en-US/docs/Web/CSS/CSS_animations) and [transitions](/en-US/docs/Web/CSS/CSS_transitions) between a [``](/en-US/docs/Web/CSS/length-percentage) value and an [intrinsic size value](/en-US/docs/Glossary/Intrinsic_Size) such as `auto`, [`fit-content`](/en-US/docs/Web/CSS/fit-content), or [`max-content`](/en-US/docs/Web/CSS/max-content). + +This property is typically used to animate the {{cssxref("width")}} and/or {{cssxref("height")}} of a container between a `` and the full size of its content (i.e. between "closed" and "open" or "hide" and "reveal" states). + +> [!NOTE] +> The behavior opted-into by `interpolate-size` cannot be enabled by default across the web because many sites in the wild use stylesheets that assume intrinsic size values cannot be animated. Enabling it by default would cause several backwards-compatibility issues (see relevant [CSS WG discussion](https://github.com/w3c/csswg-drafts/issues/626#issuecomment-2071016522)). + +## Syntax + +```css +/* Keyword values */ +interpolate-size: allow-keywords; +interpolate-size: numeric-only; + +/* Global values */ +interpolate-size: inherit; +interpolate-size: initial; +interpolate-size: revert; +interpolate-size: revert-layer; +interpolate-size: unset; +``` + +### Values + +- `allow-keywords` + - : Enables interpolation between a [``](/en-US/docs/Web/CSS/length-percentage) value and an intrinsic size value, to allow animation between the two. +- `numeric-only` + - : The default behavior — intrinsic size values cannot be interpolated. + +## Description + +`interpolate-size: allow-keywords` enables interpolation between a [``](/en-US/docs/Web/CSS/length-percentage) value and an intrinsic size value. Note that it does not enable animating between two intrinsic size values. One end of the animation must be a ``. + +`interpolate-size` is inherited, so animating to (or from) an intrinsic size value can be enabled for an entire document by setting it on the document root: + +```css +:root { + interpolate-size: allow-keywords; +} +``` + +If you want to limit the scope, you can set it on the relevant container element: + +```css +main { + /* Enable only for
and its descendents */ + interpolate-size: allow-keywords; +} +``` + +> [!NOTE] +> The {{cssxref("calc-size()")}} function can also be used to enable animations between a `` and an intrinsic size value, when used to specify a value to animate or transition to/from. However, `interpolate-size` is simpler to implement in most cases, especially when there are multiple animations to consider (it only needs to be declared once). `calc-size()` should only be used if you need to create animations that include performing calculations on an intrinsic size value. + +### Values that can be interpolated + +At the time of writing, the following intrinsic values can be opted-in to animations: + +- `auto` +- {{cssxref("min-content")}} +- {{cssxref("max-content")}} +- {{cssxref("fit-content")}} +- `content` (for containers sized using {{cssxref("flex-basis")}}). + +## Formal definition + +{{cssinfo}} + +## Formal syntax + +{{csssyntax}} + +## Examples + +### Basic `interpolate-size` usage + +This example demonstrates how to set `interpolate-size: allow-keywords` on a document to enable animations involving an intrinsic size. The demo features a character badge/"name tag", which can be hovered or focused to reveal information about the character. The reveal is handled by a {{cssxref("height")}} navigation between a set length and `max-content`. + +#### HTML + +The HTML contains a single {{htmlelement("section")}} element with [`tabindex="0"`](/en-US/docs/Web/HTML/Global_attributes/tabindex) set on it so it can receive keyboard focus. The `
` contains {{htmlelement("header")}} and {{htmlelement("main")}} elements, each with their own child content. + +```html +
+
+

Chris Mills

+
+
+

Chris is the silent phantom of MDN.

+
    +
  • Height: 3.03m
  • +
  • Weight: 160kg
  • +
  • Tech Fu: 7
  • +
  • Bad Jokes: 9
  • +
+
+
+``` + +#### CSS + +```css hidden +* { + box-sizing: border-box; +} + +section { + font-family: Arial, Helvetica, sans-serif; + width: 175px; + border-radius: 5px; + background: #eee; + box-shadow: + inset 1px 1px 4px rgb(255 255 255 / 0.5), + inset -1px -1px 4px rgb(0 0 0 / 0.5); +} + +header { + padding: 10px; + border-bottom: 2px solid #ccc; +} + +main { + padding: 10px; +} + +h2 { + margin: 0; + font-weight: 400; + font-size: 1.1rem; + text-align: center; + letter-spacing: 1px; +} + +p, +li { + font-size: 0.8rem; + line-height: 1.5; +} + +p { + margin-top: 0; +} +``` + +In the CSS, we first set `interpolate-size: allow-keywords` on the {{cssxref(":root")}}, to enable it for the whole document. + +```css +:root { + interpolate-size: allow-keywords; +} +``` + +We then set the `
`'s {{cssxref("height")}} to `42px` and {{cssxref("overflow")}} to `hidden` so only the `
` is shown by default. Finally, we set the `
` `height` on {{cssxref(":hover")}} and {{cssxref(":focus")}} to `max-content`. The rest of the CSS has been hidden for brevity. + +```css +section { + height: 42px; + overflow: hidden; + transition: height ease 1s; +} + +section:hover, +section:focus { + height: max-content; +} +``` + +#### Result + +Try hovering over the `
` or focusing it via the keyboard — it will animate to its full height, revealing all the content. + +{{ EmbedLiveSample('Basic `interpolate-size` usage', '100%', '225') }} + +## Specifications + +{{Specifications}} + +## Browser compatibility + +{{Compat}} + +## See also + +- {{cssxref("calc-size()")}} +- [Animate to height: auto; (and other intrinsic sizing keywords) in CSS](https://developer.chrome.com/docs/css-ui/animate-to-height-auto) on developer.chrome.com (2024) From 608a8c9d33b1fcecf908d7b215a108772ca9b99e Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Thu, 19 Sep 2024 15:50:55 +0100 Subject: [PATCH 2/3] Add calc-size() page --- files/en-us/web/css/calc-size/index.md | 537 ++++++++++++++++++ files/en-us/web/css/interpolate-size/index.md | 8 +- 2 files changed, 542 insertions(+), 3 deletions(-) create mode 100644 files/en-us/web/css/calc-size/index.md diff --git a/files/en-us/web/css/calc-size/index.md b/files/en-us/web/css/calc-size/index.md new file mode 100644 index 000000000000000..eec687b8e6226d0 --- /dev/null +++ b/files/en-us/web/css/calc-size/index.md @@ -0,0 +1,537 @@ +--- +title: calc-size() +slug: Web/CSS/calc-size +page-type: css-function +status: + - experimental +browser-compat: css.types.calc-size +--- + +{{CSSRef}}{{seecompattable}} + +The **`calc-size()`** [CSS](/en-US/docs/Web/CSS) [function](/en-US/docs/Web/CSS/CSS_Functions) allows you to perform calculations on [intrinsic size values](/en-US/docs/Glossary/Intrinsic_Size) such as `auto`, [`fit-content`](/en-US/docs/Web/CSS/fit-content), and [`max-content`](/en-US/docs/Web/CSS/max-content); this is not supported by the regular {{cssxref("calc()")}} function. + +`calc-size()` return values can also be interpolated, enabling their use in [animations](/en-US/docs/Web/CSS/CSS_animations) and [transitions](/en-US/docs/Web/CSS/CSS_transitions). In effect, they have [`interpolate-size: allow-keywords`](/en-US/docs/Web/CSS/interpolate-size) applied to them automatically. + +## Syntax + +```css +/* Pass a value through calc-size() */ +calc-size(auto, size) +calc-size(fit-content, size) + +/* Perform a calculation */ +calc-size(min-content, size + 100px) +calc-size(fit-content, size / 2) + +/* Calculation including a function */ +calc-size(auto, round(up, size, 50px)) +``` + +### Parameters + +The `calc-size()` function's syntax is as follows: + +```plain +calc-size(, ) +``` + +The parameters are: + +- `` + + - : The value (most commonly an intrinsic size) that you want to perform a calculation on. + +- `` + + - : An expression that defines the calculation to be performed on the ``. In the expression, the keyword `size` represents the `` specified as the first argument. The `+`, `-`, `*`, and `/` operators can be included. Operands can include `size`, and any value types that make sense in the context. For example, it makes sense to add a `px` or `rem` value to the `size`, but not a percentage. To calculate a percentage of a `size`, multiply or divide by a unitless value. Expressions can also include other mathematical functions such as {{cssxref("round()")}} and {{cssxref("max()")}}. + +### Return value + +Returns a value equal to the `` modified by the `` expression. If the `` value is an intrinsic size value, the return value will be a modified intrinsic size value that still behaves like the intrinsic size value input into the function. + +## Description + +`calc-size()` enables calculations to be performed on intrinsic size values in a safe, well-defined manner. This solution was chosen over extending `calc()` to support intrinsic sizes for a couple of reasons: + +1. Mixing different intrinsic sizes together in the same calculation doesn't work (`max-content - min-content` doesn't make sense, for example). `calc-size()` only allows a single intrinsic size value in each calculation, avoiding this problem. +2. Certain browser layout algorithms have a special behavior for intrinsic sizing keywords. `calc-size()` is explicitly defined to represent an intrinsic size rather than a [``](/en-US/docs/Web/CSS/length-percentage), thereby enforcing correctness. + +The first `calc-size()` argument can be one of the following intrinsic values: + +- `auto` +- {{cssxref("min-content")}} +- {{cssxref("max-content")}} +- {{cssxref("fit-content")}} +- `content` (for containers sized using {{cssxref("flex-basis")}}). + +There are also a few special values that this argument can take: + +- A nested `calc-size()` value. This isn't something you'd be likely to do very often, but this was made available so that authors can use a [CSS variable](/en-US/docs/Web/CSS/Using_CSS_custom_properties) as the `` and it will always work, provided the variable is a valid value for the property `calc-size()` is being set on. So for example, This will work: + + ```css + section { + height: calc-size(calc-size(max-content, size), size + 2rem); + } + ``` + + As will this: + + ```css + :root { + --intrinsic-size: calc-size(max-content, size); + } + + section { + height: calc-size(var(--intrinsic-size), size + 2rem); + } + ``` + +- Another ``, with the same restrictions as the `` specified for the second argument, except that the `size` keyword cannot be included. It is not particularly clear why you'd want to do this, as you are no longer doing a calculation on an intrinsic size value, but for example this will work: + + ```css + section { + height: calc-size(300px + 2rem, size / 2); + } + ``` + +- The keyword `any`, which represents an unspecified definite size. In this case, the `size` keyword cannot be included in the second argument, and the `calc-size()` returns the result of the second argument calculation. For example: + + ```css + section { + height: calc-size(any, 300px * 1.5); /* Returns 450px */ + } + ``` + +### Enabling animation of intrinsic size values + +Another useful effect of `calc-size()` is that its return values can be interpolated, enabling animations between a [``](/en-US/docs/Web/CSS/length-percentage) value and a `calc-size()` intrinsic size return value. + +For example, you could use a [transition](/en-US/docs/Web/CSS/CSS_transitions) to animate a container `width` between `0` and `auto` like so: + +```css +section { + width: 0; + transition: width ease 1s; +} + +section:hover, +section:focus { + width: calc-size(auto, size); +} +``` + +However, in the above case we are not calculating anything — we are putting `auto` into the `calc-size()` function and returning it unchanged. the {{cssxref("interpolate-size")}} property can also be used to enable animations like the above, and it is simpler to implement in most cases, especially when there are multiple animations to consider (it only needs to be declared once). `calc-size()` should only be used to enable intrinsic size animations if they also require calculations. + +For example, in the following case we are animating the `width` _and_ applying a calculation to the intrinsic size end state: + +```css +section { + width: 0; + transition: width ease 1s; +} + +section:hover, +section:focus { + width: calc-size(auto, size + 2rem); +} +``` + +One case in which `calc-size()` is useful is when you want to animate between an intrinsic size and a modified version of the same intrinsic size. This is not possible with `interpolate-size`. For example, the following {{cssxref("@keyframes")}} definition animates a container `width` between `fit-content` and 70% of the `fit-content`. + +```css +@keyframes narrower { + from { + width: fit-content; + } + + to { + width: calc-size(fit-content, size * 0.7); + } +} +``` + +> [!NOTE] +> Note that `calc-size()` does not enable animating between two different intrinsic size values. + +## Formal syntax + +{{csssyntax}} + +## Examples + +### Basic `calc-size` usage + +This example shows basic dimension sizing of a container using `calc-size()` + +#### HTML + +The HTML contains a single {{htmlelement("section")}} element that contains some child content. + +```html +
+

Favorite quote

+ +

+ Fashion is something so ugly it has to be changed every fifteen minutes. +

+
+``` + +#### CSS + +```css hidden +* { + box-sizing: border-box; +} + +section { + font-family: Arial, Helvetica, sans-serif; + padding: 10px; + border: 1px solid black; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +h2 { + margin: 0; + font-weight: 400; + font-size: 1.1rem; + text-align: center; + letter-spacing: 1px; +} + +p { + font-size: 0.8rem; + line-height: 1.5; +} +``` + +In the CSS, we set `width` and `height` of the `
` to `calc-size()` functions. The `width` is set equal to `fit-content` plus `4rem`. The `height` is set to `auto` plus 40%. + +```css +section { + width: calc-size(fit-content, size + 4rem); + height: calc-size(auto, size * 1.40; +} +``` + +The rest of the CSS has been hidden for brevity. + +#### Result + +{{ EmbedLiveSample('Basic `calc-size` usage', '100%', '150') }} + +### Basic `calc-size` animations + +This example demonstrates how to use `calc-size()` to animate between a specific size and an intrinsic size. The demo features a character badge/"name tag", which can be hovered or focused to reveal information about the character. The reveal is handled by a {{cssxref("height")}} transition between a set length and `max-content`. + +#### HTML + +The HTML contains a single {{htmlelement("section")}} element with [`tabindex="0"`](/en-US/docs/Web/HTML/Global_attributes/tabindex) set on it so it can receive keyboard focus. The `
` contains {{htmlelement("header")}} and {{htmlelement("main")}} elements, each with their own child content. + +```html +
+
+

Chris Mills

+
+
+

Chris is the silent phantom of MDN.

+
    +
  • Height: 3.03m
  • +
  • Weight: 160kg
  • +
  • Tech Fu: 7
  • +
  • Bad Jokes: 9
  • +
+
+
+``` + +#### CSS + +```css hidden +* { + box-sizing: border-box; +} + +section { + font-family: Arial, Helvetica, sans-serif; + width: 175px; + border-radius: 5px; + background: #eee; + box-shadow: + inset 1px 1px 4px rgb(255 255 255 / 0.5), + inset -1px -1px 4px rgb(0 0 0 / 0.5); +} + +header { + padding: 10px; + border-bottom: 2px solid #ccc; +} + +main { + padding: 10px; +} + +h2 { + margin: 0; + font-weight: 400; + font-size: 1.1rem; + text-align: center; + letter-spacing: 1px; +} + +p, +li { + font-size: 0.8rem; + line-height: 1.5; +} + +p { + margin-top: 0; +} +``` + +In the CSS, we set the `
`'s {{cssxref("height")}} to `42px` and {{cssxref("overflow")}} to `hidden` so only the `
` is shown by default, then specify a `transition` that animates the `
` `height` over 1 second during state changes. Finally, we set the `
` `height` to a `calc-size()` function call on {{cssxref(":hover")}} and {{cssxref(":focus")}}. The function return value is the equivalent of `max-content` + `2rem`. + +```css +section { + height: 42px; + overflow: hidden; + transition: height ease 1s; +} + +section:hover, +section:focus { + height: calc-size(max-content, size + 2rem); +} +``` + +The rest of the CSS has been hidden for brevity. + +#### Result + +Try hovering over the `
` or focusing it via the keyboard — it will animate to its full height + 2rem, revealing all the content. + +{{ EmbedLiveSample('Basic `calc-size` animations', '100%', '250') }} + +### Adjusting reading width based on `fit-content` + +This example shows a container with text inside it, and a button that can be clicked to make the container width narrower or wider depending on reading preference. + +#### HTML + +The HTML contains a single {{htmlelement("section")}} element containing child text content, plus a {{htmlelement("button")}} to change the `
` width. + +```html +
+

Easy reader

+ +

+ Eius velit aperiam ipsa. Deleniti eum excepturi ut magni maxime maxime + beatae. Dicta aperiam est laudantium ut illum facere qui officiis. Sunt + deleniti quam id. Quis sunt voluptatem praesentium minima dolorum autem + consequatur velit. +

+ +

+ Vitae ab incidunt velit aspernatur deleniti distinctio rerum. Et natus sed + et quos mollitia quia quod. Quae officia ex ea. Ducimus ut voluptatem et et + debitis. Quidem provident laboriosam exercitationem similique deleniti. + Temporibus vel veniam mollitia magni unde a nostrum. +

+ + +
+``` + +#### CSS + +```css hidden +* { + box-sizing: border-box; +} + +body { + width: 600px; + margin: 0 auto; +} + +section { + margin-top: 20px; + font-family: Arial, Helvetica, sans-serif; + background: #eee; + border: 2px solid #ccc; + padding: 0 20px; + position: relative; +} + +p, +li { + font-size: 0.8rem; + line-height: 1.5; +} + +button { + position: absolute; + top: 2px; + right: 2px; +} +``` + +In the CSS, we set the `
`'s {{cssxref("width")}} to a default of {{cssxref("fit-content")}}. We then define two sets of {{cssxref("@keyframes")}}, `narrower`, which animates from `fit-content` to 70% of `fit-content` (calculated using `calc-size()`), and `wider`, which animates the same values but in the opposite direction. Finally, we attach those animations to two classes — `.narrower` and `.wider`. Each animation is defined to last one second and to keep the final state applied once finished. + +```css +section { + width: fit-content; +} + +@keyframes narrower { + from { + width: fit-content; + } + + to { + width: calc-size(fit-content, size * 0.7); + } +} + +@keyframes wider { + from { + width: calc-size(fit-content, size * 0.7); + } + + to { + width: fit-content; + } +} + +.narrower { + animation: narrower 1s ease forwards; +} + +.wider { + animation: wider 1s ease forwards; +} +``` + +The rest of the CSS has been hidden for brevity. + +#### JavaScript + +The JavaScript provides a narrower/wider toggle that applies the relevant class to the `
` when the button is clicked: + +```js +const widthAdjustBtn = document.querySelector(".width-adjust"); +const easyReader = document.querySelector(".easy-reader"); + +widthAdjustBtn.addEventListener("click", () => { + if (easyReader.classList.length === 1) { + easyReader.classList.add("narrower"); + widthAdjustBtn.textContent = "Wider"; + } else if (easyReader.classList.contains("wider")) { + easyReader.classList.replace("wider", "narrower"); + widthAdjustBtn.textContent = "Wider"; + } else if (easyReader.classList.contains("narrower")) { + easyReader.classList.replace("narrower", "wider"); + widthAdjustBtn.textContent = "Narrower"; + } +}); +``` + +#### Result + +Try clicking the `