@starting-style

Baseline 2024
新推出

自 ⁨2024 年 8 月⁩起,此功能可在最新的设备和浏览器版本上使用。此功能可能无法在旧设备或浏览器上使用。

@starting-style CSS @ 规则用于为元素上设置的属性定义起始值,当元素接收到首次样式更新时(即当元素在先前加载的页面上首次显示时),你希望从这些起始值开始过渡。

语法

@starting-style @ 规则有两种使用方式:

  1. 作为一个独立的块,此时它包含一个或多个规则集,定义了起始样式声明并选择它们所应用的元素。

    css
    @starting-style {
      /* rulesets */
    }
    
  2. 嵌套在现有规则集中,此时它包含一个或多个声明,为该规则集已选择的元素定义起始属性值。

    css
    selector {
      /* existing ruleset */
      /* ... */
    
      @starting-style {
        /* declarations */
      }
    }
    

描述

为避免意外行为,默认情况下,CSS 过渡在元素的初始样式更新时,或当其 display 类型从 none 更改为其他值时,不会被触发。要启用首次样式过渡,需要使用 @starting-style 规则。它们为没有先前状态的元素提供起始样式,定义了过渡的起始属性值。

@starting-style 在为显示在顶层的元素(例如 popover 和模态 <dialog>)创建进入和退出过渡时特别有用,也适用于在 display: none 和其他值之间切换的元素,以及首次添加到 DOM 或从 DOM 中移除的元素。

备注: @starting-style 仅与 CSS 过渡相关。当使用 CSS 动画来实现此类效果时,不需要 @starting-style。请参阅使用 CSS 动画中的示例。

有两种使用 @starting-style 的方式:作为独立规则或嵌套在规则集内。

让我们考虑一个场景:我们希望在 popover 显示时(即添加到顶层时)为其添加动画。指定打开的 popover 样式的“原始规则”可能如下所示(参见下面的popover 示例):

css
[popover]:popover-open {
  opacity: 1;
  transform: scaleX(1);
}

要使用第一种方法指定 popover 将要进行动画的属性的起始值,你可以在 CSS 中包含一个独立的 @starting-style 块:

css
@starting-style {
  [popover]:popover-open {
    opacity: 0;
    transform: scaleX(0);
  }
}

备注: @starting-style @ 规则和“原始规则”具有相同的层叠优先级。为确保应用起始样式,请将 @starting-style @ 规则包含在“原始规则”之后。如果你在“原始规则”之前指定 @starting-style @ 规则,原始样式将覆盖起始样式。

要使用嵌套方法为 popover 指定起始样式,你可以将 @starting-style 块嵌套在“原始规则”内:

css
[popover]:popover-open {
  opacity: 1;
  transform: scaleX(1);

  @starting-style {
    opacity: 0;
    transform: scaleX(0);
  }
}

起始样式究竟在什么时候使用?

重要的是要理解,当元素首次在 DOM 中渲染时,或者当它从 display: none 过渡到一个可见值时,元素将从其 @starting-style 样式开始过渡。当它从初始可见状态过渡回来时,它将不再使用 @starting-style 样式,因为它现在在 DOM 中是可见的。相反,它将过渡回该元素默认状态的任何现有样式。

实际上,在这些情况下需要管理三种样式状态——起始样式状态、过渡后状态和默认状态。在这种情况下,“进入”和“退出”的过渡可能是不同的。你可以在我们下面的何时使用起始样式的演示示例中看到这一点。

正式语法

@starting-style = 
@starting-style { <rule-list> }

示例

@starting-style 的基本用法

在元素初始渲染时,将其 background-color 从透明过渡到绿色。

css
#target {
  transition: background-color 1.5s;
  background-color: green;
}

@starting-style {
  #target {
    background-color: transparent;
  }
}

当元素的 display 值在 none 和其他值之间切换时,过渡其 opacity

css
#target {
  transition-property: opacity, display;
  transition-duration: 0.5s;
  display: block;
  opacity: 1;
  @starting-style {
    opacity: 0;
  }
}

#target.hidden {
  display: none;
  opacity: 0;
}

何时使用起始样式的演示

在此示例中,按下一个按钮会创建一个 <div> 元素,给它一个 showingclass,并将其添加到 DOM 中。

showing 被赋予了一个 background-color: red@starting-style 和一个 background-color: blue 的目标样式以进行过渡。默认的 div 规则集包含 background-color: yellow,并且 transition 也在此处设置。

