├── LICENSE ├── README.md ├── cssanimevent.js ├── cssanimevent.min.js └── example ├── index.css ├── index.html ├── index.js └── index.scss /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2013 Peter Mescalchin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CSS animation event 2 | 3 | A very small (approx **800 bytes** minified and gzipped) cross browser compatible library to handle CSS3 animation and transition DOM events with a fall back pattern for unsupported browsers. 4 | 5 | Tested successfully with CSS animation/transition supported browsers of: 6 | 7 | - Google Chrome 8 | - Mozilla Firefox 9 | - Opera (12.10+) 10 | - IE10+ 11 | 12 | Library supports Internet Explorer IE9 and above, a final version with IE8 support is [tagged here](https://github.com/magnetikonline/css-animation-event/tree/ie8-final). 13 | 14 | - [Why?](#why) 15 | - [Usage](#usage) 16 | - [Example](#example) 17 | - [Methods](#methods) 18 | - [onAnimationEnd(element,handler[,data])](#onanimationendelementhandlerdata) 19 | - [cancelAnimationEnd(element)](#cancelanimationendelement) 20 | - [onTransitionEnd(element,handler[,data])](#ontransitionendelementhandlerdata) 21 | - [cancelTransitionEnd(element)](#canceltransitionendelement) 22 | - [animationSupport()](#animationsupport) 23 | - [transitionSupport()](#transitionsupport) 24 | - [addAnimation{Start|Iteration|End}(element,handler)](#addanimationstartiterationendelementhandler) 25 | - [removeAnimation{Start|Iteration|End}(element,handler)](#removeanimationstartiterationendelementhandler) 26 | - [addTransitionEnd(element,handler)](#addtransitionendelementhandler) 27 | - [removeTransitionEnd(element,handler)](#removetransitionendelementhandler) 28 | 29 | ## Why? 30 | 31 | The CSS3 [animation](https://www.w3.org/TR/css3-animations/) and [transition](https://www.w3.org/TR/css3-transitions/) modules both provide useful DOM events which can be used to track the current state of an animation or transition - extremely useful for chaining future application logic as they progress and complete. 32 | 33 | Whilst support for these events is (thankfully) provided in virtually every browser that offers CSS [animations](https://caniuse.com/#feat=css-animation) and [transitions](https://caniuse.com/#feat=css-transitions), as a front-end developer you are still left with the issue of coding alternative program flows where support isn't available and therefore won't fire your animation/transition event handlers. 34 | 35 | Consider the following example: 36 | 37 | ```css 38 | #movethis { 39 | /* leaving out browser prefixes for brevity */ 40 | height: 100px; 41 | transition: height 1.5s ease-in; 42 | } 43 | 44 | #movethis.nowmove { 45 | height: 300px; 46 | } 47 | ``` 48 | 49 | ```js 50 | // determineCSSTransitionSupport() would determine if browser supports CSS transitions 51 | var supportCSSTransitions = determineCSSTransitionSupport(); 52 | 53 | // move our element 54 | var moveThisEl = document.getElementById('movethis'); 55 | moveThisEl.className += ' nowmove'; 56 | 57 | // code around no transition support 58 | if (supportCSSTransitions) { 59 | moveThisEl.addEventListener('transitionend',nextUIStep,false); 60 | } else { 61 | nextUIStep(); 62 | } 63 | 64 | function nextUIStep() { 65 | // the transition ended, keep going 66 | } 67 | ``` 68 | 69 | Having to continually make the decision to utilise DOM animation/transition events vs. a graceful fallback (via `supportCSSTransitions` in example above) throughout your UI code soon becomes clumsy and error prone. 70 | 71 | ## Usage 72 | 73 | [CSSAnimEvent](cssanimevent.js) manages the above situation in a different way, relying on the fact that CSS transitions by design fall back gracefully with unsupported browsers handling element CSS property changes as instant, with a zero transition time. 74 | 75 | Methods `onAnimationEnd(element,handler)` and `onTransitionEnd(element,handler)` simply mimic this behaviour by instantaneously calling the given `handler` for browsers that don't provide animation and/or transition support. 76 | 77 | Rewriting the above JavaScript example we can now do: 78 | 79 | ```js 80 | // move our element 81 | var moveThisEl = document.getElementById('movethis'); 82 | moveThisEl.className += ' nowmove'; 83 | 84 | // browsers supporting CSS transitions will call nextUIStep() after the transition ends 85 | // ...otherwise it will be called as window.setTimeout(nextUIStep) 86 | CSSAnimEvent.onTransitionEnd(moveThisEl,nextUIStep); 87 | 88 | function nextUIStep() { 89 | // the transition ended, keep going 90 | } 91 | ``` 92 | 93 | One caveat to be aware of, both `onAnimationEnd()` and `onTransitionEnd()` create 'one shot' event handlers and should be called *just after* CSS updates have been applied to the element, allowing instant delegation back to the given callback handler for unsupported browsers. 94 | 95 | Internally `CSSAnimEvent` attaches singular `animationend` and `transitionend` event handlers to the `` element and delegates to given callbacks as required. 96 | 97 | Using CSS `animation/@keyframes` is *slightly* more work since animated elements will never reach their intended keyframe target within unsupported browsers, but a little CSS/JavaScript can handle this situation: 98 | 99 | ```css 100 | @keyframes myanimation { 101 | /* prior animation steps in here */ 102 | 103 | 100% { 104 | background: #f00; 105 | height: 300px; 106 | } 107 | } 108 | 109 | #movethis { 110 | height: 100px; 111 | } 112 | 113 | #movethis.nowmove { 114 | animation: myanimation 1s linear 1 forwards; 115 | } 116 | 117 | #movethis.finish { 118 | /* faux the target of myanimation for older browsers */ 119 | background: #f00; 120 | height: 300px; 121 | } 122 | ``` 123 | 124 | ```js 125 | // move our element 126 | var moveThisEl = document.getElementById('movethis'); 127 | moveThisEl.className += ' nowmove'; 128 | 129 | CSSAnimEvent.onAnimationEnd(moveThisEl,nextUIStep); 130 | 131 | function nextUIStep(el) { 132 | // faux the animation target, by adding 'finish' class to element 133 | el.className += ' finish'; 134 | 135 | // keep going 136 | } 137 | ``` 138 | 139 | ## Example 140 | 141 | View a very [basic example of this in action](https://magnetikonline.github.io/css-animation-event/) using animation and transition chaining. 142 | 143 | For capable browsers the tweens will run as expected - alternatively the DOM elements will update instantly from start to finish, via the same event handler execution flow. 144 | 145 | As a small bonus, `CSSAnimEvent` also adds a handy CSS styling hook; `cssanimactive`, which can be used for specific styling which is applied *only* during the animation/transition period. 146 | 147 | ```html 148 | 149 |
150 | 151 | 152 |
153 | 154 | 155 |
156 | ``` 157 | 158 | The [example](https://magnetikonline.github.io/css-animation-event/) uses this CSS styling hook to provide a red border to each box during it's animation/transition period. 159 | 160 | ## Methods 161 | 162 | All methods are under a `window.CSSAnimEvent` namespace. 163 | 164 | ### `onAnimationEnd(element,handler[,data])` 165 | 166 | - Adds a 'one shot' event handler to the given DOM `element`, with `handler` executing either upon `animationend` or instantaneously if CSS animation support not detected. 167 | - The `handler` will be passed `element` that fired the event and an optional `data` payload as a second parameter. 168 | - The given DOM element will be decorated with a CSS class `cssanimactive`, removed upon animation completion which can be used as a CSS styling hook. 169 | 170 | ### `cancelAnimationEnd(element)` 171 | 172 | Cancel a 'one shot' event handler set by `onAnimationEnd()` on the given DOM `element`. 173 | 174 | ### `onTransitionEnd(element,handler[,data])` 175 | 176 | - Adds a 'one shot' event handler to the given DOM `element`, with `handler` executing either upon `transitionend` or instantaneously if CSS transition support not detected. 177 | - The `handler` will be passed `element` that fired the event and an optional `data` payload as a second parameter. 178 | - The given DOM element will be decorated with a CSS class `cssanimactive`, removed upon transition completion which can be used as a CSS styling hook. 179 | 180 | ### `cancelTransitionEnd(element)` 181 | 182 | Cancel a 'one shot' event handler set by `onTransitionEnd()` on the given DOM `element`. 183 | 184 | ### `animationSupport()` 185 | 186 | Returns `true` if CSS animation support is detected. 187 | 188 | ### `transitionSupport()` 189 | 190 | Returns `true` if CSS transition support is detected. 191 | 192 | ### `addAnimation{Start|Iteration|End}(element,handler)` 193 | 194 | - Add `animation{start|iteration|end}` native event handlers to DOM elements. 195 | - Provides a handy cross browser wrapper having done browser detection for you. Does **not** provide faux event firing for non-supported browsers. 196 | - Returns `true` where supported, `false` otherwise. 197 | 198 | ### `removeAnimation{Start|Iteration|End}(element,handler)` 199 | 200 | - Remove `animation{start|iteration|end}` native event handlers for above. 201 | - Returns `true` where supported, otherwise `false`. 202 | 203 | ### `addTransitionEnd(element,handler)` 204 | 205 | - Add `transitionend` native event handlers to DOM elements. 206 | - Provides a handy cross browser wrapper having done browser detection for you. Does **not** provide faux event firing for non-supported browsers. 207 | - Returns `true` on success/support, `false` otherwise. 208 | 209 | ### `removeTransitionEnd(element,handler)` 210 | 211 | - Remove `transitionend` native event handlers for above. 212 | - Returns `true` where supported, otherwise `false`. 213 | -------------------------------------------------------------------------------- /cssanimevent.js: -------------------------------------------------------------------------------- 1 | (function(win,documentElement,undefined) { 2 | 3 | 'use strict'; 4 | 5 | var ELEMENT_HANDLER_ID_ATTRIBUTE = 'picohCSSAnimID', 6 | CLASS_NAME_ANIM_ACTIVE = 'cssanimactive', 7 | HANDLER_TYPE_INDEX_ANIMATION = 0, 8 | HANDLER_TYPE_INDEX_TRANSITION = 1, 9 | HANDLER_ID_LENGTH = 3, 10 | HANDLER_ID_START_CHAR = 97, // character 'a' 11 | HANDLER_ID_END_CHAR = 122, // character 'z' 12 | isDetected, 13 | animationEventTypeStart, 14 | animationEventTypeIteration, 15 | animationEventTypeEnd, 16 | transitionEventTypeEnd, 17 | handlerCollection = [undefined,undefined]; 18 | 19 | function detectCapabilities() { 20 | 21 | // if already detected support then exit 22 | if (isDetected) { 23 | return; 24 | } 25 | 26 | isDetected = true; 27 | 28 | // list of animation/transition style properties per browser engine and matching event names 29 | // note: non-prefixed properties are intentionally checked first 30 | var ANIMATION_DETECT_COLLECTION = { 31 | animation: ['animationstart','animationiteration','animationend'], 32 | webkitAnimation: ['webkitAnimationStart','webkitAnimationIteration','webkitAnimationEnd'] 33 | }, 34 | TRANSITION_DETECT_COLLECTION = { 35 | transition: 'transitionend', 36 | webkitTransition: 'webkitTransitionEnd' 37 | }; 38 | 39 | function detectEventType(detectCollection) { 40 | 41 | var styleNameList = Object.keys(detectCollection); 42 | 43 | while (styleNameList.length) { 44 | var styleNameItem = styleNameList.shift() 45 | if (documentElement.style[styleNameItem] !== undefined) { 46 | // found capability 47 | return detectCollection[styleNameItem]; 48 | } 49 | } 50 | 51 | // no match 52 | } 53 | 54 | // determine if animation and transition support available 55 | animationEventTypeEnd = detectEventType(ANIMATION_DETECT_COLLECTION); 56 | if (animationEventTypeEnd) { 57 | // animation support detected - split out event types from collection 58 | animationEventTypeStart = animationEventTypeEnd[0]; 59 | animationEventTypeIteration = animationEventTypeEnd[1]; 60 | animationEventTypeEnd = animationEventTypeEnd[2]; 61 | } 62 | 63 | transitionEventTypeEnd = detectEventType(TRANSITION_DETECT_COLLECTION); 64 | } 65 | 66 | function eventAdd(obj,type,handler) { 67 | 68 | obj.addEventListener(type,handler,false); 69 | return true; 70 | } 71 | 72 | function eventRemove(obj,type,handler) { 73 | 74 | obj.removeEventListener(type,handler,false); 75 | return true; 76 | } 77 | 78 | function getElHandlerCollectionID(handlerTypeIndex,el) { 79 | 80 | // look for ID as a custom property of the DOM element 81 | var handlerID = el[ELEMENT_HANDLER_ID_ATTRIBUTE]; 82 | 83 | return ( 84 | (handlerID !== undefined) && 85 | (handlerCollection[handlerTypeIndex][handlerID] !== undefined) 86 | ) 87 | // found handler ID in collection 88 | ? handlerID 89 | // not found 90 | : false; 91 | } 92 | 93 | function removeElHandlerItem(handlerTypeIndex,el,handlerID) { 94 | 95 | // if handlerID already given, no need to find again for element 96 | handlerID = handlerID || getElHandlerCollectionID(handlerTypeIndex,el); 97 | 98 | if (handlerID !== false) { 99 | // found element in collection, now remove 100 | delete handlerCollection[handlerTypeIndex][handlerID]; 101 | delete el[ELEMENT_HANDLER_ID_ATTRIBUTE]; 102 | 103 | el.className = ( 104 | (' ' + el.className + ' '). 105 | replace(' ' + CLASS_NAME_ANIM_ACTIVE + ' ',' ') 106 | ).trim(); 107 | } 108 | } 109 | 110 | function onEndProcess(eventTypeEnd,handlerTypeIndex,el,handler,data) { 111 | 112 | if (!eventTypeEnd) { 113 | // no animation/transition support - call handler right away 114 | return setTimeout(function() { handler(el,data); }); 115 | } 116 | 117 | if (!handlerCollection[handlerTypeIndex]) { 118 | // setup end handler 119 | handlerCollection[handlerTypeIndex] = {}; 120 | eventAdd(documentElement,eventTypeEnd,function(event) { 121 | 122 | // ensure event returned a target element 123 | if (event.target) { 124 | // get the element handler list ID - skip over if not found 125 | var targetEl = event.target, 126 | handlerID = getElHandlerCollectionID(handlerTypeIndex,targetEl); 127 | 128 | if (handlerID !== false) { 129 | // execute handler then remove from handler list 130 | var handlerItem = handlerCollection[handlerTypeIndex][handlerID]; 131 | removeElHandlerItem(handlerTypeIndex,targetEl,handlerID); 132 | handlerItem[0](targetEl,handlerItem[1]); 133 | } 134 | } 135 | }); 136 | } 137 | 138 | // remove possible existing end handler associated to element 139 | removeElHandlerItem(handlerTypeIndex,el); 140 | 141 | // generate new, unique handler ID 142 | var handlerID; 143 | while (!handlerID || handlerCollection[handlerTypeIndex][handlerID]) { 144 | handlerID = ''; 145 | while (handlerID.length < HANDLER_ID_LENGTH) { 146 | // append characters between [a-z] to a total of HANDLER_ID_LENGTH 147 | handlerID += String.fromCharCode( 148 | Math.floor(Math.random() * (HANDLER_ID_END_CHAR - HANDLER_ID_START_CHAR)) + 149 | HANDLER_ID_START_CHAR 150 | ); 151 | } 152 | } 153 | 154 | // add element to handler list and a 'animation active' class identifier to the target element 155 | el[ELEMENT_HANDLER_ID_ATTRIBUTE] = handlerID; 156 | handlerCollection[handlerTypeIndex][handlerID] = [handler,data]; 157 | el.className = el.className.trim() + ' ' + CLASS_NAME_ANIM_ACTIVE; 158 | } 159 | 160 | win.CSSAnimEvent = { 161 | animationSupport: function() { 162 | 163 | detectCapabilities(); 164 | return !!animationEventTypeEnd; 165 | }, 166 | 167 | transitionSupport: function() { 168 | 169 | detectCapabilities(); 170 | return !!transitionEventTypeEnd; 171 | }, 172 | 173 | addAnimationStart: function(el,handler) { 174 | 175 | detectCapabilities(); 176 | 177 | return (animationEventTypeStart) 178 | ? eventAdd(el,animationEventTypeStart,handler) 179 | : false; 180 | }, 181 | 182 | removeAnimationStart: function(el,handler) { 183 | 184 | detectCapabilities(); 185 | 186 | return (animationEventTypeStart) 187 | ? eventRemove(el,animationEventTypeStart,handler) 188 | : false; 189 | }, 190 | 191 | addAnimationIteration: function(el,handler) { 192 | 193 | detectCapabilities(); 194 | 195 | return (animationEventTypeIteration) 196 | ? eventAdd(el,animationEventTypeIteration,handler) 197 | : false; 198 | }, 199 | 200 | removeAnimationIteration: function(el,handler) { 201 | 202 | detectCapabilities(); 203 | 204 | return (animationEventTypeIteration) 205 | ? eventRemove(el,animationEventTypeIteration,handler) 206 | : false; 207 | }, 208 | 209 | addAnimationEnd: function(el,handler) { 210 | 211 | detectCapabilities(); 212 | 213 | return (animationEventTypeEnd) 214 | ? eventAdd(el,animationEventTypeEnd,handler) 215 | : false; 216 | }, 217 | 218 | removeAnimationEnd: function(el,handler) { 219 | 220 | detectCapabilities(); 221 | 222 | return (animationEventTypeEnd) 223 | ? eventRemove(el,animationEventTypeEnd,handler) 224 | : false; 225 | }, 226 | 227 | addTransitionEnd: function(el,handler) { 228 | 229 | detectCapabilities(); 230 | 231 | return (transitionEventTypeEnd) 232 | ? eventAdd(el,transitionEventTypeEnd,handler) 233 | : false; 234 | }, 235 | 236 | removeTransitionEnd: function(el,handler) { 237 | 238 | detectCapabilities(); 239 | 240 | return (transitionEventTypeEnd) 241 | ? eventRemove(el,transitionEventTypeEnd,handler) 242 | : false; 243 | }, 244 | 245 | onAnimationEnd: function(el,handler,data) { 246 | 247 | detectCapabilities(); 248 | 249 | onEndProcess( 250 | animationEventTypeEnd, 251 | HANDLER_TYPE_INDEX_ANIMATION, 252 | el,handler,data 253 | ); 254 | }, 255 | 256 | cancelAnimationEnd: function(el) { 257 | 258 | removeElHandlerItem(HANDLER_TYPE_INDEX_ANIMATION,el); 259 | }, 260 | 261 | onTransitionEnd: function(el,handler,data) { 262 | 263 | detectCapabilities(); 264 | 265 | onEndProcess( 266 | transitionEventTypeEnd, 267 | HANDLER_TYPE_INDEX_TRANSITION, 268 | el,handler,data 269 | ); 270 | }, 271 | 272 | cancelTransitionEnd: function(el) { 273 | 274 | removeElHandlerItem(HANDLER_TYPE_INDEX_TRANSITION,el); 275 | } 276 | }; 277 | })(window,document.documentElement); 278 | -------------------------------------------------------------------------------- /cssanimevent.min.js: -------------------------------------------------------------------------------- 1 | (function(v,q,h){function d(){function a(b){for(var a=Object.keys(b);a.length;){var d=a.shift();if(q.style[d]!==h)return b[d]}}if(!r){r=!0;if(f=a({animation:["animationstart","animationiteration","animationend"],webkitAnimation:["webkitAnimationStart","webkitAnimationIteration","webkitAnimationEnd"]}))k=f[0],l=f[1],f=f[2];e=a({transition:"transitionend",webkitTransition:"webkitTransitionEnd"})}}function m(a,b,c){a.addEventListener(b,c,!1);return!0}function n(a,b,c){a.removeEventListener(b,c,!1);return!0} 2 | function t(a,b){var c=b.picohCSSAnimID;return c!==h&&g[a][c]!==h?c:!1}function p(a,b,c){c=c||t(a,b);!1!==c&&(delete g[a][c],delete b.picohCSSAnimID,b.className=(" "+b.className+" ").replace(" cssanimactive "," ").trim())}function u(a,b,c,d,f){if(!a)return setTimeout(function(){d(c,f)});g[b]||(g[b]={},m(q,a,function(a){if(a.target){a=a.target;var c=t(b,a);if(!1!==c){var d=g[b][c];p(b,a,c);d[0](a,d[1])}}}));p(b,c);for(var e;!e||g[b][e];)for(e="";3>e.length;)e+=String.fromCharCode(Math.floor(25*Math.random())+ 3 | 97);c.picohCSSAnimID=e;g[b][e]=[d,f];c.className=c.className.trim()+" cssanimactive"}var r,k,l,f,e,g=[h,h];v.CSSAnimEvent={animationSupport:function(){d();return!!f},transitionSupport:function(){d();return!!e},addAnimationStart:function(a,b){d();return k?m(a,k,b):!1},removeAnimationStart:function(a,b){d();return k?n(a,k,b):!1},addAnimationIteration:function(a,b){d();return l?m(a,l,b):!1},removeAnimationIteration:function(a,b){d();return l?n(a,l,b):!1},addAnimationEnd:function(a,b){d();return f?m(a, 4 | f,b):!1},removeAnimationEnd:function(a,b){d();return f?n(a,f,b):!1},addTransitionEnd:function(a,b){d();return e?m(a,e,b):!1},removeTransitionEnd:function(a,b){d();return e?n(a,e,b):!1},onAnimationEnd:function(a,b,c){d();u(f,0,a,b,c)},cancelAnimationEnd:function(a){p(0,a)},onTransitionEnd:function(a,b,c){d();u(e,1,a,b,c)},cancelTransitionEnd:function(a){p(1,a)}}})(window,document.documentElement); 5 | -------------------------------------------------------------------------------- /example/index.css: -------------------------------------------------------------------------------- 1 | /* line 24, example.scss */ 2 | input { 3 | font-size: 100%; 4 | margin: 0; } 5 | 6 | /* line 29, example.scss */ 7 | body, 8 | input { 9 | color: #000; 10 | font-family: sans-serif; } 11 | 12 | /* line 35, example.scss */ 13 | body { 14 | background: #fff; 15 | font-size: 62.5%; 16 | line-height: 1; 17 | margin-left: 20px; 18 | margin-right: 20px; } 19 | 20 | /* line 43, example.scss */ 21 | h1 { 22 | font-size: 3em; 23 | margin: 0 0 20px; 24 | white-space: nowrap; } 25 | 26 | /* line 49, example.scss */ 27 | p { 28 | margin: 0 0 15px; } 29 | 30 | /* line 53, example.scss */ 31 | ul { 32 | margin: 0 0 15px 15px; 33 | padding: 0; } 34 | 35 | /* line 58, example.scss */ 36 | p, 37 | ul > li { 38 | font-size: 1.3em; 39 | line-height: 1.4; } 40 | /* line 63, example.scss */ 41 | p > code, 42 | ul > li > code { 43 | font-family: monospace; 44 | font-size: 100%; } 45 | 46 | /* line 69, example.scss */ 47 | a { 48 | color: #555; } 49 | 50 | /* line 74, example.scss */ 51 | .boxanimation, 52 | .boxtransition1, 53 | .boxtransition2 { 54 | border: 1px solid #000; 55 | height: 100px; 56 | left: 20px; 57 | position: absolute; 58 | top: 260px; 59 | width: 100px; } 60 | /* line 85, example.scss */ 61 | .boxanimation.cssanimactive, 62 | .boxtransition1.cssanimactive, 63 | .boxtransition2.cssanimactive { 64 | border: 2px solid #f00; } 65 | 66 | /* line 91, example.scss */ 67 | .boxanimation { 68 | background: #b2382d; } 69 | /* line 94, example.scss */ 70 | .boxanimation.move { 71 | -webkit-animation: boxanimation 1s ease-in-out 0s 3 alternate forwards; 72 | animation: boxanimation 1s ease-in-out 0s 3 alternate forwards; } 73 | /* line 99, example.scss */ 74 | .boxanimation.complete { 75 | background: #e89041; 76 | top: 500px; } 77 | @-webkit-keyframes boxanimation { 78 | 20% { 79 | left: 40px; 80 | top: 310px; } 81 | 40% { 82 | left: 140px; 83 | top: 400px; } 84 | 60% { 85 | background: #ffc787; } 86 | 100% { 87 | background: #e89041; 88 | top: 500px; } } 89 | @keyframes boxanimation { 90 | 20% { 91 | left: 40px; 92 | top: 310px; } 93 | 40% { 94 | left: 140px; 95 | top: 400px; } 96 | 60% { 97 | background: #ffc787; } 98 | 100% { 99 | background: #e89041; 100 | top: 500px; } } 101 | /* line 129, example.scss */ 102 | .boxtransition1, 103 | .boxtransition2 { 104 | background: #f2df7e; 105 | left: 160px; 106 | -webkit-transition: background 0.5s linear, top 0.5s ease-in; 107 | transition: background 0.5s linear, top 0.5s ease-in; } 108 | /* line 135, example.scss */ 109 | .boxtransition1.move, 110 | .boxtransition2.move { 111 | background: #314a59; 112 | top: 500px; } 113 | 114 | /* line 141, example.scss */ 115 | .boxtransition2 { 116 | left: 300px; } 117 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | CSS animation event example 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 |
22 | 23 |

CSS animation event example

24 | 25 |
    26 |
  • Upon start first box will start animating, followed by transitions of box two and three.
  • 27 |
  • Box animations and transitions chained using CSSAnimEvent methods of onAnimationEnd() and onTransitionEnd().
  • 28 |
  • For browsers not supporting CSS3 animations or transitions, faux end callbacks will be fired by cssanimevent.js.
  • 29 |
  • Open browser developer console for further debug output.
  • 30 |
31 | 32 |

33 | https://github.com/magnetikonline/css-animation-event 34 |

35 | 36 |

37 | 38 |

39 | 40 |
41 | 42 |
43 | 44 |
45 | 46 |
47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | (function(doc) { 2 | 3 | 'use strict'; 4 | 5 | // add console.group(End)() sinks where not supported by browser 6 | if (!console.group) { 7 | console.group = console.groupEnd = function() {}; 8 | } 9 | 10 | function $(id) { 11 | 12 | return doc.getElementById(id); 13 | } 14 | 15 | function startAnimation() { 16 | 17 | $('boxanim').className += ' move'; 18 | 19 | // onAnimationEnd() hook 20 | CSSAnimEvent.onAnimationEnd( 21 | $('boxanim'), 22 | function(el,data) { 23 | 24 | console.group('Animation finished'); 25 | console.log(el); 26 | console.dir(data); 27 | console.groupEnd(); 28 | 29 | // set final element position for browsers that don't support animations 30 | $('boxanim').className += ' complete'; 31 | 32 | // start first box transition 33 | startTransition1(); 34 | }, 35 | { random: 'animData' } 36 | ); 37 | 38 | //CSSAnimEvent.cancelAnimationEnd($('boxanim')); 39 | } 40 | 41 | function startTransition1() { 42 | 43 | $('boxtran1').className += ' move'; 44 | 45 | // onTransitionEnd() hook #1 46 | CSSAnimEvent.onTransitionEnd( 47 | $('boxtran1'), 48 | function(el,data) { 49 | 50 | console.group('Transition #1 finished'); 51 | console.log(el); 52 | console.dir(data); 53 | console.groupEnd(); 54 | 55 | // start second box transition 56 | startTransition2(); 57 | }, 58 | { random: 'transData1' } 59 | ); 60 | 61 | //CSSAnimEvent.cancelTransitionEnd($('boxtran1')); 62 | } 63 | 64 | function startTransition2() { 65 | 66 | $('boxtran2').className += ' move'; 67 | 68 | // onTransitionEnd() hook #2 69 | CSSAnimEvent.onTransitionEnd( 70 | $('boxtran2'), 71 | function(el,data) { 72 | 73 | console.group('Transition #2 finished'); 74 | console.log(el); 75 | console.dir(data); 76 | console.groupEnd(); 77 | 78 | console.log('Finished!'); 79 | }, 80 | { random: 'transData2' } 81 | ); 82 | 83 | //CSSAnimEvent.cancelTransitionEnd($('boxtran2')); 84 | } 85 | 86 | doc.addEventListener('DOMContentLoaded',function() { 87 | 88 | // report status of browser animation/transition support 89 | console.log('DOMContentLoaded fired'); 90 | console.log('animationSupport:',CSSAnimEvent.animationSupport()); 91 | console.log('transitionSupport:',CSSAnimEvent.transitionSupport()); 92 | 93 | // add event handlers for animation start/iteration/end and transition end 94 | var boxAnimEl = $('boxanim'); 95 | 96 | console.log( 97 | 'animationstart supported?', 98 | CSSAnimEvent.addAnimationStart( 99 | boxAnimEl, 100 | function() { console.log('animationstart fired'); } 101 | ) 102 | ); 103 | 104 | console.log( 105 | 'animationiteration supported?', 106 | CSSAnimEvent.addAnimationIteration( 107 | boxAnimEl, 108 | function() { console.log('animationiteration fired'); } 109 | ) 110 | ); 111 | 112 | console.log( 113 | 'animationend supported?', 114 | CSSAnimEvent.addAnimationEnd( 115 | boxAnimEl, 116 | function() { console.log('animationend fired'); } 117 | ) 118 | ); 119 | 120 | console.log( 121 | 'transitionend supported?', 122 | CSSAnimEvent.addTransitionEnd( 123 | $('boxtran1'), 124 | function() { console.log('transitionend fired'); } 125 | ) 126 | ); 127 | 128 | // wire up start button to chained animation demo 129 | $('startexample').addEventListener('click',startAnimation); 130 | }); 131 | })(document); 132 | -------------------------------------------------------------------------------- /example/index.scss: -------------------------------------------------------------------------------- 1 | // -- variables -- 2 | $boxLeft: 20px; 3 | $boxGap: 140px; 4 | $boxEndTop: 500px; 5 | $boxEndBackground: #e89041; 6 | 7 | // -- mixins for CSS animation/transition -- 8 | @mixin animation($value) { 9 | -webkit-animation: $value; 10 | animation: $value; 11 | } 12 | 13 | @mixin keyframes($name) { 14 | @-webkit-keyframes #{$name} { @content; } 15 | @keyframes #{$name} { @content; } 16 | } 17 | 18 | @mixin transition($value...) { 19 | -webkit-transition: $value; 20 | transition: $value; 21 | } 22 | 23 | // -- base -- 24 | input { 25 | font-size: 100%; 26 | margin: 0; 27 | } 28 | 29 | body, 30 | input { 31 | color: #000; 32 | font-family: sans-serif; 33 | } 34 | 35 | body { 36 | background: #fff; 37 | font-size: 62.5%; 38 | line-height: 1; 39 | margin-left: $boxLeft; 40 | margin-right: $boxLeft; 41 | } 42 | 43 | h1 { 44 | font-size: 3em; 45 | margin: 0 0 20px; 46 | white-space: nowrap; 47 | } 48 | 49 | p { 50 | margin: 0 0 15px; 51 | } 52 | 53 | ul { 54 | margin: 0 0 15px 15px; 55 | padding: 0; 56 | } 57 | 58 | p, 59 | ul > li { 60 | font-size: 1.3em; 61 | line-height: 1.4; 62 | 63 | > code { 64 | font-family: monospace; 65 | font-size: 100%; 66 | } 67 | } 68 | 69 | a { 70 | color: #555; 71 | } 72 | 73 | // -- boxes -- 74 | .boxanimation, 75 | .boxtransition1, 76 | .boxtransition2 { 77 | border: 1px solid #000; 78 | height: 100px; 79 | left: $boxLeft; 80 | position: absolute; 81 | top: 260px; 82 | width: 100px; 83 | 84 | // add a red box to each box as it's animating 85 | &.cssanimactive { 86 | border: 2px solid #f00; 87 | } 88 | } 89 | 90 | // -- boxes - animation -- 91 | .boxanimation { 92 | background: #b2382d; 93 | 94 | &.move { 95 | @include animation(boxanimation 1s ease-in-out 0s 3 alternate forwards); 96 | } 97 | 98 | // for browsers that don't support animation, place box at intended end position 99 | &.complete { 100 | background: $boxEndBackground; 101 | top: $boxEndTop; 102 | } 103 | 104 | @at-root { 105 | @include keyframes(boxanimation) { 106 | 20% { 107 | left: 40px; 108 | top: 310px; 109 | } 110 | 111 | 40% { 112 | left: 140px; 113 | top: 400px; 114 | } 115 | 116 | 60% { 117 | background: #ffc787; 118 | } 119 | 120 | 100% { 121 | background: $boxEndBackground; 122 | top: $boxEndTop; 123 | } 124 | } 125 | } 126 | } 127 | 128 | // -- boxes - transition -- 129 | .boxtransition1, 130 | .boxtransition2 { 131 | background: #f2df7e; 132 | left: ($boxLeft + $boxGap); 133 | @include transition(background 0.5s linear,top 0.5s ease-in); 134 | 135 | &.move { 136 | background: #314a59; 137 | top: $boxEndTop; 138 | } 139 | } 140 | 141 | .boxtransition2 { 142 | left: ($boxLeft + ($boxGap * 2)); 143 | } 144 | --------------------------------------------------------------------------------