>  기사  >  웹 프론트엔드  >  JS를 사용하여 여러 스크롤 막대를 동기식으로 스크롤하도록 제어하는 ​​방법

JS를 사용하여 여러 스크롤 막대를 동기식으로 스크롤하도록 제어하는 ​​방법

亚连
亚连원래의
2018-06-15 23:30:114178검색

이 글에서 다룰 내용은 두 컨테이너 요소의 내용이 컨테이너의 높이를 초과하는 경우, 즉 스크롤 상자가 나타날 때 컨테이너 요소 중 하나가 스크롤될 때 다른 요소를 스크롤하도록 하는 방법입니다

markdown을 사용하여 기사 작성을 지원하는 일부 웹사이트에서는 배경 쓰기 페이지가 일반적으로 markdown 실시간 미리보기를 지원합니다. 즉, 전체 페이지가 두 부분으로 나누어지고, 왼쪽 절반은 당신을 위한 것입니다. 입력된 markdown 텍스트의 오른쪽 절반은 해당 미리보기 페이지에 즉시 출력됩니다. 예를 들어 다음은 markdown 즉시 미리보기 효과입니다. CSDN 백엔드 작성 페이지: markdown 写文章的网站,后台写作页面,一般都是支持 markdown 即时预览的,也就是将整个页面分成两部分,左半部分是你输入的 markdown 文字,右半部分则即时输出对应的预览页面,例如下面就是 CSDN 后台写作页面的 markdown 即时预览效果:

本文不是阐述如何从 0 实现这种效果的(后续 很可能 会单出文章,),抛开其他,单看页面主体中左右两个容器元素,即 markdown 输入框元素和预览显示框元素

本文要探讨的是,当这两个容器元素的内容都超出了容器高度,即都出现了滚动框的时候,如何在其中一个容器元素滚动时,让另外一个元素也随之滚动。

DOM 结构

既然是与滚动条有关,那么首先想到 js 中控制滚动条高度的一个属性: scrollTop ,只要能控制这个属性的值,自然也就能控制滚动条的滚动了。

对于以下 DOM 结构:

<p id="container">
 <p class="left"></p>
 <p class="right"></p>
</p>

其中, .left 元素是左半部分输入框容器元素, .right 元素是右半部分显示框容器元素, .container 是它们共同的父元素。

由于需要溢出滚动,所以还需要设置一下对应的样式(只是关键样式,非全部):

#container {
 display: flex;
 border: 1px solid #bbb;
}
.left, .right {
 flex: 1;
 height: 100%;
 word-wrap: break-word;
 overflow-y: scroll;
}

再向 .left.right 元素中塞入足够的内容,让二者出现滚动条,就是下面这种效果:

样式是出来个大概了,下面就可以在这些 DOM 上进行一系列的操作了。

初次尝试

大致思路,监听两个容器元素的滚动事件,在其中一个元素滚动的时候,获取这个元素的 scrollTop 属性的值,同时将此值设置为另外一个滚动元素的 scrollTop 值即可。

例如:

var l=document.querySelector(&#39;.left&#39;)
var r=document.querySelector(&#39;.right&#39;)
l.addEventListener(&#39;scroll&#39;,function(){
 r.scrollTop = l.scrollTop
})

效果如下:

似乎很不错,但是现在是不仅想让右边跟随左边滚动,还想左边跟随右边滚动,于是再加以下代码:

addEventListener(&#39;scroll&#39;,function(){
 r.scrollTop = l.scrollTop
})

看上去很不错,然而,哪有那么简单的事情。

这个时候你再用鼠标滚轮进行滚动的时候,却发现滚动得有点吃力,两个容器元素的滚动似乎被什么阻碍住了,很难滚动。

仔细分析,原因很简单,当你在左边滚动的时候,触发了左边的滚动事件,于是右边跟随滚动,但是与此同时右边的跟随滚动也是滚动,于是也触发了右边的滚动,于是左边也要跟随右边滚动...然后就进入了一个类似于相互触发的情况,所以就会发现滚动得很吃力。

解决 scroll 事件同时触发的问题

想要解决上述问题,暂时有以下两种方案。

将 scroll 事件换成 mousewheel 事件

由于 scroll 事件不仅会被鼠标主动滚动触发,同时改变容器元素的 scrollTop 也会触发,元素的主动滚动其实就是鼠标滚轮触发的,所以可以将 scroll 事件换成一个对鼠标滚动敏感而不是元素滚动敏感的事件:'mousewheel',于是上述监听代码变成了:

