手寫一個文章目錄插件


手寫一個文章目錄插件。

  • 兼容博客園 markdown 和 TinyMCE 編輯器
  • 給標題添加活躍樣式
  • 可選的固定位置

插件的配置

catalog: {
    enable: true,
    position: 'left',
},
  • enable 是否啟用
  • position 目錄固定的位置
    • left 固定在左側
    • right 固定在右側
    • sidebar '類似掘金文章目錄固定效果的效果'


代碼結構

import { pageName, userAgent, hasPostTitle, getClientRect, throttle } from '@tools'
const { enable, position } = opts.catalog

// 在這里寫幾個 func

function catalog() {
  // 在入口處做一些基本的判斷
  if (conditions) return 
  // 在這里執行上面的一些 func
}

// 導出插件
export default catalog  

import

  • pageName 返回當前頁面名稱,如果不是文章詳情頁則不必執行代碼
  • userAgent 返回用戶客戶端類型,移動端無需文章目錄
  • hasPostTitle 返回當前文章是否存在文章標題
  • getClientRect 返回元素相對與瀏覽器視口的位置

構建 html

思路:遍歷博客園隨筆內容子元素 DOM,通過正則表達式獲取標題,創建目錄 html 元素,並添加錨點鏈接。由於非 markdown 編輯器的標題沒有 id,需要在遍歷時添加 id,其值即為標題。有些情況下,非 markdown 編輯器的標題內容可能不直接被 h123 標簽所嵌套, 判斷處理即可。

function build() {
  let $catalogContainer = $(
    `<div id="catalog">
            <div class='catListTitle'><h3>目錄</h3></div>
        </div>`
  )
  const $ulContainer = $('<ul></ul>')
  const titleRegExp = /^h[1-3]$/

  $('#cnblogs_post_body')
    .children()
    .each(function () {
      if (titleRegExp.test(this.tagName.toLowerCase())) {
        let id
        let text

        if (this.id !== '') { 
          // 如果沒有標題上沒有id屬性,說明不是markdown編輯器
          id = this.id
          text = this.childNodes.length === 2 ? this.childNodes[1].nodeValue : this.childNodes[0].nodeValue
        } else {
          if (this.childNodes.length === 2) {
            // 從length === 2 開始判斷,因為標題中插入了一個 svg icon
            const value = this.childNodes[1].nodeValue
            text = value ? value : $(this.childNodes[1]).text()
          } else {
            const value = this.childNodes[0].nodeValue
            text = value ? value : $(this.childNodes[0]).text()
          }
          id = text.trim()
          $(this).attr('id', id)
        }

        const title = `
                            <li class='${this.nodeName.toLowerCase()}-list'>
                                <a href='#${id}'>${text}</a>
                            </li>
                        `

        $ulContainer.append(title)
      }
    })

  $($catalogContainer.append($ulContainer)).appendTo('#sideBar')
  setCatalogPosition()
}

固定目錄

接下來根據用戶配置的 position,將目錄固定在指定位置。

function setCatalogPosition() {
  const actions = {
    sidebar: () => {
      setCatalogToggle()
    },
    left: () => {
      $('#catalog').addClass('catalog-sticky-left')
    },
    right: () => {
      $('#catalog').addClass('catalog-sticky-right')
    },
  }

  actions[position]()
}

可以采用更為簡潔的寫法, 這里考慮到擴展性。

處理固定在側欄的情況

目錄固定在側欄時,原來的側邊欄滾動到不可見位置才顯示目錄,很簡單,我們只需要監聽滾動事件,獲取原側欄相對於視口的高度,當它超出屏幕,即高度小於0(此處使用小於10),則固定目錄。反之,則相反。

function setCatalogToggle() {
  if (position !== 'sidebar') return
  var p = 0,
    t = 0
  $(window).scroll(
    throttle(
      function () {
        const bottom = getClientRect(document.querySelector('#sideBarMain')).bottom
        if (bottom <= 0) {
          $('#catalog').addClass('catalog-sticky')
          p = $(this).scrollTop()
          t <= p ? $('#catalog').addClass('catalog-scroll-up') : $('#catalog').removeClass('catalog-scroll-up')
          setTimeout(function () {
            t = p
          }, 0)
        } else {
          $('#catalog').removeClass('catalog-sticky')
        }
      },
      50,
      1000 / 60
    )
  )
}

給標題添加活躍樣式

這一步實現思路和處理固定在側欄的情況基本一致,當一個文章目錄超出視口時,我們給對應的標題添加活躍的樣式就可以了。

function setActiveCatalogTitle() {
  $(window).scroll(
    throttle(
      function () {
        for (let i = $('#catalog ul li').length - 1; i >= 0; i--) {
          const titleId = $($('#catalog ul li')[i]).find('a').attr('href').replace(/[#]/g, '')
          const postTitle = document.querySelector(`#cnblogs_post_body [id='${titleId}']`)
          if (getClientRect(postTitle).top <= 10) {
            if ($($('#catalog ul li')[i]).hasClass('catalog-active')) return
            $($('#catalog ul li')[i]).addClass('catalog-active')
            $($('#catalog ul li')[i]).siblings().removeClass('catalog-active')
            return
          }
        }
      },
      50,
      1000 / 60
    )
  )
}

如有錯誤或不足,歡迎指正。代碼鏈接:https://gitee.com/guangzan/awescnb/tree/master/src/plugins/catalog


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM