├── favicon.png ├── apple-touch-icon.png ├── component.json ├── README.md ├── index.html ├── css └── impress-demo.css └── js └── impress.js /favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrummerHead/impress.js/master/favicon.png -------------------------------------------------------------------------------- /apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DrummerHead/impress.js/master/apple-touch-icon.png -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "impress-js", 3 | "version": "0.5.3", 4 | "main": "./js/impress.js", 5 | "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", 6 | "homepage": "https://github.com/bartaz/impress.js", 7 | "license" : ["http://bartaz.mit-license.org/", "http://www.gnu.org/licenses/"], 8 | "main": [ 9 | "./js/impress.js", 10 | ], 11 | "keywords": [ 12 | "slideshow", 13 | "css3" 14 | ], 15 | "author": { 16 | "name": "Bartek Szopka", 17 | "web": "http://bartaz.github.com" 18 | } 19 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | impress.js 2 | ============ 3 | 4 | It's a presentation framework based on the power of CSS3 transforms and 5 | transitions in modern browsers and inspired by the idea behind prezi.com. 6 | 7 | **WARNING** 8 | 9 | impress.js may not help you if you have nothing interesting to say ;) 10 | 11 | 12 | ABOUT THE NAME 13 | ---------------- 14 | 15 | impress.js name in [courtesy of @skuzniak](http://twitter.com/skuzniak/status/143627215165333504). 16 | 17 | It's an (un)fortunate coincidence that a Open/LibreOffice presentation tool is called Impress ;) 18 | 19 | 20 | VERSION HISTORY 21 | ----------------- 22 | 23 | ### 0.5.3 ([browse](http://github.com/bartaz/impress.js/tree/0.5.3), [zip](http://github.com/bartaz/impress.js/zipball/0.5.3), [tar](http://github.com/bartaz/impress.js/tarball/0.5.3)) 24 | 25 | #### BUGFIX RELEASE 26 | 27 | Version 0.5 introduced events including `impress:stepenter`, but this event was not triggered properly in some 28 | specific transition types (for example when only scale was changing between steps). It was caused by the fact that 29 | in such cases expected `transitionend` event was not triggered. 30 | 31 | This version fixes this issue. Unfortunately modern `transitionend` event is no longer used to detect when the 32 | transition has finished, but old school (and more reliable) `setTimeout` is used. 33 | 34 | 35 | ### 0.5.2 ([browse](http://github.com/bartaz/impress.js/tree/0.5.2), [zip](http://github.com/bartaz/impress.js/zipball/0.5.2), [tar](http://github.com/bartaz/impress.js/tarball/0.5.2)) 36 | 37 | #### DOCUMENTATION RELEASE 38 | 39 | More descriptive comments added to demo CSS and impress.js source file, so now not only `index.html` is worth reading ;) 40 | 41 | 42 | ### 0.5.1 ([browse](http://github.com/bartaz/impress.js/tree/0.5.1), [zip](http://github.com/bartaz/impress.js/zipball/0.5.1), [tar](http://github.com/bartaz/impress.js/tarball/0.5.1)) 43 | 44 | #### BUGFIX RELEASE 45 | 46 | Changes in version 0.5 introduced a bug (#126) that was preventing clicks on links (or any clickable elements) on 47 | currently active step. This release fixes this issue. 48 | 49 | 50 | 51 | ### 0.5 ([browse](http://github.com/bartaz/impress.js/tree/0.5), [zip](http://github.com/bartaz/impress.js/zipball/0.5), [tar](http://github.com/bartaz/impress.js/tarball/0.5)) 52 | 53 | #### CHANGELOG 54 | 55 | * API changed, so that `impress()` function no longer automatically initialize presentation; new method called `init` 56 | was added to API and it should be used to start the presentation 57 | * event `impress:init` is triggered on root presentation element (`#impress` by default) when presentation is initialized 58 | * new CSS classes were added: `impress-disabled` is added to body element by the impress.js script and it's changed to 59 | `impress-enabled` when `init()` function is called 60 | * events added when step is entered and left - custom `impress:stepenter` and `impress:stepleave` events are triggered 61 | on step elements and can be handled like any other DOM events (with `addEventListener`) 62 | * additional `past`, `present` and `future` classes are added to step elements 63 | - `future` class appears on steps that were not yet visited 64 | - `present` class appears on currently visible step - it's different from `active` class as `present` class 65 | is added when transition finishes (step is entered) 66 | - `past` class is added to already visited steps (when the step is left) 67 | * and good news, `goto()` API method is back! it seems that `goto` **was** a future reserved word but isn't anymore, 68 | so we can use this short and pretty name instead of camelCassy `stepTo` - and yes, that means API changed again... 69 | * additionally `goto()` method now supports new types of parameters: 70 | - you can give it a number of step you want to go to: `impress().goto(7)` 71 | - or its id: `impress().goto("the-best-slide-ever")` 72 | - of course DOM element is still acceptable: `impress().goto( document.getElementById("overview") )` 73 | * and if it's not enough, `goto()` also accepts second parameter to define the transition duration in ms, for example 74 | `impress().goto("make-it-quick", 300)` or `impress().goto("now", 0)` 75 | 76 | #### UPGRADING FROM PREVIOUS VERSIONS 77 | 78 | In current version calling `impress()` doesn't automatically initialize the presentation. You need to call `init()` 79 | function from the API. So in a place were you called `impress()` to initialize impress.js simply change this call 80 | to `impress().init()`. 81 | 82 | Version 0.4 changed `goto` API method into `stepTo`. It turned out that `goto` is not a reserved word anymore, so it 83 | can be used in JavaScript. That's why version 0.5 brings it back and removes `stepTo`. 84 | 85 | So if you have been using version 0.4 and have any reference to `stepTo` API method make sure to change it to `goto`. 86 | 87 | 88 | 89 | ### 0.4.1 ([browse](http://github.com/bartaz/impress.js/tree/0.4.1), [zip](http://github.com/bartaz/impress.js/zipball/0.4.1), [tar](http://github.com/bartaz/impress.js/tarball/0.4.1)) 90 | 91 | #### BUGFIX RELEASE 92 | 93 | Changes is version 0.4 introduced a bug causing JavaScript errors being thrown all over the place in fallback mode. 94 | This release fixes this issue. 95 | 96 | It also adds a flag `impress.supported` that can be used in JavaScript to check if impress.js is supported in the browser. 97 | 98 | 99 | 100 | ### 0.4 ([browse](http://github.com/bartaz/impress.js/tree/0.4), [zip](http://github.com/bartaz/impress.js/zipball/0.4), [tar](http://github.com/bartaz/impress.js/tarball/0.4)) 101 | 102 | #### CHANGELOG 103 | 104 | * configuration options on `#impress` element: `data-perspective` (in px, defaults so 1000), 105 | `data-transition-duration` (in ms, defaults to 1000) 106 | * automatic scaling to fit window size, with configuration options: `data-width` (in px, defaults to 1024), 107 | `data-height` (in px, defaults to 768), `max-scale` (defaults to 1), `min-scale` (defaults to 0) 108 | * `goto` API function was renamed to `stepTo` because `goto` is a future reserved work in JavaScript, 109 | so **please make sure to update your code** 110 | * fallback `impress-not-supported` class is now set on `body` element instead of `#impress` element and it's 111 | replaced with `impress-supported` when browser supports all required features 112 | * classes `step-ID` used to indicate progress of the presentation are now renamed to `impress-on-ID` and are 113 | set on `body` element, so **please make sure to update your code** 114 | * basic validation of configuration options 115 | * couple of typos and bugs fixed 116 | * favicon added ;) 117 | 118 | 119 | #### UPGRADING FROM PREVIOUS VERSIONS 120 | 121 | If in your custom JavaScript code you were using `goto()` function from impress.js API make sure to change it 122 | to `stepTo()`. 123 | 124 | If in your CSS you were using classes based on currently active step with `step-` prefix, such as `step-bored` 125 | (where `bored` is the id of the step element) make sure to change it to `impress-on-` prefix 126 | (for example `impress-on-bored`). Also in previous versions these classes were assigned to `#impress` element 127 | and now they are added to `body` element, so if your CSS code depends on this, it also should be updated. 128 | 129 | Same happened to `impress-not-supported` class name - it was moved from `#impress` element to `body`, so update 130 | your CSS if it's needed. 131 | 132 | #### NOTE ON BLACKBERRY PLAYBOOK 133 | 134 | Changes and fixes added in this version have broken the experience on Blackberry Playbook with OS in version 1.0. 135 | It happened due to a bug in the Playbook browser in this version. Fortunately in version 2.0 of Playbook OS this 136 | bug was fixed and impress.js works fine. 137 | 138 | So currently impress.js work only on Blackberry Playbook with latest OS. Fortunately, [it seems that most of the 139 | users](http://twitter.com/n_adam_stanley/status/178188611827679233) [are quite quick with updating their devices] 140 | (http://twitter.com/brcewane/status/178230406196379648) 141 | 142 | 143 | 144 | ### 0.3 ([browse](http://github.com/bartaz/impress.js/tree/0.3), [zip](http://github.com/bartaz/impress.js/zipball/0.3), [tar](http://github.com/bartaz/impress.js/tarball/0.3)) 145 | 146 | #### CHANGELOG 147 | 148 | * minor CSS 3D fixes 149 | * basic API to control the presentation flow from JavaScript 150 | * touch event support 151 | * basic support for iPad (iOS 5 and iOS 4 with polyfills) and Blackberry Playbook 152 | 153 | #### UPGRADING FROM PREVIOUS VERSIONS 154 | 155 | Because API was introduced the way impress.js script is initialized was changed a bit. You not only has to include 156 | `impress.js` script file, but also call `impress()` function. 157 | 158 | See the source of `index.html` for example and more details. 159 | 160 | 161 | ### 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)) 162 | 163 | * tutorial/documentation added to `index.html` source file 164 | * being even more strict with strict mode 165 | * code clean-up 166 | * couple of small bug-fixes 167 | 168 | 169 | ### 0.1 ([browse](http://github.com/bartaz/impress.js/tree/0.1), [zip](http://github.com/bartaz/impress.js/zipball/0.1), [tar](http://github.com/bartaz/impress.js/tarball/0.1)) 170 | 171 | First release. 172 | 173 | Contains basic functionality for step placement and transitions between them 174 | with simple fallback for non-supporting browsers. 175 | 176 | 177 | 178 | HOW TO USE IT 179 | --------------- 180 | 181 | [Use the source](http://github.com/bartaz/impress.js/blob/master/index.html), Luke ;) 182 | 183 | If you have no idea what I mean by that, or you just clicked that link above and got 184 | very confused by all these strange characters that got displayed on your screen, 185 | it's a sign, that impress.js is not for you. 186 | 187 | Sorry. 188 | 189 | Fortunately there are some guys on GitHub that got quite excited with the idea of building 190 | editing tool for impress.js. Let's hope they will manage to do it. 191 | 192 | 193 | EXAMPLES AND DEMOS 194 | -------------------- 195 | 196 | ### Official demo 197 | 198 | [impress.js demo](http://bartaz.github.com/impress.js) by [@bartaz](http://twitter.com/bartaz) 199 | 200 | ### Other examples 201 | 202 | More examples and demos can be found on [Examples and demos wiki page](http://github.com/bartaz/impress.js/wiki/Examples-and-demos). 203 | 204 | Feel free to add your own example presentations (or websites) there. 205 | 206 | 207 | 208 | WANT TO CONTRIBUTE? 209 | --------------------- 210 | 211 | If you've found a bug or have a great idea for new feature let me know by [adding your suggestion] 212 | (http://github.com/bartaz/impress.js/issues/new) to [issues list](https://github.com/bartaz/impress.js/issues). 213 | 214 | If you have fixed a bug or implemented a feature that you'd like to share, send your pull request against [dev branch] 215 | (http://github.com/bartaz/impress.js/tree/dev). But remember that I only accept code that fits my vision of impress.js 216 | and my coding standards - so make sure you are open for discussion :) 217 | 218 | 219 | 220 | BROWSER SUPPORT 221 | ----------------- 222 | 223 | ### TL;DR; 224 | 225 | Currently impress.js works fine in latest Chrome/Chromium browser, Safari 5.1 and Firefox 10. 226 | With addition of some HTML5 polyfills (see below for details) it should work in Internet Explorer 10 227 | (currently available as Developers Preview). 228 | It doesn't work in Opera, as it doesn't support CSS 3D transforms. 229 | 230 | As a presentation tool it was not developed with mobile browsers in mind, but some tablets are good 231 | enough to run it, so it should work quite well on iPad (iOS 5, or iOS 4 with HTML5 polyfills) and 232 | Blackberry Playbook. 233 | 234 | ### Still interested? Read more... 235 | 236 | Additionally for the animations to run smoothly it's required to have hardware 237 | acceleration support in your browser. This depends on the browser, your operating 238 | system and even kind of graphic hardware you have in your machine. 239 | 240 | For browsers not supporting CSS3 3D transforms impress.js adds `impress-not-supported` 241 | class on `#impress` element, so fallback styles can be applied to make all the content accessible. 242 | 243 | 244 | ### Even more explanation and technical stuff 245 | 246 | Let's put this straight -- wide browser support was (and is) not on top of my priority list for 247 | impress.js. It's built on top of fresh technologies that just start to appear in the browsers 248 | and I'd like to rather look forward and develop for the future than being slowed down by the past. 249 | 250 | But it's not "hard-coded" for any particular browser or engine. If any browser in future will 251 | support features required to run impress.js, it will just begin to work there without changes in 252 | the code. 253 | 254 | From technical point of view all the positioning of presentation elements in 3D requires CSS 3D 255 | transforms support. Transitions between presentation steps are based on CSS transitions. 256 | So these two features are required by impress.js to display presentation correctly. 257 | 258 | Unfortunately the support for CSS 3D transforms and transitions is not enough for animations to 259 | run smoothly. If the browser doesn't support hardware acceleration or the graphic card is not 260 | good enough the transitions will be laggy. 261 | 262 | Additionally the code of impress.js relies on APIs proposed in HTML5 specification, including 263 | `classList` and `dataset` APIs. If they are not available in the browser, impress.js will not work. 264 | 265 | Fortunately, as these are JavaScript APIs there are polyfill libraries that patch older browsers 266 | with these APIs. 267 | 268 | For example IE10 is said to support CSS 3D transforms and transitions, but it doesn't have `classList` 269 | not `dataset` APIs implemented at the moment. So including polyfill libraries *should* help IE10 270 | with running impress.js. 271 | 272 | 273 | ### And few more details about mobile support 274 | 275 | Mobile browsers are currently not supported. Even Android browsers that support CSS 3D transforms are 276 | forced into fallback view at this point. 277 | 278 | Fortunately some tablets seem to have good enough hardware support and browsers to handle it. 279 | Currently impress.js presentations should work on iPad and Blackberry Playbook. 280 | 281 | In theory iPhone should also be able to run it (as it runs the same software as iPad), but I haven't 282 | found a good way to handle it's small screen. 283 | 284 | Also note that iOS supports `classList` and `dataset` APIs starting with version 5, so iOS 4.X and older 285 | requires polyfills to work. 286 | 287 | 288 | LICENSE 289 | --------- 290 | 291 | Copyright 2011-2012 Bartek Szopka 292 | 293 | Released under the MIT and GPL Licenses. 294 | 295 | 296 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 46 | 47 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | impress.js | presentation tool based on the power of CSS3 transforms and transitions in modern browsers | by Bartek Szopka @bartaz 72 | 73 | 74 | 75 | 76 | 77 | 78 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 117 | 118 | 119 | 122 |
123 |

