Vue 中命名过渡的威力

Avatar of Travis Almand
Travis Almand

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

Vue 提供了几种方法来控制元素或组件在插入 DOM 时如何视觉化地显示。例如淡入、滑入或其他视觉效果。几乎所有这些功能都基于单个组件:**transition 组件**。

一个简单的示例是使用基于布尔值的单个v-if。当布尔值为真时,元素出现。当布尔值为假时,元素消失。通常,此元素只会突然出现和消失,但使用 transition 组件,您可以控制视觉效果。

<transition>
  <div v-if="isVisible">is this visible?</div>
</transition>

已经撰写了几篇文章很好地介绍了 transition 组件,例如来自Sarah DrasnerNicolas UdyHassan Djirdeh的文章。每篇文章都详细介绍了 Vue 的 transition 组件的不同方面。本文将通过关注 transition 组件的一个方面来扩展主题;它们可以“命名”的事实。

<transition name="fade">
  <div v-if="isVisible">is this visible?</div>
</transition>

此属性提供的初始更改是,在过渡序列期间注入到元素上的 CSS 类将以给定的名称作为前缀。基本上,它将是fade-enter而不是上面示例中的v-enter。此单个属性可以远远超出此简单选项。它可用于利用 Vue 和 CSS 的某些功能,从而产生一些有趣的结果。

另一件需要考虑的事情是,name 属性可以绑定

<transition v-bind:name="currentTransition">
  <div v-if="isVisible">is this visible?</div>
</transition>

在此示例中,过渡将被命名为currentTransition解析到的值。这种简单的更改为应用程序的动画提供了另一层选项和功能。使用静态和动态命名过渡,项目可以拥有一系列预构建的过渡,准备应用于整个应用程序,可以扩展应用于它们的现有过渡的组件,切换在应用之前或之后使用的过渡,允许用户选择过渡,并控制列表的各个元素根据该列表的当前状态过渡到位。

本文旨在探讨这些功能并解释如何使用它们。

命名过渡时会发生什么?

默认情况下,当使用 transition 组件时,它会按特定顺序将特定类应用于元素。这些类可以在 CSS 中使用。如果没有 CSS,从本质上讲,这些类对元素没有任何作用。因此,需要这种性质的 CSS

.v-enter,
.v-leave-to {
  opacity: 0;
}

.v-enter-active,
.v-leave-active {
  transition: 0.5s;
}

这会导致元素以半秒的持续时间淡入淡出。对过渡进行细微更改可以为用户提供优雅的视觉反馈。但是,还有一个问题需要考虑。但首先,命名过渡有什么不同?

.fade-enter,
.fade-leave-to {
  opacity: 0;
}

.fade-enter-active,
.fade-leave-active {
  transition: 0.5s;
}

基本上是相同的 CSS,但使用fade-作为前缀而不是v-。此命名解决了使用 transition 组件的默认类名时可能发生的问题。v-前缀实际上使类具有全局作用,尤其是在 CSS 放在应用程序根级别的样式块中时。这实际上将使整个应用程序中*所有*没有 name 属性的过渡都使用相同的过渡效果。对于小型应用程序,这可能就足够了,但在更大、更复杂的应用程序中,它可能会导致不希望的视觉效果,因为并非所有内容都应该在半秒内淡入淡出。

命名过渡为开发人员在整个项目中提供了关于如何视觉化地插入或删除不同元素或组件的控制级别。建议所有过渡都命名——即使只有一个——以养成这样做的习惯。即使应用程序只有一个过渡效果,将来也可能需要添加一个新的过渡效果。在项目中已经命名了现有的过渡,可以简化添加新过渡的工作。

构建过渡效果集合

命名过渡提供了一个简单但非常有用的过程。一个常见的做法可能是将过渡类作为使用它们的组件的一部分创建。如果完成了组件样式作用域的另一个常见做法,则这些类将仅对该特定组件可用。如果两个不同的组件在其样式块中具有类似的过渡,那么我们只是在重复代码。

