ScrollView

Requires:
@ray-js/ray≥0.5.10

Description

Scrollable view container that supports horizontal or vertical scrolling, with configurable pull-to-refresh, scroll event listeners, and other features.

Props

PropertyTypeRequiredDefaultDescriptionSince
scrollXbooleanNofalseEnable horizontal scrolling-
scrollYbooleanNofalseEnable vertical scrolling-
upperThresholdnumber | stringNo50Distance from the top/left at which the scrolltoupper event fires-
lowerThresholdnumber | stringNo50Distance from the bottom/right at which the scrolltolower event fires-
scrollTopnumber | stringNo0Set vertical scroll position-
scrollLeftnumber | stringNo0Set horizontal scroll position-
scrollIntoViewstringNo-The value should be the id of a child element (id must not start with a digit). The element will be scrolled into view along the enabled scrolling direction-
scrollIntoViewOffsetnumberNo0Additional offset, in px, when scrolling to the scrollIntoView target nodev1.7.58
scrollWithAnimationbooleanNofalseUse animated transition when setting the scroll position-
No-Fires on scroll-
No-Fires when scrolled to the top/left-
No-Fires when scrolled to the bottom/right-
refresherEnabledbooleanNofalseEnable custom pull-to-refreshv0.9.3
refresherThresholdnumberNo45Set the custom pull-to-refresh thresholdv0.9.3
refresherDefaultStyle"black" | "white" | "none" | "auto"NoblackSet the default style for custom pull-to-refreshv0.9.3
refresherBackgroundstringNo#FFFSet the background color of the custom pull-to-refresh areav0.9.3
refresherTriggeredbooleanNofalseSet the current pull-to-refresh state: true means pull-to-refresh has been triggered; false means it has notv0.9.3
hideScrollbarbooleanNotrueHide the scrollbar-
bouncesbooleanNotrueWhether to enable the iOS scroll bounce effect (fully supported on iOS 16.0+)v1.7.58
No-Fires when the custom pull-to-refresh control is pulledv0.9.3
No-Fires when custom pull-to-refresh is triggeredv0.9.3
No-Fires when custom pull-to-refresh is resetv0.9.3
No-Triggered when custom pull-to-refresh is abortedv0.9.3

Referenced Types

interfaceRefresherPullingEvent
export interface RefresherPullingEvent extends BaseEvent {
  /** 事件类型 */
  type: 'refresherpulling';
}
interfaceRefresherRefreshEvent
export interface RefresherRefreshEvent extends BaseEvent {
  /** 事件类型 */
  type: 'refresherrefresh';
}
interfaceRefresherRestoreEvent
export interface RefresherRestoreEvent extends BaseEvent {
  /** 事件类型 */
  type: 'refresherrestore';
}
interfaceRefresherAbortEvent
export interface RefresherAbortEvent extends BaseEvent {
  /** 事件类型 */
  type: 'refresherabort';
}
interfaceBaseEvent
PropertyTypeRequiredDescription
typestringYesEvent type
timeStampnumberYesTime in milliseconds since the page opened
YesSource component of the event
YesCollection of property values for the current component
markanyNoEvent mark data
interfaceTarget
PropertyTypeRequiredDescription
idstringYesid of the event source component
YesCollection of custom attributes from the `dataset` on the event source component

Valid Values

ScrollDirectionDetail.direction
ValueDescriptionSince
topScroll to the top-
leftScroll to the left-
bottomScroll to the bottom-
rightScroll to the right-
refresherDefaultStyle
ValueDescriptionSince
blackBlack refresh indicator-
whiteWhite refresh indicator-
noneDisable the default refresh indicator and implement a custom refresh style-
autoMatch the refresh indicator color to the app theme-

Examples

Basic usage

import React from 'react';
import { ScrollView, View, Text } from '@ray-js/ray';

export default function BasicScrollView() {
  const handleScrollToUpper = () => {
    console.log('Scrolled to top');
  };

  const handleScrollToLower = () => {
    console.log('Scrolled to bottom');
  };

  return (
    <ScrollView
      scrollY
      style={{ height: '400rpx', backgroundColor: '#f5f5f5' }}
      onScrollToUpper={handleScrollToUpper}
      onScrollToLower={handleScrollToLower}
    >
      {Array.from({ length: 10 }, (_, i) => (
        <View
          key={i}
          style={{
            height: '100rpx',
            margin: '10rpx',
            backgroundColor: '#fff',
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
          }}
        >
          <Text>List item {i + 1}</Text>
        </View>
      ))}
    </ScrollView>
  );
}

Pull to refresh

import React, { useState, useCallback } from 'react';
import { ScrollView, View, Text } from '@ray-js/ray';

