この質問がこのフォーラムで100万回も寄せられたことは知っていますが、解決策を見つけるのに役立つ記事はありませんでした。
ハッシュリンクと同じIDのセクションにスクロールダウンすると、ハッシュリンクを強調表示する小さなjqueryコードを作成しました。
$(window).scroll(function() {
var position = $(this).scrollTop();
$('.section').each(function() {
var target = $(this).offset().top;
var id = $(this).attr('id');
if (position >= target) {
$('#navigation > ul > li > a').attr('href', id).addClass('active');
}
});
});
ここでの問題は、セクションが関係しているものだけでなく、すべてのハッシュリンクを強調表示することです。誰かが間違いを指摘できますか、それとも私が忘れていたものですか?
パフォーマンスといくつかの特定のケースについて少し話すように私の回答を変更しました。
ここでコードを探しているだけの場合は、下部にコメント付きのスニペットがあります。
_.active
_classをすべてのリンクに追加する代わりに、どの属性hrefが同じであるかを特定する必要がありますセクションのidとして。
次に、そのリンクに_.active
_classを追加して、残りのリンクから削除できます。
_ if (position >= target) {
$('#navigation > ul > li > a').removeClass('active');
$('#navigation > ul > li > a[href=#' + id + ']').addClass('active');
}
_
上記の変更により、コードは対応するリンクを正しく強調表示します。それが役に立てば幸い!
このコードが機能する場合でも、最適とは言えません。とにかく、覚えておいてください:
小さな効率については忘れてください。たとえば、97%の時間です。時期尚早な最適化がすべての悪の根源です。しかし、その重要な3%で機会を逃してはなりません。 (ドナルドクヌース)
したがって、遅いデバイスでのイベントテストでパフォーマンスの問題が発生しなかった場合、最善の方法は、読み取りを停止して、プロジェクトの次のすばらしい機能について考えることです!
基本的に、パフォーマンスを改善するには3つのステップがあります。
前の作業をできるだけ多く作成します:
(イベントがトリガーされるたびに)DOMを何度も検索しないようにするために、事前にjQueryオブジェクトをキャッシュできます(例:_document.ready
_):
_var $navigationLinks = $('#navigation > ul > li > a');
var $sections = $(".section");
_
次に、各セクションを対応するナビゲーションリンクにマッピングできます。
_var sectionIdTonavigationLink = {};
$sections.each( function(){
sectionIdTonavigationLink[ $(this).attr('id') ] = $('#navigation > ul > li > a[href=\\#' + $(this).attr('id') + ']');
});
_
アンカーセレクターの2つの円記号に注意してください。ハッシュ '#'はCSSで特別な意味を持っているため、 エスケープする必要があります (ありがとう @Johnnie )。
また、各セクションの位置をキャッシュすることもできます(Bootstrapの Scrollspy がそれを行います)。ただし、それを行う場合は、変更するたびに更新することを忘れないでください(ユーザーがウィンドウのサイズを変更したり、新しいコンテンツがajax経由で追加されたり、サブセクションが展開されたりするなど)。
イベントハンドラーを最適化します:
ユーザーが1つのセクション内をスクロールしていると想像してください。アクティブなナビゲーションリンクを変更する必要はありません。しかし、上のコードを見ると、実際には数回変更されていることがわかります。正しいリンクが強調表示される前に、以前のすべてのリンクでも同じことが行われます(対応するセクションも_position >= target
_の条件を検証するため)。
1つの解決策は、下から上のセクションを繰り返すことです。最初の.offset().top
が$(window).scrollTop
以下であるものが正しいセクションです。そして、そうです jQueryがDOMの順序でオブジェクトを返すことを信頼できます (- バージョン1.3.2 以降)。下から上に反復するには、逆の順序で選択します。
_var $sections = $( $(".section").get().reverse() );
$sections.each( ... );
_
$()
はjQueryオブジェクトではなくDOM要素を返すため、二重のget()
が必要です。
正しいセクションが見つかったら、_return false
_を実行してループを終了し、以降のセクションをチェックしないようにする必要があります。
最後に、正しいナビゲーションリンクが既に強調表示されている場合は何もしないでください。チェックしてください。
_if ( !$navigationLink.hasClass( 'active' ) ) {
$navigationLinks.removeClass('active');
$navigationLink.addClass('active');
}
_
イベントをできるだけ少なくトリガーします:
評価の高いイベント(スクロール、サイズ変更...)によってサイトが遅くなったり応答しなくなったりするのを防ぐ最も確実な方法は、イベントハンドラーの呼び出し頻度を制御することです。どのリンクを強調表示する必要があるかを確認する必要がないことを確認してください毎秒100回!リンクの強調表示のほかに、派手な視差効果を追加すると、イントロの問題をすばやく実行できます。
この時点で、スロットル、デバウンス、およびrequestAnimationFrameについて必ず読んでください。 この記事 は素晴らしい講義であり、そのうちの3つについて非常に優れた概要を説明します。私たちの場合、スロットルは私たちのニーズに最適です。
基本的に、スロットルは、2つの関数の実行間の最小時間間隔を強制します。
スニペットにスロットル機能を実装しました。そこから、より洗練された、またはさらに優れた nderscore.js または lodash などのライブラリを使用できます(ライブラリ全体が必要ない場合は、いつでもから抽出できます)そこでスロットル機能)。
注:見回すと、より単純なスロットル機能が見つかります。それらは最後のイベントトリガーを見逃す可能性があるので注意してください(そしてそれが最も重要なものです!)。
これらのケースはスニペットに含めないでください。これ以上複雑にすることはありません。
以下のスニペットでは、セクションがページの最上部に到達すると、リンクが強調表示されます。前にそれらを強調表示したい場合は、次の方法で小さなオフセットを追加できます。
_if (position + offset >= target) {
_
これは、トップナビゲーションバーがある場合に便利です。
最後のセクションが小さすぎてページの上部に到達できない場合は、スクロールバーが一番下にあるときに対応するリンクを強調表示できます。
_if ( $(window).scrollTop() >= $(document).height() - $(window).height() ) {
// highlight the last link
_
考えられるブラウザサポートの問題がいくつかあります。詳細は here および here を参照してください。
最後に、ここにコメント付きのスニペットがあります。一部の変数の名前をわかりやすくするために変更しました。
_// cache the navigation links
var $navigationLinks = $('#navigation > ul > li > a');
// cache (in reversed order) the sections
var $sections = $($(".section").get().reverse());
// map each section id to their corresponding navigation link
var sectionIdTonavigationLink = {};
$sections.each(function() {
var id = $(this).attr('id');
sectionIdTonavigationLink[id] = $('#navigation > ul > li > a[href=\\#' + id + ']');
});
// throttle function, enforces a minimum time interval
function throttle(fn, interval) {
var lastCall, timeoutId;
return function () {
var now = new Date().getTime();
if (lastCall && now < (lastCall + interval) ) {
// if we are inside the interval we wait
clearTimeout(timeoutId);
timeoutId = setTimeout(function () {
lastCall = now;
fn.call();
}, interval - (now - lastCall) );
} else {
// otherwise, we directly call the function
lastCall = now;
fn.call();
}
};
}
function highlightNavigation() {
// get the current vertical position of the scroll bar
var scrollPosition = $(window).scrollTop();
// iterate the sections
$sections.each(function() {
var currentSection = $(this);
// get the position of the section
var sectionTop = currentSection.offset().top;
// if the user has scrolled over the top of the section
if (scrollPosition >= sectionTop) {
// get the section id
var id = currentSection.attr('id');
// get the corresponding navigation link
var $navigationLink = sectionIdTonavigationLink[id];
// if the link is not active
if (!$navigationLink.hasClass('active')) {
// remove .active class from all the links
$navigationLinks.removeClass('active');
// add .active class to the current link
$navigationLink.addClass('active');
}
// we have found our section, so we return false to exit the each loop
return false;
}
});
}
$(window).scroll( throttle(highlightNavigation,100) );
// if you don't want to throttle the function use this instead:
// $(window).scroll( highlightNavigation );
_
_#navigation {
position: fixed;
}
#sections {
position: absolute;
left: 150px;
}
.section {
height: 200px;
margin: 10px;
padding: 10px;
border: 1px dashed black;
}
#section5 {
height: 1000px;
}
.active {
background: red;
}
_
_<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="navigation">
<ul>
<li><a href="#section1">Section 1</a></li>
<li><a href="#section2">Section 2</a></li>
<li><a href="#section3">Section 3</a></li>
<li><a href="#section4">Section 4</a></li>
<li><a href="#section5">Section 5</a></li>
</ul>
</div>
<div id="sections">
<div id="section1" class="section">
I'm section 1
</div>
<div id="section2" class="section">
I'm section 2
</div>
<div id="section3" class="section">
I'm section 3
</div>
<div id="section4" class="section">
I'm section 4
</div>
<div id="section5" class="section">
I'm section 5
</div>
</div>
_
そして、もしあなたが興味があるなら、 this fiddle は私たちが話し合ったさまざまな改善をテストします。
幸せなコーディング!
最近このソリューションを使用しようとしている人のために、私はそれを機能させるために思わぬ障害にぶつかりました。次のようにhrefをエスケープする必要があるかもしれません:
$('#navigation > ul > li > a[href=\\#' + id + ']');
そして今私のブラウザはその部分でエラーをスローしません。
function navHighlight() {
var scrollTop = $(document).scrollTop();
$("section").each(function () {
var xPos = $(this).position();
var sectionPos = xPos.top;
var sectionHeight = $(this).height();
var overall = scrollTop + sectionHeight;
if ((scrollTop + 20) >= sectionPos && scrollTop < overall) {
$(this).addClass("SectionActive");
$(this).prevAll().removeClass("SectionActive");
}
else if (scrollTop <= overall) {
$(this).removeClass("SectionActive");
}
var xIndex = $(".SectionActive").index();
var accIndex = xIndex + 1;
$("nav li:nth-child(" + accIndex + ")").addClass("navActivePage").siblings().removeClass("navActivePage");
});
}
.navActivePage {
color: #fdc166;
}
$(document).scroll(function () {
navHighlight();
});
私はDavidの優れたコードを取り上げ、jQueryの依存関係をすべて削除しました。
// cache the navigation links
var $navigationLinks = document.querySelectorAll('nav > ul > li > a');
// cache (in reversed order) the sections
var $sections = document.getElementsByTagName('section');
// map each section id to their corresponding navigation link
var sectionIdTonavigationLink = {};
for (var i = $sections.length-1; i >= 0; i--) {
var id = $sections[i].id;
sectionIdTonavigationLink[id] = document.querySelectorAll('nav > ul > li > a[href=\\#' + id + ']') || null;
}
// throttle function, enforces a minimum time interval
function throttle(fn, interval) {
var lastCall, timeoutId;
return function () {
var now = new Date().getTime();
if (lastCall && now < (lastCall + interval) ) {
// if we are inside the interval we wait
clearTimeout(timeoutId);
timeoutId = setTimeout(function () {
lastCall = now;
fn.call();
}, interval - (now - lastCall) );
} else {
// otherwise, we directly call the function
lastCall = now;
fn.call();
}
};
}
function getOffset( el ) {
var _x = 0;
var _y = 0;
while( el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop ) ) {
_x += el.offsetLeft - el.scrollLeft;
_y += el.offsetTop - el.scrollTop;
el = el.offsetParent;
}
return { top: _y, left: _x };
}
function highlightNavigation() {
// get the current vertical position of the scroll bar
var scrollPosition = window.pageYOffset || document.documentElement.scrollTop;
// iterate the sections
for (var i = $sections.length-1; i >= 0; i--) {
var currentSection = $sections[i];
// get the position of the section
var sectionTop = getOffset(currentSection).top;
// if the user has scrolled over the top of the section
if (scrollPosition >= sectionTop - 250) {
// get the section id
var id = currentSection.id;
// get the corresponding navigation link
var $navigationLink = sectionIdTonavigationLink[id];
// if the link is not active
if (typeof $navigationLink[0] !== 'undefined') {
if (!$navigationLink[0].classList.contains('active')) {
// remove .active class from all the links
for (i = 0; i < $navigationLinks.length; i++) {
$navigationLinks[i].className = $navigationLinks[i].className.replace(/ active/, '');
}
// add .active class to the current link
$navigationLink[0].className += (' active');
}
} else {
// remove .active class from all the links
for (i = 0; i < $navigationLinks.length; i++) {
$navigationLinks[i].className = $navigationLinks[i].className.replace(/ active/, '');
}
}
// we have found our section, so we return false to exit the each loop
return false;
}
}
}
window.addEventListener('scroll',throttle(highlightNavigation,150));
この行では:
$('#navigation > ul > li > a').attr('href', id).addClass('active');
実際には、すべての$( '#navigation> ul> li> a')要素のhref属性を設定してから、それらすべてにアクティブクラスを追加します。あなたがする必要があるのは次のようなものです:
$('#navigation > ul > li > a[href=#' + id + ']')
そして、hrefがidと一致するaのみを選択します。理にかなっていますか?