因此,让我们考虑将过渡的 CSS 保留在应用程序根目录的样式块中,通常是app.vue文件。在我的大多数项目中,我将它们放在样式块的最后一部分,以便于找到它们以进行调整和添加。将 CSS 保留在此位置使过渡效果可用于整个应用程序中 transition 组件的每个使用。以下是一些我项目中的示例。

.fade-enter,
.fade-leave-to { opacity: 0; }
.fade-enter-active,
.fade-leave-active { transition: 0.5s; }

.slide-enter {
  opacity: 0;
  transform: scale3d(2, 0.5, 1) translate3d(400px, 0, 0);
}

.slide-enter-to { transform: scale3d(1, 1, 1); }
.slide-enter-active,
.slide-leave-active { transition: 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55); }
.slide-leave { transform: scale3d(1, 1, 1); }

.slide-leave-to {
  opacity: 0;
  transform: scale3d(2, 0.5, 1) translate3d(-400px, 0, 0);
}

.rotate-enter { transform: perspective(500px) rotate3d(0, 1, 0, 90deg); }
.rotate-enter-active,
.rotate-leave-active { transition: 0.5s; }
.rotate-leave-to { transform: perspective(500px) rotate3d(0, 1, 0, -90deg); }

根据您的偏好和项目的需要,有多种方法可以存储这些过渡类。第一个,如前所述,是将它们全部保存在app.vue文件的样式块中。您还可以将项目资产文件夹中所有过渡的 Sass 部分保留,并将其导入到应用程序的样式块中。

<style lang="scss">
  @import "assets/_transitions.scss";
</style>

此方法允许在 Vue 文件之外调整和添加过渡集合。此设置的另一个好处是,如果项目共享过渡效果,则此类文件可以轻松地在项目之间传输。如果一个项目获得了一个新的过渡,那么可以很容易地将添加内容传输到另一个项目,而无需触摸主项目文件。

如果您使用的是 CSS 而不是 Sass,则可以将该文件作为项目的依赖项包含在内。您可以通过将文件保存在项目的 assets 文件夹中并在main.js文件中放置一个 require 语句来实现此目的。

require("@/assets/transitions.css");

另一个选项是将过渡样式保存在一个静态 CSS 文件中,该文件可以存储在其他位置,例如项目的 public 文件夹或服务器本身。由于这是一个常规的 CSS 文件,因此不需要构建或部署——只需在index.html文件中包含一个链接引用即可。

<link rel="stylesheet" type="text/css" href="/css/transitions.css">

此文件还可以存储在 CDN 中,供所有项目共享。每当文件更新时,更改都会立即在引用它的所有位置可用。如果创建了一个新的过渡名称,则现有项目可以根据需要开始使用新名称。

现在,让我们慢下来一分钟

当我们在构建一个用于整个项目的过渡集合时,让我们考虑一下那些可能不希望动画过于突然或根本不希望动画的用户。有些人可能会认为我们的动画过于夸张且没有必要,但对于某些人来说,它们实际上会导致问题。不久前,WebKit 引入了prefers-reduced-motion媒体查询来帮助解决可能的前庭谱系障碍问题。Eric Bailey 也发布了一个不错的介绍该媒体查询的文章。

在大多数情况下,将媒体查询作为我们过渡集合的一部分添加非常容易,并且应该考虑这样做。我们可以减少过渡中涉及的运动量以减少负面影响,或者简单地关闭它们。

以下是我演示中的一个简单示例

.next-enter {
  opacity: 0;
  transform: scale3d(2, 0.5, 1) translate3d(400px, 0, 0);
}

.next-enter-to { transform: scale3d(1, 1, 1); }
.next-enter-active,
.next-leave-active { transition: 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55); }
.next-leave { transform: scale3d(1, 1, 1); }

