【问题标题】:How do modify the class of the inline toc element with JavaScript?如何用 JavaScript 修改内联 toc 元素的类?
【发布时间】:2020-06-14 23:26:36
【问题描述】:

问题

您好,我正在创建一个简单的导航站点,并且想知道如何更改/添加一个类到右侧的内联 toc 的 li 元素之一,以使用 css 对其进行样式化

到目前为止我的页面。

https://startech-enterprises.github.io/docs/data-integration-and-etl/branches-and-loops-local.html

我想要实现的页面行为: https://docs.microsoft.com/en-us/dotnet/csharp/tutorials/intro-to-csharp/branches-and-loops-local

(如果你在主页面上下滚动,你会看到右侧toc元素的样式发生了变化)

在这里可以看到相同的行为: https://startech-enterprises.github.io/minimal-mistakes/docs/quick-start-guide/ (上下滚动页面时,请参阅右侧目录更改)

我尝试研究这些页面背后的 JavaScript,但很难理解,而且脚本看起来过于复杂。

有什么想法吗?

谢谢,

萨钦

注意 - 问题已得到解答,上面链接中的代码已更新,基于下面给出的答案

使用的代码

我目前拥有的 CSS:

.doc-outline ul li a {
  text-decoration: none;
}

.doc-outline ul li a:hover {
  text-decoration: underline;
}

.doc-outline li.selected {
  font-weight: 600;
  border-color: #0065b3;
}

.doc-outline a:visited {
  color: #0065b3;
}

HTML 的相关部分

   <div class="primary-holder column is-two-thirds-tablet is-three-quarters-desktop">
      <div class="columns has-large-gaps is-gapless-mobile">

        <!-- MAIN CONTENT GOES HERE --!>
        <div id="main-column" class="column is-full is-four-fifths-desktop">
          <main id="main" class ="content" lang="eng-us">
            <h1 id="learn-conditional-logic-with-branch-and-loop-statements">Learn conditional logic with branch and loop statements</h1>.....etc.
          </main>   
        </div>

       <!-- RHS TOC GOES HERE --!>
       <div class="right-sidebar column is-one-quarter is-one-fifth-desktop is-hidden-mobile is-hidden-tablet-only">
        <nav class="doc-outline is-fixed is-vertically-scrollable", id="affixed-right-sidebar">
        <nav id="side-doc-outline">
         <ul class="section-nav">
           <li class="toc-entry toc-h2"><a href="#make-decisions-using-the-if-statement">Make decisions using the if statement</a></li>
           <li class="toc-entry toc-h2"><a href="#make-if-and-else-work-together">Make if and else work together</a></li>
           <li class="toc-entry toc-h2"><a href="#use-loops-to-repeat-operations">Use loops to repeat operations</a></li>
           <li class="toc-entry toc-h2"><a href="#work-with-the-for-loop">Work with the for loop</a></li>
           <li class="toc-entry toc-h2"><a href="#created-nested-loops">Created nested loops</a>
           <ul>
            <li class="toc-entry toc-h3"><a href="#test">Test</a></li>
          </ul>
          </li>
          <li class="toc-entry toc-h2"><a href="#combine-branches-and-loops">Combine branches and loops</a></li>
          </ul>
       </nav>
      </nav>
     </div>

    </div>
   </div>   

JavaScript

编辑(上面链接中使用的最终 JS,结合了下面答案中的想法/cmets)- 可能简化一些代码块的范围..

突出显示正确的 RHS TOC 菜单项

