Skip to content
0

过渡

元素可以淡出、菜单可以滑入、颜色可以从一种变为另一种,实现这些效果最简单的方式是过渡(transitions)。

从这边到那边

简单示例

html
<button class="button-demo">Hover over me</button>
<style scoped>
  .button-demo {
    background-color: hsl(180, 50%, 50%);
    border: 0;
    color: white;
    font-size: 1rem;
    padding: 0.3em 1em;
    transition-property: all;
    transition-duration: 0.5s;
  }
  .button-demo:hover {
    background-color: hsl(0, 50%, 50%);
    border-radius: 1em;
  }
</style>

在 Markdown 正文内直接植入上述 HTML 片段,VitePress 可以正常渲染页面。

小结

  1. 过渡共有四个属性,均以 transistion- 开头

    • transition-property
    • transition-duration
    • transition-timing-function
    • transition-delay
  2. 开发时一般使用简写语法

    css
    transition: <transition-property> <transition-duration>
      <transition-timing-function> <transition-delay>;
  3. 过渡有两种方法触发

    • 状态变化 : hover
    • JavaScript 添加或删除影响元素样式的某个类
  4. transition 属性一般添加在元素选择器,不是添加在伪类选择器;需要过渡变化的属性则添加在伪类选择器

  5. 如果需要为不同的属性分别设置不同的过渡,可以添加多个过渡规则,以逗号分隔

定时函数

  • linear
  • ease
  • ease-in
  • ease-out
  • ease-in-out

简单示例

html
<div class="container">
  <div class="box"></div>
</div>

<style scoped>
  .container {
    position: relative;
    height: 30px;
  }
  .box {
    position: absolute;
    left: 0;
    height: 30px;
    width: 30px;
    background-color: hsl(130, 50%, 50%);
    transition: all 1s linear;
  }
  .container:hover .box {
    left: 400px;
  }
</style>

小结

出于性能考虑,应该避免对 left 属性使用过渡效果,更换使用 transform 变化。

贝塞尔曲线

待学习

非动画属性

方案一:点击按钮时在父容器上 添加/删除 is-open 类,使兄弟元素的显示状态在 display: none;display: block; 之间切换。

html
<!-- dropdownMenu2.html -->
<div class="dropdown" aria-haspopup="true">
  <button class="dropdown__toggle" type="button">Menu</button>
  <div class="dropdown__drawer">
    <ul class="menu" role="menu">
      <li role="menuitem">
        <a href="/features">Item 1</a>
      </li>
      <li role="menuitem">
        <a href="/pricing">Item 2</a>
      </li>
      <li role="menuitem">
        <a href="/blog">Item 3</a>
      </li>
      <li role="menuitem">
        <a href="/about">Item 4</a>
      </li>
    </ul>
  </div>
</div>
<p><a href="/read-more">Read more</a></p>

<script type="module">
  var toggle = document.getElementsByClassName("dropdown__toggle")[0];
  var dropdown = toggle.parentElement;
  toggle.addEventListener("click", function () {
    dropdown.classList.toggle("is-open");
  });
</script>
css
/* dropdownMenu2.css */
@layer global, modules;
@layer global {
  body {
    font-family: Helvetica, Arial, sans-serif;
  }
}
@layer modules {
  .dropdown {
    --border-color: oklch(61% 0.08 314deg);
    --text-color: oklch(39% 0.06 314deg);
    --text-color-focused: oklch(39% 0.2 314deg);
    --background-color: white;
    --highlight-color: oklch(95% 0.01 314deg);
  }
  .dropdown__toggle {
    display: block;
    padding: 0.5em 1em;
    border: 1px solid var(--border-color);
    color: var(--text-color);
    background-color: var(--background-color);
    font: inherit;
    text-decoration: none;
    transition: background-color 0.2s linear;
  }
  .dropdown__toggle:hover {
    background-color: var(--highlight-color);
  }
  .dropdown__drawer {
    position: absolute;
    display: none;
    background-color: var(--background-color);
    width: 10em;
  }
  .dropdown.is-open .dropdown__drawer {
    display: block;
  }
  .menu {
    padding-left: 0;
    margin: 0;
    list-style: none;
  }
  .menu > li + li > a {
    border-top: 0;
  }
  .menu > li > a {
    display: block;
    padding: 0.5em 1em;
    color: var(--text-color);
    background-color: var(--background-color);
    text-decoration: none;
    transition: all 0.2s linear;
    border: 1px solid var(--border-color);
  }
  .menu > li > a:hover {
    background-color: var(--highlight-color);
    color: var(--text-color-focused);
  }
}

