1.transition #

在 Vue 中,<transition> 是一个特殊的包裹组件,用于自动应用过渡效果。当嵌套的元素或组件进入/离开 DOM 时,<transition> 组件将自动检测并应用相应的 CSS 或 JavaScript 过渡/动画。

1.1 基本用法 #

最简单的使用方式就是在包裹元素或组件时使用 <transition> 标签:

<transition>
  <p v-if="show">Hello</p>
</transition>

当你修改 show 属性的值,从 true 切换到 false 或相反,Vue 将自动应用以下 CSS 类:

你可以在 CSS 中定义这些类:

.v-enter-active, .v-leave-active {
  transition: opacity 1s;
}
.v-enter, .v-leave-to {
  opacity: 0;
}

在进入/离开的过渡中,会有 6 个 class 切换。

v-enter:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。

v-enter-to:定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter 被移除),在过渡/动画完成之后移除。

v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。

v-leave:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。

v-leave-to:定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave 被删除),在过渡/动画完成之后移除。

v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。

对于这些在过渡中切换的类名来说,如果你使用一个没有名字的 ,则 v- 是这些类名的默认前缀。如果你使用了 ,那么 v-enter 会替换为 my-transition-enter。

v-enter-active 和 v-leave-active 可以控制进入/离开过渡的不同的缓和曲线,

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Vue 2 Transition Example</title>
  <style>
    .v-enter-active, .v-leave-active {
      transition: opacity 1s;
    }
    .v-enter, .v-leave-to {
      opacity: 0;
    }
  </style>
</head>
<body>
  <div id="app">
    <button @click="toggleShow">Toggle</button>
    <transition>
      <p v-if="show">Hello</p>
    </transition>
  </div>
  <script src="https://unpkg.com/vue@2/dist/vue.js"></script>
  <script>
    new Vue({
      el: '#app',
      data: {
        show: true
      },
      methods: {
        toggleShow() {
          this.show = !this.show;
        }
      }
    });
  </script>
</body>
</html>

1.2 名称和自定义类 #

你可以使用 name 属性自定义过渡类的前缀:

<transition name="fade">
  <p v-if="show">Hello</p>
</transition>

这将会改变应用的 CSS 类为 fade-enter-activefade-enter,等等。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Vue 2 Transition Example</title>
  <style>
    /* 使用自定义前缀 "fade" 替代默认的 "v" */
    .fade-enter-active, .fade-leave-active {
      transition: opacity 1s;
    }
    .fade-enter, .fade-leave-to {
      opacity: 0;
    }
  </style>
</head>
<body>
  <div id="app">
    <!-- 点击按钮切换 show 的值,触发过渡效果 -->
    <button @click="toggleShow">Toggle</button>
    <!-- 使用 name 属性自定义过渡类名的前缀 -->
    <transition name="fade">
      <p v-if="show">Hello</p>
    </transition>
  </div>
  <!-- 引入 Vue.js 库 -->
  <script src="https://unpkg.com/vue@2/dist/vue.js"></script>
  <!-- Vue 实例 -->
  <script>
    new Vue({
      el: '#app',
      data: {
        show: true // 控制元素是否显示,从而触发过渡效果
      },
      methods: {
        // 切换 show 的值
        toggleShow() {
          this.show = !this.show;
        }
      }
    });
  </script>
</body>
</html>

1.3 钩子函数 #

Vue 允许你使用 JavaScript 钩子函数来执行更复杂的过渡效果。这些钩子函数可以在 <transition> 标签内通过特定的事件(如 @before-enter@enter 等)来触发。

<transition
  @before-enter="beforeEnter"
  @enter="enter"
  @after-enter="afterEnter"
  @before-leave="beforeLeave"
  @leave="leave"
  @after-leave="afterLeave"
>
  <p v-if="show">Hello</p>
</transition>

在 Vue 实例中定义这些方法:

