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源码,从此我会把更多发现的问题写在这里,文章会持续更新,和你们一块儿讨论进步学习.

相关文章
相关标签/搜索