研究了一番impress.js:css
我的感受impress.js的代碼量(算上註釋一共不到1000行)和難度(沒有jQuery的各類black magic= =)都很是適合新手學習,因此寫一個總結,幫助你們理解源碼。html
考慮到不少朋友並不喜歡深刻細節,下文分爲四部分:css3
函數目錄:彙總全部函數及其做用,方便查看canvas
事件分析:瞭解impress.js的運行基礎api
流程分析:瞭解impress.js的運行流程瀏覽器
消化代碼:具體到行的代碼講解app
前三部分是必看的,最後一部分能夠根據我的興趣選擇。因爲我看代碼一貫喜歡摳細節,在我看來細節纔是最能提升能力而且最有趣的地方,因此我會把每行代碼甚至每一個變量每一個表達式都講清楚,讓你真正的看懂impress.js。函數
因爲最後一節會寫詳細解釋,因此前幾節中出現的代碼我不會詳細解釋,只會說明大概的功能,方便你們理解。對細節感興趣的朋友能夠看最後一節。學習
函數目錄scala
你能夠暫時先跳過這一節或者簡單瀏覽一下,後面看代碼的時候能夠再來查函數做用。
函數名 函數做用
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 };
諸如此類的例子還有不少!