Scroll to top of ScrollView

Is there a way to scroll to the top of a ScrollView in response to a button press?

I can force a re-render of the whole page but that seems very inefficient.

142941 次浏览

You can run the "scrollTo" method of the ScrollView. Check out the source.

You can get reference the ScrollView component by setting ref property and using this.refs as described here: https://facebook.github.io/react/docs/more-about-refs.html

  1. First add a ref attribute to your ScrollView component. Example: <ScrollView ref='_scrollView'>
  2. Then wherever you want to scroll to top do this Example: onPress={() => { this.refs._scrollView.scrollTo(0); }}

A simple example:

<ListView
ref={ref => this.listView = ref}
onContentSizeChange={() => {
this.listView.scrollTo({y: 0})
}}
/>

With a button to control the scrollveiw or listview to top is possible.

first, you could use onScroll method put event in it to detect the event.nativeEvent.contentOffset.y of the ListView or scrollView,you can set a state to control its show or hide according to they`.

Then, set a button on your page, use onPress method and .scrollTomethod to make it.

Here is an implementation can be used as a reference : https://www.npmjs.com/package/react-native-scrolltotop

Hoping this helps

scrollTo(x, y) is now deprecated. It's now best to pass an object to scrollTo like this:

this.refs.scrollView.scrollTo({x: 0, y: 0, animated: true})

Just to the point . The question was for scrollView and this worked fine for me:

<ScrollView
ref='_scrollView'
onContentSizeChange={() => { this.refs._scrollView.scrollTo({x: 0, y: 0, animated: true}); }}
>
< ScrollView  ref={(ref) => { this.scrollListReftop = ref; }}>< /ScrollView>

Write this to get scroll on top

this.scrollListReftop.scrollTo({x: 0, y: 0, animated: true})

A Hooks solution (in Typescript)

A bit complex but may be useful for anyone using function components instead of classes.

ImperativeScrollView.tsx:

import React, {
useRef,
useImperativeHandle,
forwardRef,
RefForwardingComponent,
PropsWithChildren,
} from "react";
import { ScrollView, ScrollViewProps } from "react-native";


export interface ImperativeScrollViewHandles {
scrollToStart(options?: { animated: boolean }): void;
scrollToEnd(options?: { animated: boolean }): void;
scrollTo(options: { x?: number; y?: number; animated?: boolean }): void;
}


const ImperativeScrollView: RefForwardingComponent<
ImperativeScrollViewHandles,
PropsWithChildren<ScrollViewProps>
> = (props, ref) => {
const scrollViewRef = useRef<ScrollView>(null);
useImperativeHandle(ref, () => ({
scrollToStart: options => {
if (scrollViewRef.current) {
scrollViewRef.current.scrollTo({
x: 0,
y: 0,
animated: options ? options.animated : true,
});
}
},
scrollToEnd: options => {
if (scrollViewRef.current) {
scrollViewRef.current.scrollToEnd(options);
}
},
scrollTo: options => {
if (scrollViewRef.current) {
scrollViewRef.current.scrollTo(options);
}
},
}));
return <ScrollView ref={scrollViewRef} {...props} />;
};


export default forwardRef(ImperativeScrollView);




Usage:

const MyComponent: React.FC<MyComponentProps> = props => {
const scrollViewRef = useRef<ImperativeScrollViewHandles>(null);
return (
<ImperativeScrollView ref={scrollViewRef}>
<Button
title={"Tap me!"}
onPress={() => {
if (scrollViewRef.current) {
scrollViewRef.current.scrollToStart();
}
}}
/>
</ImperativeScrollView>
);
};

Simple example->

 constructor(props) {
super(props);
this.state = {


}


this.listView1 = null
this.listView2 = null
this.listViewY = 0.0
this.scrollInterval = null
}


async componentDidMount() {


this.autoScroll()


}






autoScroll() {
setTimeout(() => {
this.scrollInterval = setInterval(() => {
if (this.listView !== null) {
this.listView.scrollTo({ y: this.listViewY + 1 })
this.listViewY += 1
}
}, 50);
}, 1000);
}


_stopScoller = () => {
if (this.scrollInterval !== null) {
clearInterval(this.scrollInterval)
}

}

render() {
return(
<ScrollView style={styles.fullListScrollView} ref={ref => this.listView = ref} onScrollBeginDrag={this._stopScoller}>
</ScrollView>
)
}

in functional components

import { useRef } from 'react';


const scrollRef = useRef();


const onPressTouch = () => {
scrollRef.current?.scrollTo({
y: 0,
animated: true,
});
}


<ScrollView ref={scrollRef}>
...your elements
</ScrollView>


<TouchableOpacity onPress={onPressTouch}></TouchableOpacity>
<TouchableOpacity onPress={()=>this.scrollListReftop.scrollTo({x: 0, y: 200, animated: true})}>
<Text>scroll up</Text>
</TouchableOpacity>


<ScrollView
ref={(ref) => { this.scrollListReftop = ref; }}
>

If you are using FlatList you can do this:

import {  FlatList } from 'react-native';
import React, { useRef } from 'react';


function Test(){
const flatListRef = useRef(null);


const onPress = () => {
flatListRef?.current?.scrollToIndex({ index: 0 });
}


return (
<>
<TouchableOpacity onPress={onPress}>
<Text>Scroll to Top<Text/>
</TouchableOpacity>
<FlatList
ref={flatListRef}
...
/>
</>
);
}
Unfortunately, Not working for me:-


import React, { useEffect, useRef, useState, } from "react";
import { View, TouchableOpacity, Text, StyleSheet, Modal, Image, useWindowDimensions } from "react-native";




const COLORS = ['#3ad35c', Colors.themeColor, '#efcd19', '#ff1e1a'];


function ProductDetailScreen(props) {
const [state, setState] = React.useState({
loading: true, fetchCart: false, selectedColor: 1, showZoom: false, zoomImages: [], msg: '',
showZoomBanner1: false, showZoomBanner2: false
});
const { loading, productDetail, selectedColor, zoomImages, showZoom, msg, showZoomBanner1, showZoomBanner2 } = state;


useEffect(() => {
if (product) {
// some api call here
}
}, [])


// Fatch data


enter code here
// scrooll to top
const scrollRef = useRef();
const onPressTouch = () => {
scrollRef.current?.scrollTo({
y: 0,
animated: true,
});
}


const vendorProduct = (pid, vid) => {
// some api call here
}
const otherSellerRenderCard = item => {
return (
<TouchableOpacity onPress={() => vendorProduct(product.id, item.id)} style={[styles.productBox, { backgroundColor: Colors.light_gray }]} key={item.id}>
<Text style=\{\{ flex: 1, fontSize: wp('3.5%'), fontFamily: Fonts.Font_Bold }}>
<FontAwesomeIcon name="truck" style={GlobalStyles.FavIcon} color={Colors.themeColor} /> Estimate Delivery Time : {item.delivery_days} Days
</Text>
<View style={styles.priceView}>
{
<View style={styles.SpcialView}>
<Text style={styles.price}>{currency}{parseInt(item.listing_price)} </Text>
{
(item.regular_price) && <Text style={styles.ortherSellerOriginalPrice}>{currency}{parseInt(item.regular_price)}</Text>
}
<Text style=\{\{ color: Colors.green }}>
{
(item.regular_price - item.listing_price) &&
<Text style={styles.offInPercentageText}> ({offInPercentage(item.regular_price, item.listing_price)}% off)</Text>


}
{
(item.stock_quantity < 1) && <Text style={[styles.stock, { color: Colors.red }]}>  Out Of Stock</Text>
}
</Text>
</View>
}
</View>
<View>
<Text style=\{\{ color: Colors.teal }}>
<Image source={dgCoin} />
{
((item.listing_price / 100) * siteSetting.max_coin_use_by_non_prime_member < siteSetting.max_price_upto_for_non_prime_member) ?
Math.round((item.listing_price / 100) * siteSetting.max_coin_use_by_non_prime_member) : Math.round(siteSetting.max_price_upto_for_non_prime_member)
} DG coins can be used</Text>
<Text style=\{\{ color: Colors.teal }}>
<Text style=\{\{ fontFamily: Fonts.Font_Bold }}>Privilege Members</Text> can use upto
<Text style=\{\{ fontFamily: Fonts.Font_Bold }}>
{
((item.listing_price / 100) * siteSetting.max_coin_use_by_prime_member < siteSetting.max_price_upto_for_prime_member) ?
Math.round((item.listing_price / 100) * siteSetting.max_coin_use_by_prime_member) : Math.round(siteSetting.max_price_upto_for_prime_member)
} DG coins
</Text>
</Text>
<Text style=\{\{ color: Colors.secondry_text_color }}>Sold By:
<Text style=\{\{ color: Colors.themeColor }}> {item.organization_name}</Text>
</Text>
</View>
</TouchableOpacity>
);
};




return (
<OtrixContainer customStyles=\{\{ backgroundColor: Colors.light_white }}>
{
(loading) ? <ProductDetailPageSkeleton /> : <>




{/* Content Start from here */}
<OtrixContent customStyles={styles.productDetailView}>
<OtrixDivider size={'md'} />
<ScrollView style={styles.childView} showsVerticalScrollIndicator={false} ref={scrollRef}>
{/* Info Icons */}
{
(product) && <View>
<View style={GlobalStyles.horizontalLine}></View>
<OtrixDivider />
<ProductInfoIcon data={product} />
<OtrixDivider />
<View style={GlobalStyles.horizontalLine}></View>
<OtrixDivider />
</View>
}




{
productVendors.length > 1 &&
<View>
<OtrixDivider />
<View style={GlobalStyles.horizontalLine}></View>
<OtrixDivider />


<View style={styles.catHeading}>
<Text style={GlobalStyles.boxHeading}>Other Sellers For This Product</Text>
</View>
<OtrixDivider />
{
<View style=\{\{ flexDirection: 'column', flexWrap: 'wrap' }} onPress={onPressTouch}>
{productVendors.map((item, index) => {
if (item.id != product.vendor_id) {
return otherSellerRenderCard(item);
}
})}
</View>
}
</View>
}


</ScrollView>
</OtrixContent>
</>
}
{
msg != '' &&
<Text style=\{\{ textAlign: 'center', backgroundColor: 'red', color: 'white', padding: 10 }}>{msg}</Text>


}
</OtrixContainer >
)
}


function mapStateToProps(state) {
return {
cartCount: state.cart.cartCount,
}
}


export default connect(mapStateToProps, { addToCart })(ProductDetailScreen);

✅ For Functional Component in React Native JavaScript here is the simple way to make ScrollView roll to top with Button press.

import React, { useRef } from 'react';
import { View, TouchableOpacity, ScrollView } from 'react-native';


const ExampleComp = () => {
const scrollRef = useRef(); // Create scrollRef constant


return(
<View>
<ScrollView
style=\{\{ flex: 1 }}
ref={scrollRef}
>
--- ADD ANY OF YOUR CONTENT HERE ---
</ScrollView>
<TouchableOpacity
onPress={() => {
scrollRef.current.scrollTo({
x: 0, // Required
y: 0, // Required
animated: true
})
}}
>
<Text style=\{\{color:"black"}}>Scroll To Top</Text>
</TouchableOpacity>
</View>
);
}
export default ExampleComp;

Let's me know if this helpful.