react-native-screen-transitions
Version:
Easy screen transitions for React Native and Expo
580 lines (557 loc) • 17.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ZoomIn = exports.SlideFromTop = exports.SlideFromBottom = exports.SharedXImage = exports.SharedIGImage = exports.SharedAppleMusic = exports.ElasticCard = exports.DraggableCard = void 0;
var _reactNative = require("react-native");
var _reactNativeReanimated = require("react-native-reanimated");
var _specs = require("./specs");
const platform = _reactNative.Platform.OS;
const SlideFromTop = (config = {}) => {
return {
enableTransitions: true,
gestureEnabled: true,
gestureDirection: "vertical-inverted",
screenStyleInterpolator: ({
progress,
layouts: {
screen: {
height
}
}
}) => {
"worklet";
const y = (0, _reactNativeReanimated.interpolate)(progress, [0, 1, 2], [-height, 0, height]);
return {
contentStyle: {
transform: [{
translateY: y
}]
}
};
},
transitionSpec: {
open: _specs.DefaultSpec,
close: _specs.DefaultSpec
},
...config
};
};
exports.SlideFromTop = SlideFromTop;
const ZoomIn = (config = {}) => {
return {
enableTransitions: true,
gestureEnabled: false,
screenStyleInterpolator: ({
progress
}) => {
"worklet";
const scale = (0, _reactNativeReanimated.interpolate)(progress, [0, 1, 2], [0.5, 1, 0.5], _reactNativeReanimated.Extrapolation.CLAMP);
const opacity = (0, _reactNativeReanimated.interpolate)(progress, [0, 1, 2], [0, 1, 0], _reactNativeReanimated.Extrapolation.CLAMP);
return {
contentStyle: {
transform: [{
scale
}],
opacity
}
};
},
transitionSpec: {
open: _specs.DefaultSpec,
close: _specs.DefaultSpec
},
...config
};
};
exports.ZoomIn = ZoomIn;
const SlideFromBottom = (config = {}) => {
return {
enableTransitions: true,
gestureEnabled: true,
gestureDirection: "vertical",
screenStyleInterpolator: ({
layouts: {
screen: {
height
}
},
progress
}) => {
"worklet";
const y = (0, _reactNativeReanimated.interpolate)(progress, [0, 1, 2], [height, 0, -height]);
return {
contentStyle: {
transform: [{
translateY: y
}]
}
};
},
transitionSpec: {
open: _specs.DefaultSpec,
close: _specs.DefaultSpec
},
...config
};
};
exports.SlideFromBottom = SlideFromBottom;
const DraggableCard = (config = {}) => {
return {
enableTransitions: true,
gestureEnabled: true,
gestureDirection: ["horizontal", "vertical"],
screenStyleInterpolator: ({
current,
progress,
layouts: {
screen
}
}) => {
"worklet";
/** Combined */
const scale = (0, _reactNativeReanimated.interpolate)(progress, [0, 1, 2], [0, 1, 0.75]);
/** Vertical */
const translateY = (0, _reactNativeReanimated.interpolate)(current.gesture.normalizedY, [-1, 1], [-screen.height * 0.5, screen.height * 0.5], "clamp");
/** Horizontal */
const translateX = (0, _reactNativeReanimated.interpolate)(current.gesture.normalizedX, [-1, 1], [-screen.width * 0.5, screen.width * 0.5], "clamp");
return {
contentStyle: {
transform: [{
scale
}, {
translateY: translateY
}, {
translateX
}]
}
};
},
transitionSpec: {
open: _specs.DefaultSpec,
close: _specs.DefaultSpec
},
...config
};
};
exports.DraggableCard = DraggableCard;
const ElasticCard = (config = {
elasticFactor: 0.5
}) => {
return {
enableTransitions: true,
gestureEnabled: true,
gestureDirection: "bidirectional",
screenStyleInterpolator: ({
current,
next,
layouts: {
screen
},
progress
}) => {
"worklet";
/**
* Applies to both screens ( previous and incoming)
*/
const scale = (0, _reactNativeReanimated.interpolate)(progress, [0, 1, 2], [0, 1, 0.8]);
// applies to current screen
const maxElasticityX = screen.width * (config.elasticFactor ?? 0.5);
const maxElasticityY = screen.height * (config.elasticFactor ?? 0.5);
const translateX = (0, _reactNativeReanimated.interpolate)(current.gesture.normalizedX, [-1, 0, 1], [-maxElasticityX, 0, maxElasticityX], "clamp");
const translateY = (0, _reactNativeReanimated.interpolate)(current.gesture.normalizedY, [-1, 0, 1], [-maxElasticityY, 0, maxElasticityY], "clamp");
// applies to unfocused screen ( previous screen )
const overlayColor = (0, _reactNativeReanimated.interpolateColor)(progress, [0, 1], ["rgba(0,0,0,0)", "rgba(0,0,0,0.5)"]);
return {
contentStyle: {
transform: [{
scale
}, {
translateX
}, {
translateY
}]
},
overlayStyle: {
backgroundColor: !next ? overlayColor : "rgba(0,0,0,0)"
}
};
},
transitionSpec: {
open: _specs.DefaultSpec,
close: _specs.DefaultSpec
},
...config
};
};
exports.ElasticCard = ElasticCard;
const SharedIGImage = (config = {}) => {
return {
gestureEnabled: true,
gestureDirection: ["vertical", "horizontal"],
enableTransitions: true,
gestureDrivesProgress: false,
screenStyleInterpolator: ({
current,
layouts: {
screen: {
height,
width
}
},
bounds,
progress,
focused,
activeBoundId,
active
}) => {
"worklet";
const normX = active.gesture.normalizedX;
const normY = active.gesture.normalizedY;
// animations for both bounds
const dragX = (0, _reactNativeReanimated.interpolate)(normX, [-1, 0, 1], [-width * 0.7, 0, width * 0.7], "clamp");
const dragY = (0, _reactNativeReanimated.interpolate)(normY, [-1, 0, 1], [-height * 0.4, 0, height * 0.4], "clamp");
const dragXScale = (0, _reactNativeReanimated.interpolate)(normX, [0, 1], [1, 0.8]);
const dragYScale = (0, _reactNativeReanimated.interpolate)(normY, [0, 1], [1, 0.8]);
const boundValues = bounds({
method: focused ? "content" : "transform",
scaleMode: "uniform",
raw: true
});
// focused specific animations
if (focused) {
const maskedValues = bounds({
space: "absolute",
target: "fullscreen",
method: "size",
raw: true
});
return {
overlayStyle: {
backgroundColor: "black",
opacity: (0, _reactNativeReanimated.interpolate)(progress, [0, 1], [0, 0.5])
},
contentStyle: {
transform: [{
translateX: dragX
}, {
translateY: dragY
}, {
scale: dragXScale
}, {
scale: dragYScale
}]
},
_ROOT_CONTAINER: {
transform: [{
translateX: boundValues.translateX || 0
}, {
translateY: boundValues.translateY || 0
},
//@ts-expect-error
{
scale: boundValues.scale || 1
}]
},
_ROOT_MASKED: {
width: maskedValues.width,
height: maskedValues.height,
transform: [{
translateX: maskedValues.translateX || 0
}, {
translateY: maskedValues.translateY || 0
}],
borderRadius: (0, _reactNativeReanimated.interpolate)(progress, [0, 1], [0, 24])
}
};
}
return {
contentStyle: {
pointerEvents: current.gesture.isDismissing ? "none" : "auto"
},
[activeBoundId]: {
transform: [{
translateX: dragX || 0
}, {
translateY: dragY || 0
}, {
translateX: boundValues.translateX || 0
}, {
translateY: boundValues.translateY || 0
}, {
scaleX: boundValues.scaleX || 1
}, {
scaleY: boundValues.scaleY || 1
}, {
scale: dragXScale
}, {
scale: dragYScale
}]
}
};
},
transitionSpec: {
open: {
stiffness: 1500,
damping: 1000,
mass: 3,
overshootClamping: true,
//@ts-expect-error
restSpeedThreshold: 0.02
},
close: {
stiffness: 1500,
damping: 1000,
mass: 3,
overshootClamping: true,
//@ts-expect-error
restSpeedThreshold: 0.02
}
},
...config
};
};
exports.SharedIGImage = SharedIGImage;
const SharedAppleMusic = (config = {}) => {
return {
enableTransitions: true,
gestureEnabled: true,
gestureDirection: ["vertical", "horizontal"],
gestureDrivesProgress: false,
screenStyleInterpolator: ({
bounds,
activeBoundId,
focused,
progress,
layouts: {
screen
},
current,
active
}) => {
"worklet";
const normX = active.gesture.normalizedX;
const normY = active.gesture.normalizedY;
const initialDirection = active.gesture.direction;
/**
* ===============================
* Animations for both bounds
* ===============================
*/
const xResistance = initialDirection === "horizontal" ? 0.7 : 0.4;
const yResistance = initialDirection === "vertical" ? 0.7 : 0.4;
const xScaleOuput = initialDirection === "horizontal" ? [1, 0.5] : [1, 1];
const yScaleOuput = initialDirection === "vertical" ? [1, 0.5] : [1, 1];
const dragX = (0, _reactNativeReanimated.interpolate)(normX, [-1, 0, 1], [-screen.width * xResistance, 0, screen.width * xResistance], "clamp");
const dragY = (0, _reactNativeReanimated.interpolate)(normY, [-1, 0, 1], [-screen.height * yResistance, 0, screen.height * yResistance], "clamp");
const dragXScale = (0, _reactNativeReanimated.interpolate)(normX, [0, 1], xScaleOuput);
const dragYScale = (0, _reactNativeReanimated.interpolate)(normY, [0, 1], yScaleOuput);
const boundValues = bounds({
method: focused ? "content" : "transform",
anchor: "top",
scaleMode: "uniform",
raw: true
});
const opacity = (0, _reactNativeReanimated.interpolate)(progress, [0, 0.25, 1.25, 2], [0, 1, 1, 0], "clamp");
/**
* ===============================
* Focused specific animations
* ===============================
*/
if (focused) {
const maskedValues = bounds({
space: "absolute",
method: "size",
target: "fullscreen",
raw: true
});
// Apple Music style drop shadow that increases with drag magnitude
const dragMagnitude = Math.max(Math.abs(normX), Math.abs(normY));
const shadowOpacity = (0, _reactNativeReanimated.interpolate)(dragMagnitude, [0, 1], [0, 0.25], "clamp");
const shadowRadius = (0, _reactNativeReanimated.interpolate)(dragMagnitude, [0, 1], [0, 24], "clamp");
const shadowOffsetY = (0, _reactNativeReanimated.interpolate)(dragMagnitude, [0, 1], [0, 20], "clamp");
const elevation = (0, _reactNativeReanimated.interpolate)(dragMagnitude, [0, 1], [0, 24], "clamp");
const IOSShadowStyle = {
shadowColor: "#000",
shadowOpacity,
shadowRadius,
shadowOffset: {
width: 0,
height: shadowOffsetY
}
};
const AndroidShadowStyle = {
elevation,
shadowColor: "#000"
};
return {
contentStyle: {
pointerEvents: current.animating ? "none" : "auto",
transform: [{
translateX: dragX || 0
}, {
translateY: dragY || 0
}, {
scale: dragXScale
}, {
scale: dragYScale
}],
opacity,
...(platform === "ios" ? IOSShadowStyle : AndroidShadowStyle)
},
_ROOT_CONTAINER: {
transform: [{
translateX: boundValues.translateX || 0
}, {
translateY: boundValues.translateY || 0
},
//@ts-expect-error
{
scale: boundValues.scale || 1
}]
},
_ROOT_MASKED: {
width: maskedValues.width,
height: maskedValues.height,
transform: [{
translateX: maskedValues.translateX || 0
}, {
translateY: maskedValues.translateY || 0
}],
borderRadius: (0, _reactNativeReanimated.interpolate)(progress, [0, 1], [0, 24])
}
};
}
/**
* ===============================
* Unfocused specific animations
* ===============================
*/
const scaledBoundTranslateX = (boundValues.translateX || 0) * dragXScale;
const scaledBoundTranslateY = (boundValues.translateY || 0) * dragYScale;
const scaledBoundScaleX = (boundValues.scaleX || 1) * dragXScale;
const scaledBoundScaleY = (boundValues.scaleY || 1) * dragYScale;
const contentScale = (0, _reactNativeReanimated.interpolate)(progress, [1, 2], [1, 0.9], "clamp");
return {
[activeBoundId]: {
transform: [{
translateX: dragX || 0
}, {
translateY: dragY || 0
}, {
translateX: scaledBoundTranslateX
}, {
translateY: scaledBoundTranslateY
}, {
scale: dragXScale
}, {
scale: dragYScale
}, {
scaleX: scaledBoundScaleX
}, {
scaleY: scaledBoundScaleY
}],
opacity,
zIndex: current.animating ? 999 : -1,
position: "relative"
},
contentStyle: {
transform: [{
scale: contentScale
}]
}
};
},
transitionSpec: {
open: {
stiffness: 1000,
damping: 500,
mass: 3,
overshootClamping: true,
//@ts-expect-error
restSpeedThreshold: 0.02
},
close: {
stiffness: 600,
damping: 60,
mass: 4,
overshootClamping: false,
//@ts-expect-error
restSpeedThreshold: 0.02,
restDisplacementThreshold: 0.002
}
},
...config
};
};
exports.SharedAppleMusic = SharedAppleMusic;
const SharedXImage = (config = {}) => {
return {
enableTransitions: true,
gestureEnabled: true,
gestureDirection: ["vertical", "vertical-inverted"],
gestureDrivesProgress: false,
screenStyleInterpolator: ({
focused,
activeBoundId,
bounds,
current,
layouts: {
screen
},
progress
}) => {
"worklet";
// twitter doesn't animate the unfocused screen
if (!focused) return {};
const boundValues = bounds({
method: "transform",
raw: true
});
// content styles
const dragY = (0, _reactNativeReanimated.interpolate)(current.gesture.normalizedY, [-1, 0, 1], [-screen.height, 0, screen.height]);
// dynamically changes direction based on the drag direction
const contentY = (0, _reactNativeReanimated.interpolate)(progress, [0, 1], [dragY >= 0 ? screen.height : -screen.height, 0]);
const overlayClr = (0, _reactNativeReanimated.interpolateColor)(current.progress - Math.abs(current.gesture.normalizedY), [0, 1], ["rgba(0,0,0,0)", "rgba(0,0,0,1)"]);
const borderRadius = (0, _reactNativeReanimated.interpolate)(current.progress, [0, 1], [12, 0]);
// bound styles - only enter animation
const x = !current.closing ? boundValues.translateX : 0;
const y = !current.closing ? boundValues.translateY : 0;
const scaleX = !current.closing ? boundValues.scaleX : 1;
const scaleY = !current.closing ? boundValues.scaleY : 1;
return {
[activeBoundId]: {
transform: [{
translateX: x
}, {
translateY: y
}, {
scaleX: scaleX
}, {
scaleY: scaleY
}],
borderRadius,
overflow: "hidden"
},
contentStyle: {
transform: [{
translateY: contentY
}, {
translateY: dragY
}],
pointerEvents: current.animating ? "none" : "auto"
},
overlayStyle: {
backgroundColor: overlayClr
}
};
},
transitionSpec: {
open: _specs.DefaultSpec,
close: _specs.DefaultSpec
},
...config
};
};
exports.SharedXImage = SharedXImage;
//# sourceMappingURL=presets.js.map