diff --git a/docs/docs/api/nativeMethods/measure.md b/docs/docs/api/nativeMethods/measure.md index 1900c4352a7..94b78f67d0c 100644 --- a/docs/docs/api/nativeMethods/measure.md +++ b/docs/docs/api/nativeMethods/measure.md @@ -4,34 +4,35 @@ title: measure sidebar_label: measure --- -Determines the location on screen, width, and height of the given view. Note that these measurements are not available until after the rendering has been completed in native. If you need the measurements as soon as possible, consider using [`onLayout`](https://reactnative.dev/docs/view#onlayout) instead. - -This function is implemented on native platforms only. On web, it's sufficient to use a standard version of the `measure` which is available on most of the default components provided by React Native (it's [here](https://github.com/facebook/react-native/blob/65975dd28de0a7b8b8c4eef6479bf7eee5fcfb93/Libraries/Renderer/shims/ReactNativeTypes.js#L105)). In such a case it should be invoked in the following way (note it's asynchronous so if you want to make it synchronous you should use `Promise`): - -```javascript -const aref = useAnimatedRef(); -new Promise((resolve, reject) => { - if (aref && aref.current) { - aref.current.measure((x, y, width, height, pageX, pageY) => { - resolve({ x, y, width, height, pageX, pageY }); - }); - } else { - reject(new Error('measure: animated ref not ready')); - } -}); -``` +Determines the location on screen, width, and height in the viewport of the given view synchronously and returns an object with measured dimensions or `null` if the view cannot be measured. + +If you need the measurements as soon as possible and you don't need `pageX` and `pageY`, consider using the [`onLayout`](https://reactnative.dev/docs/view#onlayout) property instead. + +:::info +You can use `measure()` only on rendered components. For example, calling `measure()` on an offscreen `FlatList` item will return `null`. It is therefore a good practice to perform a `null`-check before using the response. +::: + +:::tip +If you call `measure` inside [`useAnimatedStyle`](../hooks/useAnimatedStyle), you may get the following warning: -If you call `measure()` inside [`useAnimatedStyle()`](../hooks/useAnimatedStyle) -you may get a warning that `measure()` was called from the wrong thread. This -is safe to ignore, but if you don't want this error to appear then wrap the call -like this: +> [Reanimated] measure() was called from the main JS context. Measure is only available +in the UI runtime. (...) + +That's because in React Native apps, `useAnimatedStyle` worklet is first evaluated on the JS context during the first render, thus before rendering has been completed in native. This is safe to ignore, but if you don't want this warning to appear then wrap the call like this: ```js -if (_WORKLET) { - const measure = measure(animatedRef); - // ... +if (_WORKLET || isWeb) { + const measured = measure(animatedRef); + if (measured !== null) { + // ... + } } ``` +::: + +:::info +`measure` is not available when Chrome Developer Tools (remote JS debugger) is attached. However, the recommended tool for debugging React Native apps is Flipper (Chrome DevTools) which supports `measure`. Check out more details [here](../../guide/debugging). +::: ### Arguments @@ -52,8 +53,6 @@ An object of type `MeasuredDimensions`, which contains these fields: If the measurement could not be performed, returns `null`. -You can use `measure()` only on rendered components. For example, calling `measure()` on an offscreen `FlatList` item will return `null`. It is therefore a good practice to perform a `null`-check before using the response. - ### Example ```js diff --git a/docs/docs/api/nativeMethods/scrollTo.md b/docs/docs/api/nativeMethods/scrollTo.md index 789f4f5e21e..3862ac4781b 100644 --- a/docs/docs/api/nativeMethods/scrollTo.md +++ b/docs/docs/api/nativeMethods/scrollTo.md @@ -5,35 +5,27 @@ sidebar_label: scrollTo --- Provides synchronous scroll on the UI thread to a given offset using an animated ref to a scroll view. This allows performing smooth scrolling without lags (which might have otherwise occured when it was asynchronous and based on lots of events). - -This function is implemented on native platforms only. On web it's sufficient to use a standard version of `scrollTo` which comes with a `ScrollView` component (it's [here](https://github.com/facebook/react-native/blob/aebccd3f923c920bd85fb9e5fbdd2a8a75d3ad3d/Libraries/Components/ScrollView/ScrollView.js#L834)). In such a case it should be invoked in the following way: - -```javascript -const aref = useAnimatedRef(); -aref.current.scrollTo({ x, y }); -``` - ### Arguments #### `animatedRef` The product of [`useAnimatedRef`](../hooks/useAnimatedRef) which is Reanimated's extension of a standard React ref (delivers the view tag on the UI thread). -#### `x-cord` [Float] +#### `x` [Float] Corresponds to the pixel along the horizontal axis of the element that you want displayed in the upper left. -#### `y-cord` [Float] +#### `y` [Float] Corresponds to the pixel along the vertical axis of the element that you want displayed in the upper left. #### `animated` [Boolean] -Indicates whether the scroll should be smooth. +Indicates whether the scroll should be smooth (`true`) or instant (`false`). ### Returns -void +`void` ### Example diff --git a/docs/docs/guide/debugging.mdx b/docs/docs/guide/debugging.mdx index bff6e8d398e..5349efe085b 100644 --- a/docs/docs/guide/debugging.mdx +++ b/docs/docs/guide/debugging.mdx @@ -113,7 +113,7 @@ functions. This means that the `scrollTo` function will work function will not be available, and its usage will trigger this error: ``` -[Reanimated] measure() cannot be used for web or Chrome Debugger +[Reanimated] measure() cannot be used with Chrome Debugger. ``` You may still use the standard web version of `measure` as described diff --git a/src/reanimated2/NativeMethods.ts b/src/reanimated2/NativeMethods.ts index 4aee8694e6b..84fc2830be7 100644 --- a/src/reanimated2/NativeMethods.ts +++ b/src/reanimated2/NativeMethods.ts @@ -3,7 +3,7 @@ import { Component } from 'react'; import { findNodeHandle } from 'react-native'; import { MeasuredDimensions } from './commonTypes'; import { RefObjectFunction } from './hook/commonTypes'; -import { shouldBeUseWeb } from './PlatformChecker'; +import { isChromeDebugger, isWeb, shouldBeUseWeb } from './PlatformChecker'; export function getTag( view: null | number | React.Component | React.ComponentClass @@ -13,56 +13,71 @@ export function getTag( const isNative = !shouldBeUseWeb(); -export function measure( +export let measure: ( animatedRef: RefObjectFunction -): MeasuredDimensions | null { - 'worklet'; - if (!isNative) { - console.warn( - '[Reanimated] measure() cannot be used on web or Chrome Debugger' - ); - return null; - } +) => MeasuredDimensions | null; - if (!_WORKLET) { - console.warn( - '[Reanimated] measure() was called from the main JS context. Measure is ' + - 'only available in the UI runtime. This may also happen if measure() ' + - 'was called by a worklet in the useAnimatedStyle hook, because useAnimatedStyle ' + - 'calls the given worklet on the JS runtime during render. If you want to ' + - 'prevent this warning then wrap the call with `if (_WORKLET)`. Then it will ' + - 'only be called on the UI runtime after the render has been completed.' - ); +if (isWeb()) { + measure = (animatedRef: RefObjectFunction) => { + const element = animatedRef() as unknown as HTMLElement; // TODO: fix typing of animated refs on web + const viewportOffset = element.getBoundingClientRect(); + return { + width: element.offsetWidth, + height: element.offsetHeight, + x: element.offsetLeft, + y: element.offsetTop, + pageX: viewportOffset.left, + pageY: viewportOffset.top, + }; + }; +} else if (isChromeDebugger()) { + measure = (_animatedRef: RefObjectFunction) => { + console.warn('[Reanimated] measure() cannot be used with Chrome Debugger.'); return null; - } + }; +} else { + measure = (animatedRef: RefObjectFunction) => { + 'worklet'; + if (!_WORKLET) { + console.warn( + '[Reanimated] measure() was called from the main JS context. Measure is ' + + 'only available in the UI runtime. This may also happen if measure() ' + + 'was called by a worklet in the useAnimatedStyle hook, because useAnimatedStyle ' + + 'calls the given worklet on the JS runtime during render. If you want to ' + + 'prevent this warning then wrap the call with `if (_WORKLET)`. Then it will ' + + 'only be called on the UI runtime after the render has been completed.' + ); + return null; + } - const viewTag = animatedRef(); - if (viewTag === -1) { - console.warn( - `[Reanimated] The view with tag ${viewTag} is not a valid argument for measure(). This may be because the view is not currently rendered, which may not be a bug (e.g. an off-screen FlatList item).` - ); - return null; - } + const viewTag = animatedRef(); + if (viewTag === -1) { + console.warn( + `[Reanimated] The view with tag ${viewTag} is not a valid argument for measure(). This may be because the view is not currently rendered, which may not be a bug (e.g. an off-screen FlatList item).` + ); + return null; + } - const measured = _measure(viewTag); - if (measured === null) { - console.warn( - `[Reanimated] The view with tag ${viewTag} has some undefined, not-yet-computed or meaningless value of \`LayoutMetrics\` type. This may be because the view is not currently rendered, which may not be a bug (e.g. an off-screen FlatList item).` - ); - return null; - } else if (measured.x === -1234567) { - console.warn( - `[Reanimated] The view with tag ${viewTag} returned an invalid measurement response` - ); - return null; - } else if (isNaN(measured.x)) { - console.warn( - `[Reanimated] The view with tag ${viewTag} gets view-flattened on Android. To disable view-flattening, set \`collapsable={false}\` on this component.` - ); - return null; - } else { - return measured; - } + const measured = _measure(viewTag); + if (measured === null) { + console.warn( + `[Reanimated] The view with tag ${viewTag} has some undefined, not-yet-computed or meaningless value of \`LayoutMetrics\` type. This may be because the view is not currently rendered, which may not be a bug (e.g. an off-screen FlatList item).` + ); + return null; + } else if (measured.x === -1234567) { + console.warn( + `[Reanimated] The view with tag ${viewTag} returned an invalid measurement response.` + ); + return null; + } else if (isNaN(measured.x)) { + console.warn( + `[Reanimated] The view with tag ${viewTag} gets view-flattened on Android. To disable view-flattening, set \`collapsable={false}\` on this component.` + ); + return null; + } else { + return measured; + } + }; } export function dispatchCommand( @@ -85,7 +100,19 @@ export let scrollTo: ( animated: boolean ) => void; -if (global._IS_FABRIC) { +if (isWeb()) { + scrollTo = ( + animatedRef: RefObjectFunction, + x: number, + y: number, + animated: boolean + ) => { + 'worklet'; + const element = animatedRef() as unknown as HTMLElement; + // @ts-ignore same call as in react-native-web + element.scrollTo({ x, y, animated }); + }; +} else if (isNative && global._IS_FABRIC) { scrollTo = ( animatedRef: RefObjectFunction, x: number, @@ -95,7 +122,7 @@ if (global._IS_FABRIC) { 'worklet'; dispatchCommand(animatedRef, 'scrollTo', [x, y, animated]); }; -} else { +} else if (isNative) { scrollTo = ( animatedRef: RefObjectFunction, x: number, @@ -103,12 +130,20 @@ if (global._IS_FABRIC) { animated: boolean ) => { 'worklet'; - if (!_WORKLET || !isNative) { + if (!_WORKLET) { return; } const viewTag = animatedRef(); _scrollTo(viewTag, x, y, animated); }; +} else { + scrollTo = ( + _animatedRef: RefObjectFunction, + _x: number, + _y: number + ) => { + // no-op + }; } export function setGestureState(handlerTag: number, newState: number): void {