Не разрешать горизонтальную прокрутку при вертикальной прокрутке (и наоборот)

У меня есть список с overflow-x и overflow-y, установленными на auto. Кроме того, я настроил прокрутку импульса, поэтому сенсорная прокрутка работает в мобильном режиме с использованием webkit-overflow-scrolling: true.

Однако проблема в том, что я не могу понять, как отключить горизонтальную прокрутку при вертикальной прокрутке. это приводит к очень плохому пользовательскому опыту, так как перемещение влево или вправо вверх приведет к прокрутке таблицы по диагонали. Когда пользователь выполняет вертикальную прокрутку, я абсолютно НЕ хочу, чтобы прокрутка была горизонтальной, пока пользователь не перестал выполнять вертикальную прокрутку.

Я пробовал следующее:

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

Но каждый раз, когда я устанавливаю overflow-x или overflow-y на hidden, когда их прокручивают, прокрутка будет сбиваться и прыгать обратно наверх. Я также заметил, что webkit-overflow-scrolling: true является причиной, по которой это происходит, когда я удаляю его, поведение кажется остановленным, но мне это абсолютно необходимо для прокрутки импульса на мобильных устройствах.

Как отключить горизонтальную прокрутку при вертикальной прокрутке?

Ответы

Ответ 1

Попробуйте это

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

Вместо того, чтобы использовать один div для прокрутки, почему вы не используете два? Есть один для X и один для Y

Ответ 2

Как правило, для вашего дизайна плохая практика - использовать многоосевую прокрутку на мобильном устройстве, если, возможно, вы не показываете большие таблицы данных. При этом, почему вы хотите предотвратить это? Если пользователь хочет прокрутить по диагонали, это не кажется мне концом света. Некоторые браузеры, такие как Chrome на OSX, уже делают то, что вы описываете по умолчанию.

Если вам нужна прокрутка по одной оси, возможное решение может заключаться в том, чтобы самостоятельно отслеживать положение прокрутки с помощью событий touchstart и touchmove. Если вы установите порог перетаскивания ниже, чем в браузере, вы сможете выполнить свою работу с CSS до того, как она начнет прокручиваться, избегая заметного сбоя. Кроме того, даже если он все еще дает сбой, у вас есть сенсорный старт и текущее местоположение касания. Исходя из этого, если вы запишите свою начальную позицию прокрутки div, вы можете вручную прокрутить div в нужное место, чтобы противодействовать его прыжкам наверх, если это необходимо. Возможный алгоритм может выглядеть следующим образом:

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

Вторая идея: в вашем обработчике прокрутки вместо изменения классов CSS установите позицию прокрутки div для оси, которую вы хотите заблокировать. IE, если вы прокручиваете горизонтально, всегда устанавливайте scrollTop в начальное значение. Это также может отменить прокрутку, не уверен. Вы должны попробовать это, чтобы увидеть, работает ли это.

Ответ 3

Есть множество способов решить эту проблему. Некоторые хорошие идеи предлагаются здесь.

Однако, как уже упоминали другие, полагаться на (или пытаться избегать) многомерную прокрутку - это неприятный запах UX, что указывает на проблему UX. Я не думаю, что это законная проблема разработчиков. Было бы лучше сделать шаг назад и пересмотреть то, что вы пытаетесь достичь. Одной из проблем может быть то, что в целях повышения удобства использования пользовательского интерфейса вы будете вводить пользователей в заблуждение. Описанное здесь удобство использования может вызвать путаницу.

Why can't я scroll horizontally?

Почему, когда я прекращаю прокручивать по вертикали, только тогда я могу прокручивать по горизонтали?

Вот некоторые вопросы, которые можно задать (неразборчиво).

Если вы пытаетесь разрешить просмотр дополнительной информации о вертикальном списке данных только тогда, когда пользователь выбрал строку, было бы гораздо лучше просто иметь плоский список по умолчанию с возможностью прокрутки только по вертикали и только переключением по вертикали. информация, когда "строка" была выбрана/активирована. Свернув детали, вы вернетесь к плоскому вертикальному списку.

Если вам приходится прыгать через обручи, чтобы решить такую незначительную техническую задачу, это хороший признак того, что пользовательский опыт не был разработан с самого начала.

Ответ 4

Нужно использовать три контейнера.
В первом контейнере я включаю вертикальную прокрутку и запрещаю горизонтальную.
Во втором, наоборот, я включаю горизонтальную прокрутку и запрещаю вертикальную. Обязательно используйте overflow-x: hidden; и overflow-y: hidden;, иначе дочерние контейнеры могут выходить за пределы текущего контейнера.
Также необходимо использовать min-width: 100%; min-height: 100%;.
Для третьего контейнера нам нужно использовать display: inline-block;, и тогда внутреннее содержимое будет растягивать этот контейнер, и соответствующие полосы прокрутки появятся на двух родительских блоках.

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

Вы можете проверить это здесь в Safari на iPhone.

Удачи!! 😉

Ответ 5

это выглядит весело;)

Я не буду спорить, если это разумно.

Я попробовал это с 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();
  }

Мы буферизируем каждое 4-е событие touchemove, а затем делаем очень сложные вычисления с координатами четырех событий (map(calculateDirection)), которые выводят RIGHT, LEFT, UP или DOWN и на основе что я пытаюсь отключить вертикальную или горизонтальную прокрутку. На моем телефоне Android в Chrome это вроде работает;)

Я создал маленькую игровую площадку, которую можно улучшить, переписать, что угодно...

Ура Крис

Ответ 6

Почему вы хотите, чтобы пользователь запретил прокрутку по диагонали?

Было бы хорошо, если бы пользователь легко прокручивал в любом направлении, и это заставляло бы его выполнять свои задачи очень быстро

Для идеи прокрутки в одном направлении вы можете попробовать это

Все, что вы можете сделать, это создать пользовательские полосы прокрутки и запретить мышь или сенсорную прокрутку. Если пользователь хочет прокрутить, то ему придется перемещать полосу прокрутки, что может быть сделано только одно направление за раз