Your browser doesn't support the features required by impress.js, so you are presented with a simplified version of this presentation.

124 |

For the best experience please use the latest Chrome, Safari or Firefox browser.

125 |
126 | 127 | 148 |
149 | 150 | 167 |
168 | Aren't you just bored with all those slides-based presentations? 169 |
170 | 171 | 186 |
187 | Don't you think that presentations given in modern browsers shouldn't copy the limits of 'classic' slide decks? 188 |
189 | 190 |
191 | Would you like to impress your audience with stunning visualization of your talk? 192 |
193 | 194 | 204 |
205 | then you should try 206 |

impress.js*

207 | * no rhyme intended 208 |
209 | 210 | 218 |
219 |

It's a presentation tool
220 | inspired by the idea behind prezi.com
221 | and based on the power of CSS3 transforms and transitions in modern browsers.

222 |
223 | 224 |
225 |

visualize your big thoughts

226 |
227 | 228 | 237 |
238 |

and tiny ideas

239 |
240 | 241 | 261 |
262 |

by positioning, rotating and scaling them on an infinite canvas

263 |
264 | 265 |
266 |

the only limit is your imagination

267 |
268 | 269 |
270 |

want to know more?

271 | use the source, Luke! 272 |
273 | 274 |
275 |

one more thing...

276 |
277 | 278 | 290 |
291 |

have you noticed it's in 3D*?

292 | * beat that, prezi ;) 293 |
294 | 295 | 310 |
311 |
312 | 313 |
314 | 315 | 339 |
340 |

Use a spacebar or arrow keys to navigate

