js

 

研究了一番impress.js:

 

个人感觉impress.js的代码量(算上注释一共不到1000行)和难度(没有jQuery的各种black magic= =)都非常适合新手学习,所以写一个总结,帮助大家理解源码。

 

考虑到很多朋友并不喜欢深入细节,下文分为四部分:

 

函数目录:汇总所有函数及其作用,方便查看

事件分析:了解impress.js的运行基础

流程分析:了解impress.js的运行流程

消化代码:具体到行的代码讲解

前三部分是必看的,最后一部分可以根据个人兴趣选择。由于我看代码一向喜欢抠细节,在我看来细节才是最能提高能力并且最有趣的地方,所以我会把每行代码甚至每个变量每个表达式都讲清楚,让你真正的看懂impress.js。

 

由于最后一节会写详细解释,所以前几节中出现的代码我不会详细解释,只会说明大概的功能,方便大家理解。对细节感兴趣的朋友可以看最后一节。

 

函数目录

你可以暂时先跳过这一节或者简单浏览一下,后面看代码的时候可以再来查函数作用。

 

函数名 函数作用

pfx 给css3属性加上当前浏览器可用的前缀

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因子,用于放大和缩小

empty 什么用都没有的函数,当浏览器不支持impress的时候会用到,一点用都没有

impress 主函数,构造impress对象,这是一个全局对象

onStepEnter 用于触发impress:stepenter事件

onStepLeave 用于触发impress:stepleave事件

initStep 初始化给定step

init 主初始化函数

getStep 获取指定step

goto 切换到指定step

prev 切换到上一个step

next 切换到下一个step

throttle 可以延后运行某个函数

事件分析

先明白一个基本概念——step。 step就是impress.js画布中的基本单位,一个step就是一幕,你按一次键盘上的←键或者→键就会切换一次step。

 

事件是impress.js运行的基础,共有三个,分别是impress:init, impress:stepenter和impress:stepleave(下文将省略impress前缀)。

 

init是初始化事件,stepenter是进入下一步事件,stepleave是离开上一步事件。

 

init事件只在初始化时候触发,且只被触发一次,因为impress.js内部有一个initialized变量,初始化之后这个变量会置True,从而保证只初始化一次。 下一节中我们会详细讲解init事件,这里暂时跳过。

 

那么stepenter和stepleave有什么用呢? 假设我们现在处在第1步,我们按一下键盘上的→键就会切换到第2步,这背后impress.js实际上连续触发了两个事件:stepleave和stepenter,两者一先一后连贯起来就构成了我们看到的切换效果。

 

流程分析

impress对象暴露了四个API,分别是goto(), init(), next(), prev()。由于next()和prev()都是基于goto()写的,所以我们下面重点分析goto()和init()。

 

impress.js的运行流程可以分为两大部分——初始化过程以及step切换过程,正好对应init()和goto()。就像上面说到的。初始化过程只会被运行一次,而切换过程可能被触发很多次。

 

我们先来分析重中之重——初始化过程

 

初始化过程分为两个阶段,第一个阶段是运行init()函数,第二个阶段是运行绑定到impress:init上的函数。这两个阶段之间的连接非常简单,就是在init()函数的结尾触发impress:init事件,这样绑定上去的函数就会全部触发了。

 

来看看init()函数都干了什么:

 

 

 1 var init = function () {

 2 if (initialized) { return; }

 3 

 4 // 首先设定viewport

 5 var meta = $("meta[name='viewport']") || document.createElement("meta");

 6 meta.content = "width=device-width, minimum-scale=1, maximum-scale=1, user-scalable=no";

 7 if (meta.parentNode !== document.head) {

 8 meta.name = 'viewport';

 9 document.head.appendChild(meta);

10 }

11 

12 // 初始化config对象

13 var rootData = root.dataset;

14 config = {

15 width: toNumber( rootData.width, defaults.width ),

16 height: toNumber( rootData.height, defaults.height ),

17 maxScale: toNumber( rootData.maxScale, defaults.maxScale ),

18 minScale: toNumber( rootData.minScale, defaults.minScale ),                

19 perspective: toNumber( rootData.perspective, defaults.perspective ),

20 transitionDuration: toNumber( rootData.transitionDuration, defaults.transitionDuration )

21 };

22 

23 // 计算当前scale

24 windowScale = computeWindowScale( config );

25 

26 // 将所有step放到canvas中,再将canvas放到root中。

27 // 注意这里的canvas和css3中的canvas没关系,这里的canvas只是一个div

28 arrayify( root.childNodes ).forEach(function ( el ) {

29 canvas.appendChild( el );

30 });

31 root.appendChild(canvas);

32 

33 // 设置html元素的初始高度

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

35 

36 // 设置body元素的初始属性

37 css(body, {

38 height: "100%",

39 overflow: "hidden"

40 });

41 

42 // 设置根元素的初始属性

43 var rootStyles = {

44 position: "absolute",

45 transformOrigin: "top left",

46 transition: "all 0s ease-in-out",

47 transformStyle: "preserve-3d"

48 };

49 

50 css(root, rootStyles);

51 css(root, {

52 top: "50%",

53 left: "50%",

54 transform: perspective( config.perspective/windowScale ) + scale( windowScale )

55 });

56 css(canvas, rootStyles);

57 

58 // 不能确定impress-disabled类是否存在,所以先remove一下

59 body.classList.remove("impress-disabled");

60 body.classList.add("impress-enabled");

61 

62 // 获取所有step并初始化他们

63 steps = $$(".step", root);

64 steps.forEach( initStep );

65 

66 // 设置canvas的初始状态

67 currentState = {

68 translate: { x: 0, y: 0, z: 0 },

69 rotate: { x: 0, y: 0, z: 0 },

70 scale: 1

71 };

72 

73 initialized = true;

74 

75 // 触发init事件

76 triggerEvent(root, "impress:init", { api: roots[ "impress-root-" + rootId ] });

77 };

 

 

 

