├── .gitignore ├── ClassAnimationDemo.framer ├── framer │ ├── version │ ├── .bookmark │ ├── social-80x80.png │ ├── images │ │ ├── cursor.png │ │ ├── icon-76.png │ │ ├── cursor@2x.png │ │ ├── icon-120.png │ │ ├── icon-152.png │ │ ├── icon-180.png │ │ ├── icon-192.png │ │ ├── cursor-active.png │ │ └── cursor-active@2x.png │ ├── social-800x600.png │ ├── config.json │ ├── framer.generated.js │ ├── style.css │ └── framer.init.js ├── images │ ├── .gitkeep │ ├── stars.svg │ └── delivery.svg ├── .gitignore ├── index.html ├── app.coffee └── modules │ ├── ClassAnimation.coffee │ └── velocity.min.js ├── demo.gif ├── thumb.mov ├── module.json ├── example.coffee ├── AnimatableProperties.md ├── README.md └── ClassAnimation.coffee /.gitignore: -------------------------------------------------------------------------------- 1 | Demo.sketch -------------------------------------------------------------------------------- /ClassAnimationDemo.framer/framer/version: -------------------------------------------------------------------------------- 1 | 6 -------------------------------------------------------------------------------- /ClassAnimationDemo.framer/images/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kysely/framer-class-animation/HEAD/demo.gif -------------------------------------------------------------------------------- /thumb.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kysely/framer-class-animation/HEAD/thumb.mov -------------------------------------------------------------------------------- /ClassAnimationDemo.framer/framer/.bookmark: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kysely/framer-class-animation/HEAD/ClassAnimationDemo.framer/framer/.bookmark -------------------------------------------------------------------------------- /ClassAnimationDemo.framer/framer/social-80x80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kysely/framer-class-animation/HEAD/ClassAnimationDemo.framer/framer/social-80x80.png -------------------------------------------------------------------------------- /ClassAnimationDemo.framer/framer/images/cursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kysely/framer-class-animation/HEAD/ClassAnimationDemo.framer/framer/images/cursor.png -------------------------------------------------------------------------------- /ClassAnimationDemo.framer/framer/images/icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kysely/framer-class-animation/HEAD/ClassAnimationDemo.framer/framer/images/icon-76.png -------------------------------------------------------------------------------- /ClassAnimationDemo.framer/framer/social-800x600.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kysely/framer-class-animation/HEAD/ClassAnimationDemo.framer/framer/social-800x600.png -------------------------------------------------------------------------------- /ClassAnimationDemo.framer/framer/images/cursor@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kysely/framer-class-animation/HEAD/ClassAnimationDemo.framer/framer/images/cursor@2x.png -------------------------------------------------------------------------------- /ClassAnimationDemo.framer/framer/images/icon-120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kysely/framer-class-animation/HEAD/ClassAnimationDemo.framer/framer/images/icon-120.png -------------------------------------------------------------------------------- /ClassAnimationDemo.framer/framer/images/icon-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kysely/framer-class-animation/HEAD/ClassAnimationDemo.framer/framer/images/icon-152.png -------------------------------------------------------------------------------- /ClassAnimationDemo.framer/framer/images/icon-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kysely/framer-class-animation/HEAD/ClassAnimationDemo.framer/framer/images/icon-180.png -------------------------------------------------------------------------------- /ClassAnimationDemo.framer/framer/images/icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kysely/framer-class-animation/HEAD/ClassAnimationDemo.framer/framer/images/icon-192.png -------------------------------------------------------------------------------- /ClassAnimationDemo.framer/framer/images/cursor-active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kysely/framer-class-animation/HEAD/ClassAnimationDemo.framer/framer/images/cursor-active.png -------------------------------------------------------------------------------- /ClassAnimationDemo.framer/framer/images/cursor-active@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kysely/framer-class-animation/HEAD/ClassAnimationDemo.framer/framer/images/cursor-active@2x.png -------------------------------------------------------------------------------- /module.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Class Animation", 3 | "description": "Animate individual HTML and SVG elements inside one layer's html property", 4 | "author": "Radek Kysely", 5 | 6 | "require": "{ClassAnimation, Style} = require 'ClassAnimation'", 7 | "install": "ClassAnimation.coffee", 8 | "example": "example.coffee", 9 | 10 | "thumb": "thumb.mov" 11 | } 12 | -------------------------------------------------------------------------------- /ClassAnimationDemo.framer/framer/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "orientation" : 0, 3 | "updateDelay" : 0.3, 4 | "contentScale" : 1, 5 | "fullScreen" : false, 6 | "sharedPrototype" : 0, 7 | "propertyPanelToggleStates" : { 8 | 9 | }, 10 | "deviceType" : "apple-iphone-7-black", 11 | "projectId" : "8F1B03B1-0D3B-4115-85A0-2C81C555981B", 12 | "deviceOrientation" : 0, 13 | "selectedHand" : "", 14 | "foldedCodeRanges" : [ 15 | "{80, 531}" 16 | ], 17 | "deviceScale" : "fit" 18 | } -------------------------------------------------------------------------------- /example.coffee: -------------------------------------------------------------------------------- 1 | socialCard = new Layer 2 | 3 | # Create a content in one layer's ›html‹ property 4 | # with some classes 5 | socialCard.html = 6 | """ 7 |

Paul Paulson

8 | Montessori Teacher 9 | """ 10 | 11 | # Create a new animation for "className" class 12 | animationA = new ClassAnimation "className", 13 | color: "#ff00ff" 14 | fontSize: 60 15 | lineHeight: 70 16 | borderWidth: 5 17 | 18 | # Start the animation 19 | animationA.start() 20 | -------------------------------------------------------------------------------- /ClassAnimationDemo.framer/.gitignore: -------------------------------------------------------------------------------- 1 | # Framer Git Ignore 2 | 3 | # General OSX 4 | 5 | .DS_Store 6 | .AppleDouble 7 | .LSOverride 8 | 9 | # Icon must end with two \r 10 | Icon 11 | 12 | # Thumbnails 13 | ._* 14 | 15 | # Files that might appear in the root of a volume 16 | .DocumentRevisions-V100 17 | .fseventsd 18 | .Spotlight-V100 19 | .TemporaryItems 20 | .Trashes 21 | .VolumeIcon.icns 22 | 23 | # Directories potentially created on remote AFP share 24 | .AppleDB 25 | .AppleDesktop 26 | Network Trash Folder 27 | Temporary Items 28 | .apdisk 29 | 30 | # Framer Specific 31 | .*.html 32 | .app.js 33 | framer/*.old* 34 | framer/.*.hash 35 | framer/backup.coffee 36 | framer/backups/* 37 | framer/manifest.txt 38 | framer/metadata.json 39 | framer/preview.png 40 | framer/social-880x460.png 41 | framer/social-1200x630.png 42 | -------------------------------------------------------------------------------- /ClassAnimationDemo.framer/framer/framer.generated.js: -------------------------------------------------------------------------------- 1 | // This is autogenerated by Framer 2 | 3 | 4 | if (!window.Framer && window._bridge) {window._bridge('runtime.error', {message:'[framer.js] Framer library missing or corrupt. Select File → Update Framer Library.'})} 5 | if (DeviceComponent) {DeviceComponent.Devices["iphone-6-silver"].deviceImageJP2 = false}; 6 | if (window.Framer) {window.Framer.Defaults.DeviceView = {"deviceScale":"fit","selectedHand":"","deviceType":"apple-iphone-7-black","contentScale":1,"orientation":0}; 7 | } 8 | if (window.Framer) {window.Framer.Defaults.DeviceComponent = {"deviceScale":"fit","selectedHand":"","deviceType":"apple-iphone-7-black","contentScale":1,"orientation":0}; 9 | } 10 | window.FramerStudioInfo = {"deviceImagesUrl":"\/_server\/resources\/DeviceImages","documentTitle":"ClassAnimationDemo.framer"}; 11 | 12 | Framer.Device = new Framer.DeviceView(); 13 | Framer.Device.setupContext(); -------------------------------------------------------------------------------- /ClassAnimationDemo.framer/framer/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | border: none; 5 | -webkit-user-select: none; 6 | -webkit-tap-highlight-color: rgba(0,0,0,0); 7 | } 8 | 9 | body { 10 | background-color: #fff; 11 | font: 28px/1em "Helvetica"; 12 | color: gray; 13 | overflow: hidden; 14 | } 15 | 16 | a { 17 | color: gray; 18 | } 19 | 20 | body { 21 | cursor: url('images/cursor.png') 32 32, auto; 22 | cursor: -webkit-image-set( 23 | url('images/cursor.png') 1x, 24 | url('images/cursor@2x.png') 2x 25 | ) 32 32, auto; 26 | } 27 | 28 | body:active { 29 | cursor: url('images/cursor-active.png') 32 32, auto; 30 | cursor: -webkit-image-set( 31 | url('images/cursor-active.png') 1x, 32 | url('images/cursor-active@2x.png') 2x 33 | ) 32 32, auto; 34 | } 35 | 36 | .framerAlertBackground { 37 | position: absolute; top:0px; left:0px; right:0px; bottom:0px; 38 | z-index: 1000; 39 | background-color: #fff; 40 | } 41 | 42 | .framerAlert { 43 | font:400 14px/1.4 "Helvetica Neue", Helvetica, Arial, sans-serif; 44 | -webkit-font-smoothing:antialiased; 45 | color:#616367; text-align:center; 46 | position: absolute; top:40%; left:50%; width:260px; margin-left:-130px; 47 | } 48 | .framerAlert strong { font-weight:500; color:#000; margin-bottom:8px; display:block; } 49 | .framerAlert a { color:#28AFFA; } 50 | .framerAlert .btn { 51 | font-weight:500; text-decoration:none; line-height:1; 52 | display:inline-block; padding:6px 12px 7px 12px; 53 | border-radius:3px; margin-top:12px; 54 | background:#28AFFA; color:#fff; 55 | } 56 | 57 | ::-webkit-scrollbar { 58 | display: none; 59 | } -------------------------------------------------------------------------------- /ClassAnimationDemo.framer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /ClassAnimationDemo.framer/images/stars.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ClassAnimationDemo.framer/framer/framer.init.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | function isFileLoadingAllowed() { 4 | return (window.location.protocol.indexOf("file") == -1) 5 | } 6 | 7 | function isHomeScreened() { 8 | return ("standalone" in window.navigator) && window.navigator.standalone == true 9 | } 10 | 11 | function isCompatibleBrowser() { 12 | return Utils.isWebKit() 13 | } 14 | 15 | var alertNode; 16 | 17 | function dismissAlert() { 18 | alertNode.parentElement.removeChild(alertNode) 19 | loadProject() 20 | } 21 | 22 | function showAlert(html) { 23 | 24 | alertNode = document.createElement("div") 25 | 26 | alertNode.classList.add("framerAlertBackground") 27 | alertNode.innerHTML = html 28 | 29 | document.addEventListener("DOMContentLoaded", function(event) { 30 | document.body.appendChild(alertNode) 31 | }) 32 | 33 | window.dismissAlert = dismissAlert; 34 | } 35 | 36 | function showBrowserAlert() { 37 | var html = "" 38 | html += "
" 39 | html += "Error: Not A WebKit Browser" 40 | html += "Your browser is not supported.
Please use Safari or Chrome.
" 41 | html += "Try anyway" 42 | html += "
" 43 | 44 | showAlert(html) 45 | } 46 | 47 | function showFileLoadingAlert() { 48 | var html = "" 49 | html += "
" 50 | html += "Error: Local File Restrictions" 51 | html += "Preview this prototype with Framer Mirror or learn more about " 52 | html += "file restrictions.
" 53 | html += "Try anyway" 54 | html += "
" 55 | 56 | showAlert(html) 57 | } 58 | 59 | function loadProject() { 60 | CoffeeScript.load("app.coffee") 61 | } 62 | 63 | function setDefaultPageTitle() { 64 | // If no title was set we set it to the project folder name so 65 | // you get a nice name on iOS if you bookmark to desktop. 66 | document.addEventListener("DOMContentLoaded", function() { 67 | if (document.title == "") { 68 | if (window.FramerStudioInfo && window.FramerStudioInfo.documentTitle) { 69 | document.title = window.FramerStudioInfo.documentTitle 70 | } else { 71 | document.title = window.location.pathname.replace(/\//g, "") 72 | } 73 | } 74 | }) 75 | } 76 | 77 | function init() { 78 | 79 | if (Utils.isFramerStudio()) { 80 | return 81 | } 82 | 83 | setDefaultPageTitle() 84 | 85 | if (!isCompatibleBrowser()) { 86 | return showBrowserAlert() 87 | } 88 | 89 | if (!isFileLoadingAllowed()) { 90 | return showFileLoadingAlert() 91 | } 92 | 93 | loadProject() 94 | 95 | } 96 | 97 | init() 98 | 99 | })() 100 | -------------------------------------------------------------------------------- /ClassAnimationDemo.framer/app.coffee: -------------------------------------------------------------------------------- 1 | {ClassAnimation} = require "ClassAnimation" 2 | {Style} = require "ClassAnimation" 3 | 4 | # DEMO SETUP 5 | 6 | # Import Varela Round from Google Fonts 7 | "@import url('https://fonts.googleapis.com/css?family=Varela+Round'); body {font-family: 'Varela Round', 'Varela', sans-serif}".css() 8 | 9 | Screen.backgroundColor = "#00003C" 10 | Canvas.backgroundColor = "#000000" 11 | 12 | texts = new Layer 13 | width: Screen.width 14 | height: 280 15 | y: 260 16 | backgroundColor: null 17 | 18 | stars = new Layer 19 | x: Screen.width/2 - 326 - 20 20 | y: 30 21 | width: 652 22 | height: 290 23 | backgroundColor: null 24 | 25 | delivery = new Layer 26 | x: 0, y: 720 27 | width: 750 28 | height: 346 29 | backgroundColor: null 30 | 31 | 32 | 33 | # STYLES —————————————————————————————————————— 34 | # ————————————————————————————————————————————— 35 | # ————————————————————————————————————————————— 36 | 37 | # First, hide all elements in ›second‹ class 38 | Style "second", 39 | display: "none" 40 | 41 | # Set styles for our headlines 42 | Style "headline", 43 | fontSize: 88 44 | lineHeight: 100 45 | color: "#D3D3FF" 46 | fontWeight: 400 47 | textAlign: "center" 48 | 49 | Style "status", 50 | fontSize: 24 51 | lineHeight: 60 52 | letterSpacing: 11 53 | fontWeight: 400 54 | color: "#5856D6" 55 | textTransform: "uppercase" 56 | textAlign: "center" 57 | 58 | 59 | 60 | # LAYERS' CONTENTS ———————————————————————————— 61 | # ————————————————————————————————————————————— 62 | # ————————————————————————————————————————————— 63 | 64 | texts.html = """ 65 |

