impress.js 源码解析系列

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了impress.js 源码解析系列相关的知识,希望对你有一定的参考价值。

技术分享
  1 /**
  2  * [impress description]  定义 impress 函数
  3  * @param  {[type]} rootId [description]
  4  * @return {[type]}        [description]
  5  */
  6 var impress = window.impress = function( rootId ) {
  7         // 如果浏览器不支持,则退出
  8         if ( !impressSupported ) {
  9             return {
 10                 init: empty,
 11                 goto: empty,
 12                 prev: empty,
 13                 next: empty
 14             };
 15         }
 16         // rootId , 应该是整个画布所在元素的 id ,可以传入参数,如果不传入参数,则默认取得 impress 这个 id
 17         // 所以在 html 文件中,画布所在的元素应该设置为 impress 
 18         rootId = rootId || "impress";
 19 
 20         // If given root is already initialized just return the API
 21         if ( roots[ "impress-root-" + rootId ] ) {
 22             return roots[ "impress-root-" + rootId ];
 23         }
 24 
 25         // 变量 stepsData ,保存每一幕的数据
 26         var stepsData = {};
 27 
 28         // 变量 activeStep ,保存当前显示的这一幕的元素
 29         var activeStep = null;
 30 
 31         // 变量 currentState ,保存演示的当前位置 (position, rotation and scale) 
 32         var currentState = null;
 33 
 34         // 变量 steps ,保存 step 组成的数组
 35         var steps = null;
 36 
 37         // 变量 config ,初始化配置
 38         var config = null;
 39 
 40         // 变量 windowScale ,保存浏览器窗口的缩放因子,初始为 null
 41         var windowScale = null;
 42 
 43         // 变量 root ,保存画布的根元素,一般是 id 为 impress 的那个元素
 44         var root = byId( rootId );
 45         // 定义 canvas 
 46         var canvas = document.createElement( "div" );
 47         // 变量 initialized ,初始化为 false
 48         var initialized = false;
 49 
 50         // STEP EVENTS
 51         //
 52         // There are currently two step events triggered by impress.js
 53         // `impress:stepenter` is triggered when the step is shown on the
 54         // screen (the transition from the previous one is finished) and
 55         // `impress:stepleave` is triggered when the step is left (the
 56         // transition to next step just starts).
 57 
 58         // 变量 lastEntered ,初始化为 null
 59         var lastEntered = null;
 60 
 61         // `onStepEnter` is called whenever the step element is entered
 62         // but the event is triggered only if the step is different than
 63         // last entered step.
 64         
 65         /**
 66          * [onStepEnter description]  给每一幕进入时绑定事件
 67          * @param  {[type]} step 事件挂载的元素
 68          * @return {[type]}      
 69          */
 70         var onStepEnter = function( step ) {
 71             // 由于 lastEntered = null ,当然满足这个条件,所以继续执行
 72             if ( lastEntered !== step ) {
 73                 // 绑定事件,事件名为 impress:stepenter , 挂载在 step 元素上
 74                 triggerEvent( step, "impress:stepenter" );
 75                 // 更新 lastEntered = step
 76                 lastEntered = step;
 77             }
 78         };
 79 
 80         /**
 81          * [onStepLeave description]  给每一幕离开时绑定事件,只会在刚进入的这一幕离开时触发
 82          * @param  {[type]} step 事件挂载的元素
 83          * @return {[type]}      
 84          */
 85         var onStepLeave = function( step ) {
 86             // 由于幕布进入时 lastEntered 已经更新,所以满足这个条件,继续执行
 87             if ( lastEntered === step ) {
 88                 // 绑定事件,事件名为 impress:stepleave ,挂载在 step 元素上
 89                 triggerEvent( step, "impress:stepleave" );
 90                 // 更新 lastEntered = null
 91                 lastEntered = null;
 92             }
 93         };
 94 
 95         // `initStep` initializes given step element by reading data from its
 96         // data attributes and setting correct styles.
 97         /**
 98          * [initStep description]  通过元素上的 dataset 属性,定义元素的样式
 99          * @param  {[type]} el  元素
100          * @param  {[type]} idx [description]
101          * @return {[type]}     [description]
102          */
103         var initStep = function( el, idx ) {
104             // 变量 data ,保存元素的 dataset 属性,是一个对象,包含一些名值对
105             var data = el.dataset,
106                 // 变量 step ,保存当前幕布的一些样式属性
107                 step = {
108                     translate: {
109                         x: toNumber( data.x ),
110                         y: toNumber( data.y ),
111                         z: toNumber( data.z )
112                     },
113                     rotate: {
114                         x: toNumber( data.rotateX ),
115                         y: toNumber( data.rotateY ),
116                         z: toNumber( data.rotateZ || data.rotate )
117                     },
118                     scale: toNumber( data.scale, 1 ),
119                     el: el
120                 };
121 
122 
123             // 如果 step 元素没有 id 属性,则为它们添加 id
124             if ( !el.id ) {
125                 el.id = "step-" + ( idx + 1 );
126             }
127             // stepsData 保存着每一幕的数据,找到给定 id 的这一幕,赋值为 step
128             stepsData[ "impress-" + el.id ] = step;
129 
130             // 定义 css 样式
131             css( el, {
132                 position: "absolute",
133                 transform: "translate(-50%,-50%)" +
134                            translate( step.translate ) +
135                            rotate( step.rotate ) +
136                            scale( step.scale ),
137                 transformStyle: "preserve-3d"
138             } );
139         };
140 
141 
142 
143 
144 
145 
146         /**
147          * [init description]  初始化
148          * @return {[type]} [description]
149          */
150         var init = function() {
151             // 如果已经初始化,则直接返回
152             if ( initialized ) { return; }
153 
154             // 针对移动设备 
155             // 作者有提到 ipad ,如果不指定这些属性,显示可能有问题
156             // 如果 DOM 中存在 name="viewport" 的 meta 元素,则取得这个元素,定义它的 content 属性
157             // 如果 DOM 中不存在 这个元素,则创建一个 meta 元素,定义它的 content 属性,定义它的 name 属性,然后把这个元素添加到 head 下
158             var meta = $( "meta[name=‘viewport‘]" ) || document.createElement( "meta" );
159             meta.content = "width=device-width, minimum-scale=1, maximum-scale=1, user-scalable=no";
160             if ( meta.parentNode !== document.head ) {
161                 meta.name = "viewport";
162                 document.head.appendChild( meta );
163             }
164 
165             // root 元素是 id 为 impress 的元素
166             var rootData = root.dataset;
167             // 定义 config 对象
168             // 这里的 toNumber函数传入了两个参数,其意思是,如果第一个参数能够被转换为数值,则将其转换为数值并返回,
169             // 如果第一个参数不能转换为数值,而返回第二个参数,第二个参数的相关信息已经提前设定好
170             config = {
171                 width: toNumber( rootData.width, defaults.width ),
172                 height: toNumber( rootData.height, defaults.height ),
173                 maxScale: toNumber( rootData.maxScale, defaults.maxScale ),
174                 minScale: toNumber( rootData.minScale, defaults.minScale ),
175                 perspective: toNumber( rootData.perspective, defaults.perspective ),
176                 transitionDuration: toNumber(
177                   rootData.transitionDuration, defaults.transitionDuration
178                 )
179             };
180 
181             // 调用 computeWindowScale() 函数计算缩放因子,保存在变量 windowScale 中
182             windowScale = computeWindowScale( config );
183 
184             // 把所有的 step 都放在 canvas 中,把 canvas 放在 impress 元素中
185             arrayify( root.childNodes ).forEach( function( el ) {
186                 canvas.appendChild( el );
187             } );
188             root.appendChild( canvas );
189 
190             // 定义初始样式
191             document.documentElement.style.height = "100%";
192 
193             css( body, {
194                 height: "100%",
195                 overflow: "hidden"
196             } );
197 
198             var rootStyles = {
199                 position: "absolute",
200                 transformOrigin: "top left",
201                 transition: "all 0s ease-in-out",
202                 transformStyle: "preserve-3d"
203             };
204 
205             css( root, rootStyles );
206             css( root, {
207                 top: "50%",
208                 left: "50%",
209                 transform: perspective( config.perspective / windowScale ) + scale( windowScale )
210             } );
211             css( canvas, rootStyles );
212 
213             body.classList.remove( "impress-disabled" );
214             body.classList.add( "impress-enabled" );
215 
216             // 取得 impress 下的所有 step ,并初始化
217             steps = $$( ".step", root );
218             steps.forEach( initStep );
219 
220             // 设置 canvas 的初始状态
221             currentState = {
222                 translate: { x: 0, y: 0, z: 0 },
223                 rotate:    { x: 0, y: 0, z: 0 },
224                 scale:     1
225             };
226 
227             // 初始化之后,更新 initialized 的值
228             initialized = true;
229 
230             // 给 root 添加事件
231             triggerEvent( root, "impress:init", { api: roots[ "impress-root-" + rootId ] } );
232         };
233 
234         /**
235          * [getStep description]  获取某个 step
236          * @param  step 可以是数值,也可以是 step 的 id
237          * @return      如果能够获取某个 step ,则返回这个 step
238          */
239         var getStep = function( step ) {
240             if ( typeof step === "number" ) {
241                 step = step < 0 ? steps[ steps.length + step ] : steps[ step ];
242             } else if ( typeof step === "string" ) {
243                 step = byId( step );
244             }
245             return ( step && step.id && stepsData[ "impress-" + step.id ] ) ? step : null;
246         };
247 
248         // "impress:stepenter" 事件的超时记录
249         var stepEnterTimeout = null;
250 
251         // `goto` API function that moves to step given with `el` parameter
252         // (by index, id or element), with a transition `duration` optionally
253         // given as second parameter.
254         var goto = function( el, duration ) {
255 
256             if ( !initialized || !( el = getStep( el ) ) ) {
257 
258                 // 如果还没有初始化,或者 el 不是一个 step ,则返回 false
259                 return false;
260             }
261 
262             // Sometimes it‘s possible to trigger focus on first link with some keyboard action.
263             // Browser in such a case tries to scroll the page to make this element visible
264             // (even that body overflow is set to hidden) and it breaks our careful positioning.
265             //
266             // So, as a lousy (and lazy) workaround we will make the page scroll back to the top
267             // whenever slide is selected
268             //
269             // If you are reading this and know any better way to handle it, I‘ll be glad to hear
270             // about it!
271             window.scrollTo( 0, 0 );
272 
273             var step = stepsData[ "impress-" + el.id ];
274 
275             if ( activeStep ) {
276                 activeStep.classList.remove( "active" );
277                 body.classList.remove( "impress-on-" + activeStep.id );
278             }
279             el.classList.add( "active" );
280 
281             body.classList.add( "impress-on-" + el.id );
282 
283             // Compute target state of the canvas based on given step
284             var target = {
285                 rotate: {
286                     x: -step.rotate.x,
287                     y: -step.rotate.y,
288                     z: -step.rotate.z
289                 },
290                 translate: {
291                     x: -step.translate.x,
292                     y: -step.translate.y,
293                     z: -step.translate.z
294                 },
295                 scale: 1 / step.scale
296             };
297 
298             // Check if the transition is zooming in or not.
299             //
300             // This information is used to alter the transition style:
301             // when we are zooming in - we start with move and rotate transition
302             // and the scaling is delayed, but when we are zooming out we start
303             // with scaling down and move and rotation are delayed.
304             var zoomin = target.scale >= currentState.scale;
305 
306             duration = toNumber( duration, config.transitionDuration );
307             var delay = ( duration / 2 );
308 
309             // If the same step is re-selected, force computing window scaling,
310             // because it is likely to be caused by window resize
311             if ( el === activeStep ) {
312                 windowScale = computeWindowScale( config );
313             }
314 
315             var targetScale = target.scale * windowScale;
316 
317             // Trigger leave of currently active element (if it‘s not the same step again)
318             if ( activeStep && activeStep !== el ) {
319                 onStepLeave( activeStep );
320             }
321 
322             // Now we alter transforms of `root` and `canvas` to trigger transitions.
323             //
324             // And here is why there are two elements: `root` and `canvas` - they are
325             // being animated separately:
326             // `root` is used for scaling and `canvas` for translate and rotations.
327             // Transitions on them are triggered with different delays (to make
328             // visually nice and ‘natural‘ looking transitions), so we need to know
329             // that both of them are finished.
330             css( root, {
331 
332                 // To keep the perspective look similar for different scales
333                 // we need to ‘scale‘ the perspective, too
334                 transform: perspective( config.perspective / targetScale ) + scale( targetScale ),
335                 transitionDuration: duration + "ms",
336                 transitionDelay: ( zoomin ? delay : 0 ) + "ms"
337             } );
338 
339             css( canvas, {
340                 transform: rotate( target.rotate, true ) + translate( target.translate ),
341                 transitionDuration: duration + "ms",
342                 transitionDelay: ( zoomin ? 0 : delay ) + "ms"
343             } );
344 
345             // Here is a tricky part...
346             //
347             // If there is no change in scale or no change in rotation and translation, it means
348             // there was actually no delay - because there was no transition on `root` or `canvas`
349             // elements. We want to trigger `impress:stepenter` event in the correct moment, so
350             // here we compare the current and target values to check if delay should be taken into
351             // account.
352             //
353             // I know that this `if` statement looks scary, but it‘s pretty simple when you know
354             // what is going on
355             // - it‘s simply comparing all the values.
356             if ( currentState.scale === target.scale ||
357                 ( currentState.rotate.x === target.rotate.x &&
358                   currentState.rotate.y === target.rotate.y &&
359                   currentState.rotate.z === target.rotate.z &&
360                   currentState.translate.x === target.translate.x &&
361                   currentState.translate.y === target.translate.y &&
362                   currentState.translate.z === target.translate.z ) ) {
363                 delay = 0;
364             }
365 
366             // Store current state
367             currentState = target;
368             activeStep = el;
369 
370             // And here is where we trigger `impress:stepenter` event.
371             // We simply set up a timeout to fire it taking transition duration
372             // (and possible delay) into account.
373             //
374             // I really wanted to make it in more elegant way. The `transitionend` event seemed to
375             // be the best way to do it, but the fact that I‘m using transitions on two separate
376             // elements and that the `transitionend` event is only triggered when there was a
377             // transition (change in the values) caused some bugs and made the code really
378             // complicated, cause I had to handle all the conditions separately. And it still
379             // needed a `setTimeout` fallback for the situations when there is no transition at
380             // all.
381             // So I decided that I‘d rather make the code simpler than use shiny new
382             // `transitionend`.
383             //
384             // If you want learn something interesting and see how it was done with `transitionend`
385             // go back to
386             // version 0.5.2 of impress.js:
387             // http://github.com/bartaz/impress.js/blob/0.5.2/js/impress.js
388             window.clearTimeout( stepEnterTimeout );
389             stepEnterTimeout = window.setTimeout( function() {
390                 onStepEnter( activeStep );
391             }, duration + delay );
392 
393             return el;
394         };
395 
396         // `prev` API function goes to previous step (in document order)
397         var prev = function() {
398             var prev = steps.indexOf( activeStep ) - 1;
399             prev = prev >= 0 ? steps[ prev ] : steps[ steps.length - 1 ];
400 
401             return goto( prev );
402         };
403 
404         // `next` API function goes to next step (in document order)
405         var next = function() {
406             var next = steps.indexOf( activeStep ) + 1;
407             next = next < steps.length ? steps[ next ] : steps[ 0 ];
408 
409             return goto( next );
410         };
411 
412         // Adding some useful classes to step elements.
413         //
414         // All the steps that have not been shown yet are given `future` class.
415         // When the step is entered the `future` class is removed and the `present`
416         // class is given. When the step is left `present` class is replaced with
417         // `past` class.
418         //
419         // So every step element is always in one of three possible states:
420         // `future`, `present` and `past`.
421         //
422         // There classes can be used in CSS to style different types of steps.
423         // For example the `present` class can be used to trigger some custom
424         // animations when step is shown.
425         root.addEventListener( "impress:init", function() {
426 
427             // STEP CLASSES
428             steps.forEach( function( step ) {
429                 step.classList.add( "future" );
430             } );
431 
432             root.addEventListener( "impress:stepenter", function( event ) {
433                 event.target.classList.remove( "past" );
434                 event.target.classList.remove( "future" );
435                 event.target.classList.add( "present" );
436             }, false );
437 
438             root.addEventListener( "impress:stepleave", function( event ) {
439                 event.target.classList.remove( "present" );
440                 event.target.classList.add( "past" );
441             }, false );
442 
443         }, false );
444 
445         // Adding hash change support.
446         root.addEventListener( "impress:init", function() {
447 
448             // Last hash detected
449             var lastHash = "";
450 
451             // `#/step-id` is used instead of `#step-id` to prevent default browser
452             // scrolling to element in hash.
453             //
454             // And it has to be set after animation finishes, because in Chrome it
455             // makes transtion laggy.
456             // BUG: http://code.google.com/p/chromium/issues/detail?id=62820
457             root.addEventListener( "impress:stepenter", function( event ) {
458                 window.location.hash = lastHash = "#/" + event.target.id;
459             }, false );
460 
461             window.addEventListener( "hashchange", function() {
462 
463                 // When the step is entered hash in the location is updated
464                 // (just few lines above from here), so the hash change is
465                 // triggered and we would call `goto` again on the same element.
466                 //
467                 // To avoid this we store last entered hash and compare.
468                 if ( window.location.hash !== lastHash ) {
469                     goto( getElementFromHash() );
470                 }
471             }, false );
472 
473             // START
474             // by selecting step defined in url or first step of the presentation
475             goto( getElementFromHash() || steps[ 0 ], 0 );
476         }, false );
477 
478         body.classList.add( "impress-disabled" );
479 
480         // Store and return API for given impress.js root element
481         return ( roots[ "impress-root-" + rootId ] = {
482             init: init,
483             goto: goto,
484             next: next,
485             prev: prev
486         } );
487 
488     };
489 
490     impress.supported = impressSupported;
View Code

 

以上是关于impress.js 源码解析系列的主要内容,如果未能解决你的问题,请参考以下文章

impress.js中文API文档

如何用impress.js写有逼格的ppt

canal 源码解析系列-CanalInstance模块解析

dubbo系列dubbo启动过程源码解析

ReactiveSwift源码解析 SignalProducerProtocol延展中的StartLift系列方法的代码实现

Spring源码深度解析学习系列注册解析的BeanDefinition