likes
comments
collection
share

Vue针对不定高元素的高度变化动画解决方案

作者站长头像
站长
· 阅读数 78

在vue项目中,简单的过度动画基本上用<Transition>组件应付即可,当遇到未指明高度的元素时,基于css transition: height .5s ease便失去了效果,大部分解决方案都是去改变max-height来触发transition,例如:

// App.vue
<script setup lang="ts">
import { computed, onMounted, ref } from "vue";
const isShow = ref(true);
function showClick() {
  isShow.value = !isShow.value;
}
</script>

<template>
  <div>
    <Transition>
      <div v-show="isShow" ref="target">
        <a href="https://vitejs.dev" target="_blank">
          <img src="/vite.svg" class="logo" alt="Vite logo" />
        </a>
        <a href="https://vuejs.org/" target="_blank">
          <img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
        </a>
      </div>
    </Transition>
    <button @click="showClick">click</button>
  </div>
</template>

<style scoped>
.logo {
  height: 6em;
  padding: 1.5em;
  will-change: filter;
  transition: filter 300ms;
}
.logo:hover {
  filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
  filter: drop-shadow(0 0 2em #42b883aa);
}

.v-enter-active,
.v-leave-active {
  max-height: 200px;
  transition: height 0.5s ease;
  overflow: hidden;
}

.v-enter-from,
.v-leave-to {
  max-height: 0;
}
</style>

当元素展开时,效果其实还可以,与使用height的动画完全一致,但在元素收起时能明显感觉到一定的延迟,当然这个max-heighht的值与真实height的差越大,延迟感越明显,不够优雅。所以我们要寻找其他的办法来减小这个差距

到了这里会发现只用css是不能完美实现展开收起效果滴,聪明的你可能会想到“用js来指定目标元素的高度不就好了”,让我们将上面的代码改一改:

// App.vue
<script setup lang="ts">
import { computed, onMounted, ref } from "vue";
import HelloWorld from "./components/HelloWorld.vue";
 
const isShow = ref(true);
function showClick() {
  isShow.value = !isShow.value;
}

const target = ref();
const targetHeight = computed(() => target.value.scrollHeight + "px");

</script>

<template>
 ...
</template>

<style scoped>
...

.v-enter-active,
.v-leave-active {
  height: v-bind(targetHeight);
  transition: height 0.5s ease;
  overflow: hidden;
}

.v-enter-from,
.v-leave-to {
  height: 0;
}
</style>

用ref获取dom的scrollHeight并通过v-bind绑定给css属性,这样我们的展开收起就像吃了德芙一样丝滑,但是我们的项目中需要用到这种简单的展开收起动效地方往往很多,总不能每个地方都这样写一遍吧!因此我们需要<Transition>进行简单的封装,废话不多说直接上代码:

// myTransition.vue
<template>
  <transition v-bind="hooks">
    <slot></slot>
  </transition>
</template>

<script setup lang="ts">

const hieghtTransitionStyle = `height .5s ease`;

const hooks = {
  css: false,
  // 在元素被插入到 DOM 之前被调用
  // 用这个来设置元素的 "enter-from" 状态
  onBeforeEnter(el) {
    el.style.transition = hieghtTransitionStyle;
    el.style.height = 0;
  },

  // 在元素被插入到 DOM 之后的下一帧被调用
  // 用这个来开始进入动画
  onEnter(el, done) {
    // 调用回调函数 done 表示过渡结束
    // 如果与 CSS 结合使用,则这个回调是可选参数
    // 表示动画开始之后的样式,这里,可以设置小球完成动画之后的,结束状态
    el.style.height = el.scrollHeight + "px";
    el.style.overflow = "hidden";
  },

  // 当进入过渡完成时调用。
  onAfterEnter(el) {
    el.style.height = el.scrollHeight + "px";
  },
  // 在 leave 钩子之前调用
  // 大多数时候,你应该只会用到 leave 钩子
  onBeforeLeave(el) {
    el.style.height = el.scrollHeight + "px";
    el.style.overflow = "hidden";
  },

  // 在离开过渡开始时调用
  // 用这个来开始离开动画
  onLeave(el, done) {
    // 调用回调函数 done 表示过渡结束
    // 如果与 CSS 结合使用,则这个回调是可选参数
    if (el.scrollHeight !== 0) {
      el.style.transition = hieghtTransitionStyle;
      el.style.height = 0;
    }
  },

  // 在离开过渡完成、
  // 且元素已从 DOM 中移除时调用
  onAfterLeave(el) {
    el.style.transition = "";
  }
};
</script>

在其他组件中使用:

// App.vue
<script setup lang="ts">
import { computed, onMounted, ref } from "vue";
import myTransactionVue from "./components/myTransition.vue";
const isShow = ref(true);
function showClick() {
  isShow.value = !isShow.value;
}
</script>

<template>
  <div>
    <myTransactionVue>
      <div v-show="isShow" ref="target">
        <a href="https://vitejs.dev" target="_blank">
          <img src="/vite.svg" class="logo" alt="Vite logo" />
        </a>
        <a href="https://vuejs.org/" target="_blank">
          <img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
        </a>
      </div>
    </myTransactionVue>
    <button @click="showClick">click</button>
  </div>
</template>

<style scoped>
.logo {
  height: 6em;
  padding: 1.5em;
  will-change: filter;
  transition: filter 300ms;
}
.logo:hover {
  filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
  filter: drop-shadow(0 0 2em #42b883aa);
}
</style>

vue的<transition>组件提供了一系列钩子,用于自定义动效。贴一份官方文档链接关于这些钩子的描述。这时候问题就来了,这些钩子函数与css过渡class之间的关系如下图所示:

Vue针对不定高元素的高度变化动画解决方案 这样在以后封装新的动画组件时,只需将原先class中的样式处理搬到对应的钩子函数中即可。

转载自:https://juejin.cn/post/7233298261481013308
评论
请登录