impressive api merged

This commit is contained in:
Bartek Szopka
2012-02-15 21:20:02 +01:00
3 changed files with 286 additions and 184 deletions

View File

@@ -24,7 +24,12 @@ VERSION HISTORY
**CONTAINS UNRELEASED CHANGES, MAY BE UNSTABLE**
<<<<<<< HEAD
* minor CSS 3D fixes
=======
* basic API to control the presentation flow from JavaScript
* touch event support
>>>>>>> api
### 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))

View File

@@ -280,12 +280,40 @@
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 ;)
-->
<script src="js/impress.js"></script>
<script>impress();</script>
<!--
The `impress()` function also gives you access to API to control the presentation.
Just store the result of the call:
var api = impress();
and you will get three functions you can call:
`api.next()` - moves to next step of the presentation,
`api.prev()` - moves to previous step of the presentation
`api.goto( stepElement ) - moves the presentation to given step element (the DOM element of the step).
You can also simply call `impress()` again to get the API, so `impress().next()` is also allowed.
Don't worry, it wont initialize the presentation again.
For some example uses of this API check the last part of the source of impress.js where the API
is used in event handlers.
-->
</body>
</html>
@@ -328,5 +356,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!
-->

View File

@@ -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,203 +100,242 @@
var impressSupported = ( pfx("perspective") != null ) &&
( 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 = "";
}
rootId = rootId || "impress";
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 = {
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
};
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.stepData = step;
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];
}
css(el, {
position: "absolute",
transform: "translate(-50%,-50%)" +
translate(step.translate) +
rotate(step.rotate) +
scale(step.scale),
transformStyle: "preserve-3d"
// DOM ELEMENTS
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 select = function ( el ) {
if ( !el || !el.stepData || 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 = el.stepData;
if ( active ) {
active.classList.remove("active");
}
el.classList.add("active");
impress.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)
var current = {
translate: { x: 0, y: 0, z: 0 },
rotate: { x: 0, y: 0, z: 0 },
scale: 1
};
// check if the transition is zooming in or not
var zoomin = target.scale >= current.scale;
var stepData = {};
// if presentation starts (nothing is active yet)
// don't animate (set duration to 0)
var duration = (active) ? "1s" : "0";
var isStep = function ( el ) {
return !!(el && el.id && stepData["impress-" + 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"
});
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")
});
css(canvas, {
transform: rotate(target.rotate, true) + translate(target.translate),
transitionDuration: duration,
transitionDelay: (zoomin ? "0ms" : "500ms")
// making given step active
var active = null;
var hashTimeout = null;
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;
};
var prev = function () {
var prev = steps.indexOf( active ) - 1;
prev = prev >= 0 ? steps[ prev ] : steps[ steps.length-1 ];
return goto(prev);
};
var next = function () {
var next = steps.indexOf( active ) + 1;
next = next < steps.length ? steps[ next ] : steps[ 0 ];
return goto(next);
};
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
});
current = target;
active = el;
}
})(document, window);
return el;
};
// EVENTS
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
(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;
}
@@ -298,12 +343,12 @@
}
}, 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") &&
(!target.stepData) &&
(target != document.body) ) {
target = target.parentNode;
}
@@ -313,28 +358,47 @@
// 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(/^#\/?/,"") );
}
// 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;
}
window.addEventListener("hashchange", function () {
select( getElementFromUrl() );
if ( impress().goto(target) ) {
event.preventDefault();
}
}, false);
// START
// by selecting step defined in url or first step of the presentation
select(getElementFromUrl() || steps[0]);
// 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);