processTransform.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. /**
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. *
  7. * @format
  8. * @flow
  9. */
  10. 'use strict';
  11. const MatrixMath = require('../Utilities/MatrixMath');
  12. const Platform = require('../Utilities/Platform');
  13. const invariant = require('invariant');
  14. const stringifySafe = require('../Utilities/stringifySafe').default;
  15. /**
  16. * Generate a transform matrix based on the provided transforms, and use that
  17. * within the style object instead.
  18. *
  19. * This allows us to provide an API that is similar to CSS, where transforms may
  20. * be applied in an arbitrary order, and yet have a universal, singular
  21. * interface to native code.
  22. */
  23. function processTransform(
  24. transform: Array<Object>,
  25. ): Array<Object> | Array<number> {
  26. if (__DEV__) {
  27. _validateTransforms(transform);
  28. }
  29. // Android & iOS implementations of transform property accept the list of
  30. // transform properties as opposed to a transform Matrix. This is necessary
  31. // to control transform property updates completely on the native thread.
  32. if (Platform.OS === 'android' || Platform.OS === 'ios') {
  33. return transform;
  34. }
  35. const result = MatrixMath.createIdentityMatrix();
  36. transform.forEach(transformation => {
  37. const key = Object.keys(transformation)[0];
  38. const value = transformation[key];
  39. switch (key) {
  40. case 'matrix':
  41. MatrixMath.multiplyInto(result, result, value);
  42. break;
  43. case 'perspective':
  44. _multiplyTransform(result, MatrixMath.reusePerspectiveCommand, [value]);
  45. break;
  46. case 'rotateX':
  47. _multiplyTransform(result, MatrixMath.reuseRotateXCommand, [
  48. _convertToRadians(value),
  49. ]);
  50. break;
  51. case 'rotateY':
  52. _multiplyTransform(result, MatrixMath.reuseRotateYCommand, [
  53. _convertToRadians(value),
  54. ]);
  55. break;
  56. case 'rotate':
  57. case 'rotateZ':
  58. _multiplyTransform(result, MatrixMath.reuseRotateZCommand, [
  59. _convertToRadians(value),
  60. ]);
  61. break;
  62. case 'scale':
  63. _multiplyTransform(result, MatrixMath.reuseScaleCommand, [value]);
  64. break;
  65. case 'scaleX':
  66. _multiplyTransform(result, MatrixMath.reuseScaleXCommand, [value]);
  67. break;
  68. case 'scaleY':
  69. _multiplyTransform(result, MatrixMath.reuseScaleYCommand, [value]);
  70. break;
  71. case 'translate':
  72. _multiplyTransform(result, MatrixMath.reuseTranslate3dCommand, [
  73. value[0],
  74. value[1],
  75. value[2] || 0,
  76. ]);
  77. break;
  78. case 'translateX':
  79. _multiplyTransform(result, MatrixMath.reuseTranslate2dCommand, [
  80. value,
  81. 0,
  82. ]);
  83. break;
  84. case 'translateY':
  85. _multiplyTransform(result, MatrixMath.reuseTranslate2dCommand, [
  86. 0,
  87. value,
  88. ]);
  89. break;
  90. case 'skewX':
  91. _multiplyTransform(result, MatrixMath.reuseSkewXCommand, [
  92. _convertToRadians(value),
  93. ]);
  94. break;
  95. case 'skewY':
  96. _multiplyTransform(result, MatrixMath.reuseSkewYCommand, [
  97. _convertToRadians(value),
  98. ]);
  99. break;
  100. default:
  101. throw new Error('Invalid transform name: ' + key);
  102. }
  103. });
  104. return result;
  105. }
  106. /**
  107. * Performs a destructive operation on a transform matrix.
  108. */
  109. function _multiplyTransform(
  110. result: Array<number>,
  111. matrixMathFunction: Function,
  112. args: Array<number>,
  113. ): void {
  114. const matrixToApply = MatrixMath.createIdentityMatrix();
  115. const argsWithIdentity = [matrixToApply].concat(args);
  116. matrixMathFunction.apply(this, argsWithIdentity);
  117. MatrixMath.multiplyInto(result, result, matrixToApply);
  118. }
  119. /**
  120. * Parses a string like '0.5rad' or '60deg' into radians expressed in a float.
  121. * Note that validation on the string is done in `_validateTransform()`.
  122. */
  123. function _convertToRadians(value: string): number {
  124. const floatValue = parseFloat(value);
  125. return value.indexOf('rad') > -1 ? floatValue : (floatValue * Math.PI) / 180;
  126. }
  127. function _validateTransforms(transform: Array<Object>): void {
  128. transform.forEach(transformation => {
  129. const keys = Object.keys(transformation);
  130. invariant(
  131. keys.length === 1,
  132. 'You must specify exactly one property per transform object. Passed properties: %s',
  133. stringifySafe(transformation),
  134. );
  135. const key = keys[0];
  136. const value = transformation[key];
  137. _validateTransform(key, value, transformation);
  138. });
  139. }
  140. function _validateTransform(key, value, transformation) {
  141. invariant(
  142. !value.getValue,
  143. 'You passed an Animated.Value to a normal component. ' +
  144. 'You need to wrap that component in an Animated. For example, ' +
  145. 'replace <View /> by <Animated.View />.',
  146. );
  147. const multivalueTransforms = ['matrix', 'translate'];
  148. if (multivalueTransforms.indexOf(key) !== -1) {
  149. invariant(
  150. Array.isArray(value),
  151. 'Transform with key of %s must have an array as the value: %s',
  152. key,
  153. stringifySafe(transformation),
  154. );
  155. }
  156. switch (key) {
  157. case 'matrix':
  158. invariant(
  159. value.length === 9 || value.length === 16,
  160. 'Matrix transform must have a length of 9 (2d) or 16 (3d). ' +
  161. 'Provided matrix has a length of %s: %s',
  162. /* $FlowFixMe(>=0.84.0 site=react_native_fb) This comment suppresses an
  163. * error found when Flow v0.84 was deployed. To see the error, delete
  164. * this comment and run Flow. */
  165. value.length,
  166. stringifySafe(transformation),
  167. );
  168. break;
  169. case 'translate':
  170. invariant(
  171. value.length === 2 || value.length === 3,
  172. 'Transform with key translate must be an array of length 2 or 3, found %s: %s',
  173. /* $FlowFixMe(>=0.84.0 site=react_native_fb) This comment suppresses an
  174. * error found when Flow v0.84 was deployed. To see the error, delete
  175. * this comment and run Flow. */
  176. value.length,
  177. stringifySafe(transformation),
  178. );
  179. break;
  180. case 'rotateX':
  181. case 'rotateY':
  182. case 'rotateZ':
  183. case 'rotate':
  184. case 'skewX':
  185. case 'skewY':
  186. invariant(
  187. typeof value === 'string',
  188. 'Transform with key of "%s" must be a string: %s',
  189. key,
  190. stringifySafe(transformation),
  191. );
  192. invariant(
  193. value.indexOf('deg') > -1 || value.indexOf('rad') > -1,
  194. 'Rotate transform must be expressed in degrees (deg) or radians ' +
  195. '(rad): %s',
  196. stringifySafe(transformation),
  197. );
  198. break;
  199. case 'perspective':
  200. invariant(
  201. typeof value === 'number',
  202. 'Transform with key of "%s" must be a number: %s',
  203. key,
  204. stringifySafe(transformation),
  205. );
  206. invariant(
  207. value !== 0,
  208. 'Transform with key of "%s" cannot be zero: %s',
  209. key,
  210. stringifySafe(transformation),
  211. );
  212. break;
  213. case 'translateX':
  214. case 'translateY':
  215. case 'scale':
  216. case 'scaleX':
  217. case 'scaleY':
  218. invariant(
  219. typeof value === 'number',
  220. 'Transform with key of "%s" must be a number: %s',
  221. key,
  222. stringifySafe(transformation),
  223. );
  224. break;
  225. default:
  226. invariant(
  227. false,
  228. 'Invalid transform %s: %s',
  229. key,
  230. stringifySafe(transformation),
  231. );
  232. }
  233. }
  234. module.exports = processTransform;