impress.js 源碼分析

前言

以前作簡歷用到了impress.js,就像網頁版的preiz,簡直酷炫!貼上個人簡歷地址:但是沒想到昨天師兄內推我說須要看懂impress.js源碼,這樣才能體現你學習鑽研的精神。orz。。真是挖個坑坑把本身埋了==。javascript

以前作的時候只知道impress用transition的data-x,data-y,data-z進行3D移動。可是昨晚硬着頭皮把impress源碼讀完以後,發現收穫仍是挺多的。廢話就不說了。咱們開始剖析impress.js之旅css

一. impress.js總體的設計思想是什麼?

這裏和你們分享一個我我的分析問題的小技巧。(我是前端菜鳥,真正學習時間也不到3個月時間,有說錯的地方還請你們多多指正) 這個技巧就是用瀏覽器自帶的審查元素功能。咱們打開impress官網的demo.咱們經過審查元素,發現每次變化的過程當中html

圖片描述

1.發現一個ppt從左滑動到右邊 對應的translate3d(0px,1500px,0px)變化到translate3d(-1000px,1500px,0px)
說明整個ppt的變化是經過translate3d()這個css3屬性完成的。前端

2.咱們打開index.html頁面源碼,發現div上只有以下的代碼html5

<div id="bored" class="step slide" data-x="-1000" data-y="-1500" style="background-color:#ddd;">
.....
</div>

說明咱們查看最終效果的div style是js動態添加的。java

3.style上有哪些屬性呢?jquery

定位:position: absolute;top: 50%; left: 50%;
變化圓心:transform-origin: left top 0px;
移動translate:transition: all 0ms ease-in-out 0ms;
-webkit-transition: all 0ms ease-in-out 0ms;
3d變化樣式:transform-style: preserve-3d; //子元素保留其3d位置
變化的透視樣式:transform: perspective(14797.6878612717px)//能夠近大 遠小的效果
縮放:scale(0.067578125);css3

也就是說明這些是impress.js能實線prezi絢麗ppt效果的核心css,也都是css3新增的屬性,推薦你們在慕課網上溫習一遍 十天精通CSS3git

4.咱們在index.html頁面中能夠看到有data-xdata-ydata-z等屬性。而咱們通常作impress的時候就是隻改變這些參數來達到變換的目的,在上文中咱們經過瀏覽器的調試已經發現了這些參數和最終加載在div上的style樣式是有關係的。
data-x對應爲translateX;data-y對應translateY;data-z對應translateZgithub

5.咱們能夠很」膚淺「得出結論:impress的水平移動是改變了translateX座標,垂直移動是改變translateY座標,而忽然變小又變大的絢麗效果是改變translateZ的座標。而這些轉化樣式,事件監聽是經過js來實現的。

二. impress.js具體的技術實現?

1.源碼閱讀從data-* 屬性入手

這個是html5新增api。目的是能夠用戶自定義數據,定義好的數據又是怎樣被拿出來的呢,經過dataset()的方法。咱們來看一段源碼(line307)

var data = el.dataset, //el是經過getElememtById()得到的元素
            step = {   //定義了一個step對象。裏面有4個屬性,分別是我們上文分析過的impress變化相關的css樣式。
                translate: {
                    x: toNumber(data.x),
                    y: toNumber(data.y),
                    z: toNumber(data.z)
                },//toNumber()是一個函數。將參數轉換成數字,若是沒法轉換返回默認值
                rotate: {
                    x: toNumber(data.rotateX),
                    y: toNumber(data.rotateY),
                    z: toNumber(data.rotateZ || data.rotate)
                },
                scale: toNumber(data.scale, 1),
                el: el
            };

你們能夠在瀏覽器的console處調試這段代碼,你會發現 元素的dataset 獲得的是一個數組,咱們即可以依次取出x,y,z值。這就是爲何咱們能夠經過寫data-x最終可以影響translateX,最終可以獲得水平方向上移動的效果

2.源碼的總體代碼架構

看到第一個data屬性案例,你們確定以爲源碼這麼簡單~確定開始從github/impress.js上clone下代碼,準備本身去解讀源碼。哈哈哈,若是你和我同樣以前沒有任何閱讀js源碼的經驗的話,估計你會被虐哭的,由於源碼第一行pfx()函數就夠你研究半天的。因此咱們必須理清一下思路,一個好的程序必定是有它的書寫規範和架構。
首先源碼line1-line174都在寫通用函數。若是你直接研究的話會感受莫名奇妙
那麼咱們大體來看一下這些通用函數都是什麼功能

