Obtenir la position de défilement actuelle de ScrollView dans React Native

115

Est-il possible d'obtenir la position de défilement actuelle ou la page actuelle d'un <ScrollView>composant dans React Native?

Donc quelque chose comme:

<ScrollView
  horizontal={true}
  pagingEnabled={true}
  onScrollAnimationEnd={() => { 
      // get this scrollview's current page or x/y scroll position
  }}>
  this.state.data.map(function(e, i) { 
      <ImageCt key={i}></ImageCt> 
  })
</ScrollView>
A chanté
la source
Pertinente: un problème GitHub suggérant d'ajouter un moyen pratique d'obtenir ces informations.
Mark Amery

Réponses:

207

Essayer.

<ScrollView onScroll={this.handleScroll} />

Puis:

handleScroll: function(event: Object) {
 console.log(event.nativeEvent.contentOffset.y);
},
Brad Ooyler
la source
5
Cela ne se déclenchera pas toujours lorsque la valeur de défilement change. Si, par exemple, le scrollview est redimensionné, ce qui lui permet maintenant d'afficher le tout à l'écran en même temps, vous ne semblez pas toujours recevoir un événement onScroll vous le disant.
Thomas Parslow
19
Ou event.nativeEvent.contentOffset.xsi vous avez un ScrollView horizontal;) Merci!
Tieme
2
Nous pouvons également l'utiliser sous Android.
Janaka Pushpakumara
4
Frustrant, à moins que vous ne régliez scrollEventThrottlesur 16 ou moins (pour obtenir un événement pour chaque image), il n'y a aucune garantie que cela signalera le décalage sur l' image finale - ce qui signifie que pour être sûr que vous avez le bon décalage une fois le défilement terminé, vous devez définir scrollEventThrottle={16}et accepter l'impact sur les performances de cela.
Mark Amery
@bradoyler: est-il possible de connaître la valeur maximale de event.nativeEvent.contentOffset.y?
Isaac
55

Avertissement: ce qui suit est principalement le résultat de ma propre expérimentation dans React Native 0.50. La ScrollViewdocumentation est actuellement manque beaucoup des informations couvertes ci - dessous; par exemple onScrollEndDragest complètement non documenté. Puisque tout ici repose sur un comportement non documenté, je ne peux malheureusement pas promettre que ces informations resteront correctes dans un an ou même un mois.

De plus, tout ce qui suit suppose une vue de défilement purement verticale dont le décalage y nous intéresse; la conversion en décalages x , si nécessaire, est, espérons-le, un exercice facile pour le lecteur.


Divers gestionnaires d'événements sur une ScrollViewprise eventet vous permettent d'obtenir la position de défilement actuelle via event.nativeEvent.contentOffset.y. Certains de ces gestionnaires ont un comportement légèrement différent entre Android et iOS, comme détaillé ci-dessous.

onScroll

Sur Android

Déclenche chaque image pendant que l'utilisateur fait défiler, sur chaque image pendant que la vue de défilement glisse après que l'utilisateur la libère, sur l'image finale lorsque la vue de défilement s'arrête, et également chaque fois que le décalage de la vue de défilement change en raison de son cadre changeant (par exemple en raison de la rotation du paysage au portrait).

Sur iOS

Se déclenche lorsque l'utilisateur fait glisser ou lorsque la vue de défilement glisse, à une fréquence déterminée par scrollEventThrottleet au plus une fois par image quand scrollEventThrottle={16}. Si l'utilisateur libère la vue de défilement alors qu'il a suffisamment d'élan pour glisser, le onScrollgestionnaire se déclenchera également lorsqu'il s'arrêtera après avoir glissé. Cependant, si l'utilisateur fait glisser puis relâche la vue de défilement alors qu'elle est stationnaire, il onScrolln'est pas garanti de se déclencher pour la position finale à moins qu'il scrollEventThrottlen'ait été défini de manière à onScrolldéclencher chaque image de défilement.

Il y a un coût de performance au réglage scrollEventThrottle={16}qui peut être réduit en le définissant sur un nombre plus grand. Cependant, cela signifie que onScrollne déclenchera pas toutes les images.

