MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Vue中过渡动画的高级应用

2022-02-173.7k 阅读

Vue过渡动画基础回顾

在深入探讨Vue过渡动画的高级应用之前,我们先来简要回顾一下基础的过渡动画知识。Vue提供了<transition><transition - group>组件,用于在元素插入、更新或移除时添加过渡效果。

基础的<transition>组件

<transition>组件主要用于单个元素的过渡效果。当一个元素被插入或移除DOM时,Vue会自动给该元素添加或移除一些CSS类名,我们可以通过这些类名来定义过渡动画。

例如,我们有一个简单的按钮,点击后会显示或隐藏一个<div>元素:

<template>
  <div>
    <button @click="show =!show">Toggle</button>
    <transition name="fade">
      <div v - if="show">Hello, Transition!</div>
    </transition>
  </div>
</template>

<script>
export default {
  data() {
    return {
      show: false
    };
  }
};
</script>

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

在上述代码中,name="fade"指定了过渡的名称,Vue会根据这个名称自动生成fade - enterfade - enter - activefade - leavefade - leave - to等类名。当<div>元素插入时,会先添加fade - enter类,然后立即添加fade - enter - active类,在过渡结束后移除fade - enterfade - enter - active类。当<div>元素移除时,会先添加fade - leave类,然后立即添加fade - leave - tofade - leave - active类,过渡结束后移除这些类。

<transition - group>组件

<transition - group>组件用于多个元素或组件的过渡。与<transition>不同的是,它要求每个子元素都有一个唯一的key属性。

比如,我们有一个列表,当添加或移除列表项时会有过渡效果:

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

<script>
export default {
  data() {
    return {
      items: []
    };
  },
  methods: {
    addItem() {
      this.items.push({ id: Date.now(), text: 'New Item' });
    },
    removeItem(index) {
      this.items.splice(index, 1);
    }
  }
};
</script>

<style>
.list - enter - active,
.list - leave - active {
  transition: all 0.5s;
}
.list - enter,
.list - leave - to {
  opacity: 0;
  transform: translateY(30px);
}
</style>

在这个例子中,<transition - group>tag="ul"表示它会渲染成一个<ul>标签,每个<li>元素都有一个唯一的key属性。当添加或移除列表项时,会根据定义的CSS类名来执行过渡动画。

过渡动画的高级应用场景

列表排序过渡

在实际应用中,我们经常会遇到需要对列表进行排序的情况,并且希望在排序过程中有过渡动画。Vue的<transition - group>组件可以很好地实现这一点。

假设我们有一个任务列表,每个任务有一个优先级,我们可以通过点击按钮来按优先级对列表进行排序:

<template>
  <div>
    <button @click="sortByPriority">Sort by Priority</button>
    <transition - group name="sort" tag="ul">
      <li v - for="(task, index) in tasks" :key="task.id">
        {{ task.text }} (Priority: {{ task.priority }})
      </li>
    </transition - group>
  </div>
</template>

<script>
export default {
  data() {
    return {
      tasks: [
        { id: 1, text: 'Task 1', priority: 3 },
        { id: 2, text: 'Task 2', priority: 1 },
        { id: 3, text: 'Task 3', priority: 2 }
      ]
    };
  },
  methods: {
    sortByPriority() {
      this.tasks.sort((a, b) => a.priority - b.priority);
    }
  }
};
</script>

<style>
.sort - move {
  transition: transform 0.5s;
}
</style>

在上述代码中,我们定义了一个.sort - move类,当列表项位置发生变化时,Vue会自动给该元素添加这个类,我们通过这个类来定义排序过程中的过渡动画。transition: transform 0.5s;表示元素在位置移动时会有一个0.5秒的平滑过渡。

过渡动画的嵌套

有时候,我们需要在一个过渡动画中嵌套另一个过渡动画,以实现更复杂的效果。比如,我们有一个卡片,点击卡片会展开显示更多内容,并且展开和收起的过程都有过渡动画,而卡片内的子元素在显示和隐藏时也有各自的过渡动画。

