搜索

查看: 3176|回复: 11

[PHP] Typecho插件实现添加文章目录的方法详解

[复制链接]
发表于 2023-5-4 17:04:16 | 显示全部楼层 |阅读模式
Editor 2023-5-4 17:04:16 3176 11 看全部
目录
  • 添加文章标题锚点
  • 显示文章目录
  • 添加文章目录样式
  • 定位到文章
  • 定位到目录我的长博文不少,比较影响阅读体验,有必要添加一个文章目录功能。相比 Wordpress, Typecho 的插件就比较少了。我想找一个像掘金那样为文章添加目录的插件,没一个合适的。此类教程也不是很多,而且差不多都是前台 JavaScript 来实现的,感觉这样不如后台实现来的好。
    注意:我使用的是Joe主题7.3,其他主题文件路径可能不一样。

    添加文章标题锚点
    1.声明 createAnchor 函数
    在 core/functions.php 中添加如下代码:
    // 添加文章标题锚点
    function createAnchor($obj) {
      global $catalog;
      global $catalog_count;
      $catalog = array();
      $catalog_count = 0;
      $obj = preg_replace_callback('/(.*?)/i', function($obj) {
        global $catalog;
        global $catalog_count;
        $catalog_count ++;
        $catalog[] = array('text' => trim(strip_tags($obj[3])), 'depth' => $obj[1], 'count' => $catalog_count);
        return ''.$obj[3].'';
      }, $obj);
      return $obj;
    }
    也可以在标题元素内添加  标签,然后该标签新增 id 属性。
    createAnchor 函数主要是通过正则表达式替换文章标题H1~H4来添加锚点,接下来我们需要调用它。
    2.调用函数
    同样在 core/core.php 中的 themeInit 方法最后一行之前添加如下代码:
    if ($self->is('single')) {
      $self->content = createAnchor($self->content);
    }
    现在可以查看一下文章详情页面的源代码。文章的 H1~H4 元素应该添加了诸如 cl-1、cl-2 之类的 id 属性值。具体啥名不是关键,好记就行。

    显示文章目录
    1.声明 getCatalog 函数
    在 core/functions.php 中添加如下代码:
    // 显示文章目录
    function getCatalog() {  
      global $catalog;
      $str = '';
      if ($catalog) {
        $str = ''."\n";
        $prev_depth = '';
        $to_depth = 0;
        foreach($catalog as $catalog_item) {
          $catalog_depth = $catalog_item['depth'];
          if ($prev_depth) {
            if ($catalog_depth == $prev_depth) {
              $str .= ''."\n";
            } elseif ($catalog_depth > $prev_depth) {
              $to_depth++;
              $str .= ''."\n";
            } else {
              $to_depth2 = ($to_depth > ($prev_depth - $catalog_depth)) ? ($prev_depth - $catalog_depth) : $to_depth;
              if ($to_depth2) {
                for ($i=0; $i'."\n".'
    '."\n";
                  $to_depth--;
                }
              }
              $str .= '';
            }
          }
          $str .= '[url=#cl-'.$catalog_item['count'].']'.$catalog_item['text'].'[/url]';
          $prev_depth = $catalog_item['depth'];
        }
        for ($i=0; $i'."\n".'
    '."\n";
        }
        $str = ''."\n".'文章目录'."\n".$str.''."\n";
      }
      echo $str;
    }
    getCatalog 方法通过递归 $catalog 数组生成文章目录,接下来我们需要调用它。
    2.函数
    最好将放在右侧边栏中。为此在 public/aside.php 中添加如下代码:
    is('post')) getCatalog(); ?>
    注意:只有文章才使用目录,独立页面那些不需要,所以加了判断。Typecho 有一些神奇的 is 语法可以方便二次开发,可以访问它的官网文档了解更多。
    现在点击右侧的文章目录,可以滚动到相应的文章小标题位置了。

    添加文章目录样式
    可以看到,当前的文章目录还比较丑陋,我们来美化一下。在 assets/css/joe.post.min.scss 中添加如下 SCSS 代码:
    .joe_aside {
      .toc {
        position: sticky;
        top: 20px;
        width: 250px;
        background: var(--background);
        border-radius: var(--radius-wrap);
        box-shadow: var(--box-shadow);
        overflow: hidden;
        .title {
          display: block;
          border-bottom: 1px solid var(--classA);
          font-size: 16px;
          font-weight: 500;
          height: 45px;
          line-height: 45px;
          text-align: center;
          color: var(--theme);
        }
        .list {
          padding-top: 10px;
          padding-bottom: 10px;
          max-height: calc(100vh - 80px);
          overflow: auto;
          .link {
            display: block;
            padding: 8px 16px;
            border-left: 4px solid transparent;
            color: var(--main);
            text-decoration: none;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
            &:hover {
              background-color: var(--classC);
            }
            &.active {
              border-left-color: var(--theme);
            }
          }
        }
      }
    }
    为了方便操作,将 .toc 设置成 position: sticky; 实现了吸顶定位。考虑到文章目录可能很多,为 .toc 列表添加了 overflow: auto;,如代码第 3 ~ 4 行。
    由于 .joe_header(主题标头)也使用了吸顶定位,导致和文章目录有遮挡,所有加了 has_toc .joe_header 来取消页面主题标头的吸顶功能,如下代码:
    .has_toc {
      .joe_header {
        position: relative;
      }
    }
    定位到文章
    要显示文章目录当前选中项的状态,需要用到 JavaScript 给选中项添加一个 active 样式。在 assets/js/joe.post_page.js 中添加如下代码:
    var headings = $('.joe_detail__article').find('h1, h2, h3, h4');
    var links = $('.toc .link');
    var tocList = document.querySelector('.tocr > .list');
    var itemHeight = $('.toc .item').height();
    var distance = tocList.scrollHeight - tocList.clientHeight;
    var timer = 0;
    // 是否自动滚动
    var autoScrolling = true;
    function setItemActive(id) {
      links.removeClass('active');
      var link = links.filter("[href='#" + id + "']")
      link.addClass('active');
    }
    function onChange() {
      autoScrolling = true;
      if (location.hash) {
        id = location.hash.substr(1);
        var heading = headings.filter("[id='" + id + "']");
        var top = heading.offset().top - 15;
        window.scrollTo({ top: top })
        setItemActive(id)
      }
    }
    window.addEventListener('hashchange', onChange);
    // hash没有改变时手动调用一次
    onChange();
    由于布局和滚动动画的影响,导致锚点定位有点偏差。我们再 setItemActive 函数中用 scrollTo 或 scrollIntoView 来纠正。另外,我们希望有锚点的链接可以直接定位,因此监听了 hashchange 事件。点击文章目录测试一下定位,再手动键入锚点测试一下,应该都没啥问题。

    定位到目录
    目前可以从文章目录定位到文章标题了,是单向定位,双向定位还需要实现滚动文章内容时定位到文章目录的当前项。正如我们马上能想到的,需要监听 window 的 scroll 事件,如下代码:
    function onScroll() {
      if (timer) {
        clearTimeout(timer);
      }
      timer = setTimeout(function () {
        var top = $(window).scrollTop();
        var count = headings.length;
        for (var i = 0; i  0 && !autoScrolling) {
            j = i - 1;
          }
          var headingTop = $(headings).offset().top;
          var listTop = distance * i / count
          // 判断滚动条滚动距离是否大于当前滚动项可滚动距离
          if (headingTop > top) {
            var id = $(headings[j]).attr('id');
            setItemActive(id);
            // 如果目录列表有滑条,使被选中的下一元素可见
            if (listTop > 0) {
              // 向上滚动
              if (listTop  0) {
              $(tocList).scrollTop(distance)
            }
          }
        }
        autoScrolling = false;
      }, 100);
    }
    $(window).on('scroll', onScroll);
    首先,在 onScroll 事件处理函数中遍历标题数组 headings, 如果滚动条滚动距离 top 大于当前标题项 item 可滚动距离 headingTop,再调用 setItemActive 函数,传入当前的标题项的 id 来判断文章目录激活状态。
    如果目录列表有滑条,调用 jQuery 的 scrollTop 方法滚动目录列表滑条,使被选中目录项的上下元素可见,
    现在文章目录基本上可用了,也还美观,后续可以考虑优化再封装成一个插件。
    吐槽一下:Joe 主题太依赖jQuery了,修改起来费劲 ::(汗)。
    到此这篇关于Typecho插件实现添加文章目录的方法详解的文章就介绍到这了,更多相关Typecho添加文章目录内容请搜索知鸟论坛以前的文章或继续浏览下面的相关文章希望大家以后多多支持知鸟论坛!
  • 发表于 2023-6-28 21:49:33 | 显示全部楼层
    dxf17 2023-6-28 21:49:33 看全部
    论坛不能没有像楼主这样的人才啊!我会一直支持知鸟论坛。
    发表于 2023-6-29 21:07:53 | 显示全部楼层
    普通人物怨 2023-6-29 21:07:53 看全部
    我看不错噢 谢谢楼主!知鸟论坛越来越好!
    发表于 2023-6-29 22:27:31 | 显示全部楼层
    Gordon520 2023-6-29 22:27:31 看全部
    我看不错噢 谢谢楼主!知鸟论坛越来越好!
    发表于 2023-6-30 00:35:54 | 显示全部楼层
    胡37 2023-6-30 00:35:54 看全部
    感谢楼主的无私分享!要想知鸟论坛好 就靠你我他
    发表于 2023-6-30 14:17:04 | 显示全部楼层
    风吹吹蛋蛋疼风w 2023-6-30 14:17:04 看全部
    既然你诚信诚意的推荐了,那我就勉为其难的看看吧!知鸟论坛不走平凡路。
    发表于 2023-6-30 17:49:39 | 显示全部楼层
    冀苍鸾 2023-6-30 17:49:39 看全部
    这个帖子不回对不起自己!我想我是一天也不能离开知鸟论坛
    发表于 2023-6-30 21:08:51 | 显示全部楼层
    惜颜705 2023-6-30 21:08:51 看全部
    楼主,我太崇拜你了!我想我是一天也不能离开知鸟论坛
    发表于 2023-7-3 11:56:48 | 显示全部楼层
    术数古籍专卖疤 2023-7-3 11:56:48 看全部
    楼主太厉害了!楼主,I*老*虎*U!我觉得知鸟论坛真是个好地方!
    发表于 2023-7-3 17:31:07 | 显示全部楼层
    幸福341 2023-7-3 17:31:07 看全部
    楼主太厉害了!楼主,I*老*虎*U!我觉得知鸟论坛真是个好地方!
    • 您可能感兴趣
    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则 返回列表

    RSS订阅| 小黑屋| 知鸟论坛 |网站地图
    本站资源来自互联网用户收集发布,如有侵权请邮件联系处理。 联系邮箱E-mail:zniao@foxmail.com
    快速回复 返回顶部 返回列表