vue实现双开门动画

需求:

前一个页面离开时有双开门动画,后一个页面有缩放动画的效果。
不太理解双开门动画的小伙伴,可以参考ppt的门动画

版本迭代

第一次迭代

设计
第一想法就是通过创建一个父元素,并为其添加两个子元素,分别给这两个子元素设置vue动画来实现。

实现

//双开门动画组件 parent.vue




//APP.vue




通过动态改变class之后,parent.vue组件以及切换至其他组件的动画都可以生效了。

存在的问题
当从parent.vue组件切换至其他组件时,第二个组件由于路由的切换这个时候已经出现了,所以parent.vue组件会位于第二个组件的下方。
在实际的项目开发中,我们的parent组件的布局其实是一个整体,不会分为左右两侧分开布局的。
思考
解决问题一无非就是给组件设置定位属性来解决;

解决问题二的方案:

使用原型的png图片,离开时给图片设置动画。但是这种方案的问题在于:
要实现双开门动画,我们就需要使用两次该图片,左边和右边分别放置一半,这样实在有点鸡肋
大屏的数据是实时更新的,图片并不能做到这一点
在离开parent组件时截图。这样不仅可以保证离开这个组件时的数据的实时性还可以通过canvas的方法将截图分别放置在左右两侧。

解决方案
采用第二种方案解决第二版本的问题。既然要在离开parent组件离开时进行系列的动作,我们不妨在导航守卫中的进行处理。

第三次迭代

//parent.vue



在这个版本中我们使用html2Canvas插件实现截图的功能,并结合canvas的getImageData以及putImageData方法完成canvas的截取以及存放。具体的方法介绍可参照https://www.w3school.com.cn/html5/canvas_getimagedata.asp

在创建canvas时值得注意的是:

  • 因为设备像素的原因,在设置canvas的属性时,不仅要设置canvas的height以及width,还要设置canvas.style.height/width。如果不设置canvas.style.height/width,就会导致截图前和创建的canvas的像素比不一致的问题。canvas.style.height/width应该是通过canvas的height/width
    除以 window.devicePixelRatio得来的。

注:App.vue不改变

存在的问题

  1. 在导航守卫中完成一系列操作后才会执行next()进入下一个组件,这就导致前后两个动画并不是一起进行的;
    • 针对这个问题,大家可能想到通过把getImageData方法变为同步是否可以解决呢?答案是不行,同步的执行结果和目前是一样的,动画执行完成之后才会执行next
    • 先执行next()后执行getImageData方法呢?答案依然是否定的,当执行完next()之后,意味着已经离开parent组件了,parent组件此时已经销毁了,这是再执行动画是获取不到parent组件的元素且是无意义的
  2. getImageData方法代码冗余;

解决方案

  1. 针对问题一,可以考虑通过使用transition的js钩子来实现一下
  2. 针对问题二,将相同逻辑的代码抽离出来

第四次迭代

//parent.vue



//util/index.js
import html2canvas from "html2canvas";
function creatTempCanvas(el, height, width, leftFlag) {
  const styleHeight = `${height / window.devicePixelRatio}px`;
  const styleWidth = `${width / window.devicePixelRatio}px`;
  const tempCanvas = document.createElement("canvas");
  // 设置样式
  tempCanvas.height = height;
  tempCanvas.width = width;
  tempCanvas.style.height = styleHeight;
  tempCanvas.style.width = styleWidth;
  tempCanvas.style.top = "60px";
  tempCanvas.style.position = "absolute";
  if (leftFlag) {
    tempCanvas.style.left = "0";
  } else {
    tempCanvas.style.right = "0";
  }
  el.appendChild(tempCanvas);
  return tempCanvas;
}
function setTransition(dom, rotateAngle, time, origin) {
  dom.style.transform = `rotateY(${rotateAngle})`;
  dom.style.transition = `transform ${time} linear`;
  dom.style.transformStyle = "preserve-3d";
  dom.style.transformOrigin = `${origin}`;
}
export function getImageData(el, done, basicTemp) {
 html2canvas(basicTemp).then(canvas => {
    el.childNodes[0].style.display = "none";
    // 获取宽高
    const canvasWidth = +canvas.getAttribute("width");
    const canvasHeight = +canvas.getAttribute("height");


    // 先创建两个临时的canvas画板
    const leftCanvas = creatTempCanvas(el, canvasHeight, canvasWidth / 2, true);
    const rightCanvas = creatTempCanvas(el, canvasHeight, canvasWidth / 2);

    // 获取原始canvas的左半边和右半边
    const ctx = canvas.getContext("2d"); //获取上下文
    const leftImgData = ctx.getImageData(0, 0, canvasWidth / 2, canvasHeight);
    const rightImgData = ctx.getImageData(
      canvasWidth / 2,
      0,
      canvasWidth / 2,
      canvasHeight
    );

    // 获取创建的canvas的上下文
    const leftCtx = leftCanvas.getContext("2d");
    const rightCtx = rightCanvas.getContext("2d");

    // 左半边和右半边分别存在临时的canvas中
    leftCtx.putImageData(leftImgData, 0, 0);
    rightCtx.putImageData(rightImgData, 0, 0);
    // 设置动画
    setTransition(leftCanvas, "-90deg", "3s", "left center");
    setTransition(rightCanvas, "90deg", "3s", "right center");
    
    setTimeout(() => {
      done();
    }, 5e3);
  });
}

上述版本将离开时执行的动画放在index.js文件中,并将创建canvas以及给canvas设置动画的方法抽离出来,避免代码冗余。

值得注意的是:

  • done()执行代表已经离开parent组件,那么parent组件的动画就无法实现。所以需要让done()异步执行。且执行等待的时间需要比动画执行的时间稍长,否则会出现动画还未执行完canvas已经消失了。

存在的问题

  • 当快速在parent组件和其他组件之间切换时,由于parent组件的动画时间较长,之前的动画还未执行完成,这样就会出现重叠问题。
  • 当快速从parent组件切换至其他组件又切回parent组件时,也会存在动画未完成,导致parent组件和动画重叠的问题。

解决方案

  • 针对问题一,解决方案如下:
    • 在外层App.vue组件监听$route时,如果from.name为parent,就给router-view加上parent这样一个class名称
    • 在parent组件离开执行getImageData方法时先判断当前document中存在的class为parent元素个数,如果存在的数量大于1,就直接执行done()并return。
  • 针对问题二,解决方案如下:
    • 在外层App.vue组件监听$route时,在满足to.name为parent的情况下,判断当前存在的class为v-leave-to的元素的数量,如果存在的话,就移除该元素。

第五次迭代

实践结果

在实践中发现,当解决问题二时,问题一同时也会解决掉,所以我们只需要实现问题二的解决方案即可。

//App.vue



目前存在的问题

当从parent组件切换至其他组件时,由于对创建的canvas进行了定位,所有会出现canvas闪烁。

你可能感兴趣的:(js,vue,动画)