<template>
  <div>
    <transition name="card - transition">
      <div class="card" @click="isExpanded =!isExpanded">
        <h3>{{ card.title }}</h3>
        <transition - group name="inner - transition" v - if="isExpanded">
          <p v - for="(item, index) in card.items" :key="index">{{ item }}</p>
        </transition - group>
      </div>
    </transition>
  </div>
</template>

<script>
export default {
  data() {
    return {
      isExpanded: false,
      card: {
        title: 'My Card',
        items: ['Item 1', 'Item 2', 'Item 3']
      }
    };
  }
};
</script>

<style>
.card - transition - enter - active,
.card - transition - leave - active {
  transition: height 0.5s ease;
}
.card - transition - enter,
.card - transition - leave - to {
  height: 0;
  overflow: hidden;
}

.inner - transition - enter - active,
.inner - transition - leave - active {
  transition: opacity 0.3s ease;
}
.inner - transition - enter,
.inner - transition - leave - to {
  opacity: 0;
}
</style>

在这个例子中,外层的<transition>控制卡片整体的展开和收起,通过height属性的过渡来实现效果。内层的<transition - group>控制卡片内子元素的显示和隐藏,通过opacity属性的过渡来实现效果。这样就实现了过渡动画的嵌套,使交互效果更加丰富。

动态过渡效果

在某些情况下,我们可能需要根据不同的条件应用不同的过渡效果。Vue允许我们动态地绑定过渡的名称。

比如,我们有一个图片展示组件,当鼠标悬停在图片上时,根据图片的类型应用不同的过渡效果:

<template>
  <div>
    <div v - for="(image, index) in images" :key="index">
      <transition :name="image.type === 'portrait'? 'portrait - transition' : 'landscape - transition'">
        <img :src="image.src" alt="" @mouseenter="hoverImage(index)" @mouseleave="leaveImage(index)">
      </transition>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      images: [
        { src: 'portrait1.jpg', type: 'portrait' },
        { src: 'landscape1.jpg', type: 'landscape' }
      ],
      hoveredIndex: null
    };
  },
  methods: {
    hoverImage(index) {
      this.hoveredIndex = index;
    },
    leaveImage(index) {
      this.hoveredIndex = null;
    }
  }
};
</script>

<style>
.portrait - transition - enter - active,
.portrait - transition - leave - active {
  transition: transform 0.5s rotate(10deg);
}
.portrait - transition - enter,
.portrait - transition - leave - to {
  transform: scale(0.8);
}

.landscape - transition - enter - active,
.landscape - transition - leave - active {
  transition: transform 0.5s scale(1.2);
}
.landscape - transition - enter,
.landscape - transition - leave - to {
  transform: scale(0.9);
}
</style>

在上述代码中,通过:name="image.type === 'portrait'? 'portrait - transition' : 'landscape - transition'"动态地绑定过渡的名称。当鼠标悬停在图片上时,根据图片的类型应用不同的过渡效果,使交互更加灵活。

用JavaScript钩子实现复杂过渡

除了通过CSS类名来定义过渡效果,Vue还提供了JavaScript钩子函数,让我们可以用JavaScript代码来控制过渡过程,实现一些复杂的过渡效果。

过渡的JavaScript钩子

<transition><transition - group>组件都支持以下JavaScript钩子函数:

  • beforeEnter:在元素被插入DOM前调用。
  • enter:在元素被插入DOM后调用,此时过渡效果开始。
  • afterEnter:在过渡效果结束后调用。
  • enterCancelled:在过渡被取消时调用(例如,在过渡过程中元素被移除)。
  • beforeLeave:在元素从DOM中移除前调用。
  • leave:在元素从DOM中移除时调用,此时过渡效果开始。
  • afterLeave:在过渡效果结束且元素被移除DOM后调用。
  • leaveCancelled:仅用于<transition - group>,在离开过渡被取消时调用(例如,快速连续添加和移除元素)。

使用JavaScript钩子实现动画

下面我们通过一个例子来演示如何使用JavaScript钩子实现动画。假设我们有一个元素,当它插入时,会从屏幕底部滑入并伴有淡入效果。

<template>
  <div>
    <button @click="show =!show">Toggle</button>
    <transition
      @beforeEnter="beforeEnter"
      @enter="enter"
      @afterEnter="afterEnter"
    >
      <div v - if="show" class="animated - element">Animated Element</div>
    </transition>
  </div>
</template>

<script>
export default {
  data() {
    return {
      show: false
    };
  },
  methods: {
    beforeEnter(el) {
      el.style.opacity = 0;
      el.style.transform = 'translateY(100%)';
    },
    enter(el, done) {
      gsap.to(el, {
        opacity: 1,
        transform: 'translateY(0)',
        duration: 0.5,
        onComplete: done
      });
    },
    afterEnter(el) {
      console.log('Element has been animated in');
    }
  }
};
</script>

<style>
.animated - element {
  background - color: lightblue;
  padding: 20px;
}
</style>

在上述代码中,我们使用了GSAP(GreenSock Animation Platform)库来创建动画。beforeEnter钩子函数在元素插入前设置初始的样式,enter钩子函数使用GSAP创建动画,并在动画完成时调用done回调函数,afterEnter钩子函数在动画完成后执行一些额外的操作。

<transition - group>中使用JavaScript钩子

<transition - group>中使用JavaScript钩子与<transition>类似,但需要注意每个子元素都要进行相应的操作。

例如,我们有一个列表,当添加或移除列表项时,使用JavaScript钩子实现动画:

<template>
  <div>
    <button @click="addItem">Add Item</button>
    <transition - group
      @beforeEnter="beforeEnter"
      @enter="enter"
      @afterEnter="afterEnter"
      @beforeLeave="beforeLeave"
      @leave="leave"
      @afterLeave="afterLeave"
      tag="ul"
    >
      <li v - for="(item, index) in items" :key="item.id">
        {{ item.text }}
        <button @click="removeItem(index)">Remove</button>
      </li>
    </transition - group>
  </div>
</template>

<script>
import gsap from 'gsap';

export default {
  data() {
    return {
      items: []
    };
  },
  methods: {
    addItem() {
      this.items.push({ id: Date.now(), text: 'New Item' });
    },
    removeItem(index) {
      this.items.splice(index, 1);
    },
    beforeEnter(el) {
      el.style.opacity = 0;
      el.style.transform = 'translateY(30px)';
    },
    enter(el, done) {
      gsap.to(el, {
        opacity: 1,
        transform: 'translateY(0)',
        duration: 0.5,
        onComplete: done
      });
    },
    afterEnter(el) {
      console.log('Item has been animated in');
    },
    beforeLeave(el) {
      el.style.opacity = 1;
      el.style.transform = 'translateY(0)';
    },
    leave(el, done) {
      gsap.to(el, {
        opacity: 0,
        transform: 'translateY(-30px)',
        duration: 0.5,
        onComplete: done
      });
    },
    afterLeave(el) {
      console.log('Item has been animated out');
    }
  }
};
</script>

<style>
li {
  list - style - type: none;
  padding: 10px;
  background - color: lightgreen;
  margin: 5px;
}
</style>

在这个例子中,我们为<transition - group>的每个钩子函数都定义了相应的操作。当添加列表项时,列表项从下方滑入并淡入;当移除列表项时,列表项向上滑出并淡出。

与第三方动画库结合

Vue的过渡动画可以很方便地与第三方动画库结合使用,以实现更丰富和强大的动画效果。

与GSAP结合

我们前面已经展示了一些与GSAP结合的例子。GSAP是一个功能强大的JavaScript动画库,它提供了丰富的动画控制选项。

