Vue.js 源碼分析(二十八) 高級應用 transition組件 詳解

来源:https://www.cnblogs.com/greatdesert/archive/2019/07/29/11261494.html
-Advertisement-
Play Games

transition組件可以給任何元素和組件添加進入/離開過渡,但只能給單個組件實行過渡效果(多個元素可以用transition-group組件,下一節再講),調用該內置組件時,可以傳入如下特性: name 用於自動生成CSS過渡類名 例如:name:'fade'將自動拓展為.fade-enter, ...


transition組件可以給任何元素和組件添加進入/離開過渡,但只能給單個組件實行過渡效果(多個元素可以用transition-group組件,下一節再講),調用該內置組件時,可以傳入如下特性:

    name         用於自動生成CSS過渡類名        例如:name:'fade'將自動拓展為.fade-enter,.fade-enter-active等
    appear      是否在初始渲染時使用過渡         預設為false
    css            是否使用 CSS 過渡類。             預設為 true。如果設置為 false,將只通過組件事件觸發註冊的 JavaScript 鉤子。
    mode        控制離開/進入的過渡時間序列    可設為"out-in"或"in-out";預設同時生效
    type          指定過渡事件類型                      可設為transition或animation,用於偵聽過渡何時結束;可以不設置,Vue內部會自動檢測出持續時間長的為過渡事件類型
    duration    定製進入和移出的持續時間        以後用到再看

type表示transition對應的css過渡類里的動畫樣式既可以用transition也可以用animation來設置動畫(可以同時使用),然後我們可以用指定,Vue內部會自動判斷出來

除了以上特性,我們還可以設置如下特性,用於指定過渡的樣式:

    appear-class             初次渲染時的起始狀態    ;如果不存在則等於enter-class屬性                 這三個屬性得設置了appear為true才生效
    appear-to-class         初次渲染時的結束狀態    如果不存在則等於enter-to-class    屬性
    appear-active-class   初次渲染時的過渡           如果不存在則等於enter-active-class屬性
    enter-class                進入過渡時的起始狀態  
    enter-to-class            進入過渡時的結束狀態 
    enter-active-class     進入過渡時的過渡          
    leave-class               離開過渡時的起始狀態    
    leave-to-class          離開過渡時的結束狀態    
    leave-active-class    離開過渡時的過渡           

對於後面六個class,內部會根據name拼湊出對應的class來,例如一個transition的name="fade",拼湊出來的class名預設分別為:fade-enter、fade-enter-to、fade-enter-active、fade-leave、fade-leave-to、fade-leave-active

除此之外還可以在transition中綁定自定義事件,所有的自定義事件如下

    before-appear          初次渲染,過渡前的事件                         未指定則等於before-enter事件    
    appear                     初次渲染開始時的事件                             未指定則等於enter事件 
    after-appear             初次渲染,過渡結束後的事件                  未指定則等於enter-cancelled事件    
    appear-cancelled     初次渲染未完成又觸發隱藏條件而重新渲染時的事件,未指定則等於enter-cancelled事件    
    before-enter             進入過渡前的事件
    enter                        進入過渡時的事件                            
    after-enter               進入過渡結束後的事件
    enter-cancelled       進入過渡未完成又觸發隱藏條件而重新渲染時的事件    
    before-leave           離開過渡前的事件
    leave                      離開時的事件                                   
    after-leave              離開後的事件
    leave-cancelled      進入過渡未完成又觸發隱藏條件而重新渲染時的事件   

transition相關的所有屬性應該都列出來了(應該比官網還多吧,我是從源碼里找到的),我們舉一個例子,如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
</head>
    <style>
        .fade-enter,.fade-leave-to{background: #f00;transform:translateY(20px);}         /*.fade-enter和.fade-leave-to一般寫在一起,當然也可以分開*/
        .fade-enter-active,.fade-leave-to{transition:all 1s linear 500ms;}
    </style>
<body>
    <div id="app">
        <button @click="show=!show">按鈕</button>
        <transition name="fade" :appear="true" @before-enter="beforeenter"  @enter="enter" @after-enter="afterenter" @before-leave="beforeleave" @leave="leave" @after-leave="afterleave">
            <p v-if="show">你好</p>
        </transition>        
    </div>
    <script>
        Vue.config.productionTip=false;
        Vue.config.devtools=false;
        var app = new Vue({
            el:"#app",
            data:{
                show:true
            },
            methods:{
                beforeenter(){console.log('進入過渡前的事件')},
                enter(){console.log('進入過渡開始的事件')},
                afterenter(){console.log('進入過渡結束的事件')},
                beforeleave(){console.log('離開過渡前的事件')},
                leave(){console.log('離開過渡開始的事件')},
                afterleave(){console.log('離開過渡結束的事件')}
            }
        })
    </script>    
</body>
</html>

我們調用transition組件時設置了appear特性為true,這樣頁面載入時動畫就開始了,如下:

控制台輸出如下:

文字從透明到漸顯,同時位移也發生了變化,我們點擊按鈕時又會觸發隱藏,繼續點擊,又會顯示,這是因為我們在transition的子節點里使用了v-show指令。

對於transition組件來說,在下列情形中,可以給任何元素和組件添加進入/離開過渡:

    條件渲染 (使用 v-if)
    條件展示 (使用 v-show)
    動態組件
    組件根節點

 

用原生DOM模擬transition組件


 Vue內部是通過修改transition子節點的class名來實現動畫效果的,我們用原生DOM實現一下這個效果,如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
    <style>
        .trans{transition:all 2s linear;}
        .start{transform:translatex(100px);opacity: 0;}
    </style>
<body>
    <div id="con">
        <button name="show">顯式</button>
        <button name="hide">隱藏</button>
    </div>
    <p id="p">Hello Vue!</p>
    <script>
        var p = document.getElementsByTagName('p')[0];
        document.getElementById('con').addEventListener('click',function(event){
            switch(event.target.name){
                case "show":
                    p.style.display="block";
                    p.classList.add('trans');
                    p.classList.remove('start')        
                    break;
                case "hide":                    
                    p.classList.add('trans')
                    p.classList.add('start')    
                    break;
            }
        })
    </script>
</body>
</html>

渲染的頁面如下:

我們點擊隱藏按鈕後,Hello Vue!就逐漸隱藏了,然後我們查看DOM,如下:

這個DOM元素還是存在的,只是opacity這個透明度的屬性為0,Vue內部的transition隱藏後是一個註釋節點,這是怎麼實現的,我們能不能也實現出來,當然可以。

Vue內部通過window.getComputedStyle()這個API介面獲取到了transition或animation的結束時間,然後通過綁定transitionend或animationend事件(對應不同的動畫結束事件)執行一個回調函數,該回調函數會將DOM節點設置為一個註釋節點(隱藏節點的情況下)

我們繼續改一下代碼,如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
    <style>
        .trans{transition:all 2s linear;}
        .start{transform:translatex(100px);opacity: 0;}
    </style>
<body>
    <div id="con">
        <button name="show">顯式</button>
        <button name="hide">隱藏</button>
    </div>
    <p id="p">Hello Vue!</p>
    <script>
        var p             = document.getElementsByTagName('p')[0],
            tid           = null,
            pDom          = null,
            CommentDom    = document.createComment("");
        document.getElementById('con').addEventListener('click',function(event){
            switch(event.target.name){
                case "show":                           
                    CommentDom.parentNode.replaceChild(p,CommentDom)  
                    setTimeout(function(){p.classList.remove('start')},10)                            
                    ModifyClass(1)
                    break;
                case "hide":                    
                    p.classList.add('trans')
                    p.classList.add('start')    
                    ModifyClass(0)
                    break;
            }
        })
         
        function ModifyClass(n){    //s=1:顯式過程 s=0:隱藏過程
            var styles = window.getComputedStyle(p);
            var transitionDelays = styles['transitionDelay'].split(', ');                    //transition的延遲時間        ;比如:["0.5s"]
            var transitionDurations = styles['transitionDuration'].split(', ');              //transition的動畫持續時間    ;比如:"1s"
            var transitionTimeout = getTimeout(transitionDelays, transitionDurations);      //transition的獲取動畫結束的時間,單位ms,比如:1500
            tid && clearTimeout(tid);
            tid=setTimeout(function(){
                 if(n){                   //如果是顯式
                    p.classList.remove('trans')
                    p.removeAttribute('class');
                }else{                    //如果是隱藏
                    p.parentNode.replaceChild(CommentDom,p);
                }
            },transitionTimeout)       
        }

        function getTimeout(delays, durations) {                                      //從Vue源碼里拷貝出來的代碼的,獲取動畫完成的總時間,返回ms格式  
            while (delays.length < durations.length) {
                delays = delays.concat(delays);
            }
            return Math.max.apply(null, durations.map(function (d, i) {
                return toMs(d) + toMs(delays[i])
            }))
        }
        function toMs(s) {
            return Number(s.slice(0, -1)) * 1000
        }
    </script>    

</body>
</html>

 這樣當動畫結束後改DOM就真的隱藏了,變為了一個註釋節點,如下:

當再次點擊時,就會顯式出來,如下:

完美,這裡遇到個問題,就是當顯式的時候直接設置class不會有動畫,應該是和重繪有關的吧m所以用了一個setTImeout()來實現。

Vue也就是把這些原生DOM操作進行了封裝,我們現在來看Vue的源碼

 

 源碼分析


 transition是Vue的內置組件,在執行initGlobalAPI()時extend保存到Vue.options.component(第5052行),我們可以列印看看,如下:

Transition組件的格式為:

var Transition = {    //第8012行  transition組件的定義
  name: 'transition',
  props: transitionProps,
  abstract: true,

  render: function render (h) {
      /**/
  }
}

也就是說transition組件定義了自己的render函數。

以上面的第一個例子為例,執行到transition組件時會執行到它的render函數,如下:

  render: function render (h) {         //第8217行  transition組件的render函數,並沒有template模板,初始化或更新都會執行到這裡
    var this$1 = this;

    var children = this.$slots.default;
    if (!children) {
      return
    }

    // filter out text nodes (possible whitespaces)
    children = children.filter(function (c) { return c.tag || isAsyncPlaceholder(c); });
    /* istanbul ignore if */
    if (!children.length) {                     //獲取子節點
      return                                      //如果沒有子節點,則直接返回
    } 

    // warn multiple elements
    if ("development" !== 'production' && children.length > 1) {      //如果過濾掉空白節點後,children還是不存在,則直接返回
      warn(
        '<transition> can only be used on a single element. Use ' +
        '<transition-group> for lists.',
        this.$parent
      );
    }

    var mode = this.mode;                                             //獲取模式

    // warn invalid mode
    if ("development" !== 'production' &&
      mode && mode !== 'in-out' && mode !== 'out-in'                    //檢查mode是否規範只能是in-out或out-in
    ) {
      warn(
        'invalid <transition> mode: ' + mode,
        this.$parent
      );
    }

    var rawChild = children[0];                                   //獲取所有子節點

    // if this is a component root node and the component's
    // parent container node also has transition, skip.
    if (hasParentTransition(this.$vnode)) {                         //如果當前的transition是根組件,且調用該組件的時候外層又套了一個transition
      return rawChild                                                   //則直接返回rawChild
    }

    // apply transition data to child
    // use getRealChild() to ignore abstract components e.g. keep-alive
    var child = getRealChild(rawChild);
    /* istanbul ignore if */
    if (!child) {
      return rawChild
    }

    if (this._leaving) {
      return placeholder(h, rawChild)
    }

    // ensure a key that is unique to the vnode type and to this transition
    // component instance. This key will be used to remove pending leaving nodes
    // during entering.
    var id = "__transition-" + (this._uid) + "-";                       //拼湊key,比如:__transition-1   ;this._uid是transition組件實例的_uid,在_init初始化時定義的
    child.key = child.key == null
      ? child.isComment
        ? id + 'comment'
        : id + child.tag
      : isPrimitive(child.key)
        ? (String(child.key).indexOf(id) === 0 ? child.key : id + child.key)
        : child.key;

    var data = (child.data || (child.data = {})).transition = extractTransitionData(this);        //獲取組件上的props和自定義事件,保存到child.data.transition里
    var oldRawChild = this._vnode;
    var oldChild = getRealChild(oldRawChild);

    // mark v-show
    // so that the transition module can hand over the control to the directive
    if (child.data.directives && child.data.directives.some(function (d) { return d.name === 'show'; })) {      //如果child帶有一個v-show指令
      child.data.show = true;                                                                                       //則給child.data新增一個show屬性,值為true
    }

    if (
      oldChild &&
      oldChild.data &&
      !isSameChild(child, oldChild) &&
      !isAsyncPlaceholder(oldChild) &&
      // #6687 component root is a comment node
      !(oldChild.componentInstance && oldChild.componentInstance._vnode.isComment)            //這裡是更新組件,且子組件改變之後的邏輯
    ) {
      // replace old child transition data with fresh one
      // important for dynamic transitions!
      var oldData = oldChild.data.transition = extend({}, data);
      // handle transition mode
      if (mode === 'out-in') {
        // return placeholder node and queue update when leave finishes
        this._leaving = true;
        mergeVNodeHook(oldData, 'afterLeave', function () {
          this$1._leaving = false;
          this$1.$forceUpdate();
        });
        return placeholder(h, rawChild)
      } else if (mode === 'in-out') {
        if (isAsyncPlaceholder(child)) {
          return oldRawChild
        }
        var delayedLeave;
        var performLeave = function () { delayedLeave(); };
        mergeVNodeHook(data, 'afterEnter', performLeave);
        mergeVNodeHook(data, 'enterCancelled', performLeave);
        mergeVNodeHook(oldData, 'delayLeave', function (leave) { delayedLeave = leave; });
      }
    }

    return rawChild                                                                                         //返回DOM節點
  } 

extractTransitionData()可以獲取transition組件上的特性等,如下:

function extractTransitionData (comp) {   //第8176行  提取在transition組件上定義的data
  var data = {};
  var options = comp.$options;                //獲取comp組件的$options欄位
  // props
  for (var key in options.propsData) {        //獲取propsData
    data[key] = comp[key];                        //並保存到data裡面 ,例如:{appear: true,name: "fade"}
  }
  // events.
  // extract listeners and pass them directly to the transition methods
  var listeners = options._parentListeners;   //獲取在transition組件上定義的自定義事件
  for (var key$1 in listeners) {              //遍歷自定義事件
    data[camelize(key$1)] = listeners[key$1];   //也保存到data上面
  }
  return data
}

例子里的transition組件執行到返回的值如下:

也就是說transition返回的是子節點VNode,它只是在子節點VNode的data屬性上增加了transition組件相關的信息

對於v-show指令來說,初次綁定時會執行bind函數(可以看https://www.cnblogs.com/greatdesert/p/11157771.html),如下:

var show = {        //第8082行 
  bind: function bind (el, ref, vnode) {      //初次綁定時執行
    var value = ref.value;

    vnode = locateNode(vnode);
    var transition$$1 = vnode.data && vnode.data.transition;    //嘗試獲取transition,如果v-show綁定的標簽外層套了一個transition則會把信息保存到該對象里
    var originalDisplay = el.__vOriginalDisplay =   
      el.style.display === 'none' ? '' : el.style.display;      //保存最初的display屬性
    if (value && transition$$1) {                               //如果transition$$1存在的話
      vnode.data.show = true;
      enter(vnode, function () {                                   //執行enter函數,參數2是個函數,是動畫結束的回掉函數
        el.style.display = originalDisplay;
      });
    } else {
      el.style.display = value ? originalDisplay : 'none';
    }
  },

最後會執行enter函數,enter函數也就是動畫的入口函數,比較長,如下:

function enter (vnode, toggleDisplay) {             //第7599行  進入動畫的回調函數
  var el = vnode.elm;

  // call leave callback now
  if (isDef(el._leaveCb)) {                                 //如果el._leaveCb存在,則執行它,離開過渡未執行完時如果重新觸發了進入過渡,則執行到這裡
    el._leaveCb.cancelled = true;
    el._leaveCb();
  }

  var data = resolveTransition(vnode.data.transition);      //調用resolveTransition解析vnode.data.transition里的css屬性
  if (isUndef(data)) {
    return
  }

  /* istanbul ignore if */    
  if (isDef(el._enterCb) || el.nodeType !== 1) {      
    return
  }

  var css = data.css;                                       //是否使用 CSS 過渡類
  var type = data.type;                                     //過濾類型,可以是transition或animation   可以為空,Vue內部會自動檢測
  var enterClass = data.enterClass;                         //獲取進入過渡是的起始、結束和過渡時的狀態對應的class
  var enterToClass = data.enterToClass;
  var enterActiveClass = data.enterActiveClass;
  var appearClass = data.appearCla

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • position屬性 position屬性指定用於元素的定位方法的類型(靜態,相對,固定,絕對或粘性)。 有五種不同的值: static relative fixed absolute sticky 然後使用top,bottom,left和right屬性定位元素。但是,除非首先設置position屬 ...
  • 在人人手裡都有手機都能上網。在這樣一個信息傳播迅速的時代,怎麼樣讓別人足不出戶就能知道你的企業。 企業的建站是商戶之間網路的橋梁,我們可以通過上海網站建設公司創建一個電子商務的平臺為自己公司做一個網頁版的宣傳。 網站的標識,其目的市場可是一個年齡組或特定的連接鏈,因此採用一個高端大氣又趣味十足的能吸 ...
  • 預編譯四部曲 1.創建AO對象 2.找形參和變數聲明,將變數和形參名作為AO屬性名,值為undefined 3.將實參和形參統一 4.在函數體裡面找函數聲明,值賦予函數體 function fn(a){ console.log(a) //function a(){} var a = 123; con ...
  • description jQuery,顧名思義,也就是JavaScript和Query(查詢),即輔助JavaScript開發的庫。jQuery是一個快速、簡潔的JavaScript框架,是繼Prototype之後又一個優秀的JavaScript代碼庫(或JavaScript框架)。jQuery設計 ...
  • 一 函數 1、字元串函數 s.tolowerCase( ); -- 變小寫 s.toupperCase( ); -- 變大寫 s.substr( 2 , 8 ); -- 截取 從索引2開始截取,截取8個字元長度 s.split( ); -- 指定字元分割,返回一個數組。 括弧里放一個字元,按照這個字 ...
  • 第一種:瀏覽器支持的轉換方式(Firefox,chrome,opera,safari,ie)等瀏覽器: JSON.parse(jsonstr); //可以將json字元串轉換成json對象 JSON.stringify(jsonobj); //可以將json對象轉換成json對符串   第 ...
  • 比如上面這個頁面, 分為三層:divOne是第外層,divTwo中間層,hr_three是最裡層; 他們都有各自的click事件,最裡層a標簽還有href屬性。 運行頁面,點擊“點擊我”,會依次彈出:我是最裡層 >我是中間層 >我是最外層 >然後再鏈接到百度. 這就是事件冒泡,本來我只點擊ID為hr ...
  • 前言 最近在做後臺管理項目,採用的 "vue element admin" ,上傳圖片是一個很常用的功能,也遇到了很多問題,剛好趁此機會做一些總結。 初步總結下會提到的問題,目錄如下: 1. el upload 自定義上傳方法 2. 圖片上傳到七牛雲 3. 圖片壓縮後再上傳(壓縮使用 "lrz" ) ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...