vue加入购物车动画

4,270 阅读1分钟

先上效果图

屏幕录制2021-05-31 上午11.gif

使用方法

<addShop ref="addShop" />

import addShop from '@/views/components/addShop'

 
// dom:需要移动的dom   
// state :true添加 false移除 

 this.$refs.addschool.init(dom, state)
 

addShop组件

<template>
  <div class="add-school-box">
    <transition @before-enter="beforeEnter"
      @enter="enter"
      @after-enter="afterEnter">
      <div id="cloneBox"
        v-show="isShow"></div>
    </transition>
    <div class="add-school-shop"
      :style="{left: isCollapse ? '250px' : '100px'}">
      <div class="add-school-shop-num">
        {{count.none_publish_count > 999 ? '999+' : count.none_publish_count }}
      </div>
      <svg-icon icon-class="lanzi"
        style="font-size:60px;" />
    </div>
  </div>
</template>

<script>
import addShop from '@/utils/addShop'
import { mapGetters } from 'vuex'
export default {
  data () {
    return {
      addShop: '',
      isShow: false
    }
  },
  computed: {
    ...mapGetters([
      'count'
    ]),
    isCollapse () {
      return this.$store.state.app.sidebar.opened
    }
  },
  methods: {
    //dom 加入的dom state  true添加 false删除
    init (dom, state) {
      this.addCard = new addShop({
        dom,// 需要克隆的dom
        state, // 删除还是新增,
        cloneBoxId: 'cloneBox', // 克隆dom 的容器
        shopClass: 'add-school-shop', // 购物车dom
        shopAnmationClass: 'dom-all-in'
      })
      this.addCard.init()
      this.isShow = true
    },
    // el表示要执行动画的dom元素
    beforeEnter (el) {
      this.addCard.beforeEnter(el)
    },
    // 设置完成动画之后的结束状态
    enter (el, done) {
      this.addCard.enter(el, done)
    },
    // 动画完成之后调用afterEnter函数
    afterEnter (el) {
      this.addCard.afterEnter(el).then(() => {
        this.isShow = false
      })
    }
  },

}
</script>

<style lang="scss">
.add-school-shop {
  position: fixed;
  bottom: 100px;
  left: 200px;
  width: 100px;
  height: 100px;
  transition: all 0.4s;
  i {
    font-size: 100px;
  }
}
.add-school-shop-num {
  width: 45px;
  height: 20px;
  font-size: 14px;
  color: red;
  background: white;
  border-radius: 10px;
  text-align: center;
  line-height: 18px;
  border: 1px solid red;
  position: absolute;
  right: 25px;
  top: 6px;
}
.dom-all-in {
  animation: mymove 1s infinite;
  animation-iteration-count: 1;
}
@keyframes mymove {
  0% {
    transform: scale(1); /*开始为原始大小*/
  }
  50% {
    transform: scale(1.2);
  }
  100% {
    transform: scale(1);
  }
}
</style>

使用到的addShop方法

export default class addShop {
  constructor({ dom, shopClass, state, cloneBoxId, shopAnmationClass }) {
    // 点击的dom
    this.dom = dom;
    // 购物车dom
    this.shopDom = "";
    this.shopClass = shopClass;
    this.shopAnmationClass = shopAnmationClass;
    // 判断是添加还是删除
    this.state = state;
    // 复制的dom
    this.cloneDiv = "";
    // 装复制dom的容器
    this.cloneDomBox = "";
    this.cloneBoxId = cloneBoxId;
    this.x = 0;
    this.y = 0;
    // 点击dom的信息
    this.width = 0;
    this.height = 0;
    // 购物车的位置信息
    this.shopX = 0;
    this.shopY = 0;
  }
  init() {
    this.shopDom = document.getElementsByClassName(this.shopClass)[0]; // 购物车dom
    this.cloneDomBox = document.getElementById(this.cloneBoxId); // 克隆dom 的容器
    // 初始化被点击dom的信息
    const oRectDom = this.dom.getBoundingClientRect();
    const { width, height, x, y } = oRectDom;
    this.width = width;
    this.height = height;
    this.x = x;
    this.y = y;
    // 克隆点击的dom
    this.cloneDiv = this.dom.cloneNode(true);
    // 将克隆的元素添加到盒子里
    this.cloneDomBox.appendChild(this.cloneDiv);
    // 初始化购物车的位置
    const oRectShop = this.shopDom.getBoundingClientRect();
    this.shopX = oRectShop.x;
    this.shopY = oRectShop.y;
  }
  beforeEnter(el) {
    let top = this.state ? this.y : this.shopY;
    let left = this.state ? this.x : this.shopX;
    // 动画入场前,设置元素开始动画的起始位置
    el.style.width = this.width + "px";
    el.style.height = this.height + "px";
    el.style.position = "fixed";
    el.style.left = left + "px";
    el.style.top = top + "px";
    // 动画过程禁止滚动
    document.getElementsByTagName("html")[0].style.overflowY = "hidden";
    if (!this.state) {
      el.style.transform = `scale(0.1)`;
      el.style.transformOrigin = "0 0";
      this.shopDom.classList.add(this.shopAnmationClass);
    }
  }
  // 设置完成动画之后的结束状态
  enter(el, done) {
    el.offsetWidth; // 强制刷新动画
    // 结束位置
    let enterX = this.x - this.shopX;
    let enterY = this.shopY - this.y;
    // 这里要考虑enterX,enterY为负数的情况
    let x = this.state ? (enterX > 0 ? '-' + enterX : Math.abs(enterX)) : enterX;
    let y = this.state ? enterY : (enterY > 0 ? '-' + enterY : Math.abs(enterX));
    let scaleNum = this.state ? "0.1" : "1";
    el.style.transform = `translate(${x}px,${y}px) scale(${scaleNum})`;
    el.style.transformOrigin = "0 0";
    el.style.transition = "all 1s ease";
    // 执行done函数,完成下面钩子函数
    setTimeout(() => {
      done();
    }, 800);
  }
  // 动画完成之后调用afterEnter函数
  afterEnter(el) {
    this.shopDom.classList[this.state ? "add" : "remove"](
      this.shopAnmationClass
    );
    return new Promise(resolve => {
      setTimeout(() => {
        this.cloneDomBox.style = {
          position: "fixed",
          "z-index": 1000,
          background: "white"
        };
        this.cloneDiv && this.cloneDiv.remove();
        this.shopDom.classList.remove(this.shopAnmationClass);
        // 动画结束允许滚动
        document.getElementsByTagName("html")[0].style.overflowY = "auto";
        resolve();
      }, 500);
    });
  }
}