1// Copyright 2014 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15(function(shared, testing) { 16 var shorthandToLonghand = { 17 background: [ 18 'backgroundImage', 19 'backgroundPosition', 20 'backgroundSize', 21 'backgroundRepeat', 22 'backgroundAttachment', 23 'backgroundOrigin', 24 'backgroundClip', 25 'backgroundColor' 26 ], 27 border: [ 28 'borderTopColor', 29 'borderTopStyle', 30 'borderTopWidth', 31 'borderRightColor', 32 'borderRightStyle', 33 'borderRightWidth', 34 'borderBottomColor', 35 'borderBottomStyle', 36 'borderBottomWidth', 37 'borderLeftColor', 38 'borderLeftStyle', 39 'borderLeftWidth' 40 ], 41 borderBottom: [ 42 'borderBottomWidth', 43 'borderBottomStyle', 44 'borderBottomColor' 45 ], 46 borderColor: [ 47 'borderTopColor', 48 'borderRightColor', 49 'borderBottomColor', 50 'borderLeftColor' 51 ], 52 borderLeft: [ 53 'borderLeftWidth', 54 'borderLeftStyle', 55 'borderLeftColor' 56 ], 57 borderRadius: [ 58 'borderTopLeftRadius', 59 'borderTopRightRadius', 60 'borderBottomRightRadius', 61 'borderBottomLeftRadius' 62 ], 63 borderRight: [ 64 'borderRightWidth', 65 'borderRightStyle', 66 'borderRightColor' 67 ], 68 borderTop: [ 69 'borderTopWidth', 70 'borderTopStyle', 71 'borderTopColor' 72 ], 73 borderWidth: [ 74 'borderTopWidth', 75 'borderRightWidth', 76 'borderBottomWidth', 77 'borderLeftWidth' 78 ], 79 flex: [ 80 'flexGrow', 81 'flexShrink', 82 'flexBasis' 83 ], 84 font: [ 85 'fontFamily', 86 'fontSize', 87 'fontStyle', 88 'fontVariant', 89 'fontWeight', 90 'lineHeight' 91 ], 92 margin: [ 93 'marginTop', 94 'marginRight', 95 'marginBottom', 96 'marginLeft' 97 ], 98 outline: [ 99 'outlineColor', 100 'outlineStyle', 101 'outlineWidth' 102 ], 103 padding: [ 104 'paddingTop', 105 'paddingRight', 106 'paddingBottom', 107 'paddingLeft' 108 ] 109 }; 110 111 var shorthandExpanderElem = document.createElementNS('http://www.w3.org/1999/xhtml', 'div'); 112 113 var borderWidthAliases = { 114 thin: '1px', 115 medium: '3px', 116 thick: '5px' 117 }; 118 119 var aliases = { 120 borderBottomWidth: borderWidthAliases, 121 borderLeftWidth: borderWidthAliases, 122 borderRightWidth: borderWidthAliases, 123 borderTopWidth: borderWidthAliases, 124 fontSize: { 125 'xx-small': '60%', 126 'x-small': '75%', 127 'small': '89%', 128 'medium': '100%', 129 'large': '120%', 130 'x-large': '150%', 131 'xx-large': '200%' 132 }, 133 fontWeight: { 134 normal: '400', 135 bold: '700' 136 }, 137 outlineWidth: borderWidthAliases, 138 textShadow: { 139 none: '0px 0px 0px transparent' 140 }, 141 boxShadow: { 142 none: '0px 0px 0px 0px transparent' 143 } 144 }; 145 146 function antiAlias(property, value) { 147 if (property in aliases) { 148 return aliases[property][value] || value; 149 } 150 return value; 151 } 152 153 function isNotAnimatable(property) { 154 // https://w3c.github.io/web-animations/#concept-not-animatable 155 return property === 'display' || property.lastIndexOf('animation', 0) === 0 || property.lastIndexOf('transition', 0) === 0; 156 } 157 158 // This delegates parsing shorthand value syntax to the browser. 159 function expandShorthandAndAntiAlias(property, value, result) { 160 if (isNotAnimatable(property)) { 161 return; 162 } 163 var longProperties = shorthandToLonghand[property]; 164 if (longProperties) { 165 shorthandExpanderElem.style[property] = value; 166 for (var i in longProperties) { 167 var longProperty = longProperties[i]; 168 var longhandValue = shorthandExpanderElem.style[longProperty]; 169 result[longProperty] = antiAlias(longProperty, longhandValue); 170 } 171 } else { 172 result[property] = antiAlias(property, value); 173 } 174 }; 175 176 function convertToArrayForm(effectInput) { 177 var normalizedEffectInput = []; 178 179 for (var property in effectInput) { 180 if (property in ['easing', 'offset', 'composite']) { 181 continue; 182 } 183 184 var values = effectInput[property]; 185 if (!Array.isArray(values)) { 186 values = [values]; 187 } 188 189 var keyframe; 190 var numKeyframes = values.length; 191 for (var i = 0; i < numKeyframes; i++) { 192 keyframe = {}; 193 194 if ('offset' in effectInput) { 195 keyframe.offset = effectInput.offset; 196 } else if (numKeyframes == 1) { 197 keyframe.offset = 1.0; 198 } else { 199 keyframe.offset = i / (numKeyframes - 1.0); 200 } 201 202 if ('easing' in effectInput) { 203 keyframe.easing = effectInput.easing; 204 } 205 206 if ('composite' in effectInput) { 207 keyframe.composite = effectInput.composite; 208 } 209 210 keyframe[property] = values[i]; 211 212 normalizedEffectInput.push(keyframe); 213 } 214 } 215 216 normalizedEffectInput.sort(function(a, b) { return a.offset - b.offset; }); 217 return normalizedEffectInput; 218 }; 219 220 function normalizeKeyframes(effectInput) { 221 if (effectInput == null) { 222 return []; 223 } 224 225 if (window.Symbol && Symbol.iterator && Array.prototype.from && effectInput[Symbol.iterator]) { 226 // Handle custom iterables in most browsers by converting to an array 227 effectInput = Array.from(effectInput); 228 } 229 230 if (!Array.isArray(effectInput)) { 231 effectInput = convertToArrayForm(effectInput); 232 } 233 234 var keyframes = effectInput.map(function(originalKeyframe) { 235 var keyframe = {}; 236 for (var member in originalKeyframe) { 237 var memberValue = originalKeyframe[member]; 238 if (member == 'offset') { 239 if (memberValue != null) { 240 memberValue = Number(memberValue); 241 if (!isFinite(memberValue)) 242 throw new TypeError('Keyframe offsets must be numbers.'); 243 if (memberValue < 0 || memberValue > 1) 244 throw new TypeError('Keyframe offsets must be between 0 and 1.'); 245 } 246 } else if (member == 'composite') { 247 if (memberValue == 'add' || memberValue == 'accumulate') { 248 throw { 249 type: DOMException.NOT_SUPPORTED_ERR, 250 name: 'NotSupportedError', 251 message: 'add compositing is not supported' 252 }; 253 } else if (memberValue != 'replace') { 254 throw new TypeError('Invalid composite mode ' + memberValue + '.'); 255 } 256 } else if (member == 'easing') { 257 memberValue = shared.normalizeEasing(memberValue); 258 } else { 259 memberValue = '' + memberValue; 260 } 261 expandShorthandAndAntiAlias(member, memberValue, keyframe); 262 } 263 if (keyframe.offset == undefined) 264 keyframe.offset = null; 265 if (keyframe.easing == undefined) 266 keyframe.easing = 'linear'; 267 return keyframe; 268 }); 269 270 var everyFrameHasOffset = true; 271 var looselySortedByOffset = true; 272 var previousOffset = -Infinity; 273 for (var i = 0; i < keyframes.length; i++) { 274 var offset = keyframes[i].offset; 275 if (offset != null) { 276 if (offset < previousOffset) { 277 throw new TypeError('Keyframes are not loosely sorted by offset. Sort or specify offsets.'); 278 } 279 previousOffset = offset; 280 } else { 281 everyFrameHasOffset = false; 282 } 283 } 284 285 keyframes = keyframes.filter(function(keyframe) { 286 return keyframe.offset >= 0 && keyframe.offset <= 1; 287 }); 288 289 function spaceKeyframes() { 290 var length = keyframes.length; 291 if (keyframes[length - 1].offset == null) 292 keyframes[length - 1].offset = 1; 293 if (length > 1 && keyframes[0].offset == null) 294 keyframes[0].offset = 0; 295 296 var previousIndex = 0; 297 var previousOffset = keyframes[0].offset; 298 for (var i = 1; i < length; i++) { 299 var offset = keyframes[i].offset; 300 if (offset != null) { 301 for (var j = 1; j < i - previousIndex; j++) 302 keyframes[previousIndex + j].offset = previousOffset + (offset - previousOffset) * j / (i - previousIndex); 303 previousIndex = i; 304 previousOffset = offset; 305 } 306 } 307 } 308 if (!everyFrameHasOffset) 309 spaceKeyframes(); 310 311 return keyframes; 312 } 313 314 shared.convertToArrayForm = convertToArrayForm; 315 shared.normalizeKeyframes = normalizeKeyframes; 316 317 if (WEB_ANIMATIONS_TESTING) { 318 testing.normalizeKeyframes = normalizeKeyframes; 319 } 320 321})(webAnimationsShared, webAnimationsTesting); 322