impress.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710
  1. /**
  2. * impress.js
  3. *
  4. * impress.js is a presentation tool based on the power of CSS3 transforms and transitions
  5. * in modern browsers and inspired by the idea behind prezi.com.
  6. *
  7. *
  8. * Copyright 2011-2012 Bartek Szopka (@bartaz)
  9. *
  10. * Released under the MIT and GPL Licenses.
  11. *
  12. * ------------------------------------------------
  13. * author: Bartek Szopka
  14. * version: 0.5.3
  15. * url: http://bartaz.github.com/impress.js/
  16. * source: http://github.com/bartaz/impress.js/
  17. */
  18. /*jshint bitwise:true, curly:true, eqeqeq:true, forin:true, latedef:true, newcap:true,
  19. noarg:true, noempty:true, undef:true, strict:true, browser:true */
  20. // You are one of those who like to know how things work inside?
  21. // Let me show you the cogs that make impress.js run...
  22. (function( factory, document, window )
  23. {
  24. if ( typeof define === "function" && define.amd ) {
  25. define( factory );
  26. } else {
  27. window.impress = factory();
  28. }
  29. }(function() {
  30. "use strict";
  31. // HELPER FUNCTIONS
  32. // `pfx` is a function that takes a standard CSS property name as a parameter
  33. // and returns it's prefixed version valid for current browser it runs in.
  34. // The code is heavily inspired by Modernizr http://www.modernizr.com/
  35. var pfx = ( function() {
  36. var style = document.createElement( "dummy" ).style,
  37. prefixes = "Webkit Moz O ms Khtml".split( " " ),
  38. memory = {};
  39. return function( prop ) {
  40. if ( typeof memory[ prop ] === "undefined" ) {
  41. var ucProp = prop.charAt( 0 ).toUpperCase() + prop.substr( 1 ),
  42. props = ( prop + " " + prefixes.join( ucProp + " " ) + ucProp ).split( " " );
  43. memory[ prop ] = null;
  44. for ( var i in props ) {
  45. if ( style[ props[ i ] ] !== undefined ) {
  46. memory[ prop ] = props[ i ];
  47. break;
  48. }
  49. }
  50. }
  51. return memory[ prop ];
  52. };
  53. } )();
  54. // `arraify` takes an array-like object and turns it into real Array
  55. // to make all the Array.prototype goodness available.
  56. var arrayify = function( a ) {
  57. return [].slice.call( a );
  58. };
  59. // `css` function applies the styles given in `props` object to the element
  60. // given as `el`. It runs all property names through `pfx` function to make
  61. // sure proper prefixed version of the property is used.
  62. var css = function( el, props ) {
  63. var key, pkey;
  64. for ( key in props ) {
  65. if ( props.hasOwnProperty( key ) ) {
  66. pkey = pfx( key );
  67. if ( pkey !== null ) {
  68. el.style[ pkey ] = props[ key ];
  69. }
  70. }
  71. }
  72. return el;
  73. };
  74. // `toNumber` takes a value given as `numeric` parameter and tries to turn
  75. // it into a number. If it is not possible it returns 0 (or other value
  76. // given as `fallback`).
  77. var toNumber = function( numeric, fallback ) {
  78. return isNaN( numeric ) ? ( fallback || 0 ) : Number( numeric );
  79. };
  80. // `byId` returns element with given `id` - you probably have guessed that
  81. var byId = function( id ) {
  82. return document.getElementById( id );
  83. };
  84. // `$` returns first element for given CSS `selector` in the `context` of
  85. // the given element or whole document.
  86. var $ = function( selector, context ) {
  87. context = context || document;
  88. return context.querySelector( selector );
  89. };
  90. // `$$` return an array of elements for given CSS `selector` in the `context` of
  91. // the given element or whole document.
  92. var $$ = function( selector, context ) {
  93. context = context || document;
  94. return arrayify( context.querySelectorAll( selector ) );
  95. };
  96. // `triggerEvent` builds a custom DOM event with given `eventName` and `detail` data
  97. // and triggers it on element given as `el`.
  98. var triggerEvent = function( el, eventName, detail ) {
  99. var event = document.createEvent( "CustomEvent" );
  100. event.initCustomEvent( eventName, true, true, detail );
  101. el.dispatchEvent( event );
  102. };
  103. // `translate` builds a translate transform string for given data.
  104. var translate = function( t ) {
  105. return " translate3d(" + t.x + "px," + t.y + "px," + t.z + "px) ";
  106. };
  107. // `rotate` builds a rotate transform string for given data.
  108. // By default the rotations are in X Y Z order that can be reverted by passing `true`
  109. // as second parameter.
  110. var rotate = function( r, revert ) {
  111. var rX = " rotateX(" + r.x + "deg) ",
  112. rY = " rotateY(" + r.y + "deg) ",
  113. rZ = " rotateZ(" + r.z + "deg) ";
  114. return revert ? rZ + rY + rX : rX + rY + rZ;
  115. };
  116. // `scale` builds a scale transform string for given data.
  117. var scale = function( s ) {
  118. return " scale(" + s + ") ";
  119. };
  120. // `perspective` builds a perspective transform string for given data.
  121. var perspective = function( p ) {
  122. return " perspective(" + p + "px) ";
  123. };
  124. // `getElementFromHash` returns an element located by id from hash part of
  125. // window location.
  126. var getElementFromHash = function() {
  127. // Get id from url # by removing `#` or `#/` from the beginning,
  128. // so both "fallback" `#slide-id` and "enhanced" `#/slide-id` will work
  129. return byId( window.location.hash.replace( /^#\/?/, "" ) );
  130. };
  131. // `computeWindowScale` counts the scale factor between window size and size
  132. // defined for the presentation in the config.
  133. var computeWindowScale = function( config ) {
  134. var hScale = window.innerHeight / config.height,
  135. wScale = window.innerWidth / config.width,
  136. scale = hScale > wScale ? wScale : hScale;
  137. if ( config.maxScale && scale > config.maxScale ) {
  138. scale = config.maxScale;
  139. }
  140. if ( config.minScale && scale < config.minScale ) {
  141. scale = config.minScale;
  142. }
  143. return scale;
  144. };
  145. // CHECK SUPPORT
  146. var body = document.body;
  147. var ua = navigator.userAgent.toLowerCase();
  148. var impressSupported =
  149. // Browser should support CSS 3D transtorms
  150. ( pfx( "perspective" ) !== null ) &&
  151. // Browser should support `classList` and `dataset` APIs
  152. ( body.classList ) &&
  153. ( body.dataset ) &&
  154. // But some mobile devices need to be blacklisted,
  155. // because their CSS 3D support or hardware is not
  156. // good enough to run impress.js properly, sorry...
  157. ( ua.search( /(iphone)|(ipod)|(android)/ ) === -1 );
  158. if ( !impressSupported ) {
  159. // We can't be sure that `classList` is supported
  160. body.className += " impress-not-supported ";
  161. } else {
  162. body.classList.remove( "impress-not-supported" );
  163. body.classList.add( "impress-supported" );
  164. }
  165. // GLOBALS AND DEFAULTS
  166. // This is where the root elements of all impress.js instances will be kept.
  167. // Yes, this means you can have more than one instance on a page, but I'm not
  168. // sure if it makes any sense in practice ;)
  169. // var roots = {};
  170. // Some default config values.
  171. var defaults = {
  172. width: 1024,
  173. height: 768,
  174. maxScale: 1,
  175. minScale: 0,
  176. perspective: 1000,
  177. transitionDuration: 1000
  178. };
  179. // It's just an empty function ... and a useless comment.
  180. var empty = function() { return false; };
  181. // IMPRESS.JS API
  182. // And that's where interesting things will start to happen.
  183. // It's the core `impress` function that returns the impress.js API
  184. // for a presentation based on the element with given id ('impress'
  185. // by default).
  186. var impress = function( rootId ) {
  187. // If impress.js is not supported by the browser return a dummy API
  188. // it may not be a perfect solution but we return early and avoid
  189. // running code that may use features not implemented in the browser.
  190. if ( !impressSupported ) {
  191. return {
  192. init: empty,
  193. goto: empty,
  194. prev: empty,
  195. next: empty
  196. };
  197. }
  198. //
  199. rootId = rootId || "impress";
  200. // If given root is already initialized just return the API
  201. // if ( roots[ "impress-root-" + rootId ] ) {
  202. // return roots[ "impress-root-" + rootId ];
  203. // }
  204. // Data of all presentation steps
  205. var stepsData = {};
  206. // Element of currently active step
  207. var activeStep = null;
  208. // Current state (position, rotation and scale) of the presentation
  209. var currentState = null;
  210. // Array of step elements
  211. var steps = null;
  212. // Configuration options
  213. var config = null;
  214. // Scale factor of the browser window
  215. var windowScale = null;
  216. // Root presentation elements
  217. var root = byId( rootId );
  218. var canvas = document.createElement( "div" );
  219. var initialized = false;
  220. // STEP EVENTS
  221. //
  222. // There are currently two step events triggered by impress.js
  223. // `impress:stepenter` is triggered when the step is shown on the
  224. // screen (the transition from the previous one is finished) and
  225. // `impress:stepleave` is triggered when the step is left (the
  226. // transition to next step just starts).
  227. // Reference to last entered step
  228. var lastEntered = null;
  229. // `onStepEnter` is called whenever the step element is entered
  230. // but the event is triggered only if the step is different than
  231. // last entered step.
  232. var onStepEnter = function( step ) {
  233. if ( lastEntered !== step ) {
  234. triggerEvent( step, "impress:stepenter" );
  235. lastEntered = step;
  236. }
  237. };
  238. // `onStepLeave` is called whenever the step element is left
  239. // but the event is triggered only if the step is the same as
  240. // last entered step.
  241. var onStepLeave = function( step ) {
  242. if ( lastEntered === step ) {
  243. triggerEvent( step, "impress:stepleave" );
  244. lastEntered = null;
  245. }
  246. };
  247. // `initStep` initializes given step element by reading data from its
  248. // data attributes and setting correct styles.
  249. var initStep = function( el, idx ) {
  250. var data = el.dataset,
  251. step = {
  252. translate: {
  253. x: toNumber( data.x ),
  254. y: toNumber( data.y ),
  255. z: toNumber( data.z )
  256. },
  257. rotate: {
  258. x: toNumber( data.rotateX ),
  259. y: toNumber( data.rotateY ),
  260. z: toNumber( data.rotateZ || data.rotate )
  261. },
  262. scale: toNumber( data.scale, 1 ),
  263. el: el
  264. };
  265. if ( !el.id ) {
  266. el.id = "step-" + ( idx + 1 );
  267. }
  268. stepsData[ "impress-" + el.id ] = step;
  269. css( el, {
  270. position: "absolute",
  271. transform: "translate(-50%,-50%)" +
  272. translate( step.translate ) +
  273. rotate( step.rotate ) +
  274. scale( step.scale ),
  275. transformStyle: "preserve-3d"
  276. } );
  277. };
  278. // `init` API function that initializes (and runs) the presentation.
  279. var init = function() {
  280. if ( initialized ) { return; }
  281. // First we set up the viewport for mobile devices.
  282. // For some reason iPad goes nuts when it is not done properly.
  283. var meta = $( "meta[name='viewport']" ) || document.createElement( "meta" );
  284. meta.content = "width=device-width, minimum-scale=1, maximum-scale=1, user-scalable=no";
  285. if ( meta.parentNode !== document.head ) {
  286. meta.name = "viewport";
  287. document.head.appendChild( meta );
  288. }
  289. // Initialize configuration object
  290. var rootData = root.dataset;
  291. config = {
  292. width: toNumber( rootData.width, defaults.width ),
  293. height: toNumber( rootData.height, defaults.height ),
  294. maxScale: toNumber( rootData.maxScale, defaults.maxScale ),
  295. minScale: toNumber( rootData.minScale, defaults.minScale ),
  296. perspective: toNumber( rootData.perspective, defaults.perspective ),
  297. transitionDuration: toNumber(
  298. rootData.transitionDuration, defaults.transitionDuration
  299. )
  300. };
  301. windowScale = computeWindowScale( config );
  302. // Wrap steps with "canvas" element
  303. arrayify( root.childNodes ).forEach( function( el ) {
  304. canvas.appendChild( el );
  305. } );
  306. root.appendChild( canvas );
  307. // Set initial styles
  308. document.documentElement.style.height = "100%";
  309. css( body, {
  310. height: "100%",
  311. overflow: "hidden"
  312. } );
  313. var rootStyles = {
  314. position: "absolute",
  315. transformOrigin: "top left",
  316. transition: "all 0s ease-in-out",
  317. transformStyle: "preserve-3d"
  318. };
  319. css( root, rootStyles );
  320. css( root, {
  321. top: "50%",
  322. left: "50%",
  323. transform: perspective( config.perspective / windowScale ) + scale( windowScale )
  324. } );
  325. css( canvas, rootStyles );
  326. body.classList.remove( "impress-disabled" );
  327. body.classList.add( "impress-enabled" );
  328. // Get and init steps
  329. steps = $$( ".step", root );
  330. steps.forEach( initStep );
  331. // Set a default initial state of the canvas
  332. currentState = {
  333. translate: { x: 0, y: 0, z: 0 },
  334. rotate: { x: 0, y: 0, z: 0 },
  335. scale: 1
  336. };
  337. initialized = true;
  338. triggerEvent( root, "impress:init", { /*api: roots[ "impress-root-" + rootId ] */} );
  339. };
  340. var addStep = function(selector, insertBefore) {
  341. var customStep = $$( selector, root );
  342. customStep.forEach( initStep );
  343. var index = steps.length;
  344. var afterStep = getStep(insertBefore)
  345. if( afterStep !== null) {
  346. index = steps.indexOf(afterStep);
  347. }
  348. // customStep is an array and we have to insert the actual elements and not the array itself.
  349. // apply will expand the provided array to individual objects.
  350. Array.prototype.splice.apply(steps, [index, 0].concat(customStep));
  351. };
  352. var removeStep = function(selector) {
  353. var toRemove = getStep(selector);
  354. var index = steps.indexOf(toRemove);
  355. steps.splice(index, 1);
  356. };
  357. // `getStep` is a helper function that returns a step element defined by parameter.
  358. // If a number is given, step with index given by the number is returned, if a string
  359. // is given step element with such id is returned, if DOM element is given it is returned
  360. // if it is a correct step element.
  361. var getStep = function( step ) {
  362. if ( typeof step === "number" ) {
  363. step = step < 0 ? steps[ steps.length + step ] : steps[ step ];
  364. } else if ( typeof step === "string" ) {
  365. step = byId( step );
  366. }
  367. return ( step && step.id && stepsData[ "impress-" + step.id ] ) ? step : null;
  368. };
  369. // Used to reset timeout for `impress:stepenter` event
  370. var stepEnterTimeout = null;
  371. // `goto` API function that moves to step given with `el` parameter
  372. // (by index, id or element), with a transition `duration` optionally
  373. // given as second parameter.
  374. var goto = function( el, duration ) {
  375. if ( !initialized || !( el = getStep( el ) ) ) {
  376. // Presentation not initialized or given element is not a step
  377. return false;
  378. }
  379. // Sometimes it's possible to trigger focus on first link with some keyboard action.
  380. // Browser in such a case tries to scroll the page to make this element visible
  381. // (even that body overflow is set to hidden) and it breaks our careful positioning.
  382. //
  383. // So, as a lousy (and lazy) workaround we will make the page scroll back to the top
  384. // whenever slide is selected
  385. //
  386. // If you are reading this and know any better way to handle it, I'll be glad to hear
  387. // about it!
  388. window.scrollTo( 0, 0 );
  389. var step = stepsData[ "impress-" + el.id ];
  390. if ( activeStep ) {
  391. activeStep.classList.remove( "active" );
  392. body.classList.remove( "impress-on-" + activeStep.id );
  393. }
  394. el.classList.add( "active" );
  395. body.classList.add( "impress-on-" + el.id );
  396. // Compute target state of the canvas based on given step
  397. var target = {
  398. rotate: {
  399. x: -step.rotate.x,
  400. y: -step.rotate.y,
  401. z: -step.rotate.z
  402. },
  403. translate: {
  404. x: -step.translate.x,
  405. y: -step.translate.y,
  406. z: -step.translate.z
  407. },
  408. scale: 1 / step.scale
  409. };
  410. // Check if the transition is zooming in or not.
  411. //
  412. // This information is used to alter the transition style:
  413. // when we are zooming in - we start with move and rotate transition
  414. // and the scaling is delayed, but when we are zooming out we start
  415. // with scaling down and move and rotation are delayed.
  416. var zoomin = target.scale >= currentState.scale;
  417. duration = toNumber( duration, config.transitionDuration );
  418. var delay = ( duration / 2 );
  419. // If the same step is re-selected, force computing window scaling,
  420. // because it is likely to be caused by window resize
  421. if ( el === activeStep ) {
  422. windowScale = computeWindowScale( config );
  423. }
  424. var targetScale = target.scale * windowScale;
  425. // Trigger leave of currently active element (if it's not the same step again)
  426. if ( activeStep && activeStep !== el ) {
  427. onStepLeave( activeStep );
  428. }
  429. // Now we alter transforms of `root` and `canvas` to trigger transitions.
  430. //
  431. // And here is why there are two elements: `root` and `canvas` - they are
  432. // being animated separately:
  433. // `root` is used for scaling and `canvas` for translate and rotations.
  434. // Transitions on them are triggered with different delays (to make
  435. // visually nice and 'natural' looking transitions), so we need to know
  436. // that both of them are finished.
  437. css( root, {
  438. // To keep the perspective look similar for different scales
  439. // we need to 'scale' the perspective, too
  440. transform: perspective( config.perspective / targetScale ) + scale( targetScale ),
  441. transitionDuration: duration + "ms",
  442. transitionDelay: ( zoomin ? delay : 0 ) + "ms"
  443. } );
  444. css( canvas, {
  445. transform: rotate( target.rotate, true ) + translate( target.translate ),
  446. transitionDuration: duration + "ms",
  447. transitionDelay: ( zoomin ? 0 : delay ) + "ms"
  448. } );
  449. // Here is a tricky part...
  450. //
  451. // If there is no change in scale or no change in rotation and translation, it means
  452. // there was actually no delay - because there was no transition on `root` or `canvas`
  453. // elements. We want to trigger `impress:stepenter` event in the correct moment, so
  454. // here we compare the current and target values to check if delay should be taken into
  455. // account.
  456. //
  457. // I know that this `if` statement looks scary, but it's pretty simple when you know
  458. // what is going on
  459. // - it's simply comparing all the values.
  460. if ( currentState.scale === target.scale ||
  461. ( currentState.rotate.x === target.rotate.x &&
  462. currentState.rotate.y === target.rotate.y &&
  463. currentState.rotate.z === target.rotate.z &&
  464. currentState.translate.x === target.translate.x &&
  465. currentState.translate.y === target.translate.y &&
  466. currentState.translate.z === target.translate.z ) ) {
  467. delay = 0;
  468. }
  469. // Store current state
  470. currentState = target;
  471. activeStep = el;
  472. // And here is where we trigger `impress:stepenter` event.
  473. // We simply set up a timeout to fire it taking transition duration
  474. // (and possible delay) into account.
  475. //
  476. // I really wanted to make it in more elegant way. The `transitionend` event seemed to
  477. // be the best way to do it, but the fact that I'm using transitions on two separate
  478. // elements and that the `transitionend` event is only triggered when there was a
  479. // transition (change in the values) caused some bugs and made the code really
  480. // complicated, cause I had to handle all the conditions separately. And it still
  481. // needed a `setTimeout` fallback for the situations when there is no transition at
  482. // all.
  483. // So I decided that I'd rather make the code simpler than use shiny new
  484. // `transitionend`.
  485. //
  486. // If you want learn something interesting and see how it was done with `transitionend`
  487. // go back to
  488. // version 0.5.2 of impress.js:
  489. // http://github.com/bartaz/impress.js/blob/0.5.2/js/impress.js
  490. window.clearTimeout( stepEnterTimeout );
  491. stepEnterTimeout = window.setTimeout( function() {
  492. onStepEnter( activeStep );
  493. }, duration + delay );
  494. return el;
  495. };
  496. // `prev` API function goes to previous step (in document order)
  497. var prev = function() {
  498. var prev = steps.indexOf( activeStep ) - 1;
  499. prev = prev >= 0 ? steps[ prev ] : steps[ steps.length - 1 ];
  500. return goto( prev );
  501. };
  502. // `next` API function goes to next step (in document order)
  503. var next = function() {
  504. var next = steps.indexOf( activeStep ) + 1;
  505. next = next < steps.length ? steps[ next ] : steps[ 0 ];
  506. return goto( next );
  507. };
  508. // Adding some useful classes to step elements.
  509. //
  510. // All the steps that have not been shown yet are given `future` class.
  511. // When the step is entered the `future` class is removed and the `present`
  512. // class is given. When the step is left `present` class is replaced with
  513. // `past` class.
  514. //
  515. // So every step element is always in one of three possible states:
  516. // `future`, `present` and `past`.
  517. //
  518. // There classes can be used in CSS to style different types of steps.
  519. // For example the `present` class can be used to trigger some custom
  520. // animations when step is shown.
  521. root.addEventListener( "impress:init", function() {
  522. // STEP CLASSES
  523. steps.forEach( function( step ) {
  524. step.classList.add( "future" );
  525. } );
  526. root.addEventListener( "impress:stepenter", function( event ) {
  527. event.target.classList.remove( "past" );
  528. event.target.classList.remove( "future" );
  529. event.target.classList.add( "present" );
  530. }, false );
  531. root.addEventListener( "impress:stepleave", function( event ) {
  532. event.target.classList.remove( "present" );
  533. event.target.classList.add( "past" );
  534. }, false );
  535. }, false );
  536. // Adding hash change support.
  537. root.addEventListener( "impress:init", function() {
  538. // Last hash detected
  539. var lastHash = "";
  540. // `#/step-id` is used instead of `#step-id` to prevent default browser
  541. // scrolling to element in hash.
  542. //
  543. // And it has to be set after animation finishes, because in Chrome it
  544. // makes transtion laggy.
  545. // BUG: http://code.google.com/p/chromium/issues/detail?id=62820
  546. root.addEventListener( "impress:stepenter", function( event ) {
  547. window.location.hash = lastHash = "#/" + event.target.id;
  548. }, false );
  549. window.addEventListener( "hashchange", function() {
  550. // When the step is entered hash in the location is updated
  551. // (just few lines above from here), so the hash change is
  552. // triggered and we would call `goto` again on the same element.
  553. //
  554. // To avoid this we store last entered hash and compare.
  555. if ( window.location.hash !== lastHash ) {
  556. goto( getElementFromHash() );
  557. }
  558. }, false );
  559. // START
  560. // by selecting step defined in url or first step of the presentation
  561. goto( getElementFromHash() || steps[ 0 ], 0 );
  562. }, false );
  563. body.classList.add( "impress-disabled" );
  564. // Store and return API for given impress.js root element
  565. return ( /*roots[ "impress-root-" + rootId ] = */ {
  566. init: init,
  567. goto: goto,
  568. next: next,
  569. prev: prev,
  570. addStep: addStep,
  571. removeStep: removeStep,
  572. } );
  573. };
  574. // Flag that can be used in JS to check if browser have passed the support test
  575. impress.supported = impressSupported;
  576. return impress;
  577. }, document, window));
  578. // THAT'S ALL FOLKS!
  579. //
  580. // Thanks for reading it all.
  581. // Or thanks for scrolling down and reading the last part.
  582. //
  583. // I've learnt a lot when building impress.js and I hope this code and comments
  584. // will help somebody learn at least some part of it.