<div> 首次添加到 DOM 时,你会看到背景从红色过渡到蓝色。在超时之后,我们通过 JavaScript 从 <div> 中移除 showing 类。此时,它从蓝色过渡回黄色,而不是红色。这证明了起始样式仅在元素首次在 DOM 中渲染时使用。一旦它出现,元素就会过渡回其上设置的默认样式。

在另一个超时之后,我们从 DOM 中完全移除 <div>,重置示例的初始状态,以便可以再次运行。

HTML

html
<button>Display <code>&lt;div&gt;</code></button>

CSS

css
div {
  background-color: yellow;
  transition: background-color 3s;
}

div.showing {
  background-color: skyblue;
}

@starting-style {
  div.showing {
    background-color: red;
  }
}

JavaScript

js
const btn = document.querySelector("button");

btn.addEventListener("click", () => {
  btn.disabled = true;
  const divElem = document.createElement("div");
  divElem.classList.add("showing");
  document.body.append(divElem);

  setTimeout(() => {
    divElem.classList.remove("showing");

    setTimeout(() => {
      divElem.remove();
      btn.disabled = false;
    }, 3000);
  }, 3000);
});

结果

代码渲染如下:

为 popover 添加动画

在此示例中,使用 CSS 过渡为一个 popover 添加了动画。使用 transition 属性提供了基本的进入和退出动画。

HTML

HTML 包含一个使用 popover 属性声明为 popover 的 <div> 元素,以及一个使用其 popovertarget 属性指定为 popover 显示控件的 <button> 元素。

html
<button popovertarget="mypopover">Show the popover</button>
<div popover="auto" id="mypopover">I'm a Popover! I should animate.</div>

CSS

在此示例中,我们希望为两个属性添加动画:opacitytransform(具体来说是水平缩放变换),以使 popover 淡入淡出以及水平放大和缩小。

css
html {
  font-family: "Helvetica", "Arial", sans-serif;
}

[popover]:popover-open {
  opacity: 1;
  transform: scaleX(1);
}

[popover] {
  font-size: 1.2rem;
  padding: 10px;

  /* Final state of the exit animation */
  opacity: 0;
  transform: scaleX(0);

  transition:
    opacity 0.7s,
    transform 0.7s,
    overlay 0.7s allow-discrete,
    display 0.7s allow-discrete;
  /* Equivalent to
  transition: all 0.7s allow-discrete; */
}

/* Include after the [popover]:popover-open rule */
@starting-style {
  [popover]:popover-open {
    opacity: 0;
    transform: scaleX(0);
  }
}

/* Transition for the popover's backdrop */
[popover]::backdrop {
  background-color: transparent;
  transition:
    display 0.7s allow-discrete,
    overlay 0.7s allow-discrete,
    background-color 0.7s;
  /* Equivalent to
  transition: all 0.7s allow-discrete; */
}

[popover]:popover-open::backdrop {
  background-color: rgb(0 0 0 / 25%);
}

/* Nesting (&) is not supported for pseudo-elements
so specify a standalone starting-style block. */
@starting-style {
  [popover]:popover-open::backdrop {
    background-color: transparent;
  }
}

为实现此目的,我们在 popover 元素的默认隐藏状态(通过 [popover] 选择)上为这些属性设置了起始状态,并在 popover 的打开状态(通过 :popover-open 伪类选择)上设置了结束状态。

然后,我们设置了一个 transition 属性以在这两种状态之间进行动画。动画的起始状态包含在 @starting-style @ 规则内,以启用进入动画。

因为被动画的元素在显示时被提升到顶层,而在隐藏时从顶层移除(使用 display: none),所以需要一些额外的步骤来确保动画在两个方向上都能正常工作:

  • display 被添加到过渡元素的列表中,以确保动画元素在进入和退出动画期间都可见(设置为 display: block 或其他可见的 display 值)。没有这个,退出动画将不可见;实际上,popover 会直接消失。请注意,transition-behavior: allow-discrete 值也在简写中设置,以激活动画。
  • overlay 被添加到过渡元素的列表中,以确保将元素从顶层移除的操作延迟到动画结束。这对于像这样的动画来说差别不大,但在更复杂的情况下,不这样做可能导致元素过早地从顶层移除,意味着动画不平滑或无效。同样,在这种情况下,需要 transition-behavior: allow-discrete 才能发生动画。

