tizenporta/impress.js

changeset 13
ee91ce4e01a7
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/tizenporta/impress.js	Wed May 02 20:50:26 2012 +0200
     1.3 @@ -0,0 +1,800 @@
     1.4 +/**
     1.5 + * impress.js
     1.6 + *
     1.7 + * impress.js is a presentation tool based on the power of CSS3 transforms and transitions
     1.8 + * in modern browsers and inspired by the idea behind prezi.com.
     1.9 + *
    1.10 + *
    1.11 + * Copyright 2011-2012 Bartek Szopka (@bartaz)
    1.12 + *
    1.13 + * Released under the MIT and GPL Licenses.
    1.14 + *
    1.15 + * ------------------------------------------------
    1.16 + *  author:  Bartek Szopka
    1.17 + *  version: 0.5.3
    1.18 + *  url:     http://bartaz.github.com/impress.js/
    1.19 + *  source:  http://github.com/bartaz/impress.js/
    1.20 + */
    1.21 +
    1.22 +/*jshint bitwise:true, curly:true, eqeqeq:true, forin:true, latedef:true, newcap:true,
    1.23 +         noarg:true, noempty:true, undef:true, strict:true, browser:true */
    1.24 +
    1.25 +// You are one of those who like to know how thing work inside?
    1.26 +// Let me show you the cogs that make impress.js run...
    1.27 +(function ( document, window ) {
    1.28 +    'use strict';
    1.29 +    
    1.30 +    // HELPER FUNCTIONS
    1.31 +    
    1.32 +    // `pfx` is a function that takes a standard CSS property name as a parameter
    1.33 +    // and returns it's prefixed version valid for current browser it runs in.
    1.34 +    // The code is heavily inspired by Modernizr http://www.modernizr.com/
    1.35 +    var pfx = (function () {
    1.36 +        
    1.37 +        var style = document.createElement('dummy').style,
    1.38 +            prefixes = 'Webkit Moz O ms Khtml'.split(' '),
    1.39 +            memory = {};
    1.40 +        
    1.41 +        return function ( prop ) {
    1.42 +            if ( typeof memory[ prop ] === "undefined" ) {
    1.43 +                
    1.44 +                var ucProp  = prop.charAt(0).toUpperCase() + prop.substr(1),
    1.45 +                    props   = (prop + ' ' + prefixes.join(ucProp + ' ') + ucProp).split(' ');
    1.46 +                
    1.47 +                memory[ prop ] = null;
    1.48 +                for ( var i in props ) {
    1.49 +                    if ( style[ props[i] ] !== undefined ) {
    1.50 +                        memory[ prop ] = props[i];
    1.51 +                        break;
    1.52 +                    }
    1.53 +                }
    1.54 +            
    1.55 +            }
    1.56 +            
    1.57 +            return memory[ prop ];
    1.58 +        };
    1.59 +    
    1.60 +    })();
    1.61 +    
    1.62 +    // `arraify` takes an array-like object and turns it into real Array
    1.63 +    // to make all the Array.prototype goodness available.
    1.64 +    var arrayify = function ( a ) {
    1.65 +        return [].slice.call( a );
    1.66 +    };
    1.67 +    
    1.68 +    // `css` function applies the styles given in `props` object to the element
    1.69 +    // given as `el`. It runs all property names through `pfx` function to make
    1.70 +    // sure proper prefixed version of the property is used.
    1.71 +    var css = function ( el, props ) {
    1.72 +        var key, pkey;
    1.73 +        for ( key in props ) {
    1.74 +            if ( props.hasOwnProperty(key) ) {
    1.75 +                pkey = pfx(key);
    1.76 +                if ( pkey !== null ) {
    1.77 +                    el.style[pkey] = props[key];
    1.78 +                }
    1.79 +            }
    1.80 +        }
    1.81 +        return el;
    1.82 +    };
    1.83 +    
    1.84 +    // `toNumber` takes a value given as `numeric` parameter and tries to turn
    1.85 +    // it into a number. If it is not possible it returns 0 (or other value
    1.86 +    // given as `fallback`).
    1.87 +    var toNumber = function (numeric, fallback) {
    1.88 +        return isNaN(numeric) ? (fallback || 0) : Number(numeric);
    1.89 +    };
    1.90 +    
    1.91 +    // `byId` returns element with given `id` - you probably have guessed that ;)
    1.92 +    var byId = function ( id ) {
    1.93 +        return document.getElementById(id);
    1.94 +    };
    1.95 +    
    1.96 +    // `$` returns first element for given CSS `selector` in the `context` of
    1.97 +    // the given element or whole document.
    1.98 +    var $ = function ( selector, context ) {
    1.99 +        context = context || document;
   1.100 +        return context.querySelector(selector);
   1.101 +    };
   1.102 +    
   1.103 +    // `$$` return an array of elements for given CSS `selector` in the `context` of
   1.104 +    // the given element or whole document.
   1.105 +    var $$ = function ( selector, context ) {
   1.106 +        context = context || document;
   1.107 +        return arrayify( context.querySelectorAll(selector) );
   1.108 +    };
   1.109 +    
   1.110 +    // `triggerEvent` builds a custom DOM event with given `eventName` and `detail` data
   1.111 +    // and triggers it on element given as `el`.
   1.112 +    var triggerEvent = function (el, eventName, detail) {
   1.113 +        var event = document.createEvent("CustomEvent");
   1.114 +        event.initCustomEvent(eventName, true, true, detail);
   1.115 +        el.dispatchEvent(event);
   1.116 +    };
   1.117 +    
   1.118 +    // `translate` builds a translate transform string for given data.
   1.119 +    var translate = function ( t ) {
   1.120 +        return " translate3d(" + t.x + "px," + t.y + "px," + t.z + "px) ";
   1.121 +    };
   1.122 +    
   1.123 +    // `rotate` builds a rotate transform string for given data.
   1.124 +    // By default the rotations are in X Y Z order that can be reverted by passing `true`
   1.125 +    // as second parameter.
   1.126 +    var rotate = function ( r, revert ) {
   1.127 +        var rX = " rotateX(" + r.x + "deg) ",
   1.128 +            rY = " rotateY(" + r.y + "deg) ",
   1.129 +            rZ = " rotateZ(" + r.z + "deg) ";
   1.130 +        
   1.131 +        return revert ? rZ+rY+rX : rX+rY+rZ;
   1.132 +    };
   1.133 +    
   1.134 +    // `scale` builds a scale transform string for given data.
   1.135 +    var scale = function ( s ) {
   1.136 +        return " scale(" + s + ") ";
   1.137 +    };
   1.138 +    
   1.139 +    // `perspective` builds a perspective transform string for given data.
   1.140 +    var perspective = function ( p ) {
   1.141 +        return " perspective(" + p + "px) ";
   1.142 +    };
   1.143 +    
   1.144 +    // `getElementFromHash` returns an element located by id from hash part of
   1.145 +    // window location.
   1.146 +    var getElementFromHash = function () {
   1.147 +        // get id from url # by removing `#` or `#/` from the beginning,
   1.148 +        // so both "fallback" `#slide-id` and "enhanced" `#/slide-id` will work
   1.149 +        return byId( window.location.hash.replace(/^#\/?/,"") );
   1.150 +    };
   1.151 +    
   1.152 +    // `computeWindowScale` counts the scale factor between window size and size
   1.153 +    // defined for the presentation in the config.
   1.154 +    var computeWindowScale = function ( config ) {
   1.155 +        var hScale = window.innerHeight / config.height,
   1.156 +            wScale = window.innerWidth / config.width,
   1.157 +            scale = hScale > wScale ? wScale : hScale;
   1.158 +        
   1.159 +        if (config.maxScale && scale > config.maxScale) {
   1.160 +            scale = config.maxScale;
   1.161 +        }
   1.162 +        
   1.163 +        if (config.minScale && scale < config.minScale) {
   1.164 +            scale = config.minScale;
   1.165 +        }
   1.166 +        
   1.167 +        return scale;
   1.168 +    };
   1.169 +    
   1.170 +    // CHECK SUPPORT
   1.171 +    var body = document.body;
   1.172 +    
   1.173 +    var ua = navigator.userAgent.toLowerCase();
   1.174 +    var impressSupported = 
   1.175 +                          // browser should support CSS 3D transtorms 
   1.176 +                           ( pfx("perspective") !== null ) &&
   1.177 +                           
   1.178 +                          // and `classList` and `dataset` APIs
   1.179 +                           ( body.classList ) &&
   1.180 +                           ( body.dataset ) &&
   1.181 +                           
   1.182 +                          // but some mobile devices need to be blacklisted,
   1.183 +                          // because their CSS 3D support or hardware is not
   1.184 +                          // good enough to run impress.js properly, sorry...
   1.185 +                           ( ua.search(/(iphone)|(ipod)|(android)/) === -1 );
   1.186 +    
   1.187 +    if (!impressSupported) {
   1.188 +        // we can't be sure that `classList` is supported
   1.189 +        body.className += " impress-not-supported ";
   1.190 +    } else {
   1.191 +        body.classList.remove("impress-not-supported");
   1.192 +        body.classList.add("impress-supported");
   1.193 +    }
   1.194 +    
   1.195 +    // GLOBALS AND DEFAULTS
   1.196 +    
   1.197 +    // This is were the root elements of all impress.js instances will be kept.
   1.198 +    // Yes, this means you can have more than one instance on a page, but I'm not
   1.199 +    // sure if it makes any sense in practice ;)
   1.200 +    var roots = {};
   1.201 +    
   1.202 +    // some default config values.
   1.203 +    var defaults = {
   1.204 +        width: 1024,
   1.205 +        height: 768,
   1.206 +        maxScale: 1,
   1.207 +        minScale: 0,
   1.208 +        
   1.209 +        perspective: 1000,
   1.210 +        
   1.211 +        transitionDuration: 1000
   1.212 +    };
   1.213 +    
   1.214 +    // it's just an empty function ... and a useless comment.
   1.215 +    var empty = function () { return false; };
   1.216 +    
   1.217 +    // IMPRESS.JS API
   1.218 +    
   1.219 +    // And that's where interesting things will start to happen.
   1.220 +    // It's the core `impress` function that returns the impress.js API
   1.221 +    // for a presentation based on the element with given id ('impress'
   1.222 +    // by default).
   1.223 +    var impress = window.impress = function ( rootId ) {
   1.224 +        
   1.225 +        // If impress.js is not supported by the browser return a dummy API
   1.226 +        // it may not be a perfect solution but we return early and avoid
   1.227 +        // running code that may use features not implemented in the browser.
   1.228 +        if (!impressSupported) {
   1.229 +            return {
   1.230 +                init: empty,
   1.231 +                goto: empty,
   1.232 +                prev: empty,
   1.233 +                next: empty
   1.234 +            };
   1.235 +        }
   1.236 +        
   1.237 +        rootId = rootId || "impress";
   1.238 +        
   1.239 +        // if given root is already initialized just return the API
   1.240 +        if (roots["impress-root-" + rootId]) {
   1.241 +            return roots["impress-root-" + rootId];
   1.242 +        }
   1.243 +        
   1.244 +        // data of all presentation steps
   1.245 +        var stepsData = {};
   1.246 +        
   1.247 +        // element of currently active step
   1.248 +        var activeStep = null;
   1.249 +        
   1.250 +        // current state (position, rotation and scale) of the presentation
   1.251 +        var currentState = null;
   1.252 +        
   1.253 +        // array of step elements
   1.254 +        var steps = null;
   1.255 +        
   1.256 +        // configuration options
   1.257 +        var config = null;
   1.258 +        
   1.259 +        // scale factor of the browser window
   1.260 +        var windowScale = null;        
   1.261 +        
   1.262 +        // root presentation elements
   1.263 +        var root = byId( rootId );
   1.264 +        var canvas = document.createElement("div");
   1.265 +        
   1.266 +        var initialized = false;
   1.267 +        
   1.268 +        // STEP EVENTS
   1.269 +        //
   1.270 +        // There are currently two step events triggered by impress.js
   1.271 +        // `impress:stepenter` is triggered when the step is shown on the 
   1.272 +        // screen (the transition from the previous one is finished) and
   1.273 +        // `impress:stepleave` is triggered when the step is left (the
   1.274 +        // transition to next step just starts).
   1.275 +        
   1.276 +        // reference to last entered step
   1.277 +        var lastEntered = null;
   1.278 +        
   1.279 +        // `onStepEnter` is called whenever the step element is entered
   1.280 +        // but the event is triggered only if the step is different than
   1.281 +        // last entered step.
   1.282 +        var onStepEnter = function (step) {
   1.283 +            if (lastEntered !== step) {
   1.284 +                triggerEvent(step, "impress:stepenter");
   1.285 +                lastEntered = step;
   1.286 +            }
   1.287 +        };
   1.288 +        
   1.289 +        // `onStepLeave` is called whenever the step element is left
   1.290 +        // but the event is triggered only if the step is the same as
   1.291 +        // last entered step.
   1.292 +        var onStepLeave = function (step) {
   1.293 +            if (lastEntered === step) {
   1.294 +                triggerEvent(step, "impress:stepleave");
   1.295 +                lastEntered = null;
   1.296 +            }
   1.297 +        };
   1.298 +        
   1.299 +        // `initStep` initializes given step element by reading data from its
   1.300 +        // data attributes and setting correct styles.
   1.301 +        var initStep = function ( el, idx ) {
   1.302 +            var data = el.dataset,
   1.303 +                step = {
   1.304 +                    translate: {
   1.305 +                        x: toNumber(data.x),
   1.306 +                        y: toNumber(data.y),
   1.307 +                        z: toNumber(data.z)
   1.308 +                    },
   1.309 +                    rotate: {
   1.310 +                        x: toNumber(data.rotateX),
   1.311 +                        y: toNumber(data.rotateY),
   1.312 +                        z: toNumber(data.rotateZ || data.rotate)
   1.313 +                    },
   1.314 +                    scale: toNumber(data.scale, 1),
   1.315 +                    el: el
   1.316 +                };
   1.317 +            
   1.318 +            if ( !el.id ) {
   1.319 +                el.id = "step-" + (idx + 1);
   1.320 +            }
   1.321 +            
   1.322 +            stepsData["impress-" + el.id] = step;
   1.323 +            
   1.324 +            css(el, {
   1.325 +                position: "absolute",
   1.326 +                transform: "translate(-50%,-50%)" +
   1.327 +                           translate(step.translate) +
   1.328 +                           rotate(step.rotate) +
   1.329 +                           scale(step.scale),
   1.330 +                transformStyle: "preserve-3d"
   1.331 +            });
   1.332 +        };
   1.333 +        
   1.334 +        // `init` API function that initializes (and runs) the presentation.
   1.335 +        var init = function () {
   1.336 +            if (initialized) { return; }
   1.337 +            
   1.338 +            // First we set up the viewport for mobile devices.
   1.339 +            // For some reason iPad goes nuts when it is not done properly.
   1.340 +            var meta = $("meta[name='viewport']") || document.createElement("meta");
   1.341 +            meta.content = "width=device-width, minimum-scale=1, maximum-scale=1, user-scalable=no";
   1.342 +            if (meta.parentNode !== document.head) {
   1.343 +                meta.name = 'viewport';
   1.344 +                document.head.appendChild(meta);
   1.345 +            }
   1.346 +            
   1.347 +            // initialize configuration object
   1.348 +            var rootData = root.dataset;
   1.349 +            config = {
   1.350 +                width: toNumber( rootData.width, defaults.width ),
   1.351 +                height: toNumber( rootData.height, defaults.height ),
   1.352 +                maxScale: toNumber( rootData.maxScale, defaults.maxScale ),
   1.353 +                minScale: toNumber( rootData.minScale, defaults.minScale ),                
   1.354 +                perspective: toNumber( rootData.perspective, defaults.perspective ),
   1.355 +                transitionDuration: toNumber( rootData.transitionDuration, defaults.transitionDuration )
   1.356 +            };
   1.357 +            
   1.358 +            windowScale = computeWindowScale( config );
   1.359 +            
   1.360 +            // wrap steps with "canvas" element
   1.361 +            arrayify( root.childNodes ).forEach(function ( el ) {
   1.362 +                canvas.appendChild( el );
   1.363 +            });
   1.364 +            root.appendChild(canvas);
   1.365 +            
   1.366 +            // set initial styles
   1.367 +            document.documentElement.style.height = "100%";
   1.368 +            
   1.369 +            css(body, {
   1.370 +                height: "100%",
   1.371 +                overflow: "hidden"
   1.372 +            });
   1.373 +            
   1.374 +            var rootStyles = {
   1.375 +                position: "absolute",
   1.376 +                transformOrigin: "top left",
   1.377 +                transition: "all 0s ease-in-out",
   1.378 +                transformStyle: "preserve-3d"
   1.379 +            };
   1.380 +            
   1.381 +            css(root, rootStyles);
   1.382 +            css(root, {
   1.383 +                top: "50%",
   1.384 +                left: "50%",
   1.385 +                transform: perspective( config.perspective/windowScale ) + scale( windowScale )
   1.386 +            });
   1.387 +            css(canvas, rootStyles);
   1.388 +            
   1.389 +            body.classList.remove("impress-disabled");
   1.390 +            body.classList.add("impress-enabled");
   1.391 +            
   1.392 +            // get and init steps
   1.393 +            steps = $$(".step", root);
   1.394 +            steps.forEach( initStep );
   1.395 +            
   1.396 +            // set a default initial state of the canvas
   1.397 +            currentState = {
   1.398 +                translate: { x: 0, y: 0, z: 0 },
   1.399 +                rotate:    { x: 0, y: 0, z: 0 },
   1.400 +                scale:     1
   1.401 +            };
   1.402 +            
   1.403 +            initialized = true;
   1.404 +            
   1.405 +            triggerEvent(root, "impress:init", { api: roots[ "impress-root-" + rootId ] });
   1.406 +        };
   1.407 +        
   1.408 +        // `getStep` is a helper function that returns a step element defined by parameter.
   1.409 +        // If a number is given, step with index given by the number is returned, if a string
   1.410 +        // is given step element with such id is returned, if DOM element is given it is returned
   1.411 +        // if it is a correct step element.
   1.412 +        var getStep = function ( step ) {
   1.413 +            if (typeof step === "number") {
   1.414 +                step = step < 0 ? steps[ steps.length + step] : steps[ step ];
   1.415 +            } else if (typeof step === "string") {
   1.416 +                step = byId(step);
   1.417 +            }
   1.418 +            return (step && step.id && stepsData["impress-" + step.id]) ? step : null;
   1.419 +        };
   1.420 +        
   1.421 +        // used to reset timeout for `impress:stepenter` event
   1.422 +        var stepEnterTimeout = null;
   1.423 +        
   1.424 +        // `goto` API function that moves to step given with `el` parameter (by index, id or element),
   1.425 +        // with a transition `duration` optionally given as second parameter.
   1.426 +        var goto = function ( el, duration ) {
   1.427 +            
   1.428 +            if ( !initialized || !(el = getStep(el)) ) {
   1.429 +                // presentation not initialized or given element is not a step
   1.430 +                return false;
   1.431 +            }
   1.432 +            
   1.433 +            // Sometimes it's possible to trigger focus on first link with some keyboard action.
   1.434 +            // Browser in such a case tries to scroll the page to make this element visible
   1.435 +            // (even that body overflow is set to hidden) and it breaks our careful positioning.
   1.436 +            //
   1.437 +            // So, as a lousy (and lazy) workaround we will make the page scroll back to the top
   1.438 +            // whenever slide is selected
   1.439 +            //
   1.440 +            // If you are reading this and know any better way to handle it, I'll be glad to hear about it!
   1.441 +            window.scrollTo(0, 0);
   1.442 +            
   1.443 +            var step = stepsData["impress-" + el.id];
   1.444 +            
   1.445 +            if ( activeStep ) {
   1.446 +                activeStep.classList.remove("active");
   1.447 +                body.classList.remove("impress-on-" + activeStep.id);
   1.448 +            }
   1.449 +            el.classList.add("active");
   1.450 +            
   1.451 +            body.classList.add("impress-on-" + el.id);
   1.452 +            
   1.453 +            // compute target state of the canvas based on given step
   1.454 +            var target = {
   1.455 +                rotate: {
   1.456 +                    x: -step.rotate.x,
   1.457 +                    y: -step.rotate.y,
   1.458 +                    z: -step.rotate.z
   1.459 +                },
   1.460 +                translate: {
   1.461 +                    x: -step.translate.x,
   1.462 +                    y: -step.translate.y,
   1.463 +                    z: -step.translate.z
   1.464 +                },
   1.465 +                scale: 1 / step.scale
   1.466 +            };
   1.467 +            
   1.468 +            // Check if the transition is zooming in or not.
   1.469 +            //
   1.470 +            // This information is used to alter the transition style:
   1.471 +            // when we are zooming in - we start with move and rotate transition
   1.472 +            // and the scaling is delayed, but when we are zooming out we start
   1.473 +            // with scaling down and move and rotation are delayed.
   1.474 +            var zoomin = target.scale >= currentState.scale;
   1.475 +            
   1.476 +            duration = toNumber(duration, config.transitionDuration);
   1.477 +            var delay = (duration / 2);
   1.478 +            
   1.479 +            // if the same step is re-selected, force computing window scaling,
   1.480 +            // because it is likely to be caused by window resize
   1.481 +            if (el === activeStep) {
   1.482 +                windowScale = computeWindowScale(config);
   1.483 +            }
   1.484 +            
   1.485 +            var targetScale = target.scale * windowScale;
   1.486 +            
   1.487 +            // trigger leave of currently active element (if it's not the same step again)
   1.488 +            if (activeStep && activeStep !== el) {
   1.489 +                onStepLeave(activeStep);
   1.490 +            }
   1.491 +            
   1.492 +            // Now we alter transforms of `root` and `canvas` to trigger transitions.
   1.493 +            //
   1.494 +            // And here is why there are two elements: `root` and `canvas` - they are
   1.495 +            // being animated separately:
   1.496 +            // `root` is used for scaling and `canvas` for translate and rotations.
   1.497 +            // Transitions on them are triggered with different delays (to make
   1.498 +            // visually nice and 'natural' looking transitions), so we need to know
   1.499 +            // that both of them are finished.
   1.500 +            css(root, {
   1.501 +                // to keep the perspective look similar for different scales
   1.502 +                // we need to 'scale' the perspective, too
   1.503 +                transform: perspective( config.perspective / targetScale ) + scale( targetScale ),
   1.504 +                transitionDuration: duration + "ms",
   1.505 +                transitionDelay: (zoomin ? delay : 0) + "ms"
   1.506 +            });
   1.507 +            
   1.508 +            css(canvas, {
   1.509 +                transform: rotate(target.rotate, true) + translate(target.translate),
   1.510 +                transitionDuration: duration + "ms",
   1.511 +                transitionDelay: (zoomin ? 0 : delay) + "ms"
   1.512 +            });
   1.513 +            
   1.514 +            // Here is a tricky part...
   1.515 +            //
   1.516 +            // If there is no change in scale or no change in rotation and translation, it means there was actually
   1.517 +            // no delay - because there was no transition on `root` or `canvas` elements.
   1.518 +            // We want to trigger `impress:stepenter` event in the correct moment, so here we compare the current
   1.519 +            // and target values to check if delay should be taken into account.
   1.520 +            //
   1.521 +            // I know that this `if` statement looks scary, but it's pretty simple when you know what is going on
   1.522 +            // - it's simply comparing all the values.
   1.523 +            if ( currentState.scale === target.scale ||
   1.524 +                (currentState.rotate.x === target.rotate.x && currentState.rotate.y === target.rotate.y &&
   1.525 +                 currentState.rotate.z === target.rotate.z && currentState.translate.x === target.translate.x &&
   1.526 +                 currentState.translate.y === target.translate.y && currentState.translate.z === target.translate.z) ) {
   1.527 +                delay = 0;
   1.528 +            }
   1.529 +            
   1.530 +            // store current state
   1.531 +            currentState = target;
   1.532 +            activeStep = el;
   1.533 +            
   1.534 +            // And here is where we trigger `impress:stepenter` event.
   1.535 +            // We simply set up a timeout to fire it taking transition duration (and possible delay) into account.
   1.536 +            //
   1.537 +            // I really wanted to make it in more elegant way. The `transitionend` event seemed to be the best way
   1.538 +            // to do it, but the fact that I'm using transitions on two separate elements and that the `transitionend`
   1.539 +            // event is only triggered when there was a transition (change in the values) caused some bugs and 
   1.540 +            // made the code really complicated, cause I had to handle all the conditions separately. And it still
   1.541 +            // needed a `setTimeout` fallback for the situations when there is no transition at all.
   1.542 +            // So I decided that I'd rather make the code simpler than use shiny new `transitionend`.
   1.543 +            //
   1.544 +            // If you want learn something interesting and see how it was done with `transitionend` go back to
   1.545 +            // version 0.5.2 of impress.js: http://github.com/bartaz/impress.js/blob/0.5.2/js/impress.js
   1.546 +            window.clearTimeout(stepEnterTimeout);
   1.547 +            stepEnterTimeout = window.setTimeout(function() {
   1.548 +                onStepEnter(activeStep);
   1.549 +            }, duration + delay);
   1.550 +            
   1.551 +            return el;
   1.552 +        };
   1.553 +        
   1.554 +        // `prev` API function goes to previous step (in document order)
   1.555 +        var prev = function () {
   1.556 +            var prev = steps.indexOf( activeStep ) - 1;
   1.557 +            prev = prev >= 0 ? steps[ prev ] : steps[ steps.length-1 ];
   1.558 +            
   1.559 +            return goto(prev);
   1.560 +        };
   1.561 +        
   1.562 +        // `next` API function goes to next step (in document order)
   1.563 +        var next = function () {
   1.564 +            var next = steps.indexOf( activeStep ) + 1;
   1.565 +            next = next < steps.length ? steps[ next ] : steps[ 0 ];
   1.566 +            
   1.567 +            return goto(next);
   1.568 +        };
   1.569 +        
   1.570 +        // Adding some useful classes to step elements.
   1.571 +        //
   1.572 +        // All the steps that have not been shown yet are given `future` class.
   1.573 +        // When the step is entered the `future` class is removed and the `present`
   1.574 +        // class is given. When the step is left `present` class is replaced with
   1.575 +        // `past` class.
   1.576 +        //
   1.577 +        // So every step element is always in one of three possible states:
   1.578 +        // `future`, `present` and `past`.
   1.579 +        //
   1.580 +        // There classes can be used in CSS to style different types of steps.
   1.581 +        // For example the `present` class can be used to trigger some custom
   1.582 +        // animations when step is shown.
   1.583 +        root.addEventListener("impress:init", function(){
   1.584 +            // STEP CLASSES
   1.585 +            steps.forEach(function (step) {
   1.586 +                step.classList.add("future");
   1.587 +            });
   1.588 +            
   1.589 +            root.addEventListener("impress:stepenter", function (event) {
   1.590 +                event.target.classList.remove("past");
   1.591 +                event.target.classList.remove("future");
   1.592 +                event.target.classList.add("present");
   1.593 +            }, false);
   1.594 +            
   1.595 +            root.addEventListener("impress:stepleave", function (event) {
   1.596 +                event.target.classList.remove("present");
   1.597 +                event.target.classList.add("past");
   1.598 +            }, false);
   1.599 +            
   1.600 +        }, false);
   1.601 +        
   1.602 +        // Adding hash change support.
   1.603 +        root.addEventListener("impress:init", function(){
   1.604 +            
   1.605 +            // last hash detected
   1.606 +            var lastHash = "";
   1.607 +            
   1.608 +            // `#/step-id` is used instead of `#step-id` to prevent default browser
   1.609 +            // scrolling to element in hash.
   1.610 +            //
   1.611 +            // And it has to be set after animation finishes, because in Chrome it
   1.612 +            // makes transtion laggy.
   1.613 +            // BUG: http://code.google.com/p/chromium/issues/detail?id=62820
   1.614 +            root.addEventListener("impress:stepenter", function (event) {
   1.615 +                window.location.hash = lastHash = "#/" + event.target.id;
   1.616 +            }, false);
   1.617 +            
   1.618 +            window.addEventListener("hashchange", function () {
   1.619 +                // When the step is entered hash in the location is updated
   1.620 +                // (just few lines above from here), so the hash change is 
   1.621 +                // triggered and we would call `goto` again on the same element.
   1.622 +                //
   1.623 +                // To avoid this we store last entered hash and compare.
   1.624 +                if (window.location.hash !== lastHash) {
   1.625 +                    goto( getElementFromHash() );
   1.626 +                }
   1.627 +            }, false);
   1.628 +            
   1.629 +            // START 
   1.630 +            // by selecting step defined in url or first step of the presentation
   1.631 +            goto(getElementFromHash() || steps[0], 0);
   1.632 +        }, false);
   1.633 +        
   1.634 +        body.classList.add("impress-disabled");
   1.635 +        
   1.636 +        // store and return API for given impress.js root element
   1.637 +        return (roots[ "impress-root-" + rootId ] = {
   1.638 +            init: init,
   1.639 +            goto: goto,
   1.640 +            next: next,
   1.641 +            prev: prev
   1.642 +        });
   1.643 +
   1.644 +    };
   1.645 +    
   1.646 +    // flag that can be used in JS to check if browser have passed the support test
   1.647 +    impress.supported = impressSupported;
   1.648 +    
   1.649 +})(document, window);
   1.650 +
   1.651 +// NAVIGATION EVENTS
   1.652 +
   1.653 +// As you can see this part is separate from the impress.js core code.
   1.654 +// It's because these navigation actions only need what impress.js provides with
   1.655 +// its simple API.
   1.656 +//
   1.657 +// In future I think about moving it to make them optional, move to separate files
   1.658 +// and treat more like a 'plugins'.
   1.659 +(function ( document, window ) {
   1.660 +    'use strict';
   1.661 +    
   1.662 +    // throttling function calls, by Remy Sharp
   1.663 +    // http://remysharp.com/2010/07/21/throttling-function-calls/
   1.664 +    var throttle = function (fn, delay) {
   1.665 +        var timer = null;
   1.666 +        return function () {
   1.667 +            var context = this, args = arguments;
   1.668 +            clearTimeout(timer);
   1.669 +            timer = setTimeout(function () {
   1.670 +                fn.apply(context, args);
   1.671 +            }, delay);
   1.672 +        };
   1.673 +    };
   1.674 +    
   1.675 +    // wait for impress.js to be initialized
   1.676 +    document.addEventListener("impress:init", function (event) {
   1.677 +        // Getting API from event data.
   1.678 +        // So you don't event need to know what is the id of the root element
   1.679 +        // or anything. `impress:init` event data gives you everything you 
   1.680 +        // need to control the presentation that was just initialized.
   1.681 +        var api = event.detail.api;
   1.682 +        
   1.683 +        // KEYBOARD NAVIGATION HANDLERS
   1.684 +        
   1.685 +        // Prevent default keydown action when one of supported key is pressed.
   1.686 +        document.addEventListener("keydown", function ( event ) {
   1.687 +            if ( event.keyCode === 9 || ( event.keyCode >= 32 && event.keyCode <= 34 ) || (event.keyCode >= 37 && event.keyCode <= 40) ) {
   1.688 +                event.preventDefault();
   1.689 +            }
   1.690 +        }, false);
   1.691 +        
   1.692 +        // Trigger impress action (next or prev) on keyup.
   1.693 +        
   1.694 +        // Supported keys are:
   1.695 +        // [space] - quite common in presentation software to move forward
   1.696 +        // [up] [right] / [down] [left] - again common and natural addition,
   1.697 +        // [pgdown] / [pgup] - often triggered by remote controllers,
   1.698 +        // [tab] - this one is quite controversial, but the reason it ended up on
   1.699 +        //   this list is quite an interesting story... Remember that strange part
   1.700 +        //   in the impress.js code where window is scrolled to 0,0 on every presentation
   1.701 +        //   step, because sometimes browser scrolls viewport because of the focused element?
   1.702 +        //   Well, the [tab] key by default navigates around focusable elements, so clicking
   1.703 +        //   it very often caused scrolling to focused element and breaking impress.js
   1.704 +        //   positioning. I didn't want to just prevent this default action, so I used [tab]
   1.705 +        //   as another way to moving to next step... And yes, I know that for the sake of
   1.706 +        //   consistency I should add [shift+tab] as opposite action...
   1.707 +        document.addEventListener("keyup", function ( event ) {
   1.708 +            if ( event.keyCode === 9 || ( event.keyCode >= 32 && event.keyCode <= 34 ) || (event.keyCode >= 37 && event.keyCode <= 40) ) {
   1.709 +                switch( event.keyCode ) {
   1.710 +                    case 33: // pg up
   1.711 +                    case 37: // left
   1.712 +                    case 38: // up
   1.713 +                             api.prev();
   1.714 +                             break;
   1.715 +                    case 9:  // tab
   1.716 +                    case 32: // space
   1.717 +                    case 34: // pg down
   1.718 +                    case 39: // right
   1.719 +                    case 40: // down
   1.720 +                             api.next();
   1.721 +                             break;
   1.722 +                }
   1.723 +                
   1.724 +                event.preventDefault();
   1.725 +            }
   1.726 +        }, false);
   1.727 +        
   1.728 +        // delegated handler for clicking on the links to presentation steps
   1.729 +        document.addEventListener("click", function ( event ) {
   1.730 +            // event delegation with "bubbling"
   1.731 +            // check if event target (or any of its parents is a link)
   1.732 +            var target = event.target;
   1.733 +            while ( (target.tagName !== "A") &&
   1.734 +                    (target !== document.documentElement) ) {
   1.735 +                target = target.parentNode;
   1.736 +            }
   1.737 +            
   1.738 +            if ( target.tagName === "A" ) {
   1.739 +                var href = target.getAttribute("href");
   1.740 +                
   1.741 +                // if it's a link to presentation step, target this step
   1.742 +                if ( href && href[0] === '#' ) {
   1.743 +                    target = document.getElementById( href.slice(1) );
   1.744 +                }
   1.745 +            }
   1.746 +            
   1.747 +            if ( api.goto(target) ) {
   1.748 +                event.stopImmediatePropagation();
   1.749 +                event.preventDefault();
   1.750 +            }
   1.751 +        }, false);
   1.752 +        
   1.753 +        // delegated handler for clicking on step elements
   1.754 +        document.addEventListener("click", function ( event ) {
   1.755 +            var target = event.target;
   1.756 +            // find closest step element that is not active
   1.757 +            while ( !(target.classList.contains("step") && !target.classList.contains("active")) &&
   1.758 +                    (target !== document.documentElement) ) {
   1.759 +                target = target.parentNode;
   1.760 +            }
   1.761 +            
   1.762 +            if ( api.goto(target) ) {
   1.763 +                event.preventDefault();
   1.764 +            }
   1.765 +        }, false);
   1.766 +        
   1.767 +        // touch handler to detect taps on the left and right side of the screen
   1.768 +        // based on awesome work of @hakimel: https://github.com/hakimel/reveal.js
   1.769 +        document.addEventListener("touchstart", function ( event ) {
   1.770 +            if (event.touches.length === 1) {
   1.771 +                var x = event.touches[0].clientX,
   1.772 +                    width = window.innerWidth * 0.3,
   1.773 +                    result = null;
   1.774 +                    
   1.775 +                if ( x < width ) {
   1.776 +                    result = api.prev();
   1.777 +                } else if ( x > window.innerWidth - width ) {
   1.778 +                    result = api.next();
   1.779 +                }
   1.780 +                
   1.781 +                if (result) {
   1.782 +                    event.preventDefault();
   1.783 +                }
   1.784 +            }
   1.785 +        }, false);
   1.786 +        
   1.787 +        // rescale presentation when window is resized
   1.788 +        window.addEventListener("resize", throttle(function () {
   1.789 +            // force going to active step again, to trigger rescaling
   1.790 +            api.goto( document.querySelector(".active"), 500 );
   1.791 +        }, 250), false);
   1.792 +        
   1.793 +    }, false);
   1.794 +        
   1.795 +})(document, window);
   1.796 +
   1.797 +// THAT'S ALL FOLKS!
   1.798 +//
   1.799 +// Thanks for reading it all.
   1.800 +// Or thanks for scrolling down and reading the last part.
   1.801 +//
   1.802 +// I've learnt a lot when building impress.js and I hope this code and comments
   1.803 +// will help somebody learn at least some part of it.

mercurial