web-dev-qa-db-fra.com

Ne pas autoriser le défilement horizontal lors du défilement vertical (et vice versa)

J'ai une liste, avec le overflow-x et overflow-y défini sur auto. De plus, j'ai configuré le défilement de momentum, donc le défilement tactile fonctionne bien sur mobile, en utilisant webkit-overflow-scrolling: true.

Le problème, cependant, est que je ne peux pas comprendre comment désactiver le défilement horizontal lors du défilement vertical. Cela conduit à une très mauvaise expérience utilisateur, car le glissement vers le haut à gauche ou le haut à droite entraînera un défilement diagonal du tableau. Lorsque l'utilisateur fait défiler verticalement, je ne veux absolument PAS de défilement horizontal tant que l'utilisateur n'a pas arrêté de défiler verticalement.

J'ai essayé ce qui suit:

JS:

offsetX: number;
offsetY: number;
isScrollingHorizontally = false;
isScrollingVertically = false;

//Detect the scrolling events
ngOnInit() {
    this.scrollListener = this.renderer.listen(
      this.taskRows.nativeElement,
      'scroll',
      evt => {
        this.didScroll();
      }
    );

    fromEvent(this.taskRows.nativeElement, 'scroll')
      .pipe(
        debounceTime(100),
        takeUntil(this.destroy$)
      )
      .subscribe(() => {
        this.endScroll();
    });
}

didScroll() {
    if ((this.taskRows.nativeElement.scrollLeft != this.offsetX) && (!this.isScrollingHorizontally)){
        console.log("Scrolling horizontally")
        this.isScrollingHorizontally = true;
        this.isScrollingVertically = false;
        this.changeDetectorRef.markForCheck();
    }else if ((this.taskRows.nativeElement.scrollTop != this.offsetY) && (!this.isScrollingVertically)) {
        console.log("Scrolling Vertically")
        this.isScrollingHorizontally = false;
        this.isScrollingVertically = true;
        this.changeDetectorRef.markForCheck();
    }
}

endScroll() {
    console.log("Ended scroll")
    this.isScrollingVertically = false;
    this.isScrollingHorizontally = false;
    this.changeDetectorRef.markForCheck();
}

HTML:

<div
    class="cu-dashboard-table__scroll"
    [class.cu-dashboard-table__scroll_disable-x]="isScrollingVertically"
    [class.cu-dashboard-table__scroll_disable-y]="isScrollingHorizontally"
>

CSS:

&__scroll {
  display: flex;
  width: 100%;
  height: 100%;
  overflow-y: auto;
  overflow-x: auto;
  will-change: transform;
  -webkit-overflow-scrolling: touch;

  &_disable-x {
     overflow-x: hidden;
  }

  &_disable-y {
    overflow-y: hidden;
  }
}

Mais à chaque fois que je mets overflow-x ou overflow-y à hidden lorsqu'elle a été défilée, le défilement va pépiner et revenir en haut. J'ai également remarqué que webkit-overflow-scrolling: true est la raison pour laquelle cela se produit, lorsque je le supprime, le comportement semble s'arrêter, mais j'en ai absolument besoin pour faire défiler l'élan sur les appareils mobiles.

Comment désactiver le défilement horizontal lors du défilement vertical?

17
Josh O'Connor

Essaye ça

HTML
<div
  class="cu-dashboard-table__scroll-vertical"
>
  <div
    class="cu-dashboard-table__scroll-horizontal"
  >
    <!-- Scroll content here -->
  </div>
</div>


CSS
&__scroll {
  &-horizontal {
    overflow-x: auto;
    width: 100%;
    -webkit-overflow-scrolling: touch;
  }

  &-vertical {
    overflow-y: auto;
    height: 100%;
    -webkit-overflow-scrolling: touch;
  }
}

Au lieu d'utiliser un div pour le défilement, pourquoi n'en utilisez-vous pas deux? Avoir un pour X et un pour Y

4
cup_of

C'est généralement une mauvaise pratique pour votre conception d'avoir besoin d'un défilement multiaxe sur mobile, à moins que vous ne montriez peut-être de grandes tables de données. Cela étant dit, pourquoi voulez-vous l'empêcher? Si un utilisateur veut faire défiler en diagonale, cela ne me semble pas être la fin du monde. Certains navigateurs, comme Chrome sur OSX, font déjà ce que vous décrivez par défaut.

Si vous devez avoir un défilement sur un seul axe, une solution possible pourrait être de suivre vous-même la position du défilement via les événements touchstart et touchmove. Si vous définissez votre seuil de glissement inférieur à celui du navigateur, vous pourrez peut-être faire vos trucs css avant qu'il ne commence à défiler, en évitant le problème perçu. De plus, même si cela ne fonctionne toujours pas, vous avez le démarrage tactile et l'emplacement actuel du toucher. À partir de ceux-ci, si vous enregistrez la position de défilement de départ de votre div, vous pouvez faire défiler manuellement le div au bon endroit pour contrebalancer le saut vers le haut si vous le devez. Un algorithme possible pourrait ressembler à ceci:

// Touchstart handler
if (scrollState === ScrollState.Default) {
    // Record position and id of touch
    touchStart = touch.location
    touchStartId = touch.id.
    scrollState = ScrollState.Touching

    // If you have to manually scroll the div, first store its starting scroll position:
    containerTop = $('.myContainer').scrollTop();
    containerLeft = $('.myContainer').scrollLeft();
}

