垂直滚动时不允许水平滚动(反之亦然)
Posted
技术标签:
【中文标题】垂直滚动时不允许水平滚动(反之亦然)【英文标题】:Don't allow horizontal scroll when scrolling vertically (and vice versa) 【发布时间】:2020-01-31 01:54:07 【问题描述】:我有一个列表,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
是发生这种情况的原因,当我删除它时,行为似乎停止了,但我绝对需要它来在移动设备中进行动量滚动。
垂直滚动时如何禁用水平滚动?
【问题讨论】:
我不知道如何解决滚动问题。也许退后一步并尝试阻止两个轴之一滚动?题外话:表格不适用于非常小的屏幕。 您的代码中缺少一个大括号。如果将其添加为 sn-p,您将在控制台中看到一条错误消息。 只是一个愚蠢的想法。为什么不使用两个滑块或自定义滑块来滚动? 相关概念:***.com/questions/24594653/… 【参考方案1】:如果您的设计需要在移动设备上进行多轴滚动,这通常是一种不好的做法,除非您正在显示大数据表。话虽如此,您为什么要阻止它?如果用户想要对角滚动,那对我来说似乎不是世界末日。某些浏览器(例如 OSX 上的 Chrome)已经默认执行您所描述的操作。
如果您必须进行单轴滚动,一个可能的解决方案可能是通过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 设置为其起始值。这也可能取消滚动,不确定。您必须尝试一下,看看它是否有效。
【讨论】:
我试过这个,但它超级跳动,性能很糟糕。【参考方案2】:试试这个
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 一个
【讨论】:
好主意!我试一试。 大声笑,我不得不说这是一个巧妙的想法。【参考方案3】:需要使用三个容器。
在第一个容器中,我启用垂直滚动并禁止水平滚动。
在第二个中,反之亦然,我启用水平滚动并禁止垂直滚动。
请务必使用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 中测试 here。
祝你好运!! ?
【讨论】:
:举手::举手:【参考方案4】:有很多方法可以尝试解决这个问题。这里提供了一些好主意。
然而,正如其他人所暗示的那样,依赖(或试图避免)多维滚动是一种糟糕的 UX 气味 - 表明 UX 存在问题。我不认为这是一个合法的开发问题。最好退后一步,重新评估你想要完成的事情。其中一个问题可能是,为了使 UI 更易于使用,您会混淆用户。此处描述的可用性可能会引起混淆。
为什么我不能水平滚动?
为什么当我停止垂直滚动时,我才能水平滚动?
这些是可能会被问到的一些问题(听不清)。
如果您试图让垂直数据列表的其他信息仅在用户选择了行时才可浏览,那么在默认情况下简单地设置一个仅可垂直滚动的平面列表可能会好得多,并且仅选择/激活“行”时切换垂直信息。折叠细节会让你回到平面垂直列表。
如果您不得不费尽心思来解决这样一个边缘技术挑战,这很好地表明用户体验一开始就没有设计好。
【讨论】:
【参考方案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
事件缓冲一次,然后使用输出 RIGHT
、LEFT
、UP
或 DOWN
的四个事件 (map(calculateDirection)
) 的坐标进行一些高度复杂的计算,并基于我尝试禁用垂直或水平滚动。在我的 chrome 安卓手机上,它可以工作;)
我创建了一个小playground,可以对其进行增强、重写,等等...
克里斯干杯
【讨论】:
以上是关于垂直滚动时不允许水平滚动(反之亦然)的主要内容,如果未能解决你的问题,请参考以下文章