Skip to content

add transformOrigin style property #2106

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Libraries/Animation/Animated/Animated.js
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,11 @@ class AnimatedStyle extends AnimatedWithChildren {
style[key] = value.getAnimatedValue();
}
}
// if a transformOrigin value is set, it must be passed up with `.getAnimatedValue()` since the two are
// dependent on one another. This is kind of a hacky way to accomplish this. Open to other suggestions.
if (style.transform && !style.transformOrigin && this._style.transformOrigin) {
style.transformOrigin = this._style.transformOrigin;
}
return style;
}

Expand Down
5 changes: 5 additions & 0 deletions Libraries/StyleSheet/TransformPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ var TransformPropTypes = {
ReactPropTypes.shape({translateY: ReactPropTypes.number})
])
),
transformOrigin: ReactPropTypes.shape({
x: ReactPropTypes.number,
y: ReactPropTypes.number,
z: ReactPropTypes.number,
}),

/*
* `transformMatrix` accepts a 4x4 matrix expressed as a row-major ordered
Expand Down
73 changes: 72 additions & 1 deletion Libraries/StyleSheet/precomputeStyle.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ function precomputeStyle(style: ?Object): ?Object {
* interface to native code.
*/
function _precomputeTransforms(style: Object): Object {
var {transform} = style;
var {transform, transformOrigin} = style;
var result = MatrixMath.createIdentityMatrix();

transform.forEach(transformation => {
Expand Down Expand Up @@ -94,6 +94,12 @@ function _precomputeTransforms(style: Object): Object {
}
});

if (transformOrigin) {
// adjusts the `result` transform matrix to be applied from
// the corresponding `transformOrigin`
_applyTransformOrigin(result, transformOrigin);
}

// Android does not support the direct application of a transform matrix to
// a view, so we need to decompose the result matrix into transforms that can
// get applied in the specific order of (1) translate (2) scale (3) rotate.
Expand Down Expand Up @@ -125,6 +131,71 @@ function _multiplyTransform(
MatrixMath.multiplyInto(result, result, matrixToApply);
}

/**
* Takes a transform matrix and an origin, and applies a transformation to
* the matrix to change the transform origin. Mutates the passed in matrix.
*/
function _applyTransformOrigin(
matrix: Array<number>,
origin: Object
): void {
origin = _normalizeOrigin(origin);
var translate = MatrixMath.createIdentityMatrix();
var untranslate = MatrixMath.createIdentityMatrix();
MatrixMath.reuseTranslate3dCommand(
translate,
origin.x,
origin.y,
origin.z
);
MatrixMath.reuseTranslate3dCommand(
untranslate,
-origin.x,
-origin.y,
-origin.z
);
MatrixMath.multiplyInto(matrix, translate, matrix);
MatrixMath.multiplyInto(matrix, matrix, untranslate);
}

/**
* Accepts the user passed in "transformOrigin" property, which
* in the future could be a something other than an object, and
* normalizes it to an {x, y, z} point.
*
* Right now this function is little more than an optimized
* Object.assign, but in the future it could be more complicated.
*/
function _normalizeOrigin(input: Object): Object {
var output = { x: 0, y: 0, z: 0 };
invariant(
false,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typeof input === 'object' ?
Linter says that this code is not reachable.
@lelandrichardson could you fix? That is the only noticeable thing that stops this PR from being accepted.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@azproduction sorry for not seeing this sooner. I'm happy to fix this or anything else if that's all that's stopping it.

Do we feel like the other 3 issues I brought up are compromises we are willing to make at this time?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is important to have transform-origin CSS-compatible in order to avoid future collisions and misunderstandings.

By default, a layer's anchorPoint is (0.5, 0.5), which lies at the center of the layer

// Obj-C Fix
view.layer.anchorPoint = CGPointMake(0, 0);

Which could be applied somewhere here in React/Views/RCTViewManager.m

Or even we could make a separate property and avoid matrix math on JS-side. This also fixes 3-rd issue.

RCT_CUSTOM_VIEW_PROPERTY(transformOrigin, RCTTransformOrigin, RCTView)
{
  view.layer.anchorPoint = json ? [RCTConvert RCTTransformOrigin:json] : defaultView.layer.anchorPoint;
}

// RCTTransformOrigin and [RCTConvert RCTTransformOrigin] to be defined
// defaultView.layer.anchorPoint to be defined as CGPointMake(0, 0)
  • We could use anchorPoint instead of multiplying matrixes in order to support % values (with the default value of 0, 0).
  • Pixel values we could be casted to % using view.frame.

@vjeux any ideas how to fix it a better way?

'transformOrigin should be a string or an object'
);
if (input.x) {
invariant(
typeof input.x === 'number',
'transformOrigin expects a number for x'
);
output.x = input.x;
}
if (input.y) {
invariant(
typeof input.y === 'number',
'transformOrigin expects a number for y'
);
output.y = input.y;
}
if (input.z) {
invariant(
typeof input.z === 'number',
'transformOrigin expects a number for z'
);
output.z = input.z;
}
return output;
}

/**
* Parses a string like '0.5rad' or '60deg' into radians expressed in a float.
* Note that validation on the string is done in `_validateTransform()`.
Expand Down