├── CHANGELOG.md ├── README.md ├── build ├── build ├── build.xml └── google-compiler-20100917.jar ├── compass ├── config.rb └── scss │ └── style.scss ├── css ├── img │ └── html5.png └── style.css ├── demo.html └── js ├── cssanimation.jquery.js ├── cssanimation.jquery.min.js ├── cssanimation.js ├── cssanimation.min.js └── src ├── cssanimation.jquery.js ├── cssanimation.js └── requestanimationframe.js /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.2 (2011/06/14) 2 | - Added `iterationCount` property to allow for repeating animations. 3 | - Added support for Firefox (FF5 will support CSS Animations). 4 | - Fixed a bug in `CSSAnimation.find` where trying to interrogate a stylesheet from another domain could throw a security error. 5 | 6 | # 0.1 (2011/05/20) 7 | - Initial release -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CSS3 Animation Keyframe Events 2 | CSS3 Animations are great but the current implementation doesn't trigger Javascript Events for each Keyframe [(see here for more information)](http://blog.joelambert.co.uk/2011/05/17/keyframe-events-for-css3-animations/). 3 | 4 | The CSSAnimation object provides the events the browser vendors left out! This allows you to bind event handlers to `cssAnimationKeyframe` events and perform any additional code that needs to happen at each keyframe. 5 | 6 | *Note: This won't work as well on Mobile Webkit until `webkitRequestAnimationFrame()` is implemented!* 7 | 8 | ## Tested Under 9 | - Safari 5 10 | - Chrome 12 11 | - Firefox 5 12 | 13 | ## May/should also work for 14 | - iOS (see above caveat about Mobile WebKit) 15 | 16 | # Requirements 17 | A browser capable of rendering CSS3 Animations. 18 | 19 | # Usage 20 | Create the CSS animation keyframes in CSS as you would normally, for example with Safari/Chrome: 21 | 22 | @-webkit-keyframes boxrotate { 23 | 0% { 24 | -webkit-transform: translate3d(0, 0, 0); 25 | background: #da371e; 26 | } 27 | 28 | 25% { 29 | -webkit-transform: translate3d(0px, 200px, 0) rotate(90deg); 30 | background: #da3ab9; 31 | } 32 | 33 | 50% { 34 | -webkit-transform: translate3d(200px, 200px, 0) rotate(180deg); 35 | background: #34b6da; 36 | } 37 | 38 | 75% { 39 | -webkit-transform: translate3d(200px, 0, 0) rotate(270deg); 40 | background: #88da50; 41 | } 42 | 43 | 100% { 44 | -webkit-transform: translate3d(0, 0, 0) rotate(360deg); 45 | background: #da371e; 46 | } 47 | } 48 | 49 | Next trigger the animation on a specified DOM element: 50 | 51 | var elem = document.getElementById('animateme'); 52 | 53 | // Trigger the animation named 'boxrotate' with duration 3000ms 54 | CSSAnimation.trigger(elem, 'boxrotate', 3000); 55 | 56 | There is also a jQuery plugin provided for convenience (`cssanimation.jquery.js`): 57 | 58 | $('#animateme').cssanimation('boxrotate', 3000); 59 | 60 | You can then listen for `cssAnimationKeyframe` events the same way you'd listen for any other. Here's an example using jQuery: 61 | 62 | $('#animateme').bind('cssAnimationKeyframe', function(event){ 63 | var text = ""; 64 | 65 | switch(event.originalEvent.keyText) { 66 | case '0%': 67 | text = "down ↓"; break; 68 | case '25%': 69 | text = "right →"; break; 70 | case '50%': 71 | text = "up ↑"; break; 72 | case '75%': 73 | text = "left ←"; break; 74 | case '100%': 75 | text = "click me"; break; 76 | }; 77 | 78 | $('#text').html(text); 79 | }); 80 | 81 | # Limitations 82 | 83 | Currently events will only be fired for keyframes at 5% increments (e.g. 0%, 5%, 10% etc). So if you have a keyframe at 23%, you won't be notified. This is similar in design to how [jQuery Runloop](http://farukat.es/journal/2011/02/514-new-creation-jquery-runloop-plugin) works. 84 | 85 | You can change this if you want but you may begin to miss events! If you really want to change this you can by passing in an option to either the native function or jQuery plugin: 86 | 87 | // Native 88 | CSSAnimation.trigger(elem, 'boxrotate', 3000, { 89 | base: 1 // Raise events for keyframes at 1% increments 90 | }); 91 | 92 | // jQuery 93 | elem.cssanimation('boxrotate', 3000, { 94 | base: 1 // Raise events for keyframes at 1% increments 95 | }); 96 | 97 | # How does it work? 98 | 99 | Using the `requestAnimFrame()` shim by [Paul Irish](http://paulirish.com/2011/requestanimationframe-for-smart-animating/) we can get very accurate callbacks (@60fps), this means that: 100 | 101 | > The browser can optimize concurrent animations together into a single reflow and repaint cycle, leading to higher fidelity animation. For example, JS-based animations synchronized with CSS transitions. 102 | 103 | This enables us to work out when a keyframe ought to have occured and raise a suitable event. 104 | 105 | # Contributing 106 | 107 | Contribution is welcomed but to make it easier to accept a pull request here are some guidelines: 108 | 109 | - Please make all changes into the source files found under `js/src`. You can then use the build tool (PHP script found at `./build/build`) to create the concatenated and minified files. 110 | 111 | - Please [camelCase](http://en.wikipedia.org/wiki/CamelCase) your variables, especially if they are used in the options object. 112 | 113 | # License 114 | 115 | CSSAnimation Keyframe Events is Copyright © 2011 [Joe Lambert](http://www.joelambert.co.uk) and is licensed under the terms of the [MIT License](http://www.opensource.org/licenses/mit-license.php). -------------------------------------------------------------------------------- /build/build: -------------------------------------------------------------------------------- 1 | #!/usr/bin/php 2 | docroot ? $xml->docroot : "./"; 13 | 14 | $version = $xml->version ? $xml->version : "1.0"; 15 | 16 | // Process each output file 17 | foreach($xml->output->file as $output) 18 | { 19 | $concat = ""; 20 | $attr = $output->attributes(); 21 | 22 | $outputname = $attr['name'] ? $attr['name']."" : "javascript"; 23 | 24 | // Concat javascript files 25 | foreach($output->source as $file) 26 | $concat .= file_get_contents(dirname(__FILE__)."/$docroot/$file").";\n\n"; 27 | 28 | // Process any replacements 29 | $concat = str_replace('@VERSION', $version, $concat); 30 | 31 | // Save to disk 32 | $max_path = dirname(__FILE__)."/$docroot/$outputname.js"; 33 | file_put_contents($max_path, $concat); 34 | 35 | // Compile and minify with Google Closure Compiler 36 | $min_path = dirname(__FILE__)."/$docroot/$outputname.min.js"; 37 | 38 | system("java -jar ".escapeshellarg(dirname(__FILE__)."/google-compiler-20100917.jar")." --js ".escapeshellarg($max_path)." --warning_level QUIET --js_output_file ".escapeshellarg($min_path)); 39 | } 40 | ?> -------------------------------------------------------------------------------- /build/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 0.2 4 | ../js/ 5 | 6 | 7 | src/cssanimation.js 8 | src/requestanimationframe.js 9 | 10 | 11 | src/cssanimation.jquery.js 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /build/google-compiler-20100917.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelambert/CSSAnimationKeyframeEvents/cb72cdee63a5ec193353522520b69896b3f72aea/build/google-compiler-20100917.jar -------------------------------------------------------------------------------- /compass/config.rb: -------------------------------------------------------------------------------- 1 | # Require any additional compass plugins here. 2 | 3 | # Set this to the root of your project when deployed: 4 | http_path = "/" 5 | css_dir = "../css" 6 | sass_dir = "scss" 7 | images_dir = "../css/img" 8 | javascripts_dir = "javascripts" 9 | 10 | # You can select your preferred output style here (can be overridden via the command line): 11 | # output_style = :expanded or :nested or :compact or :compressed 12 | 13 | # To enable relative paths to assets via compass helper functions. Uncomment: 14 | # relative_assets = true 15 | 16 | # To disable debugging comments that display the original location of your selectors. Uncomment: 17 | # line_comments = false 18 | 19 | 20 | # If you prefer the indented syntax, you might want to regenerate this 21 | # project again passing --syntax sass, or you can uncomment this: 22 | # preferred_syntax = :sass 23 | # and then run: 24 | # sass-convert -R --from scss --to sass sass scss && rm -rf sass && mv scss sass 25 | -------------------------------------------------------------------------------- /compass/scss/style.scss: -------------------------------------------------------------------------------- 1 | /* Welcome to Compass. 2 | * In this file you should write your main styles. (or centralize your imports) 3 | * Import this file using the following HTML or equivalent: 4 | * */ 5 | 6 | @import "compass/reset"; 7 | @import "compass/css3"; 8 | @include reset-html5; 9 | 10 | @-webkit-keyframes boxrotate { 11 | 0% 12 | { 13 | -webkit-transform: translate3d(0, 0, 0); 14 | background: #da371e; 15 | } 16 | 17 | 25% 18 | { 19 | -webkit-transform: translate3d(0px, 200px, 0) rotate(90deg); 20 | background: #da3ab9; 21 | } 22 | 23 | 50% 24 | { 25 | -webkit-transform: translate3d(200px, 200px, 0) rotate(180deg); 26 | background: #34b6da; 27 | } 28 | 29 | 75% 30 | { 31 | -webkit-transform: translate3d(200px, 0, 0) rotate(270deg); 32 | background: #88da50; 33 | } 34 | 35 | 100% 36 | { 37 | -webkit-transform: translate3d(0, 0, 0) rotate(360deg); 38 | background: #da371e; 39 | } 40 | } 41 | 42 | @-moz-keyframes boxrotate { 43 | 0% 44 | { 45 | -moz-transform: translate(0, 0); 46 | background: #da371e; 47 | } 48 | 49 | 25% 50 | { 51 | -moz-transform: translate(0px, 200px) rotate(90deg); 52 | background: #da3ab9; 53 | } 54 | 55 | 50% 56 | { 57 | -moz-transform: translate(200px, 200px) rotate(180deg); 58 | background: #34b6da; 59 | } 60 | 61 | 75% 62 | { 63 | -moz-transform: translate(200px, 0) rotate(270deg); 64 | background: #88da50; 65 | } 66 | 67 | 100% 68 | { 69 | -moz-transform: translate(0, 0) rotate(360deg); 70 | background: #da371e; 71 | } 72 | } 73 | 74 | html 75 | { 76 | background: #222; 77 | font-family: helvetica, arial, sans-serif; 78 | color: #EEE; 79 | text-shadow: 0 1px 0 rgba(#000, 0.2); 80 | } 81 | 82 | 83 | 84 | em 85 | { 86 | font-style: italic; 87 | } 88 | 89 | code 90 | { 91 | font-family: courier; 92 | padding: 0.3em; 93 | margin: 0 0.2em; 94 | background: rgba(#FFF, 0.1); 95 | @include border-radius(3px); 96 | @include box-shadow(0px 1px 3px rgba(#000, 0.5)); 97 | } 98 | 99 | body 100 | { 101 | 102 | header 103 | { 104 | background: #333; 105 | padding: 10px; 106 | overflow: auto; 107 | 108 | a 109 | { 110 | color: #CCC; 111 | text-decoration: none; 112 | } 113 | 114 | a.name 115 | { 116 | font-family: 'Pacifico'; 117 | font-size: 1.1em; 118 | margin-right: 1em; 119 | position: relative; 120 | top: 3px; 121 | } 122 | 123 | a.writeup 124 | { 125 | font-size: 0.8em; 126 | border-bottom: 1px dashed #CCC; 127 | 128 | &:after 129 | { 130 | content: ' \2192'; 131 | } 132 | } 133 | 134 | a.twitter 135 | { 136 | float: right; 137 | } 138 | 139 | div.nav 140 | { 141 | float: right; 142 | font-size: 0.7em; 143 | padding: 0.75em; 144 | padding-right: 1.5em; 145 | 146 | a 147 | { 148 | margin-left: 1.5em; 149 | 150 | &:hover 151 | { 152 | border-bottom: 1px dashed #CCC; 153 | } 154 | } 155 | } 156 | } 157 | 158 | section#container 159 | { 160 | width: 230px; 161 | height: 230px; 162 | background: rgba(#FFF, 0.1); 163 | margin: 100px auto 50px; 164 | padding: 20px; 165 | @include box-shadow(0px 1px 6px rgba(#000, 0.5)); 166 | cursor: pointer; 167 | 168 | div#animate 169 | { 170 | width: 30px; 171 | height: 30px; 172 | background: #da371e; 173 | @include border-radius(3px); 174 | /* border: 1px solid rgba(#000, 0.2); 175 | @include background-clip(padding);*/ 176 | } 177 | 178 | div#text 179 | { 180 | text-align: center; 181 | font-size: 3em; 182 | margin-top: 1em; 183 | } 184 | } 185 | 186 | section#blurb 187 | { 188 | width: 600px; 189 | margin: 0px auto; 190 | line-height: 1.6em; 191 | 192 | a 193 | { 194 | color: #FFF; 195 | text-decoration: none; 196 | border-bottom: 1px dashed #CCC; 197 | } 198 | 199 | h1 200 | { 201 | font-size: 2em; 202 | font-weight: bold; 203 | } 204 | 205 | p 206 | { 207 | margin: 1.6em 0; 208 | } 209 | 210 | h2 211 | { 212 | font-size: 2em; 213 | font-weight: bold; 214 | margin-top: 1.3em; 215 | } 216 | 217 | .note 218 | { 219 | font-size: 0.8em; 220 | color: #666 !important; 221 | } 222 | 223 | section.code 224 | { 225 | h3 226 | { 227 | background: rgba(#FFF, 0.2); 228 | @include box-shadow(0px 1px 6px rgba(#000, 0.5)); 229 | padding: 0.5em 0.8em; 230 | 231 | span 232 | { 233 | font-family: times, georgia, serif; 234 | font-style: italic; 235 | font-size: 1.1em; 236 | } 237 | } 238 | 239 | pre 240 | { 241 | @include box-shadow(0px 1px 6px rgba(#000, 0.5)); 242 | @include border-radius(0px 0px 3px 3px); 243 | padding: 1em; 244 | font-family: courier; 245 | font-size: 0.8em; 246 | background: rgba(#FFF, 0.5); 247 | color: #111; 248 | text-shadow: 0 1px 0 rgba(#FFF, 0.4); 249 | margin-bottom: 3em; 250 | } 251 | } 252 | } 253 | 254 | section#details 255 | { 256 | text-align: center; 257 | padding-bottom: 4em; 258 | 259 | a 260 | { 261 | margin-left: 1em; 262 | padding: 1em; 263 | background: rgba(#FFF, 0.1); 264 | @include border-radius(3px); 265 | color: #EEE; 266 | text-shadow: 0 1px 0 rgba(#FFF, 0.2); 267 | text-decoration: none; 268 | border: 1px solid rgba(#FFF, 0.3); 269 | font-weight: bold; 270 | font-size: 0.8em; 271 | 272 | &:first-child 273 | { 274 | margin-left: 0; 275 | } 276 | 277 | &:hover 278 | { 279 | background: rgba(#FFF, 0.3); 280 | color: #000; 281 | } 282 | } 283 | } 284 | 285 | footer 286 | { 287 | font-size: 0.7em; 288 | text-align: center; 289 | margin-bottom: 3em; 290 | margin-top: 5em; 291 | 292 | img 293 | { 294 | margin-top: 2em; 295 | } 296 | 297 | a 298 | { 299 | color: #FFF; 300 | text-decoration: none; 301 | border-bottom: 1px dashed #CCC; 302 | } 303 | } 304 | } 305 | 306 | div#carbonads-container 307 | { 308 | div.carbonad 309 | { 310 | background: #353535; 311 | margin: 2em auto 3em; 312 | border: 1px solid rgba(#FFF, 0.1); 313 | @include border-radius(3px); 314 | text-align: left; 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /css/img/html5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelambert/CSSAnimationKeyframeEvents/cb72cdee63a5ec193353522520b69896b3f72aea/css/img/html5.png -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | /* Welcome to Compass. 2 | * In this file you should write your main styles. (or centralize your imports) 3 | * Import this file using the following HTML or equivalent: 4 | * */ 5 | /* line 17, ../../../../../.gem/ruby/1.8/gems/compass-0.11.1/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | vertical-align: baseline; 25 | } 26 | 27 | /* line 20, ../../../../../.gem/ruby/1.8/gems/compass-0.11.1/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ 28 | body { 29 | line-height: 1; 30 | } 31 | 32 | /* line 22, ../../../../../.gem/ruby/1.8/gems/compass-0.11.1/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ 33 | ol, ul { 34 | list-style: none; 35 | } 36 | 37 | /* line 24, ../../../../../.gem/ruby/1.8/gems/compass-0.11.1/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ 38 | table { 39 | border-collapse: collapse; 40 | border-spacing: 0; 41 | } 42 | 43 | /* line 26, ../../../../../.gem/ruby/1.8/gems/compass-0.11.1/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ 44 | caption, th, td { 45 | text-align: left; 46 | font-weight: normal; 47 | vertical-align: middle; 48 | } 49 | 50 | /* line 28, ../../../../../.gem/ruby/1.8/gems/compass-0.11.1/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ 51 | q, blockquote { 52 | quotes: none; 53 | } 54 | /* line 101, ../../../../../.gem/ruby/1.8/gems/compass-0.11.1/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ 55 | q:before, q:after, blockquote:before, blockquote:after { 56 | content: ""; 57 | content: none; 58 | } 59 | 60 | /* line 30, ../../../../../.gem/ruby/1.8/gems/compass-0.11.1/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ 61 | a img { 62 | border: none; 63 | } 64 | 65 | /* line 115, ../../../../../.gem/ruby/1.8/gems/compass-0.11.1/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ 66 | article, aside, details, figcaption, figure, 67 | footer, header, hgroup, menu, nav, section { 68 | display: block; 69 | } 70 | 71 | /* line 115, ../../../../../.gem/ruby/1.8/gems/compass-0.11.1/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ 72 | article, aside, details, figcaption, figure, 73 | footer, header, hgroup, menu, nav, section { 74 | display: block; 75 | } 76 | 77 | @-webkit-keyframes boxrotate { 78 | /* line 12, ../compass/scss/style.scss */ 79 | 0% { 80 | -webkit-transform: translate3d(0, 0, 0); 81 | background: #da371e; 82 | } 83 | 84 | /* line 18, ../compass/scss/style.scss */ 85 | 25% { 86 | -webkit-transform: translate3d(0px, 200px, 0) rotate(90deg); 87 | background: #da3ab9; 88 | } 89 | 90 | /* line 24, ../compass/scss/style.scss */ 91 | 50% { 92 | -webkit-transform: translate3d(200px, 200px, 0) rotate(180deg); 93 | background: #34b6da; 94 | } 95 | 96 | /* line 30, ../compass/scss/style.scss */ 97 | 75% { 98 | -webkit-transform: translate3d(200px, 0, 0) rotate(270deg); 99 | background: #88da50; 100 | } 101 | 102 | /* line 36, ../compass/scss/style.scss */ 103 | 100% { 104 | -webkit-transform: translate3d(0, 0, 0) rotate(360deg); 105 | background: #da371e; 106 | } 107 | } 108 | 109 | @-moz-keyframes boxrotate { 110 | /* line 44, ../compass/scss/style.scss */ 111 | 0% { 112 | -moz-transform: translate(0, 0); 113 | background: #da371e; 114 | } 115 | 116 | /* line 50, ../compass/scss/style.scss */ 117 | 25% { 118 | -moz-transform: translate(0px, 200px) rotate(90deg); 119 | background: #da3ab9; 120 | } 121 | 122 | /* line 56, ../compass/scss/style.scss */ 123 | 50% { 124 | -moz-transform: translate(200px, 200px) rotate(180deg); 125 | background: #34b6da; 126 | } 127 | 128 | /* line 62, ../compass/scss/style.scss */ 129 | 75% { 130 | -moz-transform: translate(200px, 0) rotate(270deg); 131 | background: #88da50; 132 | } 133 | 134 | /* line 68, ../compass/scss/style.scss */ 135 | 100% { 136 | -moz-transform: translate(0, 0) rotate(360deg); 137 | background: #da371e; 138 | } 139 | } 140 | 141 | /* line 75, ../compass/scss/style.scss */ 142 | html { 143 | background: #222; 144 | font-family: helvetica, arial, sans-serif; 145 | color: #EEE; 146 | text-shadow: 0 1px 0 rgba(0, 0, 0, 0.2); 147 | } 148 | 149 | /* line 85, ../compass/scss/style.scss */ 150 | em { 151 | font-style: italic; 152 | } 153 | 154 | /* line 90, ../compass/scss/style.scss */ 155 | code { 156 | font-family: courier; 157 | padding: 0.3em; 158 | margin: 0 0.2em; 159 | background: rgba(255, 255, 255, 0.1); 160 | -moz-border-radius: 3px; 161 | -webkit-border-radius: 3px; 162 | -o-border-radius: 3px; 163 | -ms-border-radius: 3px; 164 | -khtml-border-radius: 3px; 165 | border-radius: 3px; 166 | -moz-box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.5); 167 | -webkit-box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.5); 168 | -o-box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.5); 169 | box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.5); 170 | } 171 | 172 | /* line 103, ../compass/scss/style.scss */ 173 | body header { 174 | background: #333; 175 | padding: 10px; 176 | overflow: auto; 177 | } 178 | /* line 109, ../compass/scss/style.scss */ 179 | body header a { 180 | color: #CCC; 181 | text-decoration: none; 182 | } 183 | /* line 115, ../compass/scss/style.scss */ 184 | body header a.name { 185 | font-family: 'Pacifico'; 186 | font-size: 1.1em; 187 | margin-right: 1em; 188 | position: relative; 189 | top: 3px; 190 | } 191 | /* line 124, ../compass/scss/style.scss */ 192 | body header a.writeup { 193 | font-size: 0.8em; 194 | border-bottom: 1px dashed #CCC; 195 | } 196 | /* line 129, ../compass/scss/style.scss */ 197 | body header a.writeup:after { 198 | content: ' \2192'; 199 | } 200 | /* line 135, ../compass/scss/style.scss */ 201 | body header a.twitter { 202 | float: right; 203 | } 204 | /* line 140, ../compass/scss/style.scss */ 205 | body header div.nav { 206 | float: right; 207 | font-size: 0.7em; 208 | padding: 0.75em; 209 | padding-right: 1.5em; 210 | } 211 | /* line 147, ../compass/scss/style.scss */ 212 | body header div.nav a { 213 | margin-left: 1.5em; 214 | } 215 | /* line 151, ../compass/scss/style.scss */ 216 | body header div.nav a:hover { 217 | border-bottom: 1px dashed #CCC; 218 | } 219 | /* line 159, ../compass/scss/style.scss */ 220 | body section#container { 221 | width: 230px; 222 | height: 230px; 223 | background: rgba(255, 255, 255, 0.1); 224 | margin: 100px auto 50px; 225 | padding: 20px; 226 | -moz-box-shadow: 0px 1px 6px rgba(0, 0, 0, 0.5); 227 | -webkit-box-shadow: 0px 1px 6px rgba(0, 0, 0, 0.5); 228 | -o-box-shadow: 0px 1px 6px rgba(0, 0, 0, 0.5); 229 | box-shadow: 0px 1px 6px rgba(0, 0, 0, 0.5); 230 | cursor: pointer; 231 | } 232 | /* line 169, ../compass/scss/style.scss */ 233 | body section#container div#animate { 234 | width: 30px; 235 | height: 30px; 236 | background: #da371e; 237 | -moz-border-radius: 3px; 238 | -webkit-border-radius: 3px; 239 | -o-border-radius: 3px; 240 | -ms-border-radius: 3px; 241 | -khtml-border-radius: 3px; 242 | border-radius: 3px; 243 | /* border: 1px solid rgba(#000, 0.2); 244 | @include background-clip(padding);*/ 245 | } 246 | /* line 179, ../compass/scss/style.scss */ 247 | body section#container div#text { 248 | text-align: center; 249 | font-size: 3em; 250 | margin-top: 1em; 251 | } 252 | /* line 187, ../compass/scss/style.scss */ 253 | body section#blurb { 254 | width: 600px; 255 | margin: 0px auto; 256 | line-height: 1.6em; 257 | } 258 | /* line 193, ../compass/scss/style.scss */ 259 | body section#blurb a { 260 | color: #FFF; 261 | text-decoration: none; 262 | border-bottom: 1px dashed #CCC; 263 | } 264 | /* line 200, ../compass/scss/style.scss */ 265 | body section#blurb h1 { 266 | font-size: 2em; 267 | font-weight: bold; 268 | } 269 | /* line 206, ../compass/scss/style.scss */ 270 | body section#blurb p { 271 | margin: 1.6em 0; 272 | } 273 | /* line 211, ../compass/scss/style.scss */ 274 | body section#blurb h2 { 275 | font-size: 2em; 276 | font-weight: bold; 277 | margin-top: 1.3em; 278 | } 279 | /* line 218, ../compass/scss/style.scss */ 280 | body section#blurb .note { 281 | font-size: 0.8em; 282 | color: #666 !important; 283 | } 284 | /* line 226, ../compass/scss/style.scss */ 285 | body section#blurb section.code h3 { 286 | background: rgba(255, 255, 255, 0.2); 287 | -moz-box-shadow: 0px 1px 6px rgba(0, 0, 0, 0.5); 288 | -webkit-box-shadow: 0px 1px 6px rgba(0, 0, 0, 0.5); 289 | -o-box-shadow: 0px 1px 6px rgba(0, 0, 0, 0.5); 290 | box-shadow: 0px 1px 6px rgba(0, 0, 0, 0.5); 291 | padding: 0.5em 0.8em; 292 | } 293 | /* line 232, ../compass/scss/style.scss */ 294 | body section#blurb section.code h3 span { 295 | font-family: times, georgia, serif; 296 | font-style: italic; 297 | font-size: 1.1em; 298 | } 299 | /* line 240, ../compass/scss/style.scss */ 300 | body section#blurb section.code pre { 301 | -moz-box-shadow: 0px 1px 6px rgba(0, 0, 0, 0.5); 302 | -webkit-box-shadow: 0px 1px 6px rgba(0, 0, 0, 0.5); 303 | -o-box-shadow: 0px 1px 6px rgba(0, 0, 0, 0.5); 304 | box-shadow: 0px 1px 6px rgba(0, 0, 0, 0.5); 305 | -moz-border-radius: 0px 0px 3px 3px; 306 | -webkit-border-radius: 0px 0px 3px 3px; 307 | -o-border-radius: 0px 0px 3px 3px; 308 | -ms-border-radius: 0px 0px 3px 3px; 309 | -khtml-border-radius: 0px 0px 3px 3px; 310 | border-radius: 0px 0px 3px 3px; 311 | padding: 1em; 312 | font-family: courier; 313 | font-size: 0.8em; 314 | background: rgba(255, 255, 255, 0.5); 315 | color: #111; 316 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.4); 317 | margin-bottom: 3em; 318 | } 319 | /* line 255, ../compass/scss/style.scss */ 320 | body section#details { 321 | text-align: center; 322 | padding-bottom: 4em; 323 | } 324 | /* line 260, ../compass/scss/style.scss */ 325 | body section#details a { 326 | margin-left: 1em; 327 | padding: 1em; 328 | background: rgba(255, 255, 255, 0.1); 329 | -moz-border-radius: 3px; 330 | -webkit-border-radius: 3px; 331 | -o-border-radius: 3px; 332 | -ms-border-radius: 3px; 333 | -khtml-border-radius: 3px; 334 | border-radius: 3px; 335 | color: #EEE; 336 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2); 337 | text-decoration: none; 338 | border: 1px solid rgba(255, 255, 255, 0.3); 339 | font-weight: bold; 340 | font-size: 0.8em; 341 | } 342 | /* line 273, ../compass/scss/style.scss */ 343 | body section#details a:first-child { 344 | margin-left: 0; 345 | } 346 | /* line 278, ../compass/scss/style.scss */ 347 | body section#details a:hover { 348 | background: rgba(255, 255, 255, 0.3); 349 | color: #000; 350 | } 351 | /* line 286, ../compass/scss/style.scss */ 352 | body footer { 353 | font-size: 0.7em; 354 | text-align: center; 355 | margin-bottom: 3em; 356 | margin-top: 5em; 357 | } 358 | /* line 293, ../compass/scss/style.scss */ 359 | body footer img { 360 | margin-top: 2em; 361 | } 362 | /* line 298, ../compass/scss/style.scss */ 363 | body footer a { 364 | color: #FFF; 365 | text-decoration: none; 366 | border-bottom: 1px dashed #CCC; 367 | } 368 | 369 | /* line 309, ../compass/scss/style.scss */ 370 | div#carbonads-container div.carbonad { 371 | background: #353535; 372 | margin: 2em auto 3em; 373 | border: 1px solid rgba(255, 255, 255, 0.1); 374 | -moz-border-radius: 3px; 375 | -webkit-border-radius: 3px; 376 | -o-border-radius: 3px; 377 | -ms-border-radius: 3px; 378 | -khtml-border-radius: 3px; 379 | border-radius: 3px; 380 | text-align: left; 381 | } 382 | -------------------------------------------------------------------------------- /demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CSS3 Animation Keyframe Events 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 57 | 58 | 59 |
60 |
61 |
click me
62 |
63 |
64 |

