Skip to content
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

Add new spring feature - clamp #5195

Merged
merged 45 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
4b311b9
Improve config verification
Oct 5, 2023
b3663e6
Type fix
Oct 5, 2023
a4343f0
Add new feature
Oct 6, 2023
3c75718
Improve comments
Oct 6, 2023
d78218a
Self-review
Oct 6, 2023
fd47b13
More fixes
Oct 6, 2023
47dfc19
Merge branch 'acynk/spring-zero-duration' into acynk/spring-clamp
Oct 6, 2023
9f128fc
Code review
Oct 10, 2023
fc5c0eb
Code review - happy path optimization
Oct 10, 2023
cb62d24
Merge branch 'acynk/spring-zero-duration' into acynk/spring-clamp
Oct 10, 2023
6f428c8
Fix boolean
Oct 10, 2023
58768bc
More fixes
Oct 10, 2023
7b5f66f
Add exmple
Oct 19, 2023
4773193
Merge
Oct 19, 2023
1f5a651
Self review
Oct 19, 2023
2d533eb
Uniform style
Oct 19, 2023
7c88e62
Merge branch 'main' into acynk/spring-clamp
Oct 19, 2023
1a579c8
Code review
Oct 31, 2023
ce30f48
merge
Oct 31, 2023
5d044d8
Docs
Oct 31, 2023
16caa8e
Add reanimated versions
Oct 31, 2023
9f76d81
Undo documentation changes
Nov 3, 2023
9a3901d
Add fragment <>
Nov 13, 2023
980d594
Update app/src/examples/PendulumExample.tsx
Latropos Nov 13, 2023
03d705f
Merge branch 'main' into acynk/spring-clamp
Nov 16, 2023
bd163d5
Make API consistent
Nov 16, 2023
0d3b0bc
Fixes and improvements
Nov 16, 2023
7b22791
self-review
Nov 16, 2023
a6ec727
Remove console.log
Nov 16, 2023
922854d
Visual improvement
Nov 16, 2023
339361e
Merge branch 'main' into acynk/spring-clamp
Latropos Dec 11, 2023
3fc102e
Code review
Dec 11, 2023
2e0d653
Apply suggestions from code review
Latropos Dec 11, 2023
8d66c88
Format files
Dec 11, 2023
ec72c18
Run prettier
Dec 11, 2023
74d9f2e
Add tests
Dec 11, 2023
7a1160f
Fix NDEBUG macro not present in Release builds (#5366)
tjzel Nov 20, 2023
6eed4ff
Code review
Dec 18, 2023
0385232
Merge branch 'main' into acynk/spring-clamp
Jan 3, 2024
355dae7
Restore FlatList.tsx
piaskowyk Jan 9, 2024
2fca1be
Merge branch 'main' into acynk/spring-clamp
piaskowyk Jan 9, 2024
3f772f7
Merge branch 'main' into acynk/spring-clamp
Jan 10, 2024
41da808
Check for undefined
Jan 15, 2024
ff57979
Check for undefined
Jan 15, 2024
c19b0f5
Merge branch 'main' into acynk/spring-clamp
piaskowyk Jan 16, 2024
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
46 changes: 46 additions & 0 deletions __tests__/spring.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {
SpringAnimation,
bisectRoot,
scaleZetaToMatchClamps,
} from '../src/reanimated2/animation/springUtils';

describe('Spring utils', () => {
test.each([
[[0, 5, (x) => x - 3, 20], 3],
[[0, 5, (x) => x * x - 9, 20], 3],
[[0, 5, (x) => x * x - 2, 20], Math.sqrt(2)],
[[0, 5, (x) => x - 1 / x - 1, 20], (1 + Math.sqrt(5)) / 2],
] as const)('Bisect root', (testCase, result) => {
const [min, max, func, maxIterations] = testCase;
const calculatedOutput = bisectRoot({
min,
max,
func,
maxIterations,
});
expect(calculatedOutput).toBeCloseTo(result);
});

const animation = {
startValue: 0,
toValue: 10,
zeta: 0.5,
} as SpringAnimation;

test.each([
{ min: -1, max: 10 },
{ min: -100, max: 100 },
{ min: -40, max: 50 },
{ min: -0.005, max: 10.005 },
] as const)('Test scaleZetaToMatchClamps', (clamp) => {
const zeta = scaleZetaToMatchClamps(animation, clamp);
const extremum1 = Math.exp(-zeta * Math.PI);
const extremum2 = Math.exp(-zeta * 2 * Math.PI);

expect(extremum1).toBeLessThan(clamp.max);
expect(extremum1).toBeGreaterThan(clamp.min);

expect(extremum2).toBeLessThan(clamp.max);
expect(extremum2).toBeGreaterThan(clamp.min);
});
});
1 change: 1 addition & 0 deletions app/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ module.exports = {
'react-native/no-unused-styles': 'error',
'react-native/no-raw-text': 'off', // This rule is great, we don't enable it because of its performance. If we ever find similar rule we should enable it.
'react-native/no-single-element-style-arrays': 'error',
'react/jsx-fragments': ['error', 'syntax'],
Latropos marked this conversation as resolved.
Show resolved Hide resolved
},
};
24 changes: 10 additions & 14 deletions app/src/examples/PendulumExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,20 +166,16 @@ export default function SpringExample() {
</Animated.View>
</View>
</GestureDetector>
<React.Fragment>
{fields.map((item) => {
return (
<InputField
fieldName={item.fieldName}
value={item.value}
setValue={(value) => {
item.setValue(value);
}}
key={item.fieldName}
/>
);
})}
</React.Fragment>
{fields.map((item) => {
return (
<InputField
fieldName={item.fieldName}
value={item.value}
setValue={item.setValue}
key={item.fieldName}
/>
);
})}
</GestureHandlerRootView>
);
}
Expand Down
110 changes: 81 additions & 29 deletions app/src/examples/WithClampExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import Animated, {
useAnimatedStyle,
withSpring,
withClamp,
withDelay,
} from 'react-native-reanimated';
import { View, Text, Button, StyleSheet, ViewStyle } from 'react-native';
import React, { useState } from 'react';
Expand All @@ -17,7 +16,16 @@ const CLAMP_MARKER_HEIGHT = 40;
const LOWER_BOUND = 120;
const UPPER_BOUND = 220;