备注: 我们还为 popover 打开时出现在其后面的 ::backdrop 添加了过渡,以提供一个漂亮的变暗动画。[popover]:popover-open::backdrop 用于选择 popover 打开时的背景板。

结果

代码渲染如下:

备注: 因为 popover 每次显示时都会从 display: none 变为 display: block,所以每次进入过渡发生时,popover 都会从其 @starting-style 样式过渡到其 [popover]:popover-open 样式。当 popover 关闭时,它会从其 [popover]:popover-open 状态过渡到默认的 [popover] 状态。

备注: 你可以在 <dialog> 参考页面上找到一个演示如何为 <dialog> 元素及其背景板在显示和隐藏时添加过渡的示例——请参阅为对话框元素添加过渡

在 DOM 添加和移除时为元素添加过渡

此示例包含一个按钮,按下时会将新元素附加到一个 <section> 容器中。每个元素内部又包含一个嵌套的按钮,按下时会移除该元素。此示例演示了如何在元素添加到 DOM 或从 DOM 中移除时使用过渡为其添加动画。

HTML

html
<button>Create new column</button>
<section></section>

JavaScript

JavaScript 实现了元素的添加和移除:

js
const btn = document.querySelector("button");
const sectionElem = document.querySelector("section");

btn.addEventListener("click", createColumn);

function randomBackground() {
  function randomNum() {
    return Math.floor(Math.random() * 255);
  }
  const baseColor = `${randomNum()} ${randomNum()} ${randomNum()}`;

  return `linear-gradient(to right, rgb(${baseColor} / 0), rgb(${baseColor} / 0.5))`;
}

function createColumn() {
  const divElem = document.createElement("div");
  divElem.style.background = randomBackground();

  const closeBtn = document.createElement("button");
  closeBtn.textContent = "✖";
  closeBtn.setAttribute("aria-label", "close");
  divElem.append(closeBtn);
  sectionElem.append(divElem);

  closeBtn.addEventListener("click", () => {
    divElem.classList.add("fade-out");

    setTimeout(() => {
      divElem.remove();
    }, 1000);
  });
}

当点击“创建新列”按钮时,会调用 createColumn() 函数。该函数会创建一个带有随机生成背景色的 <div> 元素和一个用于关闭该 <div><button> 元素。然后它将 <button> 附加到 <div>,再将 <div> 附加到 <section> 容器。

然后,我们通过 addEventListener() 为关闭按钮添加一个事件监听器。点击关闭按钮会做两件事:

  • <div> 添加 fade-out 类。添加该类会触发设置在该类上的退出动画。
  • 在 1000 毫秒延迟后移除 <div>setTimeout() 会延迟从 DOM 中移除 <div>(通过 Element.remove()),直到动画结束。

CSS

我们包含了一个 transition,用于在每列添加和移除时为其 opacityscale 添加动画:

css
div {
  flex: 1;
  border: 1px solid gray;
  position: relative;
  opacity: 1;
  scale: 1 1;

  transition:
    opacity 0.7s,
    scale 0.7s,
    display 0.7s allow-discrete,
    all 0.7s allow-discrete;
  /* Equivalent to
  transition: all 0.7s allow-discrete; */
}

/* Include after the `div` rule */
@starting-style {
  div {
    opacity: 0;
    scale: 1 0;
  }
}

.fade-out {
  opacity: 0;
  display: none;
  scale: 1 0;
}

div > button {
  font-size: 1.6rem;
  background: none;
  border: 0;
  text-shadow: 2px 1px 1px white;
  border-radius: 15px;
  position: absolute;
  top: 1px;
  right: 1px;
  cursor: pointer;
}

为了在每个 <div> 添加到 DOM 时为其 opacityscale 添加动画,并在其从 DOM 中移除时反转动画,我们:

  • div { ... } 规则上指定我们想要过渡的属性的结束状态。
  • @starting-style 块内指定属性过渡的起始状态。
  • .fade-out 规则内指定退出动画——这是 JavaScript 在按下关闭按钮时分配给 <div> 元素的类。除了设置 opacityscale 的结束状态外,我们还为 <div> 设置了 display: none——我们希望它们在从 UI 中移除时立即变得不可用。
  • div { ... } 规则内指定 transition 列表,为 opacityscaledisplay 添加动画。请注意,对于 displaytransition-behavior: allow-discrete 值也在简写中设置,以便它能够进行动画。

结果

最终结果如下:

规范

规范
CSS Transitions Level 2
# defining-before-change-style

浏览器兼容性

另见