impressive API

This commit is contained in:
Bartek Szopka
2012-02-01 21:28:55 +01:00
parent 9641f5750b
commit cfe6448594
2 changed files with 228 additions and 188 deletions

View File

@@ -286,6 +286,7 @@
--> -->
<script src="js/impress.js"></script> <script src="js/impress.js"></script>
<script>impress();</script>
</body> </body>
</html> </html>

View File

@@ -86,7 +86,13 @@
var scale = function ( s ) { var scale = function ( s ) {
return " scale(" + 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 // CHECK SUPPORT
@@ -94,223 +100,255 @@
var impressSupported = ( pfx("perspective") != null ) && var impressSupported = ( pfx("perspective") != null ) &&
( ua.search(/(iphone)|(ipod)|(ipad)|(android)/) == -1 ); ( ua.search(/(iphone)|(ipod)|(ipad)|(android)/) == -1 );
// DOM ELEMENTS var roots = {};
var impress = byId("impress"); var impress = window.impress = function ( rootId ) {
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 props = { rootId = rootId || "impress";
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
};
if ( !el.id ) { // if already initialized just return the API
el.id = "step-" + (idx + 1); if (roots["impress-root-" + rootId]) {
return roots["impress-root-" + rootId];
} }
stepData["impress-" + el.id] = step; // DOM ELEMENTS
css(el, { var root = byId( rootId );
position: "absolute",
transform: "translate(-50%,-50%)" + if (!impressSupported) {
translate(step.translate) + root.className = "impress-not-supported";
rotate(step.rotate) + return;
scale(step.scale), } else {
transformStyle: "preserve-3d" 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 props = {
position: "absolute",
var active = null; transformOrigin: "top left",
var hashTimeout = null; transition: "all 0s ease-in-out",
transformStyle: "preserve-3d"
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;
} }
// Sometimes it's possible to trigger focus on first link with some keyboard action. css(root, props);
// Browser in such a case tries to scroll the page to make this element visible css(root, {
// (even that body overflow is set to hidden) and it breaks our careful positioning. top: "50%",
// left: "50%",
// So, as a lousy (and lazy) workaround we will make the page scroll back to the top perspective: "1000px"
// whenever slide is selected });
// css(canvas, props);
// 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]; var current = {
translate: { x: 0, y: 0, z: 0 },
rotate: { x: 0, y: 0, z: 0 },
scale: 1
};
var stepData = {};
if ( active ) { var isStep = function ( el ) {
active.classList.remove("active"); 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 var goto = function ( el ) {
// scrolling to element in hash if ( !isStep(el) || el == active) {
// // selected element is not defined as step or is already active
// and it has to be set after animation finishes, because in chrome it return false;
// causes transtion being laggy }
window.clearTimeout( hashTimeout );
hashTimeout = window.setTimeout(function () { // Sometimes it's possible to trigger focus on first link with some keyboard action.
window.location.hash = "#/" + el.id; // Browser in such a case tries to scroll the page to make this element visible
}, 1000); // (even that body overflow is set to hidden) and it breaks our careful positioning.
//
var target = { // So, as a lousy (and lazy) workaround we will make the page scroll back to the top
rotate: { // whenever slide is selected
x: -parseInt(step.rotate.x, 10), //
y: -parseInt(step.rotate.y, 10), // If you are reading this and know any better way to handle it, I'll be glad to hear about it!
z: -parseInt(step.rotate.z, 10) window.scrollTo(0, 0);
},
translate: { var step = stepData["impress-" + el.id];
x: -step.translate.x,
y: -step.translate.y, if ( active ) {
z: -step.translate.z active.classList.remove("active");
}, }
scale: 1 / parseFloat(step.scale) 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 prev = function () {
var zoomin = target.scale >= current.scale; 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) var next = function () {
// don't animate (set duration to 0) var next = steps.indexOf( active ) + 1;
var duration = (active) ? "1s" : "0"; next = next < steps.length ? steps[ next ] : steps[ 0 ];
return goto(next);
};
css(impress, { window.addEventListener("hashchange", function () {
// to keep the perspective look similar for different scales goto( getElementFromUrl() );
// we need to 'scale' the perspective, too }, false);
perspective: step.scale * 1000 + "px",
transform: scale(target.scale), // START
transitionDuration: duration, // by selecting step defined in url or first step of the presentation
transitionDelay: (zoomin ? "500ms" : "0ms") 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), })(document, window);
transitionDuration: duration,
transitionDelay: (zoomin ? "0ms" : "500ms") // EVENTS
});
(function ( document, window ) {
current = target; 'use strict';
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
// keyboard navigation handler
document.addEventListener("keydown", function ( event ) { document.addEventListener("keydown", function ( event ) {
if ( event.keyCode == 9 || ( event.keyCode >= 32 && event.keyCode <= 34 ) || (event.keyCode >= 37 && event.keyCode <= 40) ) { if ( event.keyCode == 9 || ( event.keyCode >= 32 && event.keyCode <= 34 ) || (event.keyCode >= 37 && event.keyCode <= 40) ) {
switch( event.keyCode ) { switch( event.keyCode ) {
case 33: ; // pg up case 33: ; // pg up
case 37: ; // left case 37: ; // left
case 38: // up case 38: // up
selectPrev(); impress().prev();
break; break;
case 9: ; // tab case 9: ; // tab
case 32: ; // space case 32: ; // space
case 34: ; // pg down case 34: ; // pg down
case 39: ; // right case 39: ; // right
case 40: // down case 40: // down
selectNext(); impress().next();
break; break;
} }
event.preventDefault(); event.preventDefault();
} }
}, false); }, false);
// delegated handler for clicking on the links to presentation steps
document.addEventListener("click", function ( event ) { document.addEventListener("click", function ( event ) {
// event delegation with "bubbling" // 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; var target = event.target;
while ( (target.tagName != "A") && while ( (target.tagName != "A") &&
(!isStep(target)) &&
(target != document.body) ) { (target != document.body) ) {
target = target.parentNode; target = target.parentNode;
} }
@@ -320,28 +358,29 @@
// if it's a link to presentation step, target this step // if it's a link to presentation step, target this step
if ( href && href[0] == '#' ) { 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(); event.preventDefault();
} }
}, false); }, false);
var getElementFromUrl = function () { // delegated handler for clicking on step elements
// get id from url # by removing `#` or `#/` from the beginning, document.addEventListener("click", function ( event ) {
// so both "fallback" `#slide-id` and "enhanced" `#/slide-id` will work var target = event.target;
return byId( window.location.hash.replace(/^#\/?/,"") ); // find closest step element
} while ( !target.classList.contains("step") &&
(target != document.body) ) {
window.addEventListener("hashchange", function () { target = target.parentNode;
select( getElementFromUrl() ); }
if ( impress().goto(target) ) {
event.preventDefault();
}
}, false); }, false);
// START
// by selecting step defined in url or first step of the presentation
select(getElementFromUrl() || steps[0]);
})(document, window); })(document, window);