vueデュアルオープンアニメーションを実現


必要:
前のページが離れるとダブルドアアニメーションがあり、後のページはアニメーションをスケールする効果があります.ダブルドアアニメーションをよく理解していない方は、pptのドアアニメーションを参考にしてください
バージョン反復
第1反復
設計の最初のアイデアは、親要素を作成し、2つのサブ要素を追加し、2つのサブ要素にvueアニメーションを設定することです.
インプリメンテーション
//        parent.vue




//APP.vue


export default {
  data() {
    return {
      name: "parant",
    };
  },
  watch: {
    $route(to, from) {
	//    $route to from ,   name   
      if (from.name === "parent") {
        this.name = "parent";
      } else {
        this.name = "index";
      }
    }
  }
};
<style lang="less">
.parent-enter-active {
  animation: parent-in 3.5s;
  position: absolute;
  top: 0;
}
.parent-leave-active {
  animation: parent-in 3.5s reverse;
}
@keyframes parent-in {
  0% {
    transform: scale(0);
  }
  50% {
    transform: scale(1.5);
  }
  100% {
    transform: scale(1);
  }
}
</style>
</code></pre> 
  <p>   parent.vue -                      App.vue      transition css  。</p> 
  <p><strong>     </strong><br>           ,  App.vue   transition      parent.vue      。</p> 
  <p><strong>    </strong><br>     ,     App.vue  transition  ,  router-view     class    。                  。</p> 
  <h2>     </h2> 
  <pre><code>//App.vue
<template>
  <div id="app">
    <div id="nav">
      <router-link to="/parent">parent</router-link> |
      <router-link to="/about">About</router-link> |
      <router-link to="/multi-element">Multielement</router-link> |
      <router-link to="/list">list</router-link>
    </div>
    <router-view :class="{ view: isView }" ref="view" />
  </div>
</template>
<script>
export default {
  data() {
    return {
      isView: false
    };
  },
  watch: {
    $route(to, from) {
      if (from.name === "parent") {
        this.isView = true;
      } else {
        this.isView = false;
      }
    }
  }
};




classを動的に変更した後、parent.vueコンポーネントと他のコンポーネントに切り替えるアニメーションが有効になります.
存在する問題はparent.vueコンポーネントが他のコンポーネントに切り替える場合、2番目のコンポーネントはルーティングの切り替えがこの時点で既に現れるためparent.vueコンポーネントは、2番目のコンポーネントの下にあります.実際のプロジェクト開発では,我々のparentコンポーネントのレイアウトは実際には全体であり,左右に分かれてレイアウトされることはない.問題を解決することを考えるのは、コンポーネントに位置決め属性を設定して解決することにほかならない.
問題2の解決策:
プロトタイプのpngピクチャを使用して、離れるときにピクチャにアニメーションを設定します.しかし、この案の問題は、デュアルオープンアニメーションを実現するには、左と右を半分ずつ配置する2回の画像を使用する必要があります.これにより、本当に鶏肋大画面のデータがリアルタイムで更新され、parentコンポーネントを離れたときに画像を切り取ることはできません.これにより、このコンポーネントを離れる際のデータのリアルタイム性を保証するだけでなく、canvasの方法でスクリーンショットを左右にそれぞれ配置することができる.
ソリューションは、第2のバージョンの問題を解決するために第2のソリューションを採用します.parentコンポーネントから離れるときにシリーズの動作を行う以上、ナビゲーションガードで処理してもいいです.
3回目の反復
//parent.vue


import echarts from "echarts";
import html2canvas from "html2canvas";


export default {
  data() {
    return {
      transitionName: "slide-left",
      options: {
        xAxis: {
          type: "category",
          data: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
        },
        yAxis: {
          type: "value"
        },
        series: [
          {
            data: [120, 200, 150, 80, 70, 110, 130],
            type: "bar",
            showBackground: true,
            backgroundStyle: {
              color: "rgba(220, 220, 220, 0.8)"
            }
          }
        ]
      }
    };
  },
  //           transition
  beforeRouteLeave(to, from, next) {
    this.getImageData();
    setTimeout(() => {
      next();
    }, 3000);
  },
  methods: {
    getImageData() {
      html2canvas(this.$refs["door"]).then(canvas => {
        this.$refs["door"].style.display = "none";
        //     
        const canvasWidth = +canvas.getAttribute("width");
        const canvasHeight = +canvas.getAttribute("height");

        //         canvas  
        const styleHeight = `${canvasHeight / window.devicePixelRatio}px`;
        const styleWidth = `${canvasWidth / 2 / window.devicePixelRatio}px`;
        const leftCanvas = document.createElement("canvas");
        //     
        leftCanvas.height = canvasHeight;
        leftCanvas.width = canvasWidth / 2;
        leftCanvas.style.height = styleHeight;
        leftCanvas.style.width = styleWidth;
        leftCanvas.style.top = "60px";
        leftCanvas.style.position = "absolute";
        leftCanvas.style.left = "0";
        this.$refs["parent"].appendChild(leftCanvas);
        const rightCanvas = document.createElement("canvas");

        //     
        rightCanvas.height = canvasHeight;
        rightCanvas.width = canvasWidth / 2;
        rightCanvas.style.height = styleHeight;
        rightCanvas.style.width = styleWidth;
        rightCanvas.style.top = "60px";
        rightCanvas.style.position = "absolute";
        rightCanvas.style.right = "0";
        this.$refs["parent"].appendChild(rightCanvas);

        //     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);

        //     
        leftCanvas.style.transform = "rotateY(90deg)";
        leftCanvas.style.transition = "transform 3s linear";
        leftCanvas.style.transformStyle = "preserve-3d";
        leftCanvas.style.transformOrigin = "left center";
        rightCanvas.style.transform = "rotateY(-90deg)";
        rightCanvas.style.transition = "transform 3s linear";
        rightCanvas.style.transformStyle = "preserve-3d";
        rightCanvas.style.transformOrigin = "right center";
      });
    }
  },
  created() {
    this.$nextTick(() => {
      let myChart = echarts.init(this.$refs.bar);
      myChart.setOption(this.options);
    });
  }
};



