impress.js 源码剖析

媒介

之前做简历用到了impress.js,就像网页版的preiz,几乎酷炫!贴上我的简历地点:然则没想到昨天师兄内推我说须要看懂impress.js源码,如许才表现你进修研讨的精力。orz。。真是挖个坑坑把本身埋了==。

之前做的时刻只知道impress用transition的data-x,data-y,data-z举行3D挪动。然则昨晚硬着头皮把impress源码读完以后,发明收成照样挺多的。空话就不说了。我们最先理会impress.js之旅

一. impress.js团体的设想头脑是什么?

这里和人人分享一个我个人剖析题目标小技能。(我是前端菜鸟,真正进修时候也不到3个月时候,有说错的处所还请人人多多斧正) 这个技能就是用浏览器自带的检察元素功用。我们翻开impress官网的demo.我们经由历程检察元素,发明每次变化的历程当中

《impress.js 源码剖析》

1.发明一个ppt从左滑动到右侧 对应的translate3d(0px,1500px,0px)变化到translate3d(-1000px,1500px,0px)
申明悉数ppt的变化是经由历程translate3d()这个css3属性完成的。

2.我们翻开index.html页面源码,发明div上只要以下的代码

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

申明我们检察终究结果的div style是js动态增加的。

3.style上有哪些属性呢?

定位: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);

也就是申明这些是impress.js能实线prezi绮丽ppt结果的中心css,也都是css3新增的属性,引荐人人在慕课网上复习一遍 十天通晓CSS3

4.我们在index.html页面中可以看到有data-xdata-ydata-z等属性。而我们平常做impress的时刻就是只转变这些参数来到达变更的目标,在上文中我们经由历程浏览器的调试已发明了这些参数和终究加载在div上的style款式是有关联的。
data-x对应为translateX;data-y对应translateY;data-z对应translateZ

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源码,以后我会把更多发明的题目写在这里,文章会延续更新,和人人一同议论提高进修.

    原文作者:pomelo
    原文地址: https://segmentfault.com/a/1190000003018108
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