Nie zezwalaj na przewijanie w poziomie podczas przewijania w pionie (i odwrotnie)

17

Mam listę, z overflow-xoraz overflow-yzestaw do auto. Ponadto skonfigurowałem przewijanie pędu, więc przewijanie dotykowe działa dobrze w urządzeniach mobilnych, przy użyciu webkit-overflow-scrolling: true.

Problem polega jednak na tym, że nie mogę dowiedzieć się, jak wyłączyć przewijanie w poziomie podczas przewijania w pionie. Prowadzi to do naprawdę złych wrażeń dla użytkownika, ponieważ przesuwanie w kierunku lewego górnego rogu lub prawego górnego spowoduje przewijanie tabeli po przekątnej. Gdy użytkownik przewija w pionie, absolutnie NIE chcę, aby przewijało się w poziomie, dopóki użytkownik nie przestanie przewijać w pionie.

Próbowałem następujące:

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;
  }
}

Ale za każdym razem, gdy ustawiam overflow-xlub overflow-ydo hiddenkiedy jest przewijany, przewijanie spowoduje usterkę i przeskoczy z powrotem na górę. Zauważyłem również, że webkit-overflow-scrolling: truejest to powód, dla którego tak się dzieje, kiedy go usuwam, zachowanie wydaje się przestać, ale absolutnie potrzebuję tego do przewijania pędu w urządzeniach mobilnych.

Jak wyłączyć przewijanie w poziomie podczas przewijania w pionie?

Josh O'Connor
źródło
Nie wiem, jak rozwiązać problem z przewijaniem. Może cofnąć się o krok i spróbować zapobiec przewinięciu jednej z dwóch osi? Off-topic: Tabele nie są przeznaczone do naprawdę małych ekranów.
Joep Kockelkorn
W kodzie brakuje nawiasów klamrowych. Jeśli dodasz go jako fragment kodu, zobaczysz komunikat o błędzie w konsoli.
Razvan Zamfir
tylko głupi pomysł. dlaczego nie użyć dwóch suwaków lub niestandardowych suwaków do przewijania?
Thomas Ludewig,
Powiązana koncepcja: stackoverflow.com/questions/24594653/…
James Dunn

Odpowiedzi:

4

Spróbuj tego

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;
  }
}

Zamiast używać jednego div do przewijania, dlaczego nie używasz dwóch? Mają jeden dla X i jeden dla Y

kubek
źródło
1
Świetny pomysł! źle dam temu szansę.
Josh O'Connor,
1
Lol, muszę powiedzieć, że to genialny pomysł.
frodo2975,
15

Generalnie złą praktyką w twoim projekcie jest konieczność przewijania wieloosiowego na telefonie komórkowym, chyba że wyświetlasz duże tabele danych. Biorąc to pod uwagę, dlaczego chcesz temu zapobiec? Jeśli użytkownik chce przewijać po przekątnej, nie wydaje mi się to końcem świata. Niektóre przeglądarki, takie jak Chrome na OSX, już domyślnie robią to, co opisujesz.

Jeśli potrzebujesz przewijania w jednej osi, możliwym rozwiązaniem może być samodzielne śledzenie pozycji przewijania touchstarti touchmovezdarzeń. Jeśli ustawisz próg przeciągania poniżej wartości niższej niż w przeglądarce, możesz być w stanie wykonać css, zanim zacznie się ono przewijać, unikając zauważonej usterki. Ponadto, nawet jeśli nadal się zlewa, masz dotyk dotykowy i jego bieżącą lokalizację. Z nich, jeśli zarejestrujesz początkową pozycję przewijania div, możesz ręcznie przewinąć div do właściwego miejsca, aby przeciwdziałać skokowi na górę, jeśli musisz. Możliwy algorytm może wyglądać następująco:

// 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.

Drugi pomysł: w module obsługi przewijania zamiast zmieniać klasy css, ustaw pozycję przewijania div bezpośrednio dla osi, którą chcesz zablokować. IE, jeśli przewijasz w poziomie, zawsze ustaw scrollTop na jego wartość początkową. Może to również anulować przewijanie, nie jestem pewien. Musisz spróbować, aby sprawdzić, czy to działa.

frodo2975
źródło
Próbowałem tego, ale było bardzo nerwowo, a wydajność była okropna.
Josh O'Connor,
3

Istnieje wiele sposobów rozwiązania tego problemu. Oto kilka dobrych pomysłów.

Jednak, jak nawiązywali inni, poleganie na przewijaniu wielowymiarowym (lub próba uniknięcia) jest nieprzyjemnym zapachem UX - co wskazuje na problem z UX. Nie sądzę, że jest to uzasadniona kwestia deweloperów. Lepiej byłoby zrobić krok do tyłu i ponownie ocenić to, co próbujesz osiągnąć. Jednym z problemów może być to, że w celu uczynienia interfejsu użytkownika bardziej użytecznym będziesz wprowadzać użytkowników w błąd. Opisana tutaj użyteczność spowodowałaby zamieszanie.

Dlaczego nie mogę przewijać w poziomie?

Dlaczego kiedy przestaję przewijać w pionie, tylko wtedy mogę przewijać w poziomie?

Oto niektóre pytania, które można zadać (niesłyszalne).

Jeśli próbujesz zezwolić, aby dodatkowe informacje z pionowej listy danych były możliwe do przeglądania tylko wtedy, gdy użytkownik wybrał wiersz, prawdopodobnie lepiej byłoby po prostu mieć płaską listę domyślnie przewijaną w pionie i tylko przełączać pionową informacja o wybraniu / aktywacji „wiersza”. Zwinięcie szczegółów spowoduje powrót do płaskiej listy pionowej.

Jeśli musisz skakać przez obręcze, aby rozwiązać tak trudne wyzwanie techniczne, to dobra wskazówka, że ​​wrażenia użytkownika nie zostały dobrze zaprojektowane na początek.

Arthur K.
źródło
3

Musisz użyć trzech pojemników.
W pierwszym kontenerze włączam przewijanie w pionie i wyłączam poziomo.
W drugim, odwrotnie, włączam przewijanie w poziomie i nie zezwalam na pionowe. Należy użyć overflow-x: hidden;i overflow-y: hidden;, inaczej pojemniki dziecko może wykraczać poza bieżącym kontenerze.
Trzeba też użyć min-width: 100%; min-height: 100%;.
W przypadku trzeciego kontenera musimy użyć, display: inline-block;a następnie zawartość wewnętrzna go rozciągnie, a odpowiednie paski przewijania pojawią się na dwóch blokach nadrzędnych.

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;
}

Możesz to przetestować tutaj w Safari na iPhonie.

Powodzenia!! 😉

Konstantin Piliugin
źródło
: podniesione ręce:: podniesione ręce:
cup_of 11.10.19
0

to wygląda na zabawę;)

Nie będę się kłócił, czy to rozsądne.

Próbowałem z 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();
  }

Mamy bufor co 4 touchemovezdarzenia, a następnie dokonać pewnych obliczeń wysoce wyrafinowany ze współrzędnymi czterech zdarzeń ( map(calculateDirection)), które wyjścia RIGHT, LEFT, UPczy DOWNi w oparciu o które staram się wyłączyć pionowej lub horicontal przewijania. Na moim telefonie z Androidem w chromie to działa;)

Stworzyłem mały plac zabaw , który można ulepszyć, przepisać, cokolwiek ...

Pozdrawiam Chris

ChrisY
źródło