UNPKG

web-animations-js

Version:

JavaScript implementation of the Web Animations API

240 lines (219 loc) 8.6 kB
// Copyright 2014 Google Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. (function(scope, testing) { var SVG_TRANSFORM_PROP = '_webAnimationsUpdateSvgTransformAttr'; /** * IE/Edge do not support `transform` styles for SVG elements. Instead, * `transform` attribute can be animated with some restrictions. * See https://connect.microsoft.com/IE/feedback/details/811744/ie11-bug-with-implementation-of-css-transforms-in-svg, * https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/1173754/, * https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/101242/, etc. * The same problem is exhibited by pre-Chrome Android browsers (ICS). * Unfortunately, there's no easy way to feature-detect it. */ function updateSvgTransformAttr(window, element) { if (!element.namespaceURI || element.namespaceURI.indexOf('/svg') == -1) { return false; } if (!(SVG_TRANSFORM_PROP in window)) { window[SVG_TRANSFORM_PROP] = /Trident|MSIE|IEMobile|Edge|Android 4/i.test(window.navigator.userAgent); } return window[SVG_TRANSFORM_PROP]; } var styleAttributes = { cssText: 1, length: 1, parentRule: 1, }; var styleMethods = { getPropertyCSSValue: 1, getPropertyPriority: 1, getPropertyValue: 1, item: 1, removeProperty: 1, setProperty: 1, }; var styleMutatingMethods = { removeProperty: 1, setProperty: 1, }; function configureProperty(object, property, descriptor) { descriptor.enumerable = true; descriptor.configurable = true; Object.defineProperty(object, property, descriptor); } function AnimatedCSSStyleDeclaration(element) { WEB_ANIMATIONS_TESTING && console.assert(!(element.style instanceof AnimatedCSSStyleDeclaration), 'Element must not already have an animated style attached.'); this._element = element; // Stores the inline style of the element on its behalf while the // polyfill uses the element's inline style to simulate web animations. // This is needed to fake regular inline style CSSOM access on the element. this._surrogateStyle = document.createElementNS('http://www.w3.org/1999/xhtml', 'div').style; this._style = element.style; this._length = 0; this._isAnimatedProperty = {}; this._updateSvgTransformAttr = updateSvgTransformAttr(window, element); this._savedTransformAttr = null; // Copy the inline style contents over to the surrogate. for (var i = 0; i < this._style.length; i++) { var property = this._style[i]; this._surrogateStyle[property] = this._style[property]; } this._updateIndices(); } AnimatedCSSStyleDeclaration.prototype = { get cssText() { return this._surrogateStyle.cssText; }, set cssText(text) { var isAffectedProperty = {}; for (var i = 0; i < this._surrogateStyle.length; i++) { isAffectedProperty[this._surrogateStyle[i]] = true; } this._surrogateStyle.cssText = text; this._updateIndices(); for (var i = 0; i < this._surrogateStyle.length; i++) { isAffectedProperty[this._surrogateStyle[i]] = true; } for (var property in isAffectedProperty) { if (!this._isAnimatedProperty[property]) { this._style.setProperty(property, this._surrogateStyle.getPropertyValue(property)); } } }, get length() { return this._surrogateStyle.length; }, get parentRule() { return this._style.parentRule; }, // Mirror the indexed getters and setters of the surrogate style. _updateIndices: function() { while (this._length < this._surrogateStyle.length) { Object.defineProperty(this, this._length, { configurable: true, enumerable: false, get: (function(index) { return function() { return this._surrogateStyle[index]; }; })(this._length) }); this._length++; } while (this._length > this._surrogateStyle.length) { this._length--; Object.defineProperty(this, this._length, { configurable: true, enumerable: false, value: undefined }); } }, _set: function(property, value) { this._style[property] = value; this._isAnimatedProperty[property] = true; if (this._updateSvgTransformAttr && scope.unprefixedPropertyName(property) == 'transform') { // On IE/Edge, also set SVG element's `transform` attribute to 2d // matrix of the transform. The `transform` style does not work, but // `transform` attribute can be used instead. // Notice, if the platform indeed supports SVG/CSS transforms the CSS // declaration is supposed to override the attribute. if (this._savedTransformAttr == null) { this._savedTransformAttr = this._element.getAttribute('transform'); } this._element.setAttribute('transform', scope.transformToSvgMatrix(value)); } }, _clear: function(property) { this._style[property] = this._surrogateStyle[property]; if (this._updateSvgTransformAttr && scope.unprefixedPropertyName(property) == 'transform') { if (this._savedTransformAttr) { this._element.setAttribute('transform', this._savedTransformAttr); } else { this._element.removeAttribute('transform'); } this._savedTransformAttr = null; } delete this._isAnimatedProperty[property]; }, }; // Wrap the style methods. for (var method in styleMethods) { AnimatedCSSStyleDeclaration.prototype[method] = (function(method, modifiesStyle) { return function() { var result = this._surrogateStyle[method].apply(this._surrogateStyle, arguments); if (modifiesStyle) { if (!this._isAnimatedProperty[arguments[0]]) this._style[method].apply(this._style, arguments); this._updateIndices(); } return result; } })(method, method in styleMutatingMethods); } // Wrap the style.cssProperty getters and setters. for (var property in document.documentElement.style) { if (property in styleAttributes || property in styleMethods) { continue; } (function(property) { configureProperty(AnimatedCSSStyleDeclaration.prototype, property, { get: function() { return this._surrogateStyle[property]; }, set: function(value) { this._surrogateStyle[property] = value; this._updateIndices(); if (!this._isAnimatedProperty[property]) this._style[property] = value; } }); })(property); } function ensureStyleIsPatched(element) { if (element._webAnimationsPatchedStyle) return; var animatedStyle = new AnimatedCSSStyleDeclaration(element); try { configureProperty(element, 'style', { get: function() { return animatedStyle; } }); } catch (_) { // iOS and older versions of Safari (pre v7) do not support overriding an element's // style object. Animations will clobber any inline styles as a result. element.style._set = function(property, value) { element.style[property] = value; }; element.style._clear = function(property) { element.style[property] = ''; }; } // We must keep a handle on the patched style to prevent it from getting GC'd. element._webAnimationsPatchedStyle = element.style; } scope.apply = function(element, property, value) { ensureStyleIsPatched(element); element.style._set(scope.propertyName(property), value); }; scope.clear = function(element, property) { if (element._webAnimationsPatchedStyle) { element.style._clear(scope.propertyName(property)); } }; if (WEB_ANIMATIONS_TESTING) { testing.ensureStyleIsPatched = ensureStyleIsPatched; testing.updateSvgTransformAttr = updateSvgTransformAttr; } })(webAnimations1, webAnimationsTesting);