new Vue({
  el: '#app',
  data: {
    show: true
  },
  methods: {
    beforeEnter(el) {
    },
    enter(el, done) {
      done();
    },
  }
})
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue 2 JavaScript Transition Hooks Example</title>
</head>
<body>
    <div id="app">
        <button @click="toggleShow">Toggle</button>
        <transition
          @before-enter="beforeEnter"
          @enter="enter"
          @leave="leave"
        >
          <p v-if="show">Hello</p>
        </transition>
    </div>
    <!-- Vue.js -->
    <script src="https://unpkg.com/vue@2/dist/vue.js"></script>
    <script>
        new Vue({
            el: '#app',
            data: {
                show: true
            },
            methods: {
                toggleShow() {
                    this.show = !this.show;
                },
                beforeEnter(el) {
                    el.style.opacity = 0;
                },
                enter(el, done) {
                    let currentTime = 0;
                    const interval = 16;  // 60 frames per second
                    const totalTime = 1000;  // 1 second
                    function animate(time) {
                        currentTime += interval;
                        const progress = currentTime / totalTime;
                        if (progress < 1) {
                            el.style.opacity = progress;
                            requestAnimationFrame(animate);
                        } else {
                            el.style.opacity = 1;
                            done();
                        }
                    }
                    requestAnimationFrame(animate);
                },
                leave(el, done) {
                    let currentTime = 0;
                    const interval = 16;  // 60 frames per second
                    const totalTime = 1000;  // 1 second
                    function animate(time) {
                        currentTime += interval;
                        const progress = 1 - currentTime / totalTime;
                        if (progress > 0) {
                            el.style.opacity = progress;
                            requestAnimationFrame(animate);
                        } else {
                            el.style.opacity = 0;
                            done();
                        }
                    }
                    requestAnimationFrame(animate);
                }
            }
        });
    </script>
</body>
</html>

1.4 transition-group #

在Vue中,<transition-group>用于管理多个元素或组件的过渡效果。与<transition>不同,<transition-group>不仅可以应用过渡效果于元素进入和离开,还能在排序改变(例如列表排序)时产生过渡效果。

列表的排序过渡

使用<transition-group>通常需要指定一个tag属性来决定应该渲染什么类型的DOM元素来包裹子元素。默认为<span>

下面是一个基础示例:

<!DOCTYPE html>
<html>
<head>
    <title>Vue 2 Transition Group Example</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
    <style>
        .list-enter-active, .list-leave-active {
          transition: all 1s;
        }
        .list-enter, .list-leave-to {
          opacity: 0;
          transform: translateY(30px);
        }
    </style>
</head>
<body>

<div id="app">
    <button @click="addItem">Add Item</button>
    <button @click="removeItem">Remove Item</button>
    <transition-group name="list" tag="ul">
        <li v-for="item in items" :key="item.id">{{ item.text }}</li>
    </transition-group>
</div>

<script>
    new Vue({
        el: '#app',
        data: {
            items: [
                { id: 1, text: 'Item 1' },
                { id: 2, text: 'Item 2' },
                { id: 3, text: 'Item 3' }
            ],
            nextId: 4
        },
        methods: {
            addItem() {
                this.items.push({ id: this.nextId++, text: `Item ${this.nextId}` });
            },
            removeItem() {
                this.items.length > 0 && this.items.pop();
            }
        }
    });
</script>

</body>
</html>

在这个例子中:

  1. 按钮用于添加和删除列表项。
  2. 列表项有一个唯一的key属性,这对于<transition-group>来说是必需的。
  3. CSS过渡类的前缀(这里是list)与<transition-group>name属性值相匹配。

点击“Add Item”和“Remove Item”按钮,你会看到列表项有平滑的过渡效果。

使用<transition-group>,你可以很容易地创建诸如排序、添加或删除的复杂过渡效果,这在普通的<transition>组件中要更复杂地实现。

2.CSS 动画 #

在 Vue 中,CSS 动画用于在元素进入或离开 DOM 时添加动画效果。这些效果通过使用专门的 CSS 类和 Vue 的 <transition> 组件进行管理。

CSS 动画的基本示例

下面是一个使用 CSS 动画的简单示例:

<!DOCTYPE html>
<html>
<head>
    <title>Vue CSS Animation</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
    <style>
        @keyframes slide-in {
            from {
                transform: translateX(100%);
            }
            to {
                transform: translateX(0);
            }
        }
        @keyframes slide-out {
            from {
                transform: translateX(0);
            }
            to {
                transform: translateX(100%);
            }
        }
        .slide-enter-active, .slide-leave-active {
            animation-duration: 1s;
        }
        .slide-enter-active {
            animation-name: slide-in;
        }
        .slide-leave-active {
            animation-name: slide-out;
        }
    </style>
</head>
<body>
<div id="app">
    <button @click="show = !show">Toggle</button>
    <transition name="slide">
        <div v-if="show">Hello, World!</div>
    </transition>
</div>
<script>
    new Vue({
        el: '#app',
        data: {
            show: true
        }
    });
</script>
</body>
</html>

在这个示例中:

当你点击 "Toggle" 按钮时,"Hello, World!" 的 div 元素会滑入和滑出。

说明

3.参考 #

3.1 CSS 过渡 #

CSS 过渡(CSS Transitions)是一种简单的方式,用于在两个不同的状态间添加动画效果。它们主要用于在鼠标悬停、焦点或激活某个元素,或在修改 DOM 时创建平滑的动画。

基础语法

CSS 过渡的基础语法如下:

.selector {
  /* 初始状态 */
  background-color: blue;
  transition: background-color 0.3s ease;
}

