Skip to content

Commit 451c0d1

Browse files
Mark Verlingierifacebook-github-bot
authored andcommitted
Process event-driven animations synchronously on every event
Summary: The C++ NativeAnimatedNodesManager only processes the first scroll event in a gesture synchronously via trigger()/onRender(). Subsequent events just store the updated value and defer graph traversal + prop commit to the next choreographer frame, introducing ~16ms (1 frame) latency. This differs from the Java NativeAnimatedNodesManager which calls updateNodes() → updateView() → synchronouslyUpdateViewOnUIThread() for every event with no gating. The issue was the `!isEventAnimationInProgress_` guard at line 502, which prevented trigger()/onRender() from running on the 2nd+ events. `isEventAnimationInProgress_` stays true for the entire scroll gesture (only resets when a choreographer frame produces zero changes), so every scroll event after the first was deferred. This change moves the trigger()/onRender() call outside the `!isEventAnimationInProgress_` gate so every scroll event gets immediate synchronous processing, matching the Java implementation. The startRenderCallbackIfNeeded + isEventAnimationInProgress_ setup still only happens once (first event). ## Changelog: [General] [Fixed] - Fix 1-frame latency in C++ NativeAnimatedNodesManager for event-driven animations by processing the animation graph synchronously on every scroll event, matching the Java implementation behavior Reviewed By: zeyap Differential Revision: D100719854
1 parent 8550370 commit 451c0d1

File tree

1 file changed

+17
-9
lines changed

1 file changed

+17
-9
lines changed

packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -499,15 +499,23 @@ void NativeAnimatedNodesManager::handleAnimatedEvent(
499499
}
500500
}
501501

502-
if (foundAtLeastOneDriver && !isEventAnimationInProgress_) {
503-
// There is an animation driver handling this event and
504-
// event driven animation has not been started yet.
505-
isEventAnimationInProgress_ = true;
506-
// Some platforms (e.g. iOS) have UI tick listener disable
507-
// when there are no active animations. Calling
508-
// `startRenderCallbackIfNeeded` will call platform specific code to
509-
// register UI tick listener.
510-
startRenderCallbackIfNeeded(false);
502+
if (foundAtLeastOneDriver) {
503+
// Process event-driven animation updates synchronously, matching the
504+
// behavior of the Java NativeAnimatedNodesManager which calls
505+
// updateNodes() for every event. Without this, only the first event
506+
// in a scroll sequence is processed synchronously — subsequent events
507+
// just store the updated value and defer graph traversal + prop commit
508+
// to the next choreographer frame, introducing 1-frame latency.
509+
if (!isEventAnimationInProgress_) {
510+
// There is an animation driver handling this event and
511+
// event driven animation has not been started yet.
512+
isEventAnimationInProgress_ = true;
513+
// Some platforms (e.g. iOS) have UI tick listener disable
514+
// when there are no active animations. Calling
515+
// `startRenderCallbackIfNeeded` will call platform specific code to
516+
// register UI tick listener.
517+
startRenderCallbackIfNeeded(false);
518+
}
511519
// Calling startOnRenderCallback_ will register a UI tick listener.
512520
// The UI ticker listener will not be called until the next frame.
513521
// That's why, in case this is called from the UI thread, we need to

0 commit comments

Comments
 (0)