pfx()-----它經過檢測瀏覽器給css3屬性加上當前瀏覽器可用的前綴,這樣就不用人工手寫'Webkit" ,"Moz" 'O' ,'ms' .'Khtml'等瀏覽器前綴
arrayify() ----將Array-Like對象轉換成Array對象
css()------將指定屬性應用到指定元素上
toNumber()----- 將參數轉換成數字,若是沒法轉換返回默認值
byId()-------經過id獲取元素
$()---- 返回知足選擇器的第一個元素
$$()------- 返回知足選擇器的全部元素
triggerEvent()------- 在指定元素上觸發指定事件
translate()------- 將translate對象轉換成css使用的字符串
rotate()--------- 將rotate對象轉換成css使用的字符串
scale()------- 將scale對象轉換成css使用的字符串
perspective()------ 將perspective對象轉換成css使用的字符串
getElementFromHash()---- 根據hash來獲取元素,hash就是URL中形如#step1的東西
computeWindowScale()---- 根據當前窗口尺寸計算scale。用於放大和縮小

這裏必須給impress.js的做者點個贊!文檔寫的太仔細了,不少時候你看不懂代碼,可是看看註釋就懂了~
很顯然咱們在閱讀源碼之初不必逐字逐句去分析這些通用函數的語法和做用,由於通用函數就是工具。咱們真正應該關心的是impress的主體架構。

從源碼的223line起就是impress主函數和5大api

API:  goto(), init(), next(), prev(),initStep()

主函數: var impress = window.impress = function ( rootId ) {......}

咱們能夠再看index.html,它先引入impress.js,而後調用init()這個api函數

<script src="js/impress.js"></script>
<script>
impress().init();
</script>

那麼咱們接下來重點來研究這個init()函數

var init = function () {
    if (initialized) { return; }//初始值initialized=false;
    //第一步咱們簡歷viewport來支持手機設備
    var meta = $("meta[name='viewport']") || document.createElement("meta");
                      //$是一個函數,本人以爲就是借鑑了jquery的源碼。line104
                      //  var $ = function ( selector, context ) {
                      //context = context || document;
                      //return context.querySelector(selector);
                      //};

    meta.content = "width=device-width, minimum-scale=1, maximum-scale=1, user-scalable=no";
    if (meta.parentNode !== document.head) {//判斷meta的parentNode節點是否是<head>
        meta.name = 'viewport';   //若是不是head標籤,就js添加一個meta標籤
        document.head.appendChild(meta);
    }

                                   //初始化配置root
                                   //  243line  : rootId = rootId || "impress";
                                    //269line:var root = byId( rootId );
        var rootData = root.dataset;//獲取到初始化的root數據,即id=「impress」的div標籤裏的內容
        config = {
            width: toNumber( rootData.width, defaults.width ),
            height: toNumber( rootData.height, defaults.height ),
            maxScale: toNumber( rootData.maxScale, defaults.maxScale ),
            minScale: toNumber( rootData.minScale, defaults.minScale ),                
            perspective: toNumber( rootData.perspective, defaults.perspective ),
            transitionDuration: toNumber( rootData.transitionDuration, defaults.transitionDuration )
        };

        windowScale = computeWindowScale( config );

        // wrap steps with "canvas" element
        arrayify( root.childNodes ).forEach(function ( el ) {
            canvas.appendChild( el );
        });
        root.appendChild(canvas);
    //這裏出現了arrayify函數,在69行。'arraify'函數可以把類數組對象轉化爲真正數組,
    //slice() 方法可從已有的數組中返回選定的元素。   
                     //   var arrayify = function ( a ) {
                    //return [].slice.call( a );
                    // };

//forEach是javascript的數組循環遍歷函數。
// canvas的來源:line270 var canvas = document.createElement("div");
//咱們在瀏覽器中調試發現 root.childNodes是一個數組,是包裹在

裏面 的全部div塊
//由於咱們的html結構是這樣的<div id="impress"><div id="step-1"></div><div id="step-2"></div>..... </div>
//而後利用arrayify函數把 root.childNodes轉化爲小數組。再利用forEach()函數把數組遍歷一遍,動態在root節點後面插入div,這個是dom操做
//仍是無法理解的同窗,請在瀏覽器中一行一行的代碼敲入,觀察效果 ==。js太須要一個能夠斷點調試的ide了!!!

document.documentElement.style.height = "100%";

        css(body, {
            height: "100%",
            overflow: "hidden"
        });

        var rootStyles = {
            position: "absolute",
            transformOrigin: "top left",
            transition: "all 0s ease-in-out",
            transformStyle: "preserve-3d"
        };

        css(root, rootStyles);
        css(root, {
            top: "50%",
            left: "50%",
            transform: perspective( config.perspective/windowScale ) + scale( windowScale )
        });
        css(canvas, rootStyles);

        body.classList.remove("impress-disabled");
        body.classList.add("impress-enabled");

        // get and init steps
        steps = $$(".step", root);
                 // $$函數以下
                /*  var $$ = function ( selector, context ) {
                            context = context || document;
                            return arrayify( context.querySelectorAll(selector) );
                        };*/
        steps.forEach( initStep );
        //找到每個class爲」step「的元素,返回root(id=「impress」)的數組
        //forEach遍歷每個數組,給每一個div用initstep()函數初始化。
        //即咱們一開始分析的那個函數。主要是把data-*自定義的數據得到,附上transtion樣式。
        // set a default initial state of the canvas
        currentState = {
            translate: { x: 0, y: 0, z: 0 },
            rotate:    { x: 0, y: 0, z: 0 },
            scale:     1
        };
     //當前的狀態。位移爲0,旋轉爲0,縮放爲1.   
        initialized = true;
        //初始化爲true,即完成初始化
        triggerEvent(root, "impress:init", { api: roots[ "impress-root-" + rootId ] });
    };

//咱們遇了triggerEvent()函數,這個是自定義事件監聽函數,源碼以下

/*var triggerEvent = function (el, eventName, detail) {
                var event = document.createEvent("CustomEvent");
                event.initCustomEvent(eventName, true, true, detail);
                el.dispatchEvent(event);
            };*/

//document.createEvent("CustomEvent");是自定義事件函數
// 而後初始化事件對象event.initCustomEvent(eventName, true, true, detail);
//其中,第一個參數爲要處理的事件名
//第二個參數爲代表事件是否冒泡
//第三個參數爲代表是否能夠取消事件的默認行爲
//第四個參數爲細節參數
//(參考https://developer.mozilla.org/en-US/docs/Web/API/Document/createEvent
//經過dispatchEvent()方法來將事件應用到特定的dom節點上,以便其支持該事件。這個dispatchEvent()事件,支持一個參數,就是你建立的event對象。

總結:初始化過程分爲兩個階段,第一個階段是運行init()函數,第二個階段是運行綁定到impress:init上的函數。這兩個階段之間的鏈接很是簡單,就是在init()函數的結尾觸發impress:init事件,這樣綁定上去的函數就會所有觸發了。而這個事件是用戶自定義的dom3事件

3.事件對象綁定與監聽
init()函數搞清楚了,下面咱們分析第二階段:運行綁定到impress:init事件上的函數。咱們 來看看impress:init事件綁定了什麼函數:

root.addEventListener("impress:init", function(){
            // STEP CLASSES
            steps.forEach(function (step) {
                step.classList.add("future");
            });
   //做者所有用的都是原生js,真是給大神跪了.         
            root.addEventListener("impress:stepenter", function (event) {
                event.target.classList.remove("past");
 //利用html5 classList屬性對class類增刪改查了,不再須要jquery的addclass()等二次封裝的函數了.
                event.target.classList.remove("future");
                event.target.classList.add("present");
            }, false);

            root.addEventListener("impress:stepleave", function (event) {
                event.target.classList.remove("present");
                event.target.classList.add("past");
            }, false);

        }, false);

init是初始化事件,stepenter是進入下一步事件,stepleave是離開上一步事件。具體的函數源碼以下

var onStepEnter = function (step) {
            if (lastEntered !== step) {
                triggerEvent(step, "impress:stepenter");
                lastEntered = step;
            }
        };
  var onStepLeave = function (step) {
            if (lastEntered === step) {
                triggerEvent(step, "impress:stepleave");
                lastEntered = null;
            }
        };

一個step就是一個ppt,你按一次鍵盤上的left鍵或者right鍵就會切換一次step。它也把鍵盤事件綁定了,源碼以下

document.addEventListener("keyup", function ( event ) {...}
document.addEventListener("keydown", function ( event ) {...}
document.addEventListener("click", function ( event ) {...}
window.addEventListener("resize", throttle(function () {...}
document.addEventListener("touchstart", function ( event ) {...}

分析到這裏其實也差很少可以搞懂源碼了,只是有點思惟混亂,畢竟初次讀源碼,光找各類通用函數都塊找哭了.
咱們把這一節介紹的init函數和自定義事件的源碼函數理一理,便於你們分析

impress 主函數,構造impress對象,這是一個全局對象
onStepEnter 用於觸發impress:stepenter事件
onStepLeave 用於觸發impress:stepleave事件
initStep 初始化給定step init 主初始化函數
getStep 獲取指定step goto 切換到指定step
prev 切換到上一個step next 切換到下一個step

三. impress.js源碼分析的總結

我會把impress.js源碼逐字解讀放在github上,稍後更新,就不在這裏囉嗦了.我是前端菜鳥,但願你們一塊兒來分析討論.共享代碼和思想.
關於總結,與其說是總結,不如說是個人一點心得體會吧.

咱們也許用原生js作過單獨的全屏滾動,
咱們也許重寫過鼠標鍵盤事件,
咱們也許也作過自定義事件的綁定.
咱們也許用過data-*的自定義數據
咱們也許用過css3 transform和translate3d 作過動畫
咱們也許....

有不少技術咱們單獨實現都很簡單,可是把他們綜合在一塊兒就發現好難,如何保證命名空間不污染,變量做用域,如何寫出兼容性的js和css代碼,如何處理好各類代碼細節,這都是咱們須要反思的地方.impress.js是我第一次閱讀的js源碼,從此我會把更多發現的問題寫在這裏,文章會持續更新,和你們一塊兒討論進步學習.

相關文章
相關標籤/搜索