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

Implement measure and scrollTo for web #3661

Merged
merged 10 commits into from
Oct 26, 2022
Merged

Implement measure and scrollTo for web #3661

merged 10 commits into from
Oct 26, 2022

Conversation

tomekzaw
Copy link
Member

@tomekzaw tomekzaw commented Oct 10, 2022

Description

This PR adds implementation of measure and scrollTo functions on web.

The following example verifies the implementation using a component with non-zero margin, padding, and borderWidth. The results are identical to those from measure from React Native.

measure doesn't work when Chrome Debugger is attached because in such case worklets are executed on the main JS context (like on web, but it's still React Native, so we can't just check HTMLElement size).

MeasureExample.tsx
import Animated, {
  measure,
  runOnUI,
  useAnimatedRef,
  useAnimatedStyle,
  useSharedValue,
  withTiming,
} from 'react-native-reanimated';
import { Button, ScrollView, View } from 'react-native';

import React from 'react';

export default function MeasureExample() {
  const aref = useAnimatedRef<Animated.View>();

  const ref = React.useRef(0);

  const sv = useSharedValue(0);

  const handleAnimateWidth = () => {
    ref.current = 1 - ref.current;
    sv.value = withTiming(ref.current, { duration: 1500 });
  };

  const handleMeasureFromJS = () => {
    aref.current?.measure((x, y, width, height, pageX, pageY) =>
      console.log({ width, height, x, y, pageX, pageY })
    );
  };

  const handleMeasureFromUI = () => {
    runOnUI(() => {
      'worklet';
      console.log(measure(aref));
    })();
  };

  const animatedStyle = useAnimatedStyle(() => {
    return {
      width: 80 + 100 * sv.value,
      height: 80,
      margin: 10,
      padding: 10,
      borderWidth: 10,
      borderColor: 'blue',
      backgroundColor: 'navy',
    };
  }, []);

  return (
    <View style={{ flexDirection: 'column', marginHorizontal: 'auto' }}>
      <View>
        <Button onPress={handleAnimateWidth} title="Animate width" />
        <Button onPress={handleMeasureFromJS} title="Measure from JS" />
        <Button onPress={handleMeasureFromUI} title="Measure from UI" />
      </View>
      <View>
        <ScrollView style={{ height: 400 }}>
          <View style={{ paddingVertical: 350, backgroundColor: 'lightgray' }}>
            <ScrollView style={{ width: 400 }} horizontal>
              <View
                style={{
                  paddingHorizontal: 350,
                  backgroundColor: 'lightgray',
                }}>
                <Animated.View ref={aref} style={animatedStyle} />
              </View>
            </ScrollView>
          </View>
        </ScrollView>
      </View>
    </View>
  );
}
ScrollToExample.tsx
import Animated, {
  runOnUI,
  scrollTo,
  useAnimatedRef,
} from 'react-native-reanimated';
import { Button, Switch, Text, View } from 'react-native';

import React from 'react';

export default function ScrollToExample() {
  const [animated, setAnimated] = React.useState(true);

  const aref = useAnimatedRef<Animated.ScrollView>();

  const scrollFromJS = () => {
    aref.current?.scrollTo({ y: Math.random() * 2000, animated });
  };

  const scrollFromUI = () => {
    runOnUI(() => {
      'worklet';
      scrollTo(aref, 0, Math.random() * 2000, animated);
    })();
  };

  return (
    <View>
      <Switch value={animated} onValueChange={setAnimated} />
      <Button onPress={scrollFromJS} title="Scroll from JS" />
      <Button onPress={scrollFromUI} title="Scroll from UI" />
      <Animated.ScrollView ref={aref} style={{ width: 200, height: 400 }}>
        {[...Array(100)].map((_, i) => (
          <Text key={i} style={{ fontSize: 50, textAlign: 'center' }}>
            {i}
          </Text>
        ))}
      </Animated.ScrollView>
    </View>
  );
}
measure.mov
scrollTo.mov