Pssst!

66 |

Shipping
Dreams

67 | 68 |

Yasss!

69 |

Dreams
on the way

70 | """ 71 | 72 | 73 | # Import SVGs from files for cleaner code ————— 74 | # Individual objects (paths) in Sketch-generated SVGs 75 | # will have ›id‹ attribute equal to the layer name 76 | # in Sketch. You simply need to rewrite them from ›id‹ 77 | # to ›class‹ to make them work with the module 78 | 79 | stars.html = Utils.domLoadDataSync("images/stars.svg") 80 | delivery.html = Utils.domLoadDataSync("images/delivery.svg") 81 | 82 | 83 | 84 | # ANIMATIONS SETUP ———————————————————————————— 85 | # ————————————————————————————————————————————— 86 | # ————————————————————————————————————————————— 87 | 88 | # Since both wheel objects in SVG belong to one 89 | # ›wheel‹ class, we can animate both with one animation 90 | toWheel = new ClassAnimation "wheel", 91 | rotateZ: 360 92 | options: 93 | curve: "linear" 94 | repeat: true 95 | 96 | # Add some bouncing effect for Dream Delivery truck 97 | truckRumble = new ClassAnimation "truckBody", 98 | translateY: 5 99 | rotateZ: 0.2 100 | options: 101 | time: 0.3 102 | repeat: true 103 | 104 | # Show off some driving skills 105 | driveSlightly = new ClassAnimation "movingElements", 106 | translateX: 180 107 | options: 108 | time: 5 109 | curve: [0,0,.58,1] # Bezier Curve 110 | 111 | # This will move truck out of screen 112 | driveAway = new ClassAnimation "movingElements", 113 | translateX: 800 114 | options: 115 | time: 0.7 116 | 117 | # Let's animate the stars... cause we can 118 | starsGroupOne = new ClassAnimation "stars1", 119 | scale: 0.97 120 | rotateZ: -1 121 | fill: "#2E2C87" 122 | options: 123 | time: 2 124 | repeat: true 125 | 126 | starsGroupTwo = new ClassAnimation "stars2", 127 | scale: 1.02 128 | rotateZ: 1 129 | options: 130 | time: 2 131 | repeat: true 132 | 133 | # Add unnecessary pulsing effect to ›first‹ headers 134 | pulseHead = new ClassAnimation "first", 135 | opacity: 0.5 136 | options: 137 | time: 0.5 138 | repeat: true 139 | 140 | 141 | 142 | # START THE ANIMATIONS ———————————————————————— 143 | # ————————————————————————————————————————————— 144 | # ————————————————————————————————————————————— 145 | 146 | starsGroupOne.start() 147 | starsGroupTwo.start() 148 | 149 | pulseHead.start() 150 | 151 | truckRumble.start() 152 | toWheel.start() 153 | 154 | driveSlightly.start() 155 | 156 | 157 | # When ›driveSlightly‹ ends, lets ›driveAway‹ 158 | # and show the elements in ›second‹ class 159 | 160 | driveSlightly.on Events.AnimationEnd, -> 161 | driveAway.start() 162 | pulseHead.fadeOut() 163 | 164 | pulseHead.onAnimationEnd -> 165 | "second".fadeIn() 166 | 167 | -------------------------------------------------------------------------------- /AnimatableProperties.md: -------------------------------------------------------------------------------- 1 | # List of Supported Properties in Velocity.js 2 | 3 | `opacity` is unitless *(works for SVG)* 4 | 5 | `width` *(works for SVG)* 6 | 7 | `height` *(works for SVG)* 8 | 9 | `minWidth` 10 | 11 | `minHeight` 12 | 13 | `maxWidth` 14 | 15 | `maxHeight` 16 | 17 | 18 | ## Positioning 19 | `padding` 20 | 21 | `paddingTop` 22 | 23 | `paddingRight` 24 | 25 | `paddingBottom` 26 | 27 | `paddingLeft` 28 | 29 | `top` 30 | 31 | `right` 32 | 33 | `bottom` 34 | 35 | `left` 36 | 37 | `margin` 38 | 39 | `marginTop` 40 | 41 | `marginRight` 42 | 43 | `marginBottom` 44 | 45 | `marginLeft` 46 | 47 | ## Border Dimensions 48 | `borderWidth` 49 | 50 | `borderTopWidth` 51 | 52 | `borderRightWidth` 53 | 54 | `borderBottomWidth` 55 | 56 | `borderLeftWidth` 57 | 58 | `borderRadius` 59 | 60 | `outlineWidth` 61 | 62 | ## Text Properties 63 | `lineHeight` 64 | 65 | `fontSize` *(works for SVG)* 66 | 67 | `letterSpacing` *(works for SVG)* 68 | 69 | `wordSpacing` *(works for SVG)* 70 | 71 | ## Text Colors 72 | `color` 73 | 74 | `colorRed` is unitless 75 | 76 | `colorGreen` is unitless 77 | 78 | `colorBlue` is unitless 79 | 80 | `colorAlpha` is unitless 81 | 82 | ## Background Colors 83 | `backgroundColor` 84 | 85 | `backgroundColorRed` is unitless 86 | 87 | `backgroundColorGreen` is unitless 88 | 89 | `backgroundColorBlue` is unitless 90 | 91 | `backgroundColorAlpha` is unitless 92 | 93 | ## Border Colors 94 | `borderColor` 95 | 96 | `borderTopColor` 97 | 98 | `borderRightColor` 99 | 100 | `borderBottomColor` 101 | 102 | `borderLeftColor` 103 | 104 | ## Red Channel for Borders 105 | `borderColorRed` is unitless 106 | 107 | `borderTopColorRed` is unitless 108 | 109 | `borderRightColorRed` is unitless 110 | 111 | `borderBottomColorRed` is unitless 112 | 113 | `borderLeftColorRed` is unitless 114 | 115 | ### Green 116 | `borderColorGreen` is unitless 117 | 118 | `borderTopColorGreen` is unitless 119 | 120 | `borderRightColorGreen` is unitless 121 | 122 | `borderBottomColorGreen` is unitless 123 | 124 | `borderLeftColorGreen` is unitless 125 | 126 | ### Blue 127 | `borderColorBlue` is unitless 128 | 129 | `borderTopColorBlue` is unitless 130 | 131 | `borderRightColorBlue` is unitless 132 | 133 | `borderBottomColorBlue` is unitless 134 | 135 | `borderLeftColorBlue` is unitless 136 | 137 | ### Alpha 138 | `borderColorAlpha` is unitless 139 | 140 | `borderTopColorAlpha` is unitless 141 | 142 | `borderRightColorAlpha` is unitless 143 | 144 | `borderBottomColorAlpha` is unitless 145 | 146 | `borderLeftColorAlpha` is unitless 147 | 148 | ## Outline Color 149 | `outlineColor` 150 | 151 | `outlineColorRed` is unitless 152 | 153 | `outlineColorGreen` is unitless 154 | 155 | `outlineColorBlue` is unitless 156 | 157 | `outlineColorAlpha` is unitless 158 | 159 | ## Advanced Styles 160 | `backgroundPositionX` 161 | 162 | `backgroundPositionY` 163 | 164 | `textShadowX` 165 | 166 | `textShadowY` 167 | 168 | `textShadowBlur ` 169 | 170 | `boxShadowX` 171 | 172 | `boxShadowY` 173 | 174 | `boxShadowBlur` 175 | 176 | `boxShadowSpread` 177 | 178 | ## Transformations 179 | `translateX` *(works for SVG)* 180 | 181 | `translateY` *(works for SVG)* 182 | 183 | `translateZ` *(works for SVG)* 184 | 185 | `scale` is unitless *(works for SVG)* 186 | 187 | `scaleX` is unitless *(works for SVG)* 188 | 189 | `scaleY` is unitless *(works for SVG)* 190 | 191 | `scaleZ` is unitless 192 | 193 | `rotateX` is unitless *(works for SVG)* 194 | 195 | `rotateY` is unitless *(works for SVG)* 196 | 197 | `rotateZ` is unitless *(works for SVG)* 198 | 199 | `skewX` is unitless *(works for SVG)* 200 | 201 | `skewZ` is unitless *(works for SVG)* 202 | 203 | `transformPerspective` *(works for SVG)* 204 | 205 | `perspective` 206 | 207 | `perspectiveOriginX` 208 | 209 | `perspectiveOriginY` 210 | 211 | `transformOriginX` *(works for SVG)* 212 | 213 | `transformOriginY` *(works for SVG)* 214 | 215 | `transformOriginZ` *(works for SVG)* 216 | 217 | `clipTop` 218 | 219 | `clipRight` 220 | 221 | `clipBottom` 222 | 223 | `clipLeft` 224 | 225 | `blur` 226 | 227 | For full CSS support, see [Velocity.js Tester](http://velocityjs.org/#cssSupport) 228 | 229 | # SVG-only Animatables 230 | `x` 231 | 232 | `y` 233 | 234 | `cx` 235 | 236 | `cy` 237 | 238 | `r` 239 | 240 | `rx` 241 | 242 | `ry` 243 | 244 | `x1` 245 | 246 | `x2` 247 | 248 | `y1` 249 | 250 | `y2` 251 | 252 | `strokeDasharray` 253 | 254 | `strokeDashoffset` 255 | 256 | `strokeWidth` 257 | 258 | `strokeMiterlimit` is unitless 259 | 260 | `startOffset` 261 | 262 | `fill` 263 | 264 | `fillRed` is unitless 265 | 266 | `fillGreen` is unitless 267 | 268 | `fillBlue` is unitless 269 | 270 | `fillAlpha` is unitless 271 | 272 | `fillOpacity` is unitless 273 | 274 | `stroke` 275 | 276 | `strokeRed` is unitless 277 | 278 | `strokeGreen` is unitless 279 | 280 | `strokeBlue` is unitless 281 | 282 | `strokeAlpha` is unitless 283 | 284 | `strokeOpacity` is unitless 285 | 286 | `stopColor` 287 | 288 | `stopColorRed` is unitless 289 | 290 | `stopColorGreen` is unitless 291 | 292 | `stopColorBlue` is unitless 293 | 294 | `stopColorAlpha` is unitless 295 | 296 | `stopOpacity` is unitless 297 | 298 | `offset` 299 | 300 | For full SVG support, see [Velocity.js site](http://velocityjs.org/#svg) 301 | 302 | If you find property you know Velocity can animate but ClassAnimation returns 303 | as unsupported, please [send me an e-mail about the issue](mailto:kyselyradek@gmail.com) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ClassAnimation Module for Framer 2 | 3 | [![ClassAnimation Example](demo.gif)](https://framer.cloud/aMvYj/) 4 | 5 | A simple module that allows you **animate individual HTML and SVG elements** inside your 6 | layer's **html** property in addition to Framer's core per-layer Animation. 7 | 8 | Shout out to [Velocity.js](http://velocityjs.org) for making great animation library ♥ 9 | 10 | ## Install 11 | 12 | Install with Framer Modules 14 | 15 | 16 | or 17 | 18 | 1. [Download the module](ClassAnimation.coffee?raw=true) 19 | 2. Copy the `ClassAnimation.coffee` file to your prototype's `modules` folder. 20 | 3. Call `{ClassAnimation} = require "ClassAnimation"` in your Framer prototype. 21 | 22 | ClassAnimation module depends on Velocity.js library. If you want to use your prototype offline, 23 | please download [velocity.min.js](https://github.com/julianshapiro/velocity/raw/master/velocity.min.js) 24 | and also include it in your 25 | `modules` folder. Otherwise, the module will download the library from web and you don't need to worry about it. 26 | 27 | ## How to Use [(see the live demo)](https://framer.cloud/aMvYj/) 28 | 29 | Import the module and optional handy [Style](#bonus-set-your-styles-like-a-human-being) function for fancy CSS initiation. 30 | ```coffeescript 31 | {ClassAnimation} = require "ClassAnimation" 32 | {Style} = require "ClassAnimation" # Optional 33 | ``` 34 | 35 | ### Animate 36 | 37 | Module is designed to mimic Framer's core `Animation` as much as possible 38 | in order to make it easier to work with. 39 | 40 | Thanks to that, it takes similar inputs as the default Framer animation—just 41 | instead of `layer` use `string` with the name of class you want to animate. 42 | 43 | 44 | ```coffeescript 45 | socialCard = new Layer 46 | 47 | socialCard.html = 48 | """ 49 |

Paul Paulson

50 | Montessori Teacher 51 | """ 52 | 53 | # Create a new animation for "className" class 54 | animationA = new ClassAnimation "className", 55 | color: "#ff00ff" 56 | fontSize: 60 57 | lineHeight: 70 58 | borderWidth: 5 59 | 60 | 61 | animationA.start() 62 | ``` 63 | 64 | ### Animatable Properties [(see the full list)](AnimatableProperties.md) 65 | Because the module depends on Velocity.js library, it will animate whatever Velocity can animate. 66 | 67 | Although most properties are same as in core animation, there are very few exceptions, such as instead 68 | `rotation` you might want to use `rotateZ`. 69 | 70 | ClassAnimation *will print message if you use unsupported property* and will log the full 71 | list of supported ones to the console. 72 | 73 | If you find property you know Velocity can animate but ClassAnimation returns 74 | as unsupported, please [send me an e-mail about the issue](mailto:kyselyradek@gmail.com) 75 | 76 | 77 | ### Options 78 | Same options as core Animation are available, only `curve` takes different value format 79 | and `repeat` has the option `true` to repeat forever. 80 | 81 | ```coffeescript 82 | animationB = new ClassAnimation "className", 83 | color: "#ff00ff" 84 | fontSize: 60 85 | lineHeight: 70 86 | borderWidth: 5 87 | options: 88 | time: 2 89 | delay: .5 90 | repeat: 2 91 | curve: "spring" 92 | 93 | animationB.start() 94 | ``` 95 | 96 | #### Curve Options 97 | `curve: "linear"` 98 | 99 | `curve: "ease"` 100 | 101 | `curve: "ease-in"` 102 | 103 | `curve: "ease-out"` 104 | 105 | `curve: "ease-in-out"` *(default)* 106 | 107 | `curve: [0, 1, 0, 1]` 4 parameters for [Bézier curve](http://cubic-bezier.com/) 108 | 109 | `curve: "spring"` 110 | 111 | `curve: [500, 20]` custom spring [tension, friction] 112 | 113 | For all the available magic and per-property curves, see [Velocity.js docs](http://velocityjs.org/#easing) 114 | 115 | 116 | ### Methods 117 | **`animation.toggle()`** switches between default and animated states 118 | 119 | **`animation.fadeIn()`** / **`animation.fadeOut()`** shows/hides elements targeted by animation 120 | 121 | *plus methods you know from core Animation:* 122 | 123 | **`animation.start()`** 124 | 125 | **`animation.stop()`** 126 | 127 | **`animation.reverse()`** 128 | 129 | **`animation.reset()`** 130 | 131 | **`animation.restart()`** 132 | 133 | ### Events 134 | **`Events.Animation`** returns progress `(0 to 1)` and remaining time `(ms)` **during animation** 135 | 136 | *plus events you know core Animation:* 137 | 138 | **`Events.AnimationStart`** doesn't return any value 139 | 140 | **`Events.AnimationStop`** doesn't return any value 141 | 142 | **`Events.AnimationEnd`** doesn't return any value 143 | 144 | ```coffeescript 145 | # Event Shortcuts 146 | 147 | animationA.onAnimation (prog, remain) -> 148 | print "#{prog*100}% done" 149 | print "#{remain}ms left" 150 | 151 | animationA.onAnimationStart -> 152 | print "Animation started" 153 | 154 | animationA.onAnimationStop -> 155 | print "Animation stopped" 156 | 157 | animationA.onAnimationEnd -> 158 | print "Animation ended" 159 | ``` 160 | 161 | ## "But where's my .animate()?" 162 | Right here. 163 | 164 | ```coffeescript 165 | "className".animate 166 | color: "#ff00ff" 167 | fontSize: 60 168 | lineHeight: 70 169 | borderWidth: 5 170 | options: 171 | time: 2 172 | delay: .5 173 | repeat: 2 174 | curve: "spring" 175 | 176 | 177 | # You can also directly fadeIn/Out on any string 178 | "className".fadeOut() 179 | "differentClass".fadeIn() 180 | ``` 181 | 182 | 183 | ## Bonus: Set your styles like a human being 184 | 185 | In case you tried writing your styles as CSS strings—or even worse, in-line 186 | inside the layer's html property—see this: 187 | 188 | ```coffeescript 189 | {Style} = require "ClassAnimation" 190 | 191 | # Style(name,props) helps you set your styles 192 | # in a cultivated way (as object, in camelCase) 193 | Style "className", 194 | fontSize: 50 195 | color: "#ff0000" 196 | borderWidth: 1 197 | borderColor: "rgb(255, 255, 0)" 198 | 199 | 200 | socialCard = new Layer 201 | socialCard.html = 202 | """ 203 |

Paul Paulson

204 | Montessori Teacher 205 | """ 206 | 207 | 208 | # PS: If you already have a CSS string, just call .css() 209 | # method on it (this doesn't require {Style} import) 210 | ".differentClass {font-size: 30px; color: #ff00ff}".css() 211 | ``` 212 | -------------------------------------------------------------------------------- /ClassAnimation.coffee: -------------------------------------------------------------------------------- 1 | # VERSION 1.0.1 2 | # HELPER METHODS AND FUNCTIONS —————————————————————————— 3 | String::toCamel = () -> 4 | @replace( /([-_][a-z])/g, ($1) -> return $1.toUpperCase().replace(/[-_]/,'') ) 5 | 6 | String::toCss = () -> 7 | @replace( /([A-Z])/g, ($1) -> return "-"+$1.toLowerCase() ); 8 | 9 | point = (cl) -> if cl.indexOf(".") is 0 then cl else ".#{cl}" 10 | 11 | getHex = (c) -> 12 | hexa = c.toString(16) 13 | return if hexa.length is 1 then "0#{hexa}" else hexa 14 | 15 | rgb = ([r,g,b] = color) -> "##{getHex(r)}#{getHex(g)}#{getHex(b)}"; 16 | 17 | String::checkColor = () -> 18 | if @indexOf("rgb") != 0 19 | return @ 20 | else 21 | color = [r,g,b] = @split("(")[1].split(",") 22 | return rgb( (parseInt(value) for value in color) ) 23 | 24 | 25 | renameKey = (obj, oldName, newName) -> 26 | if oldName is newName 27 | return obj 28 | 29 | if obj.hasOwnProperty oldName 30 | obj[newName] = obj[oldName] 31 | delete obj[oldName] 32 | 33 | return obj 34 | 35 | 36 | # INCLUDE VELOCITY.JS LIBRARY ——————————————————————————— 37 | insertVelocity = (script, webscript, name) -> 38 | try 39 | Utils.domLoadScriptSync(script) 40 | Utils.domComplete -> 41 | console.log "%c#{script} Successfully Included", "background: #DDFFE3; color: #007814" 42 | catch e 43 | console.log "%cCouldn't load '#{script}' locally. Will try downloading from web.", "background: #FFF0DB; color: #D27B00" 44 | try 45 | Utils.domLoadScriptSync(webscript) 46 | Utils.domComplete -> 47 | console.log "%c#{name} Successfully Included from Web", "background: #DDFFE3; color: #007814" 48 | catch e 49 | throw Error("ClassAnimation: Sorry, I don't know how to animate without #{name} library") 50 | 51 | 52 | insertVelocity("modules/velocity.min.js", "//cdn.jsdelivr.net/velocity/1.4/velocity.min.js", "Velocity.js") 53 | #insertVelocity("modules/velocity.ui.min.js", "//cdn.jsdelivr.net/velocity/1.4/velocity.ui.min.js", "Velocity UI") 54 | 55 | 56 | 57 | # OBJECT WITH PROPERTIES AND THEIR OPTIONS —————————————— 58 | # [UNITLESS, ANIMATABLE, SVG] 59 | ANIMATABLES = 60 | fontWeight: [true, false] 61 | fontFamily: [true, false] 62 | textAlign: [false, false] 63 | opacity: [true, true, true] 64 | zIndex: [true, false] 65 | 66 | width: [false, true, true] 67 | height: [false, true, true] 68 | minWidth: [false, true] 69 | minHeight: [false, true] 70 | maxWidth: [false, true] 71 | maxHeight: [false, true] 72 | 73 | "\n— POSITIONING —": [false, true] 74 | padding: [false, true] 75 | paddingTop: [false, true] 76 | paddingRight: [false, true] 77 | paddingBottom: [false, true] 78 | paddingLeft: [false, true] 79 | 80 | top: [false, true] 81 | right: [false, true] 82 | bottom: [false, true] 83 | left: [false, true] 84 | 85 | margin: [false, true] 86 | marginTop: [false, true] 87 | marginRight: [false, true] 88 | marginBottom: [false, true] 89 | marginLeft: [false, true] 90 | 91 | "\n— BORDER DIMENSIONS —": [false, true] 92 | borderWidth: [false, true] 93 | borderTopWidth: [false, true] 94 | borderRightWidth: [false, true] 95 | borderBottomWidth: [false, true] 96 | borderLeftWidth: [false, true] 97 | borderRadius: [false, true] 98 | outlineWidth: [false, true] 99 | 100 | "\n— TEXT PROPERTIES —": [false, true] 101 | lineHeight: [false, true] 102 | fontSize: [false, true, true] 103 | letterSpacing: [false, true, true] 104 | wordSpacing: [false, true, true] 105 | 106 | "\n— TEXT COLORS —": [false, true] 107 | color: [false, true] 108 | colorRed: [true, true] 109 | colorGreen: [true, true] 110 | colorBlue: [true, true] 111 | colorAlpha: [true, true] 112 | 113 | "\n— BACKGROUND COLORS —": [false, true] 114 | backgroundColor: [false, true] 115 | backgroundColorRed: [true, true] 116 | backgroundColorGreen: [true, true] 117 | backgroundColorBlue: [true, true] 118 | backgroundColorAlpha: [true, true] 119 | 120 | "\n— BORDER COLORS —": [false, true] 121 | borderColor: [false, true] 122 | borderTopColor: [false, true] 123 | borderRightColor: [false, true] 124 | borderBottomColor: [false, true] 125 | borderLeftColor: [false, true] 126 | 127 | "\n— RED CHANNEL OF BORDERS —": [false, true] 128 | borderColorRed: [true, true] 129 | borderTopColorRed: [true, true] 130 | borderRightColorRed: [true, true] 131 | borderBottomColorRed: [true, true] 132 | borderLeftColorRed: [true, true] 133 | 134 | "\nGREEN:": [false, true] 135 | borderColorGreen: [true, true] 136 | borderTopColorGreen: [true, true] 137 | borderRightColorGreen: [true, true] 138 | borderBottomColorGreen: [true, true] 139 | borderLeftColorGreen: [true, true] 140 | 141 | "\nBLUE:": [false, true] 142 | borderColorBlue: [true, true] 143 | borderTopColorBlue: [true, true] 144 | borderRightColorBlue: [true, true] 145 | borderBottomColorBlue: [true, true] 146 | borderLeftColorBlue: [true, true] 147 | 148 | "\nALPHA:": [false, true] 149 | borderColorAlpha: [true, true] 150 | borderTopColorAlpha: [true, true] 151 | borderRightColorAlpha: [true, true] 152 | borderBottomColorAlpha: [true, true] 153 | borderLeftColorAlpha: [true, true] 154 | 155 | "\n— OUTLINE COLOR —": [false, true] 156 | outlineColor: [false, true] 157 | outlineColorRed: [true, true] 158 | outlineColorGreen: [true, true] 159 | outlineColorBlue: [true, true] 160 | outlineColorAlpha: [true, true] 161 | 162 | "\n— ADVANCED STYLES —": [false, true] 163 | backgroundPositionX: [false, true] 164 | backgroundPositionY: [false, true] 165 | textShadowX: [false, true] 166 | textShadowY: [false, true] 167 | textShadowBlur: [false, true] 168 | boxShadowX: [false, true] 169 | boxShadowY: [false, true] 170 | boxShadowBlur: [false, true] 171 | boxShadowSpread: [false, true] 172 | 173 | "\n— TRANSFORMATIONS —": [false, true] 174 | translateX: [false, true, true] 175 | translateY: [false, true, true] 176 | translateZ: [false, true, true] 177 | 178 | scale: [true, true, true] 179 | scaleX: [true, true, true] 180 | scaleY: [true, true, true] 181 | scaleZ: [true, true] 182 | 183 | rotateX: [true, true, true] 184 | rotateY: [true, true, true] 185 | rotateZ: [true, true, true] 186 | 187 | skewX: [true, true, true] 188 | skewZ: [true, true, true] 189 | 190 | transformPerspective: [false, true, true] 191 | perspective: [false, true] 192 | perspectiveOriginX: [false, true] 193 | perspectiveOriginY: [false, true] 194 | transformOriginX: [false, true, true] 195 | transformOriginY: [false, true, true] 196 | transformOriginZ: [false, true, true] 197 | 198 | clipTop: [false, true] 199 | clipRight: [false, true] 200 | clipBottom: [false, true] 201 | clipLeft: [false, true] 202 | 203 | blur: [false, true] 204 | 205 | "\n— SVG-only —": [false, true] 206 | x: [false, true] 207 | y: [false, true] 208 | cx: [false, true] 209 | cy: [false, true] 210 | r: [false, true] 211 | rx: [false, true] 212 | ry: [false, true] 213 | x1: [false, true] 214 | x2: [false, true] 215 | y1: [false, true] 216 | y2: [false, true] 217 | 218 | strokeDasharray: [false, true] 219 | strokeDashoffset: [false, true] 220 | strokeWidth: [false, true] 221 | strokeMiterlimit: [true, true] 222 | startOffset: [false, true] 223 | 224 | fill: [false, true] 225 | fillRed: [true, true] 226 | fillGreen: [true, true] 227 | fillBlue: [true, true] 228 | fillAlpha: [true, true] 229 | fillOpacity: [true, true] 230 | 231 | stroke: [false, true] 232 | strokeRed: [true, true] 233 | strokeGreen: [true, true] 234 | strokeBlue: [true, true] 235 | strokeAlpha: [true, true] 236 | strokeOpacity: [true, true] 237 | 238 | stopColor: [false, true] 239 | stopColorRed: [true, true] 240 | stopColorGreen: [true, true] 241 | stopColorBlue: [true, true] 242 | stopColorAlpha: [true, true] 243 | stopOpacity: [true, true] 244 | 245 | offset: [false, true] 246 | 247 | isAnimatable = (property, thr) -> 248 | if typeof ANIMATABLES[property] is 'object' && ANIMATABLES[property][1] 249 | return true 250 | else 251 | moduleHelp() if thr 252 | print "›#{property}‹ is not animatable. Open console (Target icon bottom left) and Cmd+R to see animatable properties." if thr 253 | return false 254 | 255 | 256 | 257 | # GET CSS DECLARATIONS FROM CLASS ——————————————————————— 258 | getCSS = (cl, checker) -> 259 | classes = document.getElementsByClassName(cl); 260 | cssDOM = window.getComputedStyle(classes[0], null) 261 | 262 | cssObject = {} 263 | 264 | for val in [0...cssDOM.length] 265 | currentVal = cssDOM[val] 266 | if currentVal.toCamel() of checker 267 | cssObject[currentVal.toCamel()] = cssDOM.getPropertyValue(currentVal) 268 | cssObject[currentVal.toCamel()] = cssDOM.getPropertyValue(currentVal).checkColor() if typeof cssDOM.getPropertyValue(currentVal) is "string" 269 | 270 | return cssObject 271 | 272 | 273 | # MAIN CSSANIMATION CLASS ——————————————————————————————— 274 | # ——————————————————————————————————————————————————————— 275 | # ——————————————————————————————————————————————————————— 276 | class exports.ClassAnimation 277 | constructor: (name, end = {}, init, options = {}, optionsFactor = 1000) -> 278 | @class = name 279 | @target = document.querySelectorAll(".#{@class}"); 280 | 281 | @endState = end 282 | 283 | @options = @endState.options || options 284 | @options.time = @options.time*optionsFactor || 1000 285 | @options.delay = @options.delay*optionsFactor || 0 286 | @options.repeat = @options.repeat || false 287 | @options.curve = @options.curve || "ease-in-out" 288 | 289 | delete @endState.options 290 | 291 | for key, val of @endState 292 | @endState[key] = val.checkColor() if typeof val is "string" 293 | renameKey(@endState, key, key.toCamel()) 294 | isAnimatable(key.toCamel(), true) 295 | 296 | @initState = init || getCSS(@class, @endState) 297 | 298 | return @ 299 | 300 | 301 | # METHODS ——————————————————————————————————————————— 302 | 303 | start: () -> 304 | _this = @ 305 | Utils.domComplete -> 306 | Velocity _this.target, 307 | _this.endState, 308 | duration: _this.options.time 309 | delay: _this.options.delay 310 | loop: _this.options.repeat 311 | easing: _this.options.curve 312 | begin: -> _this.onStart?() 313 | complete: -> _this.onEnd?() 314 | progress: (elements, complete, remaining, start) -> 315 | _this.onAnim?(complete, remaining) 316 | @notFirst = true 317 | return @ 318 | 319 | stop: () -> 320 | @onStop?() 321 | Velocity(@target, "stop") 322 | return @ 323 | 324 | reset: () -> 325 | Velocity @target, 326 | @initState, 327 | duration: 0 328 | delay: 0 329 | return @ 330 | 331 | restart: () -> 332 | @reset() 333 | @start() 334 | return @ 335 | 336 | finish: () -> 337 | Velocity(@target, "finish") 338 | return @ 339 | 340 | toggle: () -> 341 | _this = @ 342 | if @notFirst 343 | Velocity @target, 344 | "reverse", 345 | begin: -> _this.onStart?() 346 | complete: -> _this.onEnd?() 347 | progress: (elements, complete, remaining, start) -> 348 | _this.onAnim?(complete, remaining) 349 | else 350 | @start() 351 | return @ 352 | 353 | reverse: () -> 354 | return new ClassAnimation(@class, @initState, @endState, @options, 1) 355 | 356 | fadeIn: () -> 357 | _this = @ 358 | Velocity @target, 359 | "fadeIn", 360 | begin: -> _this.onStart?() 361 | complete: -> _this.onEnd?() 362 | progress: (elements, complete, remaining, start) -> 363 | _this.onAnim?(complete, remaining) 364 | return @ 365 | 366 | fadeOut: () -> 367 | _this = @ 368 | Velocity @target, 369 | "fadeOut", 370 | begin: -> _this.onStart?() 371 | complete: -> _this.onEnd?() 372 | progress: (elements, complete, remaining, start) -> 373 | _this.onAnim?(complete, remaining) 374 | return @ 375 | 376 | help: () -> moduleHelp() 377 | 378 | 379 | # METHOD ALIASES ———————————————————————————————————— 380 | 381 | switch: -> @toggle() 382 | stateSwitch: -> @toggle() 383 | revert: -> @reverse() 384 | inverse: -> @reverse() 385 | invert: -> @reverse() 386 | 387 | 388 | # EVENTS ———————————————————————————————————————————— 389 | 390 | Events.Animation = "move" 391 | 392 | on: (eventName, cb) -> 393 | switch eventName 394 | when "end" then @onAnimationEnd(cb) 395 | when "stop" then @onAnimationStop(cb) 396 | when "start" then @onAnimationStart(cb) 397 | when "move" then @onAnimation(cb) 398 | else throw Error("Sorry, I'm too stupid to handle this event") 399 | 400 | onAnimationEnd: (cb) -> @onEnd = @eventReturnData(cb) 401 | onAnimationStop: (cb) -> @onStop = @eventReturnData(cb) 402 | onAnimationStart: (cb) -> @onStart = @eventReturnData(cb) 403 | onAnimation: (cb) -> @onAnim = @eventReturnData(cb) 404 | 405 | eventReturnData: (cb) -> 406 | return (prog, rem) -> cb.call?(@, prog, rem) 407 | 408 | 409 | 410 | # .ANIMATE METHOD ——————————————————————————————————————— 411 | String::animate = (props) -> 412 | (new exports.ClassAnimation @, props).start() 413 | 414 | String::fadeIn = () -> @animate().fadeIn() 415 | 416 | String::fadeOut = () -> @animate().fadeOut() 417 | 418 | 419 | 420 | # STYLE INIT ———————————————————————————————————————————— 421 | String::css = () -> 422 | Utils.insertCSS(@) 423 | 424 | addUnit = (value, key) -> 425 | if typeof value is 'number' && isAnimatable(key, false) && !ANIMATABLES[key][0] then "#{value}px" else value 426 | 427 | exports.Style = (cl, css) -> 428 | insertableCss = "#{point(cl)} {border-style: solid; border-width: 0; " 429 | for key, val of css 430 | css[key] = addUnit( css[key], key.toCamel() ) 431 | insertableCss += "#{key.toCss()}:#{css[key]}; " 432 | insertableCss += "}" 433 | 434 | Utils.insertCSS(insertableCss) 435 | 436 | return cl 437 | 438 | exports.style = (cl, css) -> exports.Style(cl, css) 439 | 440 | 441 | 442 | # HELP —————————————————————————————————————————————————— 443 | moduleHelp = () -> 444 | console.log "\n" 445 | console.log "LIST OF ANIMATABLE PROPERTIES" 446 | for key, val of ANIMATABLES 447 | help = if ANIMATABLES[key][0] is true then "%c#{key} %cis unitless" else "%c#{key} %c" 448 | help += if ANIMATABLES[key][2] is true then " %c(works for SVG)" else " %c" 449 | console.log help, "color: #007AFF", "color: #000", "color: #007814" if ANIMATABLES[key][1] is true 450 | console.log "For full support, go to http://velocityjs.org/#cssSupport" 451 | -------------------------------------------------------------------------------- /ClassAnimationDemo.framer/modules/ClassAnimation.coffee: -------------------------------------------------------------------------------- 1 | # VERSION 1.0.0 2 | # HELPER METHODS AND FUNCTIONS —————————————————————————— 3 | String::toCamel = () -> 4 | @replace( /([-_][a-z])/g, ($1) -> return $1.toUpperCase().replace(/[-_]/,'') ) 5 | 6 | String::toCss = () -> 7 | @replace( /([A-Z])/g, ($1) -> return "-"+$1.toLowerCase() ); 8 | 9 | point = (cl) -> if cl.indexOf(".") is 0 then cl else ".#{cl}" 10 | 11 | getHex = (c) -> 12 | hexa = c.toString(16) 13 | return if hexa.length is 1 then "0#{hexa}" else hexa 14 | 15 | rgb = ([r,g,b] = color) -> "##{getHex(r)}#{getHex(g)}#{getHex(b)}"; 16 | 17 | String::checkColor = () -> 18 | if @indexOf("rgb") != 0 19 | return @ 20 | else 21 | color = [r,g,b] = @split("(")[1].split(",") 22 | return rgb( (parseInt(value) for value in color) ) 23 | 24 | 25 | renameKey = (obj, oldName, newName) -> 26 | if oldName is newName 27 | return obj 28 | 29 | if obj.hasOwnProperty oldName 30 | obj[newName] = obj[oldName] 31 | delete obj[oldName] 32 | 33 | return obj 34 | 35 | 36 | # INCLUDE VELOCITY.JS LIBRARY ——————————————————————————— 37 | insertVelocity = (script, webscript, name) -> 38 | try 39 | Utils.domLoadScriptSync(script) 40 | Utils.domComplete -> 41 | console.log "%c#{script} Successfully Included", "background: #DDFFE3; color: #007814" 42 | catch e 43 | console.log "%cCouldn't load '#{script}' locally. Will try downloading from web.", "background: #FFF0DB; color: #D27B00" 44 | try 45 | Utils.domLoadScriptSync(webscript) 46 | Utils.domComplete -> 47 | console.log "%c#{name} Successfully Included from Web", "background: #DDFFE3; color: #007814" 48 | catch e 49 | throw Error("ClassAnimation: Sorry, I don't know how to animate without #{name} library") 50 | 51 | 52 | insertVelocity("modules/velocity.min.js", "//cdn.jsdelivr.net/velocity/1.4/velocity.min.js", "Velocity.js") 53 | #insertVelocity("modules/velocity.ui.min.js", "//cdn.jsdelivr.net/velocity/1.4/velocity.ui.min.js", "Velocity UI") 54 | 55 | 56 | 57 | # OBJECT WITH PROPERTIES AND THEIR OPTIONS —————————————— 58 | # [UNITLESS, ANIMATABLE, SVG] 59 | ANIMATABLES = 60 | fontWeight: [true, false] 61 | fontFamily: [true, false] 62 | textAlign: [false, false] 63 | opacity: [true, true, true] 64 | zIndex: [true, false] 65 | 66 | width: [false, true, true] 67 | height: [false, true, true] 68 | minWidth: [false, true] 69 | minHeight: [false, true] 70 | maxWidth: [false, true] 71 | maxHeight: [false, true] 72 | 73 | "\n— POSITIONING —": [false, true] 74 | padding: [false, true] 75 | paddingTop: [false, true] 76 | paddingRight: [false, true] 77 | paddingBottom: [false, true] 78 | paddingLeft: [false, true] 79 | 80 | top: [false, true] 81 | right: [false, true] 82 | bottom: [false, true] 83 | left: [false, true] 84 | 85 | margin: [false, true] 86 | marginTop: [false, true] 87 | marginRight: [false, true] 88 | marginBottom: [false, true] 89 | marginLeft: [false, true] 90 | 91 | "\n— BORDER DIMENSIONS —": [false, true] 92 | borderWidth: [false, true] 93 | borderTopWidth: [false, true] 94 | borderRightWidth: [false, true] 95 | borderBottomWidth: [false, true] 96 | borderLeftWidth: [false, true] 97 | borderRadius: [false, true] 98 | outlineWidth: [false, true] 99 | 100 | "\n— TEXT PROPERTIES —": [false, true] 101 | lineHeight: [false, true] 102 | fontSize: [false, true, true] 103 | letterSpacing: [false, true, true] 104 | wordSpacing: [false, true, true] 105 | 106 | "\n— TEXT COLORS —": [false, true] 107 | color: [false, true] 108 | colorRed: [true, true] 109 | colorGreen: [true, true] 110 | colorBlue: [true, true] 111 | colorAlpha: [true, true] 112 | 113 | "\n— BACKGROUND COLORS —": [false, true] 114 | backgroundColor: [false, true] 115 | backgroundColorRed: [true, true] 116 | backgroundColorGreen: [true, true] 117 | backgroundColorBlue: [true, true] 118 | backgroundColorAlpha: [true, true] 119 | 120 | "\n— BORDER COLORS —": [false, true] 121 | borderColor: [false, true] 122 | borderTopColor: [false, true] 123 | borderRightColor: [false, true] 124 | borderBottomColor: [false, true] 125 | borderLeftColor: [false, true] 126 | 127 | "\n— RED CHANNEL OF BORDERS —": [false, true] 128 | borderColorRed: [true, true] 129 | borderTopColorRed: [true, true] 130 | borderRightColorRed: [true, true] 131 | borderBottomColorRed: [true, true] 132 | borderLeftColorRed: [true, true] 133 | 134 | "\nGREEN:": [false, true] 135 | borderColorGreen: [true, true] 136 | borderTopColorGreen: [true, true] 137 | borderRightColorGreen: [true, true] 138 | borderBottomColorGreen: [true, true] 139 | borderLeftColorGreen: [true, true] 140 | 141 | "\nBLUE:": [false, true] 142 | borderColorBlue: [true, true] 143 | borderTopColorBlue: [true, true] 144 | borderRightColorBlue: [true, true] 145 | borderBottomColorBlue: [true, true] 146 | borderLeftColorBlue: [true, true] 147 | 148 | "\nALPHA:": [false, true] 149 | borderColorAlpha: [true, true] 150 | borderTopColorAlpha: [true, true] 151 | borderRightColorAlpha: [true, true] 152 | borderBottomColorAlpha: [true, true] 153 | borderLeftColorAlpha: [true, true] 154 | 155 | "\n— OUTLINE COLOR —": [false, true] 156 | outlineColor: [false, true] 157 | outlineColorRed: [true, true] 158 | outlineColorGreen: [true, true] 159 | outlineColorBlue: [true, true] 160 | outlineColorAlpha: [true, true] 161 | 162 | "\n— ADVANCED STYLES —": [false, true] 163 | backgroundPositionX: [false, true] 164 | backgroundPositionY: [false, true] 165 | textShadowX: [false, true] 166 | textShadowY: [false, true] 167 | textShadowBlur: [false, true] 168 | boxShadowX: [false, true] 169 | boxShadowY: [false, true] 170 | boxShadowBlur: [false, true] 171 | boxShadowSpread: [false, true] 172 | 173 | "\n— TRANSFORMATIONS —": [false, true] 174 | translateX: [false, true, true] 175 | translateY: [false, true, true] 176 | translateZ: [false, true, true] 177 | 178 | scale: [true, true, true] 179 | scaleX: [true, true, true] 180 | scaleY: [true, true, true] 181 | scaleZ: [true, true] 182 | 183 | rotateX: [true, true, true] 184 | rotateY: [true, true, true] 185 | rotateZ: [true, true, true] 186 | 187 | skewX: [true, true, true] 188 | skewZ: [true, true, true] 189 | 190 | transformPerspective: [false, true, true] 191 | perspective: [false, true] 192 | perspectiveOriginX: [false, true] 193 | perspectiveOriginY: [false, true] 194 | transformOriginX: [false, true, true] 195 | transformOriginY: [false, true, true] 196 | transformOriginZ: [false, true, true] 197 | 198 | clipTop: [false, true] 199 | clipRight: [false, true] 200 | clipBottom: [false, true] 201 | clipLeft: [false, true] 202 | 203 | blur: [false, true] 204 | 205 | "\n— SVG-only —": [false, true] 206 | x: [false, true] 207 | y: [false, true] 208 | cx: [false, true] 209 | cy: [false, true] 210 | r: [false, true] 211 | rx: [false, true] 212 | ry: [false, true] 213 | x1: [false, true] 214 | x2: [false, true] 215 | y1: [false, true] 216 | y2: [false, true] 217 | 218 | strokeDasharray: [false, true] 219 | strokeDashoffset: [false, true] 220 | strokeWidth: [false, true] 221 | strokeMiterlimit: [true, true] 222 | startOffset: [false, true] 223 | 224 | fill: [false, true] 225 | fillRed: [true, true] 226 | fillGreen: [true, true] 227 | fillBlue: [true, true] 228 | fillAlpha: [true, true] 229 | fillOpacity: [true, true] 230 | 231 | stroke: [false, true] 232 | strokeRed: [true, true] 233 | strokeGreen: [true, true] 234 | strokeBlue: [true, true] 235 | strokeAlpha: [true, true] 236 | strokeOpacity: [true, true] 237 | 238 | stopColor: [false, true] 239 | stopColorRed: [true, true] 240 | stopColorGreen: [true, true] 241 | stopColorBlue: [true, true] 242 | stopColorAlpha: [true, true] 243 | stopOpacity: [true, true] 244 | 245 | offset: [false, true] 246 | 247 | isAnimatable = (property, thr) -> 248 | if typeof ANIMATABLES[property] is 'object' && ANIMATABLES[property][1] 249 | return true 250 | else 251 | moduleHelp() if thr 252 | print "›#{property}‹ is not animatable. Open console (Target icon bottom left) and Cmd+R to see animatable properties." if thr 253 | return false 254 | 255 | 256 | 257 | # GET CSS DECLARATIONS FROM CLASS ——————————————————————— 258 | getCSS = (cl, checker) -> 259 | classes = document.getElementsByClassName(cl); 260 | cssDOM = window.getComputedStyle(classes[0], null) 261 | 262 | cssObject = {} 263 | 264 | for val in [0...cssDOM.length] 265 | currentVal = cssDOM[val] 266 | if currentVal.toCamel() of checker 267 | cssObject[currentVal.toCamel()] = cssDOM.getPropertyValue(currentVal) 268 | cssObject[currentVal.toCamel()] = cssDOM.getPropertyValue(currentVal).checkColor() if typeof cssDOM.getPropertyValue(currentVal) is "string" 269 | 270 | return cssObject 271 | 272 | 273 | # MAIN CSSANIMATION CLASS ——————————————————————————————— 274 | # ——————————————————————————————————————————————————————— 275 | # ——————————————————————————————————————————————————————— 276 | class exports.ClassAnimation 277 | constructor: (name, end = {}, init, options = {}, optionsFactor = 1000) -> 278 | @class = name 279 | @target = document.querySelectorAll(".#{@class}"); 280 | 281 | @endState = end 282 | 283 | @options = @endState.options || options 284 | @options.time = @options.time*optionsFactor || 1000 285 | @options.delay = @options.delay*optionsFactor || 0 286 | @options.repeat = @options.repeat || false 287 | @options.curve = @options.curve || "ease-in-out" 288 | 289 | delete @endState.options 290 | 291 | for key, val of @endState 292 | @endState[key] = val.checkColor() if typeof val is "string" 293 | renameKey(@endState, key, key.toCamel()) 294 | isAnimatable(key.toCamel(), true) 295 | 296 | @initState = init || getCSS(@class, @endState) 297 | 298 | return @ 299 | 300 | 301 | # METHODS ——————————————————————————————————————————— 302 | 303 | start: () -> 304 | _this = @ 305 | Utils.domComplete -> 306 | Velocity _this.target, 307 | _this.endState, 308 | duration: _this.options.time 309 | delay: _this.options.delay 310 | loop: _this.options.repeat 311 | easing: _this.options.curve 312 | begin: -> _this.onStart?() 313 | complete: -> _this.onEnd?() 314 | progress: (elements, complete, remaining, start) -> 315 | _this.onAnim?(complete, remaining) 316 | @notFirst = true 317 | return @ 318 | 319 | stop: () -> 320 | @onStop?() 321 | Velocity(@target, "stop") 322 | return @ 323 | 324 | reset: () -> 325 | Velocity @target, 326 | @initState, 327 | duration: 0 328 | delay: 0 329 | return @ 330 | 331 | restart: () -> 332 | @reset() 333 | @start() 334 | return @ 335 | 336 | finish: () -> 337 | Velocity(@target, "finish") 338 | return @ 339 | 340 | toggle: () -> 341 | _this = @ 342 | if @notFirst 343 | Velocity @target, 344 | "reverse", 345 | begin: -> _this.onStart?() 346 | complete: -> _this.onEnd?() 347 | progress: (elements, complete, remaining, start) -> 348 | _this.onAnim?(complete, remaining) 349 | else 350 | @start() 351 | return @ 352 | 353 | reverse: () -> 354 | return new ClassAnimation(@class, @initState, @endState, @options, 1) 355 | 356 | fadeIn: () -> 357 | _this = @ 358 | Velocity @target, 359 | "fadeIn", 360 | begin: -> _this.onStart?() 361 | complete: -> _this.onEnd?() 362 | progress: (elements, complete, remaining, start) -> 363 | _this.onAnim?(complete, remaining) 364 | return @ 365 | 366 | fadeOut: () -> 367 | _this = @ 368 | Velocity @target, 369 | "fadeOut", 370 | begin: -> _this.onStart?() 371 | complete: -> _this.onEnd?() 372 | progress: (elements, complete, remaining, start) -> 373 | _this.onAnim?(complete, remaining) 374 | return @ 375 | 376 | help: () -> moduleHelp() 377 | 378 | 379 | # METHOD ALIASES ———————————————————————————————————— 380 | 381 | switch: -> @toggle() 382 | stateSwitch: -> @toggle() 383 | revert: -> @reverse() 384 | inverse: -> @reverse() 385 | invert: -> @reverse() 386 | 387 | 388 | # EVENTS ———————————————————————————————————————————— 389 | 390 | Events.Animation = "move" 391 | 392 | on: (eventName, cb) -> 393 | switch eventName 394 | when "end" then @onAnimationEnd(cb) 395 | when "stop" then @onAnimationStop(cb) 396 | when "start" then @onAnimationStart(cb) 397 | when "move" then @onAnimation(cb) 398 | else throw Error("Sorry, I'm too stupid to handle this event") 399 | 400 | onAnimationEnd: (cb) -> @onEnd = @eventReturnData(cb) 401 | onAnimationStop: (cb) -> @onStop = @eventReturnData(cb) 402 | onAnimationStart: (cb) -> @onStart = @eventReturnData(cb) 403 | onAnimation: (cb) -> @onAnim = @eventReturnData(cb) 404 | 405 | eventReturnData: (cb) -> 406 | return (prog, rem) -> cb.call?(@, prog, rem) 407 | 408 | 409 | 410 | # .ANIMATE METHOD ——————————————————————————————————————— 411 | String::animate = (props) -> 412 | (new exports.ClassAnimation @, props).start() 413 | 414 | String::fadeIn = () -> @animate().fadeIn() 415 | 416 | String::fadeOut = () -> @animate().fadeOut() 417 | 418 | 419 | 420 | # STYLE INIT ———————————————————————————————————————————— 421 | String::css = () -> 422 | Utils.insertCSS(@) 423 | 424 | addUnit = (value, key) -> 425 | if typeof value is 'number' && isAnimatable(key, false) && !ANIMATABLES[key][0] then "#{value}px" else value 426 | 427 | exports.Style = (cl, css) -> 428 | insertableCss = "#{point(cl)} {border-style: solid; border-width: 0; " 429 | for key, val of css 430 | css[key] = addUnit( css[key], key.toCamel() ) 431 | insertableCss += "#{key.toCss()}:#{css[key]}; " 432 | insertableCss += "}" 433 | 434 | Utils.insertCSS(insertableCss) 435 | 436 | return cl 437 | 438 | exports.style = (cl, css) -> exports.Style(cl, css) 439 | 440 | 441 | 442 | # HELP —————————————————————————————————————————————————— 443 | moduleHelp = () -> 444 | console.log "\n" 445 | console.log "LIST OF ANIMATABLE PROPERTIES" 446 | for key, val of ANIMATABLES 447 | help = if ANIMATABLES[key][0] is true then "%c#{key} %cis unitless" else "%c#{key} %c" 448 | help += if ANIMATABLES[key][2] is true then " %c(works for SVG)" else " %c" 449 | console.log help, "color: #007AFF", "color: #000", "color: #007814" if ANIMATABLES[key][1] is true 450 | console.log "For full support, go to http://velocityjs.org/#cssSupport" -------------------------------------------------------------------------------- /ClassAnimationDemo.framer/images/delivery.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | delivery 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /ClassAnimationDemo.framer/modules/velocity.min.js: -------------------------------------------------------------------------------- 1 | /*! VelocityJS.org (1.4.2). (C) 2014 Julian Shapiro. MIT @license: en.wikipedia.org/wiki/MIT_License */ 2 | /*! VelocityJS.org jQuery Shim (1.0.1). (C) 2014 The jQuery Foundation. MIT @license: en.wikipedia.org/wiki/MIT_License. */ 3 | !function(a){"use strict";function b(a){var b=a.length,d=c.type(a);return"function"!==d&&!c.isWindow(a)&&(!(1!==a.nodeType||!b)||("array"===d||0===b||"number"==typeof b&&b>0&&b-1 in a))}if(!a.jQuery){var c=function(a,b){return new c.fn.init(a,b)};c.isWindow=function(a){return a&&a===a.window},c.type=function(a){return a?"object"==typeof a||"function"==typeof a?e[g.call(a)]||"object":typeof a:a+""},c.isArray=Array.isArray||function(a){return"array"===c.type(a)},c.isPlainObject=function(a){var b;if(!a||"object"!==c.type(a)||a.nodeType||c.isWindow(a))return!1;try{if(a.constructor&&!f.call(a,"constructor")&&!f.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(d){return!1}for(b in a);return void 0===b||f.call(a,b)},c.each=function(a,c,d){var e,f=0,g=a.length,h=b(a);if(d){if(h)for(;f0?e=g:c=g;while(Math.abs(f)>r&&++h=q?k(b,h):0===i?h:m(b,c,c+u)}function o(){y=!0,a===c&&d===e||l()}var p=4,q=.001,r=1e-7,s=10,t=11,u=1/(t-1),v="Float32Array"in b;if(4!==arguments.length)return!1;for(var w=0;w<4;++w)if("number"!=typeof arguments[w]||isNaN(arguments[w])||!isFinite(arguments[w]))return!1;a=Math.min(a,1),d=Math.min(d,1),a=Math.max(a,0),d=Math.max(d,0);var x=v?new Float32Array(t):new Array(t),y=!1,z=function(b){return y||o(),a===c&&d===e?b:0===b?0:1===b?1:i(n(b),c,e)};z.getControlPoints=function(){return[{x:a,y:c},{x:d,y:e}]};var A="generateBezier("+[a,c,d,e]+")";return z.toString=function(){return A},z}function l(a,b){var c=a;return t.isString(a)?x.Easings[a]||(c=!1):c=t.isArray(a)&&1===a.length?j.apply(null,a):t.isArray(a)&&2===a.length?y.apply(null,a.concat([b])):!(!t.isArray(a)||4!==a.length)&&k.apply(null,a),c===!1&&(c=x.Easings[x.defaults.easing]?x.defaults.easing:w),c}function m(a){if(a){var b=x.timestamp&&a!==!0?a:r.now(),c=x.State.calls.length;c>1e4&&(x.State.calls=e(x.State.calls),c=x.State.calls.length);for(var f=0;f4;a--){var b=c.createElement("div");if(b.innerHTML="",b.getElementsByTagName("span").length)return b=null,a}return d}(),q=function(){var a=0;return b.webkitRequestAnimationFrame||b.mozRequestAnimationFrame||function(b){var c,d=(new Date).getTime();return c=Math.max(0,16-(d-a)),a=d+c,setTimeout(function(){b(d+c)},c)}}(),r=function(){var a=b.performance||{};if(!Object.prototype.hasOwnProperty.call(a,"now")){var c=a.timing&&a.timing.domComplete?a.timing.domComplete:(new Date).getTime();a.now=function(){return(new Date).getTime()-c}}return a}(),s=function(){var a=Array.prototype.slice;try{a.call(c.documentElement)}catch(b){a=function(){for(var a=this.length,b=[];--a>0;)b[a]=this[a];return b}}return a}(),t={isNumber:function(a){return"number"==typeof a},isString:function(a){return"string"==typeof a},isArray:Array.isArray||function(a){return"[object Array]"===Object.prototype.toString.call(a)},isFunction:function(a){return"[object Function]"===Object.prototype.toString.call(a)},isNode:function(a){return a&&a.nodeType},isWrapped:function(a){return a&&t.isNumber(a.length)&&!t.isString(a)&&!t.isFunction(a)&&!t.isNode(a)&&(0===a.length||t.isNode(a[0]))},isSVG:function(a){return b.SVGElement&&a instanceof b.SVGElement},isEmptyObject:function(a){for(var b in a)if(a.hasOwnProperty(b))return!1;return!0}},u=!1;if(a.fn&&a.fn.jquery?(o=a,u=!0):o=b.Velocity.Utilities,p<=8&&!u)throw new Error("Velocity: IE8 and below require jQuery to be loaded before Velocity.");if(p<=7)return void(jQuery.fn.velocity=jQuery.fn.animate);var v=400,w="swing",x={State:{isMobile:/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),isAndroid:/Android/i.test(navigator.userAgent),isGingerbread:/Android 2\.3\.[3-7]/i.test(navigator.userAgent),isChrome:b.chrome,isFirefox:/Firefox/i.test(navigator.userAgent),prefixElement:c.createElement("div"),prefixMatches:{},scrollAnchor:null,scrollPropertyLeft:null,scrollPropertyTop:null,isTicking:!1,calls:[],delayedElements:{count:0}},CSS:{},Utilities:o,Redirects:{},Easings:{},Promise:b.Promise,defaults:{queue:"",duration:v,easing:w,begin:d,complete:d,progress:d,display:d,visibility:d,loop:!1,delay:!1,mobileHA:!0,_cacheValues:!0,promiseRejectEmpty:!0},init:function(a){o.data(a,"velocity",{isSVG:t.isSVG(a),isAnimating:!1,computedStyle:null,tweensContainer:null,rootPropertyValueCache:{},transformCache:{}})},hook:null,mock:!1,version:{major:1,minor:4,patch:2},debug:!1,timestamp:!0,pauseAll:function(a){var b=(new Date).getTime();o.each(x.State.calls,function(b,c){if(c){if(a!==d&&(c[2].queue!==a||c[2].queue===!1))return!0;c[5]={resume:!1}}}),o.each(x.State.delayedElements,function(a,c){c&&h(c,b)})},resumeAll:function(a){var b=(new Date).getTime();o.each(x.State.calls,function(b,c){if(c){if(a!==d&&(c[2].queue!==a||c[2].queue===!1))return!0;c[5]&&(c[5].resume=!0)}}),o.each(x.State.delayedElements,function(a,c){c&&i(c,b)})}};b.pageYOffset!==d?(x.State.scrollAnchor=b,x.State.scrollPropertyLeft="pageXOffset",x.State.scrollPropertyTop="pageYOffset"):(x.State.scrollAnchor=c.documentElement||c.body.parentNode||c.body,x.State.scrollPropertyLeft="scrollLeft",x.State.scrollPropertyTop="scrollTop");var y=function(){function a(a){return-a.tension*a.x-a.friction*a.v}function b(b,c,d){var e={x:b.x+d.dx*c,v:b.v+d.dv*c,tension:b.tension,friction:b.friction};return{dx:e.v,dv:a(e)}}function c(c,d){var e={dx:c.v,dv:a(c)},f=b(c,.5*d,e),g=b(c,.5*d,f),h=b(c,d,g),i=1/6*(e.dx+2*(f.dx+g.dx)+h.dx),j=1/6*(e.dv+2*(f.dv+g.dv)+h.dv);return c.x=c.x+i*d,c.v=c.v+j*d,c}return function d(a,b,e){var f,g,h,i={x:-1,v:0,tension:null,friction:null},j=[0],k=0,l=1e-4,m=.016;for(a=parseFloat(a)||500,b=parseFloat(b)||20,e=e||null,i.tension=a,i.friction=b,f=null!==e,f?(k=d(a,b),g=k/e*m):g=m;;)if(h=c(h||i,g),j.push(1+h.x),k+=16,!(Math.abs(h.x)>l&&Math.abs(h.v)>l))break;return f?function(a){return j[a*(j.length-1)|0]}:k}}();x.Easings={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},spring:function(a){return 1-Math.cos(4.5*a*Math.PI)*Math.exp(6*-a)}},o.each([["ease",[.25,.1,.25,1]],["ease-in",[.42,0,1,1]],["ease-out",[0,0,.58,1]],["ease-in-out",[.42,0,.58,1]],["easeInSine",[.47,0,.745,.715]],["easeOutSine",[.39,.575,.565,1]],["easeInOutSine",[.445,.05,.55,.95]],["easeInQuad",[.55,.085,.68,.53]],["easeOutQuad",[.25,.46,.45,.94]],["easeInOutQuad",[.455,.03,.515,.955]],["easeInCubic",[.55,.055,.675,.19]],["easeOutCubic",[.215,.61,.355,1]],["easeInOutCubic",[.645,.045,.355,1]],["easeInQuart",[.895,.03,.685,.22]],["easeOutQuart",[.165,.84,.44,1]],["easeInOutQuart",[.77,0,.175,1]],["easeInQuint",[.755,.05,.855,.06]],["easeOutQuint",[.23,1,.32,1]],["easeInOutQuint",[.86,0,.07,1]],["easeInExpo",[.95,.05,.795,.035]],["easeOutExpo",[.19,1,.22,1]],["easeInOutExpo",[1,0,0,1]],["easeInCirc",[.6,.04,.98,.335]],["easeOutCirc",[.075,.82,.165,1]],["easeInOutCirc",[.785,.135,.15,.86]]],function(a,b){x.Easings[b[0]]=k.apply(null,b[1])});var z=x.CSS={RegEx:{isHex:/^#([A-f\d]{3}){1,2}$/i,valueUnwrap:/^[A-z]+\((.*)\)$/i,wrappedValueAlreadyExtracted:/[0-9.]+ [0-9.]+ [0-9.]+( [0-9.]+)?/,valueSplit:/([A-z]+\(.+\))|(([A-z0-9#-.]+?)(?=\s|$))/gi},Lists:{colors:["fill","stroke","stopColor","color","backgroundColor","borderColor","borderTopColor","borderRightColor","borderBottomColor","borderLeftColor","outlineColor"],transformsBase:["translateX","translateY","scale","scaleX","scaleY","skewX","skewY","rotateZ"],transforms3D:["transformPerspective","translateZ","scaleZ","rotateX","rotateY"],units:["%","em","ex","ch","rem","vw","vh","vmin","vmax","cm","mm","Q","in","pc","pt","px","deg","grad","rad","turn","s","ms"],colorNames:{aliceblue:"240,248,255",antiquewhite:"250,235,215",aquamarine:"127,255,212",aqua:"0,255,255",azure:"240,255,255",beige:"245,245,220",bisque:"255,228,196",black:"0,0,0",blanchedalmond:"255,235,205",blueviolet:"138,43,226",blue:"0,0,255",brown:"165,42,42",burlywood:"222,184,135",cadetblue:"95,158,160",chartreuse:"127,255,0",chocolate:"210,105,30",coral:"255,127,80",cornflowerblue:"100,149,237",cornsilk:"255,248,220",crimson:"220,20,60",cyan:"0,255,255",darkblue:"0,0,139",darkcyan:"0,139,139",darkgoldenrod:"184,134,11",darkgray:"169,169,169",darkgrey:"169,169,169",darkgreen:"0,100,0",darkkhaki:"189,183,107",darkmagenta:"139,0,139",darkolivegreen:"85,107,47",darkorange:"255,140,0",darkorchid:"153,50,204",darkred:"139,0,0",darksalmon:"233,150,122",darkseagreen:"143,188,143",darkslateblue:"72,61,139",darkslategray:"47,79,79",darkturquoise:"0,206,209",darkviolet:"148,0,211",deeppink:"255,20,147",deepskyblue:"0,191,255",dimgray:"105,105,105",dimgrey:"105,105,105",dodgerblue:"30,144,255",firebrick:"178,34,34",floralwhite:"255,250,240",forestgreen:"34,139,34",fuchsia:"255,0,255",gainsboro:"220,220,220",ghostwhite:"248,248,255",gold:"255,215,0",goldenrod:"218,165,32",gray:"128,128,128",grey:"128,128,128",greenyellow:"173,255,47",green:"0,128,0",honeydew:"240,255,240",hotpink:"255,105,180",indianred:"205,92,92",indigo:"75,0,130",ivory:"255,255,240",khaki:"240,230,140",lavenderblush:"255,240,245",lavender:"230,230,250",lawngreen:"124,252,0",lemonchiffon:"255,250,205",lightblue:"173,216,230",lightcoral:"240,128,128",lightcyan:"224,255,255",lightgoldenrodyellow:"250,250,210",lightgray:"211,211,211",lightgrey:"211,211,211",lightgreen:"144,238,144",lightpink:"255,182,193",lightsalmon:"255,160,122",lightseagreen:"32,178,170",lightskyblue:"135,206,250",lightslategray:"119,136,153",lightsteelblue:"176,196,222",lightyellow:"255,255,224",limegreen:"50,205,50",lime:"0,255,0",linen:"250,240,230",magenta:"255,0,255",maroon:"128,0,0",mediumaquamarine:"102,205,170",mediumblue:"0,0,205",mediumorchid:"186,85,211",mediumpurple:"147,112,219",mediumseagreen:"60,179,113",mediumslateblue:"123,104,238",mediumspringgreen:"0,250,154",mediumturquoise:"72,209,204",mediumvioletred:"199,21,133",midnightblue:"25,25,112",mintcream:"245,255,250",mistyrose:"255,228,225",moccasin:"255,228,181",navajowhite:"255,222,173",navy:"0,0,128",oldlace:"253,245,230",olivedrab:"107,142,35",olive:"128,128,0",orangered:"255,69,0",orange:"255,165,0",orchid:"218,112,214",palegoldenrod:"238,232,170",palegreen:"152,251,152",paleturquoise:"175,238,238",palevioletred:"219,112,147",papayawhip:"255,239,213",peachpuff:"255,218,185",peru:"205,133,63",pink:"255,192,203",plum:"221,160,221",powderblue:"176,224,230",purple:"128,0,128",red:"255,0,0",rosybrown:"188,143,143",royalblue:"65,105,225",saddlebrown:"139,69,19",salmon:"250,128,114",sandybrown:"244,164,96",seagreen:"46,139,87",seashell:"255,245,238",sienna:"160,82,45",silver:"192,192,192",skyblue:"135,206,235",slateblue:"106,90,205",slategray:"112,128,144",snow:"255,250,250",springgreen:"0,255,127",steelblue:"70,130,180",tan:"210,180,140",teal:"0,128,128",thistle:"216,191,216",tomato:"255,99,71",turquoise:"64,224,208",violet:"238,130,238",wheat:"245,222,179",whitesmoke:"245,245,245",white:"255,255,255",yellowgreen:"154,205,50",yellow:"255,255,0"}},Hooks:{templates:{textShadow:["Color X Y Blur","black 0px 0px 0px"],boxShadow:["Color X Y Blur Spread","black 0px 0px 0px 0px"],clip:["Top Right Bottom Left","0px 0px 0px 0px"],backgroundPosition:["X Y","0% 0%"],transformOrigin:["X Y Z","50% 50% 0px"],perspectiveOrigin:["X Y","50% 50%"]},registered:{},register:function(){for(var a=0;a=0?c:""},fixColors:function(a){return a.replace(/(rgba?\(\s*)?(\b[a-z]+\b)/g,function(a,b,c){return z.Lists.colorNames.hasOwnProperty(c)?(b?b:"rgba(")+z.Lists.colorNames[c]+(b?"":",1)"):b+c})},cleanRootPropertyValue:function(a,b){return z.RegEx.valueUnwrap.test(b)&&(b=b.match(z.RegEx.valueUnwrap)[1]),z.Values.isCSSNullValue(b)&&(b=z.Hooks.templates[a][1]),b},extractValue:function(a,b){var c=z.Hooks.registered[a];if(c){var d=c[0],e=c[1];return b=z.Hooks.cleanRootPropertyValue(d,b),b.toString().match(z.RegEx.valueSplit)[e]}return b},injectValue:function(a,b,c){var d=z.Hooks.registered[a];if(d){var e,f,g=d[0],h=d[1];return c=z.Hooks.cleanRootPropertyValue(g,c),e=c.toString().match(z.RegEx.valueSplit),e[h]=b,f=e.join(" ")}return c}},Normalizations:{registered:{clip:function(a,b,c){switch(a){case"name":return"clip";case"extract":var d;return z.RegEx.wrappedValueAlreadyExtracted.test(c)?d=c:(d=c.toString().match(z.RegEx.valueUnwrap),d=d?d[1].replace(/,(\s+)?/g," "):c),d;case"inject":return"rect("+c+")"}},blur:function(a,b,c){switch(a){case"name":return x.State.isFirefox?"filter":"-webkit-filter";case"extract":var d=parseFloat(c);if(!d&&0!==d){var e=c.toString().match(/blur\(([0-9]+[A-z]+)\)/i);d=e?e[1]:0}return d;case"inject":return parseFloat(c)?"blur("+c+")":"none"}},opacity:function(a,b,c){if(p<=8)switch(a){case"name":return"filter";case"extract":var d=c.toString().match(/alpha\(opacity=(.*)\)/i);return c=d?d[1]/100:1;case"inject":return b.style.zoom=1,parseFloat(c)>=1?"":"alpha(opacity="+parseInt(100*parseFloat(c),10)+")"}else switch(a){case"name":return"opacity";case"extract":return c;case"inject":return c}}},register:function(){function a(a,b,c){var d="border-box"===z.getPropertyValue(b,"boxSizing").toString().toLowerCase();if(d===(c||!1)){var e,f,g=0,h="width"===a?["Left","Right"]:["Top","Bottom"],i=["padding"+h[0],"padding"+h[1],"border"+h[0]+"Width","border"+h[1]+"Width"];for(e=0;e9)||x.State.isGingerbread||(z.Lists.transformsBase=z.Lists.transformsBase.concat(z.Lists.transforms3D));for(var c=0;c8)&&3===f.split(" ").length&&(f+=" 1"),f;case"inject":return/^rgb/.test(e)?e:(p<=8?4===e.split(" ").length&&(e=e.split(/\s+/).slice(0,3).join(" ")):3===e.split(" ").length&&(e+=" 1"),(p<=8?"rgb":"rgba")+"("+e.replace(/\s+/g,",").replace(/\.(\d)+(?=,)/g,"")+")")}}}();z.Normalizations.registered.innerWidth=b("width",!0),z.Normalizations.registered.innerHeight=b("height",!0),z.Normalizations.registered.outerWidth=b("width"),z.Normalizations.registered.outerHeight=b("height")}},Names:{camelCase:function(a){return a.replace(/-(\w)/g,function(a,b){return b.toUpperCase()})},SVGAttribute:function(a){var b="width|height|x|y|cx|cy|r|rx|ry|x1|x2|y1|y2";return(p||x.State.isAndroid&&!x.State.isChrome)&&(b+="|transform"),new RegExp("^("+b+")$","i").test(a)},prefixCheck:function(a){if(x.State.prefixMatches[a])return[x.State.prefixMatches[a],!0];for(var b=["","Webkit","Moz","ms","O"],c=0,d=b.length;c=2&&console.log("Get "+c+": "+i),i},setPropertyValue:function(a,c,d,e,f){var h=c;if("scroll"===c)f.container?f.container["scroll"+f.direction]=d:"Left"===f.direction?b.scrollTo(d,f.alternateValue):b.scrollTo(f.alternateValue,d);else if(z.Normalizations.registered[c]&&"transform"===z.Normalizations.registered[c]("name",a))z.Normalizations.registered[c]("inject",a,d),h="transform",d=g(a).transformCache[c];else{if(z.Hooks.registered[c]){var i=c,j=z.Hooks.getRoot(c);e=e||z.getPropertyValue(a,j),d=z.Hooks.injectValue(i,d,e),c=j}if(z.Normalizations.registered[c]&&(d=z.Normalizations.registered[c]("inject",a,d),c=z.Normalizations.registered[c]("name",a)),h=z.Names.prefixCheck(c)[0],p<=8)try{a.style[h]=d}catch(k){x.debug&&console.log("Browser does not support ["+d+"] for ["+h+"]")}else{var l=g(a);l&&l.isSVG&&z.Names.SVGAttribute(c)?a.setAttribute(c,d):a.style[h]=d}x.debug>=2&&console.log("Set "+c+" ("+h+"): "+d)}return[h,d]},flushTransformCache:function(a){var b="",c=g(a);if((p||x.State.isAndroid&&!x.State.isChrome)&&c&&c.isSVG){var d=function(b){return parseFloat(z.getPropertyValue(a,b))},e={translate:[d("translateX"),d("translateY")],skewX:[d("skewX")],skewY:[d("skewY")],scale:1!==d("scale")?[d("scale"),d("scale")]:[d("scaleX"),d("scaleY")],rotate:[d("rotateZ"),0,0]};o.each(g(a).transformCache,function(a){/^translate/i.test(a)?a="translate":/^scale/i.test(a)?a="scale":/^rotate/i.test(a)&&(a="rotate"),e[a]&&(b+=a+"("+e[a].join(" ")+") ",delete e[a])})}else{var f,h;o.each(g(a).transformCache,function(c){return f=g(a).transformCache[c],"transformPerspective"===c?(h=f,!0):(9===p&&"rotateZ"===c&&(c="rotate"),void(b+=c+f+" "))}),h&&(b="perspective"+h+" "+b)}z.setPropertyValue(a,"transform",b)}};z.Hooks.register(),z.Normalizations.register(),x.hook=function(a,b,c){var e;return a=f(a),o.each(a,function(a,f){if(g(f)===d&&x.init(f),c===d)e===d&&(e=z.getPropertyValue(f,b));else{var h=z.setPropertyValue(f,b,c);"transform"===h[0]&&x.CSS.flushTransformCache(f),e=h}}),e};var A=function(){function a(){return k?y.promise||null:p}function e(a,e){function f(f){var k,n;if(i.begin&&0===C)try{i.begin.call(r,r)}catch(p){setTimeout(function(){throw p},1)}if("scroll"===F){var q,v,w,A=/^x$/i.test(i.axis)?"Left":"Top",D=parseFloat(i.offset)||0;i.container?t.isWrapped(i.container)||t.isNode(i.container)?(i.container=i.container[0]||i.container,q=i.container["scroll"+A],w=q+o(a).position()[A.toLowerCase()]+D):i.container=null:(q=x.State.scrollAnchor[x.State["scrollProperty"+A]],v=x.State.scrollAnchor[x.State["scrollProperty"+("Left"===A?"Top":"Left")]],w=o(a).offset()[A.toLowerCase()]+D),j={scroll:{rootPropertyValue:!1,startValue:q,currentValue:q,endValue:w,unitType:"",easing:i.easing,scrollData:{container:i.container,direction:A,alternateValue:v}},element:a},x.debug&&console.log("tweensContainer (scroll): ",j.scroll,a)}else if("reverse"===F){if(k=g(a),!k)return;if(!k.tweensContainer)return void o.dequeue(a,i.queue);"none"===k.opts.display&&(k.opts.display="auto"),"hidden"===k.opts.visibility&&(k.opts.visibility="visible"),k.opts.loop=!1,k.opts.begin=null,k.opts.complete=null,u.easing||delete i.easing,u.duration||delete i.duration,i=o.extend({},k.opts,i),n=o.extend(!0,{},k?k.tweensContainer:null);for(var E in n)if(n.hasOwnProperty(E)&&"element"!==E){var G=n[E].startValue;n[E].startValue=n[E].currentValue=n[E].endValue,n[E].endValue=G,t.isEmptyObject(u)||(n[E].easing=i.easing),x.debug&&console.log("reverse tweensContainer ("+E+"): "+JSON.stringify(n[E]),a)}j=n}else if("start"===F){k=g(a),k&&k.tweensContainer&&k.isAnimating===!0&&(n=k.tweensContainer);var H=function(b,c){var d,f,g;return t.isFunction(b)&&(b=b.call(a,e,B)),t.isArray(b)?(d=b[0],!t.isArray(b[1])&&/^[\d-]/.test(b[1])||t.isFunction(b[1])||z.RegEx.isHex.test(b[1])?g=b[1]:t.isString(b[1])&&!z.RegEx.isHex.test(b[1])&&x.Easings[b[1]]||t.isArray(b[1])?(f=c?b[1]:l(b[1],i.duration),g=b[2]):g=b[1]||b[2]):d=b,c||(f=f||i.easing),t.isFunction(d)&&(d=d.call(a,e,B)),t.isFunction(g)&&(g=g.call(a,e,B)),[d||0,f,g]},I=function(e,f){var g,l=z.Hooks.getRoot(e),m=!1,p=f[0],q=f[1],r=f[2]; 4 | if(!(k&&k.isSVG||"tween"===l||z.Names.prefixCheck(l)[1]!==!1||z.Normalizations.registered[l]!==d))return void(x.debug&&console.log("Skipping ["+l+"] due to a lack of browser support."));(i.display!==d&&null!==i.display&&"none"!==i.display||i.visibility!==d&&"hidden"!==i.visibility)&&/opacity|filter/.test(e)&&!r&&0!==p&&(r=0),i._cacheValues&&n&&n[e]?(r===d&&(r=n[e].endValue+n[e].unitType),m=k.rootPropertyValueCache[l]):z.Hooks.registered[e]?r===d?(m=z.getPropertyValue(a,l),r=z.getPropertyValue(a,e,m)):m=z.Hooks.templates[l][1]:r===d&&(r=z.getPropertyValue(a,e));var s,u,v,w=!1,y=function(a,b){var c,d;return d=(b||"0").toString().toLowerCase().replace(/[%A-z]+$/,function(a){return c=a,""}),c||(c=z.Values.getUnitType(a)),[d,c]};if(r!==p&&t.isString(r)&&t.isString(p)){g="";var A=0,B=0,C=[],D=[],E=0,F=0,G=0;for(r=z.Hooks.fixColors(r),p=z.Hooks.fixColors(p);A=4&&"("===H?E++:(E&&E<5||E>=4&&")"===H&&--E<5)&&(E=0),0===F&&"r"===H||1===F&&"g"===H||2===F&&"b"===H||3===F&&"a"===H||F>=3&&"("===H?(3===F&&"a"===H&&(G=1),F++):G&&","===H?++G>3&&(F=G=0):(G&&F<(G?5:4)||F>=(G?4:3)&&")"===H&&--F<(G?5:4))&&(F=G=0)}}A===r.length&&B===p.length||(x.debug&&console.error('Trying to pattern match mis-matched strings ["'+p+'", "'+r+'"]'),g=d),g&&(C.length?(x.debug&&console.log('Pattern found "'+g+'" -> ',C,D,"["+r+","+p+"]"),r=C,p=D,u=v=""):g=d)}g||(s=y(e,r),r=s[0],v=s[1],s=y(e,p),p=s[0].replace(/^([+-\/*])=/,function(a,b){return w=b,""}),u=s[1],r=parseFloat(r)||0,p=parseFloat(p)||0,"%"===u&&(/^(fontSize|lineHeight)$/.test(e)?(p/=100,u="em"):/^scale/.test(e)?(p/=100,u=""):/(Red|Green|Blue)$/i.test(e)&&(p=p/100*255,u="")));var S=function(){var d={myParent:a.parentNode||c.body,position:z.getPropertyValue(a,"position"),fontSize:z.getPropertyValue(a,"fontSize")},e=d.position===L.lastPosition&&d.myParent===L.lastParent,f=d.fontSize===L.lastFontSize;L.lastParent=d.myParent,L.lastPosition=d.position,L.lastFontSize=d.fontSize;var g=100,h={};if(f&&e)h.emToPx=L.lastEmToPx,h.percentToPxWidth=L.lastPercentToPxWidth,h.percentToPxHeight=L.lastPercentToPxHeight;else{var i=k&&k.isSVG?c.createElementNS("http://www.w3.org/2000/svg","rect"):c.createElement("div");x.init(i),d.myParent.appendChild(i),o.each(["overflow","overflowX","overflowY"],function(a,b){x.CSS.setPropertyValue(i,b,"hidden")}),x.CSS.setPropertyValue(i,"position",d.position),x.CSS.setPropertyValue(i,"fontSize",d.fontSize),x.CSS.setPropertyValue(i,"boxSizing","content-box"),o.each(["minWidth","maxWidth","width","minHeight","maxHeight","height"],function(a,b){x.CSS.setPropertyValue(i,b,g+"%")}),x.CSS.setPropertyValue(i,"paddingLeft",g+"em"),h.percentToPxWidth=L.lastPercentToPxWidth=(parseFloat(z.getPropertyValue(i,"width",null,!0))||1)/g,h.percentToPxHeight=L.lastPercentToPxHeight=(parseFloat(z.getPropertyValue(i,"height",null,!0))||1)/g,h.emToPx=L.lastEmToPx=(parseFloat(z.getPropertyValue(i,"paddingLeft"))||1)/g,d.myParent.removeChild(i)}return null===L.remToPx&&(L.remToPx=parseFloat(z.getPropertyValue(c.body,"fontSize"))||16),null===L.vwToPx&&(L.vwToPx=parseFloat(b.innerWidth)/100,L.vhToPx=parseFloat(b.innerHeight)/100),h.remToPx=L.remToPx,h.vwToPx=L.vwToPx,h.vhToPx=L.vhToPx,x.debug>=1&&console.log("Unit ratios: "+JSON.stringify(h),a),h};if(/[\/*]/.test(w))u=v;else if(v!==u&&0!==r)if(0===p)u=v;else{h=h||S();var T=/margin|padding|left|right|width|text|word|letter/i.test(e)||/X$/.test(e)||"x"===e?"x":"y";switch(v){case"%":r*="x"===T?h.percentToPxWidth:h.percentToPxHeight;break;case"px":break;default:r*=h[v+"ToPx"]}switch(u){case"%":r*=1/("x"===T?h.percentToPxWidth:h.percentToPxHeight);break;case"px":break;default:r*=1/h[u+"ToPx"]}}switch(w){case"+":p=r+p;break;case"-":p=r-p;break;case"*":p*=r;break;case"/":p=r/p}j[e]={rootPropertyValue:m,startValue:r,currentValue:r,endValue:p,unitType:u,easing:q},g&&(j[e].pattern=g),x.debug&&console.log("tweensContainer ("+e+"): "+JSON.stringify(j[e]),a)};for(var J in s)if(s.hasOwnProperty(J)){var K=z.Names.camelCase(J),N=H(s[J]);if(z.Lists.colors.indexOf(K)>=0){var O=N[0],P=N[1],Q=N[2];if(z.RegEx.isHex.test(O)){for(var R=["Red","Green","Blue"],S=z.Values.hexToRgb(O),T=Q?z.Values.hexToRgb(Q):d,U=0;U