// Touchmove handler - If the user has dragged beyond a distance threshold,
// do the css classes swap.
if (touch.id === touchStartId && distance(touchStart, touch.location > threshold) {
    scrollState = ScrollState.Scrolling;
    swapCssClasses();

    // If you have to manually scroll the div to prevent jump:
    $('.myContainer').scrollTop(containerTop + (touch.location.y - touchStart.y));
    // Do the same for horizontal scroll.
}

// Then, listen for debounced scroll events, like you're already doing,
// to reset your state back to default.

Deuxième idée: dans votre gestionnaire de défilement, au lieu de changer les classes css, définissez la position de défilement de la div directement pour l'axe que vous souhaitez verrouiller. IE, si vous faites défiler horizontalement, définissez toujours scrollTop sur sa valeur de départ. Cela peut également annuler le défilement, pas sûr. Vous devriez l'essayer pour voir si cela fonctionne.

15
frodo2975

Besoin d'utiliser trois conteneurs.
Dans le premier conteneur, j'autorise le défilement vertical et l'interdiction horizontale.
Dans la seconde, vice versa, j'active le défilement horizontal et interdit la verticale. Assurez-vous d'utiliser overflow-x: hidden; et overflow-y: hidden;, sinon les conteneurs enfants peuvent dépasser le conteneur actuel.
Vous devez également utiliser min-width: 100%; min-height: 100%;.
Pour le troisième conteneur, nous devons utiliser display: inline-block; puis le contenu interne étirera ce conteneur et les barres de défilement correspondantes apparaîtront sur les deux blocs parents.

HTML

<div class="scroll-y">
  <div class="scroll-x">
    <div class="content-container">

      <!-- scrollable content here -->

    </div>
  </div>
</div>

CSS

.scroll-y {
  position: absolute;
  overflow-x: hidden;
  overflow-y: auto;
  width: 100%;
  height: 100%;
  min-width: 100%;
  min-height: 100%;
}

.scroll-x {
  overflow-y: hidden;
  overflow-x: auto;
  width: auto;
  min-width: 100%;
  min-height: 100%;
}

.content-container {
  min-width: 100%;
  min-height: 100%;
  display: inline-block;
}

Vous pouvez le tester ici dans Safari sur iPhone.

Bonne chance!! ????

3

Il existe de nombreuses façons de résoudre ce problème. Quelques bonnes idées vous sont proposées ici.

Cependant, comme d'autres l'ont fait allusion, s'appuyer sur (ou essayer d'éviter) le défilement multidimensionnel est une mauvaise odeur UX - indiquant que l'UX est un problème. Je ne pense pas que ce soit un problème de développement légitime. Il serait préférable de prendre du recul et de réévaluer ce que vous essayez d'accomplir. L'un des problèmes pourrait être que dans un effort pour rendre l'interface utilisateur plus utilisable, vous confondrez les utilisateurs. La convivialité décrite ici provoquerait probablement de la confusion.

Pourquoi ne puis-je pas faire défiler horizontalement?

Pourquoi quand j'arrête de faire défiler verticalement, alors seulement puis-je faire défiler horizontalement?

Ce sont quelques questions qui peuvent être posées (inaudible).

Si vous essayez de permettre que des informations supplémentaires sur la liste de données verticale ne soient consultables que lorsque l'utilisateur a sélectionné la ligne, il serait probablement beaucoup mieux d'avoir simplement une liste plate par défaut défilante verticalement et de basculer uniquement la verticale informations lorsque la "ligne" a été sélectionnée/activée. Réduire les détails vous ramènerait à la liste verticale plate.

Si vous devez sauter à travers des cerceaux pour résoudre un tel défi technique, c'est une bonne indication que l'expérience utilisateur n'a pas été bien conçue au départ.

3
Arthur K

ça a l'air amusant;)

Je ne discuterai pas si c'est raisonnable.

Je l'ai essayé avec RxJS:

  ngAfterViewInit() {
    const table = document.getElementById("table");

    const touchstart$ = fromEvent(table, "touchstart");
    const touchmove$ = fromEvent(table, "touchmove");
    const touchend$ = fromEvent(table, "touchend");

    touchstart$
      .pipe(
        switchMapTo(
          touchmove$.pipe(
            // tap(console.log),
            map((e: TouchEvent) => ({
              x: e.touches[0].clientX,
              y: e.touches[0].clientY
            })),
            bufferCount(4),
            map(calculateDirection),
            tap(direction => this.setScrollingStyles(direction)),
            takeUntil(touchend$)
          )
        )
      )
      .subscribe();
  }

Nous tamponnons tous les 4 événements touchemove, puis effectuons des calculs très sophistiqués avec les coordonnées des quatre événements (map(calculateDirection)) qui produisent RIGHT, LEFT, UP ou DOWN et sur cette base, j'essaie de désactiver le défilement vertical ou horizontal. Sur mon Android dans chrome cela fonctionne en quelque sorte;)

J'ai créé un peu terrain de je , qui pourrait être amélioré, réécrit, que ce soit ...

Bravo Chris

0
ChrisY