├── CHANGELOG.md ├── MIT-LICENSE ├── README.md ├── build ├── build ├── build.xml └── google-compiler-20100917.jar ├── css └── demo.css ├── demo.html └── js ├── modernizr.js ├── morf.js ├── morf.min.js ├── morf.noshifty.js ├── morf.noshifty.min.js ├── shifty.min.js └── src ├── WebkitCSSMatrix.ext.js ├── morf.js ├── morf.utils.js └── shifty.fn.scripty2.js /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v0.1.5 (2011/08/05) 2 | 3 | - Added a sanity check for scale(0) values and replace them with 0.0001. It doesn't seem possible to decompose matrices with scale(0) using the method implemented 4 | - Switched from Swifty.js to Mifty.js for the core distribution which is a build of Swifty optimised for Morf.js use 5 | - The `decompose` function now returns the identity matrix on failure to decompose so as not to throw an error 6 | - Updated the build script to provide output file size and give an estimate of the gzipped file size 7 | - Added a function to optimise the CSS. WebKitCSSMatrix *always* outputs values to 5 decimal places, to save space the string is now parsed before output 8 | - Fixed a bug where some transitions would throw errors and miss keyframes when using `-webkit-transform`. Problem was caused by floats with too many decimal places being passed to the `WebKitCSSMatrix` constructor (5 is the limit). 9 | 10 | # v0.1.4 (2011/07/30) 11 | 12 | - Fixed a bug with numeric values, such as `opacity` 13 | - Ensured Morf tidies up after itself when using native transitions. Otherwise future CSS changes might be unexpectedly animated 14 | - Working out the initial CSS state now uses `window.getComputedStyle()` to take into account any styling applied by external CSS stylesheets 15 | - Fixed a bug that prevented the `webkitAnimationEnd` listener being removed as it was being accidentally defined in global scope 16 | - Callback function now passes back the original element as a parameter 17 | - Disabled the caching as it doesn't appear to be working 100% as expected 18 | 19 | # v0.1.3 (2011/07/27) 20 | 21 | - Added a cache to keep track of generated animations to reduce CPU usage for repeated transition effects 22 | - Improved the efficiency of the setup loop 23 | 24 | # v0.1.2 (2011/07/20) 25 | 26 | - Added a callback to the config options as an alternative to listening for `webkitTransitionEnd` 27 | - Updated Shifty.js to 0.4.1 28 | 29 | # v0.1.1 (2011/07/15) 30 | 31 | - Fixed binding issue with `webkitAnimationEnd` event 32 | - Updated Shifty.js to 0.2.0 33 | 34 | # v0.1 (2011/06/11) 35 | 36 | - Initial Commit -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Joe Lambert 2 | http://www.joelambert.co.uk 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Morf.js 2 | 3 | Morf.js is a Javascript work-around for hardware accelerated CSS3 transitions with custom easing functions. Ever wanted to produce CSS transitions with more compelling effects than just `linear`, `ease`, `ease-out`, `ease-in` or `cubic-bezier`? Well now you can! 4 | 5 | [View Demo](http://www.joelambert.co.uk/morf) 6 | 7 | # Requirements 8 | Morf requires the following: 9 | 10 | - A WebKit browser capable of CSS Animations (Morf uses `@keyframes` animations under the hood) 11 | - Shifty.js (>= 0.1.3) - Morf can be downloaded with or without Shifty pre-bundled. 12 | 13 | ***Note**: As of Morf.js v0.1.5, Shifty.js has been replaced by Mifty.js in the pre-bundled build. Mifty.js is a special build of Shifty optimised for use with Morf.js, which allows us to reduce the Morf filesize.* 14 | 15 | ## Why is this WebKit only? 16 | 17 | Although other browser vendors have started to add CSS Animations (e.g. Firefox 5) they do not yet have an alternative implementation for the `WebKitCSSMatrix` object which is used to calculate the interpolated matrix values. 18 | 19 | # How do I use it? 20 | 21 | Using it is simple but does require that you trigger the transition from Javascript. To transition an element to a new state you would do the following: 22 | 23 | // Get a reference to the element 24 | var elem = document.getElementById('elem'); 25 | 26 | var trans = Morf.transition(elem, { 27 | // New CSS state 28 | '-webkit-transform': 'translate3d(300px, 0, 0) rotate(90deg)', 29 | 'background-color': '#FF0000' 30 | }, { 31 | duration: '1500ms', 32 | timingFunction: 'bounce', 33 | callback: function (elem) { 34 | // You can optionally add a callback option for when the animation completes. 35 | } 36 | }); 37 | 38 | Thats it! Your element will then transition right 300px, rotate 90deg & change colour to red using the `bounce` easing function. If you would like to invoke a function when the animation completes, you can do so with the `callback` option or listen for the `webkitTransitionEnd` event. 39 | 40 | ## Additional Parameters 41 | 42 | Here is a list of parameters you can pass into the options object: 43 | 44 | - `duration` *(string)* **required** 45 | 46 | The length of time the transition is to take. e.g. `1000ms` or `2s`. 47 | 48 | - `timingFunction` *(string)* **required** 49 | 50 | The name of the timing function to use. See *Available easing functions* for more details. 51 | 52 | - `callback` *(function)* 53 | 54 | A function to execute once the transition has completed. The element that the transition was applied to is passed back as an argument to the callback function. 55 | 56 | - `increment` *(float)* default: `0.01` 57 | 58 | The frequency at which to produce CSS animation keyframes. The default 0.01 produces keyframes at 1% intervals, increasing this value *may* result in uneven animations. 59 | 60 | - `debug` *(boolean)* default: `false` 61 | 62 | If set to true then Morf.js will `console.log` the generated CSS. 63 | 64 | - `optimise` *(boolean)* default: `true` 65 | 66 | The `WebKitCSSMatrix`'s `toString()` function will output numbers with 5 decimal places, that is you may end up with many `0.00000` references in your outputted CSS. By default Morf will optimise this string. If you wish to get the originally generated string you can by passing in `false`. 67 | 68 | - `decimalPlaces` *(int)* default: `5` 69 | 70 | This is the number of decimal places the optimised CSS string will be rounded to. If `optimise` is `false` then this parameter has no effect. 71 | 72 | ##Using morf.js as a CSS3 Animation Generator 73 | 74 | You may also just want to use Morf as a CSS animation generator, in which case you can get the generated keyframes in CSS format using the `.css` property. So, continuing the above example: 75 | 76 | console.log(trans.css); 77 | 78 | You can then paste the `@keyframes` code straight into your stylesheet and use it as normal, without the need for Javascript. 79 | 80 | ##Available easing functions 81 | 82 | All of [Robert Penner](http://www.robertpenner.com/easing/)'s easing functions are available (via [Shifty.js](https://github.com/jeremyckahn/shifty)) 83 | 84 | - `easeInQuad` 85 | - `easeOutQuad` 86 | - `easeInOutQuad` 87 | - `easeInCubic` 88 | - `easeOutCubic` 89 | - `easeInOutCubic` 90 | - `easeInQuart` 91 | - `easeOutQuart` 92 | - `easeInOutQuart` 93 | - `easeInQuint` 94 | - `easeOutQuint` 95 | - `easeInOutQuint` 96 | - `easeInSine` 97 | - `easeOutSine` 98 | - `easeInOutSine` 99 | - `easeInExpo` 100 | - `easeOutExpo` 101 | - `easeInOutExpo` 102 | - `easeInCirc` 103 | - `easeOutCirc` 104 | - `easeInOutCirc` 105 | - `easeOutBounce` 106 | - `easeInBack` 107 | - `easeOutBack` 108 | - `easeInOutBack` 109 | - `elastic` 110 | - `swingFromTo` 111 | - `swingFrom` 112 | - `swingTo` 113 | - `bounce` 114 | - `bouncePast` 115 | - `easeFromTo` 116 | - `easeFrom` 117 | - `easeTo` 118 | 119 | There are also a couple of handpicked formulas from [Thomas Fuch](http://mir.aculo.us)'s [Scripty2](http://scripty2.com/): 120 | 121 | - `spring` 122 | - `sinusoidal` 123 | 124 | ##How can I add my own? 125 | 126 | Adding your own is easy, an easing function has the following prototype: 127 | 128 | // pos is the percentage of the way through the transition (0.0-1.0) 129 | function(pos) { 130 | var newPos = someMaths(pos) 131 | return newPos; 132 | }; 133 | 134 | Once you've written your function, you just need to load it into Shifty's available formulas. Here's how I the Scripty2 functions are added: 135 | 136 | (function(){ 137 | var scripty2 = { 138 | spring: function(pos) { 139 | return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); 140 | }, 141 | 142 | sinusoidal: function(pos) { 143 | return (-Math.cos(pos*Math.PI)/2) + 0.5; 144 | } 145 | }; 146 | 147 | // Load the Scripty2 functions 148 | for(var t in scripty2) 149 | Tweenable.prototype.formula[t] = scripty2[t]; 150 | })(); 151 | 152 | # How does it work? 153 | 154 | So you know how to use it but you want to know how it works? Well, what Morf actually does is create a CSS3 animation on the fly for the requested transition. In other words at the time that `Morf.transition` is called, all the necessary keyframes are generated to give the impression that a transition has taken place. 155 | 156 | Even though this is actually a CSS Animation, Morf does its best to masquerade as a transition, even throwing a `webkitTransitionEnd` event when its finished. 157 | 158 | ## Tweening CSS 159 | To work out all the interpolated CSS states, Morf uses the fantastic [Shifty.js](https://github.com/jeremyckahn/shifty) along with some custom code to handle matrix transformations. Shifty is responsible for working out all regular CSS tween values. e.g. `width`, `height`, `background-color`. 160 | 161 | ## Tweening Matrix Transformations 162 | In order to tween the 3D matrix, I had to add some custom functions to the `WebKitCSSMatrix` object. The process of accurately tweening between two matrix states requires that the matrixes themselves be first decomposed into their composite parts (translate, rotate, scale etc). Once these composite parts are known, its a matter of tweening between each part of each state and then rebuilding the composite matrix. 163 | 164 | The bulk of this calculation is done using the custom `WebKitCSSMatrix.decompose()` function. This function is a Javascript implementation of [pseudo code provided by the W3C](http://www.w3.org/TR/css3-2d-transforms/#matrix-decomposition). Its likely that this is pretty close to how WebKit produces tween values internally but in Javascript rather than native code, so not quite as quick. 165 | 166 | In order to get the `decompose()` function working I also had to supplement the `WebKitCSSMatrix` object with some other helpful matrix functions and add a basic `Vector4` implementation. This may be of use to others so feel free to repurpose the code for your own projects. 167 | 168 | ***Note:** The `decompose()` function is fairly expensive so its only called once for the start and end state of each transition.* 169 | 170 | # Morf.js Ports & Use Cases 171 | 172 | - [**webOS Enyo Port**](https://github.com/germboy/MorfJS) - Morf.js ported for use with the Enyo framework for webOS devices such as the HP Touchpad. 173 | 174 | # Contributing 175 | 176 | Contribution is welcomed but to make it easier to accept a pull request here are some guidelines: 177 | 178 | - 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. 179 | 180 | - Please [camelCase](http://en.wikipedia.org/wiki/CamelCase) your variables. 181 | 182 | # Thanks 183 | 184 | A big thanks to [Jeremy Kahn](https://twitter.com/#!/jeremyckahn) for his excellent [Shifty.js](https://github.com/jeremyckahn/shifty) micro-library & for modifying it to make it compatible with Morf! 185 | 186 | # License 187 | 188 | Morf 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 | echo $outputname.".js:\n ".round(strlen(file_get_contents($max_path))/1024, 2)."KB\n ".round(strlen(gzcompress(file_get_contents($max_path)))/1024, 2)."KB (gzipped)\n\n"; 41 | echo $outputname.".min.js:\n ".round(strlen(file_get_contents($min_path))/1024, 2)."KB\n ".round(strlen(gzcompress(file_get_contents($min_path)))/1024, 2)."KB (gzipped)\n\n"; 42 | } 43 | ?> -------------------------------------------------------------------------------- /build/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 0.1.5 4 | ../js/ 5 | 6 | 7 | src/morf.js 8 | src/morf.utils.js 9 | src/WebkitCSSMatrix.ext.js 10 | shifty.min.js 11 | src/shifty.fn.scripty2.js 12 | 13 | 14 | src/morf.js 15 | src/morf.utils.js 16 | src/WebkitCSSMatrix.ext.js 17 | src/shifty.fn.scripty2.js 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /build/google-compiler-20100917.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joelambert/morf/95eebf8d7b6d0846d2d6f1236fe4f2adf1183595/build/google-compiler-20100917.jar -------------------------------------------------------------------------------- /css/demo.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.3/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.3/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.3/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.3/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.3/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.3/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.3/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.3/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.3/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 9, ../compass/sass/demo.scss */ 72 | * { 73 | -moz-box-sizing: border-box; 74 | -webkit-box-sizing: border-box; 75 | -ms-box-sizing: border-box; 76 | box-sizing: border-box; 77 | } 78 | 79 | /* line 14, ../compass/sass/demo.scss */ 80 | body { 81 | font-family: helvetica, arial, sans-serif; 82 | font-size: 12px; 83 | line-height: 1.4em; 84 | color: #333; 85 | } 86 | 87 | /* line 22, ../compass/sass/demo.scss */ 88 | a { 89 | color: #148a88; 90 | text-decoration: none; 91 | } 92 | /* line 27, ../compass/sass/demo.scss */ 93 | a:hover { 94 | color: #990101; 95 | } 96 | 97 | /* line 33, ../compass/sass/demo.scss */ 98 | p { 99 | margin: 0.6em 0; 100 | } 101 | 102 | /* line 38, ../compass/sass/demo.scss */ 103 | em { 104 | font-style: italic; 105 | } 106 | 107 | /* line 43, ../compass/sass/demo.scss */ 108 | h1 { 109 | font-size: 2em; 110 | font-weight: bold; 111 | margin: 0 0 2em; 112 | text-align: center; 113 | color: #111; 114 | } 115 | 116 | /* line 52, ../compass/sass/demo.scss */ 117 | h2 { 118 | font-size: 1.4em; 119 | font-weight: bold; 120 | margin: 1em 0 0.5em; 121 | color: #333; 122 | } 123 | 124 | /* line 60, ../compass/sass/demo.scss */ 125 | div#container { 126 | width: 648px; 127 | margin: 20px auto; 128 | } 129 | 130 | /* line 66, ../compass/sass/demo.scss */ 131 | div#elemcontainer { 132 | background: #EEE; 133 | } 134 | 135 | /* line 71, ../compass/sass/demo.scss */ 136 | ul { 137 | width: 660px; 138 | margin: 20px 0 0 0; 139 | padding: 0; 140 | overflow: auto; 141 | } 142 | 143 | /* line 79, ../compass/sass/demo.scss */ 144 | ul li { 145 | list-style: none; 146 | float: left; 147 | margin-right: 12px; 148 | margin-bottom: 1em; 149 | cursor: pointer; 150 | padding: 0.5em; 151 | background: #f0f0f0 -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #f0f0f0), color-stop(100%, #e3e3e3)); 152 | background: #f0f0f0 -webkit-linear-gradient(#f0f0f0, #e3e3e3); 153 | background: #f0f0f0 -moz-linear-gradient(#f0f0f0, #e3e3e3); 154 | background: #f0f0f0 -o-linear-gradient(#f0f0f0, #e3e3e3); 155 | background: #f0f0f0 -ms-linear-gradient(#f0f0f0, #e3e3e3); 156 | background: #f0f0f0 linear-gradient(#f0f0f0, #e3e3e3); 157 | -moz-border-radius: 2px; 158 | -webkit-border-radius: 2px; 159 | -o-border-radius: 2px; 160 | -ms-border-radius: 2px; 161 | -khtml-border-radius: 2px; 162 | border-radius: 2px; 163 | -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.8) inset; 164 | -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.8) inset; 165 | -o-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.8) inset; 166 | box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.8) inset; 167 | border: 1px solid #d0d0d0; 168 | width: 120px; 169 | text-align: center; 170 | color: #333; 171 | text-shadow: 0px 1px 0px white; 172 | } 173 | 174 | /* line 97, ../compass/sass/demo.scss */ 175 | ul li:hover { 176 | background: #ddd; 177 | border-color: #bbb; 178 | } 179 | 180 | /* line 103, ../compass/sass/demo.scss */ 181 | h1 { 182 | clear: both; 183 | margin-top: 1.5em; 184 | } 185 | 186 | /* line 109, ../compass/sass/demo.scss */ 187 | section#code { 188 | background: #EEE; 189 | padding: 1em; 190 | } 191 | /* line 114, ../compass/sass/demo.scss */ 192 | section#code textarea { 193 | clear: both; 194 | display: block; 195 | font-size: 10px; 196 | font-family: courier, sans-serif; 197 | line-height: 2em; 198 | height: 500px; 199 | overflow: auto; 200 | width: 100%; 201 | resize: none; 202 | padding: 0.5em; 203 | } 204 | -------------------------------------------------------------------------------- /demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Morf.js - CSS3 Transitions with custom easing functions 6 | 7 | 8 | 9 | 10 | 11 | 42 | 43 | 44 |
45 |

CSS3 Transitions with custom easing functions

46 |
47 |

Native

48 |

These are the natively supported easing functions, built into WebKit.

49 | 56 |

Custom

57 |

These are custom easing functions (thanks to Robert Penner & Thomas Fuchs) that can produce much more interesting transitions.

58 | 59 |

Generated Animation CSS

60 |

Internally the custom easing function for the transition is faked using CSS animations. Here is the code that is produced on the fly for the most recent "transition".

