当垂直滚动时不要允许水平滚动(反之亦然)

24
我有一个列表,其中 overflow-xoverflow-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-yhidden时,页面已经滚动的部分会出现跳动的情况并回到页面顶部。我也注意到这是由于webkit-overflow-scrolling: true造成的,如果我去掉它,这种行为似乎就不再发生了,但是在移动设备上,我绝对需要它来进行动量滚动。

如何在垂直滚动时禁用水平滚动?


我不知道如何修复滚动问题。也许可以退一步,尝试防止其中一个轴向任何方向滚动?离题:表格不适用于非常小的屏幕。 - Joep Kockelkorn
你的代码中有一个丢失的大括号。如果你将它添加为片段,你会在控制台看到一个错误消息。 - Razvan Zamfir
只是一个愚蠢的想法。为什么不使用两个滑块或自定义滑块来滚动? - Thomas Ludewig
相关概念:https://dev59.com/_IHba4cB1Zd3GeqPO0N1 - James Dunn
5个回答

16

一般来说,在移动设备上需要多轴滚动的设计做法不好,除非您正在显示大量数据表格。话虽如此,为什么您要阻止它呢?如果用户想要对角线滚动,这对我来说似乎并不是世界末日。一些浏览器(例如OSX上的Chrome)已经默认执行您所描述的操作。

如果你必须使用单轴滚动,一个可能的解决方案是通过touchstarttouchmove事件自己跟踪滚动位置。如果您将您的拖动阈值设置得低于浏览器的阈值,您可能能够在它开始滚动之前完成css样式,并避免出现感知上的故障。此外,即使它仍然会出现故障,您也有触摸起始点和触摸当前位置。从这些信息中,如果您记录了您的

的起始滚动位置,您可以手动向下滚动
以使其跳转到正确的位置。一个可能的算法可能是这样的:

// 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的滚动位置。例如,如果您正在水平滚动,则始终将scrollTop设置为其起始值。这可能也会取消滚动,不确定。您必须尝试一下看是否有效。


我尝试过这个,但是它非常卡顿,性能很差。 - Josh O'Connor

5

试试这个

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轴,而不是只使用一个?


1
好主意!我会试一试。 - Josh O'Connor
1
哈哈,我不得不说这是一个巧妙的想法。 - frodo2975

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

您可以在iPhone上的Safari中这里进行测试。
祝您好运!

:raised hands: :raised hands: - cup_of

3

有许多方法可以尝试解决这个问题。这里提供了一些好的想法。

然而,正如其他人所暗示的那样,依赖于(或试图避免)多维滚动是一个糟糕的UX特征 - 表明UX存在问题。我认为这不是一个合法的开发问题。最好退一步重新评估你要完成的任务。其中一个问题可能是,在努力使UI更易用的过程中,你会让用户感到困惑。这里描述的可用性可能会导致混淆。

为什么我不能水平滚动?

为什么当我停止垂直滚动时,才可以水平滚动?

这些是可能会被问到的问题(听不清)。

如果你正在尝试允许在用户选择行时浏览垂直数据列表的附加信息,那么通常最好只有默认情况下具有垂直滚动功能的平面列表,并且仅在“行”已被选择/激活后切换垂直信息。折叠详细信息将使您回到平面垂直列表。

如果你必须跳过许多障碍来解决这样一个边缘技术挑战,那么这表明用户体验一开始就没有被很好地设计。


0

这看起来很有趣 ;)

我不会争论它是否合理。

我用 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();
  }

我们会缓存每第四个touchemove事件,然后对这四个事件的坐标进行高度复杂的计算(map(calculateDirection)),输出RIGHTLEFTUPDOWN,并基于此尝试禁用垂直或水平滚动。在我的安卓手机上,在Chrome中它有点起作用;)

我创建了一个小playground,可以增强、重写等等...

祝好 Chris


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接