.next-leave-to {
  opacity: 0;
  transform: scale3d(2, 0.5, 1) translate3d(-400px, 0, 0);
}

/* If animations are reduced at the OS level, use simpler transitions */
@media screen and (prefers-reduced-motion: reduce) {
  .next-enter {
    opacity: 0;
    transform: translate3d(100px, 0, 0);
  }

  .next-enter-active,
  .next-leave-active { transition: 0.5s; }

  .next-leave-to {
    opacity: 0;
    transform: translate3d(-100px, 0, 0);
  }
}

在该示例中,我采用了相当夸张的过渡并使其更简单。动画是一个向左移动的弹性缓动滑动,然后在移开时缩小并淡出。如果有人设置了减少运动的偏好,则动画将变成一个更简单的过渡,距离更短(这使其速度更慢)并保留淡出效果。如果我们想关闭它们,那么我们只需要引用具有 transition 属性的类并将它们的值设置为none

要测试此功能,需要在您各自的操作系统上找到并选中一个复选框。在 Windows 上,您可以在控制面板 > 轻松访问中心 > 使计算机更易于查看部分找到它;查找“关闭所有不必要的动画(如果可能)”。在 Mac 上,请在系统偏好设置 > 辅助功能 > 显示下查找;查找“减少运动”。最新的 iOS 设备在辅助功能下也有类似的设置。

让我们保持过渡集合的灵活性

使用此过渡集合,可能会存在缺乏灵活性的问题。例如,如果一个元素需要稍微慢一点的淡出时间怎么办?假设效果中的其他所有内容都可以保持不变,只需要更改transition-duration。有一些方法可以进行调整,而无需创建全新的过渡名称。

最简单的方法是直接在 transition 组件内的元素上使用内联样式。

<transition name="fade">
  <div style="transition-duration: 6s;" v-if="isVisible">this has a different duration</div>
</transition>

可以通过 Vue 提供的各种处理样式和类的方法来完成此类更改。

假设您正在使用带有is属性的组件元素来实现动态组件,例如这样

<transition name="fade" mode="out-in">
  <component :is="currentComponent"></component>
</transition>

即使有了这个动态组件,我们仍然可以选择调整过渡效果的属性。同样,我们可以在组件元素上应用内联样式,该样式将放置在组件的根元素上。根元素也会接收过渡类,因此我们可以直接覆盖它们的属性。

<transition name="fade" mode="out-in">
  <component :is="currentComponent" style="transition-duration: 6s;"></component>
</transition>

另一个选择是将 props 传递给我们的组件。这样,就可以通过组件的代码将其根元素应用所需的更改。

<transition name="fade" mode="out-in">
  <component :is="currentComponent" duration="6s"></component>
</transition>
<template>
  <div :style="`transition-duration: ${duration}`">component one</div>
</template>

<script>
export default {
  name: "component-one",
  props: {
    duration: String
  }
};
</script>

我们也可以在组件的样式块内覆盖过渡类的属性,尤其是在样式作用域的情况下。

<style scoped>
  .fade-enter-active,
  .fade-leave-active { transition-duration: 1s; }
</style>

在这种情况下,组件的淡出持续时间将为一秒,而不是全局持续时间半秒。我们甚至可以更进一步,为序列的每一侧设置不同的持续时间。

<style scoped>
  .fade-enter-active { transition-duration: 1s; }
  .fade-leave-active { transition-duration: 2s; }
</style>

如果需要,可以在组件中更改任何全局过渡类。虽然这不如在类结构之外更改属性灵活,但在某些情况下仍然非常有用。

如您所见,即使我们有一系列预构建的过渡,我们仍然可以选择灵活地进行调整。

动态过渡

即使在探索了 Vue 的过渡组件的所有这些有趣功能之后,另一个有趣的特性等待我们去探索。过渡组件上的 name 属性本质上是动态的,这意味着我们可以根据需要更改当前使用的过渡。