init()函数搞清楚了,下面我们分析第二阶段:运行绑定到impress:init事件上的函数。 来看看impress:init事件上面都绑定了什么函数:

 

按 Ctrl+C 复制代码

 

按 Ctrl+C 复制代码

 

 

我们来梳理一遍,初始化过程做了什么事:

 

init()函数中主要初始化画布、step以及impress对象内部用到的一些状态

绑定到impress:init事件上的函数把其他需要绑定的事件都进行了绑定,让impress可以正常工作

接下来我们分析step切换过程,来看看goto函数都干了什么

 

什么?你有点累了?加把劲,一定要看完goto

 

 

 1 var goto = function ( el, duration ) {

 2 

 3 if ( !initialized || !(el = getStep(el)) ) {

 4 //如果没初始化或者el不是一个step就返回

 5 return false;

 6 }

 7 

 8 // 为了避免载入时候浏览器滚动,手动滚动到0,0

 9 window.scrollTo(0, 0);

10 

11 var step = stepsData["impress-" + el.id];

12 

13 // 清理当前活跃step上面的标记

14 if ( activeStep ) {

15 activeStep.classList.remove("active");

16 body.classList.remove("impress-on-" + activeStep.id);

17 }

18 // 给el加活跃标记

19 el.classList.add("active");

20 

21 body.classList.add("impress-on-" + el.id);

22 

23 // 计算canvas相对于当前step的变换参数

24 var target = {

25 rotate: {

26 x: -step.rotate.x,

27 y: -step.rotate.y,

28 z: -step.rotate.z

29 },

30 translate: {

31 x: -step.translate.x,

32 y: -step.translate.y,

33 z: -step.translate.z

34 },

35 scale: 1 / step.scale

36 };

37 

38 // 处理缩放

39 var zoomin = target.scale >= currentState.scale;

40 

41 duration = toNumber(duration, config.transitionDuration);

42 var delay = (duration / 2);

43 

44 // 如果el就是当前活跃step,重新计算scale

45 if (el === activeStep) {

46 windowScale = computeWindowScale(config);

47 }

48 

49 var targetScale = target.scale * windowScale;

50 

51 // 触发stepleave事件

52 if (activeStep && activeStep !== el) {

53 onStepLeave(activeStep);

54 }

55 

56 css(root, {

57 transform: perspective( config.perspective / targetScale ) + scale( targetScale ),

58 transitionDuration: duration + "ms",

59 transitionDelay: (zoomin ? delay : 0) + "ms"

60 });

61 

62 css(canvas, {

63 transform: rotate(target.rotate, true) + translate(target.translate),

64 transitionDuration: duration + "ms",

65 transitionDelay: (zoomin ? 0 : delay) + "ms"

66 });

67 

68 if ( currentState.scale === target.scale ||

69 (currentState.rotate.x === target.rotate.x && currentState.rotate.y === target.rotate.y &&

70 currentState.rotate.z === target.rotate.z && currentState.translate.x === target.translate.x &&

71 currentState.translate.y === target.translate.y && currentState.translate.z === target.translate.z) ) {

72 delay = 0;

73 }

74 

75 // 存储当前状态

76 currentState = target;

77 activeStep = el;

78 

79 // 触发stepenter事件

80 window.clearTimeout(stepEnterTimeout);

81 stepEnterTimeout = window.setTimeout(function() {

82 onStepEnter(activeStep);

83 }, duration + delay);

84 

85 return el;

86 };

 

 

 

好了,下面简单看看prev和next函数:

 

 

 1 var prev = function () {

 2 var prev = steps.indexOf( activeStep ) - 1;

 3 prev = prev >= 0 ? steps[ prev ] : steps[ steps.length-1 ];

 4 

 5 return goto(prev);

 6 };

 7 

 8 var next = function () {

 9 var next = steps.indexOf( activeStep ) + 1;

10 next = next < steps.length ? steps[ next ] : steps[ 0 ];

11 

12 return goto(next);

13 };

诸如此类的例子还有很多!

posted @ 2017-05-13 19:29  婺源  阅读(156)  评论(0编辑  收藏  举报