比如,我们可以使用GSAP创建更复杂的列表过渡动画。假设我们有一个可排序的列表,当列表项排序时,不仅有位置移动的过渡,还有旋转和缩放的动画:

<template>
  <div>
    <button @click="sortItems">Sort Items</button>
    <transition - group
      @beforeEnter="beforeEnter"
      @enter="enter"
      @afterEnter="afterEnter"
      @beforeLeave="beforeLeave"
      @leave="leave"
      @afterLeave="afterLeave"
      tag="ul"
      :move - class="moveClass"
    >
      <li v - for="(item, index) in items" :key="item.id">
        {{ item.text }}
      </li>
    </transition - group>
  </div>
</template>

<script>
import gsap from 'gsap';

export default {
  data() {
    return {
      items: [
        { id: 1, text: 'Item 1' },
        { id: 2, text: 'Item 2' },
        { id: 3, text: 'Item 3' }
      ],
      moveClass: 'list - move'
    };
  },
  methods: {
    sortItems() {
      this.items.sort((a, b) => a.id - b.id);
    },
    beforeEnter(el) {
      el.style.opacity = 0;
      el.style.transform = 'scale(0.8) rotate(-10deg)';
    },
    enter(el, done) {
      gsap.to(el, {
        opacity: 1,
        transform: 'scale(1) rotate(0deg)',
        duration: 0.5,
        onComplete: done
      });
    },
    afterEnter(el) {
      console.log('Item has been animated in');
    },
    beforeLeave(el) {
      el.style.opacity = 1;
      el.style.transform = 'scale(1) rotate(0deg)';
    },
    leave(el, done) {
      gsap.to(el, {
        opacity: 0,
        transform: 'scale(0.6) rotate(10deg)',
        duration: 0.5,
        onComplete: done
      });
    },
    afterLeave(el) {
      console.log('Item has been animated out');
    }
  }
};
</script>

<style>
.list - move {
  transition: transform 0.5s;
}
li {
  list - style - type: none;
  padding: 10px;
  background - color: lightblue;
  margin: 5px;
}
</style>

在这个例子中,我们使用GSAP为列表项的插入、移除和排序过程都添加了复杂的动画效果,使列表的交互更加生动。

与Anime.js结合

Anime.js也是一个流行的JavaScript动画库,它提供了简洁的API来创建各种动画。

例如,我们用Anime.js实现一个元素的渐变过渡动画:

<template>
  <div>
    <button @click="show =!show">Toggle</button>
    <transition
      @beforeEnter="beforeEnter"
      @enter="enter"
      @afterEnter="afterEnter"
    >
      <div v - if="show" class="colored - element"></div>
    </transition>
  </div>
</template>

<script>
import anime from 'animejs';

export default {
  data() {
    return {
      show: false
    };
  },
  methods: {
    beforeEnter(el) {
      el.style.backgroundColor = 'transparent';
    },
    enter(el, done) {
      anime({
        targets: el,
        backgroundColor: ['transparent', 'lightgreen'],
        duration: 1000,
        complete: done
      });
    },
    afterEnter(el) {
      console.log('Element has been animated in');
    }
  }
};
</script>

<style>
.colored - element {
  width: 200px;
  height: 200px;
}
</style>

在上述代码中,我们使用Anime.js在元素插入时实现了从透明到淡绿色的渐变动画。通过与第三方动画库的结合,我们可以突破CSS过渡动画的一些限制,创造出更加独特和吸引人的动画效果。

过渡动画性能优化

在应用过渡动画时,性能优化是非常重要的,尤其是在复杂动画或大量元素的场景下。

避免重排和重绘

重排(reflow)和重绘(repaint)会消耗性能。重排是指浏览器重新计算元素的几何属性(如位置、大小等),重绘是指浏览器重新绘制元素的外观。尽量避免在过渡动画中频繁触发重排和重绘。

例如,在CSS过渡动画中,尽量使用transformopacity属性,因为这两个属性的变化不会触发重排,只触发重绘。相比之下,改变widthheightmargin等属性会触发重排。

<template>
  <div>
    <button @click="show =!show">Toggle</button>
    <transition name="scale - transition">
      <div v - if="show" class="scaled - element"></div>
    </transition>
  </div>
</template>

<script>
export default {
  data() {
    return {
      show: false
    };
  }
};
</script>

<style>
.scale - transition - enter - active,
.scale - transition - leave - active {
  transition: transform 0.5s, opacity 0.5s;
}
.scale - transition - enter,
.scale - transition - leave - to {
  transform: scale(0);
  opacity: 0;
}
.scaled - element {
  width: 200px;
  height: 200px;
  background - color: orange;
}
</style>

在这个例子中,我们通过transformopacity属性来实现元素的缩放和淡入淡出效果,避免了重排的发生,从而提高了性能。

节流和防抖

在一些交互频繁的场景下,如滚动或频繁点击触发过渡动画,可以使用节流(throttle)和防抖(debounce)技术来优化性能。

节流是指在一定时间内只执行一次函数,即使事件被频繁触发。防抖是指在事件触发后等待一定时间,如果在这段时间内事件再次触发,则重新计时,直到最后一次触发后的等待时间结束才执行函数。

例如,我们有一个页面滚动时显示或隐藏导航栏的功能,并且导航栏的显示和隐藏有过渡动画。为了避免频繁触发过渡动画,我们可以使用节流函数:

<template>
  <div>
    <div v - if="showNavbar" class="navbar">
      <a href="#">Home</a>
      <a href="#">About</a>
      <a href="#">Contact</a>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      showNavbar: true
    };
  },
  mounted() {
    window.addEventListener('scroll', this.throttle(this.handleScroll, 200));
  },
  methods: {
    handleScroll() {
      if (window.pageYOffset > 100) {
        this.showNavbar = false;
      } else {
        this.showNavbar = true;
      }
    },
    throttle(func, delay) {
      let timer = null;
      return function() {
        if (!timer) {
          func.apply(this, arguments);
          timer = setTimeout(() => {
            timer = null;
          }, delay);
        }
      };
    }
  }
};
</script>

<style>
.navbar {
  background - color: #333;
  color: white;
  padding: 10px;
  position: fixed;
  top: 0;
  width: 100%;
  transition: opacity 0.5s;
}
.navbar.hidden {
  opacity: 0;
}
</style>

在上述代码中,我们通过throttle函数将handleScroll函数的执行频率限制在每200毫秒一次,避免了因频繁滚动导致过度触发过渡动画,从而提升了性能。

硬件加速

利用CSS的will - change属性可以提示浏览器提前准备好动画所需的资源,从而启用硬件加速。例如,当我们知道一个元素即将发生transform变化时,可以提前设置will - change: transform

<template>
  <div>
    <button @click="show =!show">Toggle</button>
    <transition name="transform - transition">
      <div v - if="show" class="transform - element"></div>
    </transition>
  </div>
</template>

<script>
export default {
  data() {
    return {
      show: false
    };
  }
};
</script>

<style>
.transform - element {
  width: 100px;
  height: 100px;
  background - color: lightblue;
  will - change: transform;
}
.transform - transition - enter - active,
.transform - transition - leave - active {
  transition: transform 0.5s;
}
.transform - transition - enter,
.transform - transition - leave - to {
  transform: translateX(100px);
}
</style>

在这个例子中,我们通过will - change: transform提示浏览器提前准备好transform动画所需的资源,使动画更加流畅,提升了性能。

通过上述的各种高级应用、与第三方库的结合以及性能优化方法,我们可以在Vue项目中创建出丰富、高效且吸引人的过渡动画,为用户带来更好的交互体验。无论是简单的元素过渡还是复杂的列表动画,都能通过合理的技术选型和优化手段实现出色的效果。在实际项目中,我们需要根据具体的需求和场景,灵活运用这些知识,打造出优秀的前端应用。