@devx-commerce/react-native-viewport-observer
v1.0.4
Published
feature for having more videos forhandling smoothly in react native without extra load.
Readme
@devx-commerce/react-native-viewport-observer
A React Native library that tracks whether a view is visible on screen. Built for smooth video feed experiences — plays a video when it scrolls into view and pauses it when it scrolls out, without extra load or jank.
Works on both iOS and Android using native viewport observers.
Installation
npm install @devx-commerce/react-native-viewport-observeror
yarn add @devx-commerce/react-native-viewport-observerUsage
Wrap any component with VisibilityView. It fires onFocus when the view becomes visible and onBlur when it leaves the screen.
import { VisibilityView } from '@devx-commerce/react-native-viewport-observer';
<VisibilityView
style={{ flex: 1 }}
threshold={0.8}
onFocus={() => console.log('visible')}
onBlur={() => console.log('hidden')}
>
{/* your content */}
</VisibilityView>Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| threshold | number | 0.5 | Fraction of the view that must be visible to trigger onFocus. Range: 0.0 – 1.0 |
| trackDuringScroll | boolean | false | Emit onFocus/onBlur during an active scroll instead of only after it settles. See During-scroll tracking. |
| onFocus | () => void | — | Called when the view enters the viewport |
| onBlur | () => void | — | Called when the view exits the viewport |
| style | ViewStyle | — | Standard React Native view styles |
| ...ViewProps | — | — | All other standard View props are supported |
Example — Auto-play Video Feed
This is the primary use case: a scrollable feed where videos auto-play when visible and pause when scrolled away.
import { useState, useRef } from 'react';
import { View, StyleSheet } from 'react-native';
import Video from 'react-native-video';
import { VisibilityView } from '@devx-commerce/react-native-viewport-observer';
import { FlashList } from '@shopify/flash-list';
const videos = [
{ id: '1', url: 'https://example.com/video1.mp4' },
{ id: '2', url: 'https://example.com/video2.mp4' },
{ id: '3', url: 'https://example.com/video3.mp4' },
];
const VideoRow = ({ url }: { url: string }) => {
const [paused, setPaused] = useState(true);
const videoRef = useRef<any>(null);
const isReady = useRef(false);
const isFocused = useRef(false);
return (
<View style={styles.videoContainer}>
<VisibilityView
style={{ flex: 1 }}
threshold={0.8}
onFocus={() => {
isFocused.current = true;
// Only play once the video is ready to display
if (isReady.current) setPaused(false);
}}
onBlur={() => {
isFocused.current = false;
setPaused(true);
}}
>
<Video
ref={videoRef}
source={{ uri: url }}
paused={paused}
repeat
resizeMode="cover"
style={StyleSheet.absoluteFill}
onReadyForDisplay={() => {
isReady.current = true;
// Start playing if already in view
if (isFocused.current) setPaused(false);
}}
/>
</VisibilityView>
</View>
);
};
export default function App() {
return (
<View style={{ flex: 1 }}>
<FlashList
data={videos}
renderItem={({ item }) => <VideoRow url={item.url} />}
keyExtractor={(item) => item.id}
estimatedItemSize={500}
/>
</View>
);
}
const styles = StyleSheet.create({
videoContainer: {
height: 500,
backgroundColor: 'black',
marginVertical: 6,
},
});How it works
VisibilityView uses native view tracking on iOS and Android to detect how much of the view is within the visible screen area. When the visible fraction crosses the threshold, it fires onFocus. When it drops below, it fires onBlur.
This is more reliable and performant than JS-based scroll position calculations because:
- No scroll event listeners needed
- Works with any scroll container (
FlatList,FlashList,ScrollView, etc.) - Native-level precision — no frame drops
During-scroll tracking
By default (trackDuringScroll={false}) the observer reports visibility only once a scroll settles — it stays quiet while the list is moving. This keeps scrolling cheap when you have many observed views, and is the right default for most cases.
Set trackDuringScroll={true} to also receive onFocus/onBlur while the list is scrolling. Use it when a view must react the instant it enters or leaves the viewport mid-scroll — e.g. keeping the on-screen video in a feed playing as the user scrolls, while off-screen ones pause.
<VisibilityView
style={{ flex: 1 }}
threshold={0.6}
trackDuringScroll
onFocus={() => setPaused(false)}
onBlur={() => setPaused(true)}
>
{/* video */}
</VisibilityView>Notes:
- Opt-in per view. Views that don't set it keep the cheaper settle-only behaviour.
- Cost. During-scroll tracking runs the visibility check each frame while scrolling — enable it only where the live feedback is worth it, and cap how many such views can be active at once if you have a busy screen.
- Platform detail. Android re-checks on each throttled scroll frame; iOS emits on each display-link frame while the scroll offset is changing. Both fall back to settle-only when the prop is off.
Tips
- Use
threshold={0.8}for videos so they only play when mostly visible - Use
threshold={0.1}for lazy-loading content that should trigger early - Combine with
onReadyForDisplayfromreact-native-videoto avoid playing before the first frame is decoded (prevents black flash) - Save video playback position in a
Mapkeyed by URL to resume from the same spot when the video re-enters view
Contributing
License
MIT
Made with create-react-native-library
