首页 > 解决方案 > 顶部菜单中的活动指示在用户手动滚动时保持同步

问题描述

我有一个包含几个部分的 HTML 页面,用户可以通过单击顶部的相应链接导航到这些部分。当用户通过该链接导航时,该链接将突出显示。

现在我希望当用户手动向上或向下滚动,并且看到不同的部分时,顶部菜单中的相应链接将突出显示。

我的尝试是在scroll事件处理程序中,但我在查找与当前滚动位置相对应的 HTML 部分锚点 ID 时遇到了问题。

请注意,我需要li动态生成 - 因此不能更改:ul根据我的项目的要求必须为空,因此必须在 JavaScript 中生成所有列表项和超链接(就像目前所做的那样)。

这是我的页面。我的问题出在scroll事件处理程序中:

/**
 * 
 * Manipulating the DOM exercise.
 * Exercise programmatically builds navigation,
 * scrolls to anchors from navigation,
 * and highlights section in viewport upon scrolling.
 * 
 * Dependencies: None
 * 
 * JS Version: ES2015/ES6
 * 
 * JS Standard: ESlint
 * 
*/

/**
 * Define Global Variables
 * 
*/


//  All sections - Navigation Bar - Fragment & AllLinks

const navigationBar = document.getElementById('navbar__list');
var navigationBarContainer = document.getElementById("landing__container");
const fragment = document.createDocumentFragment();
const pagesections = document.querySelectorAll('section');
const navigationBarLi = document.querySelectorAll('nav .landing__container ul li');

// adding new classes to sections to match with anchor id for scroll purposes
//document.getElementById('li').className('section')


// for each single section

/*function retrieveElementsById(ids) {
  var listIds = ids.split(" ");
  var arrayresults = [], item;
  for (var i = 0; i < listIds.length; i++){
    item = document.getElementById(idList[i]);
    if (item) {
        results.push(item);
    } 
  }
  return(results);
}





allSectionsinSingFnct(querySelectorAll("section1 section2 section3 section4 section5 section6 section7"))
*/
//

function sectionsidattr() {
  var sec1 = document.getElementById("section1");
  var sec2 = document.getElementById("section2");
  var sec3 = document.getElementById("section3");
  var sec4 = document.getElementById("section4");
  var sec5 = document.getElementById("section5");
  var sec6 = document.getElementById("section6");
  var sec7 = document.getElementById("section7");

}

const sectionsids = [
  'section1', '#section1',
  'section2', '#section2',
  'section3', '#section3',
  'section4', '#section4',
  'section5', '#section5',
  'section6', '#section6',
  'section7', '#section7'
]
//const elements = document.querySelectorAll(ids.map(id => `#${id}`).join(', '));
//const sectionidelements = document.querySelectorAll(sectionids.map(id => `#${id}`).join(', '))


/**
 * End Global Variables
 * Start Helper Functions
 *
 */

/**
 * End Helper Functions
 * Begin Main Functions
 *
 */


/* Reference
https://stackoverflow.com/questions/65407419/how-to-add-active-class-to-the-list-href-that-equal-showed-in-the-viewport-sec
*/

// Building the Navigation Bar

for (let pagesection of pagesections) {
  const pagelist = document.createElement('li');
  const pagelinks = document.createElement('a');
  const pagesectionId = pagesection.getAttribute('id');
  const pagesectionTitle = pagesection.getAttribute('data-nav');

  pagelinks.classList = 'menu__link';
  pagelinks.setAttribute('href', `#${pagesectionId}`);
  pagelinks.innerText = pagesectionTitle;
  fragment.appendChild(pagelist);
  pagelist.appendChild(pagelinks);


  // Smooth Scroll   
  pagelinks.addEventListener('click', function (event) {
    event.preventDefault();
    window.scrollTo({
      top: pagesection.offsetTop - 50,
      behavior: 'smooth'

    });
  });
}

navigationBar.appendChild(fragment);


const allpageLinks = navigationBar.querySelectorAll('a');

//Scroll to using anchor ID
var scrollanchorid = document.querySelectorAll("section");
function scrollToAnchor() {
  scrollToAnchor.scrollIntoView(true);
}

// Set the section as active when it is in the scope of the screen.
// vars at top pagesections & navigationBarLi


