From 9641f5750ba9a8a46c2bf4e5f7cdec192cc39166 Mon Sep 17 00:00:00 2001 From: Bartek Szopka Date: Mon, 30 Jan 2012 20:35:47 +0100 Subject: [PATCH 1/7] step data moved from DOM to internal data structure --- js/impress.js | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/js/impress.js b/js/impress.js index c96f91b..34339f5 100644 --- a/js/impress.js +++ b/js/impress.js @@ -146,6 +146,8 @@ scale: 1 }; + var stepData = {}; + steps.forEach(function ( el, idx ) { var data = el.dataset, step = { @@ -159,15 +161,16 @@ y: data.rotateY || 0, z: data.rotateZ || data.rotate || 0 }, - scale: data.scale || 1 + scale: data.scale || 1, + el: el }; - el.stepData = step; - if ( !el.id ) { el.id = "step-" + (idx + 1); } + stepData["impress-" + el.id] = step; + css(el, { position: "absolute", transform: "translate(-50%,-50%)" + @@ -184,8 +187,12 @@ var active = null; var hashTimeout = null; + var isStep = function ( el ) { + return !!(el && el.id && stepData["impress-" + el.id]); + } + var select = function ( el ) { - if ( !el || !el.stepData || el == active) { + if ( !el || !isStep(el) || el == active) { // selected element is not defined as step or is already active return false; } @@ -200,7 +207,7 @@ // 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 = el.stepData; + var step = stepData["impress-" + el.id]; if ( active ) { active.classList.remove("active"); @@ -303,7 +310,7 @@ // check if event target (or any of its parents is a link or a step) var target = event.target; while ( (target.tagName != "A") && - (!target.stepData) && + (!isStep(target)) && (target != document.body) ) { target = target.parentNode; } @@ -335,6 +342,6 @@ // START // by selecting step defined in url or first step of the presentation select(getElementFromUrl() || steps[0]); - + })(document, window); From cfe6448594157e3c5ce2383525ce6f23f0a07bb3 Mon Sep 17 00:00:00 2001 From: Bartek Szopka Date: Wed, 1 Feb 2012 21:28:55 +0100 Subject: [PATCH 2/7] impressive API --- index.html | 1 + js/impress.js | 415 +++++++++++++++++++++++++++----------------------- 2 files changed, 228 insertions(+), 188 deletions(-) diff --git a/index.html b/index.html index 65bc93c..716b492 100644 --- a/index.html +++ b/index.html @@ -286,6 +286,7 @@ --> + diff --git a/js/impress.js b/js/impress.js index 34339f5..0e810a6 100644 --- a/js/impress.js +++ b/js/impress.js @@ -86,7 +86,13 @@ var scale = function ( s ) { return " scale(" + s + ") "; - } + }; + + var getElementFromUrl = 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(/^#\/?/,"") ); + }; // CHECK SUPPORT @@ -94,223 +100,255 @@ var impressSupported = ( pfx("perspective") != null ) && ( ua.search(/(iphone)|(ipod)|(ipad)|(android)/) == -1 ); - // DOM ELEMENTS + var roots = {}; - var impress = byId("impress"); - - if (!impressSupported) { - impress.className = "impress-not-supported"; - return; - } else { - impress.className = ""; - } - - var canvas = document.createElement("div"); - canvas.className = "canvas"; - - arrayify( impress.childNodes ).forEach(function ( el ) { - canvas.appendChild( el ); - }); - impress.appendChild(canvas); - - var steps = $$(".step", impress); - - // SETUP - // set initial values and defaults - - document.documentElement.style.height = "100%"; - - css(document.body, { - height: "100%", - overflow: "hidden" - }); + var impress = window.impress = function ( rootId ) { - var props = { - position: "absolute", - transformOrigin: "top left", - transition: "all 0s ease-in-out", - transformStyle: "preserve-3d" - } - - css(impress, props); - css(impress, { - top: "50%", - left: "50%", - perspective: "1000px" - }); - css(canvas, props); - - var current = { - translate: { x: 0, y: 0, z: 0 }, - rotate: { x: 0, y: 0, z: 0 }, - scale: 1 - }; - - var stepData = {}; - - steps.forEach(function ( el, idx ) { - var data = el.dataset, - step = { - translate: { - x: data.x || 0, - y: data.y || 0, - z: data.z || 0 - }, - rotate: { - x: data.rotateX || 0, - y: data.rotateY || 0, - z: data.rotateZ || data.rotate || 0 - }, - scale: data.scale || 1, - el: el - }; + rootId = rootId || "impress"; - if ( !el.id ) { - el.id = "step-" + (idx + 1); + // if already initialized just return the API + if (roots["impress-root-" + rootId]) { + return roots["impress-root-" + rootId]; } - stepData["impress-" + el.id] = step; + // DOM ELEMENTS - css(el, { - position: "absolute", - transform: "translate(-50%,-50%)" + - translate(step.translate) + - rotate(step.rotate) + - scale(step.scale), - transformStyle: "preserve-3d" + var root = byId( rootId ); + + if (!impressSupported) { + root.className = "impress-not-supported"; + return; + } else { + root.className = ""; + } + + var canvas = document.createElement("div"); + canvas.className = "canvas"; + + arrayify( root.childNodes ).forEach(function ( el ) { + canvas.appendChild( el ); }); + root.appendChild(canvas); - }); + var steps = $$(".step", root); + + // SETUP + // set initial values and defaults + + document.documentElement.style.height = "100%"; + + css(document.body, { + height: "100%", + overflow: "hidden" + }); - // making given step active - - var active = null; - var hashTimeout = null; - - var isStep = function ( el ) { - return !!(el && el.id && stepData["impress-" + el.id]); - } - - var select = function ( el ) { - if ( !el || !isStep(el) || el == active) { - // selected element is not defined as step or is already active - return false; + var props = { + position: "absolute", + transformOrigin: "top left", + transition: "all 0s ease-in-out", + transformStyle: "preserve-3d" } - // 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. - // - // 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); + css(root, props); + css(root, { + top: "50%", + left: "50%", + perspective: "1000px" + }); + css(canvas, props); - var step = stepData["impress-" + el.id]; + var current = { + translate: { x: 0, y: 0, z: 0 }, + rotate: { x: 0, y: 0, z: 0 }, + scale: 1 + }; + + var stepData = {}; - if ( active ) { - active.classList.remove("active"); + var isStep = function ( el ) { + return !!(el && el.id && stepData["impress-" + el.id]); } - el.classList.add("active"); - impress.className = "step-" + el.id; + steps.forEach(function ( el, idx ) { + var data = el.dataset, + step = { + translate: { + x: data.x || 0, + y: data.y || 0, + z: data.z || 0 + }, + rotate: { + x: data.rotateX || 0, + y: data.rotateY || 0, + z: data.rotateZ || data.rotate || 0 + }, + scale: data.scale || 1, + el: el + }; + + if ( !el.id ) { + el.id = "step-" + (idx + 1); + } + + stepData["impress-" + el.id] = step; + + css(el, { + position: "absolute", + transform: "translate(-50%,-50%)" + + translate(step.translate) + + rotate(step.rotate) + + scale(step.scale), + transformStyle: "preserve-3d" + }); + + }); + + // making given step active + + var active = null; + var hashTimeout = null; - // `#/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 - // causes transtion being laggy - window.clearTimeout( hashTimeout ); - hashTimeout = window.setTimeout(function () { - window.location.hash = "#/" + el.id; - }, 1000); - - var target = { - rotate: { - x: -parseInt(step.rotate.x, 10), - y: -parseInt(step.rotate.y, 10), - z: -parseInt(step.rotate.z, 10) - }, - translate: { - x: -step.translate.x, - y: -step.translate.y, - z: -step.translate.z - }, - scale: 1 / parseFloat(step.scale) + var goto = function ( el ) { + if ( !isStep(el) || el == active) { + // selected element is not defined as step or is already active + 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. + // + // 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 = stepData["impress-" + el.id]; + + if ( active ) { + active.classList.remove("active"); + } + el.classList.add("active"); + + root.className = "step-" + el.id; + + // `#/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 + // causes transtion being laggy + window.clearTimeout( hashTimeout ); + hashTimeout = window.setTimeout(function () { + window.location.hash = "#/" + el.id; + }, 1000); + + var target = { + rotate: { + x: -parseInt(step.rotate.x, 10), + y: -parseInt(step.rotate.y, 10), + z: -parseInt(step.rotate.z, 10) + }, + translate: { + x: -step.translate.x, + y: -step.translate.y, + z: -step.translate.z + }, + scale: 1 / parseFloat(step.scale) + }; + + // check if the transition is zooming in or not + var zoomin = target.scale >= current.scale; + + // if presentation starts (nothing is active yet) + // don't animate (set duration to 0) + var duration = (active) ? "1s" : "0"; + + css(root, { + // to keep the perspective look similar for different scales + // we need to 'scale' the perspective, too + perspective: step.scale * 1000 + "px", + transform: scale(target.scale), + transitionDuration: duration, + transitionDelay: (zoomin ? "500ms" : "0ms") + }); + + css(canvas, { + transform: rotate(target.rotate, true) + translate(target.translate), + transitionDuration: duration, + transitionDelay: (zoomin ? "0ms" : "500ms") + }); + + current = target; + active = el; + + return el; }; - // check if the transition is zooming in or not - var zoomin = target.scale >= current.scale; + var prev = function () { + var prev = steps.indexOf( active ) - 1; + prev = prev >= 0 ? steps[ prev ] : steps[ steps.length-1 ]; + + return goto(prev); + }; - // if presentation starts (nothing is active yet) - // don't animate (set duration to 0) - var duration = (active) ? "1s" : "0"; + var next = function () { + var next = steps.indexOf( active ) + 1; + next = next < steps.length ? steps[ next ] : steps[ 0 ]; + + return goto(next); + }; - css(impress, { - // to keep the perspective look similar for different scales - // we need to 'scale' the perspective, too - perspective: step.scale * 1000 + "px", - transform: scale(target.scale), - transitionDuration: duration, - transitionDelay: (zoomin ? "500ms" : "0ms") + window.addEventListener("hashchange", function () { + goto( getElementFromUrl() ); + }, false); + + // START + // by selecting step defined in url or first step of the presentation + goto(getElementFromUrl() || steps[0]); + + return (roots[ "impress-root-" + rootId ] = { + goto: goto, + next: next, + prev: prev }); - - css(canvas, { - transform: rotate(target.rotate, true) + translate(target.translate), - transitionDuration: duration, - transitionDelay: (zoomin ? "0ms" : "500ms") - }); - - current = target; - active = el; - - return el; - }; - - var selectPrev = function () { - var prev = steps.indexOf( active ) - 1; - prev = prev >= 0 ? steps[ prev ] : steps[ steps.length-1 ]; - - return select(prev); - }; - - var selectNext = function () { - var next = steps.indexOf( active ) + 1; - next = next < steps.length ? steps[ next ] : steps[ 0 ]; - - return select(next); - }; - - // EVENTS + + } +})(document, window); + +// EVENTS + +(function ( document, window ) { + 'use strict'; + // keyboard navigation handler document.addEventListener("keydown", function ( event ) { 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 - selectPrev(); + impress().prev(); break; case 9: ; // tab case 32: ; // space case 34: ; // pg down case 39: ; // right case 40: // down - selectNext(); + impress().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 or a step) + // check if event target (or any of its parents is a link) var target = event.target; while ( (target.tagName != "A") && - (!isStep(target)) && (target != document.body) ) { target = target.parentNode; } @@ -320,28 +358,29 @@ // if it's a link to presentation step, target this step if ( href && href[0] == '#' ) { - target = byId( href.slice(1) ); + target = document.getElementById( href.slice(1) ); } } - if ( select(target) ) { + if ( impress().goto(target) ) { + event.stopImmediatePropagation(); event.preventDefault(); } }, false); - var getElementFromUrl = 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(/^#\/?/,"") ); - } - - window.addEventListener("hashchange", function () { - select( getElementFromUrl() ); + // delegated handler for clicking on step elements + document.addEventListener("click", function ( event ) { + var target = event.target; + // find closest step element + while ( !target.classList.contains("step") && + (target != document.body) ) { + target = target.parentNode; + } + + if ( impress().goto(target) ) { + event.preventDefault(); + } }, false); - // START - // by selecting step defined in url or first step of the presentation - select(getElementFromUrl() || steps[0]); - })(document, window); From db5857afdafea7e7fd4f35be9c6155fa36c33956 Mon Sep 17 00:00:00 2001 From: Bartek Szopka Date: Wed, 15 Feb 2012 20:39:45 +0100 Subject: [PATCH 3/7] impressive touch support --- js/impress.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/js/impress.js b/js/impress.js index 0e810a6..a6be86b 100644 --- a/js/impress.js +++ b/js/impress.js @@ -382,5 +382,23 @@ } }, false); + // touch handler to detect taps on the left and right side of the screen + 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 = impress().prev(); + } else if ( x > window.innerWidth - width ) { + result = impress().next(); + } + + if (result) { + event.preventDefault(); + } + } + }, false); })(document, window); From 7c46679b39dc7a48d8f6f8aee3eb08e3ec400fa5 Mon Sep 17 00:00:00 2001 From: Bartek Szopka Date: Wed, 15 Feb 2012 20:53:41 +0100 Subject: [PATCH 4/7] comment update after API changes --- index.html | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index 716b492..ce10c4d 100644 --- a/index.html +++ b/index.html @@ -280,9 +280,14 @@ Last, but not least. To make all described above really work, you need to include impress.js in the page. + I strongly encourage to minify it first. + + In here I just include full source of the script to make it more readable. + + You also need to call a `impress()` function to initialize impress.js presentation. And you should do it in the end of your document. Not only because it's a good practice, but also - because I was lazy, haven't wrapped the code in any kind of "DOM ready" event, so it will not work - if included too early in the source ;) + because I should be done when the whole document is ready. + Of course you can wrap it in any kind of "DOM ready" event, but I was to lazy to do so ;) --> From f248a39aabfe3aa42f676b0ae9cf4b0375a1c122 Mon Sep 17 00:00:00 2001 From: Bartek Szopka Date: Wed, 15 Feb 2012 21:01:19 +0100 Subject: [PATCH 5/7] even more friendly ending ;) --- index.html | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/index.html b/index.html index ce10c4d..0078fb8 100644 --- a/index.html +++ b/index.html @@ -334,5 +334,10 @@ I'm impressed! Feel free to let me know that you got that far (I'm @bartaz on Twitter), 'cause I'd like to congratulate you personally :) + But you don't have to do it now. Take my advice and take some time off. Make yourself a cup of coffee, tea, + or anything you like to drink. And raise a glass for me ;) + + Cheers! + --> From ff81a1305411ac269b1a494e320b87c4d7d558d2 Mon Sep 17 00:00:00 2001 From: Bartek Szopka Date: Wed, 15 Feb 2012 21:10:56 +0100 Subject: [PATCH 6/7] some very basic documentation of the API --- index.html | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/index.html b/index.html index 0078fb8..6649992 100644 --- a/index.html +++ b/index.html @@ -293,6 +293,28 @@ + + From 14a86ef9efcd234e2fd11704130254df07730db6 Mon Sep 17 00:00:00 2001 From: Bartek Szopka Date: Wed, 15 Feb 2012 21:12:15 +0100 Subject: [PATCH 7/7] README updates --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index ead0b3a..4d983b7 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,8 @@ VERSION HISTORY **CONTAINS UNRELEASED CHANGES, MAY BE UNSTABLE** +* basic API to control the presentation flow from JavaScript +* touch event support ### 0.2 ([browse](http://github.com/bartaz/impress.js/tree/0.2), [zip](http://github.com/bartaz/impress.js/zipball/0.2), [tar](http://github.com/bartaz/impress.js/tarball/0.2))