(function rhsToc() {
// initialise global variables outside functions
let observer = new IntersectionObserver(handler, { threshold: [0] });
let selection;
let headings = [...document.querySelectorAll("#main h2, #main h3")];
let rhsToc = [...document.querySelectorAll("ul.section-nav a")];
let a = null;
let lastScroll = 0;
let headingMenuMap = headings.reduce((acc, h) => {
    let id = h.id;
    acc[id] = rhsToc.find(a => a.getAttribute("href") === "#" + id);
    return acc;
  }, {})

headings.forEach(elem => observer.observe(elem));

// detect scroll direction
scrollDetect();
let scrollDirection = [];
function scrollDetect(){
    var lastScroll = 0;
    window.onscroll = function() {
        let currentScroll = document.documentElement.scrollTop || document.body.scrollTop; // Get Current Scroll Value
        if (currentScroll > 0 && lastScroll <= currentScroll){
          lastScroll = currentScroll;
          scrollDirection = "down";
        }else{
          lastScroll = currentScroll;
          scrollDirection = "up"
        }
    };
  }

function handler(entries) {

    // Update selection with current entries.
    selection = (selection || entries).map( s => entries.find(e => e.target.id === s.target.id) || s);

    // keep only true values
    filteredArr = selection.filter(x => x.isIntersecting == true );

    // Find last visible/intersecting (use a copied array for that, since reverse is an in place method)
    let firstVisibleId = [...selection].find(x => x.isIntersecting) ? [...selection].find(x => x.isIntersecting).target.id : null;

    // Is a firstVisibleId returned? If not, then follow the below steps
    if (firstVisibleId === null){
        // were you scrolling down? - then do nothing
        if (scrollDirection == "down"){
            // do nothing!
        } else {
            // scrolling up - so remove 'selected' from current menu item, and add it to the menu item above it
            const current = document.querySelector(`#side-doc-outline > ul li.selected`);
            if (current) {
                current.classList.remove('selected');
            }
            // if there is no previous sibling with a class of 'toc-entry', you're at top of branch, so go up a level, provided you don't get to section-nav
            if(previousByClass(a.parentElement, "toc-entry") == null){
                parent_by_selector(a.parentElement, "ul:not(.section-nav)") ? parent_by_selector(a.parentElement, "ul:not(.section-nav)").parentElement.classList.add("selected") : null;
            } else {
                previousByClass(a.parentElement, "toc-entry") ? previousByClass(a.parentElement, "toc-entry").classList.add("selected") : null;
            }
        }
        return;
    }

    // otherwise, remove 'selected' from the active item in the RHS toc
    const current = document.querySelector(`#side-doc-outline > ul li.selected`);
    if (current) {
        current.classList.remove('selected');
    }

    // add 'selected' to the target item in the RHS toc
    for (s of selection) {
        let targetId = s.target.id;
        // get the entry from the generated map.
        a = headingMenuMap[targetId];
        if (firstVisibleId === targetId) {
            a.parentElement.classList.add("selected");
            return;
        } 
    };
}

})();

// WAIT TILL DOCUMENT HAS LOADED BEFORE INITIATING FUNCTIONS
document.addEventListener('DOMContentLoaded', tree);

【问题讨论】:

  • 试试 jQuery toggleClass 或 addClass
  • 问题解决了一半。除了我的页面,与docs.microsoft.com/en-us/dotnet/csharp/tutorials/… 的行为不太一样(例如,当视口中有两个 h2/h3 时,或者对于较长的部分,h2/h3 标签从视口中消失)
  • 问题完全基于 cmets 解决,来自 Paul 下面

标签: javascript html css


【解决方案1】:

我用paragraph 类包围了每个部分的内容,该类用于观察并在其内容中添加了一个类content - 实际上还没有使用,但我仍然建议这样做那个。

编辑:整合您的想法如何在多个交叉点的情况下选择元素。

https://jsbin.com/cufeyoreno/edit?js,output

let observer = new IntersectionObserver(handler, {
  threshold: [0.2]
});
let selection;
let paragraphs = [...document.querySelectorAll("#main .paragraph")];
let submenu = [...document.querySelectorAll("ul.section-nav a")];
let paragraphMenuMap = paragraphs.reduce((acc, p) => {
  let id = p.firstElementChild.id;
  acc[id] = submenu.find(a => a.getAttribute("href") === "#" + id);
  return acc;
}, {})

paragraphs.forEach(elem => observer.observe(elem));

function handler(entries) {

  // Update selection with current entries.
  selection = (selection || entries).map(
    s => entries.find(e => e.target.firstElementChild.id === s.target.firstElementChild.id) || s
  );

  // Find last visible/intersecting (use a copied array for that, since reverse is an in place method)
  let lastVisibleId = [...selection].reverse().find(x => x.isIntersecting).target.firstElementChild.id;

  for (s of selection) {
    let targetId = s.target.firstElementChild.id;
    // avoid searching the dom and just get the entry from our map.
    let a = paragraphMenuMap[targetId];

    if (lastVisibleId === targetId) {
      let parentElem = a;
      // ensure that parent menu entries are selected too
      while (parentElem = parentElem.parentElement.closest(".toc-entry")) {
        parentElem.classList.add("selected");
      }
    } else {
      a.parentElement.classList.remove("selected");
    }
  }
};