61 |
62 | 63 |
64 |
65 | 66 | -------------------------------------------------------------------------------- /js/modernizr.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Modernizr v2.0 3 | * http://www.modernizr.com 4 | * 5 | * Copyright (c) 2009-2011 Faruk Ates, Paul Irish, Alex Sexton 6 | * Dual-licensed under the BSD or MIT licenses: www.modernizr.com/license/ 7 | */ 8 | 9 | /* 10 | * Modernizr tests which native CSS3 and HTML5 features are available in 11 | * the current UA and makes the results available to you in two ways: 12 | * as properties on a global Modernizr object, and as classes on the 13 | * element. This information allows you to progressively enhance 14 | * your pages with a granular level of control over the experience. 15 | * 16 | * Modernizr has an optional (not included) conditional resource loader 17 | * called Modernizr.load(), based on Yepnope.js (yepnopejs.com). 18 | * To get a build that includes Modernizr.load(), as well as choosing 19 | * which tests to include, go to www.modernizr.com/download/ 20 | * 21 | * @author Faruk Ates 22 | * @author Paul Irish 23 | * @author Alex Sexton 24 | * @copyright (c) 2009-2011 25 | * @contributor Ben Alman 26 | */ 27 | 28 | window.Modernizr = (function( window, document, undefined ) { 29 | 30 | var version = '2.0', 31 | 32 | Modernizr = {}, 33 | 34 | // option for enabling the HTML classes to be added 35 | enableClasses = true, 36 | 37 | docElement = document.documentElement, 38 | docHead = document.head || document.getElementsByTagName('head')[0], 39 | 40 | /** 41 | * Create our "modernizr" element that we do most feature tests on. 42 | */ 43 | mod = 'modernizr', 44 | modElem = document.createElement(mod), 45 | mStyle = modElem.style, 46 | 47 | /** 48 | * Create the input element for various Web Forms feature tests. 49 | */ 50 | inputElem = document.createElement('input'), 51 | 52 | smile = ':)', 53 | 54 | toString = Object.prototype.toString, 55 | 56 | // List of property values to set for css tests. See ticket #21 57 | prefixes = ' -webkit- -moz- -o- -ms- -khtml- '.split(' '), 58 | 59 | // Following spec is to expose vendor-specific style properties as: 60 | // elem.style.WebkitBorderRadius 61 | // and the following would be incorrect: 62 | // elem.style.webkitBorderRadius 63 | 64 | // Webkit ghosts their properties in lowercase but Opera & Moz do not. 65 | // Microsoft foregoes prefixes entirely <= IE8, but appears to 66 | // use a lowercase `ms` instead of the correct `Ms` in IE9 67 | 68 | // More here: http://github.com/Modernizr/Modernizr/issues/issue/21 69 | domPrefixes = 'Webkit Moz O ms Khtml'.split(' '), 70 | 71 | ns = {'svg': 'http://www.w3.org/2000/svg'}, 72 | 73 | tests = {}, 74 | inputs = {}, 75 | attrs = {}, 76 | 77 | classes = [], 78 | 79 | featureName, // used in testing loop 80 | 81 | 82 | // Inject element with style element and some CSS rules 83 | injectElementWithStyles = function( rule, callback, nodes, testnames ) { 84 | 85 | var style, ret, node, 86 | div = document.createElement('div'); 87 | 88 | if ( parseInt(nodes, 10) ) { 89 | // In order not to give false positives we create a node for each test 90 | // This also allows the method to scale for unspecified uses 91 | while ( nodes-- ) { 92 | node = document.createElement('div'); 93 | node.id = testnames ? testnames[nodes] : mod + (nodes + 1); 94 | div.appendChild(node); 95 | } 96 | } 97 | 98 | // '].join(''); 103 | div.id = mod; 104 | div.innerHTML += style; 105 | docElement.appendChild(div); 106 | 107 | ret = callback(div, rule); 108 | div.parentNode.removeChild(div); 109 | 110 | return !!ret; 111 | 112 | }, 113 | 114 | 115 | // adapted from matchMedia polyfill 116 | // by Scott Jehl and Paul Irish 117 | // gist.github.com/786768 118 | testMediaQuery = function( mq ) { 119 | 120 | if ( window.matchMedia ) { 121 | return matchMedia(mq).matches; 122 | } 123 | 124 | var bool; 125 | 126 | injectElementWithStyles('@media ' + mq + ' { #' + mod + ' { position: absolute; } }', function( node ) { 127 | bool = (window.getComputedStyle ? 128 | getComputedStyle(node, null) : 129 | node.currentStyle)['position'] == 'absolute'; 130 | }); 131 | 132 | return bool; 133 | 134 | }, 135 | 136 | 137 | /** 138 | * isEventSupported determines if a given element supports the given event 139 | * function from http://yura.thinkweb2.com/isEventSupported/ 140 | */ 141 | isEventSupported = (function() { 142 | 143 | var TAGNAMES = { 144 | 'select': 'input', 'change': 'input', 145 | 'submit': 'form', 'reset': 'form', 146 | 'error': 'img', 'load': 'img', 'abort': 'img' 147 | }; 148 | 149 | function isEventSupported( eventName, element ) { 150 | 151 | element = element || document.createElement(TAGNAMES[eventName] || 'div'); 152 | eventName = 'on' + eventName; 153 | 154 | // When using `setAttribute`, IE skips "unload", WebKit skips "unload" and "resize", whereas `in` "catches" those 155 | var isSupported = eventName in element; 156 | 157 | if ( !isSupported ) { 158 | // If it has no `setAttribute` (i.e. doesn't implement Node interface), try generic element 159 | if ( !element.setAttribute ) { 160 | element = document.createElement('div'); 161 | } 162 | if ( element.setAttribute && element.removeAttribute ) { 163 | element.setAttribute(eventName, ''); 164 | isSupported = is(element[eventName], 'function'); 165 | 166 | // If property was created, "remove it" (by setting value to `undefined`) 167 | if ( !is(element[eventName], undefined) ) { 168 | element[eventName] = undefined; 169 | } 170 | element.removeAttribute(eventName); 171 | } 172 | } 173 | 174 | element = null; 175 | return isSupported; 176 | } 177 | return isEventSupported; 178 | })(); 179 | 180 | // hasOwnProperty shim by kangax needed for Safari 2.0 support 181 | var _hasOwnProperty = ({}).hasOwnProperty, hasOwnProperty; 182 | if ( !is(_hasOwnProperty, undefined) && !is(_hasOwnProperty.call, undefined) ) { 183 | hasOwnProperty = function (object, property) { 184 | return _hasOwnProperty.call(object, property); 185 | }; 186 | } 187 | else { 188 | hasOwnProperty = function (object, property) { /* yes, this can give false positives/negatives, but most of the time we don't care about those */ 189 | return ((property in object) && is(object.constructor.prototype[property], undefined)); 190 | }; 191 | } 192 | 193 | /** 194 | * setCss applies given styles to the Modernizr DOM node. 195 | */ 196 | function setCss( str ) { 197 | mStyle.cssText = str; 198 | } 199 | 200 | /** 201 | * setCssAll extrapolates all vendor-specific css strings. 202 | */ 203 | function setCssAll( str1, str2 ) { 204 | return setCss(prefixes.join(str1 + ';') + ( str2 || '' )); 205 | } 206 | 207 | /** 208 | * is returns a boolean for if typeof obj is exactly type. 209 | */ 210 | function is( obj, type ) { 211 | return typeof obj === type; 212 | } 213 | 214 | /** 215 | * contains returns a boolean for if substr is found within str. 216 | */ 217 | function contains( str, substr ) { 218 | return !!~('' + str).indexOf(substr); 219 | } 220 | 221 | /** 222 | * testProps is a generic CSS / DOM property test; if a browser supports 223 | * a certain property, it won't return undefined for it. 224 | * A supported CSS property returns empty string when its not yet set. 225 | */ 226 | function testProps( props, prefixed ) { 227 | for ( var i in props ) { 228 | if ( mStyle[ props[i] ] !== undefined ) { 229 | return prefixed == 'pfx' ? props[i] : true; 230 | } 231 | } 232 | return false; 233 | } 234 | 235 | /** 236 | * testPropsAll tests a list of DOM properties we want to check against. 237 | * We specify literally ALL possible (known and/or likely) properties on 238 | * the element including the non-vendor prefixed one, for forward- 239 | * compatibility. 240 | */ 241 | function testPropsAll( prop, prefixed ) { 242 | 243 | var ucProp = prop.charAt(0).toUpperCase() + prop.substr(1), 244 | props = (prop + ' ' + domPrefixes.join(ucProp + ' ') + ucProp).split(' '); 245 | 246 | return testProps(props, prefixed); 247 | } 248 | 249 | /** 250 | * testBundle tests a list of CSS features that require element and style injection. 251 | * By bundling them together we can reduce the need to touch the DOM multiple times. 252 | */ 253 | /*>>testBundle*/ 254 | var testBundle = (function( styles, tests ) { 255 | var style = styles.join(''), 256 | len = tests.length; 257 | 258 | injectElementWithStyles(style, function( node, rule ) { 259 | var style = document.styleSheets[document.styleSheets.length - 1], 260 | cssText = style.cssText || style.cssRules[0].cssText, 261 | children = node.childNodes, hash = {}; 262 | 263 | while ( len-- ) { 264 | hash[children[len].id] = children[len]; 265 | } 266 | 267 | /*>>touch*/ Modernizr['touch'] = ('ontouchstart' in window) || hash['touch'].offsetTop === 9; /*>>touch*/ 268 | /*>>csstransforms3d*/ Modernizr['csstransforms3d'] = hash['csstransforms3d'].offsetLeft === 9; /*>>csstransforms3d*/ 269 | /*>>generatedcontent*/Modernizr['generatedcontent'] = hash['generatedcontent'].offsetHeight >= 1; /*>>generatedcontent*/ 270 | /*>>fontface*/ Modernizr['fontface'] = /src/i.test(cssText) && 271 | cssText.indexOf(rule.split(' ')[0]) === 0; /*>>fontface*/ 272 | }, len, tests); 273 | 274 | })([ 275 | // Pass in styles to be injected into document 276 | /*>>fontface*/ '@font-face {font-family:"font";src:url("//:")}' /*>>fontface*/ 277 | 278 | /*>>touch*/ ,['@media (',prefixes.join('touch-enabled),('),mod,')', 279 | '{#touch{top:9px;position:absolute}}'].join('') /*>>touch*/ 280 | 281 | /*>>csstransforms3d*/ ,['@media (',prefixes.join('transform-3d),('),mod,')', 282 | '{#csstransforms3d{left:9px;position:absolute}}'].join('')/*>>csstransforms3d*/ 283 | 284 | /*>>generatedcontent*/,['#generatedcontent:after{content:"',smile,'"}'].join('') /*>>generatedcontent*/ 285 | ], 286 | [ 287 | /*>>fontface*/ 'fontface' /*>>fontface*/ 288 | /*>>touch*/ ,'touch' /*>>touch*/ 289 | /*>>csstransforms3d*/ ,'csstransforms3d' /*>>csstransforms3d*/ 290 | /*>>generatedcontent*/,'generatedcontent' /*>>generatedcontent*/ 291 | 292 | ]);/*>>testBundle*/ 293 | 294 | 295 | /** 296 | * Tests 297 | * ----- 298 | */ 299 | 300 | tests['flexbox'] = function() { 301 | /** 302 | * setPrefixedValueCSS sets the property of a specified element 303 | * adding vendor prefixes to the VALUE of the property. 304 | * @param {Element} element 305 | * @param {string} property The property name. This will not be prefixed. 306 | * @param {string} value The value of the property. This WILL be prefixed. 307 | * @param {string=} extra Additional CSS to append unmodified to the end of 308 | * the CSS string. 309 | */ 310 | function setPrefixedValueCSS( element, property, value, extra ) { 311 | property += ':'; 312 | element.style.cssText = (property + prefixes.join(value + ';' + property)).slice(0, -property.length) + (extra || ''); 313 | } 314 | 315 | /** 316 | * setPrefixedPropertyCSS sets the property of a specified element 317 | * adding vendor prefixes to the NAME of the property. 318 | * @param {Element} element 319 | * @param {string} property The property name. This WILL be prefixed. 320 | * @param {string} value The value of the property. This will not be prefixed. 321 | * @param {string=} extra Additional CSS to append unmodified to the end of 322 | * the CSS string. 323 | */ 324 | function setPrefixedPropertyCSS( element, property, value, extra ) { 325 | element.style.cssText = prefixes.join(property + ':' + value + ';') + (extra || ''); 326 | } 327 | 328 | var c = document.createElement('div'), 329 | elem = document.createElement('div'); 330 | 331 | setPrefixedValueCSS(c, 'display', 'box', 'width:42px;padding:0;'); 332 | setPrefixedPropertyCSS(elem, 'box-flex', '1', 'width:10px;'); 333 | 334 | c.appendChild(elem); 335 | docElement.appendChild(c); 336 | 337 | var ret = elem.offsetWidth === 42; 338 | 339 | c.removeChild(elem); 340 | docElement.removeChild(c); 341 | 342 | return ret; 343 | }; 344 | 345 | // On the S60 and BB Storm, getContext exists, but always returns undefined 346 | // http://github.com/Modernizr/Modernizr/issues/issue/97/ 347 | 348 | tests['canvas'] = function() { 349 | var elem = document.createElement('canvas'); 350 | return !!(elem.getContext && elem.getContext('2d')); 351 | }; 352 | 353 | tests['canvastext'] = function() { 354 | return !!(Modernizr['canvas'] && is(document.createElement('canvas').getContext('2d').fillText, 'function')); 355 | }; 356 | 357 | // This WebGL test may false positive. 358 | // But really it's quite impossible to know whether webgl will succeed until after you create the context. 359 | // You might have hardware that can support a 100x100 webgl canvas, but will not support a 1000x1000 webgl 360 | // canvas. So this feature inference is weak, but intentionally so. 361 | 362 | // It is known to false positive in FF4 with certain hardware and the iPad 2. 363 | 364 | tests['webgl'] = function() { 365 | return !!window.WebGLRenderingContext; 366 | }; 367 | 368 | /* 369 | * The Modernizr.touch test only indicates if the browser supports 370 | * touch events, which does not necessarily reflect a touchscreen 371 | * device, as evidenced by tablets running Windows 7 or, alas, 372 | * the Palm Pre / WebOS (touch) phones. 373 | * 374 | * Additionally, Chrome (desktop) used to lie about its support on this, 375 | * but that has since been rectified: http://crbug.com/36415 376 | * 377 | * We also test for Firefox 4 Multitouch Support. 378 | * 379 | * For more info, see: http://modernizr.github.com/Modernizr/touch.html 380 | */ 381 | 382 | tests['touch'] = function() { 383 | return Modernizr['touch']; 384 | }; 385 | 386 | 387 | /** 388 | * geolocation tests for the new Geolocation API specification. 389 | * This test is a standards compliant-only test; for more complete 390 | * testing, including a Google Gears fallback, please see: 391 | * http://code.google.com/p/geo-location-javascript/ 392 | * or view a fallback solution using google's geo API: 393 | * http://gist.github.com/366184 394 | */ 395 | tests['geolocation'] = function() { 396 | return !!navigator.geolocation; 397 | }; 398 | 399 | // Per 1.6: 400 | // This used to be Modernizr.crosswindowmessaging but the longer 401 | // name has been deprecated in favor of a shorter and property-matching one. 402 | // The old API is still available in 1.6, but as of 2.0 will throw a warning, 403 | // and in the first release thereafter disappear entirely. 404 | tests['postmessage'] = function() { 405 | return !!window.postMessage; 406 | }; 407 | 408 | // Web SQL database detection is tricky: 409 | 410 | // In chrome incognito mode, openDatabase is truthy, but using it will 411 | // throw an exception: http://crbug.com/42380 412 | // We can create a dummy database, but there is no way to delete it afterwards. 413 | 414 | // Meanwhile, Safari users can get prompted on any database creation. 415 | // If they do, any page with Modernizr will give them a prompt: 416 | // http://github.com/Modernizr/Modernizr/issues/closed#issue/113 417 | 418 | // We have chosen to allow the Chrome incognito false positive, so that Modernizr 419 | // doesn't litter the web with these test databases. As a developer, you'll have 420 | // to account for this gotcha yourself. 421 | tests['websqldatabase'] = function() { 422 | var result = !!window.openDatabase; 423 | /* if (result){ 424 | try { 425 | result = !!openDatabase( mod + "testdb", "1.0", mod + "testdb", 2e4); 426 | } catch(e) { 427 | } 428 | } */ 429 | return result; 430 | }; 431 | 432 | // Vendors had inconsistent prefixing with the experimental Indexed DB: 433 | // - Webkit's implementation is accessible through webkitIndexedDB 434 | // - Firefox shipped moz_indexedDB before FF4b9, but since then has been mozIndexedDB 435 | // For speed, we don't test the legacy (and beta-only) indexedDB 436 | tests['indexedDB'] = function() { 437 | for ( var i = -1, len = domPrefixes.length; ++i < len; ){ 438 | if ( window[domPrefixes[i].toLowerCase() + 'IndexedDB'] ){ 439 | return true; 440 | } 441 | } 442 | return !!window.indexedDB; 443 | }; 444 | 445 | // documentMode logic from YUI to filter out IE8 Compat Mode 446 | // which false positives. 447 | tests['hashchange'] = function() { 448 | return isEventSupported('hashchange', window) && (document.documentMode === undefined || document.documentMode > 7); 449 | }; 450 | 451 | // Per 1.6: 452 | // This used to be Modernizr.historymanagement but the longer 453 | // name has been deprecated in favor of a shorter and property-matching one. 454 | // The old API is still available in 1.6, but as of 2.0 will throw a warning, 455 | // and in the first release thereafter disappear entirely. 456 | tests['history'] = function() { 457 | return !!(window.history && history.pushState); 458 | }; 459 | 460 | tests['draganddrop'] = function() { 461 | return isEventSupported('dragstart') && isEventSupported('drop'); 462 | }; 463 | 464 | // Mozilla is targeting to land MozWebSocket for FF6 465 | // bugzil.la/659324 466 | tests['websockets'] = function() { 467 | for ( var i = -1, len = domPrefixes.length; ++i < len; ){ 468 | if ( window[domPrefixes[i] + 'WebSocket'] ){ 469 | return true; 470 | } 471 | } 472 | return 'WebSocket' in window; 473 | }; 474 | 475 | 476 | // http://css-tricks.com/rgba-browser-support/ 477 | tests['rgba'] = function() { 478 | // Set an rgba() color and check the returned value 479 | 480 | setCss('background-color:rgba(150,255,150,.5)'); 481 | 482 | return contains(mStyle.backgroundColor, 'rgba'); 483 | }; 484 | 485 | tests['hsla'] = function() { 486 | // Same as rgba(), in fact, browsers re-map hsla() to rgba() internally, 487 | // except IE9 who retains it as hsla 488 | 489 | setCss('background-color:hsla(120,40%,100%,.5)'); 490 | 491 | return contains(mStyle.backgroundColor, 'rgba') || contains(mStyle.backgroundColor, 'hsla'); 492 | }; 493 | 494 | tests['multiplebgs'] = function() { 495 | // Setting multiple images AND a color on the background shorthand property 496 | // and then querying the style.background property value for the number of 497 | // occurrences of "url(" is a reliable method for detecting ACTUAL support for this! 498 | 499 | setCss('background:url(//:),url(//:),red url(//:)'); 500 | 501 | // If the UA supports multiple backgrounds, there should be three occurrences 502 | // of the string "url(" in the return value for elemStyle.background 503 | 504 | return /(url\s*\(.*?){3}/.test(mStyle.background); 505 | }; 506 | 507 | 508 | // In testing support for a given CSS property, it's legit to test: 509 | // `elem.style[styleName] !== undefined` 510 | // If the property is supported it will return an empty string, 511 | // if unsupported it will return undefined. 512 | 513 | // We'll take advantage of this quick test and skip setting a style 514 | // on our modernizr element, but instead just testing undefined vs 515 | // empty string. 516 | 517 | 518 | tests['backgroundsize'] = function() { 519 | return testPropsAll('backgroundSize'); 520 | }; 521 | 522 | tests['borderimage'] = function() { 523 | return testPropsAll('borderImage'); 524 | }; 525 | 526 | 527 | // Super comprehensive table about all the unique implementations of 528 | // border-radius: http://muddledramblings.com/table-of-css3-border-radius-compliance 529 | 530 | tests['borderradius'] = function() { 531 | return testPropsAll('borderRadius'); 532 | }; 533 | 534 | // WebOS unfortunately false positives on this test. 535 | tests['boxshadow'] = function() { 536 | return testPropsAll('boxShadow'); 537 | }; 538 | 539 | // FF3.0 will false positive on this test 540 | tests['textshadow'] = function() { 541 | return document.createElement('div').style.textShadow === ''; 542 | }; 543 | 544 | 545 | tests['opacity'] = function() { 546 | // Browsers that actually have CSS Opacity implemented have done so 547 | // according to spec, which means their return values are within the 548 | // range of [0.0,1.0] - including the leading zero. 549 | 550 | setCssAll('opacity:.55'); 551 | 552 | // The non-literal . in this regex is intentional: 553 | // German Chrome returns this value as 0,55 554 | // https://github.com/Modernizr/Modernizr/issues/#issue/59/comment/516632 555 | return /^0.55$/.test(mStyle.opacity); 556 | }; 557 | 558 | 559 | tests['cssanimations'] = function() { 560 | return testPropsAll('animationName'); 561 | }; 562 | 563 | 564 | tests['csscolumns'] = function() { 565 | return testPropsAll('columnCount'); 566 | }; 567 | 568 | 569 | tests['cssgradients'] = function() { 570 | /** 571 | * For CSS Gradients syntax, please see: 572 | * http://webkit.org/blog/175/introducing-css-gradients/ 573 | * https://developer.mozilla.org/en/CSS/-moz-linear-gradient 574 | * https://developer.mozilla.org/en/CSS/-moz-radial-gradient 575 | * http://dev.w3.org/csswg/css3-images/#gradients- 576 | */ 577 | 578 | var str1 = 'background-image:', 579 | str2 = 'gradient(linear,left top,right bottom,from(#9f9),to(white));', 580 | str3 = 'linear-gradient(left top,#9f9, white);'; 581 | 582 | setCss( 583 | (str1 + prefixes.join(str2 + str1) + prefixes.join(str3 + str1)).slice(0, -str1.length) 584 | ); 585 | 586 | return contains(mStyle.backgroundImage, 'gradient'); 587 | }; 588 | 589 | 590 | tests['cssreflections'] = function() { 591 | return testPropsAll('boxReflect'); 592 | }; 593 | 594 | 595 | tests['csstransforms'] = function() { 596 | return !!testProps(['transformProperty', 'WebkitTransform', 'MozTransform', 'OTransform', 'msTransform']); 597 | }; 598 | 599 | 600 | tests['csstransforms3d'] = function() { 601 | 602 | var ret = !!testProps(['perspectiveProperty', 'WebkitPerspective', 'MozPerspective', 'OPerspective', 'msPerspective']); 603 | 604 | // Webkit’s 3D transforms are passed off to the browser's own graphics renderer. 605 | // It works fine in Safari on Leopard and Snow Leopard, but not in Chrome in 606 | // some conditions. As a result, Webkit typically recognizes the syntax but 607 | // will sometimes throw a false positive, thus we must do a more thorough check: 608 | if ( ret && 'webkitPerspective' in docElement.style ) { 609 | 610 | // Webkit allows this media query to succeed only if the feature is enabled. 611 | // `@media (transform-3d),(-o-transform-3d),(-moz-transform-3d),(-ms-transform-3d),(-webkit-transform-3d),(modernizr){ ... }` 612 | ret = Modernizr['csstransforms3d']; 613 | } 614 | return ret; 615 | }; 616 | 617 | 618 | tests['csstransitions'] = function() { 619 | return testPropsAll('transitionProperty'); 620 | }; 621 | 622 | 623 | /*>>fontface*/ 624 | // @font-face detection routine by Diego Perini 625 | // http://javascript.nwbox.com/CSSSupport/ 626 | tests['fontface'] = function() { 627 | return Modernizr['fontface']; 628 | }; 629 | /*>>fontface*/ 630 | 631 | // CSS generated content detection 632 | tests['generatedcontent'] = function() { 633 | return Modernizr['generatedcontent']; 634 | }; 635 | 636 | 637 | 638 | // These tests evaluate support of the video/audio elements, as well as 639 | // testing what types of content they support. 640 | // 641 | // We're using the Boolean constructor here, so that we can extend the value 642 | // e.g. Modernizr.video // true 643 | // Modernizr.video.ogg // 'probably' 644 | // 645 | // Codec values from : http://github.com/NielsLeenheer/html5test/blob/9106a8/index.html#L845 646 | // thx to NielsLeenheer and zcorpan 647 | 648 | // Note: in FF 3.5.1 and 3.5.0, "no" was a return value instead of empty string. 649 | // Modernizr does not normalize for that. 650 | 651 | tests['video'] = function() { 652 | var elem = document.createElement('video'), 653 | bool = false; 654 | 655 | // IE9 Running on Windows Server SKU can cause an exception to be thrown, bug #224 656 | try { 657 | if ( bool = !!elem.canPlayType ) { 658 | bool = new Boolean(bool); 659 | bool.ogg = elem.canPlayType('video/ogg; codecs="theora"'); 660 | 661 | // Workaround required for IE9, which doesn't report video support without audio codec specified. 662 | // bug 599718 @ msft connect 663 | var h264 = 'video/mp4; codecs="avc1.42E01E'; 664 | bool.h264 = elem.canPlayType(h264 + '"') || elem.canPlayType(h264 + ', mp4a.40.2"'); 665 | 666 | bool.webm = elem.canPlayType('video/webm; codecs="vp8, vorbis"'); 667 | } 668 | 669 | } catch(e) { } 670 | 671 | return bool; 672 | }; 673 | 674 | tests['audio'] = function() { 675 | var elem = document.createElement('audio'), 676 | bool = false; 677 | 678 | try { 679 | if ( bool = !!elem.canPlayType ) { 680 | bool = new Boolean(bool); 681 | bool.ogg = elem.canPlayType('audio/ogg; codecs="vorbis"'); 682 | bool.mp3 = elem.canPlayType('audio/mpeg;'); 683 | 684 | // Mimetypes accepted: 685 | // https://developer.mozilla.org/En/Media_formats_supported_by_the_audio_and_video_elements 686 | // http://bit.ly/iphoneoscodecs 687 | bool.wav = elem.canPlayType('audio/wav; codecs="1"'); 688 | bool.m4a = elem.canPlayType('audio/x-m4a;') || elem.canPlayType('audio/aac;'); 689 | } 690 | } catch(e) { } 691 | 692 | return bool; 693 | }; 694 | 695 | 696 | // Firefox has made these tests rather unfun. 697 | 698 | // In FF4, if disabled, window.localStorage should === null. 699 | 700 | // Normally, we could not test that directly and need to do a 701 | // `('localStorage' in window) && ` test first because otherwise Firefox will 702 | // throw http://bugzil.la/365772 if cookies are disabled 703 | 704 | // However, in Firefox 4 betas, if dom.storage.enabled == false, just mentioning 705 | // the property will throw an exception. http://bugzil.la/599479 706 | // This looks to be fixed for FF4 Final. 707 | 708 | // Because we are forced to try/catch this, we'll go aggressive. 709 | 710 | // FWIW: IE8 Compat mode supports these features completely: 711 | // http://www.quirksmode.org/dom/html5.html 712 | // But IE8 doesn't support either with local files 713 | 714 | tests['localstorage'] = function() { 715 | try { 716 | return !!localStorage.getItem; 717 | } catch(e) { 718 | return false; 719 | } 720 | }; 721 | 722 | tests['sessionstorage'] = function() { 723 | try { 724 | return !!sessionStorage.getItem; 725 | } catch(e){ 726 | return false; 727 | } 728 | }; 729 | 730 | 731 | tests['webworkers'] = function() { 732 | return !!window.Worker; 733 | }; 734 | 735 | 736 | tests['applicationcache'] = function() { 737 | return !!window.applicationCache; 738 | }; 739 | 740 | 741 | // Thanks to Erik Dahlstrom 742 | tests['svg'] = function() { 743 | return !!document.createElementNS && !!document.createElementNS(ns.svg, 'svg').createSVGRect; 744 | }; 745 | 746 | // specifically for SVG inline in HTML, not within XHTML 747 | // test page: paulirish.com/demo/inline-svg 748 | tests['inlinesvg'] = function() { 749 | var div = document.createElement('div'); 750 | div.innerHTML = ''; 751 | return (div.firstChild && div.firstChild.namespaceURI) == ns.svg; 752 | }; 753 | 754 | // Thanks to F1lt3r and lucideer, ticket #35 755 | tests['smil'] = function() { 756 | return !!document.createElementNS && /SVG/.test(toString.call(document.createElementNS(ns.svg, 'animate'))); 757 | }; 758 | 759 | tests['svgclippaths'] = function() { 760 | // Possibly returns a false positive in Safari 3.2? 761 | return !!document.createElementNS && /SVG/.test(toString.call(document.createElementNS(ns.svg, 'clipPath'))); 762 | }; 763 | 764 | // input features and input types go directly onto the ret object, bypassing the tests loop. 765 | // Hold this guy to execute in a moment. 766 | function webforms() { 767 | // Run through HTML5's new input attributes to see if the UA understands any. 768 | // We're using f which is the element created early on 769 | // Mike Taylr has created a comprehensive resource for testing these attributes 770 | // when applied to all input types: 771 | // http://miketaylr.com/code/input-type-attr.html 772 | // spec: http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#input-type-attr-summary 773 | 774 | // Only input placeholder is tested while textarea's placeholder is not. 775 | // Currently Safari 4 and Opera 11 have support only for the input placeholder 776 | // Both tests are available in feature-detects/forms-placeholder.js 777 | Modernizr['input'] = (function( props ) { 778 | for ( var i = 0, len = props.length; i < len; i++ ) { 779 | attrs[ props[i] ] = !!(props[i] in inputElem); 780 | } 781 | return attrs; 782 | })('autocomplete autofocus list placeholder max min multiple pattern required step'.split(' ')); 783 | 784 | // Run through HTML5's new input types to see if the UA understands any. 785 | // This is put behind the tests runloop because it doesn't return a 786 | // true/false like all the other tests; instead, it returns an object 787 | // containing each input type with its corresponding true/false value 788 | 789 | // Big thanks to @miketaylr for the html5 forms expertise. http://miketaylr.com/ 790 | Modernizr['inputtypes'] = (function(props) { 791 | 792 | for ( var i = 0, bool, inputElemType, defaultView, len = props.length; i < len; i++ ) { 793 | 794 | inputElem.setAttribute('type', inputElemType = props[i]); 795 | bool = inputElem.type !== 'text'; 796 | 797 | // We first check to see if the type we give it sticks.. 798 | // If the type does, we feed it a textual value, which shouldn't be valid. 799 | // If the value doesn't stick, we know there's input sanitization which infers a custom UI 800 | if ( bool ) { 801 | 802 | inputElem.value = smile; 803 | inputElem.style.cssText = 'position:absolute;visibility:hidden;'; 804 | 805 | if ( /^range$/.test(inputElemType) && inputElem.style.WebkitAppearance !== undefined ) { 806 | 807 | docElement.appendChild(inputElem); 808 | defaultView = document.defaultView; 809 | 810 | // Safari 2-4 allows the smiley as a value, despite making a slider 811 | bool = defaultView.getComputedStyle && 812 | defaultView.getComputedStyle(inputElem, null).WebkitAppearance !== 'textfield' && 813 | // Mobile android web browser has false positive, so must 814 | // check the height to see if the widget is actually there. 815 | (inputElem.offsetHeight !== 0); 816 | 817 | docElement.removeChild(inputElem); 818 | 819 | } else if ( /^(search|tel)$/.test(inputElemType) ){ 820 | // Spec doesnt define any special parsing or detectable UI 821 | // behaviors so we pass these through as true 822 | 823 | // Interestingly, opera fails the earlier test, so it doesn't 824 | // even make it here. 825 | 826 | } else if ( /^(url|email)$/.test(inputElemType) ) { 827 | // Real url and email support comes with prebaked validation. 828 | bool = inputElem.checkValidity && inputElem.checkValidity() === false; 829 | 830 | } else if ( /^color$/.test(inputElemType) ) { 831 | // chuck into DOM and force reflow for Opera bug in 11.00 832 | // github.com/Modernizr/Modernizr/issues#issue/159 833 | docElement.appendChild(inputElem); 834 | docElement.offsetWidth; 835 | bool = inputElem.value != smile; 836 | docElement.removeChild(inputElem); 837 | 838 | } else { 839 | // If the upgraded input compontent rejects the :) text, we got a winner 840 | bool = inputElem.value != smile; 841 | } 842 | } 843 | 844 | inputs[ props[i] ] = !!bool; 845 | } 846 | return inputs; 847 | })('search tel url email datetime date month week time datetime-local number range color'.split(' ')); 848 | } 849 | 850 | 851 | // End of test definitions 852 | // ----------------------- 853 | 854 | 855 | 856 | // Run through all tests and detect their support in the current UA. 857 | // todo: hypothetically we could be doing an array of tests and use a basic loop here. 858 | for ( var feature in tests ) { 859 | if ( hasOwnProperty(tests, feature) ) { 860 | // run the test, throw the return value into the Modernizr, 861 | // then based on that boolean, define an appropriate className 862 | // and push it into an array of classes we'll join later. 863 | featureName = feature.toLowerCase(); 864 | Modernizr[featureName] = tests[feature](); 865 | 866 | classes.push((Modernizr[featureName] ? '' : 'no-') + featureName); 867 | } 868 | } 869 | 870 | // input tests need to run. 871 | Modernizr.input || webforms(); 872 | 873 | 874 | /** 875 | * addTest allows the user to define their own feature tests 876 | * the result will be added onto the Modernizr object, 877 | * as well as an appropriate className set on the html element 878 | * 879 | * @param feature - String naming the feature 880 | * @param test - Function returning true if feature is supported, false if not 881 | */ 882 | Modernizr.addTest = function ( feature, test ) { 883 | if ( typeof feature == "object" ) { 884 | for ( var key in feature ) { 885 | if ( hasOwnProperty( feature, key ) ) { 886 | Modernizr.addTest( key, feature[ key ] ); 887 | } 888 | } 889 | } else { 890 | 891 | feature = feature.toLowerCase(); 892 | 893 | if ( Modernizr[feature] !== undefined ) { 894 | // we're going to quit if you're trying to overwrite an existing test 895 | // if we were to allow it, we'd do this: 896 | // var re = new RegExp("\\b(no-)?" + feature + "\\b"); 897 | // docElement.className = docElement.className.replace( re, '' ); 898 | // but, no rly, stuff 'em. 899 | return; 900 | } 901 | 902 | test = typeof test == "boolean" ? test : !!test(); 903 | 904 | docElement.className += ' ' + (test ? '' : 'no-') + feature; 905 | Modernizr[feature] = test; 906 | 907 | } 908 | 909 | return Modernizr; // allow chaining. 910 | }; 911 | 912 | 913 | // Reset modElem.cssText to nothing to reduce memory footprint. 914 | setCss(''); 915 | modElem = inputElem = null; 916 | 917 | //>>BEGIN IEPP 918 | // Enable HTML 5 elements for styling (and printing) in IE. 919 | if ( window.attachEvent && (function(){ var elem = document.createElement('div'); 920 | elem.innerHTML = ''; 921 | return elem.childNodes.length !== 1; })() ) { 922 | 923 | // iepp v2 by @jon_neal & afarkas : github.com/aFarkas/iepp/ 924 | (function(win, doc) { 925 | win.iepp = win.iepp || {}; 926 | var iepp = win.iepp, 927 | elems = iepp.html5elements || 'abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video', 928 | elemsArr = elems.split('|'), 929 | elemsArrLen = elemsArr.length, 930 | elemRegExp = new RegExp('(^|\\s)('+elems+')', 'gi'), 931 | tagRegExp = new RegExp('<(\/*)('+elems+')', 'gi'), 932 | filterReg = /^\s*[\{\}]\s*$/, 933 | ruleRegExp = new RegExp('(^|[^\\n]*?\\s)('+elems+')([^\\n]*)({[\\n\\w\\W]*?})', 'gi'), 934 | docFrag = doc.createDocumentFragment(), 935 | html = doc.documentElement, 936 | head = html.firstChild, 937 | bodyElem = doc.createElement('body'), 938 | styleElem = doc.createElement('style'), 939 | printMedias = /print|all/, 940 | body; 941 | function shim(doc) { 942 | var a = -1; 943 | while (++a < elemsArrLen) 944 | // Use createElement so IE allows HTML5-named elements in a document 945 | doc.createElement(elemsArr[a]); 946 | } 947 | 948 | iepp.getCSS = function(styleSheetList, mediaType) { 949 | if(styleSheetList+'' === undefined){return '';} 950 | var a = -1, 951 | len = styleSheetList.length, 952 | styleSheet, 953 | cssTextArr = []; 954 | while (++a < len) { 955 | styleSheet = styleSheetList[a]; 956 | //currently no test for disabled/alternate stylesheets 957 | if(styleSheet.disabled){continue;} 958 | mediaType = styleSheet.media || mediaType; 959 | // Get css from all non-screen stylesheets and their imports 960 | if (printMedias.test(mediaType)) cssTextArr.push(iepp.getCSS(styleSheet.imports, mediaType), styleSheet.cssText); 961 | //reset mediaType to all with every new *not imported* stylesheet 962 | mediaType = 'all'; 963 | } 964 | return cssTextArr.join(''); 965 | }; 966 | 967 | iepp.parseCSS = function(cssText) { 968 | var cssTextArr = [], 969 | rule; 970 | while ((rule = ruleRegExp.exec(cssText)) != null){ 971 | // Replace all html5 element references with iepp substitute classnames 972 | cssTextArr.push(( (filterReg.exec(rule[1]) ? '\n' : rule[1]) +rule[2]+rule[3]).replace(elemRegExp, '$1.iepp_$2')+rule[4]); 973 | } 974 | return cssTextArr.join('\n'); 975 | }; 976 | 977 | iepp.writeHTML = function() { 978 | var a = -1; 979 | body = body || doc.body; 980 | while (++a < elemsArrLen) { 981 | var nodeList = doc.getElementsByTagName(elemsArr[a]), 982 | nodeListLen = nodeList.length, 983 | b = -1; 984 | while (++b < nodeListLen) 985 | if (nodeList[b].className.indexOf('iepp_') < 0) 986 | // Append iepp substitute classnames to all html5 elements 987 | nodeList[b].className += ' iepp_'+elemsArr[a]; 988 | } 989 | docFrag.appendChild(body); 990 | html.appendChild(bodyElem); 991 | // Write iepp substitute print-safe document 992 | bodyElem.className = body.className; 993 | bodyElem.id = body.id; 994 | // Replace HTML5 elements with which is print-safe and shouldn't conflict since it isn't part of html5 995 | bodyElem.innerHTML = body.innerHTML.replace(tagRegExp, '<$1font'); 996 | }; 997 | 998 | 999 | iepp._beforePrint = function() { 1000 | // Write iepp custom print CSS 1001 | styleElem.styleSheet.cssText = iepp.parseCSS(iepp.getCSS(doc.styleSheets, 'all')); 1002 | iepp.writeHTML(); 1003 | }; 1004 | 1005 | iepp.restoreHTML = function(){ 1006 | // Undo everything done in onbeforeprint 1007 | bodyElem.innerHTML = ''; 1008 | html.removeChild(bodyElem); 1009 | html.appendChild(body); 1010 | }; 1011 | 1012 | iepp._afterPrint = function(){ 1013 | // Undo everything done in onbeforeprint 1014 | iepp.restoreHTML(); 1015 | styleElem.styleSheet.cssText = ''; 1016 | }; 1017 | 1018 | 1019 | 1020 | // Shim the document and iepp fragment 1021 | shim(doc); 1022 | shim(docFrag); 1023 | 1024 | // 1025 | if(iepp.disablePP){return;} 1026 | 1027 | // Add iepp custom print style element 1028 | head.insertBefore(styleElem, head.firstChild); 1029 | styleElem.media = 'print'; 1030 | styleElem.className = 'iepp-printshim'; 1031 | win.attachEvent( 1032 | 'onbeforeprint', 1033 | iepp._beforePrint 1034 | ); 1035 | win.attachEvent( 1036 | 'onafterprint', 1037 | iepp._afterPrint 1038 | ); 1039 | })(window, document); 1040 | } 1041 | //>>END IEPP 1042 | 1043 | // Assign private properties to the return object with prefix 1044 | Modernizr._version = version; 1045 | 1046 | // expose these for the plugin API. Look in the source for how to join() them against your input 1047 | Modernizr._prefixes = prefixes; 1048 | Modernizr._domPrefixes = domPrefixes; 1049 | 1050 | // Modernizr.mq tests a given media query, live against the current state of the window 1051 | // A few important notes: 1052 | // * If a browser does not support media queries at all (eg. oldIE) the mq() will always return false 1053 | // * A max-width or orientation query will be evaluated against the current state, which may change later. 1054 | // * You must specify values. Eg. If you are testing support for the min-width media query use: 1055 | // Modernizr.mq('(min-width:0)') 1056 | // usage: 1057 | // Modernizr.mq('only screen and (max-width:768)') 1058 | Modernizr.mq = testMediaQuery; 1059 | 1060 | // Modernizr.hasEvent() detects support for a given event, with an optional element to test on 1061 | // Modernizr.hasEvent('gesturestart', elem) 1062 | Modernizr.hasEvent = isEventSupported; 1063 | 1064 | // Modernizr.testProp() investigates whether a given style property is recognized 1065 | // Note that the property names must be provided in the camelCase variant. 1066 | // Modernizr.testProp('pointerEvents') 1067 | Modernizr.testProp = function(prop){ 1068 | return testProps([prop]); 1069 | }; 1070 | 1071 | // Modernizr.testAllProps() investigates whether a given style property, 1072 | // or any of its vendor-prefixed variants, is recognized 1073 | // Note that the property names must be provided in the camelCase variant. 1074 | // Modernizr.testAllProps('boxSizing') 1075 | Modernizr.testAllProps = testPropsAll; 1076 | 1077 | 1078 | 1079 | // Modernizr.testStyles() allows you to add custom styles to the document and test an element afterwards 1080 | // Modernizr.testStyles('#modernizr { position:absolute }', function(elem, rule){ ... }) 1081 | Modernizr.testStyles = injectElementWithStyles; 1082 | 1083 | 1084 | // Modernizr.prefixed() returns the prefixed or nonprefixed property name variant of your input 1085 | // Modernizr.prefixed('boxSizing') // 'MozBoxSizing' 1086 | 1087 | // Properties must be passed as dom-style camelcase, rather than `box-sizing` hypentated style. 1088 | // Return values will also be the camelCase variant, if you need to translate that to hypenated style use: 1089 | // 1090 | // str.replace(/([A-Z])/g, function(str,m1){ return '-' + m1.toLowerCase(); }).replace(/^ms-/,'-ms-'); 1091 | 1092 | // If you're trying to ascertain which transition end event to bind to, you might do something like... 1093 | // 1094 | // var transEndEventNames = { 1095 | // 'WebkitTransition' : 'webkitTransitionEnd', 1096 | // 'MozTransition' : 'transitionend', 1097 | // 'OTransition' : 'oTransitionEnd', 1098 | // 'msTransition' : 'msTransitionEnd', // maybe? 1099 | // 'transition' : 'transitionEnd' 1100 | // }, 1101 | // transEndEventName = transEndEventNames[ Modernizr.prefixed('transition') ]; 1102 | 1103 | Modernizr.prefixed = function(prop){ 1104 | return testPropsAll(prop, 'pfx'); 1105 | }; 1106 | 1107 | 1108 | 1109 | // Remove "no-js" class from element, if it exists: 1110 | docElement.className = docElement.className.replace(/\bno-js\b/, '') 1111 | 1112 | // Add the new classes to the element. 1113 | + (enableClasses ? ' js ' + classes.join(' ') : ''); 1114 | 1115 | return Modernizr; 1116 | 1117 | })(this, this.document); -------------------------------------------------------------------------------- /js/morf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve Morf v0.1.5 3 | * http://www.joelambert.co.uk/morf 4 | * 5 | * Copyright 2011, Joe Lambert. 6 | * Free to use under the MIT license. 7 | * http://www.opensource.org/licenses/mit-license.php 8 | */ 9 | 10 | var Morf = function(elem, css, opts) { 11 | var from = {}, to = {}, 12 | 13 | fromElem = document.createElement('div'), 14 | toElem = document.createElement('div'), 15 | 16 | options = { 17 | timingFunction: 'ease', 18 | duration: null, 19 | increment: 0.01, 20 | debug: false, 21 | optimise: true, // Whether the outputted CSS should be optimised 22 | decimalPlaces: 5 // How many decimal places to optimise the WebKitCSSMatrix output to 23 | }, 24 | 25 | // Define all other var's used in the function 26 | i = rule = ruleName = camel = m1 = m2 = progress = frame = rule = transEvent = val = null, cacheKey = '', 27 | 28 | // Setup a scoped reference to ourselves 29 | _this = this, 30 | 31 | keyframes = {}, 32 | 33 | // Create a unique name for this animation 34 | animName = 'anim'+(new Date().getTime()), 35 | 36 | 37 | /* --- Helper Functions ------------------------------------------------------------------- */ 38 | 39 | // Event listener for the webkitAnimationEnd Event 40 | animationEndListener = function(event){ 41 | elem.removeEventListener('webkitAnimationEnd', animationEndListener, true); 42 | 43 | // Dispatch a faux webkitTransitionEnd event to complete the appearance of this being a transition rather than an animation 44 | // TODO: Should we throw an event for each property changed? (event.propertyName = 'opacity' etc) 45 | transEvent = document.createEvent("Event"); 46 | transEvent.initEvent("webkitTransitionEnd", true, true); 47 | elem.dispatchEvent(transEvent); 48 | 49 | // Reset transition effects after use 50 | elem.style.webkitTransitionTimingFunction = null; 51 | elem.style.webkitTransitionDuration = 0; 52 | 53 | if (options.callback) { 54 | options.callback(elem); 55 | } 56 | }, 57 | 58 | // Adds the CSS to the current page 59 | addKeyframeRule = function(rule) { 60 | if (document.styleSheets && document.styleSheets.length) 61 | document.styleSheets[0].insertRule(rule, 0); 62 | else 63 | { 64 | var style = document.createElement('style'); 65 | style.innerHTML = rule; 66 | document.head.appendChild(style); 67 | } 68 | }, 69 | 70 | // Produces a CSS string representation of the Keyframes 71 | createAnimationCSS = function(kf, name) { 72 | var str = '@-webkit-keyframes '+name+' {\n', f = pos = rule = null, fStr = ''; 73 | 74 | for(pos in kf) 75 | { 76 | f = kf[pos]; 77 | fStr = '\t'+pos+' {\n'; 78 | 79 | for(rule in f) 80 | fStr += '\t\t'+_this.util.toDash(rule)+': '+f[rule]+';\n'; 81 | 82 | fStr += "\t}\n\n"; 83 | 84 | str += fStr; 85 | } 86 | 87 | return options.optimise ? optimiseCSS(str+' }') : str+' }'; 88 | }, 89 | 90 | // Replaces scale(0) with 0.0001 to get around the inability to these decompose matrix 91 | sanityCheckTransformString = function(str) { 92 | var scale = str.match(/scale[Y|X|Z]*\([0-9, ]*0[,0-9 ]*\)/g), 93 | i = 0; 94 | 95 | if(scale) 96 | { 97 | // There might be multiple scale() properties in the string 98 | for(i = 0; i < scale.length; i++) 99 | str = str.replace(scale[i], scale[i].replace(/([^0-9])0([^0.9])/g, "$10.0001$2")); 100 | } 101 | 102 | return str; 103 | }, 104 | 105 | // WebKitCSSMatrix toString() ALWAYS outputs numbers to 5 decimal places - this helps optimise the string 106 | optimiseCSS = function(str, decimalPlaces) { 107 | decimalPlaces = typeof options.decimalPlaces == 'number' ? options.decimalPlaces : 5; 108 | var matches = str.match(/[0-9\.]+/gm), 109 | i = 0; 110 | 111 | if(matches) 112 | { 113 | for(i = 0; i < matches.length; i++) 114 | str = str.replace(matches[i], parseFloat( parseFloat(matches[i]).toFixed(decimalPlaces))); 115 | } 116 | 117 | return str; 118 | }; 119 | 120 | /* --- Helper Functions End --------------------------------------------------------------- */ 121 | 122 | 123 | // Import the options 124 | for(i in opts) 125 | options[i] = opts[i]; 126 | 127 | 128 | // If timingFunction is a natively supported function then just trigger normal transition 129 | if( options.timingFunction === 'ease' || 130 | options.timingFunction === 'linear' || 131 | options.timingFunction === 'ease-in' || 132 | options.timingFunction === 'ease-out' || 133 | options.timingFunction === 'ease-in-out' || 134 | /^cubic-bezier/.test(options.timingFunction)) { 135 | 136 | elem.style.webkitTransitionDuration = options.duration; 137 | elem.style.webkitTransitionTimingFunction = options.timingFunction; 138 | 139 | // Listen for the transitionEnd event to fire the callback if needed 140 | var transitionEndListener = function(event) { 141 | elem.removeEventListener('webkitTransitionEnd', transitionEndListener, true); 142 | 143 | // Clean up after ourself 144 | elem.style.webkitTransitionDuration = 0; 145 | elem.style.webkitTransitionTimingFunction = null; 146 | 147 | if (options.callback) { 148 | // Delay execution to ensure the clean up CSS has taken effect 149 | setTimeout(function() { 150 | options.callback(elem); 151 | }, 10); 152 | } 153 | }; 154 | 155 | elem.addEventListener('webkitTransitionEnd', transitionEndListener, true); 156 | 157 | setTimeout(function() { 158 | for(rule in css) { 159 | camel = _this.util.toCamel(rule); 160 | elem.style[camel] = css[rule]; 161 | } 162 | }, 10); 163 | 164 | this.css = ''; 165 | 166 | return; 167 | } 168 | else 169 | { 170 | // Reset transition properties for this element 171 | elem.style.webkitTransitionTimingFunction = null; 172 | elem.style.webkitTransitionDuration = 0; 173 | } 174 | 175 | // Create the key used to cache this animation 176 | cacheKey += options.timingFunction; 177 | 178 | // Setup the start and end CSS state 179 | for(rule in css) 180 | { 181 | camel = this.util.toCamel(rule); 182 | 183 | toElem.style[camel] = css[rule]; 184 | 185 | // Set the from/start state 186 | from[rule] = (camel == 'WebkitTransform') ? new WebKitCSSMatrix( sanityCheckTransformString( window.getComputedStyle(elem)['-webkit-transform'] ) ) : window.getComputedStyle(elem)[rule]; 187 | 188 | // Set the to/end state 189 | to[rule] = (camel == 'WebkitTransform') ? new WebKitCSSMatrix( sanityCheckTransformString( toElem.style.WebkitTransform ) ) : toElem.style[camel]; 190 | 191 | // Shifty requires numeric values to be a number rather than a string (e.g. for opacity) 192 | from[rule] = from[rule] == (val = parseInt(from[rule], 10)) ? val : from[rule]; 193 | to[rule] = to[rule] == (val = parseInt(from[rule], 10)) ? val : to[rule]; 194 | 195 | // Update the cacheKey 196 | cacheKey += ';' + rule + ':' + from[rule] + '->' + to[rule]; 197 | } 198 | 199 | // Check the cache to save expensive calculations 200 | if(Morf.cache[cacheKey]) 201 | { 202 | this.css = Morf.cache[cacheKey].css; 203 | animName = Morf.cache[cacheKey].name; 204 | } 205 | else 206 | { 207 | // Produce decompositions of matrices here so we don't have to redo it on each iteration 208 | // Decomposing the matrix is expensive so we need to minimise these requests 209 | if(from['-webkit-transform']) 210 | { 211 | m1 = from['-webkit-transform'].decompose(); 212 | m2 = to['-webkit-transform'].decompose(); 213 | } 214 | 215 | // Produce style keyframes 216 | for(progress = 0; progress <= 1; progress += options.increment) { 217 | // Use Shifty.js to work out the interpolated CSS state 218 | frame = Tweenable.util.interpolate(from, to, progress, options.timingFunction); 219 | 220 | // Work out the interpolated matrix transformation 221 | if(m1 !== null && m2 !== null) 222 | frame['-webkit-transform'] = m1.tween(m2, progress, Tweenable.prototype.formula[options.timingFunction]); 223 | 224 | keyframes[parseInt(progress*100, 10)+'%'] = frame; 225 | } 226 | 227 | // Ensure the last frame has been added 228 | keyframes['100%'] = to; 229 | 230 | // Add the new animation to the document 231 | this.css = createAnimationCSS(keyframes, animName); 232 | addKeyframeRule(this.css); 233 | 234 | Morf.cache[cacheKey] = {css: this.css, name: animName}; 235 | } 236 | 237 | // Set the final position state as this should be a transition not an animation & the element should end in the 'to' state 238 | for(rule in to) 239 | elem.style[this.util.toCamel(rule)] = to[rule]; 240 | 241 | // Trigger the animation 242 | elem.addEventListener('webkitAnimationEnd', animationEndListener, true); 243 | elem.style.webkitAnimationDuration = options.duration; 244 | elem.style.webkitAnimationTimingFunction = 'linear'; 245 | elem.style.webkitAnimationName = animName; 246 | 247 | // Print the animation to the console if the debug switch is given 248 | if(options.debug && window.console && window.console.log) 249 | console.log(this.css); 250 | }; 251 | 252 | 253 | /** 254 | * Convenience function for triggering a transition 255 | * @param {HTMLDom} elem The element to apply the transition to 256 | * @param {Object} css Key value pair of CSS properties 257 | * @param {Object} opts Additional configuration options 258 | * 259 | * Configuration options 260 | * - timingFunction: {String} Name of the easing function to perform 261 | * - duration: {integer} Duration in ms 262 | * - increment: {float} How frequently to generate keyframes (Defaults to 0.01, which is every 1%) 263 | * - debug: {Boolean} Should the generated CSS Animation be printed to the console 264 | * 265 | * @returns {Morf} An instance of the Morf object 266 | */ 267 | 268 | Morf.transition = function(elem, css, opts){ 269 | return new Morf(elem, css, opts); 270 | }; 271 | 272 | /** 273 | * Object to cache generated animations 274 | */ 275 | Morf.cache = {}; 276 | 277 | /** 278 | * Current version 279 | */ 280 | Morf.version = '0.1.5'; 281 | 282 | // Utilities Placeholder 283 | Morf.prototype.util = {}; 284 | 285 | 286 | /** 287 | * Converts a DOM style string to CSS property name 288 | * @param {String} str A DOM style string 289 | * @returns {String} CSS property name 290 | */ 291 | 292 | Morf.prototype.util.toDash = function(str){ 293 | str = str.replace(/([A-Z])/g, function($1){return "-"+$1.toLowerCase();}); 294 | return /^webkit/.test(str) ? '-'+str : str; 295 | }; 296 | 297 | 298 | /** 299 | * Converts a CSS property name to DOM style string 300 | * @param {String} str A CSS property name 301 | * @returns {String} DOM style string 302 | */ 303 | 304 | Morf.prototype.util.toCamel = function(str){ 305 | return str.replace(/(\-[a-z])/g, function($1){return $1.toUpperCase().replace('-','');}); 306 | }; 307 | 308 | /** 309 | * WebKitCSSMatrix Extensions 310 | * 311 | * Copyright 2011, Joe Lambert (http://www.joelambert.co.uk) 312 | * Free to use under the MIT license. 313 | * http://joelambert.mit-license.org/ 314 | */ 315 | 316 | // Wrap this functionality up to prevent poluting the global namespace 317 | (function(){ 318 | 319 | 320 | /** 321 | * A 4 dimensional vector 322 | * @author Joe Lambert 323 | * @constructor 324 | */ 325 | 326 | var Vector4 = function(x, y, z, w) 327 | { 328 | this.x = x ? x : 0; 329 | this.y = y ? y : 0; 330 | this.z = z ? z : 0; 331 | this.w = w ? w : 0; 332 | 333 | 334 | /** 335 | * Ensure that values are not undefined 336 | * @author Joe Lambert 337 | * @returns null 338 | */ 339 | 340 | this.checkValues = function() { 341 | this.x = this.x ? this.x : 0; 342 | this.y = this.y ? this.y : 0; 343 | this.z = this.z ? this.z : 0; 344 | this.w = this.w ? this.w : 0; 345 | }; 346 | 347 | 348 | /** 349 | * Get the length of the vector 350 | * @author Joe Lambert 351 | * @returns {float} 352 | */ 353 | 354 | this.length = function() { 355 | this.checkValues(); 356 | return Math.sqrt(this.x*this.x + this.y*this.y + this.z*this.z); 357 | }; 358 | 359 | 360 | /** 361 | * Get a normalised representation of the vector 362 | * @author Joe Lambert 363 | * @returns {Vector4} 364 | */ 365 | 366 | this.normalise = function() { 367 | var len = this.length(), 368 | v = new Vector4(this.x / len, this.y / len, this.z / len); 369 | 370 | return v; 371 | }; 372 | 373 | 374 | /** 375 | * Vector Dot-Product 376 | * @param {Vector4} v The second vector to apply the product to 377 | * @author Joe Lambert 378 | * @returns {float} The Dot-Product of this and v. 379 | */ 380 | 381 | this.dot = function(v) { 382 | return this.x*v.x + this.y*v.y + this.z*v.z + this.w*v.w; 383 | }; 384 | 385 | 386 | /** 387 | * Vector Cross-Product 388 | * @param {Vector4} v The second vector to apply the product to 389 | * @author Joe Lambert 390 | * @returns {Vector4} The Cross-Product of this and v. 391 | */ 392 | 393 | this.cross = function(v) { 394 | return new Vector4(this.y*v.z - this.z*v.y, this.z*v.x - this.x*v.z, this.x*v.y - this.y*v.x); 395 | }; 396 | 397 | 398 | /** 399 | * Helper function required for matrix decomposition 400 | * A Javascript implementation of pseudo code available from http://www.w3.org/TR/css3-2d-transforms/#matrix-decomposition 401 | * @param {Vector4} aPoint A 3D point 402 | * @param {float} ascl 403 | * @param {float} bscl 404 | * @author Joe Lambert 405 | * @returns {Vector4} 406 | */ 407 | 408 | this.combine = function(aPoint, ascl, bscl) { 409 | return new Vector4( (ascl * this.x) + (bscl * aPoint.x), 410 | (ascl * this.y) + (bscl * aPoint.y), 411 | (ascl * this.z) + (bscl * aPoint.z) ); 412 | } 413 | }; 414 | 415 | 416 | /** 417 | * Object containing the decomposed components of a matrix 418 | * @author Joe Lambert 419 | * @constructor 420 | */ 421 | 422 | var CSSMatrixDecomposed = function(obj) { 423 | obj === undefined ? obj = {} : null; 424 | var components = {perspective: null, translate: null, skew: null, scale: null, rotate: null}; 425 | 426 | for(var i in components) 427 | this[i] = obj[i] ? obj[i] : new Vector4(); 428 | 429 | /** 430 | * Tween between two decomposed matrices 431 | * @param {CSSMatrixDecomposed} dm The destination decomposed matrix 432 | * @param {float} progress A float value between 0-1, representing the percentage of completion 433 | * @param {function} fn An easing function following the prototype function(pos){} 434 | * @author Joe Lambert 435 | * @returns {WebKitCSSMatrix} A new matrix for the tweened state 436 | */ 437 | 438 | this.tween = function(dm, progress, fn) { 439 | if(fn === undefined) 440 | fn = function(pos) {return pos;}; // Default to a linear easing 441 | 442 | if(!dm) 443 | dm = new CSSMatrixDecomposed(new WebKitCSSMatrix().decompose()); 444 | 445 | var r = new CSSMatrixDecomposed(), 446 | i = index = null, 447 | trans = ''; 448 | 449 | progress = fn(progress); 450 | 451 | for(index in components) 452 | for(i in {x:'x', y:'y', z:'z', w:'w'}) 453 | r[index][i] = (this[index][i] + (dm[index][i] - this[index][i]) * progress ).toFixed(5); 454 | 455 | trans = 'matrix3d(1,0,0,0, 0,1,0,0, 0,0,1,0, '+r.perspective.x+', '+r.perspective.y+', '+r.perspective.z+', '+r.perspective.w+') ' + 456 | 'translate3d('+r.translate.x+'px, '+r.translate.y+'px, '+r.translate.z+'px) ' + 457 | 'rotateX('+r.rotate.x+'rad) rotateY('+r.rotate.y+'rad) rotateZ('+r.rotate.z+'rad) ' + 458 | 'matrix3d(1,0,0,0, 0,1,0,0, 0,'+r.skew.z+',1,0, 0,0,0,1) ' + 459 | 'matrix3d(1,0,0,0, 0,1,0,0, '+r.skew.y+',0,1,0, 0,0,0,1) ' + 460 | 'matrix3d(1,0,0,0, '+r.skew.x+',1,0,0, 0,0,1,0, 0,0,0,1) ' + 461 | 'scale3d('+r.scale.x+', '+r.scale.y+', '+r.scale.z+')'; 462 | 463 | try { r = new WebKitCSSMatrix(trans); return r; } 464 | catch(e) { console.error('Invalid matrix string: '+trans); return '' }; 465 | }; 466 | }; 467 | 468 | 469 | /** 470 | * Tween between two matrices 471 | * @param {WebKitCSSMatrix} matrix The destination matrix 472 | * @param {float} progress A float value between 0-1, representing the percentage of completion 473 | * @param {function} fn An easing function following the prototype function(pos){} 474 | * @author Joe Lambert 475 | * @returns {WebKitCSSMatrix} A new matrix for the tweened state 476 | */ 477 | 478 | WebKitCSSMatrix.prototype.tween = function(matrix, progress, fn) { 479 | if(fn === undefined) 480 | fn = function(pos) {return pos;}; // Default to a linear easing 481 | 482 | var m = new WebKitCSSMatrix, 483 | m1 = this.decompose(), 484 | m2 = matrix.decompose(), 485 | r = m.decompose() 486 | trans = '', 487 | index = i = null; 488 | 489 | // Tween between the two decompositions 490 | return m1.tween(m2, progress, fn); 491 | }; 492 | 493 | 494 | /** 495 | * Transform a Vector4 object using the current matrix 496 | * @param {Vector4} v The vector to transform 497 | * @author Joe Lambert 498 | * @returns {Vector4} The transformed vector 499 | */ 500 | 501 | WebKitCSSMatrix.prototype.transformVector = function(v) { 502 | // TODO: Do we need to mod this for Vector4? 503 | return new Vector4( this.m11*v.x + this.m12*v.y + this.m13*v.z, 504 | this.m21*v.x + this.m22*v.y + this.m23*v.z, 505 | this.m31*v.x + this.m32*v.y + this.m33*v.z ); 506 | }; 507 | 508 | 509 | /** 510 | * Transposes the matrix 511 | * @author Joe Lambert 512 | * @returns {WebKitCSSMatrix} The transposed matrix 513 | */ 514 | 515 | WebKitCSSMatrix.prototype.transpose = function() { 516 | var matrix = new WebKitCSSMatrix(), n = m = 0; 517 | 518 | for (n = 0; n <= 4-2; n++) 519 | { 520 | for (m = n + 1; m <= 4-1; m++) 521 | { 522 | matrix['m'+(n+1)+(m+1)] = this['m'+(m+1)+(n+1)]; 523 | matrix['m'+(m+1)+(n+1)] = this['m'+(n+1)+(m+1)]; 524 | } 525 | } 526 | 527 | return matrix; 528 | }; 529 | 530 | 531 | /** 532 | * Calculates the determinant 533 | * @author Joe Lambert 534 | * @returns {float} The determinant of the matrix 535 | */ 536 | 537 | WebKitCSSMatrix.prototype.determinant = function() { 538 | return this.m14 * this.m23 * this.m32 * this.m41-this.m13 * this.m24 * this.m32 * this.m41 - 539 | this.m14 * this.m22 * this.m33 * this.m41+this.m12 * this.m24 * this.m33 * this.m41 + 540 | this.m13 * this.m22 * this.m34 * this.m41-this.m12 * this.m23 * this.m34 * this.m41 - 541 | this.m14 * this.m23 * this.m31 * this.m42+this.m13 * this.m24 * this.m31 * this.m42 + 542 | this.m14 * this.m21 * this.m33 * this.m42-this.m11 * this.m24 * this.m33 * this.m42 - 543 | this.m13 * this.m21 * this.m34 * this.m42+this.m11 * this.m23 * this.m34 * this.m42 + 544 | this.m14 * this.m22 * this.m31 * this.m43-this.m12 * this.m24 * this.m31 * this.m43 - 545 | this.m14 * this.m21 * this.m32 * this.m43+this.m11 * this.m24 * this.m32 * this.m43 + 546 | this.m12 * this.m21 * this.m34 * this.m43-this.m11 * this.m22 * this.m34 * this.m43 - 547 | this.m13 * this.m22 * this.m31 * this.m44+this.m12 * this.m23 * this.m31 * this.m44 + 548 | this.m13 * this.m21 * this.m32 * this.m44-this.m11 * this.m23 * this.m32 * this.m44 - 549 | this.m12 * this.m21 * this.m33 * this.m44+this.m11 * this.m22 * this.m33 * this.m44; 550 | }; 551 | 552 | 553 | /** 554 | * Decomposes the matrix into its component parts. 555 | * A Javascript implementation of the pseudo code available from http://www.w3.org/TR/css3-2d-transforms/#matrix-decomposition 556 | * @author Joe Lambert 557 | * @returns {Object} An object with each of the components of the matrix (perspective, translate, skew, scale, rotate) or identity matrix on failure 558 | */ 559 | 560 | WebKitCSSMatrix.prototype.decompose = function() { 561 | var matrix = new WebKitCSSMatrix(this.toString()), 562 | perspectiveMatrix = rightHandSide = inversePerspectiveMatrix = transposedInversePerspectiveMatrix = 563 | perspective = translate = row = i = scale = skew = pdum3 = rotate = null; 564 | 565 | if (matrix.m33 == 0) 566 | return new CSSMatrixDecomposed(new WebKitCSSMatrix().decompose()); // Return the identity matrix 567 | 568 | // Normalize the matrix. 569 | for (i = 1; i <= 4; i++) 570 | for (j = 1; j <= 4; j++) 571 | matrix['m'+i+j] /= matrix.m44; 572 | 573 | // perspectiveMatrix is used to solve for perspective, but it also provides 574 | // an easy way to test for singularity of the upper 3x3 component. 575 | perspectiveMatrix = matrix; 576 | 577 | for (i = 1; i <= 3; i++) 578 | perspectiveMatrix['m'+i+'4'] = 0; 579 | 580 | perspectiveMatrix.m44 = 1; 581 | 582 | if (perspectiveMatrix.determinant() == 0) 583 | return new CSSMatrixDecomposed(new WebKitCSSMatrix().decompose()); // Return the identity matrix 584 | 585 | // First, isolate perspective. 586 | if (matrix.m14 != 0 || matrix.m24 != 0 || matrix.m34 != 0) 587 | { 588 | // rightHandSide is the right hand side of the equation. 589 | rightHandSide = new Vector4(matrix.m14, matrix.m24, matrix.m34, matrix.m44); 590 | 591 | // Solve the equation by inverting perspectiveMatrix and multiplying 592 | // rightHandSide by the inverse. 593 | inversePerspectiveMatrix = perspectiveMatrix.inverse(); 594 | transposedInversePerspectiveMatrix = inversePerspectiveMatrix.transpose(); 595 | perspective = transposedInversePerspectiveMatrix.transformVector(rightHandSide); 596 | 597 | // Clear the perspective partition 598 | matrix.m14 = matrix.m24 = matrix.m34 = 0; 599 | matrix.m44 = 1; 600 | } 601 | else 602 | { 603 | // No perspective. 604 | perspective = new Vector4(0,0,0,1); 605 | } 606 | 607 | // Next take care of translation 608 | translate = new Vector4(matrix.m41, matrix.m42, matrix.m43); 609 | 610 | matrix.m41 = 0; 611 | matrix.m42 = 0; 612 | matrix.m43 = 0; 613 | 614 | // Now get scale and shear. 'row' is a 3 element array of 3 component vectors 615 | row = [ 616 | new Vector4(), new Vector4(), new Vector4() 617 | ]; 618 | 619 | for (i = 1; i <= 3; i++) 620 | { 621 | row[i-1].x = matrix['m'+i+'1']; 622 | row[i-1].y = matrix['m'+i+'2']; 623 | row[i-1].z = matrix['m'+i+'3']; 624 | } 625 | 626 | // Compute X scale factor and normalize first row. 627 | scale = new Vector4(); 628 | skew = new Vector4(); 629 | 630 | scale.x = row[0].length(); 631 | row[0] = row[0].normalise(); 632 | 633 | // Compute XY shear factor and make 2nd row orthogonal to 1st. 634 | skew.x = row[0].dot(row[1]); 635 | row[1] = row[1].combine(row[0], 1.0, -skew.x); 636 | 637 | // Now, compute Y scale and normalize 2nd row. 638 | scale.y = row[1].length(); 639 | row[1] = row[1].normalise(); 640 | skew.x /= scale.y; 641 | 642 | // Compute XZ and YZ shears, orthogonalize 3rd row 643 | skew.y = row[0].dot(row[2]); 644 | row[2] = row[2].combine(row[0], 1.0, -skew.y); 645 | skew.z = row[1].dot(row[2]); 646 | row[2] = row[2].combine(row[1], 1.0, -skew.z); 647 | 648 | // Next, get Z scale and normalize 3rd row. 649 | scale.z = row[2].length(); 650 | row[2] = row[2].normalise(); 651 | skew.y /= scale.z; 652 | skew.y /= scale.z; 653 | 654 | // At this point, the matrix (in rows) is orthonormal. 655 | // Check for a coordinate system flip. If the determinant 656 | // is -1, then negate the matrix and the scaling factors. 657 | pdum3 = row[1].cross(row[2]) 658 | if (row[0].dot(pdum3) < 0) 659 | { 660 | for (i = 0; i < 3; i++) 661 | { 662 | scale.x *= -1; 663 | row[i].x *= -1; 664 | row[i].y *= -1; 665 | row[i].z *= -1; 666 | } 667 | } 668 | 669 | // Now, get the rotations out 670 | rotate = new Vector4(); 671 | rotate.y = Math.asin(-row[0].z); 672 | if (Math.cos(rotate.y) != 0) 673 | { 674 | rotate.x = Math.atan2(row[1].z, row[2].z); 675 | rotate.z = Math.atan2(row[0].y, row[0].x); 676 | } 677 | else 678 | { 679 | rotate.x = Math.atan2(-row[2].x, row[1].y); 680 | rotate.z = 0; 681 | } 682 | 683 | return new CSSMatrixDecomposed({ 684 | perspective: perspective, 685 | translate: translate, 686 | skew: skew, 687 | scale: scale, 688 | rotate: rotate 689 | }); 690 | }; 691 | 692 | 693 | })(); 694 | 695 | 696 | /** 697 | Mifty - A custom build of Shifty for use with Morf.js. 698 | By Jeremy Kahn - jeremyckahn@gmail.com 699 | v0.4.1 700 | 701 | For instructions on how to use Shifty, please consult the README: https://github.com/jeremyckahn/shifty/blob/master/README.md 702 | 703 | MIT Lincense. This code free to use, modify, distribute and enjoy. 704 | 705 | */ 706 | 707 | (function(e){function a(){return+new Date}function b(a,c){for(var j in a)a.hasOwnProperty(j)&&c(a,j)}function h(a,c){b(c,function(c,f){a[f]=c[f]});return a}function k(a,c){b(c,function(c,f){typeof a[f]==="undefined"&&(a[f]=c[f])});return a}function i(a,c,j){var f,a=(a-c.timestamp)/c.duration;for(f in j.current)j.current.hasOwnProperty(f)&&c.to.hasOwnProperty(f)&&(j.current[f]=c.originalState[f]+(c.to[f]-c.originalState[f])*c.easingFunc(a));return j.current}function n(a,c,j,f){var b;for(b=0;b=0;b++)this._hook[a][b]===c&&this._hook[a].splice(b,1);else this._hook[a]=[]};g.prototype.filter={};g.util={now:a,each:b,tweenProps:i,applyFilter:l,simpleCopy:h};g.prototype.formula={linear:function(a){return a}};e.Tweenable=g})(this); 714 | (function(e){e.Tweenable.util.simpleCopy(e.Tweenable.prototype.formula,{easeInQuad:function(a){return Math.pow(a,2)},easeOutQuad:function(a){return-(Math.pow(a-1,2)-1)},easeInOutQuad:function(a){if((a/=0.5)<1)return 0.5*Math.pow(a,2);return-0.5*((a-=2)*a-2)},easeInCubic:function(a){return Math.pow(a,3)},easeOutCubic:function(a){return Math.pow(a-1,3)+1},easeInOutCubic:function(a){if((a/=0.5)<1)return 0.5*Math.pow(a,3);return 0.5*(Math.pow(a-2,3)+2)},easeInQuart:function(a){return Math.pow(a,4)},easeOutQuart:function(a){return-(Math.pow(a- 715 | 1,4)-1)},easeInOutQuart:function(a){if((a/=0.5)<1)return 0.5*Math.pow(a,4);return-0.5*((a-=2)*Math.pow(a,3)-2)},easeInQuint:function(a){return Math.pow(a,5)},easeOutQuint:function(a){return Math.pow(a-1,5)+1},easeInOutQuint:function(a){if((a/=0.5)<1)return 0.5*Math.pow(a,5);return 0.5*(Math.pow(a-2,5)+2)},easeInSine:function(a){return-Math.cos(a*(Math.PI/2))+1},easeOutSine:function(a){return Math.sin(a*(Math.PI/2))},easeInOutSine:function(a){return-0.5*(Math.cos(Math.PI*a)-1)},easeInExpo:function(a){return a== 716 | 0?0:Math.pow(2,10*(a-1))},easeOutExpo:function(a){return a==1?1:-Math.pow(2,-10*a)+1},easeInOutExpo:function(a){if(a==0)return 0;if(a==1)return 1;if((a/=0.5)<1)return 0.5*Math.pow(2,10*(a-1));return 0.5*(-Math.pow(2,-10*--a)+2)},easeInCirc:function(a){return-(Math.sqrt(1-a*a)-1)},easeOutCirc:function(a){return Math.sqrt(1-Math.pow(a-1,2))},easeInOutCirc:function(a){if((a/=0.5)<1)return-0.5*(Math.sqrt(1-a*a)-1);return 0.5*(Math.sqrt(1-(a-=2)*a)+1)},easeOutBounce:function(a){return a<1/2.75?7.5625* 717 | a*a:a<2/2.75?7.5625*(a-=1.5/2.75)*a+0.75:a<2.5/2.75?7.5625*(a-=2.25/2.75)*a+0.9375:7.5625*(a-=2.625/2.75)*a+0.984375},easeInBack:function(a){return a*a*(2.70158*a-1.70158)},easeOutBack:function(a){return(a-=1)*a*(2.70158*a+1.70158)+1},easeInOutBack:function(a){var b=1.70158;if((a/=0.5)<1)return 0.5*a*a*(((b*=1.525)+1)*a-b);return 0.5*((a-=2)*a*(((b*=1.525)+1)*a+b)+2)},elastic:function(a){return-1*Math.pow(4,-8*a)*Math.sin((a*6-1)*2*Math.PI/2)+1},swingFromTo:function(a){var b=1.70158;return(a/=0.5)< 718 | 1?0.5*a*a*(((b*=1.525)+1)*a-b):0.5*((a-=2)*a*(((b*=1.525)+1)*a+b)+2)},swingFrom:function(a){return a*a*(2.70158*a-1.70158)},swingTo:function(a){return(a-=1)*a*(2.70158*a+1.70158)+1},bounce:function(a){return a<1/2.75?7.5625*a*a:a<2/2.75?7.5625*(a-=1.5/2.75)*a+0.75:a<2.5/2.75?7.5625*(a-=2.25/2.75)*a+0.9375:7.5625*(a-=2.625/2.75)*a+0.984375},bouncePast:function(a){return a<1/2.75?7.5625*a*a:a<2/2.75?2-(7.5625*(a-=1.5/2.75)*a+0.75):a<2.5/2.75?2-(7.5625*(a-=2.25/2.75)*a+0.9375):2-(7.5625*(a-=2.625/2.75)* 719 | a+0.984375)},easeFromTo:function(a){if((a/=0.5)<1)return 0.5*Math.pow(a,4);return-0.5*((a-=2)*Math.pow(a,3)-2)},easeFrom:function(a){return Math.pow(a,4)},easeTo:function(a){return Math.pow(a,0.25)}})})(this); 720 | (function(e){if(e.Tweenable)e.Tweenable.util.interpolate=function(a,b,h,k){var i;if(a&&a.from)b=a.to,h=a.position,k=a.easing,a=a.from;i=e.Tweenable.util.simpleCopy({},a);e.Tweenable.util.applyFilter("tweenCreated",i,[i,a,b]);e.Tweenable.util.applyFilter("beforeTween",i,[i,a,b]);h=e.Tweenable.util.tweenProps(h,{originalState:a,to:b,timestamp:0,duration:1,easingFunc:e.Tweenable.prototype.formula[k]||e.Tweenable.prototype.formula.linear},{current:i});e.Tweenable.util.applyFilter("afterTween",h,[h,a, 721 | b]);return h},e.Tweenable.prototype.interpolate=function(a,b,h){a=e.Tweenable.util.interpolate(this.get(),a,b,h);this.set(a);return a}})(this); 722 | 723 | /** 724 | * @preserve 725 | * Extra easing functions borrowed from scripty2 (c) 2005-2010 Thomas Fuchs (MIT Licence) 726 | * https://raw.github.com/madrobby/scripty2/master/src/effects/transitions/transitions.js 727 | */ 728 | 729 | (function(){ 730 | var scripty2 = { 731 | spring: function(pos) { 732 | return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); 733 | }, 734 | 735 | sinusoidal: function(pos) { 736 | return (-Math.cos(pos*Math.PI)/2) + 0.5; 737 | } 738 | }; 739 | 740 | // Load the Scripty2 functions 741 | for(var t in scripty2) 742 | Tweenable.prototype.formula[t] = scripty2[t]; 743 | })(); 744 | 745 | 746 | -------------------------------------------------------------------------------- /js/morf.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | Morf v0.1.5 3 | http://www.joelambert.co.uk/morf 4 | 5 | Copyright 2011, Joe Lambert. 6 | Free to use under the MIT license. 7 | http://www.opensource.org/licenses/mit-license.php 8 | 9 | Extra easing functions borrowed from scripty2 (c) 2005-2010 Thomas Fuchs (MIT Licence) 10 | https://raw.github.com/madrobby/scripty2/master/src/effects/transitions/transitions.js 11 | */ 12 | var Morf=function(d,a,c){var f={},l={};document.createElement("div");var o=document.createElement("div"),e={timingFunction:"ease",duration:null,increment:0.01,debug:false,optimise:true,decimalPlaces:5},q=rule=ruleName=camel=m1=m2=progress=frame=rule=transEvent=val=null,k="",p=this,b={},g="anim"+(new Date).getTime(),h=function(){d.removeEventListener("webkitAnimationEnd",h,true);transEvent=document.createEvent("Event");transEvent.initEvent("webkitTransitionEnd",true,true);d.dispatchEvent(transEvent); 13 | d.style.webkitTransitionTimingFunction=null;d.style.webkitTransitionDuration=0;e.callback&&e.callback(d)},n=function(t){if(document.styleSheets&&document.styleSheets.length)document.styleSheets[0].insertRule(t,0);else{var u=document.createElement("style");u.innerHTML=t;document.head.appendChild(u)}},s=function(t,u){var r="@-webkit-keyframes "+u+" {\n",w=pos=rule=null,v="";for(pos in t){w=t[pos];v="\t"+pos+" {\n";for(rule in w)v+="\t\t"+p.util.toDash(rule)+": "+w[rule]+";\n";v+="\t}\n\n";r+=v}if(e.optimise){r= 14 | r+" }";w=void 0;w=typeof e.decimalPlaces=="number"?e.decimalPlaces:5;v=r.match(/[0-9\.]+/gm);var x=0;if(v)for(x=0;x"+l[rule]}if(Morf.cache[k]){this.css=Morf.cache[k].css;g=Morf.cache[k].name}else{if(f["-webkit-transform"]){m1=f["-webkit-transform"].decompose();m2=l["-webkit-transform"].decompose()}for(progress=0;progress<=1;progress+=e.increment){frame=Tweenable.util.interpolate(f,l,progress,e.timingFunction);if(m1!==null&&m2!==null)frame["-webkit-transform"]=m1.tween(m2,progress,Tweenable.prototype.formula[e.timingFunction]);b[parseInt(progress* 18 | 100,10)+"%"]=frame}b["100%"]=l;this.css=s(b,g);n(this.css);Morf.cache[k]={css:this.css,name:g}}for(rule in l)d.style[this.util.toCamel(rule)]=l[rule];d.addEventListener("webkitAnimationEnd",h,true);d.style.webkitAnimationDuration=e.duration;d.style.webkitAnimationTimingFunction="linear";d.style.webkitAnimationName=g;e.debug&&window.console&&window.console.log&&console.log(this.css)}};Morf.transition=function(d,a,c){return new Morf(d,a,c)};Morf.cache={};Morf.version="0.1.5";Morf.prototype.util={}; 19 | Morf.prototype.util.toDash=function(d){d=d.replace(/([A-Z])/g,function(a){return"-"+a.toLowerCase()});return/^webkit/.test(d)?"-"+d:d};Morf.prototype.util.toCamel=function(d){return d.replace(/(\-[a-z])/g,function(a){return a.toUpperCase().replace("-","")})}; 20 | (function(){var d=function(c,f,l,o){this.x=c?c:0;this.y=f?f:0;this.z=l?l:0;this.w=o?o:0;this.checkValues=function(){this.x=this.x?this.x:0;this.y=this.y?this.y:0;this.z=this.z?this.z:0;this.w=this.w?this.w:0};this.length=function(){this.checkValues();return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z)};this.normalise=function(){var e=this.length();return new d(this.x/e,this.y/e,this.z/e)};this.dot=function(e){return this.x*e.x+this.y*e.y+this.z*e.z+this.w*e.w};this.cross=function(e){return new d(this.y* 21 | e.z-this.z*e.y,this.z*e.x-this.x*e.z,this.x*e.y-this.y*e.x)};this.combine=function(e,q,k){return new d(q*this.x+k*e.x,q*this.y+k*e.y,q*this.z+k*e.z)}},a=function(c){c===undefined&&(c={});var f={perspective:null,translate:null,skew:null,scale:null,rotate:null},l;for(l in f)this[l]=c[l]?c[l]:new d;this.tween=function(o,e,q){if(q===undefined)q=function(h){return h};o||(o=new a((new WebKitCSSMatrix).decompose()));var k=new a,p=index=null,b="";e=q(e);for(index in f)for(p in{x:"x",y:"y",z:"z",w:"w"})k[index][p]= 22 | (this[index][p]+(o[index][p]-this[index][p])*e).toFixed(5);b="matrix3d(1,0,0,0, 0,1,0,0, 0,0,1,0, "+k.perspective.x+", "+k.perspective.y+", "+k.perspective.z+", "+k.perspective.w+") translate3d("+k.translate.x+"px, "+k.translate.y+"px, "+k.translate.z+"px) rotateX("+k.rotate.x+"rad) rotateY("+k.rotate.y+"rad) rotateZ("+k.rotate.z+"rad) matrix3d(1,0,0,0, 0,1,0,0, 0,"+k.skew.z+",1,0, 0,0,0,1) matrix3d(1,0,0,0, 0,1,0,0, "+k.skew.y+",0,1,0, 0,0,0,1) matrix3d(1,0,0,0, "+k.skew.x+",1,0,0, 0,0,1,0, 0,0,0,1) scale3d("+ 23 | k.scale.x+", "+k.scale.y+", "+k.scale.z+")";try{return k=new WebKitCSSMatrix(b)}catch(g){console.error("Invalid matrix string: "+b);return""}}};WebKitCSSMatrix.prototype.tween=function(c,f,l){if(l===undefined)l=function(q){return q};var o=new WebKitCSSMatrix,e=this.decompose();c=c.decompose();o.decompose();trans="";index=i=null;return e.tween(c,f,l)};WebKitCSSMatrix.prototype.transformVector=function(c){return new d(this.m11*c.x+this.m12*c.y+this.m13*c.z,this.m21*c.x+this.m22*c.y+this.m23*c.z,this.m31* 24 | c.x+this.m32*c.y+this.m33*c.z)};WebKitCSSMatrix.prototype.transpose=function(){var c=new WebKitCSSMatrix,f=m=0;for(f=0;f<=2;f++)for(m=f+1;m<=3;m++){c["m"+(f+1)+(m+1)]=this["m"+(m+1)+(f+1)];c["m"+(m+1)+(f+1)]=this["m"+(f+1)+(m+1)]}return c};WebKitCSSMatrix.prototype.determinant=function(){return this.m14*this.m23*this.m32*this.m41-this.m13*this.m24*this.m32*this.m41-this.m14*this.m22*this.m33*this.m41+this.m12*this.m24*this.m33*this.m41+this.m13*this.m22*this.m34*this.m41-this.m12*this.m23*this.m34* 25 | this.m41-this.m14*this.m23*this.m31*this.m42+this.m13*this.m24*this.m31*this.m42+this.m14*this.m21*this.m33*this.m42-this.m11*this.m24*this.m33*this.m42-this.m13*this.m21*this.m34*this.m42+this.m11*this.m23*this.m34*this.m42+this.m14*this.m22*this.m31*this.m43-this.m12*this.m24*this.m31*this.m43-this.m14*this.m21*this.m32*this.m43+this.m11*this.m24*this.m32*this.m43+this.m12*this.m21*this.m34*this.m43-this.m11*this.m22*this.m34*this.m43-this.m13*this.m22*this.m31*this.m44+this.m12*this.m23*this.m31* 26 | this.m44+this.m13*this.m21*this.m32*this.m44-this.m11*this.m23*this.m32*this.m44-this.m12*this.m21*this.m33*this.m44+this.m11*this.m22*this.m33*this.m44};WebKitCSSMatrix.prototype.decompose=function(){var c=new WebKitCSSMatrix(this.toString()),f=rightHandSide=inversePerspectiveMatrix=transposedInversePerspectiveMatrix=perspective=translate=row=i=scale=skew=pdum3=rotate=null;if(c.m33==0)return new a((new WebKitCSSMatrix).decompose());for(i=1;i<=4;i++)for(j=1;j<=4;j++)c["m"+i+j]/=c.m44;f=c;for(i=1;i<= 27 | 3;i++)f["m"+i+"4"]=0;f.m44=1;if(f.determinant()==0)return new a((new WebKitCSSMatrix).decompose());if(c.m14!=0||c.m24!=0||c.m34!=0){rightHandSide=new d(c.m14,c.m24,c.m34,c.m44);inversePerspectiveMatrix=f.inverse();transposedInversePerspectiveMatrix=inversePerspectiveMatrix.transpose();perspective=transposedInversePerspectiveMatrix.transformVector(rightHandSide);c.m14=c.m24=c.m34=0;c.m44=1}else perspective=new d(0,0,0,1);translate=new d(c.m41,c.m42,c.m43);c.m41=0;c.m42=0;c.m43=0;row=[new d,new d,new d]; 28 | for(i=1;i<=3;i++){row[i-1].x=c["m"+i+"1"];row[i-1].y=c["m"+i+"2"];row[i-1].z=c["m"+i+"3"]}scale=new d;skew=new d;scale.x=row[0].length();row[0]=row[0].normalise();skew.x=row[0].dot(row[1]);row[1]=row[1].combine(row[0],1,-skew.x);scale.y=row[1].length();row[1]=row[1].normalise();skew.x/=scale.y;skew.y=row[0].dot(row[2]);row[2]=row[2].combine(row[0],1,-skew.y);skew.z=row[1].dot(row[2]);row[2]=row[2].combine(row[1],1,-skew.z);scale.z=row[2].length();row[2]=row[2].normalise();skew.y/=scale.z;skew.y/= 29 | scale.z;pdum3=row[1].cross(row[2]);if(row[0].dot(pdum3)<0)for(i=0;i<3;i++){scale.x*=-1;row[i].x*=-1;row[i].y*=-1;row[i].z*=-1}rotate=new d;rotate.y=Math.asin(-row[0].z);if(Math.cos(rotate.y)!=0){rotate.x=Math.atan2(row[1].z,row[2].z);rotate.z=Math.atan2(row[0].y,row[0].x)}else{rotate.x=Math.atan2(-row[2].x,row[1].y);rotate.z=0}return new a({perspective:perspective,translate:translate,skew:skew,scale:scale,rotate:rotate})}})(); 30 | (function(d){function a(){return+new Date}function c(b,g){for(var h in b)b.hasOwnProperty(h)&&g(b,h)}function f(b,g){c(g,function(h,n){b[n]=h[n]});return b}function l(b,g){c(g,function(h,n){typeof b[n]==="undefined"&&(b[n]=h[n])});return b}function o(b,g,h){var n;b=(b-g.timestamp)/g.duration;for(n in h.current)h.current.hasOwnProperty(n)&&g.to.hasOwnProperty(n)&&(h.current[n]=g.originalState[n]+(g.to[n]-g.originalState[n])*g.easingFunc(b));return h.current}function e(b,g,h,n){var s;for(s=0;s=0;h++)this._hook[b][h]===g&&this._hook[b].splice(h,1);else this._hook[b]=[]};p.prototype.filter={};p.util={now:a,each:c,tweenProps:o,applyFilter:q,simpleCopy:f};p.prototype.formula={linear:function(b){return b}};d.Tweenable=p})(this); 37 | (function(d){d.Tweenable.util.simpleCopy(d.Tweenable.prototype.formula,{easeInQuad:function(a){return Math.pow(a,2)},easeOutQuad:function(a){return-(Math.pow(a-1,2)-1)},easeInOutQuad:function(a){if((a/=0.5)<1)return 0.5*Math.pow(a,2);return-0.5*((a-=2)*a-2)},easeInCubic:function(a){return Math.pow(a,3)},easeOutCubic:function(a){return Math.pow(a-1,3)+1},easeInOutCubic:function(a){if((a/=0.5)<1)return 0.5*Math.pow(a,3);return 0.5*(Math.pow(a-2,3)+2)},easeInQuart:function(a){return Math.pow(a,4)},easeOutQuart:function(a){return-(Math.pow(a- 38 | 1,4)-1)},easeInOutQuart:function(a){if((a/=0.5)<1)return 0.5*Math.pow(a,4);return-0.5*((a-=2)*Math.pow(a,3)-2)},easeInQuint:function(a){return Math.pow(a,5)},easeOutQuint:function(a){return Math.pow(a-1,5)+1},easeInOutQuint:function(a){if((a/=0.5)<1)return 0.5*Math.pow(a,5);return 0.5*(Math.pow(a-2,5)+2)},easeInSine:function(a){return-Math.cos(a*(Math.PI/2))+1},easeOutSine:function(a){return Math.sin(a*(Math.PI/2))},easeInOutSine:function(a){return-0.5*(Math.cos(Math.PI*a)-1)},easeInExpo:function(a){return a== 39 | 0?0:Math.pow(2,10*(a-1))},easeOutExpo:function(a){return a==1?1:-Math.pow(2,-10*a)+1},easeInOutExpo:function(a){if(a==0)return 0;if(a==1)return 1;if((a/=0.5)<1)return 0.5*Math.pow(2,10*(a-1));return 0.5*(-Math.pow(2,-10*--a)+2)},easeInCirc:function(a){return-(Math.sqrt(1-a*a)-1)},easeOutCirc:function(a){return Math.sqrt(1-Math.pow(a-1,2))},easeInOutCirc:function(a){if((a/=0.5)<1)return-0.5*(Math.sqrt(1-a*a)-1);return 0.5*(Math.sqrt(1-(a-=2)*a)+1)},easeOutBounce:function(a){return a<1/2.75?7.5625* 40 | a*a:a<2/2.75?7.5625*(a-=1.5/2.75)*a+0.75:a<2.5/2.75?7.5625*(a-=2.25/2.75)*a+0.9375:7.5625*(a-=2.625/2.75)*a+0.984375},easeInBack:function(a){return a*a*(2.70158*a-1.70158)},easeOutBack:function(a){return(a-=1)*a*(2.70158*a+1.70158)+1},easeInOutBack:function(a){var c=1.70158;if((a/=0.5)<1)return 0.5*a*a*(((c*=1.525)+1)*a-c);return 0.5*((a-=2)*a*(((c*=1.525)+1)*a+c)+2)},elastic:function(a){return-1*Math.pow(4,-8*a)*Math.sin((a*6-1)*2*Math.PI/2)+1},swingFromTo:function(a){var c=1.70158;return(a/=0.5)< 41 | 1?0.5*a*a*(((c*=1.525)+1)*a-c):0.5*((a-=2)*a*(((c*=1.525)+1)*a+c)+2)},swingFrom:function(a){return a*a*(2.70158*a-1.70158)},swingTo:function(a){return(a-=1)*a*(2.70158*a+1.70158)+1},bounce:function(a){return a<1/2.75?7.5625*a*a:a<2/2.75?7.5625*(a-=1.5/2.75)*a+0.75:a<2.5/2.75?7.5625*(a-=2.25/2.75)*a+0.9375:7.5625*(a-=2.625/2.75)*a+0.984375},bouncePast:function(a){return a<1/2.75?7.5625*a*a:a<2/2.75?2-(7.5625*(a-=1.5/2.75)*a+0.75):a<2.5/2.75?2-(7.5625*(a-=2.25/2.75)*a+0.9375):2-(7.5625*(a-=2.625/2.75)* 42 | a+0.984375)},easeFromTo:function(a){if((a/=0.5)<1)return 0.5*Math.pow(a,4);return-0.5*((a-=2)*Math.pow(a,3)-2)},easeFrom:function(a){return Math.pow(a,4)},easeTo:function(a){return Math.pow(a,0.25)}})})(this); 43 | (function(d){if(d.Tweenable){d.Tweenable.util.interpolate=function(a,c,f,l){var o;if(a&&a.from){c=a.to;f=a.position;l=a.easing;a=a.from}o=d.Tweenable.util.simpleCopy({},a);d.Tweenable.util.applyFilter("tweenCreated",o,[o,a,c]);d.Tweenable.util.applyFilter("beforeTween",o,[o,a,c]);f=d.Tweenable.util.tweenProps(f,{originalState:a,to:c,timestamp:0,duration:1,easingFunc:d.Tweenable.prototype.formula[l]||d.Tweenable.prototype.formula.linear},{current:o});d.Tweenable.util.applyFilter("afterTween",f,[f, 44 | a,c]);return f};d.Tweenable.prototype.interpolate=function(a,c,f){a=d.Tweenable.util.interpolate(this.get(),a,c,f);this.set(a);return a}}})(this);(function(){var d={spring:function(c){return 1-Math.cos(c*4.5*Math.PI)*Math.exp(-c*6)},sinusoidal:function(c){return-Math.cos(c*Math.PI)/2+0.5}},a;for(a in d)Tweenable.prototype.formula[a]=d[a]})(); 45 | -------------------------------------------------------------------------------- /js/morf.noshifty.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve Morf v0.1.5 3 | * http://www.joelambert.co.uk/morf 4 | * 5 | * Copyright 2011, Joe Lambert. 6 | * Free to use under the MIT license. 7 | * http://www.opensource.org/licenses/mit-license.php 8 | */ 9 | 10 | var Morf = function(elem, css, opts) { 11 | var from = {}, to = {}, 12 | 13 | fromElem = document.createElement('div'), 14 | toElem = document.createElement('div'), 15 | 16 | options = { 17 | timingFunction: 'ease', 18 | duration: null, 19 | increment: 0.01, 20 | debug: false, 21 | optimise: true, // Whether the outputted CSS should be optimised 22 | decimalPlaces: 5 // How many decimal places to optimise the WebKitCSSMatrix output to 23 | }, 24 | 25 | // Define all other var's used in the function 26 | i = rule = ruleName = camel = m1 = m2 = progress = frame = rule = transEvent = val = null, cacheKey = '', 27 | 28 | // Setup a scoped reference to ourselves 29 | _this = this, 30 | 31 | keyframes = {}, 32 | 33 | // Create a unique name for this animation 34 | animName = 'anim'+(new Date().getTime()), 35 | 36 | 37 | /* --- Helper Functions ------------------------------------------------------------------- */ 38 | 39 | // Event listener for the webkitAnimationEnd Event 40 | animationEndListener = function(event){ 41 | elem.removeEventListener('webkitAnimationEnd', animationEndListener, true); 42 | 43 | // Dispatch a faux webkitTransitionEnd event to complete the appearance of this being a transition rather than an animation 44 | // TODO: Should we throw an event for each property changed? (event.propertyName = 'opacity' etc) 45 | transEvent = document.createEvent("Event"); 46 | transEvent.initEvent("webkitTransitionEnd", true, true); 47 | elem.dispatchEvent(transEvent); 48 | 49 | // Reset transition effects after use 50 | elem.style.webkitTransitionTimingFunction = null; 51 | elem.style.webkitTransitionDuration = 0; 52 | 53 | if (options.callback) { 54 | options.callback(elem); 55 | } 56 | }, 57 | 58 | // Adds the CSS to the current page 59 | addKeyframeRule = function(rule) { 60 | if (document.styleSheets && document.styleSheets.length) 61 | document.styleSheets[0].insertRule(rule, 0); 62 | else 63 | { 64 | var style = document.createElement('style'); 65 | style.innerHTML = rule; 66 | document.head.appendChild(style); 67 | } 68 | }, 69 | 70 | // Produces a CSS string representation of the Keyframes 71 | createAnimationCSS = function(kf, name) { 72 | var str = '@-webkit-keyframes '+name+' {\n', f = pos = rule = null, fStr = ''; 73 | 74 | for(pos in kf) 75 | { 76 | f = kf[pos]; 77 | fStr = '\t'+pos+' {\n'; 78 | 79 | for(rule in f) 80 | fStr += '\t\t'+_this.util.toDash(rule)+': '+f[rule]+';\n'; 81 | 82 | fStr += "\t}\n\n"; 83 | 84 | str += fStr; 85 | } 86 | 87 | return options.optimise ? optimiseCSS(str+' }') : str+' }'; 88 | }, 89 | 90 | // Replaces scale(0) with 0.0001 to get around the inability to these decompose matrix 91 | sanityCheckTransformString = function(str) { 92 | var scale = str.match(/scale[Y|X|Z]*\([0-9, ]*0[,0-9 ]*\)/g), 93 | i = 0; 94 | 95 | if(scale) 96 | { 97 | // There might be multiple scale() properties in the string 98 | for(i = 0; i < scale.length; i++) 99 | str = str.replace(scale[i], scale[i].replace(/([^0-9])0([^0.9])/g, "$10.0001$2")); 100 | } 101 | 102 | return str; 103 | }, 104 | 105 | // WebKitCSSMatrix toString() ALWAYS outputs numbers to 5 decimal places - this helps optimise the string 106 | optimiseCSS = function(str, decimalPlaces) { 107 | decimalPlaces = typeof options.decimalPlaces == 'number' ? options.decimalPlaces : 5; 108 | var matches = str.match(/[0-9\.]+/gm), 109 | i = 0; 110 | 111 | if(matches) 112 | { 113 | for(i = 0; i < matches.length; i++) 114 | str = str.replace(matches[i], parseFloat( parseFloat(matches[i]).toFixed(decimalPlaces))); 115 | } 116 | 117 | return str; 118 | }; 119 | 120 | /* --- Helper Functions End --------------------------------------------------------------- */ 121 | 122 | 123 | // Import the options 124 | for(i in opts) 125 | options[i] = opts[i]; 126 | 127 | 128 | // If timingFunction is a natively supported function then just trigger normal transition 129 | if( options.timingFunction === 'ease' || 130 | options.timingFunction === 'linear' || 131 | options.timingFunction === 'ease-in' || 132 | options.timingFunction === 'ease-out' || 133 | options.timingFunction === 'ease-in-out' || 134 | /^cubic-bezier/.test(options.timingFunction)) { 135 | 136 | elem.style.webkitTransitionDuration = options.duration; 137 | elem.style.webkitTransitionTimingFunction = options.timingFunction; 138 | 139 | // Listen for the transitionEnd event to fire the callback if needed 140 | var transitionEndListener = function(event) { 141 | elem.removeEventListener('webkitTransitionEnd', transitionEndListener, true); 142 | 143 | // Clean up after ourself 144 | elem.style.webkitTransitionDuration = 0; 145 | elem.style.webkitTransitionTimingFunction = null; 146 | 147 | if (options.callback) { 148 | // Delay execution to ensure the clean up CSS has taken effect 149 | setTimeout(function() { 150 | options.callback(elem); 151 | }, 10); 152 | } 153 | }; 154 | 155 | elem.addEventListener('webkitTransitionEnd', transitionEndListener, true); 156 | 157 | setTimeout(function() { 158 | for(rule in css) { 159 | camel = _this.util.toCamel(rule); 160 | elem.style[camel] = css[rule]; 161 | } 162 | }, 10); 163 | 164 | this.css = ''; 165 | 166 | return; 167 | } 168 | else 169 | { 170 | // Reset transition properties for this element 171 | elem.style.webkitTransitionTimingFunction = null; 172 | elem.style.webkitTransitionDuration = 0; 173 | } 174 | 175 | // Create the key used to cache this animation 176 | cacheKey += options.timingFunction; 177 | 178 | // Setup the start and end CSS state 179 | for(rule in css) 180 | { 181 | camel = this.util.toCamel(rule); 182 | 183 | toElem.style[camel] = css[rule]; 184 | 185 | // Set the from/start state 186 | from[rule] = (camel == 'WebkitTransform') ? new WebKitCSSMatrix( sanityCheckTransformString( window.getComputedStyle(elem)['-webkit-transform'] ) ) : window.getComputedStyle(elem)[rule]; 187 | 188 | // Set the to/end state 189 | to[rule] = (camel == 'WebkitTransform') ? new WebKitCSSMatrix( sanityCheckTransformString( toElem.style.WebkitTransform ) ) : toElem.style[camel]; 190 | 191 | // Shifty requires numeric values to be a number rather than a string (e.g. for opacity) 192 | from[rule] = from[rule] == (val = parseInt(from[rule], 10)) ? val : from[rule]; 193 | to[rule] = to[rule] == (val = parseInt(from[rule], 10)) ? val : to[rule]; 194 | 195 | // Update the cacheKey 196 | cacheKey += ';' + rule + ':' + from[rule] + '->' + to[rule]; 197 | } 198 | 199 | // Check the cache to save expensive calculations 200 | if(Morf.cache[cacheKey]) 201 | { 202 | this.css = Morf.cache[cacheKey].css; 203 | animName = Morf.cache[cacheKey].name; 204 | } 205 | else 206 | { 207 | // Produce decompositions of matrices here so we don't have to redo it on each iteration 208 | // Decomposing the matrix is expensive so we need to minimise these requests 209 | if(from['-webkit-transform']) 210 | { 211 | m1 = from['-webkit-transform'].decompose(); 212 | m2 = to['-webkit-transform'].decompose(); 213 | } 214 | 215 | // Produce style keyframes 216 | for(progress = 0; progress <= 1; progress += options.increment) { 217 | // Use Shifty.js to work out the interpolated CSS state 218 | frame = Tweenable.util.interpolate(from, to, progress, options.timingFunction); 219 | 220 | // Work out the interpolated matrix transformation 221 | if(m1 !== null && m2 !== null) 222 | frame['-webkit-transform'] = m1.tween(m2, progress, Tweenable.prototype.formula[options.timingFunction]); 223 | 224 | keyframes[parseInt(progress*100, 10)+'%'] = frame; 225 | } 226 | 227 | // Ensure the last frame has been added 228 | keyframes['100%'] = to; 229 | 230 | // Add the new animation to the document 231 | this.css = createAnimationCSS(keyframes, animName); 232 | addKeyframeRule(this.css); 233 | 234 | Morf.cache[cacheKey] = {css: this.css, name: animName}; 235 | } 236 | 237 | // Set the final position state as this should be a transition not an animation & the element should end in the 'to' state 238 | for(rule in to) 239 | elem.style[this.util.toCamel(rule)] = to[rule]; 240 | 241 | // Trigger the animation 242 | elem.addEventListener('webkitAnimationEnd', animationEndListener, true); 243 | elem.style.webkitAnimationDuration = options.duration; 244 | elem.style.webkitAnimationTimingFunction = 'linear'; 245 | elem.style.webkitAnimationName = animName; 246 | 247 | // Print the animation to the console if the debug switch is given 248 | if(options.debug && window.console && window.console.log) 249 | console.log(this.css); 250 | }; 251 | 252 | 253 | /** 254 | * Convenience function for triggering a transition 255 | * @param {HTMLDom} elem The element to apply the transition to 256 | * @param {Object} css Key value pair of CSS properties 257 | * @param {Object} opts Additional configuration options 258 | * 259 | * Configuration options 260 | * - timingFunction: {String} Name of the easing function to perform 261 | * - duration: {integer} Duration in ms 262 | * - increment: {float} How frequently to generate keyframes (Defaults to 0.01, which is every 1%) 263 | * - debug: {Boolean} Should the generated CSS Animation be printed to the console 264 | * 265 | * @returns {Morf} An instance of the Morf object 266 | */ 267 | 268 | Morf.transition = function(elem, css, opts){ 269 | return new Morf(elem, css, opts); 270 | }; 271 | 272 | /** 273 | * Object to cache generated animations 274 | */ 275 | Morf.cache = {}; 276 | 277 | /** 278 | * Current version 279 | */ 280 | Morf.version = '0.1.5'; 281 | 282 | // Utilities Placeholder 283 | Morf.prototype.util = {}; 284 | 285 | 286 | /** 287 | * Converts a DOM style string to CSS property name 288 | * @param {String} str A DOM style string 289 | * @returns {String} CSS property name 290 | */ 291 | 292 | Morf.prototype.util.toDash = function(str){ 293 | str = str.replace(/([A-Z])/g, function($1){return "-"+$1.toLowerCase();}); 294 | return /^webkit/.test(str) ? '-'+str : str; 295 | }; 296 | 297 | 298 | /** 299 | * Converts a CSS property name to DOM style string 300 | * @param {String} str A CSS property name 301 | * @returns {String} DOM style string 302 | */ 303 | 304 | Morf.prototype.util.toCamel = function(str){ 305 | return str.replace(/(\-[a-z])/g, function($1){return $1.toUpperCase().replace('-','');}); 306 | }; 307 | 308 | /** 309 | * WebKitCSSMatrix Extensions 310 | * 311 | * Copyright 2011, Joe Lambert (http://www.joelambert.co.uk) 312 | * Free to use under the MIT license. 313 | * http://joelambert.mit-license.org/ 314 | */ 315 | 316 | // Wrap this functionality up to prevent poluting the global namespace 317 | (function(){ 318 | 319 | 320 | /** 321 | * A 4 dimensional vector 322 | * @author Joe Lambert 323 | * @constructor 324 | */ 325 | 326 | var Vector4 = function(x, y, z, w) 327 | { 328 | this.x = x ? x : 0; 329 | this.y = y ? y : 0; 330 | this.z = z ? z : 0; 331 | this.w = w ? w : 0; 332 | 333 | 334 | /** 335 | * Ensure that values are not undefined 336 | * @author Joe Lambert 337 | * @returns null 338 | */ 339 | 340 | this.checkValues = function() { 341 | this.x = this.x ? this.x : 0; 342 | this.y = this.y ? this.y : 0; 343 | this.z = this.z ? this.z : 0; 344 | this.w = this.w ? this.w : 0; 345 | }; 346 | 347 | 348 | /** 349 | * Get the length of the vector 350 | * @author Joe Lambert 351 | * @returns {float} 352 | */ 353 | 354 | this.length = function() { 355 | this.checkValues(); 356 | return Math.sqrt(this.x*this.x + this.y*this.y + this.z*this.z); 357 | }; 358 | 359 | 360 | /** 361 | * Get a normalised representation of the vector 362 | * @author Joe Lambert 363 | * @returns {Vector4} 364 | */ 365 | 366 | this.normalise = function() { 367 | var len = this.length(), 368 | v = new Vector4(this.x / len, this.y / len, this.z / len); 369 | 370 | return v; 371 | }; 372 | 373 | 374 | /** 375 | * Vector Dot-Product 376 | * @param {Vector4} v The second vector to apply the product to 377 | * @author Joe Lambert 378 | * @returns {float} The Dot-Product of this and v. 379 | */ 380 | 381 | this.dot = function(v) { 382 | return this.x*v.x + this.y*v.y + this.z*v.z + this.w*v.w; 383 | }; 384 | 385 | 386 | /** 387 | * Vector Cross-Product 388 | * @param {Vector4} v The second vector to apply the product to 389 | * @author Joe Lambert 390 | * @returns {Vector4} The Cross-Product of this and v. 391 | */ 392 | 393 | this.cross = function(v) { 394 | return new Vector4(this.y*v.z - this.z*v.y, this.z*v.x - this.x*v.z, this.x*v.y - this.y*v.x); 395 | }; 396 | 397 | 398 | /** 399 | * Helper function required for matrix decomposition 400 | * A Javascript implementation of pseudo code available from http://www.w3.org/TR/css3-2d-transforms/#matrix-decomposition 401 | * @param {Vector4} aPoint A 3D point 402 | * @param {float} ascl 403 | * @param {float} bscl 404 | * @author Joe Lambert 405 | * @returns {Vector4} 406 | */ 407 | 408 | this.combine = function(aPoint, ascl, bscl) { 409 | return new Vector4( (ascl * this.x) + (bscl * aPoint.x), 410 | (ascl * this.y) + (bscl * aPoint.y), 411 | (ascl * this.z) + (bscl * aPoint.z) ); 412 | } 413 | }; 414 | 415 | 416 | /** 417 | * Object containing the decomposed components of a matrix 418 | * @author Joe Lambert 419 | * @constructor 420 | */ 421 | 422 | var CSSMatrixDecomposed = function(obj) { 423 | obj === undefined ? obj = {} : null; 424 | var components = {perspective: null, translate: null, skew: null, scale: null, rotate: null}; 425 | 426 | for(var i in components) 427 | this[i] = obj[i] ? obj[i] : new Vector4(); 428 | 429 | /** 430 | * Tween between two decomposed matrices 431 | * @param {CSSMatrixDecomposed} dm The destination decomposed matrix 432 | * @param {float} progress A float value between 0-1, representing the percentage of completion 433 | * @param {function} fn An easing function following the prototype function(pos){} 434 | * @author Joe Lambert 435 | * @returns {WebKitCSSMatrix} A new matrix for the tweened state 436 | */ 437 | 438 | this.tween = function(dm, progress, fn) { 439 | if(fn === undefined) 440 | fn = function(pos) {return pos;}; // Default to a linear easing 441 | 442 | if(!dm) 443 | dm = new CSSMatrixDecomposed(new WebKitCSSMatrix().decompose()); 444 | 445 | var r = new CSSMatrixDecomposed(), 446 | i = index = null, 447 | trans = ''; 448 | 449 | progress = fn(progress); 450 | 451 | for(index in components) 452 | for(i in {x:'x', y:'y', z:'z', w:'w'}) 453 | r[index][i] = (this[index][i] + (dm[index][i] - this[index][i]) * progress ).toFixed(5); 454 | 455 | trans = 'matrix3d(1,0,0,0, 0,1,0,0, 0,0,1,0, '+r.perspective.x+', '+r.perspective.y+', '+r.perspective.z+', '+r.perspective.w+') ' + 456 | 'translate3d('+r.translate.x+'px, '+r.translate.y+'px, '+r.translate.z+'px) ' + 457 | 'rotateX('+r.rotate.x+'rad) rotateY('+r.rotate.y+'rad) rotateZ('+r.rotate.z+'rad) ' + 458 | 'matrix3d(1,0,0,0, 0,1,0,0, 0,'+r.skew.z+',1,0, 0,0,0,1) ' + 459 | 'matrix3d(1,0,0,0, 0,1,0,0, '+r.skew.y+',0,1,0, 0,0,0,1) ' + 460 | 'matrix3d(1,0,0,0, '+r.skew.x+',1,0,0, 0,0,1,0, 0,0,0,1) ' + 461 | 'scale3d('+r.scale.x+', '+r.scale.y+', '+r.scale.z+')'; 462 | 463 | try { r = new WebKitCSSMatrix(trans); return r; } 464 | catch(e) { console.error('Invalid matrix string: '+trans); return '' }; 465 | }; 466 | }; 467 | 468 | 469 | /** 470 | * Tween between two matrices 471 | * @param {WebKitCSSMatrix} matrix The destination matrix 472 | * @param {float} progress A float value between 0-1, representing the percentage of completion 473 | * @param {function} fn An easing function following the prototype function(pos){} 474 | * @author Joe Lambert 475 | * @returns {WebKitCSSMatrix} A new matrix for the tweened state 476 | */ 477 | 478 | WebKitCSSMatrix.prototype.tween = function(matrix, progress, fn) { 479 | if(fn === undefined) 480 | fn = function(pos) {return pos;}; // Default to a linear easing 481 | 482 | var m = new WebKitCSSMatrix, 483 | m1 = this.decompose(), 484 | m2 = matrix.decompose(), 485 | r = m.decompose() 486 | trans = '', 487 | index = i = null; 488 | 489 | // Tween between the two decompositions 490 | return m1.tween(m2, progress, fn); 491 | }; 492 | 493 | 494 | /** 495 | * Transform a Vector4 object using the current matrix 496 | * @param {Vector4} v The vector to transform 497 | * @author Joe Lambert 498 | * @returns {Vector4} The transformed vector 499 | */ 500 | 501 | WebKitCSSMatrix.prototype.transformVector = function(v) { 502 | // TODO: Do we need to mod this for Vector4? 503 | return new Vector4( this.m11*v.x + this.m12*v.y + this.m13*v.z, 504 | this.m21*v.x + this.m22*v.y + this.m23*v.z, 505 | this.m31*v.x + this.m32*v.y + this.m33*v.z ); 506 | }; 507 | 508 | 509 | /** 510 | * Transposes the matrix 511 | * @author Joe Lambert 512 | * @returns {WebKitCSSMatrix} The transposed matrix 513 | */ 514 | 515 | WebKitCSSMatrix.prototype.transpose = function() { 516 | var matrix = new WebKitCSSMatrix(), n = m = 0; 517 | 518 | for (n = 0; n <= 4-2; n++) 519 | { 520 | for (m = n + 1; m <= 4-1; m++) 521 | { 522 | matrix['m'+(n+1)+(m+1)] = this['m'+(m+1)+(n+1)]; 523 | matrix['m'+(m+1)+(n+1)] = this['m'+(n+1)+(m+1)]; 524 | } 525 | } 526 | 527 | return matrix; 528 | }; 529 | 530 | 531 | /** 532 | * Calculates the determinant 533 | * @author Joe Lambert 534 | * @returns {float} The determinant of the matrix 535 | */ 536 | 537 | WebKitCSSMatrix.prototype.determinant = function() { 538 | return this.m14 * this.m23 * this.m32 * this.m41-this.m13 * this.m24 * this.m32 * this.m41 - 539 | this.m14 * this.m22 * this.m33 * this.m41+this.m12 * this.m24 * this.m33 * this.m41 + 540 | this.m13 * this.m22 * this.m34 * this.m41-this.m12 * this.m23 * this.m34 * this.m41 - 541 | this.m14 * this.m23 * this.m31 * this.m42+this.m13 * this.m24 * this.m31 * this.m42 + 542 | this.m14 * this.m21 * this.m33 * this.m42-this.m11 * this.m24 * this.m33 * this.m42 - 543 | this.m13 * this.m21 * this.m34 * this.m42+this.m11 * this.m23 * this.m34 * this.m42 + 544 | this.m14 * this.m22 * this.m31 * this.m43-this.m12 * this.m24 * this.m31 * this.m43 - 545 | this.m14 * this.m21 * this.m32 * this.m43+this.m11 * this.m24 * this.m32 * this.m43 + 546 | this.m12 * this.m21 * this.m34 * this.m43-this.m11 * this.m22 * this.m34 * this.m43 - 547 | this.m13 * this.m22 * this.m31 * this.m44+this.m12 * this.m23 * this.m31 * this.m44 + 548 | this.m13 * this.m21 * this.m32 * this.m44-this.m11 * this.m23 * this.m32 * this.m44 - 549 | this.m12 * this.m21 * this.m33 * this.m44+this.m11 * this.m22 * this.m33 * this.m44; 550 | }; 551 | 552 | 553 | /** 554 | * Decomposes the matrix into its component parts. 555 | * A Javascript implementation of the pseudo code available from http://www.w3.org/TR/css3-2d-transforms/#matrix-decomposition 556 | * @author Joe Lambert 557 | * @returns {Object} An object with each of the components of the matrix (perspective, translate, skew, scale, rotate) or identity matrix on failure 558 | */ 559 | 560 | WebKitCSSMatrix.prototype.decompose = function() { 561 | var matrix = new WebKitCSSMatrix(this.toString()), 562 | perspectiveMatrix = rightHandSide = inversePerspectiveMatrix = transposedInversePerspectiveMatrix = 563 | perspective = translate = row = i = scale = skew = pdum3 = rotate = null; 564 | 565 | if (matrix.m33 == 0) 566 | return new CSSMatrixDecomposed(new WebKitCSSMatrix().decompose()); // Return the identity matrix 567 | 568 | // Normalize the matrix. 569 | for (i = 1; i <= 4; i++) 570 | for (j = 1; j <= 4; j++) 571 | matrix['m'+i+j] /= matrix.m44; 572 | 573 | // perspectiveMatrix is used to solve for perspective, but it also provides 574 | // an easy way to test for singularity of the upper 3x3 component. 575 | perspectiveMatrix = matrix; 576 | 577 | for (i = 1; i <= 3; i++) 578 | perspectiveMatrix['m'+i+'4'] = 0; 579 | 580 | perspectiveMatrix.m44 = 1; 581 | 582 | if (perspectiveMatrix.determinant() == 0) 583 | return new CSSMatrixDecomposed(new WebKitCSSMatrix().decompose()); // Return the identity matrix 584 | 585 | // First, isolate perspective. 586 | if (matrix.m14 != 0 || matrix.m24 != 0 || matrix.m34 != 0) 587 | { 588 | // rightHandSide is the right hand side of the equation. 589 | rightHandSide = new Vector4(matrix.m14, matrix.m24, matrix.m34, matrix.m44); 590 | 591 | // Solve the equation by inverting perspectiveMatrix and multiplying 592 | // rightHandSide by the inverse. 593 | inversePerspectiveMatrix = perspectiveMatrix.inverse(); 594 | transposedInversePerspectiveMatrix = inversePerspectiveMatrix.transpose(); 595 | perspective = transposedInversePerspectiveMatrix.transformVector(rightHandSide); 596 | 597 | // Clear the perspective partition 598 | matrix.m14 = matrix.m24 = matrix.m34 = 0; 599 | matrix.m44 = 1; 600 | } 601 | else 602 | { 603 | // No perspective. 604 | perspective = new Vector4(0,0,0,1); 605 | } 606 | 607 | // Next take care of translation 608 | translate = new Vector4(matrix.m41, matrix.m42, matrix.m43); 609 | 610 | matrix.m41 = 0; 611 | matrix.m42 = 0; 612 | matrix.m43 = 0; 613 | 614 | // Now get scale and shear. 'row' is a 3 element array of 3 component vectors 615 | row = [ 616 | new Vector4(), new Vector4(), new Vector4() 617 | ]; 618 | 619 | for (i = 1; i <= 3; i++) 620 | { 621 | row[i-1].x = matrix['m'+i+'1']; 622 | row[i-1].y = matrix['m'+i+'2']; 623 | row[i-1].z = matrix['m'+i+'3']; 624 | } 625 | 626 | // Compute X scale factor and normalize first row. 627 | scale = new Vector4(); 628 | skew = new Vector4(); 629 | 630 | scale.x = row[0].length(); 631 | row[0] = row[0].normalise(); 632 | 633 | // Compute XY shear factor and make 2nd row orthogonal to 1st. 634 | skew.x = row[0].dot(row[1]); 635 | row[1] = row[1].combine(row[0], 1.0, -skew.x); 636 | 637 | // Now, compute Y scale and normalize 2nd row. 638 | scale.y = row[1].length(); 639 | row[1] = row[1].normalise(); 640 | skew.x /= scale.y; 641 | 642 | // Compute XZ and YZ shears, orthogonalize 3rd row 643 | skew.y = row[0].dot(row[2]); 644 | row[2] = row[2].combine(row[0], 1.0, -skew.y); 645 | skew.z = row[1].dot(row[2]); 646 | row[2] = row[2].combine(row[1], 1.0, -skew.z); 647 | 648 | // Next, get Z scale and normalize 3rd row. 649 | scale.z = row[2].length(); 650 | row[2] = row[2].normalise(); 651 | skew.y /= scale.z; 652 | skew.y /= scale.z; 653 | 654 | // At this point, the matrix (in rows) is orthonormal. 655 | // Check for a coordinate system flip. If the determinant 656 | // is -1, then negate the matrix and the scaling factors. 657 | pdum3 = row[1].cross(row[2]) 658 | if (row[0].dot(pdum3) < 0) 659 | { 660 | for (i = 0; i < 3; i++) 661 | { 662 | scale.x *= -1; 663 | row[i].x *= -1; 664 | row[i].y *= -1; 665 | row[i].z *= -1; 666 | } 667 | } 668 | 669 | // Now, get the rotations out 670 | rotate = new Vector4(); 671 | rotate.y = Math.asin(-row[0].z); 672 | if (Math.cos(rotate.y) != 0) 673 | { 674 | rotate.x = Math.atan2(row[1].z, row[2].z); 675 | rotate.z = Math.atan2(row[0].y, row[0].x); 676 | } 677 | else 678 | { 679 | rotate.x = Math.atan2(-row[2].x, row[1].y); 680 | rotate.z = 0; 681 | } 682 | 683 | return new CSSMatrixDecomposed({ 684 | perspective: perspective, 685 | translate: translate, 686 | skew: skew, 687 | scale: scale, 688 | rotate: rotate 689 | }); 690 | }; 691 | 692 | 693 | })(); 694 | 695 | 696 | /** 697 | * @preserve 698 | * Extra easing functions borrowed from scripty2 (c) 2005-2010 Thomas Fuchs (MIT Licence) 699 | * https://raw.github.com/madrobby/scripty2/master/src/effects/transitions/transitions.js 700 | */ 701 | 702 | (function(){ 703 | var scripty2 = { 704 | spring: function(pos) { 705 | return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); 706 | }, 707 | 708 | sinusoidal: function(pos) { 709 | return (-Math.cos(pos*Math.PI)/2) + 0.5; 710 | } 711 | }; 712 | 713 | // Load the Scripty2 functions 714 | for(var t in scripty2) 715 | Tweenable.prototype.formula[t] = scripty2[t]; 716 | })(); 717 | 718 | 719 | -------------------------------------------------------------------------------- /js/morf.noshifty.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | Morf v0.1.5 3 | http://www.joelambert.co.uk/morf 4 | 5 | Copyright 2011, Joe Lambert. 6 | Free to use under the MIT license. 7 | http://www.opensource.org/licenses/mit-license.php 8 | 9 | Extra easing functions borrowed from scripty2 (c) 2005-2010 Thomas Fuchs (MIT Licence) 10 | https://raw.github.com/madrobby/scripty2/master/src/effects/transitions/transitions.js 11 | */ 12 | var Morf=function(b,g,a){var d={},f={};document.createElement("div");var l=document.createElement("div"),c={timingFunction:"ease",duration:null,increment:0.01,debug:false,optimise:true,decimalPlaces:5},k=rule=ruleName=camel=m1=m2=progress=frame=rule=transEvent=val=null,e="",q=this,r={},t="anim"+(new Date).getTime(),v=function(){b.removeEventListener("webkitAnimationEnd",v,true);transEvent=document.createEvent("Event");transEvent.initEvent("webkitTransitionEnd",true,true);b.dispatchEvent(transEvent); 13 | b.style.webkitTransitionTimingFunction=null;b.style.webkitTransitionDuration=0;c.callback&&c.callback(b)},y=function(n){if(document.styleSheets&&document.styleSheets.length)document.styleSheets[0].insertRule(n,0);else{var o=document.createElement("style");o.innerHTML=n;document.head.appendChild(o)}},z=function(n,o){var h="@-webkit-keyframes "+o+" {\n",s=pos=rule=null,p="";for(pos in n){s=n[pos];p="\t"+pos+" {\n";for(rule in s)p+="\t\t"+q.util.toDash(rule)+": "+s[rule]+";\n";p+="\t}\n\n";h+=p}if(c.optimise){h= 14 | h+" }";s=void 0;s=typeof c.decimalPlaces=="number"?c.decimalPlaces:5;p=h.match(/[0-9\.]+/gm);var u=0;if(p)for(u=0;u"+f[rule]}if(Morf.cache[e]){this.css=Morf.cache[e].css;t=Morf.cache[e].name}else{if(d["-webkit-transform"]){m1=d["-webkit-transform"].decompose();m2=f["-webkit-transform"].decompose()}for(progress=0;progress<=1;progress+=c.increment){frame=Tweenable.util.interpolate(d,f,progress,c.timingFunction);if(m1!==null&&m2!==null)frame["-webkit-transform"]=m1.tween(m2,progress,Tweenable.prototype.formula[c.timingFunction]);r[parseInt(progress* 18 | 100,10)+"%"]=frame}r["100%"]=f;this.css=z(r,t);y(this.css);Morf.cache[e]={css:this.css,name:t}}for(rule in f)b.style[this.util.toCamel(rule)]=f[rule];b.addEventListener("webkitAnimationEnd",v,true);b.style.webkitAnimationDuration=c.duration;b.style.webkitAnimationTimingFunction="linear";b.style.webkitAnimationName=t;c.debug&&window.console&&window.console.log&&console.log(this.css)}};Morf.transition=function(b,g,a){return new Morf(b,g,a)};Morf.cache={};Morf.version="0.1.5";Morf.prototype.util={}; 19 | Morf.prototype.util.toDash=function(b){b=b.replace(/([A-Z])/g,function(g){return"-"+g.toLowerCase()});return/^webkit/.test(b)?"-"+b:b};Morf.prototype.util.toCamel=function(b){return b.replace(/(\-[a-z])/g,function(g){return g.toUpperCase().replace("-","")})}; 20 | (function(){var b=function(a,d,f,l){this.x=a?a:0;this.y=d?d:0;this.z=f?f:0;this.w=l?l:0;this.checkValues=function(){this.x=this.x?this.x:0;this.y=this.y?this.y:0;this.z=this.z?this.z:0;this.w=this.w?this.w:0};this.length=function(){this.checkValues();return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z)};this.normalise=function(){var c=this.length();return new b(this.x/c,this.y/c,this.z/c)};this.dot=function(c){return this.x*c.x+this.y*c.y+this.z*c.z+this.w*c.w};this.cross=function(c){return new b(this.y* 21 | c.z-this.z*c.y,this.z*c.x-this.x*c.z,this.x*c.y-this.y*c.x)};this.combine=function(c,k,e){return new b(k*this.x+e*c.x,k*this.y+e*c.y,k*this.z+e*c.z)}},g=function(a){a===undefined&&(a={});var d={perspective:null,translate:null,skew:null,scale:null,rotate:null},f;for(f in d)this[f]=a[f]?a[f]:new b;this.tween=function(l,c,k){if(k===undefined)k=function(v){return v};l||(l=new g((new WebKitCSSMatrix).decompose()));var e=new g,q=index=null,r="";c=k(c);for(index in d)for(q in{x:"x",y:"y",z:"z",w:"w"})e[index][q]= 22 | (this[index][q]+(l[index][q]-this[index][q])*c).toFixed(5);r="matrix3d(1,0,0,0, 0,1,0,0, 0,0,1,0, "+e.perspective.x+", "+e.perspective.y+", "+e.perspective.z+", "+e.perspective.w+") translate3d("+e.translate.x+"px, "+e.translate.y+"px, "+e.translate.z+"px) rotateX("+e.rotate.x+"rad) rotateY("+e.rotate.y+"rad) rotateZ("+e.rotate.z+"rad) matrix3d(1,0,0,0, 0,1,0,0, 0,"+e.skew.z+",1,0, 0,0,0,1) matrix3d(1,0,0,0, 0,1,0,0, "+e.skew.y+",0,1,0, 0,0,0,1) matrix3d(1,0,0,0, "+e.skew.x+",1,0,0, 0,0,1,0, 0,0,0,1) scale3d("+ 23 | e.scale.x+", "+e.scale.y+", "+e.scale.z+")";try{return e=new WebKitCSSMatrix(r)}catch(t){console.error("Invalid matrix string: "+r);return""}}};WebKitCSSMatrix.prototype.tween=function(a,d,f){if(f===undefined)f=function(k){return k};var l=new WebKitCSSMatrix,c=this.decompose();a=a.decompose();l.decompose();trans="";index=i=null;return c.tween(a,d,f)};WebKitCSSMatrix.prototype.transformVector=function(a){return new b(this.m11*a.x+this.m12*a.y+this.m13*a.z,this.m21*a.x+this.m22*a.y+this.m23*a.z,this.m31* 24 | a.x+this.m32*a.y+this.m33*a.z)};WebKitCSSMatrix.prototype.transpose=function(){var a=new WebKitCSSMatrix,d=m=0;for(d=0;d<=2;d++)for(m=d+1;m<=3;m++){a["m"+(d+1)+(m+1)]=this["m"+(m+1)+(d+1)];a["m"+(m+1)+(d+1)]=this["m"+(d+1)+(m+1)]}return a};WebKitCSSMatrix.prototype.determinant=function(){return this.m14*this.m23*this.m32*this.m41-this.m13*this.m24*this.m32*this.m41-this.m14*this.m22*this.m33*this.m41+this.m12*this.m24*this.m33*this.m41+this.m13*this.m22*this.m34*this.m41-this.m12*this.m23*this.m34* 25 | this.m41-this.m14*this.m23*this.m31*this.m42+this.m13*this.m24*this.m31*this.m42+this.m14*this.m21*this.m33*this.m42-this.m11*this.m24*this.m33*this.m42-this.m13*this.m21*this.m34*this.m42+this.m11*this.m23*this.m34*this.m42+this.m14*this.m22*this.m31*this.m43-this.m12*this.m24*this.m31*this.m43-this.m14*this.m21*this.m32*this.m43+this.m11*this.m24*this.m32*this.m43+this.m12*this.m21*this.m34*this.m43-this.m11*this.m22*this.m34*this.m43-this.m13*this.m22*this.m31*this.m44+this.m12*this.m23*this.m31* 26 | this.m44+this.m13*this.m21*this.m32*this.m44-this.m11*this.m23*this.m32*this.m44-this.m12*this.m21*this.m33*this.m44+this.m11*this.m22*this.m33*this.m44};WebKitCSSMatrix.prototype.decompose=function(){var a=new WebKitCSSMatrix(this.toString()),d=rightHandSide=inversePerspectiveMatrix=transposedInversePerspectiveMatrix=perspective=translate=row=i=scale=skew=pdum3=rotate=null;if(a.m33==0)return new g((new WebKitCSSMatrix).decompose());for(i=1;i<=4;i++)for(j=1;j<=4;j++)a["m"+i+j]/=a.m44;d=a;for(i=1;i<= 27 | 3;i++)d["m"+i+"4"]=0;d.m44=1;if(d.determinant()==0)return new g((new WebKitCSSMatrix).decompose());if(a.m14!=0||a.m24!=0||a.m34!=0){rightHandSide=new b(a.m14,a.m24,a.m34,a.m44);inversePerspectiveMatrix=d.inverse();transposedInversePerspectiveMatrix=inversePerspectiveMatrix.transpose();perspective=transposedInversePerspectiveMatrix.transformVector(rightHandSide);a.m14=a.m24=a.m34=0;a.m44=1}else perspective=new b(0,0,0,1);translate=new b(a.m41,a.m42,a.m43);a.m41=0;a.m42=0;a.m43=0;row=[new b,new b,new b]; 28 | for(i=1;i<=3;i++){row[i-1].x=a["m"+i+"1"];row[i-1].y=a["m"+i+"2"];row[i-1].z=a["m"+i+"3"]}scale=new b;skew=new b;scale.x=row[0].length();row[0]=row[0].normalise();skew.x=row[0].dot(row[1]);row[1]=row[1].combine(row[0],1,-skew.x);scale.y=row[1].length();row[1]=row[1].normalise();skew.x/=scale.y;skew.y=row[0].dot(row[2]);row[2]=row[2].combine(row[0],1,-skew.y);skew.z=row[1].dot(row[2]);row[2]=row[2].combine(row[1],1,-skew.z);scale.z=row[2].length();row[2]=row[2].normalise();skew.y/=scale.z;skew.y/= 29 | scale.z;pdum3=row[1].cross(row[2]);if(row[0].dot(pdum3)<0)for(i=0;i<3;i++){scale.x*=-1;row[i].x*=-1;row[i].y*=-1;row[i].z*=-1}rotate=new b;rotate.y=Math.asin(-row[0].z);if(Math.cos(rotate.y)!=0){rotate.x=Math.atan2(row[1].z,row[2].z);rotate.z=Math.atan2(row[0].y,row[0].x)}else{rotate.x=Math.atan2(-row[2].x,row[1].y);rotate.z=0}return new g({perspective:perspective,translate:translate,skew:skew,scale:scale,rotate:rotate})}})(); 30 | (function(){var b={spring:function(a){return 1-Math.cos(a*4.5*Math.PI)*Math.exp(-a*6)},sinusoidal:function(a){return-Math.cos(a*Math.PI)/2+0.5}},g;for(g in b)Tweenable.prototype.formula[g]=b[g]})(); 31 | -------------------------------------------------------------------------------- /js/shifty.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | Mifty - A custom build of Shifty for use with Morf.js. 3 | By Jeremy Kahn - jeremyckahn@gmail.com 4 | v0.4.1 5 | 6 | For instructions on how to use Shifty, please consult the README: https://github.com/jeremyckahn/shifty/blob/master/README.md 7 | 8 | MIT Lincense. This code free to use, modify, distribute and enjoy. 9 | 10 | */ 11 | 12 | (function(e){function a(){return+new Date}function b(a,c){for(var j in a)a.hasOwnProperty(j)&&c(a,j)}function h(a,c){b(c,function(c,f){a[f]=c[f]});return a}function k(a,c){b(c,function(c,f){typeof a[f]==="undefined"&&(a[f]=c[f])});return a}function i(a,c,j){var f,a=(a-c.timestamp)/c.duration;for(f in j.current)j.current.hasOwnProperty(f)&&c.to.hasOwnProperty(f)&&(j.current[f]=c.originalState[f]+(c.to[f]-c.originalState[f])*c.easingFunc(a));return j.current}function n(a,c,j,f){var b;for(b=0;b=0;b++)this._hook[a][b]===c&&this._hook[a].splice(b,1);else this._hook[a]=[]};g.prototype.filter={};g.util={now:a,each:b,tweenProps:i,applyFilter:l,simpleCopy:h};g.prototype.formula={linear:function(a){return a}};e.Tweenable=g})(this); 19 | (function(e){e.Tweenable.util.simpleCopy(e.Tweenable.prototype.formula,{easeInQuad:function(a){return Math.pow(a,2)},easeOutQuad:function(a){return-(Math.pow(a-1,2)-1)},easeInOutQuad:function(a){if((a/=0.5)<1)return 0.5*Math.pow(a,2);return-0.5*((a-=2)*a-2)},easeInCubic:function(a){return Math.pow(a,3)},easeOutCubic:function(a){return Math.pow(a-1,3)+1},easeInOutCubic:function(a){if((a/=0.5)<1)return 0.5*Math.pow(a,3);return 0.5*(Math.pow(a-2,3)+2)},easeInQuart:function(a){return Math.pow(a,4)},easeOutQuart:function(a){return-(Math.pow(a- 20 | 1,4)-1)},easeInOutQuart:function(a){if((a/=0.5)<1)return 0.5*Math.pow(a,4);return-0.5*((a-=2)*Math.pow(a,3)-2)},easeInQuint:function(a){return Math.pow(a,5)},easeOutQuint:function(a){return Math.pow(a-1,5)+1},easeInOutQuint:function(a){if((a/=0.5)<1)return 0.5*Math.pow(a,5);return 0.5*(Math.pow(a-2,5)+2)},easeInSine:function(a){return-Math.cos(a*(Math.PI/2))+1},easeOutSine:function(a){return Math.sin(a*(Math.PI/2))},easeInOutSine:function(a){return-0.5*(Math.cos(Math.PI*a)-1)},easeInExpo:function(a){return a== 21 | 0?0:Math.pow(2,10*(a-1))},easeOutExpo:function(a){return a==1?1:-Math.pow(2,-10*a)+1},easeInOutExpo:function(a){if(a==0)return 0;if(a==1)return 1;if((a/=0.5)<1)return 0.5*Math.pow(2,10*(a-1));return 0.5*(-Math.pow(2,-10*--a)+2)},easeInCirc:function(a){return-(Math.sqrt(1-a*a)-1)},easeOutCirc:function(a){return Math.sqrt(1-Math.pow(a-1,2))},easeInOutCirc:function(a){if((a/=0.5)<1)return-0.5*(Math.sqrt(1-a*a)-1);return 0.5*(Math.sqrt(1-(a-=2)*a)+1)},easeOutBounce:function(a){return a<1/2.75?7.5625* 22 | a*a:a<2/2.75?7.5625*(a-=1.5/2.75)*a+0.75:a<2.5/2.75?7.5625*(a-=2.25/2.75)*a+0.9375:7.5625*(a-=2.625/2.75)*a+0.984375},easeInBack:function(a){return a*a*(2.70158*a-1.70158)},easeOutBack:function(a){return(a-=1)*a*(2.70158*a+1.70158)+1},easeInOutBack:function(a){var b=1.70158;if((a/=0.5)<1)return 0.5*a*a*(((b*=1.525)+1)*a-b);return 0.5*((a-=2)*a*(((b*=1.525)+1)*a+b)+2)},elastic:function(a){return-1*Math.pow(4,-8*a)*Math.sin((a*6-1)*2*Math.PI/2)+1},swingFromTo:function(a){var b=1.70158;return(a/=0.5)< 23 | 1?0.5*a*a*(((b*=1.525)+1)*a-b):0.5*((a-=2)*a*(((b*=1.525)+1)*a+b)+2)},swingFrom:function(a){return a*a*(2.70158*a-1.70158)},swingTo:function(a){return(a-=1)*a*(2.70158*a+1.70158)+1},bounce:function(a){return a<1/2.75?7.5625*a*a:a<2/2.75?7.5625*(a-=1.5/2.75)*a+0.75:a<2.5/2.75?7.5625*(a-=2.25/2.75)*a+0.9375:7.5625*(a-=2.625/2.75)*a+0.984375},bouncePast:function(a){return a<1/2.75?7.5625*a*a:a<2/2.75?2-(7.5625*(a-=1.5/2.75)*a+0.75):a<2.5/2.75?2-(7.5625*(a-=2.25/2.75)*a+0.9375):2-(7.5625*(a-=2.625/2.75)* 24 | a+0.984375)},easeFromTo:function(a){if((a/=0.5)<1)return 0.5*Math.pow(a,4);return-0.5*((a-=2)*Math.pow(a,3)-2)},easeFrom:function(a){return Math.pow(a,4)},easeTo:function(a){return Math.pow(a,0.25)}})})(this); 25 | (function(e){if(e.Tweenable)e.Tweenable.util.interpolate=function(a,b,h,k){var i;if(a&&a.from)b=a.to,h=a.position,k=a.easing,a=a.from;i=e.Tweenable.util.simpleCopy({},a);e.Tweenable.util.applyFilter("tweenCreated",i,[i,a,b]);e.Tweenable.util.applyFilter("beforeTween",i,[i,a,b]);h=e.Tweenable.util.tweenProps(h,{originalState:a,to:b,timestamp:0,duration:1,easingFunc:e.Tweenable.prototype.formula[k]||e.Tweenable.prototype.formula.linear},{current:i});e.Tweenable.util.applyFilter("afterTween",h,[h,a, 26 | b]);return h},e.Tweenable.prototype.interpolate=function(a,b,h){a=e.Tweenable.util.interpolate(this.get(),a,b,h);this.set(a);return a}})(this); -------------------------------------------------------------------------------- /js/src/WebkitCSSMatrix.ext.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WebKitCSSMatrix Extensions 3 | * 4 | * Copyright 2011, Joe Lambert (http://www.joelambert.co.uk) 5 | * Free to use under the MIT license. 6 | * http://joelambert.mit-license.org/ 7 | */ 8 | 9 | // Wrap this functionality up to prevent poluting the global namespace 10 | (function(){ 11 | 12 | 13 | /** 14 | * A 4 dimensional vector 15 | * @author Joe Lambert 16 | * @constructor 17 | */ 18 | 19 | var Vector4 = function(x, y, z, w) 20 | { 21 | this.x = x ? x : 0; 22 | this.y = y ? y : 0; 23 | this.z = z ? z : 0; 24 | this.w = w ? w : 0; 25 | 26 | 27 | /** 28 | * Ensure that values are not undefined 29 | * @author Joe Lambert 30 | * @returns null 31 | */ 32 | 33 | this.checkValues = function() { 34 | this.x = this.x ? this.x : 0; 35 | this.y = this.y ? this.y : 0; 36 | this.z = this.z ? this.z : 0; 37 | this.w = this.w ? this.w : 0; 38 | }; 39 | 40 | 41 | /** 42 | * Get the length of the vector 43 | * @author Joe Lambert 44 | * @returns {float} 45 | */ 46 | 47 | this.length = function() { 48 | this.checkValues(); 49 | return Math.sqrt(this.x*this.x + this.y*this.y + this.z*this.z); 50 | }; 51 | 52 | 53 | /** 54 | * Get a normalised representation of the vector 55 | * @author Joe Lambert 56 | * @returns {Vector4} 57 | */ 58 | 59 | this.normalise = function() { 60 | var len = this.length(), 61 | v = new Vector4(this.x / len, this.y / len, this.z / len); 62 | 63 | return v; 64 | }; 65 | 66 | 67 | /** 68 | * Vector Dot-Product 69 | * @param {Vector4} v The second vector to apply the product to 70 | * @author Joe Lambert 71 | * @returns {float} The Dot-Product of this and v. 72 | */ 73 | 74 | this.dot = function(v) { 75 | return this.x*v.x + this.y*v.y + this.z*v.z + this.w*v.w; 76 | }; 77 | 78 | 79 | /** 80 | * Vector Cross-Product 81 | * @param {Vector4} v The second vector to apply the product to 82 | * @author Joe Lambert 83 | * @returns {Vector4} The Cross-Product of this and v. 84 | */ 85 | 86 | this.cross = function(v) { 87 | return new Vector4(this.y*v.z - this.z*v.y, this.z*v.x - this.x*v.z, this.x*v.y - this.y*v.x); 88 | }; 89 | 90 | 91 | /** 92 | * Helper function required for matrix decomposition 93 | * A Javascript implementation of pseudo code available from http://www.w3.org/TR/css3-2d-transforms/#matrix-decomposition 94 | * @param {Vector4} aPoint A 3D point 95 | * @param {float} ascl 96 | * @param {float} bscl 97 | * @author Joe Lambert 98 | * @returns {Vector4} 99 | */ 100 | 101 | this.combine = function(aPoint, ascl, bscl) { 102 | return new Vector4( (ascl * this.x) + (bscl * aPoint.x), 103 | (ascl * this.y) + (bscl * aPoint.y), 104 | (ascl * this.z) + (bscl * aPoint.z) ); 105 | } 106 | }; 107 | 108 | 109 | /** 110 | * Object containing the decomposed components of a matrix 111 | * @author Joe Lambert 112 | * @constructor 113 | */ 114 | 115 | var CSSMatrixDecomposed = function(obj) { 116 | obj === undefined ? obj = {} : null; 117 | var components = {perspective: null, translate: null, skew: null, scale: null, rotate: null}; 118 | 119 | for(var i in components) 120 | this[i] = obj[i] ? obj[i] : new Vector4(); 121 | 122 | /** 123 | * Tween between two decomposed matrices 124 | * @param {CSSMatrixDecomposed} dm The destination decomposed matrix 125 | * @param {float} progress A float value between 0-1, representing the percentage of completion 126 | * @param {function} fn An easing function following the prototype function(pos){} 127 | * @author Joe Lambert 128 | * @returns {WebKitCSSMatrix} A new matrix for the tweened state 129 | */ 130 | 131 | this.tween = function(dm, progress, fn) { 132 | if(fn === undefined) 133 | fn = function(pos) {return pos;}; // Default to a linear easing 134 | 135 | if(!dm) 136 | dm = new CSSMatrixDecomposed(new WebKitCSSMatrix().decompose()); 137 | 138 | var r = new CSSMatrixDecomposed(), 139 | i = index = null, 140 | trans = ''; 141 | 142 | progress = fn(progress); 143 | 144 | for(index in components) 145 | for(i in {x:'x', y:'y', z:'z', w:'w'}) 146 | r[index][i] = (this[index][i] + (dm[index][i] - this[index][i]) * progress ).toFixed(5); 147 | 148 | trans = 'matrix3d(1,0,0,0, 0,1,0,0, 0,0,1,0, '+r.perspective.x+', '+r.perspective.y+', '+r.perspective.z+', '+r.perspective.w+') ' + 149 | 'translate3d('+r.translate.x+'px, '+r.translate.y+'px, '+r.translate.z+'px) ' + 150 | 'rotateX('+r.rotate.x+'rad) rotateY('+r.rotate.y+'rad) rotateZ('+r.rotate.z+'rad) ' + 151 | 'matrix3d(1,0,0,0, 0,1,0,0, 0,'+r.skew.z+',1,0, 0,0,0,1) ' + 152 | 'matrix3d(1,0,0,0, 0,1,0,0, '+r.skew.y+',0,1,0, 0,0,0,1) ' + 153 | 'matrix3d(1,0,0,0, '+r.skew.x+',1,0,0, 0,0,1,0, 0,0,0,1) ' + 154 | 'scale3d('+r.scale.x+', '+r.scale.y+', '+r.scale.z+')'; 155 | 156 | try { r = new WebKitCSSMatrix(trans); return r; } 157 | catch(e) { console.error('Invalid matrix string: '+trans); return '' }; 158 | }; 159 | }; 160 | 161 | 162 | /** 163 | * Tween between two matrices 164 | * @param {WebKitCSSMatrix} matrix The destination matrix 165 | * @param {float} progress A float value between 0-1, representing the percentage of completion 166 | * @param {function} fn An easing function following the prototype function(pos){} 167 | * @author Joe Lambert 168 | * @returns {WebKitCSSMatrix} A new matrix for the tweened state 169 | */ 170 | 171 | WebKitCSSMatrix.prototype.tween = function(matrix, progress, fn) { 172 | if(fn === undefined) 173 | fn = function(pos) {return pos;}; // Default to a linear easing 174 | 175 | var m = new WebKitCSSMatrix, 176 | m1 = this.decompose(), 177 | m2 = matrix.decompose(), 178 | r = m.decompose() 179 | trans = '', 180 | index = i = null; 181 | 182 | // Tween between the two decompositions 183 | return m1.tween(m2, progress, fn); 184 | }; 185 | 186 | 187 | /** 188 | * Transform a Vector4 object using the current matrix 189 | * @param {Vector4} v The vector to transform 190 | * @author Joe Lambert 191 | * @returns {Vector4} The transformed vector 192 | */ 193 | 194 | WebKitCSSMatrix.prototype.transformVector = function(v) { 195 | // TODO: Do we need to mod this for Vector4? 196 | return new Vector4( this.m11*v.x + this.m12*v.y + this.m13*v.z, 197 | this.m21*v.x + this.m22*v.y + this.m23*v.z, 198 | this.m31*v.x + this.m32*v.y + this.m33*v.z ); 199 | }; 200 | 201 | 202 | /** 203 | * Transposes the matrix 204 | * @author Joe Lambert 205 | * @returns {WebKitCSSMatrix} The transposed matrix 206 | */ 207 | 208 | WebKitCSSMatrix.prototype.transpose = function() { 209 | var matrix = new WebKitCSSMatrix(), n = m = 0; 210 | 211 | for (n = 0; n <= 4-2; n++) 212 | { 213 | for (m = n + 1; m <= 4-1; m++) 214 | { 215 | matrix['m'+(n+1)+(m+1)] = this['m'+(m+1)+(n+1)]; 216 | matrix['m'+(m+1)+(n+1)] = this['m'+(n+1)+(m+1)]; 217 | } 218 | } 219 | 220 | return matrix; 221 | }; 222 | 223 | 224 | /** 225 | * Calculates the determinant 226 | * @author Joe Lambert 227 | * @returns {float} The determinant of the matrix 228 | */ 229 | 230 | WebKitCSSMatrix.prototype.determinant = function() { 231 | return this.m14 * this.m23 * this.m32 * this.m41-this.m13 * this.m24 * this.m32 * this.m41 - 232 | this.m14 * this.m22 * this.m33 * this.m41+this.m12 * this.m24 * this.m33 * this.m41 + 233 | this.m13 * this.m22 * this.m34 * this.m41-this.m12 * this.m23 * this.m34 * this.m41 - 234 | this.m14 * this.m23 * this.m31 * this.m42+this.m13 * this.m24 * this.m31 * this.m42 + 235 | this.m14 * this.m21 * this.m33 * this.m42-this.m11 * this.m24 * this.m33 * this.m42 - 236 | this.m13 * this.m21 * this.m34 * this.m42+this.m11 * this.m23 * this.m34 * this.m42 + 237 | this.m14 * this.m22 * this.m31 * this.m43-this.m12 * this.m24 * this.m31 * this.m43 - 238 | this.m14 * this.m21 * this.m32 * this.m43+this.m11 * this.m24 * this.m32 * this.m43 + 239 | this.m12 * this.m21 * this.m34 * this.m43-this.m11 * this.m22 * this.m34 * this.m43 - 240 | this.m13 * this.m22 * this.m31 * this.m44+this.m12 * this.m23 * this.m31 * this.m44 + 241 | this.m13 * this.m21 * this.m32 * this.m44-this.m11 * this.m23 * this.m32 * this.m44 - 242 | this.m12 * this.m21 * this.m33 * this.m44+this.m11 * this.m22 * this.m33 * this.m44; 243 | }; 244 | 245 | 246 | /** 247 | * Decomposes the matrix into its component parts. 248 | * A Javascript implementation of the pseudo code available from http://www.w3.org/TR/css3-2d-transforms/#matrix-decomposition 249 | * @author Joe Lambert 250 | * @returns {Object} An object with each of the components of the matrix (perspective, translate, skew, scale, rotate) or identity matrix on failure 251 | */ 252 | 253 | WebKitCSSMatrix.prototype.decompose = function() { 254 | var matrix = new WebKitCSSMatrix(this.toString()), 255 | perspectiveMatrix = rightHandSide = inversePerspectiveMatrix = transposedInversePerspectiveMatrix = 256 | perspective = translate = row = i = scale = skew = pdum3 = rotate = null; 257 | 258 | if (matrix.m33 == 0) 259 | return new CSSMatrixDecomposed(new WebKitCSSMatrix().decompose()); // Return the identity matrix 260 | 261 | // Normalize the matrix. 262 | for (i = 1; i <= 4; i++) 263 | for (j = 1; j <= 4; j++) 264 | matrix['m'+i+j] /= matrix.m44; 265 | 266 | // perspectiveMatrix is used to solve for perspective, but it also provides 267 | // an easy way to test for singularity of the upper 3x3 component. 268 | perspectiveMatrix = matrix; 269 | 270 | for (i = 1; i <= 3; i++) 271 | perspectiveMatrix['m'+i+'4'] = 0; 272 | 273 | perspectiveMatrix.m44 = 1; 274 | 275 | if (perspectiveMatrix.determinant() == 0) 276 | return new CSSMatrixDecomposed(new WebKitCSSMatrix().decompose()); // Return the identity matrix 277 | 278 | // First, isolate perspective. 279 | if (matrix.m14 != 0 || matrix.m24 != 0 || matrix.m34 != 0) 280 | { 281 | // rightHandSide is the right hand side of the equation. 282 | rightHandSide = new Vector4(matrix.m14, matrix.m24, matrix.m34, matrix.m44); 283 | 284 | // Solve the equation by inverting perspectiveMatrix and multiplying 285 | // rightHandSide by the inverse. 286 | inversePerspectiveMatrix = perspectiveMatrix.inverse(); 287 | transposedInversePerspectiveMatrix = inversePerspectiveMatrix.transpose(); 288 | perspective = transposedInversePerspectiveMatrix.transformVector(rightHandSide); 289 | 290 | // Clear the perspective partition 291 | matrix.m14 = matrix.m24 = matrix.m34 = 0; 292 | matrix.m44 = 1; 293 | } 294 | else 295 | { 296 | // No perspective. 297 | perspective = new Vector4(0,0,0,1); 298 | } 299 | 300 | // Next take care of translation 301 | translate = new Vector4(matrix.m41, matrix.m42, matrix.m43); 302 | 303 | matrix.m41 = 0; 304 | matrix.m42 = 0; 305 | matrix.m43 = 0; 306 | 307 | // Now get scale and shear. 'row' is a 3 element array of 3 component vectors 308 | row = [ 309 | new Vector4(), new Vector4(), new Vector4() 310 | ]; 311 | 312 | for (i = 1; i <= 3; i++) 313 | { 314 | row[i-1].x = matrix['m'+i+'1']; 315 | row[i-1].y = matrix['m'+i+'2']; 316 | row[i-1].z = matrix['m'+i+'3']; 317 | } 318 | 319 | // Compute X scale factor and normalize first row. 320 | scale = new Vector4(); 321 | skew = new Vector4(); 322 | 323 | scale.x = row[0].length(); 324 | row[0] = row[0].normalise(); 325 | 326 | // Compute XY shear factor and make 2nd row orthogonal to 1st. 327 | skew.x = row[0].dot(row[1]); 328 | row[1] = row[1].combine(row[0], 1.0, -skew.x); 329 | 330 | // Now, compute Y scale and normalize 2nd row. 331 | scale.y = row[1].length(); 332 | row[1] = row[1].normalise(); 333 | skew.x /= scale.y; 334 | 335 | // Compute XZ and YZ shears, orthogonalize 3rd row 336 | skew.y = row[0].dot(row[2]); 337 | row[2] = row[2].combine(row[0], 1.0, -skew.y); 338 | skew.z = row[1].dot(row[2]); 339 | row[2] = row[2].combine(row[1], 1.0, -skew.z); 340 | 341 | // Next, get Z scale and normalize 3rd row. 342 | scale.z = row[2].length(); 343 | row[2] = row[2].normalise(); 344 | skew.y /= scale.z; 345 | skew.y /= scale.z; 346 | 347 | // At this point, the matrix (in rows) is orthonormal. 348 | // Check for a coordinate system flip. If the determinant 349 | // is -1, then negate the matrix and the scaling factors. 350 | pdum3 = row[1].cross(row[2]) 351 | if (row[0].dot(pdum3) < 0) 352 | { 353 | for (i = 0; i < 3; i++) 354 | { 355 | scale.x *= -1; 356 | row[i].x *= -1; 357 | row[i].y *= -1; 358 | row[i].z *= -1; 359 | } 360 | } 361 | 362 | // Now, get the rotations out 363 | rotate = new Vector4(); 364 | rotate.y = Math.asin(-row[0].z); 365 | if (Math.cos(rotate.y) != 0) 366 | { 367 | rotate.x = Math.atan2(row[1].z, row[2].z); 368 | rotate.z = Math.atan2(row[0].y, row[0].x); 369 | } 370 | else 371 | { 372 | rotate.x = Math.atan2(-row[2].x, row[1].y); 373 | rotate.z = 0; 374 | } 375 | 376 | return new CSSMatrixDecomposed({ 377 | perspective: perspective, 378 | translate: translate, 379 | skew: skew, 380 | scale: scale, 381 | rotate: rotate 382 | }); 383 | }; 384 | 385 | 386 | })(); 387 | -------------------------------------------------------------------------------- /js/src/morf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve Morf v@VERSION 3 | * http://www.joelambert.co.uk/morf 4 | * 5 | * Copyright 2011, Joe Lambert. 6 | * Free to use under the MIT license. 7 | * http://www.opensource.org/licenses/mit-license.php 8 | */ 9 | 10 | var Morf = function(elem, css, opts) { 11 | var from = {}, to = {}, 12 | 13 | fromElem = document.createElement('div'), 14 | toElem = document.createElement('div'), 15 | 16 | options = { 17 | timingFunction: 'ease', 18 | duration: null, 19 | increment: 0.01, 20 | debug: false, 21 | optimise: true, // Whether the outputted CSS should be optimised 22 | decimalPlaces: 5 // How many decimal places to optimise the WebKitCSSMatrix output to 23 | }, 24 | 25 | // Define all other var's used in the function 26 | i = rule = ruleName = camel = m1 = m2 = progress = frame = rule = transEvent = val = null, cacheKey = '', 27 | 28 | // Setup a scoped reference to ourselves 29 | _this = this, 30 | 31 | keyframes = {}, 32 | 33 | // Create a unique name for this animation 34 | animName = 'anim'+(new Date().getTime()), 35 | 36 | 37 | /* --- Helper Functions ------------------------------------------------------------------- */ 38 | 39 | // Event listener for the webkitAnimationEnd Event 40 | animationEndListener = function(event){ 41 | elem.removeEventListener('webkitAnimationEnd', animationEndListener, true); 42 | 43 | // Dispatch a faux webkitTransitionEnd event to complete the appearance of this being a transition rather than an animation 44 | // TODO: Should we throw an event for each property changed? (event.propertyName = 'opacity' etc) 45 | transEvent = document.createEvent("Event"); 46 | transEvent.initEvent("webkitTransitionEnd", true, true); 47 | elem.dispatchEvent(transEvent); 48 | 49 | // Reset transition effects after use 50 | elem.style.webkitTransitionTimingFunction = null; 51 | elem.style.webkitTransitionDuration = 0; 52 | 53 | if (options.callback) { 54 | options.callback(elem); 55 | } 56 | }, 57 | 58 | // Adds the CSS to the current page 59 | addKeyframeRule = function(rule) { 60 | if (document.styleSheets && document.styleSheets.length) 61 | document.styleSheets[0].insertRule(rule, 0); 62 | else 63 | { 64 | var style = document.createElement('style'); 65 | style.innerHTML = rule; 66 | document.head.appendChild(style); 67 | } 68 | }, 69 | 70 | // Produces a CSS string representation of the Keyframes 71 | createAnimationCSS = function(kf, name) { 72 | var str = '@-webkit-keyframes '+name+' {\n', f = pos = rule = null, fStr = ''; 73 | 74 | for(pos in kf) 75 | { 76 | f = kf[pos]; 77 | fStr = '\t'+pos+' {\n'; 78 | 79 | for(rule in f) 80 | fStr += '\t\t'+_this.util.toDash(rule)+': '+f[rule]+';\n'; 81 | 82 | fStr += "\t}\n\n"; 83 | 84 | str += fStr; 85 | } 86 | 87 | return options.optimise ? optimiseCSS(str+' }') : str+' }'; 88 | }, 89 | 90 | // Replaces scale(0) with 0.0001 to get around the inability to these decompose matrix 91 | sanityCheckTransformString = function(str) { 92 | var scale = str.match(/scale[Y|X|Z]*\([0-9, ]*0[,0-9 ]*\)/g), 93 | i = 0; 94 | 95 | if(scale) 96 | { 97 | // There might be multiple scale() properties in the string 98 | for(i = 0; i < scale.length; i++) 99 | str = str.replace(scale[i], scale[i].replace(/([^0-9])0([^0.9])/g, "$10.0001$2")); 100 | } 101 | 102 | return str; 103 | }, 104 | 105 | // WebKitCSSMatrix toString() ALWAYS outputs numbers to 5 decimal places - this helps optimise the string 106 | optimiseCSS = function(str, decimalPlaces) { 107 | decimalPlaces = typeof options.decimalPlaces == 'number' ? options.decimalPlaces : 5; 108 | var matches = str.match(/[0-9\.]+/gm), 109 | i = 0; 110 | 111 | if(matches) 112 | { 113 | for(i = 0; i < matches.length; i++) 114 | str = str.replace(matches[i], parseFloat( parseFloat(matches[i]).toFixed(decimalPlaces))); 115 | } 116 | 117 | return str; 118 | }; 119 | 120 | /* --- Helper Functions End --------------------------------------------------------------- */ 121 | 122 | 123 | // Import the options 124 | for(i in opts) 125 | options[i] = opts[i]; 126 | 127 | 128 | // If timingFunction is a natively supported function then just trigger normal transition 129 | if( options.timingFunction === 'ease' || 130 | options.timingFunction === 'linear' || 131 | options.timingFunction === 'ease-in' || 132 | options.timingFunction === 'ease-out' || 133 | options.timingFunction === 'ease-in-out' || 134 | /^cubic-bezier/.test(options.timingFunction)) { 135 | 136 | elem.style.webkitTransitionDuration = options.duration; 137 | elem.style.webkitTransitionTimingFunction = options.timingFunction; 138 | 139 | // Listen for the transitionEnd event to fire the callback if needed 140 | var transitionEndListener = function(event) { 141 | elem.removeEventListener('webkitTransitionEnd', transitionEndListener, true); 142 | 143 | // Clean up after ourself 144 | elem.style.webkitTransitionDuration = 0; 145 | elem.style.webkitTransitionTimingFunction = null; 146 | 147 | if (options.callback) { 148 | // Delay execution to ensure the clean up CSS has taken effect 149 | setTimeout(function() { 150 | options.callback(elem); 151 | }, 10); 152 | } 153 | }; 154 | 155 | elem.addEventListener('webkitTransitionEnd', transitionEndListener, true); 156 | 157 | setTimeout(function() { 158 | for(rule in css) { 159 | camel = _this.util.toCamel(rule); 160 | elem.style[camel] = css[rule]; 161 | } 162 | }, 10); 163 | 164 | this.css = ''; 165 | 166 | return; 167 | } 168 | else 169 | { 170 | // Reset transition properties for this element 171 | elem.style.webkitTransitionTimingFunction = null; 172 | elem.style.webkitTransitionDuration = 0; 173 | } 174 | 175 | // Create the key used to cache this animation 176 | cacheKey += options.timingFunction; 177 | 178 | // Setup the start and end CSS state 179 | for(rule in css) 180 | { 181 | camel = this.util.toCamel(rule); 182 | 183 | toElem.style[camel] = css[rule]; 184 | 185 | // Set the from/start state 186 | from[rule] = (camel == 'WebkitTransform') ? new WebKitCSSMatrix( sanityCheckTransformString( window.getComputedStyle(elem)['-webkit-transform'] ) ) : window.getComputedStyle(elem)[rule]; 187 | 188 | // Set the to/end state 189 | to[rule] = (camel == 'WebkitTransform') ? new WebKitCSSMatrix( sanityCheckTransformString( toElem.style.WebkitTransform ) ) : toElem.style[camel]; 190 | 191 | // Shifty requires numeric values to be a number rather than a string (e.g. for opacity) 192 | from[rule] = from[rule] == (val = parseInt(from[rule], 10)) ? val : from[rule]; 193 | to[rule] = to[rule] == (val = parseInt(from[rule], 10)) ? val : to[rule]; 194 | 195 | // Update the cacheKey 196 | cacheKey += ';' + rule + ':' + from[rule] + '->' + to[rule]; 197 | } 198 | 199 | // Check the cache to save expensive calculations 200 | if(Morf.cache[cacheKey]) 201 | { 202 | this.css = Morf.cache[cacheKey].css; 203 | animName = Morf.cache[cacheKey].name; 204 | } 205 | else 206 | { 207 | // Produce decompositions of matrices here so we don't have to redo it on each iteration 208 | // Decomposing the matrix is expensive so we need to minimise these requests 209 | if(from['-webkit-transform']) 210 | { 211 | m1 = from['-webkit-transform'].decompose(); 212 | m2 = to['-webkit-transform'].decompose(); 213 | } 214 | 215 | // Produce style keyframes 216 | for(progress = 0; progress <= 1; progress += options.increment) { 217 | // Use Shifty.js to work out the interpolated CSS state 218 | frame = Tweenable.util.interpolate(from, to, progress, options.timingFunction); 219 | 220 | // Work out the interpolated matrix transformation 221 | if(m1 !== null && m2 !== null) 222 | frame['-webkit-transform'] = m1.tween(m2, progress, Tweenable.prototype.formula[options.timingFunction]); 223 | 224 | keyframes[parseInt(progress*100, 10)+'%'] = frame; 225 | } 226 | 227 | // Ensure the last frame has been added 228 | keyframes['100%'] = to; 229 | 230 | // Add the new animation to the document 231 | this.css = createAnimationCSS(keyframes, animName); 232 | addKeyframeRule(this.css); 233 | 234 | Morf.cache[cacheKey] = {css: this.css, name: animName}; 235 | } 236 | 237 | // Set the final position state as this should be a transition not an animation & the element should end in the 'to' state 238 | for(rule in to) 239 | elem.style[this.util.toCamel(rule)] = to[rule]; 240 | 241 | // Trigger the animation 242 | elem.addEventListener('webkitAnimationEnd', animationEndListener, true); 243 | elem.style.webkitAnimationDuration = options.duration; 244 | elem.style.webkitAnimationTimingFunction = 'linear'; 245 | elem.style.webkitAnimationName = animName; 246 | 247 | // Print the animation to the console if the debug switch is given 248 | if(options.debug && window.console && window.console.log) 249 | console.log(this.css); 250 | }; 251 | 252 | 253 | /** 254 | * Convenience function for triggering a transition 255 | * @param {HTMLDom} elem The element to apply the transition to 256 | * @param {Object} css Key value pair of CSS properties 257 | * @param {Object} opts Additional configuration options 258 | * 259 | * Configuration options 260 | * - timingFunction: {String} Name of the easing function to perform 261 | * - duration: {integer} Duration in ms 262 | * - increment: {float} How frequently to generate keyframes (Defaults to 0.01, which is every 1%) 263 | * - debug: {Boolean} Should the generated CSS Animation be printed to the console 264 | * 265 | * @returns {Morf} An instance of the Morf object 266 | */ 267 | 268 | Morf.transition = function(elem, css, opts){ 269 | return new Morf(elem, css, opts); 270 | }; 271 | 272 | /** 273 | * Object to cache generated animations 274 | */ 275 | Morf.cache = {}; 276 | 277 | /** 278 | * Current version 279 | */ 280 | Morf.version = '@VERSION'; -------------------------------------------------------------------------------- /js/src/morf.utils.js: -------------------------------------------------------------------------------- 1 | // Utilities Placeholder 2 | Morf.prototype.util = {}; 3 | 4 | 5 | /** 6 | * Converts a DOM style string to CSS property name 7 | * @param {String} str A DOM style string 8 | * @returns {String} CSS property name 9 | */ 10 | 11 | Morf.prototype.util.toDash = function(str){ 12 | str = str.replace(/([A-Z])/g, function($1){return "-"+$1.toLowerCase();}); 13 | return /^webkit/.test(str) ? '-'+str : str; 14 | }; 15 | 16 | 17 | /** 18 | * Converts a CSS property name to DOM style string 19 | * @param {String} str A CSS property name 20 | * @returns {String} DOM style string 21 | */ 22 | 23 | Morf.prototype.util.toCamel = function(str){ 24 | return str.replace(/(\-[a-z])/g, function($1){return $1.toUpperCase().replace('-','');}); 25 | }; -------------------------------------------------------------------------------- /js/src/shifty.fn.scripty2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve 3 | * Extra easing functions borrowed from scripty2 (c) 2005-2010 Thomas Fuchs (MIT Licence) 4 | * https://raw.github.com/madrobby/scripty2/master/src/effects/transitions/transitions.js 5 | */ 6 | 7 | (function(){ 8 | var scripty2 = { 9 | spring: function(pos) { 10 | return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); 11 | }, 12 | 13 | sinusoidal: function(pos) { 14 | return (-Math.cos(pos*Math.PI)/2) + 0.5; 15 | } 16 | }; 17 | 18 | // Load the Scripty2 functions 19 | for(var t in scripty2) 20 | Tweenable.prototype.formula[t] = scripty2[t]; 21 | })(); 22 | --------------------------------------------------------------------------------