这意味着过渡可以根据代码更改为在不同情况下具有不同的动画效果。例如,我们可以根据问题的答案更改过渡、根据用户交互决定过渡,以及让列表根据其自身当前状态使用不同的过渡。

让我们深入了解这三个示例。

示例 1:根据答案更改过渡

在这个示例中,我们有一个简单的数学问题需要回答。随机选择两个数字,我们期望提供它们的和。然后点击按钮,将答案与预期答案进行比较。方程式上方会出现一个小通知,指示答案是正确还是错误。如果答案正确,则通知会显示一个暗示点头表示同意的过渡,并伴随上下动画。如果你的答案不正确,则通知会左右移动,暗示摇头表示不同意。

查看 CodePen 上 Travis Almand (@talmand) 编写的笔
VueJS 动态过渡:根据答案更改过渡

CodePen 上。

其背后的逻辑并不复杂,过渡的设置也不复杂。以下是 HTML 代码

<transition :name="currentTransition">
  <div id="notification" :class="response.toString()" v-if="answerChecked">{{ response }}</div>
</transition>

本质上非常简单。我们在过渡上绑定了一个名称,然后在通知 div 上使用 v-if。我们还根据响应应用 truefalse 类来装饰通知。

以下是过渡的 CSS 代码

.positive-enter-active { animation: positive 1s; }
@keyframes positive {
  0% { transform: translate3d(0, 0, 0); }
  25% { transform: translate3d(0, -20px, 0); }
  50% { transform: translate3d(0, 20px, 0); }
  75% { transform: translate3d(0, -20px, 0); }
  100% { transform: translate3d(0, 0, 0); }
}

.negative-enter-active { animation: negative 1s; }
@keyframes negative {
  0% { transform: translate3d(0, 0, 0); }
  25% { transform: translate3d(-20px, 0, 0); }
  50% { transform: translate3d(20px, 0, 0); }
  75% { transform: translate3d(-20px, 0, 0); }
  100% { transform: translate3d(0, 0, 0); }
}

您会看到我正在使用 CSS 动画来实现上下和左右效果。

以下是一些 JavaScript 代码

methods: {
  randomProblem: function () {
    this.a = Math.floor(Math.random() * Math.floor(10));
    this.b = Math.floor(Math.random() * Math.floor(10));
  },
  check: function () {
    this.response = this.a + this.b === parseInt(this.answer);
    this.answerChecked = true;
    this.currentTransition = this.response ? 'positive' : 'negative';
  },
  reset: function () {
    this.answer = null;
    this.answerChecked = false;
    this.randomProblem();
  }
}

randomProblem 方法设置我们的方程式。check 方法根据提供的答案与正确答案的比较结果来决定使用哪种过渡效果。然后是简单的 reset 方法,它只是重置所有内容。

这只是一个简单的示例。另一个可能的示例是,根据通知是否重要来设置两种不同的通知效果。如果消息不那么重要,我们可以使用一个微妙的动画,不会分散用户对当前任务的注意力。如果它很重要,我们可以使用一个更直接的动画,以便将用户的目光吸引到通知上。

示例 2:根据用户交互更改过渡

我们还可以构建某种轮播。这可以是幻灯片演示、图片库或一系列说明。基本思想是我们需要按顺序向用户展示信息。在此演示中,用户可以决定何时继续以及是向前移动还是向后移动。

查看 CodePen 上 Travis Almand (@talmand) 编写的笔
VueJS 动态过渡:根据用户交互更改过渡

CodePen 上。

同样,这是一个相当简单的设置。此示例或多或少类似于幻灯片演示。底部的两个按钮在两个组件之间切换,并使用滑动过渡。一个真正的项目将有更多组件,或者可能具有根据当前幻灯片更改组件内容的逻辑。此示例保持简单,以演示这个想法。

以下是 HTML 代码

