前の記事getBoundingClientRect メソッドを使用して単純なスティッキー コンポーネントを実装する では、スティッキー コンポーネントの単純な実装を紹介しましたが、ここ 2 日間考えた結果、さらに多くの実装が提供されていることがわかりました。欠点は、前回提供した修正方法が不十分だったことを踏まえて、他の Web サイトで得られる効果が若干異なることです。この記事では、スティッキー コンポーネントの改良版を提供します。完了しました。興味を持って読んでいただければ幸いです。
1. 古いバージョンの問題
前のスティッキーコンポーネントの実装には複数の問題があります:
まず、スティッキーの効果についてですが、スティッキー要素を修正する前と後で変化しないのは、ブラウザの左側からの相対的な位置と、スティッキー要素の全体の幅です。ブラウザの上部または下部を基準とした位置とスティッキー要素の高さ。後者の 2 つの変化する値は定数値とみなされます。上限値または下限値が固定されているのに常に 0 になるのはなぜですか?もちろん、上: 20px、下: 15px など、0 以外の値にすることもできます。ブートストラップの公式ドキュメントで使用されている接辞コンポーネントの例のように、シーンによっては、そのようなオフセットを追加すると、スティッキー効果がより良く見えます。コンポーネント この機能は、この記事で実装されているスティッキー コンポーネントに似ています):
固定時はブラウザ上部からの相対位置をtop:20pxに設定します。スティッキー要素の高さについても同様であり、固定したときに見栄えの良い効果を表示するには、元の Line-height や Padding-top などの高さ関連の属性を調整することが非常に一般的です。 Tmall Huabei のこのページ、このブロック コンテンツはスティッキー コンポーネントを使用しています:
固定される前のスティッキー要素の高さは次のとおりです:
固定後のスティッキー要素の高さは次のとおりです:
2番目に、固定を解除する場合、上部に固定されているスティッキー要素を例にとります。上記の実装では、ターゲット要素とブラウザの上部の間の距離が離れたときに、スティッキー要素の位置を直接キャンセルします。 StickyHeight: 固定属性より小さい場合、sticky 要素は直ちに通常のドキュメント フローに復元され、その効果は次のとおりです:
臨界点に達するとすぐに消えますが、天猫花北の効果は次のようなものではありません:
臨界点に達してもすぐには消えませんが、スクロール バーと連動して Web ページのメイン コンテンツと一緒に上にスクロールできるように、スティッキー要素の上部の値を再調整します。
経験の観点から、Tmall Huabei の効果が優れていることは明らかです。機能の観点からは、上記の実装には致命的な欠点があります。スティッキー要素の高さが非常に大きい場合、ブラウザの能力を超えているため、どのようにスクロールしてもスティッキー要素の内容をすべて閲覧できないというバグが発生します。興味がある場合は、前回実装したコードを試してみてください。ブログのサイドバー。試してみたところこの問題が見つかったので、sticky コンポーネントを改善したいと思いました: (
第三に、最後の実装にはまだいくつかの欠点があります:
1) documentElement.clientHeight はキャッシュされないため、重要なポイントが判断されるたびに再取得されます:
2) スクロール コールバック間隔のデフォルト値は大きすぎるため、今回は 5 に設定し、ブートストラップでは 1 を使用します。この方法でのみ効果が保証されます。
3) 一部のシナリオでは、サイズ変更が必要な場合にスティッキー要素の幅をリセットする必要がない場合があります。それを制御するためのオプションを追加する必要があります。
4) スティッキー要素が固定されている場合と固定されていない場合、他のコンポーネントがこのコンポーネントに依存するときに重要なポイントで処理できるように、コールバック関数を提供する必要があります。
2. 改善方法
コンポーネントのオプションが再定義されました:
var DEFAULTS = { target: '', //target元素的jq选择器 type: 'top', //固定的位置,top | bottom,默认为top,表示固定在顶部 wait: 5, //scroll事件回调的间隔 stickyOffset: 0, //固定时距离浏览器可视区顶部或底部的偏移,用来设置top跟bottom属性的值,默认为0 isFixedWidth: true, //sticky元素宽度是否固定,默认为true,如果是自适应的宽度,需设置为false getStickyWidth: undefined, //用来获取sticky元素宽度的回调,在不传该参数的情况下,stickyWidth将设置为sticky元素的offsetWidth unStickyDistance: undefined, //该参数决定sticky元素何时进入dynamicSticky状态 onSticky: undefined, ///sticky元素固定时的回调 onUnSticky: undefined ///sticky元素取消固定时的回调 };
太字のものは新規または変更されたもので、元の高さが削除され、unStickyDistance に置き換えられています。固定する場合はstickyOffsetでブラウザの上部または下部からの相対位置を指定するため、.sticky--in-topや.sticky--inのcssにtopやbottomの属性値を記述する必要はありません。 -底。 isFixedWidth が false の場合、サイズ変更中にスティッキー要素の幅を更新するためのコールバックが追加されます:
!opts.isFixedWidth && $win.resize(throttle(function () { setStickyWidth(); $elem.hasClass(className) && $elem.css('width', stickyWidth); sticky(); }, opts.wait));
前回と比べて今回の実装で問題となったのは、固定を解除する際の論理処理です。前回はsticky要素がstickyとunstickyの2つの状態に分かれていました。 staticStickyとdynamicStickyで、前者は上値または下値が変わらないスティッキー状態を表し、後者は上値または下値が変化するスティッキー状態を表します。この問題をより明確に解決するために、本来の判断は、クリティカル ポイントと、異なるクリティカル ポイントで異なる処理を実行するコードを次のように再構成します。
setSticky = function () { !$elem.hasClass(className) && $elem.addClass(className).css('width', stickyWidth) && (typeof opts.onSticky == 'function' && opts.onSticky($elem, $target)); return true; }, states = { staticSticky: function () { setSticky() && $elem.css(opts.type, opts.stickyOffset); }, dynamicSticky: function (rect) { setSticky() && $elem.css(opts.type, rules[opts.type].getDynamicOffset(rect)); }, unSticky: function () { $elem.hasClass(className) && $elem.removeClass(className).css('width', '').css(opts.type, '') && (typeof opts.onUnSticky == 'function' && opts.onUnSticky($elem, $target)); } }, rules = { top: { getState: function (rect) { if (rect.top < 0 && (rect.bottom - unStickyDistance) > 0) return 'staticSticky'; else if ((rect.bottom - unStickyDistance) <= 0 && rect.bottom > 0) return 'dynamicSticky'; else return 'unSticky'; }, getDynamicOffset: function (rect) { return -(unStickyDistance - rect.bottom); } }, bottom: { getState: function (rect) { if (rect.bottom > docClientHeight && (rect.top + unStickyDistance) < docClientHeight) return 'staticSticky'; else if ((rect.top + unStickyDistance) >= docClientHeight && rect.top < docClientHeight) return 'dynamicSticky'; else return 'unSticky'; }, getDynamicOffset: function (rect) { return -(unStickyDistance + rect.top - docClientHeight); } } } $win.scroll(throttle(sticky, opts.wait)); function sticky() { var rect = $target[0].getBoundingClientRect(), curState = rules[opts.type].getState(rect); states[curState](rect); }
全体的な実装は次のとおりです:
var Sticky = (function ($) { function throttle(func, wait) { var timer = null; return function () { var self = this, args = arguments; if (timer) clearTimeout(timer); timer = setTimeout(function () { return typeof func === 'function' && func.apply(self, args); }, wait); } } var DEFAULTS = { target: '', //target元素的jq选择器 type: 'top', //固定的位置,top | bottom,默认为top,表示固定在顶部 wait: 5, //scroll事件回调的间隔 stickyOffset: 0, //固定时距离浏览器可视区顶部或底部的偏移,用来设置top跟bottom属性的值,默认为0 isFixedWidth: true, //sticky元素宽度是否固定,默认为true,如果是自适应的宽度,需设置为false getStickyWidth: undefined, //用来获取sticky元素宽度的回调,在不传该参数的情况下,stickyWidth将设置为sticky元素的offsetWidth unStickyDistance: undefined, //该参数决定sticky元素何时进入dynamicSticky状态 onSticky: undefined, ///sticky元素固定时的回调 onUnSticky: undefined ///sticky元素取消固定时的回调 }; return function (elem, opts) { var $elem = $(elem); opts = $.extend({}, DEFAULTS, opts || {}, $elem.data() || {}); var $target = $(opts.target); if (!$elem.length || !$target.length) return; var stickyWidth, setStickyWidth = function () { stickyWidth = typeof opts.getStickyWidth === 'function' && opts.getStickyWidth($elem) || $elem[0].offsetWidth; }, docClientHeight = document.documentElement.clientHeight, unStickyDistance = opts.unStickyDistance || $elem[0].offsetHeight, setSticky = function () { !$elem.hasClass(className) && $elem.addClass(className).css('width', stickyWidth) && (typeof opts.onSticky == 'function' && opts.onSticky($elem, $target)); return true; }, states = { staticSticky: function () { setSticky() && $elem.css(opts.type, opts.stickyOffset); }, dynamicSticky: function (rect) { setSticky() && $elem.css(opts.type, rules[opts.type].getDynamicOffset(rect)); }, unSticky: function () { $elem.hasClass(className) && $elem.removeClass(className).css('width', '').css(opts.type, '') && (typeof opts.onUnSticky == 'function' && opts.onUnSticky($elem, $target)); } }, rules = { top: { getState: function (rect) { if (rect.top < 0 && (rect.bottom - unStickyDistance) > 0) return 'staticSticky'; else if ((rect.bottom - unStickyDistance) <= 0 && rect.bottom > 0) return 'dynamicSticky'; else return 'unSticky'; }, getDynamicOffset: function (rect) { return -(unStickyDistance - rect.bottom); } }, bottom: { getState: function (rect) { if (rect.bottom > docClientHeight && (rect.top + unStickyDistance) < docClientHeight) return 'staticSticky'; else if ((rect.top + unStickyDistance) >= docClientHeight && rect.top < docClientHeight) return 'dynamicSticky'; else return 'unSticky'; }, getDynamicOffset: function (rect) { return -(unStickyDistance + rect.top - docClientHeight); } } }, className = 'sticky--in-' + opts.type, $win = $(window); setStickyWidth(); $win.scroll(throttle(sticky, opts.wait)); !opts.isFixedWidth && $win.resize(throttle(function () { setStickyWidth(); $elem.hasClass(className) && $elem.css('width', stickyWidth); sticky(); }, opts.wait)); $win.resize(throttle(function () { docClientHeight = document.documentElement.clientHeight; }, opts.wait)); function sticky() { var rect = $target[0].getBoundingClientRect(), curState = rules[opts.type].getState(rect); states[curState](rect); } } })(jQuery);
3. ブログサイドバーの適用手順
まず、この実装をブログ設定のフッター HTML テキスト フィールドに貼り付けてから、次のコードを追加して初期化する必要があります:
var timer = setInterval(function(){ if($('#blogCalendar').length && $('#profile_block').length && $('#sidebar_search').length) { new Sticky('#sideBar', { target: '#main', onSticky: function($elem, $target){ $target.css('min-height',$elem.outerHeight()); $elem.css('left', '65px'); }, onUnSticky: function($elem, $target){ $target.css('min-height',''); $elem.css('left', ''); } }); } },100);
4. 概要
この週末は、スティッキー コンポーネントを改善する方法を考えて、一日のほとんどをこの記事の執筆に費やしました。少なくとも、最後に書き終えたスティッキー コンポーネントの機能と実装には少し満足しています。何かが欠けているように奇妙に感じましたが、それはまだ多くのものが欠けているためであることがわかりました。現時点では、このコンポーネントは固定と固定解除の効果しか実現できません。固定された状態でナビゲーションのスクロールやタブのナビゲーションをサポートするインターネット上の一般的な機能も、このレベルの効果では十分ではない可能性があります。次へ この記事では、この記事を元に Sticky コンポーネント、navScrollSticky コンポーネントと tabSticky コンポーネントの実装方法を紹介しますので、ご期待ください。
読んでいただきありがとうございます:)
補足説明:
IE および Firefox では、ページを更新するときに、更新前にページがスクロールしている場合、更新操作によってページのスクロール位置が更新位置に設定されますが、スクロール イベントはトリガーされないため、スクロール イベントの直後に呼び出す必要があります。コンポーネントは初期化されます: