English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Native JS Control Multiple Scrollbars to Sync Follow Scroll Effect

在一些支持用 markdown 写文章的网站,后台写作页面,一般都是支持 markdown 即时预览的,也就是将整个页面分成两部分,左半部分是你输入的 markdown 文字,右半部分则即时输出对应的预览页面,例如下面就是 CSDN 后台写作页面的 markdown 即时预览效果:

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

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

DOM 结构

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

Für die folgende DOM-Struktur:

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

Dabei ist .left das linke Teil des Eingabefeldcontainerelements, .right das rechte Teil des Anzeigefeldcontainerelements, und .container ist der gemeinsame Übereltern.

Da eine Überschuss-Scrolling erforderlich ist, müssen auch die entsprechenden Stile eingestellt werden (nur die wichtigsten Stile, nicht alle):

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

Fügen Sie dann ausreichend Inhalt in die Elemente .left und .right ein, damit beide Scrollbalken erscheinen, wie folgt:

Das Styling ist etwa fertig, und jetzt können auf diesen DOM-Elementen eine Reihe von Operationen durchgeführt werden.

Erster Versuch

Grundsätzliche Idee: Überwachen Sie die Scroll-Events der beiden Containerelemente. Wenn eines der Elemente gescrollt wird, wird der Wert der scrollTop-Eigenschaft dieses Elements abgerufen und gleichzeitig auf den scrollTop-Wert des anderen Scroll-Elements gesetzt.

Beispiel:

var l = document.querySelector('.left')
var r = document.querySelector('.right')
l.addEventListener('scroll', function() {
 r.scrollTop = l.scrollTop
)

Das Ergebnis ist wie folgt:

Es scheint gut zu sein, aber jetzt möchte ich nicht nur, dass das rechte Element mit dem linken Element scrollt, sondern auch das linke Element mit dem rechten Element. Daher wird der folgende Code hinzugefügt:

addEventListener('scroll', function() {
 r.scrollTop = l.scrollTop
)

Es scheint sehr gut zu sein, aber so einfach ist es nicht.

Wenn Sie in diesem Moment mit der Mausradrolle scrollen, bemerken Sie, dass das Scrolling etwas anstrengend ist, und es scheint, als ob die Rollen der beiden Containerelemente durch etwas behindert werden, was das Scrolling schwierig macht.

Eine sorgfältige Analyse zeigt, dass der Grund einfach ist: Wenn Sie nach links scrollen, wird das linke Scroll-Event ausgelöst, und das rechte Element folgt der Bewegung. Gleichzeitig wird aber auch das rechte Element gescrollt, was wiederum das rechte Scroll-Event auslöst. Daher muss das linke Element ebenfalls mit dem rechten Element scrolle. Dies führt zu einer Art gegenseitiger Auslösung, daher scheint das Scrolling sehr anstrengend zu sein.

Lösung des Problems, dass die Scroll-Events gleichzeitig ausgelöst werden

Um die oben genannten Probleme zu lösen, gibt es derzeit zwei vorläufige Lösungen.

scroll-Event durch mousewheel-Ereignis ersetzen

Da das scroll-Event sowohl durch das aktive Scrollen der Maus als auch durch die Änderung des scrollTop des Containerelements ausgelöst wird, ist das aktive Scrollen des Elements eigentlich durch das Mausradereignis ausgelöst, daher kann das scroll-Event durch ein Ereignis ersetzt werden, das auf die Mausradbewegung sensibel ist, anstatt auf das Elementscrollen sensibel zu sein: 'mousewheel', daher wird der obige Überwachungscode in:

addEventListener('mousewheel',function(){
  r.scrollTop = l.scrollTop
)
r.addEventListener('mousewheel',function(){
  l.scrollTop = r.scrollTop
)

Das Ergebnis ist wie folgt:

Es scheint nützlich zu sein, aber es gibt tatsächlich zwei Probleme.

Wenn Sie einen Containerelemente rollen, rollt der andere Containerelement auch mit, rollt aber nicht flüssig, die Höhe hat eine offensichtliche plötzliche Sprungbewegung.

Nach einem Umfeldsuchen im Internet habe ich keine Informationen über die Frequenz des wheel-Ereignisses gefunden, ich vermute, das ist eine Eigenschaft dieses Ereignisses.

Die Mausrollung jedes Mal ist nicht unbedingt mit 1px als Einheit, deren kleinstes Element viel kleiner ist als das scroll-Event, ich habe mit meiner Maus im Chrome-Browser gescrollt, und der Abstand, den ich jedes Mal gescrollt habe, war genau. 100px, verschiedene Mäuse oder Browser sollten diesen Wert unterschiedlich sein, und das wheel-Ereignis hört wirklich auf das Ereignis, dass die Mausradkardanpunkte durchgelaufen werden, das auch erklären kann, warum das Springen auftritt.

Im Allgemeinen kann man hören, dass ein wheel-Ereignis jedes Mal ausgelöst wird, wenn die Mausradkardanpunkte durchgelaufen werden, von Anfang bis Ende hat das durch die Maus aktiv gescrollte Element bereits eine bestimmte Entfernung gescrollt. 100px, daher springt der andere mitfolgende Scrollcontainer auch sofort. 100px

Warum das oben genannte scroll-Event nicht dazu führt, dass der mitfolgende Rollelement plötzlich springt, liegt daran, dass der Wert des scrollTop jedes Mal, wenn sich der mitfolgende Rollcontainer ändert, nicht hat. 100px eine so große Spanne, möglicherweise auch nicht so klein. 1px, aber aufgrund seiner hohen Auslösefrequenz und kleinen Rollweite ist es zumindest optisch glatt gerollt.

wheel überwacht nur das Mausradereignis, aber wenn Sie den Scrollbalken mit der Maus ziehen, wird dieses Ereignis nicht ausgelöst, und der andere Container wird ebenfalls nicht mitgerollt.

Dies ist in der Tat leicht zu lösen, die scrollen Sie mit der Maus den Scrollbalken, wird sicherlich das scroll-Event auslösen, und in diesem Fall können Sie leicht bestimmen, zu welchem Container-Element der bewegte Scrollbalken gehört, indem Sie nur die Scrollereignisse dieses Containers behandeln, die der mitfolgende Scrollcontainer wird nicht behandelt.

Kompatibilitätsprobleme des wheel-Ereignisses

wheel-Ereignis ist DOM Level3 Standardereignisse, aber außer diesem Ereignis gibt es viele nicht-standardisierte Ereignisse, die von verschiedenen Browserkernen unterschiedlich implementiert werden, daher möglicherweise ist eine Anpassung nach Bedarf erforderlich, siehe MDN MouseWheelEvent

Realzeit-Erkennung

Wenn man die Sprungeffekte von wheel und die verschiedenen Kompatibilitätsprobleme nicht ertragen kann, gibt es tatsächlich eine andere Möglichkeit, die ebenfalls funktioniert, nämlich das scroll-Ereignis, aber es müssen zusätzliche Arbeiten erledigt werden.

Das Problem mit dem scroll-Ereignis besteht darin, dass nicht bestimmt wird, welcher Container elementar aktiv scrollt. Sobald der aktive Scroll-Container bestimmt ist, ist dies leicht zu bewältigen, zum Beispiel wurde die Verwendung des wheel-Ereignisses ermöglicht, weil es einfach war, den aktiven Scroll-Container zu bestimmen.

Der Schlüssel zur Frage liegt also darin, wie man den aktuellen aktiven Scroll-Container-Element erkennen kann. Sobald dieses Problem gelöst ist, ist alles andere leicht zu bewältigen.

Egal, ob die Mausrolle gerollt wird oder die Maus auf der Scrollleiste gezogen wird, es wird das scroll-Ereignis ausgelöst, und in diesem Moment liegt die Mauskoordinate sicherlich im Bereich des Scroll-Container-Elements auf der Achse Z, das bedeutet, dass die Maus entweder schwebt oder sich über dem Scroll-Container-Element befindet.

Wenn die Maus auf dem Bildschirm bewegt wird, können die aktuellen Koordinaten der Maus abgerufen werden.

Dabei sind clientX und clientY die aktuellen Koordinaten der Maus im Verhältnis zum Browserfenster. Man kann annehmen, dass diese Koordinaten im Bereich eines bestimmten Scroll-Containers liegen, wenn man diesen Container als aktiven Scroll-Container ansieht. Der Koordinatenbereich des Containers kann mit getBoundingClientRect abgerufen werden.

Hier ist ein Beispielcode, wenn die Maus auf das .left-Element bewegt wird:

if (e.clientX>l.left && e.clientX<l.right && e.clientY>l.top) {
  // Eingang in das .left-Element
}

Das ist tatsächlich möglich, aber da zwei Scroll-Container-Elemente fast die gesamte Bildschirmfläche einnehmen, ist die Fläche, die mousemove zu überwachen hat, zu groß, was möglicherweise eine höhere Performance-Anforderung mit sich bringt. Daher könnte man die mouseover-Ereignisse verwenden, um nur zu überwachen, ob die Maus in einen bestimmten Scroll-Container-Element hineingekommen ist, was auch die obige Koordinatenbestimmung spart.

addEventListener('mouseover',function(){
 // 进入 .left滚动容器元素内
)

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

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

按比例滚动

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

那就是下面这种效果:

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

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

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

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

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

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

结构示例如下:

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

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

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

Daher, um sicherzustellen, dass zwei Scroll-Elemente Container proportional nach oben und nach unten rollen, d.h. wenn eines der Elemente nach oben oder unten rollt, das andere Element auch in die gleiche Richtung rollt, dann müssen Sie nur das Verhältnis der größeren scrollTop-Werte der beiden Scrollcontainer-Elemente (Scale) erhalten

Nachdem der Scale festgelegt wurde, müssen Sie beim Echtzeit-Scrollen nur den scrollTop des aktiven Scrollcontainer-Elements abrufen1 , um die entsprechenden scrollTop eines anderen followenden Scrollcontainer-Elements zu erhalten2 :

Wenn die Gedanken klar sind, ist es einfach, Code zu schreiben. Das Ergebnis ist wie folgt:

Sehr glatt~

Kurz zusammengefasst

Die Anforderungen sind bereits auf diesem Niveau erfüllt. Möglicherweise müssen Sie während des praktischen Prozesses bestimmte Anpassungen an den tatsächlichen Bedingungen vornehmen. Zum Beispiel, wenn Sie eine Online-Editor- und Vorschau-Seite für Markdown schreiben, müssen Sie den Scale-Wert in Echtzeit aktualisieren, basierend auf der Höhe des Eingabeinhalts. Der Hauptteil ist jedoch fertig und kleine Änderungen sind nicht schwierig.

Die in diesem Artikel beschriebenen sind nicht nur für zwei followende Scrollcontainer-Elemente, sondern können auch erweitert werden. Die followenden Scrollbalken zwischen mehr Elementen können ebenfalls auf die Gedanken dieses Artikels angewendet werden. Dieser Artikel beschränkt sich nur auf zwei Elemente, um die Erklärung zu vereinfachen.

Zusammenfassung

Die oben genannten sind von mir vorgestellten nativen JS-Kontrollen mehrerer Scrollbalken, die gleichzeitig folgen. Ich hoffe, dass es Ihnen hilft. Wenn Sie Fragen haben, hinterlassen Sie mir bitte eine Nachricht, ich werde umgehend antworten. Ich möchte auch sehr danken, dass Sie die Unterstützung für das Schreien-Tutorial zeigen!

Erklärung: Der Inhalt dieses Artikels wurde aus dem Internet entnommen und gehört dem Urheberrecht des jeweiligen Autors. Der Inhalt wurde von Internetbenutzern freiwillig beigesteuert und hochgeladen. Diese Website besitzt keine Eigentumsrechte und hat den Inhalt nicht manuell bearbeitet. Sie übernimmt auch keine rechtlichen Verantwortlichkeiten. Wenn Sie verdächtige Urheberrechtsinhalte finden, sind Sie herzlich eingeladen, eine E-Mail an notice#w zu senden.3codebox.com (Während Sie eine E-Mail senden, ersetzen Sie bitte # durch @ und melden Sie Missbrauch, und fügen Sie relevante Beweise bei. Sobald überprüft, wird diese Website den verdächtigen Inhalten sofort löschen.)

You May Also Like