• 手写一个文章目录插件


    手写一个文章目录插件。

    • 兼容博客园 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
        )
      )
    }
    

    如有错误或不足,欢迎指正!

  • 相关阅读:
    C++数组释放问题
    C# 线程与进程
    Inspector面板Debug模式
    Unity实现汉诺塔游戏
    Unity中的销毁方法
    如何修改Unity中脚本模板
    序列帧动画
    Unity中的射线和射线图层过滤使用方法
    简单第一人称射击游戏
    C# 集合和泛型
  • 原文地址:https://www.cnblogs.com/guangzan/p/12692795.html
Copyright © 2020-2023  润新知