UNPKG

animated

Version:

Declarative Animations Library for React and React Native

185 lines (167 loc) 5.3 kB
/** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * * @flow */ 'use strict'; var Animated = require('../'); // Following are transform functions who accept arguments of type <length> or <length-percentage>. // These functions won't work if we send them numbers, so we convert those numbers to px. // Source: https://developer.mozilla.org/en-US/docs/Web/CSS/transform?v=b const transformWithLengthUnits = { translateX: true, translateY: true, translateZ: true, perspective: true, }; // { translateY: 35 } => 'translateY(35px)' // { scale: 2 } => 'scale(2)' function mapTransform(t) { var k = Object.keys(t)[0]; var unit = (transformWithLengthUnits[k] && typeof t[k] === 'number') ? 'px' : ''; return `${k}(${t[k]}${unit})`; } const isUnitlessNumber = { animationIterationCount: true, borderImageOutset: true, borderImageSlice: true, borderImageWidth: true, boxFlex: true, boxFlexGroup: true, boxOrdinalGroup: true, columnCount: true, columns: true, flex: true, flexGrow: true, flexPositive: true, flexShrink: true, flexNegative: true, flexOrder: true, gridRow: true, gridRowEnd: true, gridRowSpan: true, gridRowStart: true, gridColumn: true, gridColumnEnd: true, gridColumnSpan: true, gridColumnStart: true, fontWeight: true, lineClamp: true, lineHeight: true, opacity: true, order: true, orphans: true, tabSize: true, widows: true, zIndex: true, zoom: true, // SVG-related properties fillOpacity: true, floodOpacity: true, stopOpacity: true, strokeDasharray: true, strokeDashoffset: true, strokeMiterlimit: true, strokeOpacity: true, strokeWidth: true, }; /** * @param {string} prefix vendor-specific prefix, eg: Webkit * @param {string} key style name, eg: transitionDuration * @return {string} style name prefixed with `prefix`, properly camelCased, eg: * WebkitTransitionDuration */ function prefixKey(prefix, key) { return prefix + key.charAt(0).toUpperCase() + key.substring(1); } /** * Support style names that may come passed in prefixed by adding permutations * of vendor prefixes. */ var prefixes = ['Webkit', 'ms', 'Moz', 'O']; // Using Object.keys here, or else the vanilla for-in loop makes IE8 go into an // infinite loop, because it iterates over the newly added props too. Object.keys(isUnitlessNumber).forEach(function(prop) { prefixes.forEach(function(prefix) { isUnitlessNumber[prefixKey(prefix, prop)] = isUnitlessNumber[prop]; }); }); // NOTE(lmr): // Since this is a hot code path, right now this is mutative... // As far as I can tell, this shouldn't cause any unexpected behavior. function mapStyle(style) { if (style && style.transform && typeof style.transform !== 'string') { // TODO(lmr): this doesn't attempt to use vendor prefixed styles style.transform = style.transform.map(mapTransform).join(' '); } return style; } function dangerousStyleValue(name, value, isCustomProperty) { // Note that we've removed escapeTextForBrowser() calls here since the // whole string will be escaped when the attribute is injected into // the markup. If you provide unsafe user data here they can inject // arbitrary CSS which may be problematic (I couldn't repro this): // https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet // http://www.thespanner.co.uk/2007/11/26/ultimate-xss-css-injection/ // This is not an XSS hole but instead a potential CSS injection issue // which has lead to a greater discussion about how we're going to // trust URLs moving forward. See #2115901 var isEmpty = value == null || typeof value === 'boolean' || value === ''; if (isEmpty) { return ''; } if ( !isCustomProperty && typeof value === 'number' && value !== 0 && !(isUnitlessNumber.hasOwnProperty(name) && isUnitlessNumber[name]) ) { return value + 'px'; // Presumes implicit 'px' suffix for unitless numbers } return ('' + value).trim(); } function setValueForStyles(node, styles) { var style = node.style; for (var styleName in styles) { if (!styles.hasOwnProperty(styleName)) { continue; } var isCustomProperty = styleName.indexOf('--') === 0; var styleValue = dangerousStyleValue( styleName, styles[styleName], isCustomProperty, ); if (styleName === 'float') { styleName = 'cssFloat'; } if (isCustomProperty) { style.setProperty(styleName, styleValue); } else { style[styleName] = styleValue; } } } function ApplyAnimatedValues(instance, props) { if (instance.setNativeProps) { instance.setNativeProps(props); } else if (instance.nodeType && instance.setAttribute !== undefined) { setValueForStyles(instance, mapStyle(props.style)); } else { return false; } } Animated .inject .ApplyAnimatedValues(ApplyAnimatedValues, mapStyle); module.exports = { ...Animated, div: Animated.createAnimatedComponent('div'), span: Animated.createAnimatedComponent('span'), img: Animated.createAnimatedComponent('img'), };