onMomentumScrollEnd

Se déclenche lorsque la vue de défilement s'arrête après un vol plané. Ne se déclenche pas du tout si l'utilisateur libère la vue de défilement alors qu'elle est stationnaire de sorte qu'elle ne glisse pas.

onScrollEndDrag

Se déclenche lorsque l'utilisateur arrête de faire glisser la vue de défilement, que la vue de défilement soit laissée stationnaire ou qu'elle commence à glisser.


Compte tenu de ces différences de comportement, la meilleure façon de suivre le décalage dépend de vos circonstances précises. Dans le cas le plus compliqué (vous devez prendre en charge Android et iOS, y compris la gestion des changements dans le ScrollViewcadre de s en raison de la rotation, et vous ne voulez pas accepter la pénalité de performances sur Android du paramètre scrollEventThrottleà 16), et vous devez gérer modifications du contenu dans la vue de défilement aussi, alors c'est un sacré désordre.

Le cas le plus simple est si vous avez seulement besoin de gérer Android; utilisez simplement onScroll:

<ScrollView
  onScroll={event => { 
    this.yOffset = event.nativeEvent.contentOffset.y
  }}
>

Pour prendre en charge également iOS, si vous êtes heureux de déclencher le onScrollgestionnaire à chaque image et d'en accepter les implications sur les performances, et si vous n'avez pas besoin de gérer les changements de cadre, alors ce n'est qu'un peu plus compliqué:

<ScrollView
  onScroll={event => { 
    this.yOffset = event.nativeEvent.contentOffset.y
  }}
  scrollEventThrottle={16}
>

Pour réduire la surcharge de performances sur iOS tout en garantissant que nous enregistrons toute position sur laquelle la vue de défilement s'installe, nous pouvons augmenter scrollEventThrottleet fournir en plus un onScrollEndDraggestionnaire:

<ScrollView
  onScroll={event => { 
    this.yOffset = event.nativeEvent.contentOffset.y
  }}
  onScrollEndDrag={event => { 
    this.yOffset = event.nativeEvent.contentOffset.y
  }}
  scrollEventThrottle={160}
>

