同期スクロールに、悶え苦しんでしまった、ユゲタです。
同期スクロールって何かというと、
2つの関連したリストを表示するような場合に、それぞれリストがスクロールするほど長い状態の時、
その2つのリストの片方をスクロールしたら、もう片方がそれを追従するようにリストをスクロールさせたい場合に、
同期スクロールという方法をとるようにする事なんですが、
javascriptで、リストのスクロールイベントをキャッチして、もう片方のリストのスクロール値を変更してあげればいいだけなんですが、
思いもよらず、うまく動かなかったので、その苦労の状態を備忘録しておきたいと思います。
相互の動機スクロールでの問題点
とりあえず、まずは最初にコードを書いてみます。
<link rel="stylesheet" href="common.css">
<div class="flex">
<ul id="a">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
<li>8</li>
<li>9</li>
<li>10</li>
<li>11</li>
<li>12</li>
<li>13</li>
<li>14</li>
<li>15</li>
<li>16</li>
<li>17</li>
<li>18</li>
<li>19</li>
<li>20</li>
</ul>
<ul id="b">
<li>a</li>
<li>b</li>
<li>c</li>
<li>d</li>
<li>e</li>
<li>f</li>
<li>g</li>
<li>h</li>
<li>i</li>
<li>j</li>
<li>k</li>
<li>l</li>
<li>m</li>
<li>n</li>
<li>o</li>
<li>p</li>
<li>q</li>
<li>r</li>
<li>s</li>
<li>t</li>
</ul>
</div>
<script src="scroll_sync.js"></script>
ul,li{
list-style:none;
margin:0;
padding:0;
}
ul{
margin:10px;
overflow:scroll;
width:300px;
height:300px;
border:1px solid black;
}
li{
padding:5px;
border-bottom:1px solid #ccc;
}
.flex{
display:flex;
}
document.getElementById("a").addEventListener("scroll" , scroll_sync);
function scroll_sync(e){
let target = null;
switch(e.target.id){
case "a":
target = document.getElementById("b");
break;
}
target.scrollTop = e.target.scrollTop;
}
これを実行すると、左側のスクロールをした場合に、右側が一緒にスクロールします。
そして、今度は、右側のスクロールをした時に、左側のスクロールをさせたい場合に、
効率を考えて、同じscroll_sym関数を利用するために、次のコードをjsに付け足してみました。
document.getElementById("b").addEventListener("scroll" , scroll_sync);
サンプルの画面では、ぎこちなくスクロール同期されていますが、少しemenentが増えてくると
片方がうまくスクロールされなくなります。
これは、対象のスクロールイベントが発火してしまって、相互にスクロール同期をやり合う、コンフリクトに近い状態になているようです。
回避方法は力技
とにかくぎこちなさをなくすために、今回とった策として、次のようなjsコードにしてみました。
window.a = document.getElementById("a");
window.b = document.getElementById("b");
a.addEventListener("scroll" , scroll_sync);
b.addEventListener("scroll" , scroll_sync);
function scroll_sync(e){
let target = null;
if(window.scroll_sync_target && window.scroll_sync_target !== e.target){return;}
switch(e.target.id){
case "a":
target = window.b;
break;
case "b":
target = window.a;
break;
}
window.scroll_sync_target = e.target;
target.scrollTop = e.target.scrollTop;
setTimeout((function(){if(window.scroll_sync_target){delete window.scroll_sync_target}}) , 300);
}
最初に発火したタイミングで、window.scroll_sync_targetに、発火したエレメントを保持しておき、
0.3秒後に、そのフラグを削除しています。(もう少し短い秒数でもいいですが)
そして、対象エレメントのイベント発火の際に、window.scroll_sync_targetと同じエレメントでなければ、
returnして、処理をしないようにしています。
あと、毎回getElementをしている非効率を、グローバル変数で、最初一回のみの処理にすることで、
ほんのちょっぴり効率を良くしています。
でも、そもそもの、scrollイベントのタイムラグがあるため、多少のぎこちなさは残りますし、
めちゃくちゃ素早くスクロールすると、同期されない場合もあります。
この辺は、他のイベントと組み合わせて行うしかないでしょうね。
とりあえず、今回は2つのスクロール同期をやってみましたが、Googleのスプレッドシートのフレームのように、縦横でのスクロール同期などを
やる場合もあるので、この辺の精度の良さ追求しておきたいですね。
Googleもちょびっとぎこちないんで、仕方ないかもしれませんが・・・
デモ
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- a
- b
- c
- d
- e
- f
- g
- h
- i
- j
- k
- l
- m
- n
- o
- p
- q
- r
- s
- t
0 件のコメント:
コメントを投稿