显示效果

VitePress 内相关演示效果未能实现,请在 Codepen 内调试。

实现分析

Here’s a step-by-step explanation of how the dropdown menu works, showing how the HTML, CSS, and JavaScript interact:

  1. HTML Structure

    • The dropdown menu is built with a container <div class="dropdown">.
    • Inside, there’s a button (<button class="dropdown__toggle">Menu</button>) that the user clicks to toggle the menu.
    • The menu itself is in <div class="dropdown__drawer">, which contains a list of menu items.
  2. CSS Styling and Visibility Control

    • .dropdown__drawer is hidden by default with display: none;.
    • When the parent .dropdown has the class .is-open, the CSS rule .dropdown.is-open .dropdown__drawer { display: block; } makes the menu visible.
    • Other CSS rules style the button and menu for appearance and interactivity.
  3. JavaScript Interactivity

    • The script selects the toggle button and its parent dropdown container.
    • When the button is clicked, it toggles the class is-open on the .dropdown element.
    • This class toggle triggers the CSS to show or hide the menu.
  4. How It All Works Together

    1. Initial State:

      • The menu is hidden (display: none;).
    2. User Clicks "Menu":

      • JavaScript adds the is-open class to .dropdown.
    3. CSS Reacts:

      • The selector .dropdown.is-open .dropdown__drawer applies, making the menu visible (display: block;).
    4. User Clicks Again:

      • JavaScript removes the is-open class, hiding the menu.

Summary:

  • HTML provides the structure.
  • CSS controls visibility and style based on the presence of the is-open class.
  • JavaScript toggles the is-open class in response to user clicks, connecting the HTML and CSS for interactive behavior.

方案缺点:display 属性只能从 noneblock 切换,无法过渡。

方案二: 换用 opacity 属性,可以过渡。

使用新的 CSS 代码如下:

css
/* dropdownMenu4.css */
@layer global, modules;
@layer global {
  body {
    font-family: Helvetica, Arial, sans-serif;
  }
}
@layer modules {
  .dropdown {
    --border-color: oklch(61% 0.08 314deg);
    --text-color: oklch(39% 0.06 314deg);
    --text-color-focused: oklch(39% 0.2 314deg);
    --background-color: white;
    --highlight-color: oklch(95% 0.01 314deg);
  }
  .dropdown__toggle {
    display: block;
    padding: 0.5em 1em;
    border: 1px solid var(--border-color);
    color: var(--text-color);
    background-color: var(--background-color);
    font: inherit;
    text-decoration: none;
    transition: background-color 0.2s linear;
  }
  .dropdown__toggle:hover {
    background-color: var(--highlight-color);
  }
  .dropdown__drawer {
    position: absolute;
    /* display: none; */
    background-color: var(--background-color);
    width: 10em;
    visibility: hidden;
    opacity: 0;
    transition: visibility 0s linear 0.2s, opacity 0.2s linear;
  }
  .dropdown.is-open .dropdown__drawer {
    /* display: block; */
    opacity: 1;
    visibility: visible;
    transition-delay: 0s;
  }
  .menu {
    padding-left: 0;
    margin: 0;
    list-style: none;
  }
  .menu > li + li > a {
    border-top: 0;
  }
  .menu > li > a {
    display: block;
    padding: 0.5em 1em;
    color: var(--text-color);
    background-color: var(--background-color);
    text-decoration: none;
    transition: all 0.2s linear;
    border: 1px solid var(--border-color);
  }
  .menu > li > a:hover {
    background-color: var(--highlight-color);
    color: var(--text-color-focused);
  }
}

实现分析

Based on the code in dropdownMenu2.html and dropdownMenu4.css, here is a detailed step-by-step explanation of the menu toggling and transition effects.

Core Logic

The entire effect is controlled by adding and removing the class .is-open from the main <div class="dropdown"> container. A small piece of JavaScript listens for a click on the "Menu" button and toggles this class on its parent element. The CSS then uses this class to apply different styles to the menu drawer (<div class="dropdown__drawer">), triggering the transitions.

The key CSS properties involved are opacity (transparency) and visibility (whether the element is rendered and can be interacted with).

Action: Opening the Menu (Hidden to Visible)

This is the sequence of events when the user clicks the "Menu" button to show the dropdown.

Initial State (.dropdown__drawer):

  • opacity: 0 (completely transparent)
  • visibility: hidden (not visible, not interactive)

Step 1: User Click

  • The user clicks the <button class="dropdown__toggle">.
  • The JavaScript adds the is-open class to the parent <div class="dropdown">.

