Skip to content

feat: add transformPerspective prop and card flip demo#34

Merged
janicduplessis merged 2 commits intomainfrom
@janic/transform-perspective
Apr 8, 2026
Merged

feat: add transformPerspective prop and card flip demo#34
janicduplessis merged 2 commits intomainfrom
@janic/transform-perspective

Conversation

@janicduplessis
Copy link
Copy Markdown
Collaborator

Summary

Adds a transformPerspective prop to control the 3D camera distance for rotateX/rotateY animations, and a card flip demo showcasing it.

Previously, perspective was hardcoded to 850 on iOS and Android, with no way to customize it and no perspective on web at all. The default is now 1280, matching React Native's own default, and the value is configurable per-view.

Platform-specific implementation:

  • iOS: Perspective (m34) is included in the composed CATransform3D model transform. Transform animations use individual Core Animation key-paths (transform.rotation.y, transform.scale.x, etc.) instead of full-matrix interpolation to avoid the "fold" artifact on large rotations. For interrupted animations, "from" values are read from the presentation layer; for completed animations, old prop values are used as fallback since CA can't reliably decompose rotation angles from a matrix that also contains m34.
  • Android: Camera distance uses RN's actual normalization formula (density² × perspective × √5). backfaceVisibility: 'hidden' now works correctly — setBackfaceVisibilityDependantOpacity() is called on first mount and during rotation animations.
  • Web: perspective() CSS transform function is added when the animate prop declares rotateX/rotateY. Perspective and rotation are always included in the transform (even at 0°) so CSS transitions interpolate correctly between states.

iOS caveat: On iOS, the parent view must not be flattened by Fabric for perspective to render correctly. The parent needs collapsable={false} or a style that prevents flattening (e.g. transform, zIndex). This is because Fabric can remove "empty" parent views from the native hierarchy, which breaks the 3D rendering context.

Test plan

  • Run the Card Flip demo on iOS, Android, and web
  • Verify 3D perspective is visible during the flip animation
  • Verify backfaceVisibility: 'hidden' correctly hides the back face on all platforms
  • Test rapid tapping (animation interruption) — should smoothly redirect mid-animation
  • Test with different transformPerspective values (e.g. 400 for dramatic, 2000 for subtle)
  • On iOS, verify the parent container has collapsable={false} for correct rendering

- Add `transformPerspective` prop for configurable 3D perspective on all platforms
- iOS: use individual key-path animations for transform sub-properties,
  apply perspective to parent via didMoveToSuperview
- Android: fix cameraDistance formula to match RN (density² × perspective × √5),
  add backface visibility updates during rotation animations
- Web: add perspective() to CSS transform when 3D rotations are active,
  always include rotateX/rotateY in transform for correct CSS transitions
- Add CardFlipDemo example with 3D card flip animation
- Update default perspective from 850 to 1280 (matches RN default)
- Document iOS caveat about Fabric view flattening
@janicduplessis janicduplessis merged commit c89f5eb into main Apr 8, 2026
@janicduplessis janicduplessis deleted the @janic/transform-perspective branch April 8, 2026 22:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant