From 7c522c966186bc04a0fa8eafff7996154117f50f Mon Sep 17 00:00:00 2001 From: yandadaFreedom Date: Tue, 10 Mar 2026 16:22:13 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20scrollView=20scrollOffset=20?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=94=AF=E6=8C=81scrollWidth=E3=80=81scrollH?= =?UTF-8?q?eight=E5=B1=9E=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/runtime/components/react/mpx-scroll-view.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-scroll-view.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-scroll-view.tsx index 47bf5b0484..778326b92c 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-scroll-view.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-scroll-view.tsx @@ -180,6 +180,8 @@ const _ScrollView = forwardRef, S offset: 0, scrollLeft: 0, scrollTop: 0, + scrollWidth: 0, + scrollHeight: 0, visibleLength: 0 }) @@ -464,6 +466,8 @@ const _ScrollView = forwardRef, S const newContentLength = selectLength({ height, width }) const oldContentLength = scrollOptions.current.contentLength scrollOptions.current.contentLength = newContentLength + scrollOptions.current.scrollWidth = width + scrollOptions.current.scrollHeight = height // 内容高度变化时,Animated.event 的映射可能会有不生效的场景,所以需要手动设置一下 scrollOffset 的值 if (enableSticky && (__mpx_mode__ === 'android' || __mpx_mode__ === 'ios')) { // 当内容变少时,检查当前滚动位置是否超出新的内容范围 From a405ca5486b4735f4e208dc65239eeaf192ba28a Mon Sep 17 00:00:00 2001 From: yandadaFreedom Date: Tue, 10 Mar 2026 20:12:47 +0800 Subject: [PATCH 2/5] =?UTF-8?q?feat(scroll-view):=20=E5=AE=9E=E7=8E=B0=20S?= =?UTF-8?q?crollViewContext=20node=20=E6=94=AF=E6=8C=81=E5=8A=A8=E6=80=81?= =?UTF-8?q?=E8=AF=BB=E5=86=99=E5=B1=9E=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/react/mpx-scroll-view.tsx | 107 +++++++++++++++--- 1 file changed, 91 insertions(+), 16 deletions(-) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-scroll-view.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-scroll-view.tsx index 778326b92c..6c9018aacb 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-scroll-view.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-scroll-view.tsx @@ -38,10 +38,9 @@ import Animated, { useSharedValue, withTiming, useAnimatedStyle, runOnJS } from import { warn, hasOwn } from '@mpxjs/utils' import useInnerProps, { getCustomEvent } from './getInnerListeners' import useNodesRef, { HandlerRef } from './useNodesRef' -import { splitProps, splitStyle, useTransformStyle, useLayout, wrapChildren, extendObject, flatGesture, GestureHandler, HIDDEN_STYLE, useRunOnJSCallback } from './utils' +import { splitProps, splitStyle, useTransformStyle, useLayout, wrapChildren, extendObject, flatGesture, GestureHandler, HIDDEN_STYLE, useRunOnJSCallback, useUpdateEffect } from './utils' import { IntersectionObserverContext, ScrollViewContext } from './context' import Portal from './mpx-portal' - interface ScrollViewProps { children?: ReactNode; enhanced?: boolean; @@ -109,6 +108,16 @@ type ScrollAdditionalProps = { onScrollEndDrag?: (event: NativeSyntheticEvent) => void onMomentumScrollEnd?: (event: NativeSyntheticEvent) => void } +type ScrollViewContextType = { + scrollEnabled: boolean + bounces: boolean + showScrollbar: boolean + pagingEnabled: boolean + fastDeceleration: boolean + decelerationDisabled: boolean + scrollTo: (options: { top?: number; left?: number; animated?: boolean; duration?: number }) => void + scrollIntoView: (selector: string, options: { offset?: number; animated?: boolean }) => void +} const AnimatedScrollView = RNAnimated.createAnimatedComponent(ScrollView) as React.ComponentType @@ -167,7 +176,31 @@ const _ScrollView = forwardRef, S const [refreshing, setRefreshing] = useState(false) const [enableScroll, setEnableScroll] = useState(true) + // 下拉刷新内部维护的 bounces 状态 const [scrollBounces, setScrollBounces] = useState(false) + // 外部用户传递的 bounces 属性 + const [bouncesState, setBouncesState] = useState(!!bounces) + const [showScrollbarState, setShowScrollbarState] = useState(!!showScrollbar) + const [pagingEnabledState, setPagingEnabledState] = useState(!!pagingEnabled) + + // 用于 ScrollViewContext getter 访问最新状态 + const scrollViewStateRef = useRef({ + enableScroll: true, + bounces: false, + showScrollbar, + pagingEnabled, + scrollX, + scrollY + }) + + scrollViewStateRef.current = { + enableScroll, + bounces: bouncesState, + showScrollbar: showScrollbarState, + pagingEnabled: pagingEnabledState, + scrollX, + scrollY + } const enableScrollValue = useSharedValue(true) const bouncesValue = useSharedValue(false) @@ -228,22 +261,64 @@ const _ScrollView = forwardRef, S }) const runOnJSCallback = useRunOnJSCallback(runOnJSCallbackRef) + // 创建 ScrollViewContext 对象,支持动态读写属性 + const scrollViewContext = useRef(null) + + if (!scrollViewContext.current) { + scrollViewContext.current = { + get scrollEnabled () { + const { enableScroll, scrollX, scrollY } = scrollViewStateRef.current + return !enableScroll ? false : !!(scrollX || scrollY) + }, + set scrollEnabled (value: boolean) { + setEnableScroll(value) + }, + get bounces () { + return scrollViewStateRef.current.bounces + }, + set bounces (value: boolean) { + setBouncesState(value) + }, + get showScrollbar () { + return scrollViewStateRef.current.showScrollbar + }, + set showScrollbar (value: boolean) { + setShowScrollbarState(value) + }, + get pagingEnabled () { + return scrollViewStateRef.current.pagingEnabled + }, + set pagingEnabled (value: boolean) { + setPagingEnabledState(value) + }, + get fastDeceleration () { + return false + }, + set fastDeceleration (value: boolean) { + warn(`fastDeceleration is not supported in ${__mpx_mode__} platform`) + }, + get decelerationDisabled () { + return false + }, + set decelerationDisabled (value: boolean) { + warn(`decelerationDisabled is not supported in ${__mpx_mode__} platform`) + }, + scrollTo, + scrollIntoView: handleScrollIntoView + } + } + useNodesRef(props, ref, scrollViewRef, { style: normalStyle, scrollOffset: scrollOptions, - node: { - scrollEnabled: scrollX || scrollY, - bounces, - showScrollbar, - pagingEnabled, - fastDeceleration: false, - decelerationDisabled: false, - scrollTo, - scrollIntoView: handleScrollIntoView - }, + node: scrollViewContext.current, gestureRef: scrollViewRef }) + useUpdateEffect(() => { setScrollBounces(!!bounces) }, [bounces]) + useUpdateEffect(() => { setShowScrollbarState(!!showScrollbar) }, [showScrollbar]) + useUpdateEffect(() => { setPagingEnabledState(!!pagingEnabled) }, [pagingEnabled]) + const { layoutRef, layoutStyle, layoutProps } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef: scrollViewRef, onLayout }) const contextValue = useMemo(() => { @@ -812,8 +887,8 @@ const _ScrollView = forwardRef, S horizontal: scrollX && !scrollY, scrollEventThrottle: scrollEventThrottle, scrollsToTop: enableBackToTop, - showsHorizontalScrollIndicator: scrollX && showScrollbar, - showsVerticalScrollIndicator: scrollY && showScrollbar, + showsHorizontalScrollIndicator: scrollX && showScrollbarState, + showsVerticalScrollIndicator: scrollY && showScrollbarState, scrollEnabled: !enableScroll ? false : !!(scrollX || scrollY), bounces: false, overScrollMode: 'never', @@ -832,8 +907,8 @@ const _ScrollView = forwardRef, S if (enhanced) { Object.assign(scrollAdditionalProps, { - bounces: hasRefresher ? scrollBounces : !!bounces, - pagingEnabled + bounces: hasRefresher ? scrollBounces : bouncesState, + pagingEnabled: pagingEnabledState }) } From c9960ff89e622a70e542d7c02120bb90295e77c5 Mon Sep 17 00:00:00 2001 From: yandadaFreedom Date: Tue, 15 Sep 2026 19:43:36 +0800 Subject: [PATCH 3/5] fix(scroll-view): use latest direction for scrollIntoView --- .../lib/runtime/components/react/mpx-scroll-view.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-scroll-view.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-scroll-view.tsx index 89d2b83932..b47f853ae9 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-scroll-view.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-scroll-view.tsx @@ -475,6 +475,7 @@ const _ScrollView = forwardRef, S nodeRef.current.measureLayout( targetScrollView, (left: number, top: number) => { + const { scrollX, scrollY } = scrollViewStateRef.current const adjustedLeft = scrollX ? left + offset : left const adjustedTop = scrollY ? top + offset : top From 804d6fc56f745c939b96da66c40c8e32e5e3ab2c Mon Sep 17 00:00:00 2001 From: yandadaFreedom Date: Thu, 17 Sep 2026 19:56:58 +0800 Subject: [PATCH 4/5] fix(scroll-view): sync dynamic bounces state --- .../lib/runtime/components/react/mpx-scroll-view.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-scroll-view.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-scroll-view.tsx index b47f853ae9..ae5b04f48c 100644 --- a/packages/webpack-plugin/lib/runtime/components/react/mpx-scroll-view.tsx +++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-scroll-view.tsx @@ -326,7 +326,7 @@ const _ScrollView = forwardRef, S gestureRef: scrollViewRef }) - useUpdateEffect(() => { setScrollBounces(!!bounces) }, [bounces]) + useUpdateEffect(() => { setBouncesState(!!bounces) }, [bounces]) useUpdateEffect(() => { setShowScrollbarState(!!showScrollbar) }, [showScrollbar]) useUpdateEffect(() => { setPagingEnabledState(!!pagingEnabled) }, [pagingEnabled]) @@ -857,7 +857,7 @@ const _ScrollView = forwardRef, S if (enhanced) { Object.assign(scrollAdditionalProps, { - bounces: hasRefresher ? scrollBounces : bouncesState, + bounces: hasRefresher ? bouncesState && scrollBounces : bouncesState, pagingEnabled: pagingEnabledState }) } @@ -916,7 +916,7 @@ const _ScrollView = forwardRef, S .failOffsetX([-5, 5]) .onUpdate((event) => { 'worklet' - if (enhanced && !!bounces) { + if (enhanced && bouncesState) { if (event.translationY > 0 && bouncesValue.value) { updateBouncesState(false) } else if ((event.translationY < 0) && !bouncesValue.value) { @@ -973,7 +973,7 @@ const _ScrollView = forwardRef, S } }) .simultaneousWithExternalGesture(scrollViewRef) - }, [enhanced, bounces, refreshing, refresherThreshold]) + }, [enhanced, bouncesState, refreshing, refresherThreshold]) const ScrollViewComponent = enableSticky ? AnimatedScrollView : ScrollView From 0c6f40b2a8e9e4d83bc277053f674a9456bd7d69 Mon Sep 17 00:00:00 2001 From: yandadaFreedom Date: Thu, 17 Sep 2026 20:52:01 +0800 Subject: [PATCH 5/5] docs(rn): document scroll view query capabilities --- .agents/skills/mpx2rn/references/rn-api-reference.md | 4 ++++ docs-vitepress/guide/rn/application-api.md | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/.agents/skills/mpx2rn/references/rn-api-reference.md b/.agents/skills/mpx2rn/references/rn-api-reference.md index 25a15e2aac..fde6cb7140 100644 --- a/.agents/skills/mpx2rn/references/rn-api-reference.md +++ b/.agents/skills/mpx2rn/references/rn-api-reference.md @@ -1849,6 +1849,10 @@ mpx.config.rnConfig.wifiPermission = () => { | `nodesRef.context(callback)` / `node(callback)` / `ref(callback)` | 当前 `SelectorQuery` | 分别返回 `{ context }`、`{ node }`、`{ ref }`。 | | `query.exec(callback)` | `undefined` | 按入队顺序聚合前述回调结果,最终参数为结果数组。 | +通过 `query.select(selector).scrollOffset(callback)` 查询实际的 `scroll-view` 节点时,回调结果包含当前滚动位置以及滚动内容的实际 `scrollWidth`、`scrollHeight`。 + +RN 当前支持动态读写 `ScrollViewContext` 的 `scrollEnabled`、`bounces`、`showScrollbar` 和 `pagingEnabled` 四个属性。使用时需要为 `scroll-view` 开启 `enhanced`,并通过 `query.select(selector).node(callback)` 回调参数中的 `node` 字段获取。 + #### 注意事项 - selector 仅支持 `#id`、`.class` 和连续 class(如 `.a.b`),不支持后代、子节点、跨组件等复杂选择器。对应模板节点须添加空 `wx:ref`,并在节点渲染完成后查询;数据更新后需等待 `this.$nextTick()` 再查询。 diff --git a/docs-vitepress/guide/rn/application-api.md b/docs-vitepress/guide/rn/application-api.md index dde6607ef0..31406010f0 100644 --- a/docs-vitepress/guide/rn/application-api.md +++ b/docs-vitepress/guide/rn/application-api.md @@ -252,6 +252,10 @@ module.exports = { 使用 `createSelectorQuery` 来获取基础组件需要在基础节点上标记 `wx:ref` 标签才能生效,以及所支持的选择器范围和 `selectComponent`/`selectAllComponents` 一致。 +查询实际的 `scroll-view` 节点时,可通过 `select(selector).scrollOffset(callback)` 获取包含实际内容宽高的回调结果。 + +RN 当前支持动态读写 `ScrollViewContext` 的 `scrollEnabled`、`bounces`、`showScrollbar` 和 `pagingEnabled` 四个属性。使用时需要为 `scroll-view` 开启 `enhanced`,并通过 `select(selector).node(callback)` 回调参数中的 `node` 字段获取。 + ```javascript