【vue2】实现css动效逐个顺序展示的效果(简陋版)

发布时间 2023-10-12 18:03:00作者: 芝麻小仙女

效果(进入预约里程碑模块后,小人从第一个台阶逐个闪烁出现在当前预约等级之前的台阶并消失,最终停留在当前预约等级的台阶上):

 

虽然很low但是!就是这么设计的!于是在原本静态的代码上稍加了些修改(为什么,为什么,想问天问大地)

首先是台阶部分的代码:

    <div :class="$style.reserveBox">
      <ul :class="$style.reserveList">
        <li
          v-for="(el, index) in list"
          :key="index"
          :class="[
            $style.reserveItem,
            $style[`item_${index}`],
            active === index ? $style.active : '',
            anim === index ? $style.anim : '',
            activeGif === index ? $style.activeGif : '',
          ]"
        >
          <img
            :src="active >= index ? list[index].active : list[index].default"
            :class="$style.reserveItemImg"
            @click="showPop(index)"
          />
        </li>
      </ul>
      <div :class="$style.reserveProgress">
        <img src="./images/progress.png" :class="$style.reserveProgressBg" />
        <div
          :class="$style.reserveProgressLine"
          :style="{ width: progress + '% !important' }"
        ></div>
        <img
          src="./images/line_btn.png"
          :class="$style.progress_30"
          v-show="active >= 2"
        />
        <img
          src="./images/line_btn.png"
          :class="$style.progress_50"
          v-show="active >= 3"
        />
        <img
          src="./images/line_btn.png"
          :class="$style.progress_100"
          v-show="active >= 4"
        />
      </div>
      <div :class="$style.reserveDes"></div>
    </div>

  

每个台阶都是ul中的一个li元素,li中的img元素是柱子的状态图(分别是未达到和已达到的不同效果)(这不重要),动态的小人和对话框是用伪元素写的,样式代码:

.reserveBox {
    position: absolute;
    bottom: 15.8%;
    left: 50%;
    transform: translateX(-50%);
    box-sizing: border-box;
    .reserveDes {
      position: absolute;
      right: 0;
      bottom: -0.76rem;
      width: 3.3rem;
      height: 0.25rem;
      .background-fill("./images/reserve_des.png");
    }
    .reserveList {
      width: 100%;
      display: flex;
      justify-content: center;
      align-items: end;
      .reserveItem {
        position: relative;
        .reserveItemImg {
          width: 100%;
        }
        &.activeGif::before {
          .size(0.85rem, 1.08rem);
          .background-fill("./images/man.png");

          content: "";
          display: block;
          position: absolute;
          top: 0;
          left: 50%;
          transform: translateX(-50%);
          z-index: 1;
          animation: jump 0.9s ease infinite;
        }
        &.activeGif::after {
          .size(1.34rem, 0.82rem);
          .background-fill("./images/talk.png");

          content: "";
          display: block;
          position: absolute;
          top: 0;
          left: 44%;
          z-index: 1;
          animation: shake 1s ease infinite;
        }
        &.anim::before {
          .size(0.85rem, 1.08rem);
          .background-fill("./images/man.png");

          content: "";
          display: block;
          position: absolute;
          top: 0;
          left: 50%;
          transform: translateX(-50%);
          z-index: 1;
          animation: anim 1.5s ease;
          animation-iteration-count: 1;
        }
      }
      .item_0 {
        width: 1.66rem;
        height: 1.746rem;
        &.activeGif::before {
          top: -0.25rem;
        }
        &.anim::before {
          top: -0.25rem;
        }
        &.activeGif::after {
          top: -0.75rem;
        }
      }
      .item_1 {
        width: 2.25rem;
        height: 2.5rem;
        margin-left: -0.12rem;
        &.activeGif::before {
          top: -0.54rem;
        }
        &.anim::before {
          top: -0.54rem;
        }
        &.activeGif::after {
          top: -1.1rem;
        }
      }
      .item_2 {
        width: 2.26rem;
        height: 3.77rem;
        margin-left: -0.01rem;
        &.activeGif::before {
          top: -0.3rem;
        }
        &.anim::before {
          top: -0.3rem;
        }
        &.activeGif::after {
          top: -0.8rem;
        }
      }
      .item_3 {
        width: 2.4rem;
        height: 4.79rem;
        margin-left: -0.08rem;
        &.activeGif::before {
          top: -0.04rem;
        }
        &.anim::before {
          top: -0.04rem;
        }
        &.activeGif::after {
          top: -0.46rem;
        }
      }
      .item_4 {
        width: 2.4rem;
        height: 6.17rem;
        margin-left: -0.01rem;
        &.activeGif::before {
          top: 0.6rem;
        }
        &.anim::before {
          top: 0.6rem;
        }
        &.activeGif::after {
          top: 0.1rem;
        }
      }
    }

    .reserveProgress {
      position: relative;
      width: 10.8rem;
      height: 0.94rem;
      z-index: 1;
      .reserveProgressBg {
        width: 100%;
        height: 100%;
      }
      .reserveProgressLine {
        background-image: url("./images/line_only.png");
        background-position: left top;
        background-size: 8.92rem 0.72rem;
        background-repeat: no-repeat;
        position: absolute;
        display: inline-block;
        left: 0.26rem;
        top: 0.1rem;
        width: 8.92rem;
        height: 0.72rem;
        z-index: 1;
      }
      .progress_30 {
        position: absolute;
        top: 0.054rem;
        left: 41%;
        height: 0.72rem;
        width: auto;
        z-index: 2;
      }
      .progress_50 {
        position: absolute;
        top: 0.054rem;
        left: 63%;
        height: 0.72rem;
        width: auto;
        z-index: 2;
      }
      .progress_100 {
        position: absolute;
        top: 5.4px;
        left: 85%;
        height: 0.72rem;
        width: auto;
        z-index: 2;
      }
    }
  }

  