addEventListener(&#39;mousewheel&#39;,function(){
  r.scrollTop = l.scrollTop
})
r.addEventListener(&#39;mousewheel&#39;,function(){
  l.scrollTop = r.scrollTop
})

效果如下:

似乎是有点用,但是实际上还有两个问题。

当滚动其中一个容器元素的时候,另外一个容器元素虽然也跟着滚动,但滚动得并不流畅,高度有明显的瞬间弹跳

在网上找了一圈,没有找到关于 wheel 事件滚动频率相关内容,我推测这可能就是此事件的一个 feature

🎜이 글에서는 0부터 시작하는 방법을 설명하지 않습니다. 이 효과를 얻으려면 (향후 별도의 글이 가능 있을 것입니다) ), 다른 것들은 제쳐두고 페이지 본문의 왼쪽과 오른쪽에 있는 두 개의 컨테이너 요소, 즉 markdown 입력 상자 요소와 미리보기 표시 상자 요소를 살펴보세요 🎜🎜이 기사의 내용 이 두 컨테이너 요소의 내용이 컨테이너 높이를 초과하는 경우, 즉 스크롤 상자가 나타날 때 컨테이너 요소 중 하나에 스크롤 상자를 추가하는 방법은 스크롤할 때 다른 요소도 함께 스크롤되도록 하는 것입니다. 🎜🎜DOM 구조🎜🎜스크롤 바와 관련이 있기 때문에 가장 먼저 생각해야 할 것은 js 높이 속성: scrollTop. 이 속성의 값을 제어할 수 있으면 자연스럽게 스크롤 막대의 스크롤을 제어할 수 있습니다. 🎜🎜다음 DOM 구조의 경우: 🎜
if (e.clientX>l.left && e.clientX<l.right && e.clientY>l.top) {
  // 进入 .left元素中
}
🎜그 중 .left 요소는 왼쪽 절반 입력 상자 컨테이너 요소이고 .right code> 요소는 표시 상자 컨테이너 요소의 오른쪽 절반이고, <code>.container는 공통 상위 요소입니다. 🎜🎜오버플로 스크롤이 필요하므로 해당 스타일도 설정해야 합니다(전체가 아닌 주요 스타일만). 🎜
addEventListener(&#39;mouseover&#39;,function(){
 // 进入 .left滚动容器元素内
})
🎜 그런 다음 .left.right로 이동합니다. code> 요소 스크롤 막대가 양쪽에 나타나도록 충분한 콘텐츠를 삽입합니다. 이는 다음과 같은 효과입니다. 🎜<p style="text-align: center"><img alt="" src="https://img.php%20.cn%20/upload/article/000/000/008/8dadbca1e66cb21bdf380e2ca3783ea5-1.png">🎜🎜스타일은 대략 파악되었습니다. 이제 이러한 <code>DOM에 대해 일련의 작업을 수행할 수 있습니다. 🎜🎜첫 번째 시도🎜🎜일반적인 아이디어는 두 컨테이너 요소의 스크롤 이벤트를 수신하고 다음 중 하나가 발생할 때 이를 가져오는 것입니다. 요소의 scrollTop 속성 값을 스크롤하고 이 값을 다른 스크롤 요소의 scrollTop 값으로 설정합니다. 🎜🎜예: 🎜
<p id="container">
 <p class="left">
	 <p class="child"></p>
 </p>
 <p class="right">
	 <p class="child"></p>
 </p>
</p>
🎜효과는 다음과 같습니다.🎜

🎜🎜아주 좋은 것 같은데 이제는 오른쪽도 왼쪽으로 스크롤하고 싶고 왼쪽도 오른쪽으로 스크롤하고 싶은데, 그래서 다음 코드를 추가했습니다. 🎜