// when scrolling run the following function
window.addEventListener('scroll', () => {
  let current = ''; // no current section at the beginning
  //looping through all the sections

  pagesections.forEach(section => {
    const topSction = section.offsetTop;
    //added for future reference that the logic loops through the values of the page 
    //console.log(topSction);
    //retrieve sectionHeight

    console.log(pageYOffset);
    const sctionHeight = section.clientHeight;
    if (pageYOffset >= (topSction - sctionHeight / 1)) {
      current = section.getAttribute('id');
    }
    //Page Y Offset means how much we are scrolled here


  })


  //End of for each loop 
  // added for future refernce  
  //console.log(current);

  navigationBarLi.forEach(li => {
    li.classList.remove('active');
    if (li.classList.contains('current')) {
      li.classList.add('active')
    }

  })

  // Add class 'active' to section when near top of viewport 
  // Another for each loop for active classes
  const links = document.querySelectorAll('li');

  for (var i = 0; i < links.length; i++) {
    links[i].addEventListener("click", function () {
      var currenstate = document.getElementsByClassName("active");
      currenstate[0].className = currenstate[0].className.replace("active", "");
      this.className += "active";

    });
  };

});












//Set the menu item as active when the corresponding section is active.

/*Reference
https://www.w3schools.com/howto/howto_js_active_element.asp
*/






// Return Top Button 

function scrollTopfunction() {
  if (document.body.scrollTop > 25 ||
    document.documentElement.scrollTop > 25) {
    TopButton.style.display = "block";
  } else {
    TopButton.style.display = "none";
  }
}


// At user click return to top of page for chrome , safari , firefox & most modern browsers

function pushtopfunction() {
  document.documentElement.scrollTop = 0;
  document.body.scrollTop = 0;
}


/* User time out function when idle at 500 seconds */

setTimeout(function () { alert("User Time Out Message : 5 minutes"); }, 500000);
/*
 *
 * CSS written based on SMACSS architecture.
 * To learn more, visit: http://smacss.com/
 * 
 * For simplicity, no reset or normalize is added. 
 * To learn more, visit: https://css-tricks.com/reboot-resets-reasoning/
 *
*/



@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300&display=swap');

/* ---- Base Rules ---- */



*{
    padding: 0;
    margin: 0;
    box-sizing: border-box;
}

html{
    font-family: 'Roboto', sans-serif;
    scroll-behavior: smooth;
}



body {
    margin: 0;
    background: linear-gradient(to bottom, #c31432, #240b36);
    color: #fff;
}

ul {
    margin: 0;
    padding: 0;
    overflow: hidden;
    background: linear-gradient(to right top, #abbaab, #a34444);
}


section {
    position: relative;
    min-height: 100vh;
    width: 100%;
    height: 50px;
    display: flex;
    justify-content: center;
    align-items: center;
}

img {
    max-width: 100%;
}

button {
    cursor: pointer;
    border-radius: 12.5px;
}

/* Typeography General*/

header {
    position: absolute;
    top: 5px;
    bottom: 5px;
    align-items: center;
    text-align: center;
    background: transparent;
    text-decoration: blanchedalmond;
    text-shadow: black;
}

h1 {
    font-family: 'Fira Sans', sans-serif;
    border-radius: 50%;
    border-color: black;
    text-align: center;
    align-items: center;
    color: goldenrod;
    background: linear-gradient(to right top, #8e0e00, #1f1c18);
}

.main__hero {
    font-family: 'Fira Sans', sans-serif;
    font-size: large;
    top: 85px;
    float: middle;
    align-items: center;
    border-color: black;
    text-align: center;
    color: black;
    position: sticky;
}

@media only screen and (min-width: 35em) {
    h1 {
        font-size: large;
        text-align: center;
        align-items: center;
        color: goldenrod;
    }
}

h2 {
    border-bottom: 1px solid #cc1;
    font-family: 'Oxygen', Sans-Serif;
    font-size: 1.2em;
    color: azure;
}

p {
    line-height: 1.5em;
    font-size: 0.9em;
    color: #eee;
    word-spacing: 0.1em;
}

/* ---- Layout Rules ---- */

main {
    margin: 10vh 1em 10vh;
}

.main-hero {
    min-height: 40vh;
    padding-top: 3em;
}


/* Some sections features*/

section h2 {
    display: sticky;
    font-size: 7.7vh;
    top: 10px;
    bottom: 7px;
    text-shadow: black;
    color: cornsilk;
}

section h3 {
    display: flex;
    font-size: 2.0rem;
    top: 10px;
    bottom: 7px;
    text-shadow: black;
    color: burlywood;
}

/* ---- Module Rules ---- */


/* Return Top Button*/

.topbtnclass {
    padding: 15px;
    bottom: 20px;
    right: 10px;
    position: fixed;
    text-align: center;
    border-radius: 15px;
    outline: none;
    color: white;
    z-index: 99;
    font-size: 12.5px;
    cursor: pointer;
    align-items: center;
    background: linear-gradient(to right bottom, #304352, #d7d2cc);
    font-family: 'Oxygen', Sans-Serif;
}

/* Return Top Button Hover */

.topbtnclass:hover {
    color: goldenrod;
    display: flex;
    background: linear-gradient(to right bottom, #870000, #190a05);
}

/* Adding an active class */

.active, .a:hover {
    background: linear-gradient(to right bottom, #852121, #190a05);
    color: goldenrod;
    border-radius: 50%;
}

/* Navigation Styles*/

.navbar__menu {
    position: fixed;
    top: 0px;
    float: middle;
    background: linear-gradient(to right bottom, #304352, #d7d2cc);
    width: 100%;
    max-width: 2150px;
    margin:0 auto;
    text-align: center;
    padding: 10px;

}
.navbar__menu ul {
    position: relative;
    padding-left: 1px;
    padding-right: 1px;
    text-align: center;
    display: inline-block;
    background: linear-gradient(to right bottom, #304352, #d7d2cc);
    border-radius: 90%;

}

.navbar__menu li {
    display: inline-block;
    position: relative;
    
}


.navbar__menu li ul a {
    display: inline-block;
    padding: 7px 14px;
    text-decoration: none;
}

.navbar__menu li ul.active{
    background: linear-gradient(to right bottom, #c31432, #240b36);
    color: goldenrod;
}



.navbar__menu .menu__link {
    display: flex;
    padding: 1em;
    font-weight: bold;
    text-decoration: none;
    text-shadow: black;
    color: goldenrod;
    border-radius: 50%;
}

.navbar__menu .menu__link:hover {
    color: wheat;
    transition: ease 0.3s all;
}




/* Header Styles */

.page__header {
    background: linear-gradient(to right bottom, #c31432, #240b36);
    position: sticky;
    font-size: 2vw;
    width: 100%;
    z-index: 50;
    border-color: black;
}

/* Footer Styles */

.page__footer {
    background: #000;
    padding: 3em;
    color: #fff;
}

.page__footer p {
    color: #fff;
}

/* ---- Theme Rules ---- */

/* Landing Container Styles */

.landing__container {
    padding: 1em;
    text-align: center;
    align-items: center;
}

@media only screen and (min-width: 35em) {
    .landing__container {
        max-width: 50em;
        padding: 4em;
    }
}

section:nth-of-type(even) .landing__container {
    text-align: center;
    align-items: center;
}

/* Background Circles */

/* Note that background circles are created with psuedo elements before and after */

/* Circles appear to be random do to use of :nth-of-type psuedo class */

section:nth-of-type(odd) .landing__container::before {
    content: '';
    background: rgba(255, 255, 255, 0.187);
    position: fixed;
    z-index: -5;
    width: 50vh;
    height: 50vh;
    border-radius: 50%;
    opacity: 0;
    transition: ease 0.5s all;
}

section:nth-of-type(even) .landing__container::before {
    content: '';
    background: rgb(255, 255, 255);
    background: linear-gradient(0deg, rgba(255, 255, 255, .1) 0%, rgba(255, 255, 255, .2) 100%);
    position: fixed;
    top: 3em;
    right: 3em;
    z-index: -5;
    width: 40vh;
    height: 40vh;
    border-radius: 50%;
    opacity: 0;
    transition: ease 0.5s all;
}

section:nth-of-type(3n) .landing__container::after {
    content: '';
    background: rgb(255, 255, 255);
    position: absolute;
    right: 0;
    bottom: 0;
    z-index: -5;
    width: 10vh;
    height: 10vh;
    border-radius: 50%;
    opacity: 0;
    transition: ease 0.5s all;
}

section:nth-of-type(3n + 1) .landing__container::after {
    content: '';
    background: rgb(255, 255, 255);
    position: absolute;
    right: 20vw;
    bottom: -5em;
    z-index: -5;
    width: 15vh;
    height: 15vh;
    border-radius: 50%;
    opacity: 0;
    transition: ease 0.5s all;
}

/* ---- Theme State Rules ---- */

/* Section Active Styles */

/* Note: your-active-class class is applied through javascript. You should update the class here and in the index.html to what you set in your javascript file.  */

section.active {
    color: goldenrod;
    background: linear-gradient(to right bottom, #870000, #190a05);
}

section.active .landing__container::before {
    opacity: 1;
    animation: rotate 4s linear 0s infinite forwards;
}

section.active .landing__container::after {
    opacity: 1;
    animation: rotate 5s linear 0s infinite forwards reverse;
}

/* Section Active Styles Keyframe Animations */

@keyframes rotate {
    from {
        transform: rotate(0deg) translate(-1em) rotate(0deg);
    }
    to {
        transform: rotate(360deg) translate(-1em) rotate(-360deg);
    }
}

/* Active Class */

li a.active {
    color: goldenrod;
}

.active {
    color: goldenrod;
    background: linear-gradient(to right bottom, #870000, #190a05);
    cursor: pointer;
    position: relative;
}

.active:hover {
    color: goldenrod;
    background: linear-gradient(to right bottom, #870000, #190a05);
    position: relative;
}

/*Page sections Gradients by id*/
#section1 {
    /*border-radiuse at 25% to makesections more rounded*/
    border-radius: 25%;
    background-color: #fdb813;
    background-image: linear-gradient(315deg, #fdb813 0%, #788cb6 74%);
}


#section2 {
    border-radius: 25%;
    background-color: #edd812;
    background-image: linear-gradient(315deg, #edd812 0%, #766a65 74%);
    

}


#section3 {
    border-radius: 25%;
    background-color: #edd812;
    background-image: linear-gradient(315deg, #edd812 0%, #766a65 74%);

}


#section4 {
    border-radius: 25%;
    background-color: #fdb813;
    background-image: linear-gradient(315deg, #fdb813 0%, #788cb6 74%);
}

#section5 {
    border-radius: 25%;
    background-color: #edd812;
    background-image: linear-gradient(315deg, #edd812 0%, #766a65 74%);

}


#section6 {
    border-radius: 25%;
    background-color: #b3cdd1;
    background-image: linear-gradient(315deg, #b3cdd1 0%, #9fa4c4 74%);

}


#section7 {
    border-radius: 25%;
    background-color: #eaf818;
    background-image: linear-gradient(315deg, #eaf818 0%, #f6fc9c 74%);
}
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Manipulating the DOM</title>
  <!-- Load Google Fonts -->
  <link href="https://fonts.googleapis.com/css?family=Fira+Sans:900|Merriweather&display=swap" rel="stylesheet">
  <!-- Load Styles -->
  <link href="css/styles.css" rel="stylesheet">
</head>
  <!-- HTML Follows BEM naming conventions 
  IDs are only used for sections to connect menu achors to sections -->
  <link href="https://fonts.googleapis.com/css?family=Fira+Sans:900|Merriweather&display=swap" rel="stylesheet">
  <!-- HTML Follows BEM naming conventions 
  IDs are only used for sections to connect menu achors to sections -->
  <header class="page__header">
    <nav class="navbar__menu">
      <!-- Navigation starts as empty UL that will be populated with JS -->
     <div class="menucontainer">
       <ul id="navbar__list"></ul>
     </div>
      
    </nav>
</header>
  <main>
    <header class="main__hero">
      <h1>Landing Page </h1>
    </header>
    <!-- Each Section has an ID (used for the anchor) and 
    a data attribute that will populate the li node.
    Adding more sections will automatically populate nav.
    The first section is set to active class by default -->
    <section id="section1" data-nav="Section 1">
      <div class="landing__container">
        <h2>Section 1</h2>
        <p>Lorem ipsum dolor sit amet consectetur adipisicing elit.
          Quisquam fugit nobis eaque sunt aperiam molestiae cum in sapiente placeat iusto debitis expedita alias non,
          vitae velit cumque exercitationem, minima consectetur.
        </p>
      </div>
    </section>
    <section id="section2" data-nav="Section 2">
      <div class="landing__container">
        <h2>Section 2</h2>
        <p>Lorem ipsum dolor sit amet consectetur adipisicing elit.
          Quisquam fugit nobis eaque sunt aperiam molestiae cum in sapiente placeat iusto debitis expedita alias non,
          vitae velit cumque exercitationem, minima consectetur.
        </p>
      </div>
    </section>
    <section id="section3" data-nav="Section 3">
      <div class="landing__container">
        <h2>Section 3</h2>
        <p>Lorem ipsum dolor sit amet consectetur adipisicing elit.
          Quisquam fugit nobis eaque sunt aperiam molestiae cum in sapiente placeat iusto debitis expedita alias non,
          vitae velit cumque exercitationem, minima consectetur.
        </p>
      </div>
    </section>
    <section id="section4" data-nav="Section 4">
      <div class="landing__container">
        <h2>Section 4</h2>
        <img src="https://cdn.pixabay.com/photo/2015/01/15/16/16/staircase-600468_1280.jpg" alt=:"staircase-600468_1280"
          width="150" height="150">
        <img src="https://cdn.pixabay.com/photo/2015/12/08/00/39/steps-1081909_1280.jpg" alt=:"steps-1081909_1280"
          width="150" height="150">
        <img src="https://cdn.pixabay.com/photo/2014/03/08/22/32/escalator-283448_1280.jpg"
          alt=:"escalator-5899073_1280" width="150" height="150">
      </div>
    </section>

    <section id="section5" data-nav="Section 5">
      <div class="landing__container">
        <h2>Section 5</h2>
        <p>Lorem ipsum dolor sit amet consectetur adipisicing elit.
          Quisquam fugit nobis eaque sunt aperiam molestiae cum in sapiente placeat iusto debitis expedita alias non,
          vitae velit cumque exercitationem, minima consectetur.
        </p>
      </div>
    </section>

        <section id="section6" data-nav="Section 6">
          <div class="landing__container">
            <h2>Section 6</h2>
            <p>Lorem ipsum dolor sit amet consectetur adipisicing elit.
              Quisquam fugit nobis eaque sunt aperiam molestiae cum in sapiente placeat iusto debitis expedita alias
              non,
              vitae velit cumque exercitationem, minima consectetur.
            </p>
          </div>
        </section>

    <section id="section7" data-nav="Section 7" class="active">
      <div class="landing__container">
        <h2>Section 7</h2>
        <label for"email">Email:</label>
        </br>
        <input type="text" name="email">
        </br>
        <label for"Last_name.">Last Name:</label>
        </br>
        <input type="text" name="Last_name">
        </br>
        <label for"First_name.">First Name:</label>
        </br>
        <input type="text" name="First_name."></br>
        </form>
        </br>
        </br>
        <textarea class="text_area_class" rows="25" cols=50>
        Enterinquiry here.
        </textarea>
        </br>
        <input type="submit">
        <br>
      </div>
    </section>
    <button class="topbtnclass" onclick="pushtopfunction()">Top</button>

    <script src="js/app.js">
    </script>
  </main>
  <footer class="page__footer">
    <p>&copy Udacity</p>
  </footer>

标签: javascripthtmlcss

解决方案


有一些问题:

  • 表达pageYOffset >= (topSction - sctionHeight / 1)不正确。从顶部偏移量中减去部分的高度是没有意义的。相反,使您的循环可中断(使用for而不是forEach)并检查pageYOffset + top < topSction + sctionHeight. 如果是这样,则不再需要进一步搜索,因此跳出循环:我们找到了该部分。

  • 考虑到顶部的菜单栏使部分部分不可见,因此请确保上述点的逻辑考虑了此偏移量。定义topmenu.offsetTop + menu.clientHeight,元素在menu哪里。.navbar__menu

  • li.classList.contains('current')是一个永远不会成立的条件,因为您没有currentCSS 类。您想在此处引用变量 current,但请改用文字字符串。此外,current(例如“section3”)的值不会在li元素的类列表中找到。您可以href在子a元素的属性中查找它:

    li.children[0].href.endsWith(current)
    
  • navigationBarLi集合是空的,因为您在尚未构建菜单的那一刻初始化此变量。相反,您可以使用navigationBar.children,它将动态查找li元素。

综合考虑,在滚动事件处理程序的第一部分使用此代码:

  let current = '';
  const menu = document.querySelector('.navbar__menu');
  const top = menu.offsetTop + menu.clientHeight; // Add this offset
  
  for (let section of pagesections) { // make a for-loop, so you can break out
    const topSction = section.offsetTop;
    const sctionHeight = section.clientHeight;
    if (pageYOffset + top < topSction + sctionHeight) { // compare with end of section
      current = section.getAttribute('id');
      break; // <-- add break
    }
  }

  for (let li of navigationBar.children) { // the collection you had was empty
    li.classList.remove('active');
    if (li.children[0].href.endsWith(current)) { // This identifies the section's li
      li.classList.add('active');
    }
  }

推荐阅读