Simplicityを少し改造してみた part12 スクロール追従エリアの処理の見直し

Simplicity専用 スクロール追従ウィジェット スクリプト

2015/12/31 追記
追従エリアが存在していない時(モバイル時)にその座標を取得しに行っていた設定をfix
/commit/162404366525d933ba2272d25bc8beba9cb83d0f

/original_fix_files/scrollbar_scroll.js

scrollbar_scroll.js[20150913 fix & Resize support]

経緯

Simplicity公式のこちらのトピックに僕がトピックを建てたのが始まりなんですが、最初は、以前から気になっていたSimplicity 親テーマのjavascript.jsを色々イジっていて、ソースの修復やリインデント(コードの整頓)を行っており、気になっていた記述の不備を直しておりました。

記述の不備は、エラーに近いものからwarningが出ている箇所などで大幅な修正はしてなかったんですけれども、編集するにしてもいちいちjQueryと書くのは面倒臭い。そこで従来の$で記述できるように変更もしておりました。

結果的に、

はうまく動かず、

とすれば動くことがわかったので、それに書きなおしていると、スクロール追従の処理の部分で多くのwarningが出ているのに気が付き、スクロール追従の処理内に書かれている変数をvar sidebarHeightなどと、ローカル変数として宣言するようにしました。

うまく動作しなかったのは、あくまで想像ですけど現在のSimplicity1.8.5はjQuery、親・子テーマのjavascript.jsをfooter(body最後)で読み込ませてあって、そこに到達する前にjQuery(function(){…})と書いてあったら、jQueryがないのにjQueryってなんだよ、エラー吐いとこうって流れなんじゃなかろうかと思ったりしています。

特にファーストビューでSNSのカウントやらは設置してあるので、ページ読み込みました、jQueryが書かれてます、jQuery本体がありません、はいstopって流れで、その後でjQueryが読み込まれたとしても、jQueryの中は$(document).ready(function(){})って書いてあるじゃんって判断ではなかろうかと。

しかしうまく行く場合もあるかもしれません。つまり、どのタイミングでブラウザがjQueryを読み込んでいるかなどの状態に依ると言うことなんだろうと思います。

ここから、トピックでも返信したのですが、ブラウザのレンダリングブロックを恐れるがあまりjQueryやらをfooter付近で読み込むよりは、headで読み込ませてレンダリングブロックの問題を回避するために、defer等を入れるのが良いのではなかろうかと思う次第です。

defer(遅延読み込み)やasync(非同期読み込み)をscriptタグに入れるのは、Wordpressの各スクリプトの読み込みの処理から派生するものなので、ここらはまたわいひらさんが時間がある時にテストしてくれるだろうと思います。それで滞り無く動作すればいいんですけどね。まぁ何事もトライアンドエラーですよね。スクラップアンドビルドですよね。

それが言いたいだけだろ?って、その通りです。

話は戻りまして、以前からGitHubにソースを書いておけば、一つ前のバージョンに戻ったり管理がしやすくなるということでやりたいと思っていたものですから、親テーマのjavascript.jsをpush(アップロード)する前に色々とあげていたんです。

フォーラムでわいひらさんとのやりとりがあり、修正したjavascript.js( jQuery(function($){…});のもの )を試してほしいとしていた所、ローカルでは動くけど本番サーバーではうまく動かなかったとレスをもらったので、( (function($){…})(jQuery); )の方で書き直したものを再度pushして試してもらいました。

この時はうまく動いているように思えたのですが、pushした後、自分のサイトで試していたら、スクロール追従エリアがガクッと動くのに気が付き、何とかできないものかと思っていたら、わいひらさんもそれに気づいたようで、修正している間にレスをくれておりました。

で、元々のオリジナルスクリプトを修正するとどこに何のエラーが出るのかわからなくなってしまう恐れがあるのと、結構な量のスクリプトだったので、こんなに処理がいるものかな?と自分で新規にスクリプトを作ることにしました。

それが現在GitHubにて公開しているsidebar-scroll.jsです。

そして現在、このサイトでも適用してあります。

動作の説明

まだスクリプトは無駄な箇所があるかも知れないので、もう少し見直す必要があるのですが、動作原理としては以下のような感じです。またGitHubのissueに説明もしてあります。

スクロール追従に必要なもの

  1. 追従部分の座標
  2. 追従部分の高さ
  3. フッターの座標

おそらくこれが絶対に必要になるものかと思います。

追従部分の座標は、スクロール位置がそこを超えたら追従すると言うきっかけになりますし、追従部分の高さは、追従部分の底の座標を知るのと、判定に使います。
フッターの座標は、どこまで追従させるかのゴールポイントであり、これがわからないと追従エリアはスクロール終端まで延々と追従することになります。

これらがわかったら後はスクロールしている時にどうするかとか、追従部分の座標をスクロールが超えたら処理開始とかそういった条件に対しての処理になります。

以前のスクリプトでの問題点

オリジナルのスクリプトで問題になっていたのが、非同期で取得するTwitterのタイムラインのようなウィジェット(あるいはブログパーツという方がわかりやすいかも知れません)は、ページが表示された段階ではその高さがわからないため、ゴール地点はどこか、追従エリアの高さはどうするかなどを色々と考えられていたようです。

僕が作った方のスクリプトでは、その高さが問題になるのは「フッターにスクロール追従エリアが到達した時」のみです。追従自体は追従要素の座標がわかれば、それを「スクロール位置が超えているかどうか」の判断だけで動きます。

追従部分の座標は、本来のjQueryでは、

で取得できるのですが、ここがどうもうまくイケてなかったようなので、生のjavascriptで取得することにしました。

