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

原生JSで複数のスクロールバーを同時にスクロールする効果

markdownで文章を書けるウェブサイトや、バックエンドの書き込みページでは、一般的にmarkdownの即時プレビューをサポートしています。つまり、ページ全体を二つに分けて、左半分が入力したmarkdownテキスト、右半分が即時出力されるプレビューページになります。以下はCSDNのバックエンド書き込みページのmarkdownの即時プレビューエフェクトの例です:

この記事では、0からこの効果を実現する方法を説明するものではありません(後続 可能性が高いです 本文では、他のものを除いて、ページのメイン部分の左右に配置された二つのコンテナ要素、つまり markdown 入力ボックス要素とプレビューディスプレイ要素に注目します。

この記事では、この二つのコンテナ要素の内容がすべてコンテナの高さを超えており、スクロールボックスが表示されている場合、どのコンテナ要素がスクロールするときに、もう一方の要素も同時にスクロールする方法について説明します。

DOM 構造

スクロールバーに関連しているため、まず思い浮かぶ js でスクロールバーの高さを制御する属性は scrollTop です。この属性の値を制御できれば、自然とスクロールバーのスクロールも制御できます。

以下のDOM構造に対して:

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

その中で、.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('.left')
var r=document.querySelector('.right')
l.addEventListener('scroll',function(){
 r.scrollTop = l.scrollTop
)

効果は以下の通りです:

とても良いように見えますが、今は右側が左側に従ってスクロールするだけでなく、左側も右側に従ってスクロールしたいので、以下のコードを追加します:

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

見た目はとても良いように思えますが、本当にそんなに簡単なことではありません。

その時、マウスのロールウェルでスクロールすると、スクロールが少し重く感じられ、二つのコンテナ要素のスクロールが何かによって阻まれているように見えます。スクロールが難しくなります。

詳しく分析すると、原因は単純です。左側をスクロールすると、左側のスクロールイベントがトリガーされ、右側がそれに従ってスクロールしますが、同時に右側もスクロールして、さらに右側のスクロールがトリガーされ、左側も右側に従ってスクロールする...このように相互にトリガーされる状況に陥り、スクロールがとても重く感じられます。

scroll イベントが同時にトリガーされる問題を解決する

上記の問題を解決するためには、一時的に以下の二つの方法があります。

scroll イベントを mousewheel イベントに置き換えます

scroll イベントはマウスが自動的に巻かれることでだけでなく、コンテナ要素の scrollTop を変更することでもトリガーされます。要素の自動的なスクロールは実際にはマウスのロールウェルがトリガーしているため、scroll イベントをマウスのロールウェルに反応するイベントに置き換えることができます:'mousewheel'、そして上記のリスナーのコードは以下のように変わります:

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

効果は以下の通りです:

ある程度役立ちますが、実際には2つの問題があります。

あるコンテナ要素をスクロールすると、もう一方のコンテナ要素も一緒にスクロールしますが、スムーズではありません。高さに明らかな瞬間的なジャンプがあります。

ネットで検索してみましたが、wheel イベントのスクロール頻度に関する情報は見つかりませんでした。これはこのイベントの特徴の一つであると推測します。

マウスがスクロールするたびに、基本的には 1px で単位表示されているため、scroll イベントよりもはるかに小さい最小単位があります。私のマウスで Chrome ブラウザでスクロールすると、1度ごとに通過する距離は正確に 100px で、異なるマウスやブラウザではこの値は異なります。実際に監視されているのは、マウスのロールウェルが1つのギアカードポイントを通過するイベントであり、これがバウンス現象が起こる理由を説明できます。

一般的に、マウスのロールウェルが1つのギアカードポイントを回転すると、1つの wheel イベントが検出されます。始めから終わりまで、マウスが自動的に巻かれた要素は 100px であれば、他の追随するスクロールコンテナ要素も瞬時にジャンプします。 100px

上述の scroll イベントが追随する要素に瞬間的なジャンプを引き起こさない理由は、追随する要素が scrollTop が変更されるたびにその値が 100px という大きな範囲があれば、小さくてもないかもしれません。 1px ですが、そのトリガーフレーケンスが高く、スクロールの範囲が小さいため、視覚的には滑らかなスクロールになります。

wheel はマウスのロールウェルイベントを監視していますが、マウスでスクロールバーをドラッグすると、このイベントはトリガーされません。他のコンテナ要素も追随しません。

これは実際には非常に簡単に解決できます。マウスでスクロールバーをドラッグすると、必ず scroll イベントがトリガーされます。その場合、簡単にこのドラッグされたスクロールバーがどのコンテナ要素に属しているかを判断できます。必要なのは、そのコンテナのスクロールイベントを処理することだけで、他の追随するスクロールコンテナのイベントは処理しないことです。

wheelイベントの互換性問題

wheelイベントはDOMレベル3 の標準イベントですが、このイベント以外にも、多くの非標準イベントがあり、異なるブラウザの内核は異なる標準を使用するため、状況に応じて互換性を取る必要があります。詳細はMDN MouseWheelEventを参照してください。

リアルタイムで判断

wheelのジャンプやさまざまな互換性に耐えられない場合は、他の方法も存在します。それはscrollイベントであり、ただし、追加の作業が必要です。

scrollイベントの問題は、現在の主にスクロールするコンテナ要素を判断していないことです。主にスクロールするコンテナ要素を特定すれば、これで問題は解決します。例えば、上記のwheelイベントでは、マウスでスクロールバーをドラッグすることでscrollイベントを使用できるのは、現在の主にスクロールするコンテナ要素を簡単に特定できるからです。

したがって、鍵となる問題は、現在の主にスクロールするコンテナ要素をどのように判断するかであり、この問題を解決すれば、残りは簡単です。

マウスのホイールでスクロールしたり、スクロールバーにマウスを押してドラッグしてスクロールしたりすることで、scrollイベントが発生し、その時、Z軸上では、マウスの座標がスクロールコンテナ要素が占める面積の内にあると確実です。つまり、Z軸上では、マウスは浮遊したり、スクロールコンテナ要素の上に位置しています。

スクロール中に、現在のマウスの座標を取得することができます。

その中で、clientXとclientYは現在のマウスの視口に対する座標であり、この座標が特定のスクロールコンテナの範囲内にある場合、そのコンテナ要素が主にスクロールするコンテナ要素であると考えられます。コンテナ要素の座標範囲はgetBoundingClientRectを使用して取得できます。

以下は、要素 .leftにマウスを移動させた際のサンプルコードです:

if (e.clientX>l.left && e.clientX<l.right && e.clientY>l.top) {
  // 要素 .leftに進入します
}

これでは実際にできますが、2つのスクロールコンテナ要素がほぼスクリーンの全体を占めるため、mousemoveイベントで監視する面積が非常に大きくなり、パフォーマンスに対する要求が高くなるため、実際にはmouseoverイベントに変更することができます。必要に応じて、特定のスクロールコンテナ要素にマウスが入っているかどうかを監視するだけで十分であり、上記の座標判定を省略することもできます。

addEventListener('mouseover',function(){
 // 左側のスクロールコンテナ要素内に入ります。
)

マウスが主动的にスクロールするコンテナ要素がどれかを確定したら、そのコンテナのスクロールイベントを処理するだけで良い。もう一方の追従スクロールコンテナのイベントは処理しないでください。

効果が非常に良いで、パフォーマンスも良い、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を得ることができる。

したがって、2つのスクロール要素コンテナが等比例で上下にスクロールするようにするには、1つの要素が先頭または末尾に達した場合、もう1つの要素も対応する先頭または末尾に達するようにするには、これら2つのスクロールコンテナ要素の scrollTop の最大値の比(scale)を取得するだけで十分です。

scale が確定した後、リアルタイムでスクロールすると、アクティブなスクロールコンテナ要素の scrollTop を取得するだけで済みます。1 、すると別の追従スクロールコンテナ要素の scrollTop が得られます。2 :

考え方が明確になりましたら、コードの書き方は簡単になります。効果は以下の通りです:

とてもスムーズです~

小結

上記の実装は要件を満たしていますが、実際の作業中には状況に応じて修正が必要になる可能性があります。例えば、markdown のオンラインエディタとプレビューページを書く場合、入力内容の高さに応じて scale 値をリアルタイムで更新する必要がありますが、基本的な構造は完成しており、小さな修正は難しくありません。

この記事では、2つのスクロールコンテナ要素の追従スクロールに加えて、他の多くの要素間の追従スクロールも本文のアプローチに基づいて実現できます。ただし、具体的には2つの要素に絞って説明しています。

まとめ

以上が、編集者が皆さんに紹介した、オリジナルの JavaScript で複数のスクロールバーを同時にスクロールする効果です。皆さんに役立つことを願っています。何か疑問があれば、コメントを残してください。編集者は皆さんの質問に迅速に回答します。また、この教材へのサポートに感謝しています!

声明:本文の内容はインターネットから取得しており、著作権者に帰属します。インターネットユーザーが自発的に貢献し、自己でアップロードしたものであり、本サイトは所有権を持ちません。また、人工的な編集は行われていません。著作権侵害を疑う内容があれば、メールで notice#w までお知らせください。3codebox.com(メールを送信する際、#を@に変更してください)で通報し、関連する証拠を提供してください。一旦確認がとれましたら、本サイトは即座に侵害を疑われるコンテンツを削除します。

おすすめ