Mais si nous voulons gérer les changements de cadre (par exemple, parce que nous autorisons la rotation de l'appareil, en changeant la hauteur disponible pour le cadre de la vue de défilement) et / ou les changements de contenu, nous devons également implémenter les deux onContentSizeChangeet onLayoutgarder une trace de la hauteur des deux le cadre de la vue de défilement et son contenu, et ainsi calculer en permanence le décalage maximum possible et déduire quand le décalage a été automatiquement réduit en raison d'un changement de taille de cadre ou de contenu:

<ScrollView
  onLayout={event => {
    this.frameHeight = event.nativeEvent.layout.height;
    const maxOffset = this.contentHeight - this.frameHeight;
    if (maxOffset < this.yOffset) {
      this.yOffset = maxOffset;
    }
  }}
  onContentSizeChange={(contentWidth, contentHeight) => {
    this.contentHeight = contentHeight;
    const maxOffset = this.contentHeight - this.frameHeight;
    if (maxOffset < this.yOffset) {
      this.yOffset = maxOffset;
    }
  }}
  onScroll={event => { 
    this.yOffset = event.nativeEvent.contentOffset.y;
  }}
  onScrollEndDrag={event => { 
    this.yOffset = event.nativeEvent.contentOffset.y;
  }}
  scrollEventThrottle={160}
>

Ouais, c'est assez horrible. Je ne suis pas non plus certain à 100% que cela fonctionnera toujours correctement dans les cas où vous modifiez simultanément la taille du cadre et le contenu de la vue de défilement. Mais c'est le mieux que je puisse proposer, et jusqu'à ce que cette fonctionnalité soit ajoutée dans le cadre lui - même , je pense que c'est le mieux que quiconque puisse faire.

Mark Amery
la source
19

La réponse de Brad Oyler est correcte. Mais vous ne recevrez qu'un seul événement. Si vous avez besoin d'obtenir des mises à jour constantes de la position de défilement, vous devez définir le scrollEventThrottleprop, comme ceci:

<ScrollView onScroll={this.handleScroll} scrollEventThrottle={16} >
  <Text>
    Be like water my friend 
  </Text>
</ScrollView>

Et le gestionnaire d'événements:

handleScroll: function(event: Object) {
  console.log(event.nativeEvent.contentOffset.y);
},

Sachez que vous pouvez rencontrer des problèmes de performances. Réglez l'accélérateur en conséquence. 16 vous donne le plus de mises à jour. 0 un seul.

cutemachine
la source
12
Ceci est une erreur. D'une part, scrollEvenThrottle ne fonctionne que pour iOS et non pour Android. Deuxièmement, il ne règle que la fréquence à laquelle vous recevez des appels onScroll. La valeur par défaut est une fois par image, pas un événement. 1 vous donne plus de mises à jour et 16 vous en donne moins. 0 est la valeur par défaut. Cette réponse est complètement fausse. facebook.github.io/react-native/docs/…
Teodors
1
J'ai répondu à cela avant qu'Android ne soit pris en charge par React Native, donc oui, la réponse s'applique uniquement à iOS. La valeur par défaut est zéro, ce qui entraîne l'envoi de l'événement de défilement une seule fois à chaque défilement de la vue.
cutemachine
1
Quelle serait la notation es6 pour function (event: Object) {}?
cbartondock
1
Juste function(event) { }.
cutemachine
1
@Teodors Bien que cette réponse ne couvre pas Android, le reste de vos critiques est complètement confus. scrollEventThrottle={16}ne pas vous donner des mises à jour « moins »; tout nombre compris entre 1 et 16 vous donne le taux maximum de mises à jour possible. La valeur par défaut de 0 vous donne (sur iOS) un événement lorsque l'utilisateur commence à faire défiler, puis aucune mise à jour ultérieure (ce qui est assez inutile et peut-être un bogue); la valeur par défaut n'est pas "une fois par image". Outre ces deux erreurs dans votre commentaire, vos autres affirmations ne contredisent rien de ce que dit la réponse (même si vous semblez penser qu'elles le font).
Mark Amery
7

Si vous souhaitez simplement obtenir la position actuelle (par exemple, lorsque vous appuyez sur un bouton) plutôt que de la suivre chaque fois que l'utilisateur fait défiler, alors appeler un onScrollauditeur va causer des problèmes de performances. Actuellement, le moyen le plus efficace d'obtenir simplement la position de défilement actuelle consiste à utiliser le package react-native-invoke . Il existe un exemple pour cette chose exacte, mais le package fait plusieurs autres choses.

Lisez à ce sujet ici. https://medium.com/@talkol/invoke-any-native-api-directly-from-pure-javascript-in-react-native-1fb6afcdf57d#.68ls1sopd

Teodors
la source
2
Savez-vous s'il existe un moyen plus propre (un peu moins piraté) de demander un décalage ou est-ce toujours le meilleur moyen de le faire dont vous êtes conscient? (Je me demande pourquoi cette réponse n'est pas votée car c'est la seule qui semble avoir un sens)
cglacet
1
Le colis n'est plus là, at-il déménagé quelque part? Github montre 404.
Noitidart
6

Pour obtenir le x / y après la fin du défilement comme le demandaient les questions d'origine, le moyen le plus simple est probablement le suivant:

<ScrollView
   horizontal={true}
   pagingEnabled={true}
   onMomentumScrollEnd={(event) => { 
      // scroll animation ended
      console.log(e.nativeEvent.contentOffset.x);
      console.log(e.nativeEvent.contentOffset.y);
   }}>
   ...content
</ScrollView>
j0n
la source
-1; onMomentumScrollEndn'est déclenchée que si la vue de défilement a un certain élan lorsque l'utilisateur la lâche. S'ils se relâchent alors que la vue de défilement ne bouge pas, vous n'obtiendrez jamais aucun des événements d'élan.
Mark Amery
1
J'ai testé onMomentumScrollEnd et il m'était impossible de ne pas le déclencher, j'ai essayé de faire défiler de différentes manières, j'ai essayé de relâcher le toucher lorsque le scroll était en position finale et il a également été déclenché. Donc, cette réponse est correcte pour autant que je puisse la tester.
fermmm
6

Les réponses ci-dessus indiquent comment obtenir la position en utilisant différentes API onScroll, onMomentumScrollEndetc. Si vous souhaitez connaître l'index de la page, vous pouvez le calculer à l'aide de la valeur de décalage.

 <ScrollView 
    pagingEnabled={true}
    onMomentumScrollEnd={this._onMomentumScrollEnd}>
    {pages}
 </ScrollView> 

  _onMomentumScrollEnd = ({ nativeEvent }: any) => {
   // the current offset, {x: number, y: number} 
   const position = nativeEvent.contentOffset; 
   // page index 
   const index = Math.round(nativeEvent.contentOffset.x / PAGE_WIDTH);

   if (index !== this.state.currentIndex) {
     // onPageDidChanged
   }
 };

entrez la description de l'image ici

Dans iOS, la relation entre ScrollView et la région visible est la suivante: L'image vient de https://www.objc.io/issues/3-views/scroll-view/

réf: https://www.objc.io/issues/3-views/scroll-view/

RY Zheng
la source
Si vous souhaitez obtenir l '«index» de l'élément actuel, c'est la bonne réponse. Vous prenez event.nativeEvent.contentOffset.x / deviceWidth. Merci de votre aide!
Sara Inés Calderón
1

C'est ainsi que nous avons obtenu la position de défilement actuelle en direct à partir de la vue. Tout ce que je fais, c'est utiliser l'écouteur d'événement on scroll et scrollEventThrottle. Je le passe ensuite comme un événement pour gérer la fonction de défilement que j'ai créée et mettre à jour mes accessoires.

 export default class Anim extends React.Component {
          constructor(props) {
             super(props);
             this.state = {
               yPos: 0,
             };
           }

        handleScroll(event){
            this.setState({
              yPos : event.nativeEvent.contentOffset.y,
            })
        }

        render() {
            return (
          <ScrollView onScroll={this.handleScroll.bind(this)} scrollEventThrottle={16} />
    )
    }
    }
Sabba Keynejad
la source
est-il bon de mettre à jour l'état sur onScroll compte tenu des performances?
Hrishi
1

l'utilisation de onScroll entre en boucle infinie. onMomentumScrollEnd ou onScrollEndDrag peuvent être utilisés à la place

kmehmet
la source
0

En ce qui concerne la page, je travaille sur un composant d'ordre supérieur qui utilise essentiellement les méthodes ci-dessus pour faire exactement cela. En fait, cela prend juste un peu de temps lorsque vous abordez les subtilités telles que la mise en page initiale et les changements de contenu. Je ne prétendrai pas l'avoir fait «correctement», mais dans un certain sens, je considérerais la bonne réponse pour utiliser un composant qui le fait avec soin et cohérence.

Voir: react-native-paged-scroll-view . J'adorerais les commentaires, même si c'est que j'ai tout mal fait!


la source
1
C'est génial. merci d'avoir fait cela. J'ai tout de suite trouvé cela très utile.
Marty Cortez
Tellement heureux! J'apprécie vraiment les commentaires!
1
Une excuse -1, car le projet admet ouvertement qu'il n'est pas maintenu et que le composant a "quelques bugs laids" .
Mark Amery
Merci pour les commentaires @MarkAmery. :) Trouver le temps de l'open source n'est pas facile.
-3

Je crois contentOffsetque vous donnera un objet contenant le décalage de défilement en haut à gauche:

http://facebook.github.io/react-native/docs/scrollview.html#contentoffset

Colin Ramsay
la source
4
N'est-ce pas un setter, pas un getter?
Aaron Wang le
1
-1 c'est un non-sens; la documentation que vous avez liée concerne un accessoire JSX, pas une propriété JavaScript, et ne peut être utilisée que pour définir un décalage, pas pour en obtenir un.
Mark Amery