<transition :name="currentTransition" mode="out-in">
  <component :is="slides[currentSlide]"></component>
</transition>

您会看到,每当组件元素上的绑定 is 属性切换组件时,我们仅仅执行过渡。

以下是 CSS 代码

.next-enter {
  opacity: 0;
  transform: scale3d(2, 0.5, 1) translate3d(400px, 0, 0);
}

.next-enter-to { transform: scale3d(1, 1, 1); }
.next-enter-active,
.next-leave-active { transition: 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55); }
.next-leave { transform: scale3d(1, 1, 1); }

.next-leave-to {
  opacity: 0;
  transform: scale3d(2, 0.5, 1) translate3d(-400px, 0, 0);
}

.prev-enter {
  opacity: 0;
  transform: scale3d(2, 0.5, 1) translate3d(-400px, 0, 0);
}

.prev-enter-to { transform: scale3d(1, 1, 1); }
.prev-enter-active,
.prev-leave-active { transition: 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55); }
.prev-leave { transform: scale3d(1, 1, 1); }

.prev-leave-to {
  opacity: 0;
  transform: scale3d(2, 0.5, 1) translate3d(400px, 0, 0);
}

/* If animations are reduced at the OS level, use simpler transitions */
@media screen and (prefers-reduced-motion: reduce) {
  .next-enter { opacity: 0; transform: translate3d(100px, 0, 0); }
  .next-enter-active,
  .next-leave-active { transition: 0.5s; }
  .next-leave-to { opacity: 0; transform: translate3d(-100px, 0, 0); }
  
  .prev-enter { opacity: 0; transform: translate3d(-100px, 0, 0); }
  .prev-enter-active,
  .prev-leave-active { transition: 0.5s; }
  .prev-leave-to { opacity: 0; transform: translate3d(100px, 0, 0); }
}

这里我们有两个过渡,一个用于用户点击“下一步”按钮时,另一个用于“上一步”按钮。每个过渡本质上都使用 transform 属性将组件滑到相应的方向,但也添加了一些额外的效果,以创建一种卡通风格的挤压效果。我们还使用了 prefers-reduced-motion 将动画更改为更简单的淡入淡出,并向相应方向轻微滑动。

现在,来看 JavaScript 代码

methods: {
  changeSlide: function (dir) {
    this.currentSlide = dir === 'next' ? this.currentSlide + 1 : this.currentSlide - 1;
    this.currentTransition = dir;
  }
}

每个按钮在其点击事件上调用 changeSlide 方法并传递它所代表的方向。然后,我们有一些逻辑来跟踪当前的幻灯片是什么。一行代码控制使用哪个过渡。由于“下一步”按钮传递“next”作为方向,因此它对应于 CSS 中的“next”过渡。对于“上一步”按钮也是如此。每次用户点击按钮时,应用程序都会自动知道要使用哪个过渡。因此,我们拥有不错的过渡效果,可以提供用户在序列中前进方向的上下文。

示例 3:根据列表状态更改过渡

在我们的最后一个示例中,我们将了解如何根据 transition-group 组件内列表的当前状态更改过渡。这里的想法是每次更新列表项时都使用不同的过渡。

查看 CodePen 上 Travis Almand (@talmand) 编写的笔
VueJS 动态过渡:根据列表状态更改过渡

CodePen 上。

在此示例中,我们在右侧显示了一个城市列表,在左侧显示了一个空列表。当在右侧选择城市时,它们会填充左侧的空白。第一个城市从上方滑入并淡入视图。最后一个城市之前的城市将从右侧或左侧滑入,具体取决于之前的过渡方向,最后一个城市从下方滑入。

以下是 HTML 代码

<transition-group :name="currentListTransition" tag="ul" class="list">
  <li v-for="(item, index) in selectedItems" :key="item">{{ item }}</li>
</transition-group>

像往常一样,这是一个相当简单的设置。以下是 CSS 中的过渡