滚动条的最大滚动高度(scrollTopMax) = 滚动容器内容的高度(即子元素高度ch) - 滚动容器本身的高度(即容器元素高度ph)
🎜매우 좋아 보이지만 그렇게 간단한 것은 없습니다. 🎜🎜이때, 마우스 휠을 사용해 스크롤을 하면 스크롤이 조금 어렵다는 것을 알게 됩니다. 두 컨테이너 요소의 스크롤이 뭔가에 의해 막혀서 스크롤이 어려운 것 같습니다. 🎜🎜잘 분석해 보면 그 이유는 매우 간단합니다. 왼쪽으로 스크롤하면 왼쪽의 스크롤 이벤트가 발생하므로 오른쪽도 스크롤을 따르지만 동시에 오른쪽의 다음 스크롤도 스크롤됩니다. 그래서 오른쪽의 스크롤도 촉발되고, 왼쪽도 오른쪽의 스크롤을 따라가는데... 그러다가 상호트리거와 비슷한 상황에 들어가게 되니 스크롤이 매우 어렵다는 것을 알게 될 것입니다. 🎜🎜스크롤 이벤트가 동시에 발생하는 문제 해결🎜🎜위 문제를 해결하기 위한 해결 방법은 현재 두 가지가 있습니다. 🎜🎜스크롤 이벤트를 마우스휠 이벤트로 교체🎜🎜스크롤 이벤트는 활성 마우스에 의해서만 트리거되는 것이 아니기 때문입니다. 스크롤하고 컨테이너 요소의 scrollTop을 변경하면 요소의 활성 스크롤도 실제로 마우스 휠에 의해 트리거되므로 scroll 이벤트를 다음으로 대체할 수 있습니다. 요소 스크롤에 민감한 이벤트인 'mousewheel' 대신 마우스 스크롤에 민감한 이벤트이므로 위의 청취 코드는 다음과 같습니다. 🎜rrreee🎜효과는 다음과 같습니다: 🎜

🎜🎜 다소 유용할 것 같지만 실제로는 두 가지 문제가 있습니다. 🎜🎜컨테이너 요소 중 하나를 스크롤하면 다른 컨테이너 요소도 스크롤되는데 스크롤이 부드럽지 않고 높이가 순간적으로 바운스되는 현상이 눈에 띕니다🎜🎜인터넷을 검색해봐도 wheel에 대한 내용은 없습니다 / code> 이벤트 스크롤 빈도 관련 콘텐츠, 이것이 이 이벤트의 <code>기능🎜일 수 있다고 추측합니다.

鼠标每次滚动基本上都并不是以 1px 为单位的,其最小单元远比 scroll 事件小的多,我用我的鼠标在 chrome 浏览器上滚动,每次滚过的距离都恰好是 100px ,不同的鼠标或者浏览器这个数值应该都是不一样的,而 wheel 事件其实真正监听的是鼠标滚轮滚过一个齿轮卡点的事件,这也就能解释为何会出现弹跳的现象了。

一般来说,鼠标滚轮每滚过一个齿轮卡点,就能监听到一个 wheel 事件,从开始到结束,被鼠标主动滚动的元素已经滚动了 100px ,所以另外一个跟随滚动的容器元素也就瞬间跳动了 100px

而之所以上述 scroll 事件不会让跟随滚动元素出现瞬间弹跳,则是因为跟随滚动元素每次 scrollTop 发生变化时,其值不会有 100px 那么大的跨度,可能也没有小到 1px ,但由于其触发频率高,滚动跨度小,最起码在视觉上就是平滑滚动的了。

wheel 只是监听鼠标滚轮事件,但如果是用鼠标拖动滚动条,就不会触发此事件,另外的容器元素也就不会跟随滚动了

这个其实很好解决,用鼠标拖动滚动条肯定是能触发 scroll 事件的,而在这种情况下,你肯定能够很轻易地判断出这个被拖动的滚动条是属于哪个容器元素的,只需要处理这个容器的滚动事件,另外一个跟随滚动容器的滚动事件不做处理即可。

wheel 事件的兼容问题

wheel 事件是 DOM Level3 的标准事件,但是除了此事件之外,还有很多非标准事件,不同的浏览器内核使用不同的标准,所以可能还需要按情况来进行兼容,具体可见MDN MouseWheelEvent

实时判断

如果你难以忍受 wheel 的弹跳,以及各种兼容,那么其实还有另外的路可以走得通,依旧是 scroll 事件,只不过需要做一些额外的工作。

scroll 事件的问题在于,没有判断当前主动滚动的是哪一个容器元素,只要确定了主动滚动的容器元素,这事就好办了,例如上述使用 wheel 事件中,用鼠标拖动滚动条之所以能够使用 scroll 事件,就是因为能够很容易地确定当前主动滚动容器元素是哪一个。

所以,问题的关键在于,如何判断出当前主动滚动的容器元素,只要解决了这个问题,剩下的就很好办了。

不论是鼠标滚轮滚动还是鼠标按在滚动条上拖动滚动条滚动,都会触发 scroll 事件,并且这个时候,在坐标系 Z 轴上,鼠标的坐标肯定是位于滚动容器元素所占的面积之内的,也就是说,在 Z 轴上,鼠标肯定是悬浮或者位于滚动容器元素之上。

鼠标在屏幕上移动的时候,是可以获取到鼠标当前坐标的。