このバージョンではhtml 2 canvasプラグインを使用してスクリーンショットの機能を実現し、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は変わらない
    存在する問題
  • ナビゲーションガードで一連の操作が完了するとnext()が次のコンポーネントに移行し、前後の2つのアニメーションが一緒に行われない.
    この問題に対して,getImageDataメソッドを同期化することで解決できるかどうか考えられる.答えはだめです.同期の実行結果は現在と同じで、アニメーションの実行が完了してからnext
  • が実行されます.
    next()を先に実行してgetImageDataメソッドを実行しますか?答えは依然として否定的であり、next()を実行した後、parentコンポーネントから離れたことを意味し、parentコンポーネントはこの時点で破棄された.これは、再実行アニメーションがparentコンポーネントを取得できない要素であり、意味のないである.
  • getImageDataメソッドコード冗長性;

  • ソリューション
  • 問題1について、transitionのjsフックを用いることで
  • を実現することが考えられる.
  • 問題2に対して、同じ論理のコードを
  • から抽出する.
    4回目の反復
    //parent.vue
    
    
    import { getImageData } from "@/util/index.js";
    import echarts from "echarts";
    export default {
      data() {
        return {
          transitionName: "slide-left",
          options: {
            xAxis: {
              type: "category",
              data: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
            },
            yAxis: {
              type: "value"
            },
            series: [
              {
                data: [120, 200, 150, 80, 70, 110, 130],
                type: "bar",
                showBackground: true,
                backgroundStyle: {
                  color: "rgba(220, 220, 220, 0.8)"
                }
              }
            ]
          }
        };
      },
      methods: {
        leave(el, done) {
          const basicTemp = this.$refs["door"];
          getImageData(el, done, basicTemp);
        }
      },
      created() {
        this.$nextTick(() => {
          let myChart = echarts.init(this.$refs.bar);
          myChart.setOption(this.options);
        });
      }
    };
    
    
    //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コンポーネントとアニメーションが重なるという問題もあります.

  • ソリューション
  • 問題1について、解決策は以下の通りである.
  • は外層でApp.vueコンポーネントが$routeを傍受する場合from.nameがparentであればrouter-viewにparentというclass名
  • を付ける
  • parentコンポーネントがgetImageDataメソッドを実行する前に、現在のdocumentに存在するclassがparent要素の個数であると判断し、存在する数が1より大きい場合はdone()を直接実行し、returnを実行する.

  • 問題2について、解決策は以下の通りである.
  • は外層でApp.vueコンポーネントが$routeを傍受する場合、to.nameがparentの場合、現在存在するclassがv-leave-toの要素の数であると判断し、存在する場合はその要素を除去する.


  • 5回目の反復
    実践結果
    実践の中で発見して、問題の2を解決する時、問題は同時に解決して、だから私達はただ問題の2の解決策を実現するだけでいいです.
    //App.vue
    
    
    export default {
      data() {
        return {
          isView: false,
          isParent: false
        };
      },
      watch: {
        $route(to, from) {
          if (from.name === "parent") {
            this.isView = true;
            this.isParent = false;
          } else {
            this.isView = false;
            this.isParent = true;
          }
          //         to.name      
          if (to.name === "parent") {
            const transitionDomList = document.getElementsByClassName("v-leave-to");
            const { length } = transitionDomList;
            if (length) {
              transitionDomList[0].parentNode.removeChild(transitionDomList[0]);
            }
          }
        }
      },
      methods: {}
    };
    
    
    

    現存する問題
    parentコンポーネントから他のコンポーネントに切り替えると、作成したcanvasが位置決めされているため、canvasが点滅します.