.top-enter-active,
.top-leave-active { transition: 0.5s; }
.top-enter,
.top-leave-to {
  opacity: 0;
  transform: translate3d(0, -40px, 0);
}

.top-move {
  opacity: 0.5;
  transition: 0.5s;
}

.left-enter-active,
.left-leave-active { transition: 0.5s; }
.left-enter,
.left-leave-to {
  opacity: 0;
  transform: translate3d(-40px, 0, 0);
}

.left-move {
  opacity: 0.5;
  transition: 0.5s;
}

.right-enter-active,
.right-leave-active { transition: 0.5s; }
.right-enter,
.right-leave-to {
  opacity: 0;
  transform: translate3d(40px, 0, 0);
}

.right-move {
  opacity: 0.5;
  transition: 0.5s;
}

.bottom-enter-active,
.bottom-leave-active { transition: 0.5s; }
.bottom-enter,
.bottom-leave-to {
  opacity: 0;
  transform: translate3d(0, 30px, 0);
}

.bottom-move {
  opacity: 0.5;
  transition: 0.5s;
}

/* If animations are reduced at the OS level, turn off transitions */
@media screen and (prefers-reduced-motion: reduce) {
  .top-enter-active,
  .top-leave-active { transition: none; }
  .top-move { transition: none; }
  .left-enter-active,
  .left-leave-active { transition: none; }
  .left-move { transition: none; }
  .right-enter-active,
  .right-leave-active { transition: none; }
  .right-move { transition: none; }
  .bottom-enter-active,
  .bottom-leave-active { transition: none; }
  .bottom-move { transition: none; }
}

如您所见,每个城市出现在空列表中的可能方向都有一个过渡。

现在,来看我们的 JavaScript 代码

methods: {
  chooseCity: function (index) {
    let selectedLength = this.selectedItems.length;
    let citiesLength = this.cities.length;
    let clt = this.currentListTransition;
    
    if (selectedLength === 0) {
      clt = 'top';
    } else if (selectedLength > 0 && selectedLength < citiesLength - 1) {
      clt = clt === 'top' || clt === 'left' ? 'right' : 'left';
    } else if (selectedLength === citiesLength - 1) {
      clt = 'bottom';
    }
    
    this.currentListTransition = clt;
    this.selectedItems.push(this.cities[index]);
    document.querySelector(`.city:nth-child(${index + 1})`).classList.add('selected');
  },

  clearSelection: function () {
    this.currentListTransition = 'right';
    this.selectedItems = [];
    document.querySelectorAll('.city.selected').forEach(element => {
      element.classList.remove('selected');
    });
  }
}

chooseCity 方法处理您选择每个城市时发生的事情。我们主要关注的是方法中间的一系列 ifif/else 语句。当选择城市时,逻辑会查看最终将选定的城市推入的 selectedItems 数组的当前长度。如果长度为零,则为第一个城市,因此过渡应使其从顶部进入。如果长度在零到城市列表总数之间,则过渡应为向右或向左。使用的新方向基于先前过渡方向。然后,最后,如果我们正在选择最后一个城市,它将更改为底部过渡。同样,我们使用 prefers-reduced-motion,在这种情况下完全关闭过渡。

更改列表过渡的另一个选择是根据所选项目的类型进行更改;例如,东海岸城市与西海岸城市,每个城市都有不同的过渡。考虑根据添加到列表中的项目总数更改过渡;例如,每五个项目使用不同的过渡。

再见,感谢所有的过渡

在了解了所有这些示例和想法之后,我希望您会在自己的项目中考虑利用 Vue 的过渡组件。探索在应用程序中添加过渡和动画的可能性,为用户提供上下文和趣味性。在许多情况下,此类添加的实现非常简单,几乎到了不添加就可惜的地步。Vue 提供了一个令人兴奋且非常有用的功能,即开箱即用的过渡组件,我只能鼓励大家使用它。

干杯。