.selector:hover {
  /* 目标状态 */
  background-color: red;
}

这里的 .selector 代表你希望添加过渡效果的 CSS 选择器。在这个示例中,当你悬停在 .selector 元素上时,background-color 将在 0.3 秒内从蓝色平滑过渡到红色。

过渡属性

示例代码与 Vue 2 集成

在 Vue.js 中,你可以使用 <transition> 组件与 CSS 过渡相结合:

<!DOCTYPE html>
<html>
<head>
  <title>CSS Transition with Vue</title>
  <style>
    /* 初始状态 */
    .fade-enter-active, .fade-leave-active {
      transition: opacity 0.5s;
    }
    /* 进入与离开过渡前状态 */
    .fade-enter, .fade-leave-to {
      opacity: 0;
    }
  </style>
</head>
<body>
  <div id="app">
    <button @click="show = !show">Toggle</button>
    <transition name="fade">
      <p v-if="show">Hello</p>
    </transition>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
  <script>
    new Vue({
      el: '#app',
      data: {
        show: true
      }
    });
  </script>
</body>
</html>

在这个示例中,当你点击 "Toggle" 按钮时,文本 "Hello" 的透明度会在 0.5 秒内从 0 到 1(或从 1 到 0)平滑过渡。这是通过 .fade-enter-active.fade-leave-active 类,以及 transition: opacity 0.5s; 实现的。

3.2 transitionend #

transitionend 是一个 JavaScript 事件,它在 CSS 过渡完成后触发。这个事件允许你在 CSS 过渡结束时执行某些动作或逻辑。

基础用法

下面是一个简单的 HTML 和 JavaScript 示例,展示了如何使用 transitionend 事件。

<!DOCTYPE html>
<html>
<head>
  <title>transitionend Example</title>
  <style>
    #box {
      width: 100px;
      height: 100px;
      background-color: blue;
      transition: width 2s;
    }
  </style>
</head>
<body>
  <div id="box"></div>
  <button id="animate">Animate</button>
  <script>
    const box = document.getElementById('box');
    const animateBtn = document.getElementById('animate');

    box.addEventListener('transitionend', function() {
      alert('Transition has ended!');
    });

    animateBtn.addEventListener('click', function() {
      box.style.width = '200px';
    });
  </script>
</body>
</html>

在这个示例中,当你点击 "Animate" 按钮时,#box 的宽度会在 2 秒内从 100px 变为 200px。过渡完成后,会触发 transitionend 事件,并弹出一个警告框显示 "Transition has ended!"。

注意事项

  1. 多属性过渡: 如果你对多个属性应用过渡,transitionend 事件可能会多次触发。你可以通过事件对象的 propertyName 属性来确定是哪个属性的过渡结束了。

     box.addEventListener('transitionend', function(event) {
       console.log(event.propertyName + ' has finished transitioning.');
     });
    
  2. 兼容性: 虽然大多数现代浏览器都支持 transitionend 事件,但最好还是检查兼容性并考虑使用前缀,比如 webkitTransitionEnd

  3. 取消过渡: 如果过渡被取消(例如,元素突然不可见或过渡属性被覆盖),transitionend 事件可能不会触发。

在 Vue 中使用 transitionend

在 Vue.js 中,你可以使用原生 DOM 事件绑定方式在 transition 组件上使用 transitionend,如下:

<transition @transitionend="handleTransitionEnd">
  <p v-if="show">Hello</p>
</transition>

然后,在 Vue 实例的 methods 中定义 handleTransitionEnd 方法:

new Vue({
  el: '#app',
  data: {
    show: true
  },
  methods: {
    handleTransitionEnd() {
      console.log('Transition has ended');
    }
  }
});

这样,每当过渡结束时,handleTransitionEnd 方法就会被调用,你就可以在这里执行任何需要在过渡结束后进行的操作。

3.3 CSS动画 #

CSS @keyframes 动画用于创建复杂的动画序列,这些动画不仅限于从一个样式迁移到另一个样式,还可以包括多个中间步骤。

基础语法

创建一个 @keyframes 动画涉及定义动画的名称和各个阶段的样式。

@keyframes myAnimation {
  0% {
    background-color: red;
    left: 0px;
  }
  50% {
    background-color: yellow;
    left: 50px;
  }
  100% {
    background-color: green;
    left: 100px;
  }
}

上面的 @keyframes 动画定义了一个名为 myAnimation 的动画,其中包含了三个阶段(0%, 50%, 和 100%)。

然后你需要把这个 @keyframes 动画绑定到一个或多个元素上,通常使用 animation 属性。

#box {
  animation: myAnimation 4s infinite;
}

动画属性

这些属性也可以被组合为一个 animation 属性。

示例

HTML:

<!DOCTYPE html>
<html>
<head>
    <title>@keyframes Example</title>
    <style>
        #box {
            width: 100px;
            height: 100px;
            position: relative;
            animation: myAnimation 4s infinite;
        }
        @keyframes myAnimation {
            0% {
                background-color: red;
                left: 0px;
            }
            50% {
                background-color: yellow;
                left: 50px;
            }
            100% {
                background-color: green;
                left: 100px;
            }
        }
    </style>
</head>
<body>
    <div id="box"></div>
</body>
</html>

这个例子中,元素 #box 会进行一个 4 秒的无限循环动画,该动画按照 @keyframes myAnimation 的定义来变更其 background-colorleft 属性。

3.4 animationend #

animationend 是一个 DOM 事件,当 CSS 动画完成一个完整的周期后触发。这个事件可以用于检测动画何时结束,并执行一些后续操作,如移除动画类,改变元素状态,或启动另一个动画等。

这个事件在动画周期完成后触发,而不是每个动画迭代完成后触发。如果你的动画设置了无限循环(例如,animation-iteration-count: infinite;),那么 animationend 事件将不会被触发。

如何使用

你可以使用 JavaScript 的 addEventListener 方法来监听 animationend 事件。

// 使用 JavaScript 绑定 animationend 事件
document.getElementById("myElement").addEventListener("animationend", function(event) {
  // 动画结束后要执行的代码
  alert("Animation ended!");
});

这里是一个简单的示例:

HTML:

<!DOCTYPE html>
<html>
<head>
  <title>animationend Example</title>
  <style>
    #box {
      width: 100px;
      height: 100px;
      background-color: red;
      animation: myAnimation 2s;
    }
    @keyframes myAnimation {
      0% { background-color: red; }
      100% { background-color: green; }
    }
  </style>
</head>
<body>
  <div id="box"></div>
  <script>
    document.getElementById("box").addEventListener("animationend", function(event) {
      alert("Animation ended!");
    });
  </script>
</body>
</html>

在这个例子中,当 #box 的动画结束时,会触发 animationend 事件,然后显示一个 alert 对话框。

注意

使用 animationend 事件,你可以更精确地控制动画的行为,并在动画结束后执行特定的操作。

3.5 FLIP动画 #

FLIP 是一种用于创建高性能动画的技术。该名字是首字母缩写,分别代表以下四个步骤:First(第一步)、Last(最后一步)、Invert(反转)、Play(播放)。

工作原理

  1. First(第一步): 在动画开始之前,获取元素的当前位置和尺寸。

  2. Last(最后一步): 然后,应用任何会导致元素移动或者改变尺寸的 CSS 规则,并且重新获取该元素的最终位置和尺寸。

  3. Invert(反转): 计算第一步和第二步之间的差异(位置、尺寸等),并使用 CSS transform 反转这些更改。这意味着,你将元素移动回它原来的位置和尺寸。

  4. Play(播放): 最后,使用 CSS transition 将 transform 属性设置回 "none",这样元素就会回到它的最终状态。

通过这种方式,你实际上是在使用 transform 和 opacity 来创建动画,这些属性不会触发浏览器的布局或重绘,从而能够提供更流畅的动画效果。

示例代码

以下是一个简单的 FLIP 动画示例,该示例在一个元素从一个位置平滑移动到另一个位置:

<!DOCTYPE html>
<html>
<head>
  <style>
    #box {
      position: absolute;
      top: 0;
      left: 0;
      width: 100px;
      height: 100px;
      background: red;
    }
  </style>
</head>
<body>
<div id="box"></div>
<script>
  function moveElement() {
    // First: 获取元素初始位置
    const firstRect = box.getBoundingClientRect();
    // Last: 移动元素
    box.style.left = "400px";
    box.style.top = "300px";
    // 触发重绘以获取最终位置
    box.offsetHeight;
    // 获取元素最终位置
    const lastRect = box.getBoundingClientRect();
    // Invert: 反转元素位置
    const deltaX = firstRect.left - lastRect.left;
    const deltaY = firstRect.top - lastRect.top;
    box.style.transition = "none";
    box.style.transform = `translate(${deltaX}px, ${deltaY}px)`;
    // 触发重绘以应用反转
    box.offsetHeight;
    // Play: 播放动画
    box.style.transition = "transform 0.5s";
    box.style.transform = "";
  }
  // 延迟以便页面加载完成
  setTimeout(moveElement, 500);
</script>
</body>
</html>

在这个例子中,我们首先获取元素的初始位置(First),然后更改元素的位置(Last)。接下来,我们计算两者之间的差异并反转这些更改(Invert)。最后,我们应用一个 transition 属性,并将 transform 属性设置为 "none"(Play)。

使用 FLIP,你可以创建复杂的动画效果,而不会牺牲性能。这是一个在前端开发中非常有用的技术。