Skip to content

Commit ba87750

Browse files
authored
[Fabric] Implement scrollEventThrottle for ScrollView (#14555)
1 parent 436ae60 commit ba87750

File tree

4 files changed

+50
-14
lines changed

4 files changed

+50
-14
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "Implement scrollEventThrottle for ScrollView in Fabric",
4+
"packageName": "react-native-windows",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}

packages/playground/Samples/scrollViewSnapSample.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,8 @@ export default class Bootstrap extends React.Component<{}, any> {
304304
onScroll={() => {
305305
console.log('onScroll');
306306
}}
307-
decelerationRate={0.95}>
307+
decelerationRate={0.95}
308+
scrollEventThrottle={50}>
308309
{this.makeItems(20, [styles.itemWrapper])}
309310
</ScrollView>
310311
</View>

vnext/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.cpp

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,20 @@ void ScrollViewComponentView::updateProps(
779779
updateDecelerationRate(newViewProps.decelerationRate);
780780
}
781781

782+
if (!oldProps || oldViewProps.scrollEventThrottle != newViewProps.scrollEventThrottle) {
783+
// Zero means "send value only once per significant logical event".
784+
// Prop value is in milliseconds.
785+
auto throttleInSeconds = newViewProps.scrollEventThrottle / 1000.0;
786+
auto msPerFrame = 1.0 / 60.0;
787+
if (throttleInSeconds < 0) {
788+
m_scrollEventThrottle = INFINITY;
789+
} else if (throttleInSeconds <= msPerFrame) {
790+
m_scrollEventThrottle = 0;
791+
} else {
792+
m_scrollEventThrottle = throttleInSeconds;
793+
}
794+
}
795+
782796
if (oldViewProps.maximumZoomScale != newViewProps.maximumZoomScale) {
783797
m_scrollVisual.SetMaximumZoomScale(newViewProps.maximumZoomScale);
784798
}
@@ -1000,6 +1014,8 @@ bool ScrollViewComponentView::scrollToEnd(bool animate) noexcept {
10001014

10011015
auto x = (m_contentSize.width - m_layoutMetrics.frame.size.width) * m_layoutMetrics.pointScaleFactor;
10021016
auto y = (m_contentSize.height - m_layoutMetrics.frame.size.height) * m_layoutMetrics.pointScaleFactor;
1017+
// Ensure at least one scroll event will fire
1018+
m_allowNextScrollNoMatterWhat = true;
10031019
m_scrollVisual.TryUpdatePosition({static_cast<float>(x), static_cast<float>(y), 0.0f}, animate);
10041020
return true;
10051021
}
@@ -1240,19 +1256,27 @@ winrt::Microsoft::ReactNative::Composition::Experimental::IVisual ScrollViewComp
12401256
[this](
12411257
winrt::IInspectable const & /*sender*/,
12421258
winrt::Microsoft::ReactNative::Composition::Experimental::IScrollPositionChangedArgs const &args) {
1243-
updateStateWithContentOffset();
1244-
auto eventEmitter = GetEventEmitter();
1245-
if (eventEmitter) {
1246-
facebook::react::ScrollViewEventEmitter::Metrics scrollMetrics;
1247-
scrollMetrics.containerSize.height = m_layoutMetrics.frame.size.height;
1248-
scrollMetrics.containerSize.width = m_layoutMetrics.frame.size.width;
1249-
scrollMetrics.contentOffset.x = args.Position().x / m_layoutMetrics.pointScaleFactor;
1250-
scrollMetrics.contentOffset.y = args.Position().y / m_layoutMetrics.pointScaleFactor;
1251-
scrollMetrics.zoomScale = 1.0;
1252-
scrollMetrics.contentSize.height = std::max(m_contentSize.height, m_layoutMetrics.frame.size.height);
1253-
scrollMetrics.contentSize.width = std::max(m_contentSize.width, m_layoutMetrics.frame.size.width);
1254-
std::static_pointer_cast<facebook::react::ScrollViewEventEmitter const>(eventEmitter)
1255-
->onScroll(scrollMetrics);
1259+
auto now = std::chrono::steady_clock::now();
1260+
auto elapsed = std::chrono::duration_cast<std::chrono::duration<double>>(now - m_lastScrollEventTime).count();
1261+
1262+
if (m_allowNextScrollNoMatterWhat ||
1263+
(m_scrollEventThrottle < std::max(std::chrono::duration<double>(0.017).count(), elapsed))) {
1264+
updateStateWithContentOffset();
1265+
auto eventEmitter = GetEventEmitter();
1266+
if (eventEmitter) {
1267+
facebook::react::ScrollViewEventEmitter::Metrics scrollMetrics;
1268+
scrollMetrics.containerSize.height = m_layoutMetrics.frame.size.height;
1269+
scrollMetrics.containerSize.width = m_layoutMetrics.frame.size.width;
1270+
scrollMetrics.contentOffset.x = args.Position().x / m_layoutMetrics.pointScaleFactor;
1271+
scrollMetrics.contentOffset.y = args.Position().y / m_layoutMetrics.pointScaleFactor;
1272+
scrollMetrics.zoomScale = 1.0;
1273+
scrollMetrics.contentSize.height = std::max(m_contentSize.height, m_layoutMetrics.frame.size.height);
1274+
scrollMetrics.contentSize.width = std::max(m_contentSize.width, m_layoutMetrics.frame.size.width);
1275+
std::static_pointer_cast<facebook::react::ScrollViewEventEmitter const>(eventEmitter)
1276+
->onScroll(scrollMetrics);
1277+
m_lastScrollEventTime = now;
1278+
m_allowNextScrollNoMatterWhat = false;
1279+
}
12561280
}
12571281
});
12581282

@@ -1261,6 +1285,7 @@ winrt::Microsoft::ReactNative::Composition::Experimental::IVisual ScrollViewComp
12611285
[this](
12621286
winrt::IInspectable const & /*sender*/,
12631287
winrt::Microsoft::ReactNative::Composition::Experimental::IScrollPositionChangedArgs const &args) {
1288+
m_allowNextScrollNoMatterWhat = true; // Ensure next scroll event is recorded, regardless of throttle
12641289
updateStateWithContentOffset();
12651290
auto eventEmitter = GetEventEmitter();
12661291
if (eventEmitter) {

vnext/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,9 @@ struct ScrollInteractionTrackerOwner : public winrt::implements<
147147
bool m_isHorizontal = false;
148148
bool m_changeViewAfterLoaded = false;
149149
bool m_dismissKeyboardOnDrag = false;
150+
double m_scrollEventThrottle{0.0};
151+
bool m_allowNextScrollNoMatterWhat{false};
152+
std::chrono::steady_clock::time_point m_lastScrollEventTime{};
150153
std::shared_ptr<facebook::react::ScrollViewShadowNode::ConcreteState const> m_state;
151154
};
152155

0 commit comments

Comments
 (0)