function renderExample(testedStyle: ViewStyle, description: string) {
const LOWER_SPRING_TO_VALUE = 150;
const UPPER_SPRING_TO_VALUE = 200;

function Example({
testedStyle,
description,
}: {
testedStyle: ViewStyle;
description: string;
}) {
return (
<>
<Text style={styles.text}>{description}</Text>
Expand All @@ -27,23 +35,47 @@ function renderExample(testedStyle: ViewStyle, description: string) {
borderWidth: BORDER_WIDTH,
borderColor: VIOLET,
}}>
<Animated.View
style={[
styles.clampMarker,
{ marginBottom: -CLAMP_MARKER_HEIGHT, width: UPPER_BOUND },
]}
/>
<View>
Latropos marked this conversation as resolved.
Show resolved Hide resolved
<View
style={[
styles.toValueMarker,
{
width: LOWER_SPRING_TO_VALUE,
},
]}
/>
<View
style={[
styles.clampMarker,
{
width: LOWER_BOUND,
},
]}
/>
</View>
<Animated.View style={[styles.movingBox, testedStyle]} />
<Animated.View
style={[
styles.clampMarker,
{
marginTop: -CLAMP_MARKER_HEIGHT,
width: FRAME_WIDTH - LOWER_BOUND,
alignSelf: 'flex-end',
},
]}
/>
<View>
Latropos marked this conversation as resolved.
Show resolved Hide resolved
<View
style={[
styles.toValueMarker,
{
marginTop: -CLAMP_MARKER_HEIGHT / 2,
width: FRAME_WIDTH - UPPER_SPRING_TO_VALUE,
alignSelf: 'flex-end',
},
]}
/>
<View
style={[
styles.clampMarker,
{
marginTop: -100,
width: FRAME_WIDTH - UPPER_BOUND,
alignSelf: 'flex-end',
},
]}
/>
</View>
</View>
</>
);
Expand All @@ -59,22 +91,24 @@ export default function AnimatedStyleUpdateExample() {
dampingRatio: 0.075,
};

const clampedStyle = useAnimatedStyle(() => {
const clampedStyleWithAnimationModifier = useAnimatedStyle(() => {
return {
width: withClamp(
{ min: LOWER_BOUND, max: UPPER_BOUND },
withSpring(randomWidth.value, config)
),
};
});
Latropos marked this conversation as resolved.
Show resolved Hide resolved
const clampedStyleWithDelay = useAnimatedStyle(() => {

const clampedStyleWithConfig = useAnimatedStyle(() => {
return {
width: withClamp(
{ min: LOWER_BOUND, max: UPPER_BOUND },
withDelay(0, withSpring(randomWidth.value, config))
),
width: withSpring(randomWidth.value, {
...config,
clamp: { min: LOWER_BOUND, max: UPPER_BOUND },
}),
};
});

const style = useAnimatedStyle(() => {
return {
width: withSpring(randomWidth.value, config),
Expand All @@ -96,9 +130,15 @@ export default function AnimatedStyleUpdateExample() {

return (
<View style={styles.container}>
{renderExample(clampedStyle, 'Clamped spring')}
{renderExample(clampedStyleWithDelay, 'Clamped spring with delay')}
{renderExample(style, 'Default spring')}
<Example
testedStyle={clampedStyleWithAnimationModifier}
description="Clamped spring with withClamp HOC"
/>
<Example
testedStyle={clampedStyleWithConfig}
description="Clamped spring with clamp config property"
/>
<Example testedStyle={style} description="Default spring" />
<Animated.View
style={[
{ margin: 50, width: 50, height: 50, backgroundColor: 'teal' },
Expand All @@ -108,7 +148,9 @@ export default function AnimatedStyleUpdateExample() {
<Button
title="toggle"
onPress={() => {
randomWidth.value = toggle ? 150 : 200;
randomWidth.value = toggle
? LOWER_SPRING_TO_VALUE
: UPPER_SPRING_TO_VALUE;
rotation.value = toggle ? '380deg' : '-120deg';
setToggle(!toggle);
}}
Expand All @@ -123,13 +165,23 @@ const styles = StyleSheet.create({
flexDirection: 'column',
padding: CLAMP_MARKER_HEIGHT,
},
toValueMarker: {
position: 'absolute',
margin: 0,
opacity: 1,
zIndex: 100,
height: CLAMP_MARKER_HEIGHT / 2,
backgroundColor: VIOLET,
},
clampMarker: {
position: 'absolute',
margin: 0,
opacity: 0.5,
height: CLAMP_MARKER_HEIGHT,
height: 100,
backgroundColor: VIOLET,
},
movingBox: {
zIndex: 1,
height: 100,
opacity: 0.5,
backgroundColor: 'black',
Expand Down
6 changes: 6 additions & 0 deletions src/reanimated2/animation/spring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
underDampedSpringCalculations,
criticallyDampedSpringCalculations,
isAnimationTerminatingCalculation,
scaleZetaToMatchClamps,
checkIfConfigIsValid,
} from './springUtils';

Expand Down Expand Up @@ -58,6 +59,7 @@ export const withSpring = ((
duration: 2000,
dampingRatio: 0.5,
reduceMotion: undefined,
clamp: undefined,
Latropos marked this conversation as resolved.
Show resolved Hide resolved
} as const;

const config: DefaultSpringConfig & SpringConfigInner = {
Expand Down Expand Up @@ -210,6 +212,10 @@ export const withSpring = ((
animation.zeta = zeta;
animation.omega0 = omega0;
animation.omega1 = omega1;

if (config.clamp !== undefined) {
animation.zeta = scaleZetaToMatchClamps(animation, config.clamp);
piaskowyk marked this conversation as resolved.
Show resolved Hide resolved
}
}

animation.lastTimestamp = previousAnimation?.lastTimestamp || now;
Expand Down
Loading