diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7a3a95d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/node_modules +/npm-debug.log diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..65340fe --- /dev/null +++ b/.jshintrc @@ -0,0 +1,12 @@ +{ + "boss": true, + "curly": true, + "eqeqeq": true, + "eqnull": true, + "expr": true, + "immed": true, + "noarg": true, + "quotmark": "double", + "undef": true, + "unused": true +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 72637a1..2c78620 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,3 +19,4 @@ Guidelines: * If proposing a feature, make sure to discuss that as an issue first. * Create a new [topic branch](https://github.com/dchelimsky/rspec/wiki/Topic-Branches) for every separate change you make. * Make sure impress.js runs successfully on as many browsers as you can test. +* Run `npm lint` to make sure the code is consistent with the project standards. diff --git a/js/impress.js b/js/impress.js index 2529e26..8a49994 100644 --- a/js/impress.js +++ b/js/impress.js @@ -21,208 +21,211 @@ // You are one of those who like to know how things work inside? // Let me show you the cogs that make impress.js run... -(function ( document, window ) { - 'use strict'; - +( function( document, window ) { + "use strict"; + // HELPER FUNCTIONS - + // `pfx` is a function that takes a standard CSS property name as a parameter // and returns it's prefixed version valid for current browser it runs in. // The code is heavily inspired by Modernizr http://www.modernizr.com/ - var pfx = (function () { - - var style = document.createElement('dummy').style, - prefixes = 'Webkit Moz O ms Khtml'.split(' '), + var pfx = ( function() { + + var style = document.createElement( "dummy" ).style, + prefixes = "Webkit Moz O ms Khtml".split( " " ), memory = {}; - - return function ( prop ) { + + return function( prop ) { if ( typeof memory[ prop ] === "undefined" ) { - - var ucProp = prop.charAt(0).toUpperCase() + prop.substr(1), - props = (prop + ' ' + prefixes.join(ucProp + ' ') + ucProp).split(' '); - + + var ucProp = prop.charAt( 0 ).toUpperCase() + prop.substr( 1 ), + props = ( prop + " " + prefixes.join( ucProp + " " ) + ucProp ).split( " " ); + memory[ prop ] = null; for ( var i in props ) { - if ( style[ props[i] ] !== undefined ) { - memory[ prop ] = props[i]; + if ( style[ props[ i ] ] !== undefined ) { + memory[ prop ] = props[ i ]; break; } } - + } - + return memory[ prop ]; }; - - })(); - + + } )(); + // `arraify` takes an array-like object and turns it into real Array // to make all the Array.prototype goodness available. - var arrayify = function ( a ) { + var arrayify = function( a ) { return [].slice.call( a ); }; - + // `css` function applies the styles given in `props` object to the element // given as `el`. It runs all property names through `pfx` function to make // sure proper prefixed version of the property is used. - var css = function ( el, props ) { + var css = function( el, props ) { var key, pkey; for ( key in props ) { - if ( props.hasOwnProperty(key) ) { - pkey = pfx(key); + if ( props.hasOwnProperty( key ) ) { + pkey = pfx( key ); if ( pkey !== null ) { - el.style[pkey] = props[key]; + el.style[ pkey ] = props[ key ]; } } } return el; }; - + // `toNumber` takes a value given as `numeric` parameter and tries to turn // it into a number. If it is not possible it returns 0 (or other value // given as `fallback`). - var toNumber = function (numeric, fallback) { - return isNaN(numeric) ? (fallback || 0) : Number(numeric); + var toNumber = function( numeric, fallback ) { + return isNaN( numeric ) ? ( fallback || 0 ) : Number( numeric ); }; - + // `byId` returns element with given `id` - you probably have guessed that ;) - var byId = function ( id ) { - return document.getElementById(id); + var byId = function( id ) { + return document.getElementById( id ); }; - + // `$` returns first element for given CSS `selector` in the `context` of // the given element or whole document. - var $ = function ( selector, context ) { + var $ = function( selector, context ) { context = context || document; - return context.querySelector(selector); + return context.querySelector( selector ); }; - + // `$$` return an array of elements for given CSS `selector` in the `context` of // the given element or whole document. - var $$ = function ( selector, context ) { + var $$ = function( selector, context ) { context = context || document; - return arrayify( context.querySelectorAll(selector) ); + return arrayify( context.querySelectorAll( selector ) ); }; - + // `triggerEvent` builds a custom DOM event with given `eventName` and `detail` data // and triggers it on element given as `el`. - var triggerEvent = function (el, eventName, detail) { - var event = document.createEvent("CustomEvent"); - event.initCustomEvent(eventName, true, true, detail); - el.dispatchEvent(event); + var triggerEvent = function( el, eventName, detail ) { + var event = document.createEvent( "CustomEvent" ); + event.initCustomEvent( eventName, true, true, detail ); + el.dispatchEvent( event ); }; - + // `translate` builds a translate transform string for given data. - var translate = function ( t ) { + var translate = function( t ) { return " translate3d(" + t.x + "px," + t.y + "px," + t.z + "px) "; }; - + // `rotate` builds a rotate transform string for given data. // By default the rotations are in X Y Z order that can be reverted by passing `true` // as second parameter. - var rotate = function ( r, revert ) { + var rotate = function( r, revert ) { var rX = " rotateX(" + r.x + "deg) ", rY = " rotateY(" + r.y + "deg) ", rZ = " rotateZ(" + r.z + "deg) "; - - return revert ? rZ+rY+rX : rX+rY+rZ; + + return revert ? rZ + rY + rX : rX + rY + rZ; }; - + // `scale` builds a scale transform string for given data. - var scale = function ( s ) { + var scale = function( s ) { return " scale(" + s + ") "; }; - + // `perspective` builds a perspective transform string for given data. - var perspective = function ( p ) { + var perspective = function( p ) { return " perspective(" + p + "px) "; }; - + // `getElementFromHash` returns an element located by id from hash part of // window location. - var getElementFromHash = function () { - // get id from url # by removing `#` or `#/` from the beginning, + var getElementFromHash = function() { + + // Get id from url # by removing `#` or `#/` from the beginning, // so both "fallback" `#slide-id` and "enhanced" `#/slide-id` will work - return byId( window.location.hash.replace(/^#\/?/,"") ); + return byId( window.location.hash.replace( /^#\/?/, "" ) ); }; - + // `computeWindowScale` counts the scale factor between window size and size // defined for the presentation in the config. - var computeWindowScale = function ( config ) { + var computeWindowScale = function( config ) { var hScale = window.innerHeight / config.height, wScale = window.innerWidth / config.width, scale = hScale > wScale ? wScale : hScale; - - if (config.maxScale && scale > config.maxScale) { + + if ( config.maxScale && scale > config.maxScale ) { scale = config.maxScale; } - - if (config.minScale && scale < config.minScale) { + + if ( config.minScale && scale < config.minScale ) { scale = config.minScale; } - + return scale; }; - + // CHECK SUPPORT var body = document.body; - + var ua = navigator.userAgent.toLowerCase(); - var impressSupported = - // browser should support CSS 3D transtorms - ( pfx("perspective") !== null ) && - - // and `classList` and `dataset` APIs + var impressSupported = + + // Browser should support CSS 3D transtorms + ( pfx( "perspective" ) !== null ) && + + // Browser should support `classList` and `dataset` APIs ( body.classList ) && ( body.dataset ) && - - // but some mobile devices need to be blacklisted, + + // But some mobile devices need to be blacklisted, // because their CSS 3D support or hardware is not // good enough to run impress.js properly, sorry... - ( ua.search(/(iphone)|(ipod)|(android)/) === -1 ); - - if (!impressSupported) { - // we can't be sure that `classList` is supported + ( ua.search( /(iphone)|(ipod)|(android)/ ) === -1 ); + + if ( !impressSupported ) { + + // We can't be sure that `classList` is supported body.className += " impress-not-supported "; } else { - body.classList.remove("impress-not-supported"); - body.classList.add("impress-supported"); + body.classList.remove( "impress-not-supported" ); + body.classList.add( "impress-supported" ); } - + // GLOBALS AND DEFAULTS - + // This is where the root elements of all impress.js instances will be kept. // Yes, this means you can have more than one instance on a page, but I'm not // sure if it makes any sense in practice ;) var roots = {}; - - // some default config values. + + // Some default config values. var defaults = { width: 1024, height: 768, maxScale: 1, minScale: 0, - + perspective: 1000, - + transitionDuration: 1000 }; - - // it's just an empty function ... and a useless comment. - var empty = function () { return false; }; - + + // It's just an empty function ... and a useless comment. + var empty = function() { return false; }; + // IMPRESS.JS API - + // And that's where interesting things will start to happen. // It's the core `impress` function that returns the impress.js API // for a presentation based on the element with given id ('impress' // by default). - var impress = window.impress = function ( rootId ) { - + var impress = window.impress = function( rootId ) { + // If impress.js is not supported by the browser return a dummy API // it may not be a perfect solution but we return early and avoid // running code that may use features not implemented in the browser. - if (!impressSupported) { + if ( !impressSupported ) { return { init: empty, goto: empty, @@ -230,203 +233,207 @@ next: empty }; } - + rootId = rootId || "impress"; - - // if given root is already initialized just return the API - if (roots["impress-root-" + rootId]) { - return roots["impress-root-" + rootId]; + + // If given root is already initialized just return the API + if ( roots[ "impress-root-" + rootId ] ) { + return roots[ "impress-root-" + rootId ]; } - - // data of all presentation steps + + // Data of all presentation steps var stepsData = {}; - - // element of currently active step + + // Element of currently active step var activeStep = null; - - // current state (position, rotation and scale) of the presentation + + // Current state (position, rotation and scale) of the presentation var currentState = null; - - // array of step elements + + // Array of step elements var steps = null; - - // configuration options + + // Configuration options var config = null; - - // scale factor of the browser window - var windowScale = null; - - // root presentation elements + + // Scale factor of the browser window + var windowScale = null; + + // Root presentation elements var root = byId( rootId ); - var canvas = document.createElement("div"); - + var canvas = document.createElement( "div" ); + var initialized = false; - + // STEP EVENTS // // There are currently two step events triggered by impress.js - // `impress:stepenter` is triggered when the step is shown on the + // `impress:stepenter` is triggered when the step is shown on the // screen (the transition from the previous one is finished) and // `impress:stepleave` is triggered when the step is left (the // transition to next step just starts). - - // reference to last entered step + + // Reference to last entered step var lastEntered = null; - + // `onStepEnter` is called whenever the step element is entered // but the event is triggered only if the step is different than // last entered step. - var onStepEnter = function (step) { - if (lastEntered !== step) { - triggerEvent(step, "impress:stepenter"); + var onStepEnter = function( step ) { + if ( lastEntered !== step ) { + triggerEvent( step, "impress:stepenter" ); lastEntered = step; } }; - + // `onStepLeave` is called whenever the step element is left // but the event is triggered only if the step is the same as // last entered step. - var onStepLeave = function (step) { - if (lastEntered === step) { - triggerEvent(step, "impress:stepleave"); + var onStepLeave = function( step ) { + if ( lastEntered === step ) { + triggerEvent( step, "impress:stepleave" ); lastEntered = null; } }; - + // `initStep` initializes given step element by reading data from its // data attributes and setting correct styles. - var initStep = function ( el, idx ) { + var initStep = function( el, idx ) { var data = el.dataset, step = { translate: { - x: toNumber(data.x), - y: toNumber(data.y), - z: toNumber(data.z) + x: toNumber( data.x ), + y: toNumber( data.y ), + z: toNumber( data.z ) }, rotate: { - x: toNumber(data.rotateX), - y: toNumber(data.rotateY), - z: toNumber(data.rotateZ || data.rotate) + x: toNumber( data.rotateX ), + y: toNumber( data.rotateY ), + z: toNumber( data.rotateZ || data.rotate ) }, - scale: toNumber(data.scale, 1), + scale: toNumber( data.scale, 1 ), el: el }; - + if ( !el.id ) { - el.id = "step-" + (idx + 1); + el.id = "step-" + ( idx + 1 ); } - - stepsData["impress-" + el.id] = step; - - css(el, { + + stepsData[ "impress-" + el.id ] = step; + + css( el, { position: "absolute", transform: "translate(-50%,-50%)" + - translate(step.translate) + - rotate(step.rotate) + - scale(step.scale), + translate( step.translate ) + + rotate( step.rotate ) + + scale( step.scale ), transformStyle: "preserve-3d" - }); + } ); }; - + // `init` API function that initializes (and runs) the presentation. - var init = function () { - if (initialized) { return; } - + var init = function() { + if ( initialized ) { return; } + // First we set up the viewport for mobile devices. // For some reason iPad goes nuts when it is not done properly. - var meta = $("meta[name='viewport']") || document.createElement("meta"); + var meta = $( "meta[name='viewport']" ) || document.createElement( "meta" ); meta.content = "width=device-width, minimum-scale=1, maximum-scale=1, user-scalable=no"; - if (meta.parentNode !== document.head) { - meta.name = 'viewport'; - document.head.appendChild(meta); + if ( meta.parentNode !== document.head ) { + meta.name = "viewport"; + document.head.appendChild( meta ); } - - // initialize configuration object + + // Initialize configuration object var rootData = root.dataset; config = { width: toNumber( rootData.width, defaults.width ), height: toNumber( rootData.height, defaults.height ), maxScale: toNumber( rootData.maxScale, defaults.maxScale ), - minScale: toNumber( rootData.minScale, defaults.minScale ), + minScale: toNumber( rootData.minScale, defaults.minScale ), perspective: toNumber( rootData.perspective, defaults.perspective ), - transitionDuration: toNumber( rootData.transitionDuration, defaults.transitionDuration ) + transitionDuration: toNumber( + rootData.transitionDuration, defaults.transitionDuration + ) }; - + windowScale = computeWindowScale( config ); - - // wrap steps with "canvas" element - arrayify( root.childNodes ).forEach(function ( el ) { + + // Wrap steps with "canvas" element + arrayify( root.childNodes ).forEach( function( el ) { canvas.appendChild( el ); - }); - root.appendChild(canvas); - - // set initial styles + } ); + root.appendChild( canvas ); + + // Set initial styles document.documentElement.style.height = "100%"; - - css(body, { + + css( body, { height: "100%", overflow: "hidden" - }); - + } ); + var rootStyles = { position: "absolute", transformOrigin: "top left", transition: "all 0s ease-in-out", transformStyle: "preserve-3d" }; - - css(root, rootStyles); - css(root, { + + css( root, rootStyles ); + css( root, { top: "50%", left: "50%", - transform: perspective( config.perspective/windowScale ) + scale( windowScale ) - }); - css(canvas, rootStyles); - - body.classList.remove("impress-disabled"); - body.classList.add("impress-enabled"); - - // get and init steps - steps = $$(".step", root); + transform: perspective( config.perspective / windowScale ) + scale( windowScale ) + } ); + css( canvas, rootStyles ); + + body.classList.remove( "impress-disabled" ); + body.classList.add( "impress-enabled" ); + + // Get and init steps + steps = $$( ".step", root ); steps.forEach( initStep ); - - // set a default initial state of the canvas + + // Set a default initial state of the canvas currentState = { translate: { x: 0, y: 0, z: 0 }, rotate: { x: 0, y: 0, z: 0 }, scale: 1 }; - + initialized = true; - - triggerEvent(root, "impress:init", { api: roots[ "impress-root-" + rootId ] }); + + triggerEvent( root, "impress:init", { api: roots[ "impress-root-" + rootId ] } ); }; - + // `getStep` is a helper function that returns a step element defined by parameter. // If a number is given, step with index given by the number is returned, if a string // is given step element with such id is returned, if DOM element is given it is returned // if it is a correct step element. - var getStep = function ( step ) { - if (typeof step === "number") { - step = step < 0 ? steps[ steps.length + step] : steps[ step ]; - } else if (typeof step === "string") { - step = byId(step); + var getStep = function( step ) { + if ( typeof step === "number" ) { + step = step < 0 ? steps[ steps.length + step ] : steps[ step ]; + } else if ( typeof step === "string" ) { + step = byId( step ); } - return (step && step.id && stepsData["impress-" + step.id]) ? step : null; + return ( step && step.id && stepsData[ "impress-" + step.id ] ) ? step : null; }; - - // used to reset timeout for `impress:stepenter` event + + // Used to reset timeout for `impress:stepenter` event var stepEnterTimeout = null; - - // `goto` API function that moves to step given with `el` parameter (by index, id or element), - // with a transition `duration` optionally given as second parameter. - var goto = function ( el, duration ) { - - if ( !initialized || !(el = getStep(el)) ) { - // presentation not initialized or given element is not a step + + // `goto` API function that moves to step given with `el` parameter + // (by index, id or element), with a transition `duration` optionally + // given as second parameter. + var goto = function( el, duration ) { + + if ( !initialized || !( el = getStep( el ) ) ) { + + // Presentation not initialized or given element is not a step return false; } - + // Sometimes it's possible to trigger focus on first link with some keyboard action. // Browser in such a case tries to scroll the page to make this element visible // (even that body overflow is set to hidden) and it breaks our careful positioning. @@ -434,20 +441,21 @@ // So, as a lousy (and lazy) workaround we will make the page scroll back to the top // whenever slide is selected // - // If you are reading this and know any better way to handle it, I'll be glad to hear about it! - window.scrollTo(0, 0); - - var step = stepsData["impress-" + el.id]; - + // If you are reading this and know any better way to handle it, I'll be glad to hear + // about it! + window.scrollTo( 0, 0 ); + + var step = stepsData[ "impress-" + el.id ]; + if ( activeStep ) { - activeStep.classList.remove("active"); - body.classList.remove("impress-on-" + activeStep.id); + activeStep.classList.remove( "active" ); + body.classList.remove( "impress-on-" + activeStep.id ); } - el.classList.add("active"); - - body.classList.add("impress-on-" + el.id); - - // compute target state of the canvas based on given step + el.classList.add( "active" ); + + body.classList.add( "impress-on-" + el.id ); + + // Compute target state of the canvas based on given step var target = { rotate: { x: -step.rotate.x, @@ -461,7 +469,7 @@ }, scale: 1 / step.scale }; - + // Check if the transition is zooming in or not. // // This information is used to alter the transition style: @@ -469,23 +477,23 @@ // and the scaling is delayed, but when we are zooming out we start // with scaling down and move and rotation are delayed. var zoomin = target.scale >= currentState.scale; - - duration = toNumber(duration, config.transitionDuration); - var delay = (duration / 2); - - // if the same step is re-selected, force computing window scaling, + + duration = toNumber( duration, config.transitionDuration ); + var delay = ( duration / 2 ); + + // If the same step is re-selected, force computing window scaling, // because it is likely to be caused by window resize - if (el === activeStep) { - windowScale = computeWindowScale(config); + if ( el === activeStep ) { + windowScale = computeWindowScale( config ); } - + var targetScale = target.scale * windowScale; - - // trigger leave of currently active element (if it's not the same step again) - if (activeStep && activeStep !== el) { - onStepLeave(activeStep); + + // Trigger leave of currently active element (if it's not the same step again) + if ( activeStep && activeStep !== el ) { + onStepLeave( activeStep ); } - + // Now we alter transforms of `root` and `canvas` to trigger transitions. // // And here is why there are two elements: `root` and `canvas` - they are @@ -494,76 +502,88 @@ // Transitions on them are triggered with different delays (to make // visually nice and 'natural' looking transitions), so we need to know // that both of them are finished. - css(root, { - // to keep the perspective look similar for different scales + css( root, { + + // To keep the perspective look similar for different scales // we need to 'scale' the perspective, too transform: perspective( config.perspective / targetScale ) + scale( targetScale ), transitionDuration: duration + "ms", - transitionDelay: (zoomin ? delay : 0) + "ms" - }); - - css(canvas, { - transform: rotate(target.rotate, true) + translate(target.translate), + transitionDelay: ( zoomin ? delay : 0 ) + "ms" + } ); + + css( canvas, { + transform: rotate( target.rotate, true ) + translate( target.translate ), transitionDuration: duration + "ms", - transitionDelay: (zoomin ? 0 : delay) + "ms" - }); - + transitionDelay: ( zoomin ? 0 : delay ) + "ms" + } ); + // Here is a tricky part... // - // If there is no change in scale or no change in rotation and translation, it means there was actually - // no delay - because there was no transition on `root` or `canvas` elements. - // We want to trigger `impress:stepenter` event in the correct moment, so here we compare the current - // and target values to check if delay should be taken into account. + // If there is no change in scale or no change in rotation and translation, it means + // there was actually no delay - because there was no transition on `root` or `canvas` + // elements. We want to trigger `impress:stepenter` event in the correct moment, so + // here we compare the current and target values to check if delay should be taken into + // account. // - // I know that this `if` statement looks scary, but it's pretty simple when you know what is going on + // I know that this `if` statement looks scary, but it's pretty simple when you know + // what is going on // - it's simply comparing all the values. if ( currentState.scale === target.scale || - (currentState.rotate.x === target.rotate.x && currentState.rotate.y === target.rotate.y && - currentState.rotate.z === target.rotate.z && currentState.translate.x === target.translate.x && - currentState.translate.y === target.translate.y && currentState.translate.z === target.translate.z) ) { + ( currentState.rotate.x === target.rotate.x && + currentState.rotate.y === target.rotate.y && + currentState.rotate.z === target.rotate.z && + currentState.translate.x === target.translate.x && + currentState.translate.y === target.translate.y && + currentState.translate.z === target.translate.z ) ) { delay = 0; } - - // store current state + + // Store current state currentState = target; activeStep = el; - + // And here is where we trigger `impress:stepenter` event. - // We simply set up a timeout to fire it taking transition duration (and possible delay) into account. + // We simply set up a timeout to fire it taking transition duration + // (and possible delay) into account. // - // I really wanted to make it in more elegant way. The `transitionend` event seemed to be the best way - // to do it, but the fact that I'm using transitions on two separate elements and that the `transitionend` - // event is only triggered when there was a transition (change in the values) caused some bugs and - // made the code really complicated, cause I had to handle all the conditions separately. And it still - // needed a `setTimeout` fallback for the situations when there is no transition at all. - // So I decided that I'd rather make the code simpler than use shiny new `transitionend`. + // I really wanted to make it in more elegant way. The `transitionend` event seemed to + // be the best way to do it, but the fact that I'm using transitions on two separate + // elements and that the `transitionend` event is only triggered when there was a + // transition (change in the values) caused some bugs and made the code really + // complicated, cause I had to handle all the conditions separately. And it still + // needed a `setTimeout` fallback for the situations when there is no transition at + // all. + // So I decided that I'd rather make the code simpler than use shiny new + // `transitionend`. // - // If you want learn something interesting and see how it was done with `transitionend` go back to - // version 0.5.2 of impress.js: http://github.com/bartaz/impress.js/blob/0.5.2/js/impress.js - window.clearTimeout(stepEnterTimeout); - stepEnterTimeout = window.setTimeout(function() { - onStepEnter(activeStep); - }, duration + delay); - + // If you want learn something interesting and see how it was done with `transitionend` + // go back to + // version 0.5.2 of impress.js: + // http://github.com/bartaz/impress.js/blob/0.5.2/js/impress.js + window.clearTimeout( stepEnterTimeout ); + stepEnterTimeout = window.setTimeout( function() { + onStepEnter( activeStep ); + }, duration + delay ); + return el; }; - + // `prev` API function goes to previous step (in document order) - var prev = function () { + var prev = function() { var prev = steps.indexOf( activeStep ) - 1; - prev = prev >= 0 ? steps[ prev ] : steps[ steps.length-1 ]; - - return goto(prev); + prev = prev >= 0 ? steps[ prev ] : steps[ steps.length - 1 ]; + + return goto( prev ); }; - + // `next` API function goes to next step (in document order) - var next = function () { + var next = function() { var next = steps.indexOf( activeStep ) + 1; next = next < steps.length ? steps[ next ] : steps[ 0 ]; - - return goto(next); + + return goto( next ); }; - + // Adding some useful classes to step elements. // // All the steps that have not been shown yet are given `future` class. @@ -577,73 +597,75 @@ // There classes can be used in CSS to style different types of steps. // For example the `present` class can be used to trigger some custom // animations when step is shown. - root.addEventListener("impress:init", function(){ + root.addEventListener( "impress:init", function() { + // STEP CLASSES - steps.forEach(function (step) { - step.classList.add("future"); - }); - - root.addEventListener("impress:stepenter", function (event) { - event.target.classList.remove("past"); - event.target.classList.remove("future"); - event.target.classList.add("present"); - }, false); - - root.addEventListener("impress:stepleave", function (event) { - event.target.classList.remove("present"); - event.target.classList.add("past"); - }, false); - - }, false); - + steps.forEach( function( step ) { + step.classList.add( "future" ); + } ); + + root.addEventListener( "impress:stepenter", function( event ) { + event.target.classList.remove( "past" ); + event.target.classList.remove( "future" ); + event.target.classList.add( "present" ); + }, false ); + + root.addEventListener( "impress:stepleave", function( event ) { + event.target.classList.remove( "present" ); + event.target.classList.add( "past" ); + }, false ); + + }, false ); + // Adding hash change support. - root.addEventListener("impress:init", function(){ - - // last hash detected + root.addEventListener( "impress:init", function() { + + // Last hash detected var lastHash = ""; - + // `#/step-id` is used instead of `#step-id` to prevent default browser // scrolling to element in hash. // // And it has to be set after animation finishes, because in Chrome it // makes transtion laggy. // BUG: http://code.google.com/p/chromium/issues/detail?id=62820 - root.addEventListener("impress:stepenter", function (event) { + root.addEventListener( "impress:stepenter", function( event ) { window.location.hash = lastHash = "#/" + event.target.id; - }, false); - - window.addEventListener("hashchange", function () { + }, false ); + + window.addEventListener( "hashchange", function() { + // When the step is entered hash in the location is updated - // (just few lines above from here), so the hash change is + // (just few lines above from here), so the hash change is // triggered and we would call `goto` again on the same element. // // To avoid this we store last entered hash and compare. - if (window.location.hash !== lastHash) { + if ( window.location.hash !== lastHash ) { goto( getElementFromHash() ); } - }, false); - - // START + }, false ); + + // START // by selecting step defined in url or first step of the presentation - goto(getElementFromHash() || steps[0], 0); - }, false); - - body.classList.add("impress-disabled"); - - // store and return API for given impress.js root element - return (roots[ "impress-root-" + rootId ] = { + goto( getElementFromHash() || steps[ 0 ], 0 ); + }, false ); + + body.classList.add( "impress-disabled" ); + + // Store and return API for given impress.js root element + return ( roots[ "impress-root-" + rootId ] = { init: init, goto: goto, next: next, prev: prev - }); + } ); }; - - // flag that can be used in JS to check if browser have passed the support test + + // Flag that can be used in JS to check if browser have passed the support test impress.supported = impressSupported; - -})(document, window); + +} )( document, window ); // NAVIGATION EVENTS @@ -653,41 +675,44 @@ // // In future I think about moving it to make them optional, move to separate files // and treat more like a 'plugins'. -(function ( document, window ) { - 'use strict'; - - // throttling function calls, by Remy Sharp +( function( document, window ) { + "use strict"; + + // Throttling function calls, by Remy Sharp // http://remysharp.com/2010/07/21/throttling-function-calls/ - var throttle = function (fn, delay) { + var throttle = function( fn, delay ) { var timer = null; - return function () { + return function() { var context = this, args = arguments; - clearTimeout(timer); - timer = setTimeout(function () { - fn.apply(context, args); - }, delay); + clearTimeout( timer ); + timer = setTimeout( function() { + fn.apply( context, args ); + }, delay ); }; }; - - // wait for impress.js to be initialized - document.addEventListener("impress:init", function (event) { + + // Wait for impress.js to be initialized + document.addEventListener( "impress:init", function( event ) { + // Getting API from event data. // So you don't event need to know what is the id of the root element - // or anything. `impress:init` event data gives you everything you + // or anything. `impress:init` event data gives you everything you // need to control the presentation that was just initialized. var api = event.detail.api; - + // KEYBOARD NAVIGATION HANDLERS - + // Prevent default keydown action when one of supported key is pressed. - document.addEventListener("keydown", function ( event ) { - if ( event.keyCode === 9 || ( event.keyCode >= 32 && event.keyCode <= 34 ) || (event.keyCode >= 37 && event.keyCode <= 40) ) { + document.addEventListener( "keydown", function( event ) { + if ( event.keyCode === 9 || + ( event.keyCode >= 32 && event.keyCode <= 34 ) || + ( event.keyCode >= 37 && event.keyCode <= 40 ) ) { event.preventDefault(); } - }, false); - + }, false ); + // Trigger impress action (next or prev) on keyup. - + // Supported keys are: // [space] - quite common in presentation software to move forward // [up] [right] / [down] [left] - again common and natural addition, @@ -701,100 +726,106 @@ // positioning. I didn't want to just prevent this default action, so I used [tab] // as another way to moving to next step... And yes, I know that for the sake of // consistency I should add [shift+tab] as opposite action... - document.addEventListener("keyup", function ( event ) { + document.addEventListener( "keyup", function( event ) { - if ( event.shiftKey || event.altKey || event.ctrlKey || event.metaKey ){ + if ( event.shiftKey || event.altKey || event.ctrlKey || event.metaKey ) { return; } - - if ( event.keyCode === 9 || ( event.keyCode >= 32 && event.keyCode <= 34 ) || (event.keyCode >= 37 && event.keyCode <= 40) ) { - switch( event.keyCode ) { - case 33: // pg up - case 37: // left - case 38: // up + + if ( event.keyCode === 9 || + ( event.keyCode >= 32 && event.keyCode <= 34 ) || + ( event.keyCode >= 37 && event.keyCode <= 40 ) ) { + switch ( event.keyCode ) { + case 33: // Page up + case 37: // Left + case 38: // Up api.prev(); break; - case 9: // tab - case 32: // space - case 34: // pg down - case 39: // right - case 40: // down + case 9: // Tab + case 32: // Space + case 34: // Page down + case 39: // Right + case 40: // Down api.next(); break; } - + event.preventDefault(); } - }, false); - - // delegated handler for clicking on the links to presentation steps - document.addEventListener("click", function ( event ) { - // event delegation with "bubbling" - // check if event target (or any of its parents is a link) + }, false ); + + // Delegated handler for clicking on the links to presentation steps + document.addEventListener( "click", function( event ) { + + // Event delegation with "bubbling" + // Check if event target (or any of its parents is a link) var target = event.target; - while ( (target.tagName !== "A") && - (target !== document.documentElement) ) { + while ( ( target.tagName !== "A" ) && + ( target !== document.documentElement ) ) { target = target.parentNode; } - + if ( target.tagName === "A" ) { - var href = target.getAttribute("href"); - - // if it's a link to presentation step, target this step - if ( href && href[0] === '#' ) { - target = document.getElementById( href.slice(1) ); + var href = target.getAttribute( "href" ); + + // If it's a link to presentation step, target this step + if ( href && href[ 0 ] === "#" ) { + target = document.getElementById( href.slice( 1 ) ); } } - - if ( api.goto(target) ) { + + if ( api.goto( target ) ) { event.stopImmediatePropagation(); event.preventDefault(); } - }, false); - - // delegated handler for clicking on step elements - document.addEventListener("click", function ( event ) { + }, false ); + + // Delegated handler for clicking on step elements + document.addEventListener( "click", function( event ) { var target = event.target; - // find closest step element that is not active - while ( !(target.classList.contains("step") && !target.classList.contains("active")) && - (target !== document.documentElement) ) { + + // Find closest step element that is not active + while ( !( target.classList.contains( "step" ) && + !target.classList.contains( "active" ) ) && + ( target !== document.documentElement ) ) { target = target.parentNode; } - - if ( api.goto(target) ) { + + if ( api.goto( target ) ) { event.preventDefault(); } - }, false); - - // touch handler to detect taps on the left and right side of the screen + }, false ); + + // Touch handler to detect taps on the left and right side of the screen // based on awesome work of @hakimel: https://github.com/hakimel/reveal.js - document.addEventListener("touchstart", function ( event ) { - if (event.touches.length === 1) { - var x = event.touches[0].clientX, + document.addEventListener( "touchstart", function( event ) { + if ( event.touches.length === 1 ) { + var x = event.touches[ 0 ].clientX, width = window.innerWidth * 0.3, result = null; - + if ( x < width ) { result = api.prev(); } else if ( x > window.innerWidth - width ) { result = api.next(); } - - if (result) { + + if ( result ) { event.preventDefault(); } } - }, false); - - // rescale presentation when window is resized - window.addEventListener("resize", throttle(function () { - // force going to active step again, to trigger rescaling - api.goto( document.querySelector(".step.active"), 500 ); - }, 250), false); - - }, false); - -})(document, window); + }, false ); + + // Rescale presentation when window is resized + window.addEventListener( "resize", throttle( function() { + + // Force going to active step again, to trigger rescaling + api.goto( document.querySelector( ".step.active" ), 500 ); + }, 250 ), false ); + + }, false ); + +} )( document, window ); // THAT'S ALL FOLKS! // diff --git a/package.json b/package.json new file mode 100644 index 0000000..713ba5f --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "impress.js", + "version": "0.5.3", + "description": "It's a presentation framework based on the power of CSS3 transforms and transitions in modern browsers and inspired by the idea behind prezi.com.", + "main": "js/impress.js", + "repository": { + "type": "git", + "url": "https://github.com/impress/impress.js.git" + }, + "keywords": [ + "presentation", + "slides", + "slideshow", + "css3", + "transitions", + "transforms", + "browser" + ], + "author": "Bartek Szopka", + "license": "MIT", + "bugs": { + "url": "https://github.com/bartaz/impress.js/issues" + }, + "scripts": { + "lint": "jshint js/impress.js && jscs js/impress.js --preset=jquery" + }, + "devDependencies": { + "jscs": "2.11.0", + "jshint": "2.9.1" + } +}