Keyframe Events for CSS3 Animations

65 |

CSS3 Animations are great but the current implementation doesn't trigger Javascript Events for each Keyframe (see here for more information).

66 |

The CSSAnimation object provides the events the browser vendors left out! This allows you to bind event handlers to cssAnimationKeyframe events and perform any additional code that needs to happen at each keyframe.

67 |

Note: This won't work as well on Mobile Webkit until webkitRequestAnimationFrame() is implemented!

68 |
69 | 73 | 74 | -------------------------------------------------------------------------------- /js/cssanimation.jquery.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | /** 3 | * jQuery wrapper function around CSSAnimation.trigger() 4 | * @param {String} animationName The name given to the @-webkit-keyframes animation 5 | * @param {Integer} duration The length of time of the animation (in milliseconds) 6 | * @param {Object} opts An optional set of options used to override the defaults 7 | */ 8 | $.fn.cssanimation = function(animation, duration, opts) { 9 | return this.each(function(index, elem){ 10 | CSSAnimation.trigger(elem, animation, duration, opts); 11 | }); 12 | }; 13 | })();; 14 | 15 | -------------------------------------------------------------------------------- /js/cssanimation.jquery.min.js: -------------------------------------------------------------------------------- 1 | (function(){$.fn.cssanimation=function(a,b,c){return this.each(function(e,d){CSSAnimation.trigger(d,a,b,c)})}})(); 2 | -------------------------------------------------------------------------------- /js/cssanimation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve CSSAnimation v0.2 3 | * Provides 'cssAnimationKeyframe' events for keyframe animations. 4 | * http://www.joelambert.co.uk/cssa 5 | * 6 | * Copyright 2011, Joe Lambert. All rights reserved 7 | * Free to use under the MIT license. 8 | * http://www.opensource.org/licenses/mit-license.php 9 | */ 10 | 11 | var CSSAnimation = { 12 | version: '0.2' 13 | }; 14 | 15 | // Locate a WebKitCSSKeyframesRule 16 | // Modified version of code found @ http://stackoverflow.com/questions/2961544/cssrules-is-empty 17 | CSSAnimation.find = function(a) { 18 | var ss = document.styleSheets; 19 | for (var i = ss.length - 1; i >= 0; i--) { 20 | try { 21 | var s = ss[i], 22 | rs = s.cssRules ? s.cssRules : 23 | s.rules ? s.rules : 24 | []; 25 | 26 | for (var j = rs.length - 1; j >= 0; j--) { 27 | if ((rs[j].type === window.CSSRule.WEBKIT_KEYFRAMES_RULE || rs[j].type === window.CSSRule.MOZ_KEYFRAMES_RULE) && rs[j].name == a){ 28 | return rs[j]; 29 | } 30 | } 31 | } 32 | catch(e) { /* Trying to interrogate a stylesheet from another domain will throw a security error */ } 33 | } 34 | return null; 35 | }; 36 | 37 | // Trigger a CSS3 Animation on a given element 38 | /** 39 | * Trigger a CSS3 Animation on a given element 40 | * @param {DOMElement} elem The DOM Element to apply the animation to 41 | * @param {String} animationName The name given to the @-webkit-keyframes animation 42 | * @param {Integer} duration The length of time of the animation (in milliseconds) 43 | * @param {Object} opts An optional set of options used to override the defaults 44 | */ 45 | 46 | CSSAnimation.trigger = function(elem, animationName, duration, opts) { 47 | var keyframes = {}, loggedKeyframes = {}, animation = null, element = elem, start = 0, cycle = 0, options = { 48 | base: 5, 49 | easing: 'linear', 50 | iterationCount: 1 51 | }, 52 | prefixes = ['Webkit', 'Moz']; 53 | 54 | // Enable option setting 55 | for(var k in opts) 56 | options[k] = opts[k]; 57 | 58 | // Prevent animation triggers if the animation is already playing 59 | if(element.isPlaying) 60 | return; 61 | 62 | // Can we find the animaition called animationName? 63 | animation = CSSAnimation.find(animationName); 64 | 65 | if(!animation) 66 | return false; 67 | 68 | // Work out the timings of keyframes 69 | keyframes = {}; 70 | 71 | for(var i=0; i < animation.cssRules.length; i++) 72 | { 73 | var kf = animation.cssRules[i], 74 | name = kf.keyText, 75 | percentage = 0; 76 | 77 | // Work out the percentage 78 | name == 'from' ? percentage = 0 : 79 | name == 'to' ? percentage = 1 : 80 | percentage = name.replace('%', '') / 100; 81 | 82 | // Store keyframe for easy recall 83 | keyframes[(percentage*100)+'%'] = kf; 84 | } 85 | 86 | // Start the animation 87 | start = new Date().getTime(); 88 | 89 | // Variables used by the runloop 90 | var current = percentage = roundedKey = keyframe = null, 91 | raiseEvent = function(keyText, elapsedTime) { 92 | var event = document.createEvent("Event"); 93 | event.initEvent("cssAnimationKeyframe", true, true); 94 | event.animationName = animationName; 95 | event.keyText = keyText; 96 | event.elapsedTime = elapsedTime; 97 | element.dispatchEvent(event); 98 | }, 99 | 100 | i=0, 101 | found=false, 102 | 103 | applyCSSAnimation = function(anim) { 104 | found = false; 105 | for(i=0; i=0;g--)try{for(var d=i[g],e=d.cssRules?d.cssRules:d.rules?d.rules:[],c=e.length-1;c>=0;c--)if((e[c].type===window.CSSRule.WEBKIT_KEYFRAMES_RULE||e[c].type===window.CSSRule.MOZ_KEYFRAMES_RULE)&&e[c].name==a)return e[c]}catch(l){}return null}; 11 | CSSAnimation.trigger=function(a,i,g,d){var e={},c={},l=null,o=0,q=0,j={base:5,easing:"linear",iterationCount:1},k=["Webkit","Moz"],h;for(h in d)j[h]=d[h];if(!a.isPlaying){l=CSSAnimation.find(i);if(!l)return false;e={};for(var b=0;b= 0; i--) { 20 | try { 21 | var s = ss[i], 22 | rs = s.cssRules ? s.cssRules : 23 | s.rules ? s.rules : 24 | []; 25 | 26 | for (var j = rs.length - 1; j >= 0; j--) { 27 | if ((rs[j].type === window.CSSRule.WEBKIT_KEYFRAMES_RULE || rs[j].type === window.CSSRule.MOZ_KEYFRAMES_RULE) && rs[j].name == a){ 28 | return rs[j]; 29 | } 30 | } 31 | } 32 | catch(e) { /* Trying to interrogate a stylesheet from another domain will throw a security error */ } 33 | } 34 | return null; 35 | }; 36 | 37 | // Trigger a CSS3 Animation on a given element 38 | /** 39 | * Trigger a CSS3 Animation on a given element 40 | * @param {DOMElement} elem The DOM Element to apply the animation to 41 | * @param {String} animationName The name given to the @-webkit-keyframes animation 42 | * @param {Integer} duration The length of time of the animation (in milliseconds) 43 | * @param {Object} opts An optional set of options used to override the defaults 44 | */ 45 | 46 | CSSAnimation.trigger = function(elem, animationName, duration, opts) { 47 | var keyframes = {}, loggedKeyframes = {}, animation = null, element = elem, start = 0, cycle = 0, options = { 48 | base: 5, 49 | easing: 'linear', 50 | iterationCount: 1 51 | }, 52 | prefixes = ['Webkit', 'Moz']; 53 | 54 | // Enable option setting 55 | for(var k in opts) 56 | options[k] = opts[k]; 57 | 58 | // Prevent animation triggers if the animation is already playing 59 | if(element.isPlaying) 60 | return; 61 | 62 | // Can we find the animaition called animationName? 63 | animation = CSSAnimation.find(animationName); 64 | 65 | if(!animation) 66 | return false; 67 | 68 | // Work out the timings of keyframes 69 | keyframes = {}; 70 | 71 | for(var i=0; i < animation.cssRules.length; i++) 72 | { 73 | var kf = animation.cssRules[i], 74 | name = kf.keyText, 75 | percentage = 0; 76 | 77 | // Work out the percentage 78 | name == 'from' ? percentage = 0 : 79 | name == 'to' ? percentage = 1 : 80 | percentage = name.replace('%', '') / 100; 81 | 82 | // Store keyframe for easy recall 83 | keyframes[(percentage*100)+'%'] = kf; 84 | } 85 | 86 | // Start the animation 87 | start = new Date().getTime(); 88 | 89 | // Variables used by the runloop 90 | var current = percentage = roundedKey = keyframe = null, 91 | raiseEvent = function(keyText, elapsedTime) { 92 | var event = document.createEvent("Event"); 93 | event.initEvent("cssAnimationKeyframe", true, true); 94 | event.animationName = animationName; 95 | event.keyText = keyText; 96 | event.elapsedTime = elapsedTime; 97 | element.dispatchEvent(event); 98 | }, 99 | 100 | i=0, 101 | found=false, 102 | 103 | applyCSSAnimation = function(anim) { 104 | found = false; 105 | for(i=0; i