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.