ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

react-native-web ScrollView 组件完全指南:Props、ScrollEvent 与实例方法实战详解

react-native-web ScrollView 组件完全指南:Props、ScrollEvent 与实例方法实战详解 react-native-web ScrollView 组件完全指南Props、ScrollEvent 与实例方法实战详解【免费下载链接】react-native-webCross-platform React UI packages项目地址: https://gitcode.com/gh_mirrors/re/react-native-web导读ScrollView 是 react-native-web 中承载可滚动内容的核心容器组件它通过接入指针锁定pointer-locking响应器系统Responder System来处理触摸滚动、拖动与嵌套手势优先级。本文以官方文档 scroll-view.md 为主体骨架结合仓库内 ScrollView 源码实现、Web 端滚动基座 ScrollViewBase 及配套测试与示例系统讲解 ScrollView 的全部 Props、ScrollEvent 事件载荷、实例方法并给出可在项目中直接运行的真实示例。读完本文你将掌握如何在 Web 场景下正确布局、配置和编程式控制 ScrollView。ScrollView 是什么官方文档对它的定位是一句话A scrollable view that provides integration with the pointer-locking responder system.即一个提供可滚动能力、并与指针锁定响应器系统深度集成的视图。这意味着在 react-native-web 中ScrollView 并非简单的overflow: auto容器它还承担了手势响应者的竞争协调——例如触摸刚开始时是否把响应权交给内部子视图、滚动进行中是否拒绝交出响应权、滚动结束时是否收回焦点等这些逻辑都由 ScrollView 类中的 ScrollResponder 实现 统一处理。基本用法十分简单import { ScrollView } from react-native; ScrollView {...props}{children}/ScrollView;第一个前提ScrollView 必须有界高度文档开篇就给出了一条硬性约束ScrollView must have a bounded height: either set the height of the view directly (discouraged) or make sure all parent views have bounded height (e.g., apply{ flex: 1 }down the view stack).即 ScrollView必须有界bounded才能滚动两种做法直接给 ScrollView 设置固定高度官方不推荐不够灵活让整条视图栈上的父级都有界例如逐层应用{ flex: 1 }最终让 ScrollView 填满父容器。从源码看ScrollView 的滚动能力正是通过overflow实现的垂直模式下设置overflowX: hidden、overflowY: auto水平模式下正好相反见 styles 定义。而 CSS 中overflow: auto只有在容器尺寸受限即有界时才会产生滚动条——这正是必须有界高度的底层原因。// 推荐做法父容器 flex: 1ScrollView 自动获得有界高度 View style{{ flex: 1 }} ScrollView{items}/ScrollView /ViewProps 完全解析以下所有 Props 均在 ScrollViewProps 类型定义 中有对应实现逐一说明如下。...ViewProps全部继承属性类型说明...ViewProps?ViewPropsScrollView 支持 View 的全部 Props包括布局样式、onLayout、onTouchStart等事件与无障碍属性。这意味着 View 的能力全部向下兼容ScrollView 是View 的增强版。centerContent属性类型默认值centerContent?booleanfalse当为true时若内容小于 ScrollView 可视区内容会自动垂直/水平居中当内容超过可视区时该属性不生效因为一旦可以滚动居中就没有意义。源码中对应样式为contentContainerCenterContentjustifyContent: center配合flexGrow: 1见 index.js#L755-L758并作为contentContainerStyle前缀合并进内容容器样式ScrollView centerContent Text内容较小时自动居中/Text /ScrollView测试 index-test.js 分别验证了有无该属性的渲染差异。contentContainerStyle属性类型contentContainerStyle?Style这些样式会应用到**包裹所有子视图的内容容器content container**上而不是 ScrollView 本身。源码中内容容器是一个独立的View collapsable{false}见 index.js#L608-L620通过ref{this._setInnerViewRef}暴露给外部样式按[horizontal 样式, centerContent 样式, contentContainerStyle]合并。注意一个常见陷阱子视图的布局属性alignItems、justifyContent必须通过contentContainerStyle传递而不是style。源码在非生产环境下会对style做校验并直接抛错见 index.js#L565-L575ScrollView contentContainerStyle{{ alignItems: center, paddingVertical: 20 }} {children} /ScrollViewdisableScrollViewPanResponder属性类型默认值disableScrollViewPanResponder?booleanfalse为true时ScrollView 默认的PanResponder被禁用指针事件的完全控制权交给子组件。官方文档特别说明这用于需要原生吸附式snap-to滚动行为的场景。即当你希望子组件自己处理手势、而 ScrollView 只做原生滚动吸附时开启。horizontal属性类型默认值horizontaltrue时子视图按水平一行排列而非垂直一列。源码中水平模式切换了两组样式外层baseHorizontalflexDirection: row、overflowX: auto、overflowY: hidden与内容容器contentContainerHorizontalflexDirection: row见 index.js#L746-L754。ScrollView horizontal {items.map((item) ( Card key{item.id} data{item} / ))} /ScrollViewkeyboardDismissMode属性类型keyboardDismissMode?(none \| on-drag)决定键盘是否随滚动拖动而收起none表示滚动时键盘保持on-drag表示拖动滚动时收起键盘。注意类型定义中还包含interactive见 ScrollViewProps但文档明确支持的是none与on-drag。源码在 _handleScroll 中实现当值为on-drag时调用dismissKeyboard()来自 modules/dismissKeyboard。ScrollView keyboardDismissModeon-drag{/* 含 TextInput 的长列表 */}/ScrollViewonContentSizeChange属性类型onContentSizeChange?(width: number, height: number) void当 ScrollView 的可滚动内容尺寸发生变化时调用回调参数为内容的width和height。源码实现是当传入该回调时内容容器挂上onLayout见 index.js#L577-L582布局变化时取出nativeEvent.layout的宽高上报见 _handleContentOnLayout。典型用途已知内容总高度后手动计算滚动进度条或分页。ScrollView onContentSizeChange{(width, height) { setContentHeight(height); }} {children} /ScrollViewonScroll属性类型onScroll?(e: ScrollEvent) void滚动过程中被调用事件对象e的nativeEvent是一个描述 ScrollView 布局信息的自定义对象详见下文ScrollEvent章节。事件的触发频率由scrollEventThrottle控制。特别注意如果在非生产环境同时指定了onScroll而没有指定scrollEventThrottle控制台会打印提醒——默认只会收到一次事件见 index.js#L682-L692You specifiedonScrollon a but notscrollEventThrottle. You will only receive one event. Using16you get all the events but be aware that it may cause frame drops...pagingEnabled属性类型默认值pagingEnabled?booleanfalse为true时滚动会按列表中的单个条目吸附snap即整页翻动效果。源码中通过 CSS Scroll Snap 实现外层加scrollSnapType: x mandatory/y mandatory取决于 horizontal每个子视图外包一层带scrollSnapAlign: start的 View见 styles.pagingEnabledChild 与 render 逻辑 index.js#L584-L606。ScrollView horizontal pagingEnabled {pages.map((page) ( View key{page.id} style{{ width: screenWidth }} {page.content} /View ))} /ScrollViewscrollEnabled属性类型默认值scrollEnabled?booleantrue为false时内容不可滚动。注意 Web 端实现细节由于 Chrome 不支持在 scroll 事件里preventDefault源码通过touch-action: none配合隐藏 overflow 来真正禁用滚动见 ScrollViewBase.js#L169-L177 及注释引用的 Scrolling Intervention 说明ScrollView scrollEnabled{false} {/* 内容被锁定无法滚动 */} /ScrollViewscrollEventThrottle属性类型默认值scrollEventThrottle?number0控制滚动时onScroll的触发频率毫秒时间间隔数值越小跟踪滚动位置的精度越高但可能造成滚动性能问题默认值0表示每次滚动仅触发一次事件滚动开始/结束各一次官方提示用16可以收到全部事件但可能掉帧精度要求不高时应使用更大的值。Web 端实现见 ScrollViewBase.js#L64-L67通过Date.now()计算距离上次 tick 的时间eventThrottle 0且间隔达标时才派发事件滚动结束后 100ms 内收到最后一次onScroll见 handleScrollEnd。ScrollView onScroll{handleScrollPosition} scrollEventThrottle{16} // 每 16ms 最多触发一次 {children} /ScrollViewstickyHeaderIndices属性类型stickyHeaderIndices?Arraynumber一个子索引数组决定哪些子元素在滚动时吸顶固定到屏幕顶部。例如stickyHeaderIndices{0}会让第一个子元素固定不动。注意该属性不能与horizontal同时使用源码中hasStickyHeaderIndices显式要求!horizontal见 index.js#L584-L585。实现上利用 CSSposition: sticky; top: 0; z-index: 10见 styles.stickyHeader并为命中索引的子元素外包一层 ViewScrollView stickyHeaderIndices{[0]} View {/* 吸顶的头部 */} Text固定头部/Text /View {longList} /ScrollViewScrollEventonScroll 的事件载荷onScroll事件对象上的nativeEvent是描述 ScrollView 布局信息的自定义对象包含三个字段字段类型含义contentOffset{ x: number, y: number }滚动视图沿每个轴滚动了多远contentSize{ height: number, width: number }可滚动内容区域的尺寸layoutMeasurement{ height: number, width: number }滚动视图自身的border-box宽高这三个字段的 Web 端取值在 ScrollViewBase.js#L32-L62 中定义全部映射自原生 DOM 属性contentOffset.x→e.target.scrollLeftcontentOffset.y→e.target.scrollTopcontentSize.width→e.target.scrollWidthcontentSize.height→e.target.scrollHeightlayoutMeasurement.width→e.target.offsetWidthlayoutMeasurement.height→e.target.offsetHeight。并且这些字段是惰性 getter只有在访问时才读取 DOM——避免不必要的重排layout thrash。const handleScroll (e) { const { contentOffset, contentSize, layoutMeasurement } e.nativeEvent; // 计算滚动进度0 ~ 1 const progress contentOffset.y / Math.max(1, contentSize.height - layoutMeasurement.height); console.log(scroll progress:, progress); };同时nativeEvent上还带有timeStamp当前毫秒时间戳。测试 index-test.js#L24-L56 验证了当滚动发生在 ScrollView 自身时onScroll会被调用当滚动发生在后代元素上时不会触发事件被stopPropagation拦截见 ScrollViewBase.js#L103。实例方法通过 ref 编程式控制滚动ScrollView 通过ref暴露一组命令式方法可以在组件外部控制滚动。所有这些方法都会同时挂载到 DOM 节点上见 _setScrollNodeRef测试 index-test.js#L81-L107 逐一断言了它们的存在。方法签名说明getInnerViewNode()() void返回 ScrollView 内部内容容器 DOM 节点的引用getScrollableNode()() void返回底层可滚动 DOM 节点的引用getScrollResponder()() void返回底层滚动响应器引用支持scrollTo()等操作。所有 ScrollView 类组件都应实现该方法以便组合时访问底层滚动响应器的方法scrollTo(options)(options?: { x: number, y: number, animated: boolean }) void滚动到指定的x、y偏移动画取决于浏览器对scroll-behavior的支持scrollToEnd(options)(options?: { animated: boolean }) void滚动到滚动视图末尾垂直视图滚到底部水平视图滚到右侧scrollTo 的底层实现scrollTo的核心实现委托给scrollResponderScrollTo见 index.js#L296-L319const node this.getScrollableNode(); const left x || 0; const top y || 0; if (node ! null) { if (typeof node.scroll function) { node.scroll({ top, left, behavior: !animated ? auto : smooth }); } else { node.scrollLeft left; node.scrollTop top; } }要点优先使用现代Element.scroll({ top, left, behavior })APIanimated: true时使用smooth行为、false时使用auto不支持该 API 的环境回退到直接设置scrollLeft/scrollTop。文档特别注明动画取决于浏览器对scroll-behavior的支持。注意scrollTo也接受历史遗留的分离参数形式scrollTo(y, x, animated)但源码会打印弃用警告推荐统一使用 options 对象形式const scrollRef React.useRef(null); // 平滑滚动到 (0, 200) scrollRef.current.scrollTo({ x: 0, y: 200, animated: true }); // 立即跳转到顶部 scrollRef.current.scrollTo({ y: 0, animated: false }); // 平滑滚动到底部 scrollRef.current.scrollToEnd({ animated: true });scrollToEnd的实现逻辑见 index.js#L538-L546垂直视图取scrollHeight作为 y、x 为 0水平视图取scrollWidth作为 x、y 为 0未传 options 时animated默认true。其他暴露的方法除文档列出的五个方法外ref 节点上还挂载了getInnerViewRef、getNativeScrollRef、flashScrollIndicators、scrollResponderZoomTo、scrollResponderScrollNativeHandleToKeyboard等辅助方法见测试断言 index-test.js#L95-L106其中scrollResponderScrollNativeHandleToKeyboard用于把 TextInput 滚动到键盘上方iOS 场景scrollResponderZoomTo在非 iOS 平台会抛出未实现错误见 index.js#L327-L340。源码级原理Web 端的滚动封装ScrollView 在 Web 端的渲染分两层外层ScrollViewBase见 ScrollViewBase.js一个forwardRef的组件负责 Web 特有的滚动节流与禁用逻辑最终渲染为View即div内层内容容器View collapsable{false}承载contentContainerStyle、centerContent、onContentSizeChange等逻辑。滚动节流与 start/end 检测ScrollViewBase维护isScrolling与scrollLastTick两个状态见 ScrollViewBase.js#L88-L90首次滚动时触发handleScrollStart随后按scrollEventThrottle节流派发handleScrollTick每次滚动都会重置一个 100ms 的定时器滚动停止 100ms 后触发handleScrollEnd派发最后一次onScroll。滚动禁用scrollEnabled{false}时onTouchMove与onWheel都被包装成可拦截处理器createPreventableScrollHandler见 ScrollViewBase.js#L92-L100同时叠加scrollDisabled样式overflow隐藏 touch-action: none。通用样式与性能外层样式commonStyle见 index.js#L728-L737包含两个值得注意的优化transform: translateZ(0)开启 GPU 硬件合成为滚动元素创建独立层显著改善现代浏览器的滚动性能WebkitOverflowScrolling: touchiOS 原生惯性滚动。官方示例一个可直接运行的综合 Demo仓库自带的示例页 packages/react-native-web-examples/pages/scroll-view/index.js 演示了本文大部分 API 的组合用法核心代码如下export default function ScrollViewPage() { const [scrollEnabled, setEnabled] React.useState(true); const [throttle, setThrottle] React.useState(16); const scrollRef React.useRef(null); return ( Example titleScrollView View style{styles.container} ScrollView onScroll{() console.log(onScroll)} ref{scrollRef} scrollEnabled{scrollEnabled} scrollEventThrottle{throttle} style{[styles.scrollView, !scrollEnabled styles.disabled]} {ITEMS.map(createItemRow)} /ScrollView View style{styles.buttons} {/* 切换 scrollEnabled */} Button onPress{() setEnabled((val) !val)} title{scrollEnabled ? Disable : Enable} / {/* 在 16ms 与 1000ms 之间切换节流 */} Button onPress{() setThrottle((val) (val ! 16 ? 16 : 1000))} titleThrottle / /View View style{styles.buttons} Button onPress{() scrollRef.current.scrollTo({ y: 0 })} titleTo start / Button onPress{() scrollRef.current.scrollTo({ y: 50 })} titleTo 50px / Button onPress{() scrollRef.current.scrollToEnd({ animated: true })} titleTo end / /View /View /Example ); }示例中几个关键实践点通过ref.current.scrollTo({ y: 0 })、scrollTo({ y: 50 })与scrollToEnd({ animated: true })编程式控制滚动通过状态切换scrollEnabled观察锁定效果禁用时示例还叠加了opacity: 0.5的视觉反馈通过按钮在16与1000之间切换scrollEventThrottle直观对比onScroll触发频率容器样式scrollView使用maxHeight: 250提供有界高度ScrollView 在此范围内滚动。与列表组件的协同FlatList / SectionList / VirtualizedListScrollView 是 react-native-web 列表体系的地基。仓库中的 FlatList、SectionList、VirtualizedList 均直接复用了 React Native 的同一份 JavaScript 实现并最终渲染为 ScrollView。官方列表文档 lists.md 同时给出提醒这些 RN 列表组件并未针对 Web 做优化对于长列表性能敏感的多端场景建议使用专门的跨平台列表库如 RecyclerListView。从 lists 示例页 可以看到FlatList 上的horizontal、keyboardDismissMode、onScroll配合Animated.event驱动滚动指示器等行为最终都落到 ScrollView 的 Props 语义上。因此理解 ScrollView 的 Props 与事件模型是正确使用整个列表组件族的先决条件。总结ScrollView 是 react-native-web 可滚动 UI 的基石组件本文要点可归纳为布局前提ScrollView 必须有界自身固定高度或父级flex: 1传递滚动能力由 CSSoverflow: auto提供Props 语义horizontal、pagingEnabled、stickyHeaderIndices、centerContent、scrollEnabled、scrollEventThrottle、keyboardDismissMode等各有明确职责其中pagingEnabled与stickyHeaderIndices在 Web 端分别由 CSS Scroll Snap 与position: sticky实现事件模型onScroll的nativeEvent提供contentOffset、contentSize、layoutMeasurement三个惰性读取的布局字段scrollEventThrottle默认为 0每次滚动仅一次事件命令式控制通过 ref 可调用scrollTo、scrollToEnd、getScrollableNode、getScrollResponder等方法滚动动画依赖浏览器对scroll-behavior的支持性能细节translateZ(0)开启硬件合成层、WebkitOverflowScrolling: touch启用 iOS 原生滚动是官方为 Web 滚动体验做的关键优化。如需查看完整源码、测试与可运行示例可继续深入本仓库的 ScrollView 源码目录、ScrollViewBase.js、测试用例 与 官方示例页。【免费下载链接】react-native-webCross-platform React UI packages项目地址: https://gitcode.com/gh_mirrors/re/react-native-web创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表