其中, clientXclientY 就是当前鼠标相对于视口的坐标,可以认为,只要这个坐标在某个滚动容器的范围内,则认为这个容器元素就是主动滚动容器元素,容器元素的坐标范围可以使用 getBoundingClientRect 进行获取。

下面是鼠标移动到 .left 元素中的示例代码:

if (e.clientX>l.left && e.clientX<l.right && e.clientY>l.top) {
  // 进入 .left元素中
}

这样确实是可以的,不过考虑到两个滚动容器元素几乎占据了整个屏幕面积,所以 mousemove 所要监听的面积未免有点大,对于性能可能要求较高,所以其实可以换成 mouseover 事件,只需要监听鼠标有没有进入到某个滚动容器元素即可,也省去上述的坐标判断了。

addEventListener(&#39;mouseover&#39;,function(){
 // 进入 .left滚动容器元素内
})

当确定了鼠标主动滚动的容器元素是哪一个时,只需要处理这个容器的滚动事件,另外一个跟随滚动容器的滚动事件不做处理即可。

嗯,效果很不错,性能也很好, perfect ,可以收工喽~

按比例滚动

上述示例全部是在两个滚动容器元素的内容高度完全一致的情况下的效果,如果这两个滚动容器元素的内容高度不同呢?

那就是下面这种效果:

可见,由于两个滚动容器元素的内容高度不同,所以最大的 scrollTop 也就不同,就会出现当其中一个 scrollTop 值较小的元素滚到底时,另外一个元素还停留在一半,或者当其中一个 scrollTop 值较大的元素才滚到一半时,另外一个元素就已经滚到底了。

这种情况很常见,例如你用 markdown 写作时,一个一级标题标记 # 在编辑模式下占用的高度,一般都是小于预览模式占用的高度的,这样就出现了左右两侧滚动高度不一致的情况。

所以,如果将这种情况也考虑进来的话,那么就不能简单地为两个滚动容器元素相互设置 scrollTop 值那么简单。

虽然无法固定住滚动容器内容的高度,但是有一点可以确定,滚动条最大滚动高度,或者说 scrollTop 的值,肯定是与滚动容器内容的高度与滚动容器本身的高度呈一定的关系。

由于需要知道滚动容器内容的高度,还要存在滚动条,所以需要给此容器元素加个子元素,子元素高度不限,就是滚动容器内容的高度,容器高度固定,溢出滚动即可。

<p id="container">
 <p class="left">
	 <p class="child"></p>
 </p>
 <p class="right">
	 <p class="child"></p>
 </p>
</p>

结构示例如下:

通过我的观察推论与实践验证,已经确定下来了它们之间的关系,很简单,就是最基本的加减法运算:

滚动条的最大滚动高度(scrollTopMax) = 滚动容器内容的高度(即子元素高度ch) - 滚动容器本身的高度(即容器元素高度ph)

也就是说,如果已经确定了滚动容器内容的高度(即子元素高度ch)与滚动容器本身的高度(即容器元素高度ph),那么就一定能确定滚动条的最大滚动高度( scrollTop ),而这两个高度值基本上都是可以获取到的,所以就能得到 scrollTop

因此,想要让两个滚动元素容器等比例上下滚动,即其中一个元素滚到头或者滚到底,另外一个元素也能对应滚到头和滚到底,那么只要得到这两个滚动容器元素之间的 scrollTop 最大值的比例( scale )就行了。

确定了 scale 之后,实时滚动时,只需要获取主动滚动容器元素的 scrollTop1 ,就能得到另外一个跟随滚动的容器元素对应的 scrollTop2

思路弄清晰了,写代码就是很容易的事情了,效果如下:

很顺滑~

小结

上述本上已经实现了需求,可能在实践过程中还需要根据实际情况来进行一定的修改,例如如果你编写一个 markdown 的在线编辑和预览页面,就需要根据输入内容的高度实时更新 scale 值,不过主体已经搞定,小修小改就没什么难度了。

另外,本文所述不仅是针对两个滚动容器元素的跟随滚动,同时也可扩展开来,更多的元素间的跟随滚动都是可以根据本文思路来实现的,本文只是为了方便讲解而具体到了两个元素上。

以上就是我整理的文字,希望对大家有帮助

相关文章:

在JS中如何实现十字坐标跟随鼠标效果

在jQuery中如何使用EasyUI window窗口

在Angular4.0中如何使用laydate.js日期插件

위 내용은 JS를 사용하여 여러 스크롤 막대를 동기식으로 스크롤하도록 제어하는 ​​방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.