css3效果代码:

@keyframes shake {
  10% {
    transform: rotate(15deg);
  }
  20% {
    transform: rotate(-10deg);
  }
  30% {
    transform: rotate(5deg);
  }
  40% {
    transform: rotate(-5deg);
  }
  50%,
  100% {
    transform: rotate(0deg);
  }
}
@keyframes jump {
  10% {
    transform: translate(-50%, 2px);
  }
  100% {
    transform: translate(-50%, 0);
  }
}
@keyframes anim {
  0% {
    opacity: 1;
  }
  40% {
    opacity: 0;
  }
  70% {
    opacity: 1;
  }
  100% {
    opacity: 0;
  }
}

  

由于是在原本静态的代码上删删改改,所以只是一个简陋的方案,思路就是在data中配置3个值作为控制:

anim:用来控制当前哪个元素需要闪烁出现并消失
active:当前预约等级
activeGif:当前需要展示小人跳动和对话框的预约等级(也就是动效结束后小人停留在哪个预约阶段的台阶上)
这三个元素的取值均由0开始,对应li遍历的index。初始值-1,不展示任何效果。
同时,还需要定义一个timer初始值伪null。
写一个方法:
    showAni() {
      const _this = this;
      clearTimeout(this.timer);
      let count = 0;
      const length = this.list.filter((_, index) => index < this.active).length;
      function changeContent() {
        if (count < length) {
          _this.anim = count;
          count += 1;
          _this.timer = setTimeout(changeContent, 1500);
        } else {
          _this.anim = -1;
          clearTimeout(this.timer);
          if (count != -1 && count === length) {
            _this.activeGif = _this.active;
          }
        }
      }
      if (count < length) {
        changeContent();
      }
    },

  

这个方法就是实现小人整体动画的核心。

在watch中监听当前是否处在里程碑这一屏:

  watch: {
    activePage(newVal) {
      if (newVal && newVal === 1) {
        this.$nextTick(() => {
          clearTimeout();
          setTimeout(() => {
            this.showAni(newVal);
          }, 500);
        });
      } else {
        this.$nextTick(() => {
          this.anim = -1;
          this.activeGif = -1;
        });
      }
    },
  },

  

这个activePage是父组件传来的,当对应到里程碑的index时,清除定时,并在500ms后执行动画,这个延时是为了保证不在用户刚进入这一屏时就已经开始执行第一次闪烁,视觉效果太仓促。同时,当当前没有展示里程碑这一屏时,设置anim和activeGif为初始值-1,保证当前没有元素处在动效中。

在执行showAni方法时,定义了一个当前count为0,也就是从第一个li的动效开始展示,我们的闪烁动效只需要展示到当前阶段之前就可以停了,所以count+1的操作只执行到当前阶段之前的index,待count+1后等于当前阶段的index,就给当前阶段的元素执行activeGif动画,也就是上下跳动+对话框,动效也就至此完成了。

做完这个之后我突然觉得,是不是做一个小人从第一个逐个跳到当前阶段的台阶还更简单点??可恶啊一个官网能不能少搞点这种花里胡哨的登西!做个gif拿来展示多好!(毕竟做完之后看了眼浏览器内存用量竟高达250mb,真的有点牛)

吐槽完毕,记录一下,方便以后copy嘻嘻