341 |
342 | 347 | 348 | 363 | 364 | 365 | 366 | 390 | 391 | 392 | 393 | 394 | 422 | 423 | 438 | 439 | -------------------------------------------------------------------------------- /css/impress-demo.css: -------------------------------------------------------------------------------- 1 | /* 2 | So you like the style of impress.js demo? 3 | Or maybe you are just curious how it was done? 4 | 5 | You couldn't find a better place to find out! 6 | 7 | Welcome to the stylesheet impress.js demo presentation. 8 | 9 | Please remember that it is not meant to be a part of impress.js and is 10 | not required by impress.js. 11 | I expect that anyone creating a presentation for impress.js would create 12 | their own set of styles. 13 | 14 | But feel free to read through it and learn how to get the most of what 15 | impress.js provides. 16 | 17 | And let me be your guide. 18 | 19 | Shall we begin? 20 | */ 21 | 22 | 23 | /* 24 | We start with a good ol' reset. 25 | That's the one by Eric Meyer http://meyerweb.com/eric/tools/css/reset/ 26 | 27 | You can probably argue if it is needed here, or not, but for sure it 28 | doesn't do any harm and gives us a fresh start. 29 | */ 30 | 31 | html, body, div, span, applet, object, iframe, 32 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 33 | a, abbr, acronym, address, big, cite, code, 34 | del, dfn, em, img, ins, kbd, q, s, samp, 35 | small, strike, strong, sub, sup, tt, var, 36 | b, u, i, center, 37 | dl, dt, dd, ol, ul, li, 38 | fieldset, form, label, legend, 39 | table, caption, tbody, tfoot, thead, tr, th, td, 40 | article, aside, canvas, details, embed, 41 | figure, figcaption, footer, header, hgroup, 42 | menu, nav, output, ruby, section, summary, 43 | time, mark, audio, video { 44 | margin: 0; 45 | padding: 0; 46 | border: 0; 47 | font-size: 100%; 48 | font: inherit; 49 | vertical-align: baseline; 50 | } 51 | 52 | /* HTML5 display-role reset for older browsers */ 53 | article, aside, details, figcaption, figure, 54 | footer, header, hgroup, menu, nav, section { 55 | display: block; 56 | } 57 | body { 58 | line-height: 1; 59 | } 60 | ol, ul { 61 | list-style: none; 62 | } 63 | blockquote, q { 64 | quotes: none; 65 | } 66 | blockquote:before, blockquote:after, 67 | q:before, q:after { 68 | content: ''; 69 | content: none; 70 | } 71 | 72 | table { 73 | border-collapse: collapse; 74 | border-spacing: 0; 75 | } 76 | 77 | /* 78 | Now here is when interesting things start to appear. 79 | 80 | We set up styles with default font and nice gradient in the background. 81 | And yes, there is a lot of repetition there because of -prefixes but we don't 82 | want to leave anybody behind. 83 | */ 84 | body { 85 | font-family: 'PT Sans', sans-serif; 86 | min-height: 740px; 87 | 88 | background: rgb(215, 215, 215); 89 | background: -webkit-gradient(radial, 50% 50%, 0, 50% 50%, 500, from(rgb(240, 240, 240)), to(rgb(190, 190, 190))); 90 | background: -webkit-radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190)); 91 | background: -moz-radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190)); 92 | background: -ms-radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190)); 93 | background: -o-radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190)); 94 | background: radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190)); 95 | } 96 | 97 | /* 98 | Now let's bring some text styles back ... 99 | */ 100 | b, strong { font-weight: bold } 101 | i, em { font-style: italic } 102 | 103 | /* 104 | ... and give links a nice look. 105 | */ 106 | a { 107 | color: inherit; 108 | text-decoration: none; 109 | padding: 0 0.1em; 110 | background: rgba(255,255,255,0.5); 111 | text-shadow: -1px -1px 2px rgba(100,100,100,0.9); 112 | border-radius: 0.2em; 113 | 114 | -webkit-transition: 0.5s; 115 | -moz-transition: 0.5s; 116 | -ms-transition: 0.5s; 117 | -o-transition: 0.5s; 118 | transition: 0.5s; 119 | } 120 | 121 | a:hover, 122 | a:focus { 123 | background: rgba(255,255,255,1); 124 | text-shadow: -1px -1px 2px rgba(100,100,100,0.5); 125 | } 126 | 127 | /* 128 | Because the main point behind the impress.js demo is to demo impress.js 129 | we display a fallback message for users with browsers that don't support 130 | all the features required by it. 131 | 132 | All of the content will be still fully accessible for them, but I want 133 | them to know that they are missing something - that's what the demo is 134 | about, isn't it? 135 | 136 | And then we hide the message, when support is detected in the browser. 137 | */ 138 | 139 | .fallback-message { 140 | font-family: sans-serif; 141 | line-height: 1.3; 142 | 143 | width: 780px; 144 | padding: 10px 10px 0; 145 | margin: 20px auto; 146 | 147 | border: 1px solid #E4C652; 148 | border-radius: 10px; 149 | background: #EEDC94; 150 | } 151 | 152 | .fallback-message p { 153 | margin-bottom: 10px; 154 | } 155 | 156 | .impress-supported .fallback-message { 157 | display: none; 158 | } 159 | 160 | /* 161 | Now let's style the presentation steps. 162 | 163 | We start with basics to make sure it displays correctly in everywhere ... 164 | */ 165 | 166 | .step { 167 | position: relative; 168 | width: 900px; 169 | padding: 40px; 170 | margin: 20px auto; 171 | 172 | -webkit-box-sizing: border-box; 173 | -moz-box-sizing: border-box; 174 | -ms-box-sizing: border-box; 175 | -o-box-sizing: border-box; 176 | box-sizing: border-box; 177 | 178 | font-family: 'PT Serif', georgia, serif; 179 | font-size: 48px; 180 | line-height: 1.5; 181 | } 182 | 183 | /* 184 | ... and we enhance the styles for impress.js. 185 | 186 | Basically we remove the margin and make inactive steps a little bit transparent. 187 | */ 188 | .impress-enabled .step { 189 | margin: 0; 190 | opacity: 0.3; 191 | 192 | -webkit-transition: opacity 1s; 193 | -moz-transition: opacity 1s; 194 | -ms-transition: opacity 1s; 195 | -o-transition: opacity 1s; 196 | transition: opacity 1s; 197 | } 198 | 199 | .impress-enabled .step.active { opacity: 1 } 200 | 201 | /* 202 | These 'slide' step styles were heavily inspired by HTML5 Slides: 203 | http://html5slides.googlecode.com/svn/trunk/styles.css 204 | 205 | ;) 206 | 207 | They cover everything what you see on first three steps of the demo. 208 | */ 209 | .slide { 210 | display: block; 211 | 212 | width: 900px; 213 | height: 700px; 214 | padding: 40px 60px; 215 | 216 | background-color: white; 217 | border: 1px solid rgba(0, 0, 0, .3); 218 | border-radius: 10px; 219 | box-shadow: 0 2px 6px rgba(0, 0, 0, .1); 220 | 221 | color: rgb(102, 102, 102); 222 | text-shadow: 0 2px 2px rgba(0, 0, 0, .1); 223 | 224 | font-family: 'Open Sans', Arial, sans-serif; 225 | font-size: 30px; 226 | line-height: 36px; 227 | letter-spacing: -1px; 228 | } 229 | 230 | .slide q { 231 | display: block; 232 | font-size: 50px; 233 | line-height: 72px; 234 | 235 | margin-top: 100px; 236 | } 237 | 238 | .slide q strong { 239 | white-space: nowrap; 240 | } 241 | 242 | /* 243 | And now we start to style each step separately. 244 | 245 | I agree that this may be not the most efficient, object-oriented and 246 | scalable way of styling, but most of steps have quite a custom look 247 | and typography tricks here and there, so they had to be styled separately. 248 | 249 | First is the title step with a big

(no room for padding) and some 250 | 3D positioning along Z axis. 251 | */ 252 | 253 | #title { 254 | padding: 0; 255 | } 256 | 257 | #title .try { 258 | font-size: 64px; 259 | position: absolute; 260 | top: -0.5em; 261 | left: 1.5em; 262 | 263 | -webkit-transform: translateZ(20px); 264 | -moz-transform: translateZ(20px); 265 | -ms-transform: translateZ(20px); 266 | -o-transform: translateZ(20px); 267 | transform: translateZ(20px); 268 | } 269 | 270 | #title h1 { 271 | font-size: 190px; 272 | 273 | -webkit-transform: translateZ(50px); 274 | -moz-transform: translateZ(50px); 275 | -ms-transform: translateZ(50px); 276 | -o-transform: translateZ(50px); 277 | transform: translateZ(50px); 278 | } 279 | 280 | #title .footnote { 281 | font-size: 32px; 282 | } 283 | 284 | /* 285 | Second step is nothing special, just a text with a link, so it doesn't need 286 | any special styling. 287 | 288 | Let's move to 'big thoughts' with centered text and custom font sizes. 289 | */ 290 | #big { 291 | width: 600px; 292 | text-align: center; 293 | font-size: 60px; 294 | line-height: 1; 295 | } 296 | 297 | #big b { 298 | display: block; 299 | font-size: 250px; 300 | line-height: 250px; 301 | } 302 | 303 | #big .thoughts { 304 | font-size: 90px; 305 | line-height: 150px; 306 | } 307 | 308 | /* 309 | 'Tiny ideas' just need some tiny styling. 310 | */ 311 | #tiny { 312 | width: 500px; 313 | text-align: center; 314 | } 315 | 316 | /* 317 | This step has some animated text ... 318 | */ 319 | #ing { width: 500px } 320 | 321 | /* 322 | ... so we define display to `inline-block` to enable transforms and 323 | transition duration to 0.5s ... 324 | */ 325 | #ing b { 326 | display: inline-block; 327 | -webkit-transition: 0.5s; 328 | -moz-transition: 0.5s; 329 | -ms-transition: 0.5s; 330 | -o-transition: 0.5s; 331 | transition: 0.5s; 332 | } 333 | 334 | /* 335 | ... and we want 'positioning` word to move up a bit when the step gets 336 | `present` class ... 337 | */ 338 | #ing.present .positioning { 339 | -webkit-transform: translateY(-10px); 340 | -moz-transform: translateY(-10px); 341 | -ms-transform: translateY(-10px); 342 | -o-transform: translateY(-10px); 343 | transform: translateY(-10px); 344 | } 345 | 346 | /* 347 | ... 'rotating' to rotate a quarter of a second later ... 348 | */ 349 | #ing.present .rotating { 350 | -webkit-transform: rotate(-10deg); 351 | -moz-transform: rotate(-10deg); 352 | -ms-transform: rotate(-10deg); 353 | -o-transform: rotate(-10deg); 354 | transform: rotate(-10deg); 355 | 356 | -webkit-transition-delay: 0.25s; 357 | -moz-transition-delay: 0.25s; 358 | -ms-transition-delay: 0.25s; 359 | -o-transition-delay: 0.25s; 360 | transition-delay: 0.25s; 361 | } 362 | 363 | /* 364 | ... and 'scaling' to scale down after another quarter of a second. 365 | */ 366 | #ing.present .scaling { 367 | -webkit-transform: scale(0.7); 368 | -moz-transform: scale(0.7); 369 | -ms-transform: scale(0.7); 370 | -o-transform: scale(0.7); 371 | transform: scale(0.7); 372 | 373 | -webkit-transition-delay: 0.5s; 374 | -moz-transition-delay: 0.5s; 375 | -ms-transition-delay: 0.5s; 376 | -o-transition-delay: 0.5s; 377 | transition-delay: 0.5s; 378 | } 379 | 380 | /* 381 | The 'imagination' step is again some boring font-sizing. 382 | */ 383 | 384 | #imagination { 385 | width: 600px; 386 | } 387 | 388 | #imagination .imagination { 389 | font-size: 78px; 390 | } 391 | 392 | /* 393 | There is nothing really special about 'use the source, Luke' step, too, 394 | except maybe of the Yoda background. 395 | 396 | As you can see below I've 'hard-coded' it in data URL. 397 | That's not the best way to serve images, but because that's just this one 398 | I decided it will be OK to have it this way. 399 | 400 | Just make sure you don't blindly copy this approach. 401 | */ 402 | #source { 403 | width: 700px; 404 | padding-bottom: 300px; 405 | 406 | /* Yoda Icon :: Pixel Art from Star Wars http://www.pixeljoint.com/pixelart/1423.htm */ 407 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARgAAAEYCAMAAACwUBm+AAAAAXNSR0IArs4c6QAAAKtQTFRFsAAAvbWSLUUrLEQqY1s8UYJMqJ1vNTEgOiIdIzYhjIFVLhsXZ6lgSEIsP2U8JhcCVzMsSXZEgXdOO145XJdWOl03LzAYMk4vSXNExr+hwcuxRTs1Qmk+RW9Am49eFRANQz4pUoNMQWc+OSMDTz0wLBsCNVMxa2NBOyUDUoNNSnlEWo9VRGxAVzYFl6tXCggHbLNmMUIcHhwTXkk5f3VNRT8wUT8xAAAACQocRBWFFwAAAAF0Uk5TAEDm2GYAAAPCSURBVHja7d3JctNAFIZRMwRCCGEmzPM8z/D+T8bu/ptbXXJFdij5fMt2Wuo+2UgqxVmtttq5WVotLzBgwIABAwYMGDCn0qVqbo69psPqVpWx+1XG5iaavF8wYMCAAQMGDBgwi4DJ6Y6qkxB1HNlcN3a92gbR5P2CAQMGDBgwYMCAWSxMlrU+UY5yu2l9okfV4bAxUVbf7TJnAwMGDBgwYMCAAbMLMHeqbGR82Zy+VR1Ht81nVca6R+UdTLaU24Ruzd3qM/e4yjnAgAEDBgwYMGDA7AJMd1l/3NRdVGcj3eX/2WEhCmDGxnM7yqygu8XIPjJj8iN/MGDAgAEDBgwYMAuDGb8q0RGlLCHLv1t9qDKWn3vdNHVuEI6HPaxO9Jo3GDBgwIABAwYMmIXBdC9ShGgMk+XnkXUeuGcsP/e1+lhNnZsL/G5Vs3OAAQMGDBgwYMCAWSxMR3SzOmraG5atdy9wZKzb+vg16qyqe2FltbnAgAEDBgwYMGDALAxmTJSuN3WA76rnVca6GTnemGN1WoEBAwYMGDBgwIBZGMxUomy4+xO899V4LAg5Xnc2MGDAgAEDBgwYMGA218Wq+2K1LDqvY9xZu8zN8fICdM6btYABAwYMGDBgwIABMzfH0+pGU5afze2tXebmeAfVz+p8BQYMGDBgwIABAwbMPBzZ+oWmfJrln1273FhkbHzee9WWbw7AgAEDBgwYMGDALAKm43hcdctKgblcPamOhuXnXlY5Xs6bsW4FGyQCAwYMGDBgwIABswiYMceZKgvMo+h8mrHLTdn676rj+FEFoTtHd8MwOxEYMGDAgAEDBgyYRcBM5UhXqiymW3R3c9ARhWO/OmjqfjVZy+xEYMCAAQMGDBgwYBYG073OnCV0RFNhMhaOa9WfKmOB6XjHMN1tQmaAAQMGDBgwYMCA2VWY7vXjz1U4croAzgPztwIDBgwYMGDAgAEDZhswh035NBw59Dww3RgYMGDAgAEDBgwYMJuD6f4tXT7NUqfCdBvZLkxXdgQGDBgwYMCAAQNmt2DGj8WzwAfV/w7T/aq7mxwwYMCAAQMGDBgwuwqTOo7uTwTngflSzQ3TdaJvAwEDBgwYMGDAgAED5gSvgbyo5oHZ4Pc+gwEDBgwYMGDAgAEzhOm+5G0qTGaAAQMGDBgwYMCAAXNaMOcnls3tNwWm+zRzp54NDBgwYMCAAQMGDJh5YNL36k1TLuGvVq+qnKMbS5n7tulT9asCAwYMGDBgwIABA2ZumKuztLnjgQEDBgwYMGDAgNl5mH/4/ltKA6vBNAAAAABJRU5ErkJggg==); 408 | background-position: bottom right; 409 | background-repeat: no-repeat; 410 | } 411 | 412 | #source q { 413 | font-size: 60px; 414 | } 415 | 416 | /* 417 | And the "it's in 3D" step again brings some 3D typography - just for fun. 418 | 419 | Because we want to position elements in 3D we set transform-style to 420 | `preserve-3d` on the paragraph. 421 | It is not needed by webkit browsers, but it is in Firefox. It's hard to say 422 | which behaviour is correct as 3D transforms spec is not very clear about it. 423 | */ 424 | #its-in-3d p { 425 | -webkit-transform-style: preserve-3d; 426 | -moz-transform-style: preserve-3d; /* Y U need this Firefox?! */ 427 | -ms-transform-style: preserve-3d; 428 | -o-transform-style: preserve-3d; 429 | transform-style: preserve-3d; 430 | } 431 | 432 | /* 433 | Below we position each word separately along Z axis and we want it to transition 434 | to default position in 0.5s when the step gets `present` class. 435 | 436 | Quite a simple idea, but lot's of styles and prefixes. 437 | */ 438 | #its-in-3d span, 439 | #its-in-3d b { 440 | display: inline-block; 441 | -webkit-transform: translateZ(40px); 442 | -moz-transform: translateZ(40px); 443 | -ms-transform: translateZ(40px); 444 | -o-transform: translateZ(40px); 445 | transform: translateZ(40px); 446 | 447 | -webkit-transition: 0.5s; 448 | -moz-transition: 0.5s; 449 | -ms-transition: 0.5s; 450 | -o-transition: 0.5s; 451 | transition: 0.5s; 452 | } 453 | 454 | #its-in-3d .have { 455 | -webkit-transform: translateZ(-40px); 456 | -moz-transform: translateZ(-40px); 457 | -ms-transform: translateZ(-40px); 458 | -o-transform: translateZ(-40px); 459 | transform: translateZ(-40px); 460 | } 461 | 462 | #its-in-3d .you { 463 | -webkit-transform: translateZ(20px); 464 | -moz-transform: translateZ(20px); 465 | -ms-transform: translateZ(20px); 466 | -o-transform: translateZ(20px); 467 | transform: translateZ(20px); 468 | } 469 | 470 | #its-in-3d .noticed { 471 | -webkit-transform: translateZ(-40px); 472 | -moz-transform: translateZ(-40px); 473 | -ms-transform: translateZ(-40px); 474 | -o-transform: translateZ(-40px); 475 | transform: translateZ(-40px); 476 | } 477 | 478 | #its-in-3d .its { 479 | -webkit-transform: translateZ(60px); 480 | -moz-transform: translateZ(60px); 481 | -ms-transform: translateZ(60px); 482 | -o-transform: translateZ(60px); 483 | transform: translateZ(60px); 484 | } 485 | 486 | #its-in-3d .in { 487 | -webkit-transform: translateZ(-10px); 488 | -moz-transform: translateZ(-10px); 489 | -ms-transform: translateZ(-10px); 490 | -o-transform: translateZ(-10px); 491 | transform: translateZ(-10px); 492 | } 493 | 494 | #its-in-3d .footnote { 495 | font-size: 32px; 496 | 497 | -webkit-transform: translateZ(-10px); 498 | -moz-transform: translateZ(-10px); 499 | -ms-transform: translateZ(-10px); 500 | -o-transform: translateZ(-10px); 501 | transform: translateZ(-10px); 502 | } 503 | 504 | #its-in-3d.present span, 505 | #its-in-3d.present b { 506 | -webkit-transform: translateZ(0px); 507 | -moz-transform: translateZ(0px); 508 | -ms-transform: translateZ(0px); 509 | -o-transform: translateZ(0px); 510 | transform: translateZ(0px); 511 | } 512 | 513 | /* 514 | The last step is an overview. 515 | There is no content in it, so we make sure it's not visible because we want 516 | to be able to click on other steps. 517 | 518 | */ 519 | #overview { display: none } 520 | 521 | /* 522 | We also make other steps visible and give them a pointer cursor using the 523 | `impress-on-` class. 524 | */ 525 | .impress-on-overview .step { 526 | opacity: 1; 527 | cursor: pointer; 528 | } 529 | 530 | 531 | /* 532 | Now, when we have all the steps styled let's give users a hint how to navigate 533 | around the presentation. 534 | 535 | The best way to do this would be to use JavaScript, show a delayed hint for a 536 | first time users, then hide it and store a status in cookie or localStorage... 537 | 538 | But I wanted to have some CSS fun and avoid additional scripting... 539 | 540 | Let me explain it first, so maybe the transition magic will be more readable 541 | when you read the code. 542 | 543 | First of all I wanted the hint to appear only when user is idle for a while. 544 | You can't detect the 'idle' state in CSS, but I delayed a appearing of the 545 | hint by 5s using transition-delay. 546 | 547 | You also can't detect in CSS if the user is a first-time visitor, so I had to 548 | make an assumption that I'll only show the hint on the first step. And when 549 | the step is changed hide the hint, because I can assume that user already 550 | knows how to navigate. 551 | 552 | To summarize it - hint is shown when the user is on the first step for longer 553 | than 5 seconds. 554 | 555 | The other problem I had was caused by the fact that I wanted the hint to fade 556 | in and out. It can be easily achieved by transitioning the opacity property. 557 | But that also meant that the hint was always on the screen, even if totally 558 | transparent. It covered part of the screen and you couldn't correctly clicked 559 | through it. 560 | Unfortunately you cannot transition between display `block` and `none` in pure 561 | CSS, so I needed a way to not only fade out the hint but also move it out of 562 | the screen. 563 | 564 | I solved this problem by positioning the hint below the bottom of the screen 565 | with CSS transform and moving it up to show it. But I also didn't want this move 566 | to be visible. I wanted the hint only to fade in and out visually, so I delayed 567 | the fade in transition, so it starts when the hint is already in its correct 568 | position on the screen. 569 | 570 | I know, it sounds complicated ... maybe it would be easier with the code? 571 | */ 572 | 573 | .hint { 574 | /* 575 | We hide the hint until presentation is started and from browsers not supporting 576 | impress.js, as they will have a linear scrollable view ... 577 | */ 578 | display: none; 579 | 580 | /* 581 | ... and give it some fixed position and nice styles. 582 | */ 583 | position: fixed; 584 | left: 0; 585 | right: 0; 586 | bottom: 200px; 587 | 588 | background: rgba(0,0,0,0.5); 589 | color: #EEE; 590 | text-align: center; 591 | 592 | font-size: 50px; 593 | padding: 20px; 594 | 595 | z-index: 100; 596 | 597 | /* 598 | By default we don't want the hint to be visible, so we make it transparent ... 599 | */ 600 | opacity: 0; 601 | 602 | /* 603 | ... and position it below the bottom of the screen (relative to it's fixed position) 604 | */ 605 | -webkit-transform: translateY(400px); 606 | -moz-transform: translateY(400px); 607 | -ms-transform: translateY(400px); 608 | -o-transform: translateY(400px); 609 | transform: translateY(400px); 610 | 611 | /* 612 | Now let's imagine that the hint is visible and we want to fade it out and move out 613 | of the screen. 614 | 615 | So we define the transition on the opacity property with 1s duration and another 616 | transition on transform property delayed by 1s so it will happen after the fade out 617 | on opacity finished. 618 | 619 | This way user will not see the hint moving down. 620 | */ 621 | -webkit-transition: opacity 1s, -webkit-transform 0.5s 1s; 622 | -moz-transition: opacity 1s, -moz-transform 0.5s 1s; 623 | -ms-transition: opacity 1s, -ms-transform 0.5s 1s; 624 | -o-transition: opacity 1s, -o-transform 0.5s 1s; 625 | transition: opacity 1s, transform 0.5s 1s; 626 | } 627 | 628 | /* 629 | Now we 'enable' the hint when presentation is initialized ... 630 | */ 631 | .impress-enabled .hint { display: block } 632 | 633 | /* 634 | ... and we will show it when the first step (with id 'bored') is active. 635 | */ 636 | .impress-on-bored .hint { 637 | /* 638 | We remove the transparency and position the hint in its default fixed 639 | position. 640 | */ 641 | opacity: 1; 642 | 643 | -webkit-transform: translateY(0px); 644 | -moz-transform: translateY(0px); 645 | -ms-transform: translateY(0px); 646 | -o-transform: translateY(0px); 647 | transform: translateY(0px); 648 | 649 | /* 650 | Now for fade in transition we have the oposite situation from the one 651 | above. 652 | 653 | First after 4.5s delay we animate the transform property to move the hint 654 | into its correct position and after that we fade it in with opacity 655 | transition. 656 | */ 657 | -webkit-transition: opacity 1s 5s, -webkit-transform 0.5s 4.5s; 658 | -moz-transition: opacity 1s 5s, -moz-transform 0.5s 4.5s; 659 | -ms-transition: opacity 1s 5s, -ms-transform 0.5s 4.5s; 660 | -o-transition: opacity 1s 5s, -o-transform 0.5s 4.5s; 661 | transition: opacity 1s 5s, transform 0.5s 4.5s; 662 | } 663 | 664 | /* 665 | And as the last thing there is a workaround for quite strange bug. 666 | It happens a lot in Chrome. I don't remember if I've seen it in Firefox. 667 | 668 | Sometimes the element positioned in 3D (especially when it's moved back 669 | along Z axis) is not clickable, because it falls 'behind' the 670 | element. 671 | 672 | To prevent this, I decided to make non clickable by setting 673 | pointer-events property to `none` value. 674 | Value if this property is inherited, so to make everything else clickable 675 | I bring it back on the #impress element. 676 | 677 | If you want to know more about `pointer-events` here are some docs: 678 | https://developer.mozilla.org/en/CSS/pointer-events 679 | 680 | There is one very important thing to notice about this workaround - it makes 681 | everything 'unclickable' except what's in #impress element. 682 | 683 | So use it wisely ... or don't use at all. 684 | */ 685 | .impress-enabled { pointer-events: none } 686 | .impress-enabled #impress { pointer-events: auto } 687 | 688 | /* 689 | There is one funny thing I just realized. 690 | 691 | Thanks to this workaround above everything except #impress element is invisible 692 | for click events. That means that the hint element is also not clickable. 693 | So basically all of this transforms and delayed transitions trickery was probably 694 | not needed at all... 695 | 696 | But it was fun to learn about it, wasn't it? 697 | */ 698 | 699 | /* 700 | That's all I have for you in this file. 701 | Thanks for reading. I hope you enjoyed it at least as much as I enjoyed writing it 702 | for you. 703 | */ 704 | -------------------------------------------------------------------------------- /js/impress.js: -------------------------------------------------------------------------------- 1 | /** 2 | * impress.js 3 | * 4 | * impress.js is a presentation tool based on the power of CSS3 transforms and transitions 5 | * in modern browsers and inspired by the idea behind prezi.com. 6 | * 7 | * 8 | * Copyright 2011-2012 Bartek Szopka (@bartaz) 9 | * 10 | * Released under the MIT and GPL Licenses. 11 | * 12 | * ------------------------------------------------ 13 | * author: Bartek Szopka 14 | * version: 0.5.3 15 | * url: http://bartaz.github.com/impress.js/ 16 | * source: http://github.com/bartaz/impress.js/ 17 | */ 18 | 19 | /*jshint bitwise:true, curly:true, eqeqeq:true, forin:true, latedef:true, newcap:true, 20 | noarg:true, noempty:true, undef:true, strict:true, browser:true */ 21 | 22 | // You are one of those who like to know how thing work inside? 23 | // Let me show you the cogs that make impress.js run... 24 | (function ( document, window ) { 25 | 'use strict'; 26 | 27 | // HELPER FUNCTIONS 28 | 29 | // `pfx` is a function that takes a standard CSS property name as a parameter 30 | // and returns it's prefixed version valid for current browser it runs in. 31 | // The code is heavily inspired by Modernizr http://www.modernizr.com/ 32 | var pfx = (function () { 33 | 34 | var style = document.createElement('dummy').style, 35 | prefixes = 'Webkit Moz O ms Khtml'.split(' '), 36 | memory = {}; 37 | 38 | return function ( prop ) { 39 | if ( typeof memory[ prop ] === "undefined" ) { 40 | 41 | var ucProp = prop.charAt(0).toUpperCase() + prop.substr(1), 42 | props = (prop + ' ' + prefixes.join(ucProp + ' ') + ucProp).split(' '); 43 | 44 | memory[ prop ] = null; 45 | for ( var i in props ) { 46 | if ( style[ props[i] ] !== undefined ) { 47 | memory[ prop ] = props[i]; 48 | break; 49 | } 50 | } 51 | 52 | } 53 | 54 | return memory[ prop ]; 55 | }; 56 | 57 | })(); 58 | 59 | // `arraify` takes an array-like object and turns it into real Array 60 | // to make all the Array.prototype goodness available. 61 | var arrayify = function ( a ) { 62 | return [].slice.call( a ); 63 | }; 64 | 65 | // `css` function applies the styles given in `props` object to the element 66 | // given as `el`. It runs all property names through `pfx` function to make 67 | // sure proper prefixed version of the property is used. 68 | var css = function ( el, props ) { 69 | var key, pkey; 70 | for ( key in props ) { 71 | if ( props.hasOwnProperty(key) ) { 72 | pkey = pfx(key); 73 | if ( pkey !== null ) { 74 | el.style[pkey] = props[key]; 75 | } 76 | } 77 | } 78 | return el; 79 | }; 80 | 81 | // `toNumber` takes a value given as `numeric` parameter and tries to turn 82 | // it into a number. If it is not possible it returns 0 (or other value 83 | // given as `fallback`). 84 | var toNumber = function (numeric, fallback) { 85 | return isNaN(numeric) ? (fallback || 0) : Number(numeric); 86 | }; 87 | 88 | // `byId` returns element with given `id` - you probably have guessed that ;) 89 | var byId = function ( id ) { 90 | return document.getElementById(id); 91 | }; 92 | 93 | // `$` returns first element for given CSS `selector` in the `context` of 94 | // the given element or whole document. 95 | var $ = function ( selector, context ) { 96 | context = context || document; 97 | return context.querySelector(selector); 98 | }; 99 | 100 | // `$$` return an array of elements for given CSS `selector` in the `context` of 101 | // the given element or whole document. 102 | var $$ = function ( selector, context ) { 103 | context = context || document; 104 | return arrayify( context.querySelectorAll(selector) ); 105 | }; 106 | 107 | // `triggerEvent` builds a custom DOM event with given `eventName` and `detail` data 108 | // and triggers it on element given as `el`. 109 | var triggerEvent = function (el, eventName, detail) { 110 | var event = document.createEvent("CustomEvent"); 111 | event.initCustomEvent(eventName, true, true, detail); 112 | el.dispatchEvent(event); 113 | }; 114 | 115 | // `translate` builds a translate transform string for given data. 116 | var translate = function ( t ) { 117 | return " translate3d(" + t.x + "px," + t.y + "px," + t.z + "px) "; 118 | }; 119 | 120 | // `rotate` builds a rotate transform string for given data. 121 | // By default the rotations are in X Y Z order that can be reverted by passing `true` 122 | // as second parameter. 123 | var rotate = function ( r, revert ) { 124 | var rX = " rotateX(" + r.x + "deg) ", 125 | rY = " rotateY(" + r.y + "deg) ", 126 | rZ = " rotateZ(" + r.z + "deg) "; 127 | 128 | return revert ? rZ+rY+rX : rX+rY+rZ; 129 | }; 130 | 131 | // `scale` builds a scale transform string for given data. 132 | var scale = function ( s ) { 133 | return " scale(" + s + ") "; 134 | }; 135 | 136 | // `perspective` builds a perspective transform string for given data. 137 | var perspective = function ( p ) { 138 | return " perspective(" + p + "px) "; 139 | }; 140 | 141 | // `getElementFromHash` returns an element located by id from hash part of 142 | // window location. 143 | var getElementFromHash = function () { 144 | // get id from url # by removing `#` or `#/` from the beginning, 145 | // so both "fallback" `#slide-id` and "enhanced" `#/slide-id` will work 146 | return byId( window.location.hash.replace(/^#\/?/,"") ); 147 | }; 148 | 149 | // `computeWindowScale` counts the scale factor between window size and size 150 | // defined for the presentation in the config. 151 | var computeWindowScale = function ( config ) { 152 | var hScale = window.innerHeight / config.height, 153 | wScale = window.innerWidth / config.width, 154 | scale = hScale > wScale ? wScale : hScale; 155 | 156 | if (config.maxScale && scale > config.maxScale) { 157 | scale = config.maxScale; 158 | } 159 | 160 | if (config.minScale && scale < config.minScale) { 161 | scale = config.minScale; 162 | } 163 | 164 | return scale; 165 | }; 166 | 167 | // CHECK SUPPORT 168 | var body = document.body; 169 | 170 | var ua = navigator.userAgent.toLowerCase(); 171 | var impressSupported = 172 | // browser should support CSS 3D transtorms 173 | ( pfx("perspective") !== null ) && 174 | 175 | // and `classList` and `dataset` APIs 176 | ( body.classList ) && 177 | ( body.dataset ) && 178 | 179 | // but some mobile devices need to be blacklisted, 180 | // because their CSS 3D support or hardware is not 181 | // good enough to run impress.js properly, sorry... 182 | ( ua.search(/(iphone)|(ipod)|(android)/) === -1 ); 183 | 184 | if (!impressSupported) { 185 | // we can't be sure that `classList` is supported 186 | body.className += " impress-not-supported "; 187 | } else { 188 | body.classList.remove("impress-not-supported"); 189 | body.classList.add("impress-supported"); 190 | } 191 | 192 | // GLOBALS AND DEFAULTS 193 | 194 | // This is were the root elements of all impress.js instances will be kept. 195 | // Yes, this means you can have more than one instance on a page, but I'm not 196 | // sure if it makes any sense in practice ;) 197 | var roots = {}; 198 | 199 | // some default config values. 200 | var defaults = { 201 | width: 1024, 202 | height: 768, 203 | maxScale: 1, 204 | minScale: 0, 205 | 206 | perspective: 1000, 207 | 208 | transitionDuration: 1000 209 | }; 210 | 211 | // it's just an empty function ... and a useless comment. 212 | var empty = function () { return false; }; 213 | 214 | // IMPRESS.JS API 215 | 216 | // And that's where interesting things will start to happen. 217 | // It's the core `impress` function that returns the impress.js API 218 | // for a presentation based on the element with given id ('impress' 219 | // by default). 220 | var impress = window.impress = function ( rootId ) { 221 | 222 | // If impress.js is not supported by the browser return a dummy API 223 | // it may not be a perfect solution but we return early and avoid 224 | // running code that may use features not implemented in the browser. 225 | if (!impressSupported) { 226 | return { 227 | init: empty, 228 | goto: empty, 229 | prev: empty, 230 | next: empty 231 | }; 232 | } 233 | 234 | rootId = rootId || "impress"; 235 | 236 | // if given root is already initialized just return the API 237 | if (roots["impress-root-" + rootId]) { 238 | return roots["impress-root-" + rootId]; 239 | } 240 | 241 | // data of all presentation steps 242 | var stepsData = {}; 243 | 244 | // element of currently active step 245 | var activeStep = null; 246 | 247 | // current state (position, rotation and scale) of the presentation 248 | var currentState = null; 249 | 250 | // array of step elements 251 | var steps = null; 252 | 253 | // configuration options 254 | var config = null; 255 | 256 | // scale factor of the browser window 257 | var windowScale = null; 258 | 259 | // root presentation elements 260 | var root = byId( rootId ); 261 | var canvas = document.createElement("div"); 262 | 263 | var initialized = false; 264 | 265 | // STEP EVENTS 266 | // 267 | // There are currently two step events triggered by impress.js 268 | // `impress:stepenter` is triggered when the step is shown on the 269 | // screen (the transition from the previous one is finished) and 270 | // `impress:stepleave` is triggered when the step is left (the 271 | // transition to next step just starts). 272 | 273 | // reference to last entered step 274 | var lastEntered = null; 275 | 276 | // `onStepEnter` is called whenever the step element is entered 277 | // but the event is triggered only if the step is different than 278 | // last entered step. 279 | var onStepEnter = function (step) { 280 | if (lastEntered !== step) { 281 | triggerEvent(step, "impress:stepenter"); 282 | lastEntered = step; 283 | } 284 | }; 285 | 286 | // `onStepLeave` is called whenever the step element is left 287 | // but the event is triggered only if the step is the same as 288 | // last entered step. 289 | var onStepLeave = function (step) { 290 | if (lastEntered === step) { 291 | triggerEvent(step, "impress:stepleave"); 292 | lastEntered = null; 293 | } 294 | }; 295 | 296 | // `initStep` initializes given step element by reading data from its 297 | // data attributes and setting correct styles. 298 | var initStep = function ( el, idx ) { 299 | var data = el.dataset, 300 | step = { 301 | translate: { 302 | x: toNumber(data.x), 303 | y: toNumber(data.y), 304 | z: toNumber(data.z) 305 | }, 306 | rotate: { 307 | x: toNumber(data.rotateX), 308 | y: toNumber(data.rotateY), 309 | z: toNumber(data.rotateZ || data.rotate) 310 | }, 311 | scale: toNumber(data.scale, 1), 312 | el: el 313 | }; 314 | 315 | if ( !el.id ) { 316 | el.id = "step-" + (idx + 1); 317 | } 318 | 319 | stepsData["impress-" + el.id] = step; 320 | 321 | css(el, { 322 | position: "absolute", 323 | transform: "translate(-50%,-50%)" + 324 | translate(step.translate) + 325 | rotate(step.rotate) + 326 | scale(step.scale), 327 | transformStyle: "preserve-3d" 328 | }); 329 | }; 330 | 331 | // `init` API function that initializes (and runs) the presentation. 332 | var init = function () { 333 | if (initialized) { return; } 334 | 335 | // First we set up the viewport for mobile devices. 336 | // For some reason iPad goes nuts when it is not done properly. 337 | var meta = $("meta[name='viewport']") || document.createElement("meta"); 338 | meta.content = "width=device-width, minimum-scale=1, maximum-scale=1, user-scalable=no"; 339 | if (meta.parentNode !== document.head) { 340 | meta.name = 'viewport'; 341 | document.head.appendChild(meta); 342 | } 343 | 344 | // initialize configuration object 345 | var rootData = root.dataset; 346 | config = { 347 | width: toNumber( rootData.width, defaults.width ), 348 | height: toNumber( rootData.height, defaults.height ), 349 | maxScale: toNumber( rootData.maxScale, defaults.maxScale ), 350 | minScale: toNumber( rootData.minScale, defaults.minScale ), 351 | perspective: toNumber( rootData.perspective, defaults.perspective ), 352 | transitionDuration: toNumber( rootData.transitionDuration, defaults.transitionDuration ) 353 | }; 354 | 355 | windowScale = computeWindowScale( config ); 356 | 357 | // wrap steps with "canvas" element 358 | arrayify( root.childNodes ).forEach(function ( el ) { 359 | canvas.appendChild( el ); 360 | }); 361 | root.appendChild(canvas); 362 | 363 | // set initial styles 364 | document.documentElement.style.height = "100%"; 365 | 366 | css(body, { 367 | height: "100%", 368 | overflow: "hidden" 369 | }); 370 | 371 | var rootStyles = { 372 | position: "absolute", 373 | transformOrigin: "top left", 374 | transition: "all 0s ease-in-out", 375 | transformStyle: "preserve-3d" 376 | }; 377 | 378 | css(root, rootStyles); 379 | css(root, { 380 | top: "50%", 381 | left: "50%", 382 | transform: perspective( config.perspective/windowScale ) + scale( windowScale ) 383 | }); 384 | css(canvas, rootStyles); 385 | 386 | body.classList.remove("impress-disabled"); 387 | body.classList.add("impress-enabled"); 388 | 389 | // get and init steps 390 | steps = $$(".step", root); 391 | steps.forEach( initStep ); 392 | 393 | // set a default initial state of the canvas 394 | currentState = { 395 | translate: { x: 0, y: 0, z: 0 }, 396 | rotate: { x: 0, y: 0, z: 0 }, 397 | scale: 1 398 | }; 399 | 400 | initialized = true; 401 | 402 | triggerEvent(root, "impress:init", { api: roots[ "impress-root-" + rootId ] }); 403 | }; 404 | 405 | // `getStep` is a helper function that returns a step element defined by parameter. 406 | // If a number is given, step with index given by the number is returned, if a string 407 | // is given step element with such id is returned, if DOM element is given it is returned 408 | // if it is a correct step element. 409 | var getStep = function ( step ) { 410 | if (typeof step === "number") { 411 | step = step < 0 ? steps[ steps.length + step] : steps[ step ]; 412 | } else if (typeof step === "string") { 413 | step = byId(step); 414 | } 415 | return (step && step.id && stepsData["impress-" + step.id]) ? step : null; 416 | }; 417 | 418 | // used to reset timeout for `impress:stepenter` event 419 | var stepEnterTimeout = null; 420 | 421 | // `goto` API function that moves to step given with `el` parameter (by index, id or element), 422 | // with a transition `duration` optionally given as second parameter. 423 | var goto = function ( el, duration ) { 424 | 425 | if ( !initialized || !(el = getStep(el)) ) { 426 | // presentation not initialized or given element is not a step 427 | return false; 428 | } 429 | 430 | // Sometimes it's possible to trigger focus on first link with some keyboard action. 431 | // Browser in such a case tries to scroll the page to make this element visible 432 | // (even that body overflow is set to hidden) and it breaks our careful positioning. 433 | // 434 | // So, as a lousy (and lazy) workaround we will make the page scroll back to the top 435 | // whenever slide is selected 436 | // 437 | // If you are reading this and know any better way to handle it, I'll be glad to hear about it! 438 | window.scrollTo(0, 0); 439 | 440 | var step = stepsData["impress-" + el.id]; 441 | 442 | if ( activeStep ) { 443 | activeStep.classList.remove("active"); 444 | body.classList.remove("impress-on-" + activeStep.id); 445 | } 446 | el.classList.add("active"); 447 | 448 | body.classList.add("impress-on-" + el.id); 449 | 450 | // compute target state of the canvas based on given step 451 | var target = { 452 | rotate: { 453 | x: -step.rotate.x, 454 | y: -step.rotate.y, 455 | z: -step.rotate.z 456 | }, 457 | translate: { 458 | x: -step.translate.x, 459 | y: -step.translate.y, 460 | z: -step.translate.z 461 | }, 462 | scale: 1 / step.scale 463 | }; 464 | 465 | // Check if the transition is zooming in or not. 466 | // 467 | // This information is used to alter the transition style: 468 | // when we are zooming in - we start with move and rotate transition 469 | // and the scaling is delayed, but when we are zooming out we start 470 | // with scaling down and move and rotation are delayed. 471 | var zoomin = target.scale >= currentState.scale; 472 | 473 | duration = toNumber(duration, config.transitionDuration); 474 | var delay = (duration / 2); 475 | 476 | // if the same step is re-selected, force computing window scaling, 477 | // because it is likely to be caused by window resize 478 | if (el === activeStep) { 479 | windowScale = computeWindowScale(config); 480 | } 481 | 482 | var targetScale = target.scale * windowScale; 483 | 484 | // trigger leave of currently active element (if it's not the same step again) 485 | if (activeStep && activeStep !== el) { 486 | onStepLeave(activeStep); 487 | } 488 | 489 | // Now we alter transforms of `root` and `canvas` to trigger transitions. 490 | // 491 | // And here is why there are two elements: `root` and `canvas` - they are 492 | // being animated separately: 493 | // `root` is used for scaling and `canvas` for translate and rotations. 494 | // Transitions on them are triggered with different delays (to make 495 | // visually nice and 'natural' looking transitions), so we need to know 496 | // that both of them are finished. 497 | css(root, { 498 | // to keep the perspective look similar for different scales 499 | // we need to 'scale' the perspective, too 500 | transform: perspective( config.perspective / targetScale ) + scale( targetScale ), 501 | transitionDuration: duration + "ms", 502 | transitionDelay: (zoomin ? delay : 0) + "ms" 503 | }); 504 | 505 | css(canvas, { 506 | transform: rotate(target.rotate, true) + translate(target.translate), 507 | transitionDuration: duration + "ms", 508 | transitionDelay: (zoomin ? 0 : delay) + "ms" 509 | }); 510 | 511 | // Here is a tricky part... 512 | // 513 | // If there is no change in scale or no change in rotation and translation, it means there was actually 514 | // no delay - because there was no transition on `root` or `canvas` elements. 515 | // We want to trigger `impress:stepenter` event in the correct moment, so here we compare the current 516 | // and target values to check if delay should be taken into account. 517 | // 518 | // I know that this `if` statement looks scary, but it's pretty simple when you know what is going on 519 | // - it's simply comparing all the values. 520 | if ( currentState.scale === target.scale || 521 | (currentState.rotate.x === target.rotate.x && currentState.rotate.y === target.rotate.y && 522 | currentState.rotate.z === target.rotate.z && currentState.translate.x === target.translate.x && 523 | currentState.translate.y === target.translate.y && currentState.translate.z === target.translate.z) ) { 524 | delay = 0; 525 | } 526 | 527 | // store current state 528 | currentState = target; 529 | activeStep = el; 530 | 531 | // And here is where we trigger `impress:stepenter` event. 532 | // We simply set up a timeout to fire it taking transition duration (and possible delay) into account. 533 | // 534 | // I really wanted to make it in more elegant way. The `transitionend` event seemed to be the best way 535 | // to do it, but the fact that I'm using transitions on two separate elements and that the `transitionend` 536 | // event is only triggered when there was a transition (change in the values) caused some bugs and 537 | // made the code really complicated, cause I had to handle all the conditions separately. And it still 538 | // needed a `setTimeout` fallback for the situations when there is no transition at all. 539 | // So I decided that I'd rather make the code simpler than use shiny new `transitionend`. 540 | // 541 | // If you want learn something interesting and see how it was done with `transitionend` go back to 542 | // version 0.5.2 of impress.js: http://github.com/bartaz/impress.js/blob/0.5.2/js/impress.js 543 | window.clearTimeout(stepEnterTimeout); 544 | stepEnterTimeout = window.setTimeout(function() { 545 | onStepEnter(activeStep); 546 | }, duration + delay); 547 | 548 | return el; 549 | }; 550 | 551 | // `prev` API function goes to previous step (in document order) 552 | var prev = function () { 553 | var prev = steps.indexOf( activeStep ) - 1; 554 | prev = prev >= 0 ? steps[ prev ] : steps[ steps.length-1 ]; 555 | 556 | return goto(prev); 557 | }; 558 | 559 | // `next` API function goes to next step (in document order) 560 | var next = function () { 561 | var next = steps.indexOf( activeStep ) + 1; 562 | next = next < steps.length ? steps[ next ] : steps[ 0 ]; 563 | 564 | return goto(next); 565 | }; 566 | 567 | // Adding some useful classes to step elements. 568 | // 569 | // All the steps that have not been shown yet are given `future` class. 570 | // When the step is entered the `future` class is removed and the `present` 571 | // class is given. When the step is left `present` class is replaced with 572 | // `past` class. 573 | // 574 | // So every step element is always in one of three possible states: 575 | // `future`, `present` and `past`. 576 | // 577 | // There classes can be used in CSS to style different types of steps. 578 | // For example the `present` class can be used to trigger some custom 579 | // animations when step is shown. 580 | root.addEventListener("impress:init", function(){ 581 | // STEP CLASSES 582 | steps.forEach(function (step) { 583 | step.classList.add("future"); 584 | }); 585 | 586 | root.addEventListener("impress:stepenter", function (event) { 587 | event.target.classList.remove("past"); 588 | event.target.classList.remove("future"); 589 | event.target.classList.add("present"); 590 | }, false); 591 | 592 | root.addEventListener("impress:stepleave", function (event) { 593 | event.target.classList.remove("present"); 594 | event.target.classList.add("past"); 595 | }, false); 596 | 597 | }, false); 598 | 599 | // Adding hash change support. 600 | root.addEventListener("impress:init", function(){ 601 | 602 | // last hash detected 603 | var lastHash = ""; 604 | 605 | // `#/step-id` is used instead of `#step-id` to prevent default browser 606 | // scrolling to element in hash. 607 | // 608 | // And it has to be set after animation finishes, because in Chrome it 609 | // makes transtion laggy. 610 | // BUG: http://code.google.com/p/chromium/issues/detail?id=62820 611 | root.addEventListener("impress:stepenter", function (event) { 612 | window.location.hash = lastHash = "#/" + event.target.id; 613 | }, false); 614 | 615 | window.addEventListener("hashchange", function () { 616 | // When the step is entered hash in the location is updated 617 | // (just few lines above from here), so the hash change is 618 | // triggered and we would call `goto` again on the same element. 619 | // 620 | // To avoid this we store last entered hash and compare. 621 | if (window.location.hash !== lastHash) { 622 | goto( getElementFromHash() ); 623 | } 624 | }, false); 625 | 626 | // START 627 | // by selecting step defined in url or first step of the presentation 628 | goto(getElementFromHash() || steps[0], 0); 629 | }, false); 630 | 631 | body.classList.add("impress-disabled"); 632 | 633 | // store and return API for given impress.js root element 634 | return (roots[ "impress-root-" + rootId ] = { 635 | init: init, 636 | goto: goto, 637 | next: next, 638 | prev: prev 639 | }); 640 | 641 | }; 642 | 643 | // flag that can be used in JS to check if browser have passed the support test 644 | impress.supported = impressSupported; 645 | 646 | })(document, window); 647 | 648 | // NAVIGATION EVENTS 649 | 650 | // As you can see this part is separate from the impress.js core code. 651 | // It's because these navigation actions only need what impress.js provides with 652 | // its simple API. 653 | // 654 | // In future I think about moving it to make them optional, move to separate files 655 | // and treat more like a 'plugins'. 656 | (function ( document, window ) { 657 | 'use strict'; 658 | 659 | // throttling function calls, by Remy Sharp 660 | // http://remysharp.com/2010/07/21/throttling-function-calls/ 661 | var throttle = function (fn, delay) { 662 | var timer = null; 663 | return function () { 664 | var context = this, args = arguments; 665 | clearTimeout(timer); 666 | timer = setTimeout(function () { 667 | fn.apply(context, args); 668 | }, delay); 669 | }; 670 | }; 671 | 672 | // wait for impress.js to be initialized 673 | document.addEventListener("impress:init", function (event) { 674 | // Getting API from event data. 675 | // So you don't event need to know what is the id of the root element 676 | // or anything. `impress:init` event data gives you everything you 677 | // need to control the presentation that was just initialized. 678 | var api = event.detail.api; 679 | 680 | // KEYBOARD NAVIGATION HANDLERS 681 | 682 | // Prevent default keydown action when one of supported key is pressed. 683 | document.addEventListener("keydown", function ( event ) { 684 | if ( event.keyCode === 9 || ( event.keyCode >= 32 && event.keyCode <= 34 ) || (event.keyCode >= 37 && event.keyCode <= 40) ) { 685 | event.preventDefault(); 686 | } 687 | }, false); 688 | 689 | // Trigger impress action (next or prev) on keyup. 690 | 691 | // Supported keys are: 692 | // [space] - quite common in presentation software to move forward 693 | // [up] [right] / [down] [left] - again common and natural addition, 694 | // [pgdown] / [pgup] - often triggered by remote controllers, 695 | // [tab] - this one is quite controversial, but the reason it ended up on 696 | // this list is quite an interesting story... Remember that strange part 697 | // in the impress.js code where window is scrolled to 0,0 on every presentation 698 | // step, because sometimes browser scrolls viewport because of the focused element? 699 | // Well, the [tab] key by default navigates around focusable elements, so clicking 700 | // it very often caused scrolling to focused element and breaking impress.js 701 | // positioning. I didn't want to just prevent this default action, so I used [tab] 702 | // as another way to moving to next step... And yes, I know that for the sake of 703 | // consistency I should add [shift+tab] as opposite action... 704 | document.addEventListener("keyup", function ( event ) { 705 | if ( event.keyCode === 9 || ( event.keyCode >= 32 && event.keyCode <= 34 ) || (event.keyCode >= 37 && event.keyCode <= 40) ) { 706 | switch( event.keyCode ) { 707 | case 33: // pg up 708 | case 37: // left 709 | case 38: // up 710 | api.prev(); 711 | break; 712 | case 9: // tab 713 | case 32: // space 714 | case 34: // pg down 715 | case 39: // right 716 | case 40: // down 717 | api.next(); 718 | break; 719 | } 720 | 721 | event.preventDefault(); 722 | } 723 | }, false); 724 | 725 | // delegated handler for clicking on the links to presentation steps 726 | document.addEventListener("click", function ( event ) { 727 | // event delegation with "bubbling" 728 | // check if event target (or any of its parents is a link) 729 | var target = event.target; 730 | while ( (target.tagName !== "A") && 731 | (target !== document.documentElement) ) { 732 | target = target.parentNode; 733 | } 734 | 735 | if ( target.tagName === "A" ) { 736 | var href = target.getAttribute("href"); 737 | 738 | // if it's a link to presentation step, target this step 739 | if ( href && href[0] === '#' ) { 740 | target = document.getElementById( href.slice(1) ); 741 | } 742 | } 743 | 744 | if ( api.goto(target) ) { 745 | event.stopImmediatePropagation(); 746 | event.preventDefault(); 747 | } 748 | }, false); 749 | 750 | // delegated handler for clicking on step elements 751 | document.addEventListener("click", function ( event ) { 752 | var target = event.target; 753 | // find closest step element that is not active 754 | while ( !(target.classList.contains("step") && !target.classList.contains("active")) && 755 | (target !== document.documentElement) ) { 756 | target = target.parentNode; 757 | } 758 | 759 | if ( api.goto(target) ) { 760 | event.preventDefault(); 761 | } 762 | }, false); 763 | 764 | // touch handler to detect taps on the left and right side of the screen 765 | // based on awesome work of @hakimel: https://github.com/hakimel/reveal.js 766 | document.addEventListener("touchstart", function ( event ) { 767 | if (event.touches.length === 1) { 768 | var x = event.touches[0].clientX, 769 | width = window.innerWidth * 0.3, 770 | result = null; 771 | 772 | if ( x < width ) { 773 | result = api.prev(); 774 | } else if ( x > window.innerWidth - width ) { 775 | result = api.next(); 776 | } 777 | 778 | if (result) { 779 | event.preventDefault(); 780 | } 781 | } 782 | }, false); 783 | 784 | // rescale presentation when window is resized 785 | window.addEventListener("resize", throttle(function () { 786 | // force going to active step again, to trigger rescaling 787 | api.goto( document.querySelector(".active"), 500 ); 788 | }, 250), false); 789 | 790 | }, false); 791 | 792 | })(document, window); 793 | 794 | // THAT'S ALL FOLKS! 795 | // 796 | // Thanks for reading it all. 797 | // Or thanks for scrolling down and reading the last part. 798 | // 799 | // I've learnt a lot when building impress.js and I hope this code and comments 800 | // will help somebody learn at least some part of it. 801 | --------------------------------------------------------------------------------