なぜうまく取得できなかったのかはいまいちわかってないんですけど、おそらくは、まずvar ss_offset = $(セレクタ).offset()でtopとleftの座標をしておいて、必要な所で、ss_offset.topなどとして利用する必要があるのではないかと想像します。

まぁたいして変わらないにせよ、生のjavascriptでやる方が、jQueryの処理が無い分、多少早くなるだろうと言う感じです。

取得した追従エリアの座標(top)をスクロールトップが超えた時、処理を開始。これでスタートできることになります。

問題はゴールです。
どのような時にゴールとなるのかを考えると、フッターの上に追従エリアが到達した時ですよね。その場面はどうやってわかるかというと、

フッターの座標(top)を調べておく
その座標より上方向に向かい、追従エリアの高さと、フッター上のマージンを足した部分の座標までの範囲がわかれば、それがゴール地点です。つまりは、

この位置がわかればゴールがわかります。

デザインによってフッターの高さは異なります。しかし、その座標は常にどこにあるかはわかります。
Simplicityの構造は、

だいぶ端折ってますが、こういう順序になっており、上から記事、サイドバー、フッターと何も処理しない場合は並んでいるわけです。いわゆるタブレットなどの画面を見てもらうとその並びになっていることが理解して頂けると思います。

PCの画面では、サイドバーを右に回りこませてあるので、メイン記事の右側にサイドバーが来て、その下にフッターがある状態になっていますが、この順序は決まっているので、フッターは必ずメインの記事よりも、サイドバーよりも下にあるわけです。

ここからも、追従エリアのゴールポイントは、追従エリアやブラウザ画面を起点に考えるよりも、フッターの座標(top)を起点に考えるほうが良いことが理解してもらえると思います。

ゴールの位置がわかれば、そこまで追従させればよいわけで、後はスクロールバーの位置がどこにあるかがわかれば処理は行えます。

まとめると、

スクロールバーの位置が追従エリアの座標を超えた -> 追従スタート
フッター座標(top)から追従エリアの高さとフッターのマージンを引いた所までスクロールバーが来た -> ゴール

こういうことになります。

非同期で取得する要素の高さ問題

今回のようにスクロールする事によって処理をするような場面では、非同期要素の高さは深く考える必要はありません。

普通に考えたら、「非同期要素が読み込まれた後に高さを取得する」とか、「処理を行う時間を遅らせて非同期要素が読み込まれてから処理をしよう」と考えがちなのですが、フッターが見えている時は追従する必要がなく、追従させるためには、メインの記事がサイドバーの高さよりも高い必要があるわけです。

結果的に必ずスクロールすると言うことから、それなら、スクロールしている時に非同期要素が入っている親要素の高さを取得すれば良いじゃんと言うことになります。

今回のスクリプトでは、処理を行う流れとして、

$(document).ready(function(){…A…})のAの部分で非同期要素を無視した、それぞれのセレクタの座標や高さその他諸々を取得してあります。スクリプト自体は、その座標などの条件に従って動作するわけですが、ゴール地点の位置を求める際に、次のような問題点が出る場合があります。

  • メイン記事の中に非同期要素が含まれていた場合、メインの高さが変わるのでフッターの座標も変わる
  • 追従エリアの中に非同期要素が含まれていた場合、追従エリアの高さが変わる

これがおそらく最大の問題点です。
フッターの座標が変われば、メインの中の非同期要素の高さ分、ゴールの位置がズレてしまうことがあり、追従エリアの高さが変わればゴールの位置がやはりズレてフッターを突き抜けてしまう場合があると言う問題が出てきます。

そのために、行う処理としては、

こうする必要があるのです。スクロールが行われるごとに座標や高さを調べに行きます。その間に非同期要素は読み込まれて、おそらくフッターに到達するまでには、完全に読み込まれた状態の非同期要素の高さやフッターの位置は調べられます。
なので、ここで取得した値をゴール地点の判定に使えば、必ず目的の位置を見つけることができる寸法です。

まとめ

ひとまず今日現在の所、上記のような考え方でスクリプトを作っていますが、何か良いアイデアがあった場合は考え方が変わるかもしれません。変更した場合は、うちのGitHubに修正したものをpushしておきますので、時間がある時にふらりと立ち寄っていただくと良いかも知れません。

あれから

あれからも色々ありました。動作はするものの非同期ではないと思われる要素が入っていた場合(スクリプトで内容を読み込み)高さは読み込まれた内容によって変わります。なので、もう面倒臭いということで、追従エリアの前にdummyブロックを入れておいて、そこの座標やらをスクロール中に取得することでスタート地点の変更を調査するようにしました。

追従エリアの前に挿入したので、必ずそこはスタート地点になります。

またこれを実装した後、わいひらさんのレスがあり、「背景色をONにしていた場合に、追従エリアがそうならない」と指摘をもらいました。

あぁそんなモードもあったね、とその時思い出したのですが、これは#sidebarの中に追従エリアがある内は、#sidebarのcssが効きますのでpaddingやらは適用されますが、元々追従エリアにはcssが割り当ててられないので、追従するにあたってfixedやabsoluteにした場合、#sidebarの外に行ってしまうんですね。
そしたら、元々cssがないのですから「背景の色」がない状態で追従されてしまうのです。

そこで、もし#sidebarに背景色が適用されていた場合(背景が透過されていない場合)は、色を当てると言うことと、その他諸々の処理を加えて現在に至ります。

これらをテストしている時に、追従エリアがフッターに達しているような時、あるいは追従途中にリロード操作があった場合、次にスクロールしないと追従エリアが表示されないというのも解決しました。
同時に、トップへ戻るボタンも同じような症状だったので、それも修復。

そして現在に至ります。

スポンサーリンク

シェアする

フォローする