Tested with:

  • Example app:
  • Example app with Chrome Debugger (I had to disable Hermes in Podfile and use JSC instead because otherwise React DevTools didn't work)
  • WebExample (Firefox, Chrome, Safari)

@tomekzaw tomekzaw changed the title Implement measure for web Implement measure and scrollTo for web Oct 10, 2022
@tomekzaw tomekzaw marked this pull request as ready for review October 10, 2022 18:45
@tomekzaw tomekzaw requested a review from piaskowyk October 10, 2022 18:45
@tomekzaw tomekzaw marked this pull request as draft October 14, 2022 11:38
@tomekzaw tomekzaw marked this pull request as ready for review October 26, 2022 11:43
@piaskowyk piaskowyk merged commit 77d02b3 into main Oct 26, 2022
@piaskowyk piaskowyk deleted the @tomekzaw/measure-web branch October 26, 2022 12:51
piaskowyk pushed a commit that referenced this pull request Oct 26, 2022
This PR adds implementation of `measure` and `scrollTo` functions on
web.

The following example verifies the implementation using a component with
non-zero `margin`, `padding`, and `borderWidth`. The results are
identical to those from `measure` from React Native.

`measure` doesn't work when Chrome Debugger is attached because in such
case worklets are executed on the main JS context (like on web, but it's
still React Native, so we can't just check HTMLElement size).

<details>
<summary>MeasureExample.tsx</summary>

```tsx
import Animated, {
  measure,
  runOnUI,
  useAnimatedRef,
  useAnimatedStyle,
  useSharedValue,
  withTiming,
} from 'react-native-reanimated';
import { Button, ScrollView, View } from 'react-native';

import React from 'react';

export default function MeasureExample() {
  const aref = useAnimatedRef<Animated.View>();

  const ref = React.useRef(0);

  const sv = useSharedValue(0);

  const handleAnimateWidth = () => {
    ref.current = 1 - ref.current;
    sv.value = withTiming(ref.current, { duration: 1500 });
  };

  const handleMeasureFromJS = () => {
    aref.current?.measure((x, y, width, height, pageX, pageY) =>
      console.log({ width, height, x, y, pageX, pageY })
    );
  };

  const handleMeasureFromUI = () => {
    runOnUI(() => {
      'worklet';
      console.log(measure(aref));
    })();
  };

  const animatedStyle = useAnimatedStyle(() => {
    return {
      width: 80 + 100 * sv.value,
      height: 80,
      margin: 10,
      padding: 10,
      borderWidth: 10,
      borderColor: 'blue',
      backgroundColor: 'navy',
    };
  }, []);

  return (
    <View style={{ flexDirection: 'column', marginHorizontal: 'auto' }}>
      <View>
        <Button onPress={handleAnimateWidth} title="Animate width" />
        <Button onPress={handleMeasureFromJS} title="Measure from JS" />
        <Button onPress={handleMeasureFromUI} title="Measure from UI" />
      </View>
      <View>
        <ScrollView style={{ height: 400 }}>
          <View style={{ paddingVertical: 350, backgroundColor: 'lightgray' }}>
            <ScrollView style={{ width: 400 }} horizontal>
              <View
                style={{
                  paddingHorizontal: 350,
                  backgroundColor: 'lightgray',
                }}>
                <Animated.View ref={aref} style={animatedStyle} />
              </View>
            </ScrollView>
          </View>
        </ScrollView>
      </View>
    </View>
  );
}
```

</details>

<details>
<summary>ScrollToExample.tsx</summary>

```tsx
import Animated, {
  runOnUI,
  scrollTo,
  useAnimatedRef,
} from 'react-native-reanimated';
import { Button, Switch, Text, View } from 'react-native';

import React from 'react';

export default function ScrollToExample() {
  const [animated, setAnimated] = React.useState(true);

  const aref = useAnimatedRef<Animated.ScrollView>();

  const scrollFromJS = () => {
    aref.current?.scrollTo({ y: Math.random() * 2000, animated });
  };

  const scrollFromUI = () => {
    runOnUI(() => {
      'worklet';
      scrollTo(aref, 0, Math.random() * 2000, animated);
    })();
  };

  return (
    <View>
      <Switch value={animated} onValueChange={setAnimated} />
      <Button onPress={scrollFromJS} title="Scroll from JS" />
      <Button onPress={scrollFromUI} title="Scroll from UI" />
      <Animated.ScrollView ref={aref} style={{ width: 200, height: 400 }}>
        {[...Array(100)].map((_, i) => (
          <Text key={i} style={{ fontSize: 50, textAlign: 'center' }}>
            {i}
          </Text>
        ))}
      </Animated.ScrollView>
    </View>
  );
}
```

</details>

https://user-images.githubusercontent.com/20516055/194900280-45ad84bc-77ac-4ee3-af15-931d56d1516b.mov

https://user-images.githubusercontent.com/20516055/194924529-96644d3c-0388-489d-bb65-3e80564acd41.mov

Tested with:
- [x] Example app:
- [x] Example app with Chrome Debugger (I had to disable Hermes in
Podfile and use JSC instead because otherwise React DevTools didn't
work)
- [x] WebExample (Firefox, Chrome, Safari)
fluiddot pushed a commit to wordpress-mobile/react-native-reanimated that referenced this pull request Jun 5, 2023
## Description

This PR adds implementation of `measure` and `scrollTo` functions on
web.

The following example verifies the implementation using a component with
non-zero `margin`, `padding`, and `borderWidth`. The results are
identical to those from `measure` from React Native.

`measure` doesn't work when Chrome Debugger is attached because in such
case worklets are executed on the main JS context (like on web, but it's
still React Native, so we can't just check HTMLElement size).

<details>
<summary>MeasureExample.tsx</summary>

```tsx
import Animated, {
  measure,
  runOnUI,
  useAnimatedRef,
  useAnimatedStyle,
  useSharedValue,
  withTiming,
} from 'react-native-reanimated';
import { Button, ScrollView, View } from 'react-native';

import React from 'react';

export default function MeasureExample() {
  const aref = useAnimatedRef<Animated.View>();

  const ref = React.useRef(0);

  const sv = useSharedValue(0);

  const handleAnimateWidth = () => {
    ref.current = 1 - ref.current;
    sv.value = withTiming(ref.current, { duration: 1500 });
  };

  const handleMeasureFromJS = () => {
    aref.current?.measure((x, y, width, height, pageX, pageY) =>
      console.log({ width, height, x, y, pageX, pageY })
    );
  };

  const handleMeasureFromUI = () => {
    runOnUI(() => {
      'worklet';
      console.log(measure(aref));
    })();
  };

  const animatedStyle = useAnimatedStyle(() => {
    return {
      width: 80 + 100 * sv.value,
      height: 80,
      margin: 10,
      padding: 10,
      borderWidth: 10,
      borderColor: 'blue',
      backgroundColor: 'navy',
    };
  }, []);

  return (
    <View style={{ flexDirection: 'column', marginHorizontal: 'auto' }}>
      <View>
        <Button onPress={handleAnimateWidth} title="Animate width" />
        <Button onPress={handleMeasureFromJS} title="Measure from JS" />
        <Button onPress={handleMeasureFromUI} title="Measure from UI" />
      </View>
      <View>
        <ScrollView style={{ height: 400 }}>
          <View style={{ paddingVertical: 350, backgroundColor: 'lightgray' }}>
            <ScrollView style={{ width: 400 }} horizontal>
              <View
                style={{
                  paddingHorizontal: 350,
                  backgroundColor: 'lightgray',
                }}>
                <Animated.View ref={aref} style={animatedStyle} />
              </View>
            </ScrollView>
          </View>
        </ScrollView>
      </View>
    </View>
  );
}
```

</details>

<details>
<summary>ScrollToExample.tsx</summary>

```tsx
import Animated, {
  runOnUI,
  scrollTo,
  useAnimatedRef,
} from 'react-native-reanimated';
import { Button, Switch, Text, View } from 'react-native';

import React from 'react';

export default function ScrollToExample() {
  const [animated, setAnimated] = React.useState(true);

  const aref = useAnimatedRef<Animated.ScrollView>();

  const scrollFromJS = () => {
    aref.current?.scrollTo({ y: Math.random() * 2000, animated });
  };

  const scrollFromUI = () => {
    runOnUI(() => {
      'worklet';
      scrollTo(aref, 0, Math.random() * 2000, animated);
    })();
  };

  return (
    <View>
      <Switch value={animated} onValueChange={setAnimated} />
      <Button onPress={scrollFromJS} title="Scroll from JS" />
      <Button onPress={scrollFromUI} title="Scroll from UI" />
      <Animated.ScrollView ref={aref} style={{ width: 200, height: 400 }}>
        {[...Array(100)].map((_, i) => (
          <Text key={i} style={{ fontSize: 50, textAlign: 'center' }}>
            {i}
          </Text>
        ))}
      </Animated.ScrollView>
    </View>
  );
}
```

</details>


https://user-images.githubusercontent.com/20516055/194900280-45ad84bc-77ac-4ee3-af15-931d56d1516b.mov


https://user-images.githubusercontent.com/20516055/194924529-96644d3c-0388-489d-bb65-3e80564acd41.mov

Tested with:
- [x] Example app:
- [x] Example app with Chrome Debugger (I had to disable Hermes in
Podfile and use JSC instead because otherwise React DevTools didn't
work)
- [x] WebExample (Firefox, Chrome, Safari)
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.

2 participants