Skip to content

Vue中v-show与opacity过渡动画的冲突及解决方案 ​

问题描述 ​

在Vue项目中实现抽屉组件时,同时使用v-show和opacity过渡效果会导致过渡动画失效。这是因为v-show使用display: none控制显示隐藏,与opacity的过渡效果产生了冲突。

问题复现 ​

以下是一个简单的抽屉组件示例,展示了这个问题:

html
<template>
  <div id='hwWrapper'>
    <div class='btn' @click='bth'> 抽屉按钮</div>
    <div class='btn2' @click='bth2'> 抽屉按钮2</div>
    <div class='mask' @click="close" :class="{show:isShow}" v-show='isShow'>
      <div class='box' :class="{showBox:isShow}">
        <ul>
          <li>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Libero deleniti aperiam aut aspernatur ipsum excepturi quod dicta et nihil voluptate hic nulla, quidem provident expedita quibusdam consectetur sequi ipsa inventore.</li>
          <li>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Libero deleniti aperiam aut aspernatur ipsum excepturi quod dicta et nihil voluptate hic nulla, quidem provident expedita quibusdam consectetur sequi ipsa inventore.</li>
          <li>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Libero deleniti aperiam aut aspernatur ipsum excepturi quod dicta et nihil voluptate hic nulla, quidem provident expedita quibusdam consectetur sequi ipsa inventore.</li>
        </ul>
      </div>
    </div>
<div class='box2' :class="isShow2?'showBox2':''">
盒子
</div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      subjectList: [{ name: 'jack' }, 555, 'abc', ['dd']],
      isShow: false,
      isShow2:false,
    }
  },
  methods: {
    bth() {
      this.isShow = true
    },
    bth2() {
      this.isShow2 = !this.isShow2
    },
    close() {
      this.isShow = false
    },
  },
}
</script>
<style lang='less'>
.hwWrapper {
  width: 100%;
  height: 100vh;
  position: relative;
  margin: 0;
  padding: 0;
}
.btn {
  width: 150px;
  height: 50px;
  border: 1px solid black;
  position: absolute;
  top: 50%;
  transform: translate(-50%, -50%);
  left: 50%;
}.btn2 {
  width: 150px;
  height: 50px;
  border: 1px solid black;
  position: absolute;
  top: 50%;
  transform: translate(40%, -40%);
  left: 50%;
}
.mask {
  position: fixed;
  top: 0;
  width: 100%;
  height: 100vh;
  background-color: rgba(0, 0, 0, 0.5);
  opacity: 0;
  z-index: 1000;
  transition: all 0.5s;
  .box {
    position: absolute;
    left: -400px;
    top: 0;
    height: 100vh;
    width: 20%;
    background-color: #fff;
    transition: all 0.5s;
    ul {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      overflow: hidden;
    }
  }
  .showBox {
    left: 0;
  }
}
.show { 
  opacity: 0.5;
}
.box2{
  height: 100vh;
  position: absolute;
  width: 20%;
  top: 0;
  left: -500px;
  background-color: pink;
  transition: all 0.5s;
}
.showBox2{
  left: 0;
}
</style>
  1. 效果
  • 很明显,抽屉二生效了,抽屉一并没有生效,我的理解是,v-show,使用的display:none来控制显示与隐藏效果的,说明,该DOM元素之前就是存在的,如果同时用一个变量来控制v-show,和过渡动画,应该会有先后触发的问题,于是我增加了一个变量
  1. 修改代码
html
  <div class='mask' @click="close" :class="{show:isShow}" v-show='isShow3'></div>        
    <script> 
    bth() {
          this.isShow3 = true
        setTimeout(()=>{  //利用延时器来控制执行时机
        this.isShow = true
        },20)
      },
      bth2() {
        this.isShow2 = !this.isShow2
      },
      close() {
        this.isShow = false
        setTimeout(()=>{ //注意两次延时器的执行对象
        this.isShow3 = false
        },200)
      },
      </script>
  1. 效果图

原理解释 ​

  1. 打开抽屉时:

    • 先通过v-show显示元素
    • 短暂延时后触发过渡动画
  2. 关闭抽屉时:

    • 先执行过渡动画
    • 动画结束后才隐藏元素

这样可以确保过渡动画完整执行,实现平滑的视觉效果。

注意事项 ​

  • setTimeout的延时时间要根据实际情况调整
  • 关闭时的延时应该大于等于过渡动画的持续时间
  • 使用这种方案时要注意维护两个状态变量的同步