export default function PullToRefreshDemo() {
  const [list, setList] = useState(() =>
    Array.from({ length: 10 }, (_, i) => `Initial data ${i + 1}`)
  );
  const [refreshing, setRefreshing] = useState(false);
  const [loading, setLoading] = useState(false);

  const loadData = useCallback((startIndex: number) => {
    return new Promise<string[]>((resolve) => {
      setTimeout(() => {
        const newList = Array.from(
          { length: 10 },
          (_, i) => `Data ${startIndex + i + 1}`
        );
        resolve(newList);
      }, 1000);
    });
  }, []);

  const handleRefresh = useCallback(async () => {
    setRefreshing(true);
    try {
      const newList = await loadData(-1);
      setList(prevList => [...newList, ...prevList]);
    } finally {
      setRefreshing(false);
    }
  }, [loadData]);

  const handleScrollToLower = useCallback(async () => {
    if (loading) return;
    setLoading(true);
    try {
      const newList = await loadData(list.length);
      setList(prevList => [...prevList, ...newList]);
    } finally {
      setLoading(false);
    }
  }, [loading, list.length, loadData]);

  return (
    <ScrollView
      scrollY
      style={{ height: '600rpx', backgroundColor: '#f5f5f5' }}
      refresherEnabled
      refresherThreshold={50}
      refresherTriggered={refreshing}
      refresherBackground="#f5f5f5"
      onRefresherrefresh={handleRefresh}
      onScrollToLower={handleScrollToLower}
      lowerThreshold={100}
    >
      {list.map((item, index) => (
        <View
          key={index}
          style={{
            height: '100rpx',
            margin: '10rpx 20rpx',
            backgroundColor: '#fff',
            borderRadius: '8rpx',
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
          }}
        >
          <Text>{item}</Text>
        </View>
      ))}
      {loading && (
        <View
          style={{
            height: '80rpx',
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
          }}
        >
          <Text style={{ color: '#999', fontSize: '28rpx' }}>Loading...</Text>
        </View>
      )}
    </ScrollView>
  );
}

Horizontal scrolling

import React from 'react';
import { ScrollView, View, Text } from '@ray-js/ray';

export default function HorizontalScrollDemo() {
  const cards = ['Card 1', 'Card 2', 'Card 3', 'Card 4', 'Card 5'];

  return (
    <ScrollView
      scrollX
      style={{
        width: '100%',
        height: '200rpx',
        whiteSpace: 'nowrap',
      }}
      onScroll={(e) => console.log('Horizontal scroll position:', e.detail.scrollLeft)}
    >
      {cards.map((card, index) => (
        <View
          key={index}
          style={{
            width: '300rpx',
            height: '180rpx',
            marginRight: '20rpx',
            backgroundColor: '#1890ff',
            borderRadius: '12rpx',
            display: 'inline-flex',
            alignItems: 'center',
            justifyContent: 'center',
          }}
        >
          <Text style={{ color: '#fff', fontSize: '32rpx' }}>{card}</Text>
        </View>
      ))}
    </ScrollView>
  );
}

Scroll to a specific position

import React, { useState } from 'react';
import { ScrollView, View, Text, Button } from '@ray-js/ray';

export default function ScrollIntoViewDemo() {
  const [targetId, setTargetId] = useState('');
  const sections = ['section-a', 'section-b', 'section-c', 'section-d'];

  return (
    <View>
      <View style={{ display: 'flex', marginBottom: '20rpx' }}>
        {sections.map((id) => (
          <Button
            key={id}
            size="mini"
            style={{ marginRight: '10rpx' }}
            onClick={() => setTargetId(id)}
          >
            Scroll to {id.split('-')[1].toUpperCase()}
          </Button>
        ))}
      </View>
      <ScrollView
        scrollY
        scrollWithAnimation
        scrollIntoView={targetId}
        scrollIntoViewOffset={10}
        style={{ height: '400rpx', backgroundColor: '#f5f5f5' }}
      >
        {sections.map((id, index) => (
          <View
            key={id}
            id={id}
            style={{
              height: '300rpx',
              margin: '10rpx',
              backgroundColor: ['#e6f7ff', '#fff7e6', '#f6ffed', '#fff1f0'][index],
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'center',
            }}
          >
            <Text style={{ fontSize: '36rpx' }}>Section {id.split('-')[1].toUpperCase()}</Text>
          </View>
        ))}
      </ScrollView>
    </View>
  );
}

FAQ

Why can't scroll-view scroll inside a popup extension component?

Add the disableScroll attribute to the popup component and set it to false to enable scrolling.

How do I listen for scroll-view scrolling to the bottom?

You can handle this directly in the onScroll method, or use onScrollToLower to listen for the scroll height of scrollView to determine whether it has scrolled to the bottom. scrollHeight is the total height of all View elements inside scrollView, and scrollTop is the scroll offset value.