Before you talk about this plugin, you can go to the website bubuzou.com/ to experience the effect of this plugin, to get a general impression.

What can you gain by reading this article?

  1. withJSDirect manipulationDOM
  2. The browserBOMThe relevant knowledge
  3. Performance optimization thinking
  4. Plug-in Design Thinking

background

When we use Markdown to write articles, if we post them on our own blog or contribute to a platform like Nuggets, the content will be parsed into HTML by the browser. The first level (#), second level (##) and third level title (###) in Markdown correspond to the article title, first level table of contents and second level table of contents of the page respectively. Based on this, we can make good use of the generated page to achieve a directory, which is more intuitive and convenient to browse the content of the article, and this is the original reason WHY I want to achieve a directory function for the blog article page.

The plugin is based on Hexo generated web pages, and currently only supports level 1 and level 2 directories, because having two levels is a good way to break down a piece of content as a single article.

Demand analysis

location

The first thing I’m going to do is place the article’s table of contents on the right side of the article, and float there instead of scrolling with the browser. Because we’re used to reading articles from top to bottom and left to right, I want to see the content first, not the table of contents; In addition, since both the homepage and article list page of my blog are the browsing area on the left and the operation area on the right, placing the directory on the right can keep the layout of the whole site unified and convenient for operation.

What needs to be done

  1. First, we need to generate a table of contents according to the secondary and tertiary headings of the content of the article.
  2. Then, as the page scrolls from top to bottom, highlight the current title in the right directory with a fixed distance between the current title position and the top of the browser’s viewable area, as shown in the figure above.
  3. Scroll the list of directories when appropriate, so that the currently highlighted subdirectories appear inside the scroll area, and as far as possible in the middle of the scroll area;
  4. When clicking on a subdirectory, highlight the currently clicked directory and scroll the content of the article to the corresponding directory so that the distance from the title of the article corresponding to the clicked directory to the top of the visual area is equal to a fixed value.

What is the mechanism for scrolling directories

Point 3 of “scroll through directories when appropriate,” but when is the appropriate time? Can the directory scroll, and how to scroll is divided into the following five cases:

  1. When the directory can be fully displayed in the scroll area, that is, when the height of the scroll area is greater than the total height of all subitems of the directory, directory scrolling is not carried out, as shown in figure 1 above.
  2. A directory needs to scroll when it cannot be fully displayed in the scroll area. So all you need to do is scroll through the directory within a defined area, and any subitems outside that area will be hidden automatically. By scrolling through the directory, we can realize that the first and last two sub-items of the directory list can be well displayed in the delimited area. As the page scrolls, directories are rolled from beginning to end, so the scrolling range is from the top of the first subdirectory to the bottom of the last subdirectory;
  3. When the page is at the top of the page, the currently highlighted subdirectory must be the first one. As the page scrolls down, the highlighted position also moves down. When the highlighted position moves to the upper part of the directory scroll area, the directory scroll is not performed.
  4. When the first subdirectory is attached to the top of the scrolling area and the highlight position continues to scroll below the middle, directory scrolling is required. The scrolling distance is the height difference between the current highlight directory position and the middle position of the scrolling area, as shown in Figure ③ to Figure ④ above.
  5. If the height difference is so large that the last subelement cannot touch the bottom of the scrolling area after scrolling, the scrolling will not be carried out according to this height difference, and the actual scrolling distance is the distance between the last subdirectory and the bottom of the scrolling area, as shown in Figure ④ to Figure ⑤ above.

Function implementation

Since the article page is generated by Hexo based on Markdown, it has its own specific HTML format, which looks something like this:

<h2 id="Take your pick of seven component communication methods.">
    <a href="" class="headerlink" title="Take your pick of seven component communication methods."></a>Take your pick of seven component communication methods</h2>
<! -- Here is part of the article content -->
<h3 id="props-on-emit">
    <a href="" class="headerlink" title="props/@on+$emit"></a>
    props/@on+$emit
</h3>
<! -- Here is part of the article content -->
<h3 id="$attrs and $listeners." ">
    <a href="" class="headerlink" title="$attrs and $listeners." "></a>$attrs and $listeners</h3>
<! -- Here is part of the article content -->
Copy the code

You can see that all secondary headings (H2) and tertiary headings (H3) are under the same parent element, and that each heading contains a link with the headerLink class name, as well as the title attribute.

At this point we can easily get all the titles:

let arContentAnchor = document.querySelectorAll('.headerlink')
Copy the code

The result of this line of code will return a NodeList, which we can use to generate the directory.

Generated directory

Generating a directory is nothing more than generating a string of HTML, what else is there to do? The first step is to determine what parts the directory contains. As shown in the figure above, there are about three parts: the directory bar, the serial number and the title. The HTML structure can be determined:

<div class="arCatalog">
    <div class="arCatalog-line"></div>
    <div class="arCatalog-body">
        <dl>
            <dd class="arCatalog-tack1 on">
                <span class="arCatalog-index">1</span>
					<a href="#">Take your pick of seven component communication methods</a>
					<span class="arCatalog-dot"></span>
            </dd>
        </dl>
    </div>
</div>
Copy the code

Arcatalog-line represents the catalog bar, arcatalog-body is the scroll area, DL is the scroll list, DD is the catalog subitem, arcatalog-index is the catalog number, and the subcatalog title is placed in the link. With HTML, the next thing to do is to write the style of the directory, after writing the style is a lot, so I won’t post it here.

Is this the end of generating directories? No, since the browser viewable area is not fixed, we need to calculate the height of the scrolling area where the directory is located.

Scroll height = N directory subitems * actual height of subitems

< span style = “box-sizing: border-box; color: RGB (50, 50, 50); line-height: 20px; font-size: 14px! Important; word-break: inherit! Important;”

The actual height of the child = the row height of the child

If I have n subentries, what is n? In the Y-axis direction of the directory, in addition to the directory, there is also the menu at the top, and appropriate white space is needed for aesthetics, so:

N = (viewport height – (top menu height + white space height))/subitem line height

So, finally, we can calculate the roll height:

let DEFAULT = {
    lineHeight: 28.// The line height of each menu is 28
    moreHeight: 10.// The line to the left of the menu has more height than the menu
    surplusHeight: 180.// Except menu height + white height
    delay: 200.// The delay time of anti-shake
    duration: 200.// The duration of the scrolling animation
    toTopDistance: 80.// Trigger highlighting within the height of the viewport
    selector: '.headerlink'.// Selector for the title tag in the article content
}

// maxCatalogCount is n from the n subentries mentioned above
let maxCatalogCount = Math.floor((window.innerHeight - DEFAULT.surplusHeight) / DEFAULT.lineHeight)  

// The height of the scroll area
let catalogHeight = arContentAnchor.length > maxCatalogCount ? maxCatalogCount * DEFAULT.lineHeight : arContentAnchor.length * DEFAULT.lineHeight;
Copy the code

The complete directory generation function code is as follows:

// Generate a directory
function generateCatalog(){
    let catalogHeight = arContentAnchor.length > maxCatalogCount ? maxCatalogCount * DEFAULT.lineHeight : arContentAnchor.length * DEFAULT.lineHeight;
    let retStr = `
        <div class="arCatalog">
        <div class="arCatalog-line" 
        style="height: ${catalogHeight + DEFAULT.moreHeight}px"></div>
        <div class="arCatalog-body" 
        style="max-height: ${catalogHeight}px; height: ${catalogHeight}px">
        <dl style="margin-top: ${marginTop}px">`;
    	
    let h2Index = 0,
        h3Index = 1,
        acIndex = ' ',
        tagName = ' ',
        index = 0;
    	
    for (let currNode of arContentAnchor) {
        tagName = currNode.parentElement.tagName
        if ( tagName === 'H3' ) {
            acIndex = `${h2Index}.${h3Index++}`
            className = 'arCatalog-tack2'
        } else {
            acIndex = ++h2Index
            h3Index = 1
            className = 'arCatalog-tack1'
        }
        retStr += `
            <dd class="${className} ${index++ === lastOnIndex ? 'on' : ' '}">
            <span class="arCatalog-index">${acIndex}</span>
            <a href="#">${currNode.title}</a>
            <span class="arCatalog-dot"></span>
            </dd>`
    };
    retStr += `</dl></div></div>`
        
    document.getElementById('arAnchorBar').innerHTML = retStr
}
Copy the code

Set the scroll listening event

Add a scroll event to the window to listen for something to be done while scrolling, in this case setting the highlight and scroll directory.

window.addEventListener('scroll'.function() {
    setHighlight()
}, false)

// Directories need to be scrolled only when the total number of directories exceeds the maximum number of directories that can be scrolled
if (catalogLength > maxCatalogCount) {
	window.addEventListener('scroll'.function() {
    	scrollCatalog()
	}, false)}Copy the code

So you can listen for browser scroll events to do something. This will cause the function to be called frequently, which will cause performance problems. In fact, we prefer to execute the function only once when the scroll starts and ends.

// Anti-shake: The event will be executed once after n seconds. If the event is triggered again within n seconds, the timer will be reset.
function debounce(fn, delay = 200) {
    return function(args) {
        const _this = this
        clearTimeout(fn.id)
        fn.id = setTimeout(function() {
            fn.apply(_this, args)
        }, delay)
    }
}
Copy the code

Then we just need to change the function in the Scroll listening callback to the following:

window.addEventListener('scroll'.function() {
    debounce(setHighlight, DEFAULT.delay)()
    debounce(resetStatus, DEFAULT.delay)()
}, false)

if (catalogLength > maxCatalogCount) {
    window.addEventListener('scroll'.function() {
        debounce(scrollCatalog, DEFAULT.delay)()
    }, false)}Copy the code

Highlight the current directory

Before we begin, let’s review an API that returns the size of an element and its position relative to the viewport:

Element.getBoundingClientRect()
Copy the code

As mentioned in requirements analysis, the principle of highlighting is that the distance between the current title position and the top of the browser’s viewable area should be less than or equal to a fixed value:

arContentAnchor[index].getBoundingClientRect().top <= DEFAULT.toTopDistance
Copy the code

So when traversing the list of arContentAnchor, the position of an item is less than a fixed value and the difference is the smallest, the corresponding directory of the item should be set to highlight:

let curr = document.querySelector('.arCatalog .on')
curr.classList.remove('on')

let nextOnIndex = 0,
    currNode;

while (nextOnIndex < arContentAnchor.length) {
    currNode = arContentAnchor[nextOnIndex]
    if (currNode.getBoundingClientRect().top <= DEFAULT.toTopDistance) {
        nextOnIndex++
    } else {
        break
    }
}
nextOnIndex = nextOnIndex === arContentAnchor.length ? nextOnIndex - 1 : nextOnIndex;
let catalogDd = document.querySelectorAll('.arCatalog dd')
catalogDd[nextOnIndex].classList.add('on')
Copy the code

All looks fine up to this point, but the code above has a performance problem. As soon as the page scrolls, it will search from the first directory to the last directory until it finds the right one, which is too many iterations.

We know that scrolling is nothing more than scrolling up or down at the current position. If we remember nextOnIndex as the index before scrolling, we can reduce the number of iterations by adding, subtracting and adding the nextOnIndex according to the scrolling direction. Sounds like a good idea. Let’s try it.

First, we need to determine whether the current scroll is up or down, according to the offset before and after the two scrolls:

Scroll up = offset after scroll < offset before scroll

let lastSH = window.pageYOffset

// Get the scrolling direction of the last page
function getScrollDirection() {
    let sh = window.pageYOffset, ret = 'bottom'
    if (sh < lastSH) {
        ret = 'top'
    }
    lastSH = sh
    return ret
}
Copy the code

Knowing the scrolling direction, we can write optimized code to set the highlighting:

let curr = document.querySelector('.arCatalog .on')
let nextOnIndex = onIndex;
if (defaultDirec === 'bottom') {
    while (nextOnIndex < catalogLength) {
        let currTop = arContentAnchor[nextOnIndex].getBoundingClientRect().top
        if ( currTop > DEFAULT.toTopDistance && nextOnIndex > 0){
            nextOnIndex--
            break
        }
        nextOnIndex++
    }
} else {
    while (nextOnIndex >= 0) {
        let currTop = arContentAnchor[nextOnIndex].getBoundingClientRect().top
        if ( currTop <= DEFAULT.toTopDistance){
        	   break
        }
        nextOnIndex--
    }
}
nextOnIndex = nextOnIndex === arContentAnchor.length ? nextOnIndex - 1 : nextOnIndex < 0 ? 0 : nextOnIndex 
catalogDd[nextOnIndex].classList.add('on')
Copy the code

The number of traversals after optimization is significantly reduced, and the number of traversals is basically less than or equal to the difference between the directory index before and after scrolling. Although the number of iterations has been significantly reduced after optimization, I still want to optimize it again. What?

Many articles have long pages, so there is a function to go back to the top. Imagine if you scroll to the bottom of the page and go back to the top, how many times will the optimized code you just wrote traverse? The answer is: the number of iterations will be the total number of subentries in the directory. I can tell you that there are 43 subdirectories in the 34 articles of the experience address mentioned at the beginning of the article, so I need to go through 43 times. I really cannot accept the result, so I will optimize it again.

Secondary optimization is mainly to deal with the marginal problem, that is, when the scroll to the end and the end of the judgment, the final secondary optimization after the highlighting of the current directory function is as follows:

// Highlight the current directory
function setHighlight(){
    defaultDirec = getScrollDirection()
    
    if (hasStopSetHighlight) {
        return
    }
    let {
        scrollTop,
    } = document.scrollingElement;
    	
    let curr = document.querySelector('.arCatalog .on')
    
    let onIndex = [].indexOf.call(catalogDd, curr),  // The index is currently highlighted
        nextOnIndex = onIndex;  // Highlight the index after scrolling
        
    curr.classList.remove('on')
    
    let scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight
    if (arContentAnchor[catalogLength - 1].getBoundingClientRect().top <= DEFAULT.toTopDistance || 
        window.innerHeight + window.pageYOffset === scrollHeight) {  / / the tail
        lastOnIndex = catalogLength - 1
        catalogDd[lastOnIndex].classList.add('on')}else if (scrollTop <= firstDdTop) {  The top / /
        catalogDd[0].classList.add('on')
        lastOnIndex = 0
    } else {  // Middle: use the cache to search directly from the last onIndex position
        if (defaultDirec === 'bottom') {
            while (nextOnIndex < catalogLength) {
                let currTop = arContentAnchor[nextOnIndex].getBoundingClientRect().top
                if ( currTop > DEFAULT.toTopDistance && nextOnIndex > 0){
                    nextOnIndex--
                    break
                }
                nextOnIndex++
            	}
        } else {
            while (nextOnIndex >= 0) {
                let currTop = arContentAnchor[nextOnIndex].getBoundingClientRect().top
                if ( currTop <= DEFAULT.toTopDistance){
                    break
                }
                nextOnIndex--
            }
        }
        nextOnIndex = nextOnIndex === catalogLength ? nextOnIndex - 1 : nextOnIndex < 0 ? 0 : nextOnIndex 
        lastOnIndex = nextOnIndex
        catalogDd[nextOnIndex].classList.add('on')}}Copy the code

Scroll to the directory

As explained in the previous requirements analysis, there are three scenarios when the browser scrolls down:

  1. After scrolling, the highlight subdirectory is in the upper part of the scrolling area, that is, above the median line. In this case, the directory does not scroll, as shown in figure 1 above.
  2. The scrolling subdirectory is in the lower part of the scrolling area, that is, below the median line. In this case, the scrolling distance of the directory will be the height difference between the bottom position of the scrolling subdirectory and the median line, as shown in figure ② above.
  3. The highlighted directory after scrolling is below the median line and the last subdirectory needs to be close to the bottom of the scrolling area. In this case, the scrolling distance of the directory will be the height difference between the bottom of the scrolling list and the bottom of the scrolling area, as shown in figure ③.
let catalogBody = document.querySelector('.arCatalog-body'), initBodyTop = catalogBody.. getBoundingClientRect().top, bodyMidBottom = initBodyTop +Math.ceil((maxCatalogCount / 2 )) * DEFAULT.lineHeight;  // The median line position
    
if (curr.bottom + (maxCatalogCount / 2) * DEFAULT.lineHeight <= bodyBCR.bottom) {  // Top half
    / / not scroll
} else if (curr.bottom - bodyMidBottom < list.bottom - bodyBCR.bottom) {  // Below the median
    marginTop += -Math.floor((curr.bottom - bodyMidBottom ) / DEFAULT.lineHeight) * DEFAULT.lineHeight
} else if (bodyBCR.bottom <= list.bottom) {  // When the remaining roll distance
    marginTop = bodyBCR.bottom - initDlBottom
}
Copy the code

Similarly, when the browser scrolls up, it does a good job of figuring out the scrolling logic:

if (bodyBCR.top + (maxCatalogCount / 2) * DEFAULT.lineHeight <= curr.top) {
    / / not scroll
} else if (bodyMidBottom - curr.top < bodyBCR.top - list.top) {
    marginTop += Math.floor((bodyMidBottom - curr.top) / DEFAULT.lineHeight) * DEFAULT.lineHeight
} else if (list.top <= bodyBCR.top) {
    marginTop = 0
}
Copy the code

The final scrolling directory function complete code:

// Automatically scroll the directory tree so that the currently highlighted directory is visible
function scrollCatalog() {
    let currentCatalog = document.querySelector('.arCatalog .on');
    
    let curr = currentCatalog.getBoundingClientRect(),
        list = catalogDl.getBoundingClientRect();
    
    if (defaultDirec === 'bottom') {  // Scroll down
        if (curr.bottom + (maxCatalogCount / 2) * DEFAULT.lineHeight <= bodyBCR.bottom) {  // Top half
            / / not scroll
        } else if (curr.bottom - bodyMidBottom < list.bottom - bodyBCR.bottom){  // Below the median
            marginTop += -Math.floor((curr.bottom - bodyMidBottom ) / DEFAULT.lineHeight) * DEFAULT.lineHeight
        } else if (bodyBCR.bottom <= list.bottom) {  // When the remaining roll distance
            marginTop = bodyBCR.bottom - initDlBottom
        }
    } else {  // Scroll up
        if (bodyBCR.top + (maxCatalogCount / 2) * DEFAULT.lineHeight <= curr.top) {
            / / not scroll
        } else if (bodyMidBottom - curr.top < bodyBCR.top - list.top) {
            marginTop += Math.floor((bodyMidBottom - curr.top) / DEFAULT.lineHeight) * DEFAULT.lineHeight
        } else if (list.top <= bodyBCR.top) {
            marginTop = 0
        }
    }
    catalogDl.style.marginTop = marginTop + 'px'
}
Copy the code

Subdirectory click event

When clicking on a subdirectory, you need to do two things. The first is to scroll to the corresponding directory, and then highlight the currently clicked directory.

Scroll the page to the corresponding directory location:

// Bind events to directory subentries
let catalogDd = document.querySelectorAll('.arCatalog dd');
    
catalogDd.forEach((curr, index) = > {
    curr.addEventListener('click'.function(e) {
        e.preventDefault()
        let currTop = arContentAnchor[index].getBoundingClientRect().top
        document.documentElement.scrollTop = document.body.scrollTop = currTop + window.pageYOffset - DEFAULT.toTopDistance
    }, false)});Copy the code

This implementation of the page rolling is no problem, is not good experience, suddenly from a position to another position, it is abrupt, can you point to the animation effect? JQuery like animate()? Okay, so let’s try to do that.

I’m not going to use them this time. Instead, I’m going to use requestAnimationFrame, which is added in HTML5. This is an API designed specifically for browsers to implement animations. It is also a timer, but compared with the other two, it does not need to pass the time, because the passed callback function has its own parameter DOMHighResTimeStamp, which indicates the time when the callback function is fired.

In addition, the callbacks in requestAnimationFrame are typically executed 60 times per second, or roughly every 16.6 milliseconds, but in most browsers that follow W3C recommendations, the number of callbacks usually matches the number of browser screen refreshes. That’s it for requestAnimationFrame. Let’s get straight to the core of animation scrolling:

Each scrolling distance = (scrolling distance/animation duration) * The difference between the execution time of each animation and the first execution time + the current scrolling distance

Look directly at the full animation implementation of the scroll function:

// Scroll to target position
function scrollToDest(destScrollTop) {
    let startTime;   
    let currScrollTop = window.pageYOffset;
    let duration = 200;
    
    function step(timestamp) {
        if(! startTime) { startTime = timestamp }const elapsed = Math.round(timestamp - startTime)
        const distance = elapsed * ((Math.floor(destScrollTop) - currScrollTop) / duration) + currScrollTop
            
        document.documentElement.scrollTop = document.body.scrollTop = distance
          
        if (elapsed < duration) {
            window.requestAnimationFrame(step)
        }
    }
    window.requestAnimationFrame(step)
}
Copy the code

Ok, now that we have the animation function, we need to rewrite the click event in the subdirectory and add a transition effect to the scroll to make the experience more comfortable:

let catalogDd = document.querySelectorAll('.arCatalog dd');

catalogDd.forEach((curr, index) = > {
    curr.addEventListener('click'.function(e) {
        e.preventDefault()
        let currTop = arContentAnchor[index].getBoundingClientRect().top
        scrollToDest(currTop + window.pageYOffset - DEFAULT.toTopDistance)
    }, false)});Copy the code

Now that the first thing is done, do the second thing, highlighting the current click on a subdirectory:

// Bind events to directory subentries
let catalogDd = document.querySelectorAll('.arCatalog dd');

catalogDd.forEach((curr, index) = > {
    curr.addEventListener('click'.function(e) {
        e.preventDefault()
        hasStopSetHighlight = true
        document.querySelector('.arCatalog .on').classList.remove('on')
        catalogDd[index].classList.add('on')
        lastOnIndex = index
        let currTop = arContentAnchor[index].getBoundingClientRect().top
        scrollToDest(currTop + window.pageYOffset - DEFAULT.toTopDistance)
    }, false)});Copy the code

That’s fine, but because I click on a subdirectory, the page will scroll, and scrolling will trigger setHighlight to highlight the directory, so what I’ve done here is I’ve used a global variable hasStopSetHighlight to say that when I click on a subdirectory, SetHighlight is not highlighted.

What if the browser viewport height changes

Because our scrolling height is calculated according to the height of the browser viewport, if the browser viewport height changes, then to scroll the page at this time, it will certainly be a problem. So all you need to do is separate out the viewport height logic and put it into a function. When you listen for the viewport height change, execute the function.

First, write the listener function, also using the anti-shake function processing:

window.addEventListener('resize'.function(e) {
    debounce(initCatalog, DEFAULT.delay)()
}, false)
Copy the code

Then go and pull out the relevant logic:

/ / initialization
function initCatalog() {
    let tempHeight = window.innerHeight
    	
    if(viewPortHeight ! == tempHeight) { viewPortHeight = tempHeight maxCatalogCount =Math.floor((viewPortHeight - DEFAULT.surplusHeight) / DEFAULT.lineHeight)
        
        generateCatalog()
        
        catalogLength = arContentAnchor.length
        lastSH = window.pageYOffset
        catalogBody = document.querySelector('.arCatalog-body')
        catalogDl = document.querySelector('.arCatalog dl')
        catalogDd = document.querySelectorAll('.arCatalog dd')
        bodyBCR = catalogBody.getBoundingClientRect()
        initBodyTop = bodyBCR.top
        initDlBottom = initDlBottom || catalogDl.getBoundingClientRect().bottom
        firstDdTop = firstDdTop || catalogDd[0].getBoundingClientRect().top,
        bodyMidBottom = initBodyTop + Math.ceil((maxCatalogCount / 2 )) * DEFAULT.lineHeight;
    	
        // Bind events to directory subentries
        catalogDd.forEach((curr, index) = > {
            curr.addEventListener('click'.function(e) {
                e.preventDefault()
                hasStopSetHighlight = true
                document.querySelector('.arCatalog .on').classList.remove('on')
                catalogDd[index].classList.add('on')
                lastOnIndex = index
                let currTop = arContentAnchor[index].getBoundingClientRect().top
                scrollToDest(currTop + window.pageYOffset - DEFAULT.toTopDistance)
            }, false)}); }}Copy the code

When the height of the browser viewport changes, there is one detail that needs to be mentioned: the margin-top and the highlighting position of the scrolling directory are expected to change, so we need to pre-store them using global variables such as marginTop and lastOnIndex.

The assembly

The above code is all about extracting different function points into functions to operate on, which is a bit messy, so we need to see what a complete directory plug-in would look like.

/* * articleCatalog V2.0 * Copyright(C) 2016 by Bulandent * Date: 2017-5-27 16:10:41 * Updated: 2020-10-10 17:40:04 **/

let articleCatalog = (function() {
    if ( document.querySelectorAll('.headerlink').length === 0 || window.innerWidth < 900 ) {
        return function(){};
    }
    let DEFAULT = {
        lineHeight: 28.// The line height of each menu is 28
        moreHeight: 10.// The line to the left of the menu has more height than the menu
        surplusHeight: 180.// Except menu height + white height
        delay: 200.// The delay time of anti-shake
        duration: 200.// The duration of the scrolling animation
        toTopDistance: 80.// Trigger highlighting within the height of the viewport
        selector: '.headerlink'.// Selector for the title tag in the article content
    }
    return function(args) {
        DEFAULT = Object.assign(DEFAULT, args)
            
        let arContentAnchor = document.querySelectorAll(DEFAULT.selector),
            catalogLength = arContentAnchor.length,
            maxCatalogCount = 0.// Maximum number of directories that the viewport can hold
            viewPortHeight = 0.// The height of the viewport
            marginTop = 0.// The initial scrolling distance of the menu
            defaultDirec = 'bottom'.// Default scroll direction
            lastSH = 0.// Get the initial page scroll distance
            lastOnIndex = 0.// Last highlighted directory index
            catalogBody = [],             // .arCatalog-body
            catalogDl = null.// .arCatalog-body dl
            catalogDd = [],		         // .arCatalog-body dd
            initBodyTop = 0.// Top of the visible area of the directory
            initDlBottom = 0.// Directory dl bottom
            firstDdTop = 0.// Top of the first dd
            bodyMidBottom = 0.// The bottom of dd in the middle of the directory's visual area
            bodyBCR = null.// The boundary value of the visual area of the directory
            hasStopSetHighlight = false;  // Highlight the current directory directly when clicking a subitem of the directory, without triggering setHighlight via the Scroll event
            
        initCatalog()
        	
        window.addEventListener('scroll'.function() {
            debounce(setHighlight, DEFAULT.delay)()
        }, false)
            
        if (catalogLength > maxCatalogCount) {
            window.addEventListener('scroll'.function() {
                debounce(scrollCatalog, DEFAULT.delay)()
            }, false)}window.addEventListener('resize'.function(e) {
            debounce(initCatalog, DEFAULT.delay)()
        }, false)
        	
        // Declare the initialization function initCatalog here
        // generateCatalog is declared here
        // Set the highlighting function setHighlight
        // Declare the scrollCatalog function
        // Declare the animation to implement the scrollToDest function
        // declare the anti-shock function debounce
        // Declare getScrollDirection
        function resetStatus() {
            if (hasStopSetHighlight) {
                hasStopSetHighlight = false}}}} ());Copy the code

The use of plug-in

To use articleCatalog in the actual page, just import articleCatalog. Js and call the function directly:

articleCatalog()
Copy the code

Of course, some parameters can be passed in the call. The parameters are described as follows:

ArticleCatalog ({lineHeight: 28, // the lineHeight of each menu is 28 moreHeight: 10, // the line on the left side of the menu is more than the height of the menu surplusHeight: Duration: 200, // Animation duration of scrolling toTopDistance: 80, // Highlights selector: '. Headerlink ', // title tag selector in article content})Copy the code

Note that the parameters passed in are also passed in blind, and need to match the style of the plug-in, otherwise the process is prone to problems. For example, if you pass lineHeight: 24 on a page when the true height of the subdirectory is 28px, it will not work.

This plugin is used only if the HTML structure of the article page meets the above mentioned structure and only supports two levels of subdirectories.

Thank you for reading

First of all, thank you for reading this article. I believe you deserve the reward for your time. Look forward to your coming again. Not enjoying it? Check out the original:

  • 34 Facts about Vue THAT I can tell you
  • Small program upgrade WePY2 tread pit record
  • The JS data types that a beginner – intermediate front-end must know
  • Vue-test-utils + Jest Unit Testing Introduction and Practice