zoukankan      html  css  js  c++  java
  • 手写一个文章目录插件

    手写一个文章目录插件。

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

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

  • 相关阅读:
    15. DML, DDL, LOGON 触发器
    5. 跟踪标记 (Trace Flag) 834, 845 对内存页行为的影响
    4. 跟踪标记 (Trace Flag) 610 对索引组织表(IOT)最小化日志
    14. 类似正则表达式的字符处理问题
    01. SELECT显示和PRINT打印超长的字符
    3. 跟踪标记 (Trace Flag) 1204, 1222 抓取死锁信息
    2. 跟踪标记 (Trace Flag) 3604, 3605 输出DBCC命令结果
    1. 跟踪标记 (Trace Flag) 1117, 1118 文件增长及空间分配方式
    0. 跟踪标记 (Trace Flag) 简介
    SpringBoot + Redis + Shiro 实现权限管理(转)
  • 原文地址:https://www.cnblogs.com/guangzan/p/12692795.html
Copyright © 2011-2022 走看看