Step 2: CSS Rules Change

  • The element now matches the selector .dropdown.is-open .dropdown__drawer.
  • This new rule sets the target state for the drawer:
    • opacity: 1
    • visibility: visible
    • transition-delay: 0s (This is crucial, as it overrides any delay from the base style).

Step 3: Transitions Kick In

The browser now transitions the properties from their initial state to the new target state.

  1. Immediately (at 0 seconds):

    • The transition-delay: 0s from the .is-open rule overrides the 0.2s delay on the visibility property.
    • visibility changes from hidden to visible instantly. The menu drawer now occupies space in the layout and its content becomes interactive.
    • The opacity transition begins. It starts changing from 0 towards 1.
  2. From 0 to 0.2 seconds:

    • The opacity continues to animate from 0 to 1 over a duration of 0.2 seconds. The menu smoothly fades into view.
  3. At 0.2 seconds:

    • The opacity transition is complete. The menu is now fully visible (opacity: 1).

Summary of Opening Effect: The menu instantly appears in the document flow and starts fading in at the same time. The fade-in animation is smooth and lasts for 0.2 seconds.


Action: Closing the Menu (Visible to Hidden)

This is the sequence of events when the user clicks the "Menu" button again to hide the dropdown.

Initial State (.dropdown.is-open .dropdown__drawer):

  • opacity: 1 (fully visible)
  • visibility: visible (rendered and interactive)

Step 1: User Click

  • The user clicks the <button class="dropdown__toggle">.
  • The JavaScript removes the is-open class from the <div class="dropdown">.

Step 2: CSS Rules Revert

  • The element no longer matches the .dropdown.is-open .dropdown__drawer selector.
  • It reverts to its base style defined in .dropdown__drawer.
  • The target state for the drawer is now:
    • opacity: 0
    • visibility: hidden
  • The transition properties from the base rule are now used: transition: visibility 0s linear 0.2s, opacity 0.2s linear;

Step 3: Transitions Kick In

The browser transitions the properties from the visible state back to the hidden state.

  1. Immediately (at 0 seconds):

    • The opacity transition begins. It has a duration of 0.2s and a delay of 0s. The menu starts to fade from 1 (opaque) towards 0 (transparent).
    • The visibility property does not change yet. It has a transition-delay of 0.2s, so it waits. The menu remains visible and interactive while it's fading out.
  2. From 0 to 0.2 seconds:

    • The opacity continues to animate from 1 down to 0. The menu smoothly fades out.
  3. At 0.2 seconds:

    • The opacity transition is complete. The menu is now fully transparent (opacity: 0).
    • The 0.2s delay on the visibility transition is over. The visibility property instantly switches from visible to hidden. The element is removed from the document's visual flow and is no longer interactive.

Summary of Closing Effect: The menu smoothly fades out over 0.2 seconds. Crucially, it only gets set to visibility: hidden after the fade-out animation is complete. This allows the user to see the entire fade-out effect. If visibility changed instantly, the menu would disappear abruptly, and the opacity transition would not be seen.


扩展学习

  1. Transition Dropdown Menu CSS
  2. immediately invoked function expression (IIFE)

过渡到自动高度

代码不起作用是因为一个值不能从长度 0 过渡到 auto,因为不知道渲染以后真正的高度是多少。所以需要通过 JavaScript 来获取菜单精确的像素高度。

html
<body>
  <div class="dropdown" aria-haspopup="true">
    <button class="dropdown__toggle">Menu</button>
    <div class="dropdown__drawer">
      <ul class="menu" role="menu">
        <li role="menuitem"><a href="/features">Features</a></li>
        <li role="menuitem"><a href="/pricing">Pricing</a></li>
        <li role="menuitem"><a href="/support">Support</a></li>
        <li role="menuitem"><a href="/about">About</a></li>
      </ul>
    </div>
    <p><a href="/read-more">Read more</a></p>
    <script type="text/javascript">
      (function () {
        var toggle = document.getElementsByClassName("dropdown__toggle")[0];
        var dropdown = toggle.parentElement;
        var drawer = document.getElementsByClassName("dropdown__drawer")[0];
        var height = drawer.scrollHeight;
        toggle.addEventListener("click", function (e) {
          e.preventDefault();
          dropdown.classList.toggle("is-open");
          if (dropdown.classList.contains("is-open")) {
            drawer.style.setProperty("height", height + "px");
          } else {
            drawer.style.setProperty("height", "0");
          }
        });
      })();
    </script>
  </div>
</body>

过渡到自定义属性

Reference

最近更新