I want to use the onScroll handler to setState another component in relation to the ScrollView's contentOffset changing its value.
But the throttling of onScroll causes noticeable jumps. Sometimes the component that has setState called on it will never even update until much later on.
Is there a better way to achieve what I want?
Have you tried setting the throttling interval below 16?
@ide Yeah, it's at 15. The jittering persists no matter where I set it. I'm testing on an iPhone 5s.
The jittering is likely due to the setState calls being too slow. If you replace the setState call with a console.log call does the performance noticeably improve?
I don't think I can easily test the performance by replacing setState with console.log since the only jittering is from the component I'm calling setState on.
Can I synchronize the rendering of my component and my ScrollView?
It's hard to tell if performance improves when I take out the forced re-render of the component, no?
Well, yeah - my guess is that re-rendering is too slow.
Is there a way to batch my setState with the changing contentOffset, or is onScroll called after the ScrollView is "re-rendered"?
It's easier for me to explain the entire flow of events. When a native scroll event occurs on the main thread, it schedules a JS onScroll event for the next tick of the JS render loop (assuming no throttling) which runs every 16ms.
At the next tick on the JS thread, React runs your JS onScroll handler and your code calls setState which schedules a re-render before the end of the JS tick. At the end of the tick, the React subtrees with state changes are re-rendered and a JSON description of changes to the native views is sent back down to native. This all happens synchronously on the JS thread -- meanwhile the main thread is free to run UIKit code without being blocked by JS. But if your JS is too slow, that also means the main thread won't receive new updates in the meantime.
The native code processes the JSON description of changes on the shadow thread where it computes layout. Then it schedules the layout information to be applied to the native views at the next tick of the main thread. If you have a lot of expensive changes to the native views, this can take more than 16ms and your app will drop frames.
So basically if you've already configured the JS scroll view to throttle at 16ms or lower, you're already getting scroll events as often as possible. In fact you may want to increase the throttling so that you don't overload the system with too much work (React Native suffers from thundering herds atm).
I noticed the same thing when I implemented a parallax effect.
In my case onScroll was updating an animated value linked to the height of another component.
The lag in this configuration is very slight, maybe a frame or 2 when you scroll down really quickly before the height has caught up.
Not sure there's something that can be done for now.
Or we would need the ability to run some JS code on the main thread.
I think what you're looking for is something like this:
onScroll={Animated.event(
[{nativeEvent: {contentOffset: {y: this.state.yValue}}}]
)}
You can then animate your view using the state values & interpolate. I do this for a pull to refresh animation inside my renderHeader function:
<View style={{height: 100, justifyContent: 'center', alignItems: 'center', marginTop: -100}}>
<Animated.View style={{
backgroundColor: colors.white,
width: 40,
height: 40,
borderRadius: 20,
alignItems: 'center',
justifyContent: 'center',
opacity: this.state.yValue.interpolate({ inputRange: [-80,0], outputRange: [1, 0]}),
transform: [
{scale: this.state.yValue.interpolate({ inputRange: [-80,0], outputRange: [1, .5]}),
extrapolate: 'extend'}
]}}>
<Text style={{color: colors.black, fontSize: 16}}>RCT</Text>
</Animated.View>
</View>
@ide @brentvatne Looks resolved here.
@ide @brentvatne
I'm not sure this is resolved. I'm using the same tactic as @yamill to do animations and experiencing some very noticeable delays. The onScroll events simply do not come through the javascript bridge fast enough...
You can see the issue occurring in this gif: http://i.imgur.com/r15gMy1.gifv. The blurred background image is an Animated view that is having its height animated to always be aligned with the orange/gray bar, and its top animated to scroll off the screen as you scroll down. A console.log() in my render() method proves that this entire view is only being rendered once, therefore all the animations are being performed outside of the render loop. You can't notice it with gentle scrolling (beginning of the gif), but with fast scroll flicks (end of the gif) the problem rears its head real quick with the image noticeably snapping into the correct position after a big delay. My scrollEventThrottle is very low.
@marcshilling
Did you find a solution to this?
I have a similar issue with a View I'm animating according to the scroll position. More or less like this: http://stackoverflow.com/questions/34389672/scroll-the-view-simultaneously-as-the-scrollview-scrolls
And there is a noticeable lag in the performance.
@marcshilling @zoharlevin Have either of you tested with your Xcode project set to Release instead of Debug?
If necessary, it would be nice to hear from Facebook about how to continue forward. :smile:
@aleclarson
I'm working on Android, not ios. I haven't tried a release apk yet, but I did try disabling JS Dev Mode which made no difference.
Also, as I understand the option for scrollEventThrottle is relevant only to ios.
Do you have any other suggestions I could try to improve the performance?
It seems like just a fraction of a second behind the actual scroll, but it's definitely noticeable. I wrote a demo app in native android which did the same thing with no lag at all, so I'm really eager to achieve the same level of performance in my react-native app.
@zoharlevin nope, ended up changing our design requirements because we couldn't get the performance :(
@aleclarson yes, the problem is still apparent in a bundled release build.
Unless I'm missing something, I think this example is an example where React Native is just not gonna cut it performance-wise. Will have to drop down to a native scroll view.
Resolved. Put both of the components which you want to sync their movement into a ScrollView. Btw, If you want to scroll one of the them in other direction, it also can be achieved, wrap the target by a View component. @marcshilling @aleclarson @jeanregisser @zoharlevin
@shendajht What do you mean by put both components into a ScrollView?
I don't see how does that meet this requirement (being able to sync scroll position with something).
Can you provide a more concrete exmaple?
We'll have to wait for native drivers in AnimatedJS to be completed before we can achieve an Animated.Value that reacts to the scroll position without being delayed or losing frames.
The native⇄JS bridge cannot update the Animated.Value in response to the onScroll event without losing frames. There's a huge post about bridge performance in this post.
@aleclarson Thanks for the article, I have a better understanding of the situation now.
And while this is the case, this issue should be open since there is no solution for it right now.
I am having a similar issue. Instead of throttling the event, onChange/scrollEventThrottle seem to pass me most of the events, just tremendously delayed ( like 15 seconds for the last events to come through after scrolling as completely stopped ). So it stopes scrolling, but I get several events for 15 seconds, and none of them are accurate on position until the last several seconds.
Any thoughts?
@alexprice91 - hey, I also have problems with scrollEventThrottle, especially in DEBUG mode. It was defiantly a lot faster in RELEASE. I then also build my onw throttle, which worked for my usecase
onScroll={(e) => {
const currentScrollPos = e.nativeEvent.contentOffset.y
const sensitivity = 50
if (Math.abs(currentScrollPos - this.state.lastScrollPos) > sensitivity) {
this.props.doSomething()
this.setState({lastScrollPos: e.nativeEvent.contentOffset.y })
}
} }
(I set lastScrollPos to 0 in the constructor() )
Got it, thank you! I forget about the latency involved with debugging in chrome, so thanks!
Sent from my iPhone
On Jul 27, 2016, at 3:37 PM, Thorben Andresen [email protected] wrote:
@alexprice91 - hey, I also have problems with scrollEventThrottle, especially in DEBUG mode. It was defiantly a lot faster in RELEASE. I then also build my onw throttle, which worked for my usecase
onScroll={(e) => {
const currentScrollPos = e.nativeEvent.contentOffset.y const sensitivity = 50 if (Math.abs(currentScrollPos - this.state.lastScrollPos) > sensitivity) { this.props.doSomething() this.setState({lastScrollPos: e.nativeEvent.contentOffset.y }) }} }
(I set lastScrollPos to 0 in the constructor() )—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.
Same problem, anyone resolve this issue?
@mariodev12 You can use Animated.event with native driver today.
From react native blog:
It also works with Animated.event, this is very useful if you have an animation that must follow the scroll position because without the native driver it will always run a frame behind of the gesture because of the async nature of React Native.
Hey! I know this is old but I wanted to say thanks to those who provided insight here.
I've been trying to get a pinned header & footer solution working and synced scrolling was one of the key pieces. What I have works but isn't optimal.
If you want to follow along I put together a StackOverflow post that points to a working Expo Snack.
Thanks again!
Most helpful comment
I think what you're looking for is something like this:
You can then animate your view using the state values & interpolate. I do this for a pull to refresh animation inside my
renderHeaderfunction: