1

For my project I'm looking to implement a ScrollView that shows items as if you're browsing through a box of business cards. I based my current implementation on Albert Brand's article on Animated ScrollViews (after trying many other approaches).

However, the whole thing becomes very jittery now. Can anyone advise me how to smoothen the current implementation? Alternatively, does anyone have a tip how to get this behaviour in a different manner? Any solution should work in both iOS and Android.

This gif shows my current implementation including the jittering problem. It also should give a clear picture of what behaviour I'm after: screencapture of the ScrollView in iOS simulator

This is my current implementation:

import React from 'react'
import {
  Animated,
  Dimensions,
  ScrollView,
  StyleSheet,
  Text,
  View,
  StatusBar,
} from 'react-native'

const SCREEN_HEIGHT = Dimensions.get('window').height

const yOffset = new Animated.Value(0)

const onScroll = Animated.event(
  [{ nativeEvent: { contentOffset: { y: yOffset } } }],
)

function CardView(props: { children?: ReactElement<*> }) {
  return (
    <Animated.ScrollView
      scrollEventThrottle={16}
      onScroll={onScroll}
      pagingEnabled
    >
      {props.children}
    </Animated.ScrollView>
  )
}

function Page(props: { children?: ReactElement<*>, index: 1 }) {
  return (
    <Animated.View style={[style.scrollPage]}>
      {props.children}
    </Animated.View>
  )
}

function Card(props: { text: string, index: number }) {
  return (
    <Animated.View style={[style.card, marginTransform(props.index)]}>
      <Text>
        {props.text}
      </Text>
    </Animated.View>
  )
}

function marginTransform(index: number) {
  return {
    marginTop: yOffset.interpolate({
      inputRange: [
        (index - 1) * SCREEN_HEIGHT,
        index * SCREEN_HEIGHT,
        (index + 1) * SCREEN_HEIGHT,
        (index + 2) * SCREEN_HEIGHT,
        (index + 3) * SCREEN_HEIGHT,
      ],
      outputRange: [
        -40,
        80,
        SCREEN_HEIGHT + 40,
        2 * SCREEN_HEIGHT + 20,
        3 * SCREEN_HEIGHT,
      ],
      extrapolate: 'clamp',
    }),
  }
}

export default function App() {
  return (
    <CardView>
      {[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((i) => {
        return(
          <Page key={i}>
            <Card text={`Card ${i}`} index={i}>
            </Card>
          </Page>
        )
      })}
    </CardView>
  )
}

const style = StyleSheet.create({
  scrollPage: {
    height: SCREEN_HEIGHT,
    backgroundColor: 'transparent',
  },
  card: {
    height: SCREEN_HEIGHT,
    alignItems: 'center',
    borderRadius: 4,
    borderWidth: 1,
    backgroundColor: '#F5FCFF',
  }
})
Isilher
  • 331
  • 2
  • 8

3 Answers3

2

The short answer I found: ScrollViews will never animate smoothly if you mess with it's height while scrolling. On a sidenote I did find that performance improved drastically when I set scrollEventThrottle={1}

The solution I ended up implementing was making my own component; rendering a ScrollView on top of a list of elements. I connected the Animated height values of those elements to the scroll y offset of the ScrollView.

If anyone ever encounters the same usecase implementation issue, feel free to use my work: https://github.com/isilher/react-native-card-scroll

Isilher
  • 331
  • 2
  • 8
  • For some reason, in my situation setting scrollEventThrottle to 1..16 makes it stutter. Setting it above 16 (e.g. 18) makes it completely smooth. Increasing it even more, slowly introduces another kind of stuttering because it is missing too much events then. – mvandillen Nov 10 '20 at 15:49
0

Make sure Debug JS Remote is disabled. You should also remove all the console.log() inside your class (for example, if you are logging the yOffset for debugging reasons). This should speed up things.

  • Thank you Luca, I tried this but optimisation is not the problem here. I think the cause of the jitter is that updating the marginTop while scrolling also messes with the rendering of the ScrollView as a whole. I have no idea how to solve that though. – Isilher May 04 '17 at 09:00
  • Try to update top (not marginTop) and set the position to 'absolute' – Luca Meghnagi May 04 '17 at 13:36
  • 1
    That seems to have the same result. An additional disadvantage is that both marginTop and top offset render the Card outside of it's container: Android cuts them off. I'm afraid I will have to look for an entirely different implementation. Thanks for your help! On a sidenote, the jittery behaviour became a lot smoother when I set `scrollEventThrottle={1}` instead of 16. Still too shaky for actual use though. – Isilher May 04 '17 at 13:43
0

hey try this useNativeDriver: true will help you to get ride of the lag

onScroll={Animated.event(
   [{ nativeEvent: { contentOffset: { x: xOffset } } }],
   { useNativeDriver: true }
)}
xskxzr
  • 12,442
  • 12
  • 37
  • 77