Skip to content

Commit a38bebe

Browse files
authored
fix: CSS perspective interpolation NaN from the infinity default (#9712)
## Summary Animating a `transform` whose `perspective` falls back to the default (no perspective, represented internally as `infinity`) evaluated `inf + t * (d - inf) = NaN`, which poisoned the transform matrix and made the view disappear. Perspective is now interpolated in reciprocal space (`1/inf = 0`), matching how it enters the matrix (`-1/d`) and the spec's matrix interpolation. Finite-to-finite perspective is unchanged. The old `value == 0` guards were dead code - `sanitizePerspectiveValue` forces `|value| >= 1`, so perspective can never be `0` - so they are removed. ## Before / After Verified on the Android emulator across several CSS animations that interpolate `perspective` from/to the default (no-perspective) endpoint - the case this fixes: - `[]` -> `[{ perspective: 100 }, { rotateY: '50deg' }]` - `[]` -> `[{ perspective: 100 }, { rotateX: '50deg' }]` - `[{ perspective: 100 }, { rotateY: '50deg' }]` -> `[]` https://github.com/user-attachments/assets/1d7ca900-72fd-48e9-a7fc-ef141e22c14c
1 parent a9c93a4 commit a38bebe

1 file changed

Lines changed: 12 additions & 5 deletions

File tree

packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/transforms/TransformOperationInterpolator.cpp

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
#include <reanimated/CSS/interpolation/transforms/operations/skew.h>
66
#include <reanimated/CSS/interpolation/transforms/operations/translate.h>
77

8+
#include <cmath>
9+
#include <limits>
810
#include <utility>
911

1012
namespace reanimated::css {
@@ -31,14 +33,19 @@ std::unique_ptr<StyleOperation> TransformOperationInterpolator<PerspectiveOperat
3133
const std::shared_ptr<StyleOperation> &from,
3234
const std::shared_ptr<StyleOperation> &to,
3335
const StyleOperationsInterpolationContext & /* context */) const {
34-
// TODO - check if this implementation is correct
3536
const auto &fromValue = std::static_pointer_cast<PerspectiveOperation>(from)->value;
3637
const auto &toValue = std::static_pointer_cast<PerspectiveOperation>(to)->value;
3738

38-
if (fromValue.value == 0)
39-
return std::make_unique<PerspectiveOperation>(toValue);
40-
if (toValue.value == 0)
41-
return std::make_unique<PerspectiveOperation>(fromValue);
39+
// The default "no perspective" is infinity, so interpolating the distance
40+
// directly gives inf + t*(d - inf) = NaN. Interpolate in reciprocal space
41+
// instead (1/inf = 0, matching how perspective enters the matrix as -1/d).
42+
if (std::isinf(fromValue.value) || std::isinf(toValue.value)) {
43+
const double fromReciprocal = 1.0 / fromValue.value;
44+
const double toReciprocal = 1.0 / toValue.value;
45+
const double reciprocal = fromReciprocal + progress * (toReciprocal - fromReciprocal);
46+
return std::make_unique<PerspectiveOperation>(
47+
reciprocal == 0.0 ? std::numeric_limits<double>::infinity() : 1.0 / reciprocal);
48+
}
4249

4350
return std::make_unique<PerspectiveOperation>(fromValue.interpolate(progress, toValue));
4451
}

0 commit comments

Comments
 (0)