Typecho插件实现添加文章目录的方法详解
作者:RIA爱好者 发布时间:2023-05-25 07:19:04
我的长博文不少,比较影响阅读体验,有必要添加一个文章目录功能。相比 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('/<h([1-4])(.*?)>(.*?)<\/h\1>/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 '<h'.$obj[1].$obj[2].' id="cl-'.$catalog_count.'">'.$obj[3].'</h'.$obj[1].'>';
}, $obj);
return $obj;
}
也可以在标题元素内添加 <a>
标签,然后该标签新增 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 = '<ul class="list">'."\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 .= '</li>'."\n";
} elseif ($catalog_depth > $prev_depth) {
$to_depth++;
$str .= '<ul class="sub-list">'."\n";
} else {
$to_depth2 = ($to_depth > ($prev_depth - $catalog_depth)) ? ($prev_depth - $catalog_depth) : $to_depth;
if ($to_depth2) {
for ($i=0; $i<$to_depth2; $i++) {
$str .= '</li>'."\n".'</ul>'."\n";
$to_depth--;
}
}
$str .= '</li>';
}
}
$str .= '<li class="item"><a class="link" href="#cl-'.$catalog_item['count'].'" rel="external nofollow" title="'.$catalog_item['text'].'">'.$catalog_item['text'].'</a>';
$prev_depth = $catalog_item['depth'];
}
for ($i=0; $i<=$to_depth; $i++) {
$str .= '</li>'."\n".'</ul>'."\n";
}
$str = '<section class="toc">'."\n".'<div class="title">文章目录</div>'."\n".$str.'</section>'."\n";
}
echo $str;
}
getCatalog
方法通过递归 $catalog
数组生成文章目录,接下来我们需要调用它。
2.函数
最好将放在右侧边栏中。为此在 public/aside.php
中添加如下代码:
<?php if ($this->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 < count; i++) {
var j = i;
// 滚动和点击时 index 相差 1,需要 autoScrolling 来区分
if (i > 0 && !autoScrolling) {
j = i - 1;
}
var headingTop = $(headings[i]).offset().top;
var listTop = distance * i / count
// 判断滚动条滚动距离是否大于当前滚动项可滚动距离
if (headingTop > top) {
var id = $(headings[j]).attr('id');
setItemActive(id);
// 如果目录列表有滑条,使被选中的下一元素可见
if (listTop > 0) {
// 向上滚动
if (listTop < itemHeight) {
listTop -= itemHeight;
} else {
listTop += itemHeight;
}
$(tocList).scrollTop(listTop)
}
break;
} else if (i === count - 1) {
// 特殊处理最后一个元素
var id = $(headings[i]).attr('id');
setItemActive(id);
if (listTop > 0) {
$(tocList).scrollTop(distance)
}
}
}
autoScrolling = false;
}, 100);
}
$(window).on('scroll', onScroll);
首先,在 onScroll
事件处理函数中遍历标题数组 headings
, 如果滚动条滚动距离 top
大于当前标题项 item
可滚动距离 headingTop
,再调用 setItemActive
函数,传入当前的标题项的 id
来判断文章目录激活状态。
如果目录列表有滑条,调用 jQuery 的 scrollTop
方法滚动目录列表滑条,使被选中目录项的上下元素可见,
现在文章目录基本上可用了,也还美观,后续可以考虑优化再封装成一个插件。
吐槽一下:Joe 主题太依赖jQuery了,修改起来费劲 ::(汗)。
来源:https://juejin.cn/post/7201047960825462843
猜你喜欢
- 注:我指一个网站被第三方网站以iframe的形式调用时,被调用网站的禁止策略 和 调用网站的突破禁止策略,跟XSS麽关系,但跟clickja
- 概述做日志分析工作的经常需要跟成千上万的日志条目打交道,为了在庞大的数据量中找到特定模式的数据,常常需要编写很多复杂的正则表达式。例如枚举出
- 网站改版,如何改?如果只是设计、功能和栏目等的稍微变动,这些很简单,从技术 上说并没有多少难度。只是对于网站本身的发展来说,没有多大的作用,
- 最近正在做首页,处理很棘手的浏览器兼容的问题,主要调试的浏览器为 IE6 ,IE7 ,FF3 ,Opera9.5 ,Safari3.1.2兼
- <% Function ReplaceUrl2(HTMLstr) Dim n,st
- 做程序开发的人都知道版本控制的重要性, 代码的管理好说,TFS/SVN/VSS/CVS,哪个都能用。但涉及到数据库的版本控制,就不是太好做的
- 如果我们的web应用有大量的异步请求,而这些异步请求是在web服务器认证的情况下,那当我们请求发生在服务器认证失效下,服务器自动302到登录
- 欣赏上一篇:用画为5.12地震受灾同胞们祈福 今年我们的祖国多灾多难 雪灾的阴影还没散去又发生了地震。中国插画 * 举办5.12地震祈幅绘画活
- js表单验证只能是写限定的东西大收集 代码如下:ENTER键可以让光标移到下一个输入框<input onkeydown=&q
- 一、前言:当数据库服务器建立好以后,我们首先要做的不是考虑要在这个支持数据库的服务器运行哪些受MySQL提携的程序,而是当数据库遭到破坏后,
- 即text-overflow:ellipsis,需要配合white-space:nowrap使用。运行代码:<div style=&q
- 研究(2)中讨论了栅格系统的基础知识。这一篇将集中探讨栅格系统的粒度问题。(注:如非特别指明,栅格系统均指24列960栅格系统)淘宝的首页(
- 一、垃圾还是经典网页技术更新很快,一个网站的界面设计寿命仅仅2-3年而已。不管是垃圾还是精品,都没有所谓的经典。经典只存在于是哪个首次成功创
- 各位大哥: 在javascript中如何取整?比如: var
- 什么是RC4算法呢?也许您还不知道,没关系我为您找了下相关资料方便大家查看;RC4加密算法 RC4加密算法是大名鼎鼎的RSA三人组
- 代码如下,另存为asp文件,请传到你的服务器上就可以了马上测一下<%Response.Expires = 0Response.Expi
- 删除备份和还原历史记录表中所有早于 oldest_date 的备份集的项目。由于执行备份或还原操作时会在备份和还原历史记录表中添加行,sp_
- 本例详细介绍了如何在wiondws XP下安装与配置MySQL5.0.37 ,图文并茂,相信对初学mysql的朋友有所帮助。1 点击MySQ
- 你可能在使用MySQL过程中,各种意外导致数据库表的损坏,而且这些数据往往是最新的数据,通常不可能在备份数据中找到。本章将讲述如何检测MyS
- 如何制作一个文本文件编辑器?我们也来做一个:newdoc.asp<%@ Language=VBScript %&g