【讨论】:

  • @Sachin 我出去了一会儿。我需要一个堆栈溢出中断。所以,不要怀疑我身边没有更多的答案。 ;) 祝你的项目好运!
  • 谢谢保罗。我刚刚看到你的代码。我想我已经设法解决了多个条目可见的问题(使用上面更新的 JS 代码) - 我决定如果两个元素可见,则显示最低的元素。代码很长,因为 isIntersecting 类是只读的,所以我不得不创建另一个名为 visible 的数组 - 但我不确定代码是否可以简化。再次感谢你的帮助。我将再次检查是否可以使用您的代码解决问题的另一部分(长部分)。
  • @Sachin 检查这个:jsbin.com/cufeyoreno/edit?js,output 我整合了你的想法并改进和简化了它。我也编辑了我的帖子。现在我真的出去了。 ;)
  • 再次感谢!它工作得很好,但如果你向上滚动,最后一项仍然突出显示...... :(我现在正在研究一个完整的解决方案,合并(相当不情愿地)一个滚动事件......将很快发布解决方案
  • 好的——终于完成了。您设置选择器的一行代码替换了两个大代码块,所以这很有帮助!以上是迄今为止我能做到的最好的。我想将用于检测滚动方向的滚动功能放在处理程序函数中,但它仅在处理程序函数触发时更新,并且我正在得到故障行为(处理程序是异步函数?),所以我(不情愿地)放它在 window.onscroll 包装器中 - 现在......我确实得到了我想要的粘性菜单行为 - 不需要包装“p”标签。这比我最初想象的要困难得多!
【解决方案2】:

据我了解,您不想给它一个样式只是添加一个类? 如果您想直接为 li 赋予一些样式,例如你可以这样做

document.getElementsByTagName("li").addEventListener("click",()=>{
 this.style = "color:red";
})

但我猜你想添加已经准备好的类,在这种情况下你需要 ClasList

document.getElementById("myDIV").classList.add("mystyle");

添加

document.getElementById("myDIV").classList.remove("mystyle");

删除

document.getElementById("myDIV").classList.toggle("mystyle");

切换

在这种情况下,您需要向它们单独添加类,添加 ID 或相同的类以按类 [0]、类 [1] 等获取每个类... 你需要两件事

 `document.body.scrollTop;`

并为每个高度添加类,如上所示 例如

 if(document.body.scrollTop == 100px){
     document.getElementById("myDIV").classList.add("mystyle");
 }

如果您想从以前的版本中删除,还有一个更好的版本

if(document.body.scrollTop == 100px){
    for(let i = 0;i<classOfLis.length,i++){
    classOfLis[i].classList.remove("selected");
}
    document.getElementById("myDIV").classList.add("mystyle");
}

【讨论】:

  • 嗨 Nairi,我想添加到 li 的类只是称为“selected” - 请参阅上面的 CSS 脚本,它将对任何 li 应用正确的样式,并带有“selected”类。因此,JavaScript 需要做的就是将“选定”添加到正确的 li 标记的类中,当页面的该部分已滚动到时,这就是我试图弄清楚的方法。
  • 在这种情况下,您需要向它们单独添加类,添加 ID 或相同的类以按类 [0]、类 [1] 等获取每个...您需要两件事 document.body.scrollTop; 和每个高度添加类如上所示,例如if(document.body.scrollTop == 100px){ document.getElementById("myDIV").classList.add("mystyle");如果你想从之前的 if(document.body.scrollTop == 100px){ for(let i = 0;i
  • 感谢您的帮助奈里。我现在在上面有一个可行的解决方案,基于 Paul 提供的 cmets,经过进一步思考
猜你喜欢
  • 2019-03-12
  • 1970-01-01
  • 2017-06-28
  • 1970-01-01
  • 1970-01-01
  • 2010-09-16
  • 1970-01-01
相关资源
最近更新 更多