Typecho 添加访目录
作者博客迁移到 Halo ,并使用从Joe 迁移过来的 halo-theme-joe2.0
-
效果图如下
-
在
core/function.php
文件中添加函数,解析目录/* 生成目录树 */ function _createCatalog($html) { global $id; global $catalog; $id = 0; $catalog = array(); return preg_replace_callback('/<h([1-6])[^>]*>.*?<\/h\1>/s', static function ($match) { global $id; global $catalog; $tag = $match[0]; $level = $match[1]; $item = array( 'id' => 'index' . $id, 'level' => $level, 'title' => trim(strip_tags($tag)), 'sub' => array() ); $current = array(); if ($catalog) { $current = &$catalog[count($catalog) - 1]; } // 根 if (!$catalog || (isset($current['level']) && $level <= $current['level'])) { $catalog[] = $item; } else { while (is_array($current['sub'])) { // 父子关系 if ($current['level'] == $level - 1) { $current['sub'][] = $item; break; } // 后代关系,不存在子菜单 if (!$current['sub']) { for ($i = $current['level'] + 1; $i < $level; $i++) { $current['sub'][] = array( 'level' => $i, 'sub' => array() ); $current = &$current['sub'][0]; } $current['sub'][] = $item; break; } // 后代关系,存在子菜单 $current = &$current['sub'][count($current['sub']) - 1]; } } $id++; return "<span class=\"anchor-fix\" id=\"{$item['id']}\" name=\"{$item['id']}\"></span>" . $tag; }, $html); } /* 输出目录树 */ function _treeMenu() { global $catalog; echo '<div>' . _buildMenuHtml($catalog) . '</div>'; } /** * 构建目录树,生成索引 * * @access public * @param $tree * @param bool $include * @return string */ function _buildMenuHtml($tree, bool $include = true): string { $menuHtml = ''; foreach ($tree as $menu) { /**默认给第一个链接添加 class: .active */ $current = ($menu['id'] === 'index0') ? 'active' : ''; if (!isset($menu['id']) && $menu['sub']) { $menuHtml .= _buildMenuHtml($menu['sub'], false); } elseif ($menu['sub']) { $menuHtml .= "<li class=\"item\"><a data-scroll class=\"link {$current}\" href=\"#{$menu['id']}\" title=\"{$menu['title']}\">{$menu['title']}</a>" . _buildMenuHtml($menu['sub']) . "</li>"; } else { $menuHtml .= "<li class=\"item\"><a data-scroll class=\"link {$current}\" href=\"#{$menu['id']}\" title=\"{$menu['title']}\">{$menu['title']}</a></li>"; } } if ($include) { $menuHtml = '<ul class="list">' . $menuHtml . '</ul>'; } return $menuHtml; }
-
在
core/core.php
文件的themeInit
函数中解析目录的代码# 当前载入的是文章阅读页面(包括主页相关文章) if ($self->is('single')) { /* 添加目录标签 */ if (!_isMobile() && Helper::options()->JCatalog === "on") { $self->content = _createCatalog($self->content); } ... }
-
在
functions.php
文件中添加目录开关$JCatalog = new Typecho_Widget_Helper_Form_Element_Select( 'JCatalog', array('off' => '关闭(默认)', 'on' => '开启'), 'off', '文章页面和自定义页面将显示目录树(分辨率大于1200像素才会显示)', '介绍:开启后,文章页面和自定义页面将显示目录树' ); $JCatalog->setAttribute('class', 'joe_content joe_other'); $form->addInput($JCatalog);
效果图如下:
-
在
post.php
文件中的<div class="joe_container">
和<section class="joe_aside__item menutree">
之间加入相关代码<?php if ($this->options->JCatalog === "on" && !_isMobile()): ?> <script src="https://fastly.jsdelivr.net/npm/jquery.scrollintoview@1.9.4/jquery.scrollintoview.min.js"></script> <!-- 文章目录代码 --> <section class="joe_aside__item joe_catalog"> <a class="icon-active" href="javascript:" onclick="hiddenHandler()" title="展开目录"> <svg class="catalog-icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="28" height="28"> <path d="M192.037 287.953h640.124c17.673 0 32-14.327 32-32s-14.327-32-32-32H192.037c-17.673 0-32 14.327-32 32s14.327 32 32 32zM192.028 543.17h393.608c17.673 0 32-14.327 32-32s-14.327-32-32-32H192.028c-17.673 0-32 14.327-32 32s14.327 32 32 32zM832.161 735.802H192.037c-17.673 0-32 14.327-32 32s14.327 32 32 32h640.124c17.673 0 32-14.327 32-32s-14.327-32-32-32zM705.162 671.594l160-160-160-160z" fill="#000"></path> </svg> </a> <div class="joe_aside__item-title"> <svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="28" height="28"> <path d="M838.3 895.9H197.9c-53.9 0-97.7-43.8-97.7-97.7V236.7c0-53.9 43.8-97.7 97.7-97.7h640.3c53.9 0 97.7 43.8 97.7 97.7v561.4c0.1 53.9-43.7 97.8-97.6 97.8zM197.9 203.8c-18.1 0-32.9 14.8-32.9 32.9v561.4c0 18.1 14.8 32.9 32.9 32.9h640.3c18.1 0 32.9-14.8 32.9-32.9V236.7c0-18.1-14.8-32.9-32.9-32.9H197.9z" fill="#666666"></path> <path d="M695.1 455.2H341.2c-17.9 0-32.4-14.5-32.4-32.4s14.5-32.4 32.4-32.4h353.9c17.9 0 32.4 14.5 32.4 32.4s-14.5 32.4-32.4 32.4zM695.1 578.2H341.2c-17.9 0-32.4-14.5-32.4-32.4s14.5-32.4 32.4-32.4h353.9c17.9 0 32.4 14.5 32.4 32.4s-14.5 32.4-32.4 32.4zM695.1 701.2H341.2c-17.9 0-32.4-14.5-32.4-32.4s14.5-32.4 32.4-32.4h353.9c17.9 0 32.4 14.5 32.4 32.4s-14.5 32.4-32.4 32.4zM379.1 281.1c-17.9 0-32.4-14.5-32.4-32.4V115.4c0-17.9 14.5-32.4 32.4-32.4s32.4 14.5 32.4 32.4v133.2c0 17.9-14.5 32.5-32.4 32.5zM657.1 281.1c-17.9 0-32.4-14.5-32.4-32.4V115.4c0-17.9 14.5-32.4 32.4-32.4s32.4 14.5 32.4 32.4v133.2c0 17.9-14.5 32.5-32.4 32.5z" fill="#666666"></path> </svg> <span class="text">目录</span> <span class="line"></span> <a href="javascript:" onclick="hiddenHandler()" title="隐藏目录"> <svg class="icon-1" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="18" height="18" style=""> <path d="M192.037 287.953h640.124c17.673 0 32-14.327 32-32s-14.327-32-32-32H192.037c-17.673 0-32 14.327-32 32s14.327 32 32 32zM832.161 479.169H438.553c-17.673 0-32 14.327-32 32s14.327 32 32 32h393.608c17.673 0 32-14.327 32-32s-14.327-32-32-32zM832.161 735.802H192.037c-17.673 0-32 14.327-32 32s14.327 32 32 32h640.124c17.673 0 32-14.327 32-32s-14.327-32-32-32zM319.028 351.594l-160 160 160 160z" fill="#000"></path> </svg> </a> </div> <div class="joe_aside__item-contain" onclick="itemClick(event)"> <?php _treeMenu(); ?> </div> </section> <script> /* 获取整个目录侧边栏对象 */ const aside = document.getElementsByClassName("joe_catalog")[0]; /* 获取容器 title 的高度 */ const titleHeight = aside.children[1].offsetHeight; /* 获取容器高度 */ var containHeight = aside.children[2].offsetHeight; // 定义一个函数来修改目录的显示长度,从而使侧边栏能自适应目录的高度,避免出现大片空白部分 if (document.documentElement.clientHeight - 240 < containHeight) { containHeight = document.documentElement.clientHeight - 240; } aside.children[2].style.height = (containHeight + 20) + "px"; aside.style.height = (titleHeight + containHeight + 25) + "px"; // 初始化 const JCatalog = localStorage.getItem("JOE_CATALOG") if (JCatalog === null) { localStorage.setItem("JOE_CATALOG", "on") } if (localStorage.getItem("JOE_CATALOG") === "off") { aside.classList.add("active"); } else { aside.classList.remove("active"); } // 监听点击事件,将点击的目录高亮 const itemClick = (event) => { const href = event.target.getAttribute("href"); markSwitch(href); } // 隐藏和显示目录 const hiddenHandler = () => { localStorage.setItem("JOE_CATALOG", localStorage.getItem("JOE_CATALOG") === "off" ? "on" : "off") if (localStorage.getItem("JOE_CATALOG") === "off") { aside.classList.add("active"); } else { aside.classList.remove("active"); } } // 监听滚动事件,将滚动到的目录高亮 window.onscroll = () => { // 触发间隔 if (Date.now() - lastScroll > 100) { lastScroll = Date.now(); handleScroll(); } } // 上一次触发目录修改事件 var lastScroll = Date.now(); // 所有目录锚点元素 var catalogElements = undefined; // 切换目录高亮 const markSwitch = (tag) => { const links = document.querySelectorAll(".joe_catalog .link"); for (const link of links) { if (link.getAttribute("href") === tag) { link.classList.add("active"); $(link).scrollintoview({ duration: 2, direction: "vertical", viewPadding: { y: 20 } }); } else { link.classList.remove("active"); } } if (!!(window.history && history.pushState)) { const sUrl = window.location.href; const sUrlNew = sUrl.replace(/#.*$/, "") + tag; history.pushState(null, null, sUrlNew); } } // 获取元素可视距离顶部的距离 const getElementViewTop = (element) => { var actualTop = element.offsetTop; var current = element.offsetParent; while (current !== null) { actualTop += current.offsetTop; current = current.offsetParent; } if (document.compatMode == "BackCompat") { var elementScrollTop = document.body.scrollTop; } else { var elementScrollTop = document.documentElement.scrollTop; } return actualTop - elementScrollTop; } // 滚动事件处理函数 const handleScroll = () => { // 查询目录 if (catalogElements === undefined) { catalogElements = document.querySelectorAll("span.anchor-fix") } for (let i = 0; i < catalogElements.length; i++) { const element = catalogElements[i]; const top = getElementViewTop(element); if (top > 0 && top < 500) { markSwitch("#" + element.id); break; } else if (i + 1 < catalogElements.length) { const nextElement = catalogElements[i + 1]; const nextTop = getElementViewTop(nextElement); if (nextTop > 500) { markSwitch("#" + element.id); break; } } } } </script> <?php endif; ?>
-
加入 一些特别的 JS
在 joe.global.js 中最底部的 /* 头部滚动 */ JS中修改 handleHeader 为如下格式并编译为 min.js
const handleHeader = (diffY) => { if (window.pageYOffset >= $(".joe_header").height() && diffY <= 0) { if (flag) return; $(".joe_header").addClass("active"); $(".joe_aside .joe_aside__item:last-child").css("top", $(".joe_header").height() - 60 + 15); $(".joe_catalog").css("top", $(".joe_header").height() - 60 + 15); flag = true; } else { if (!flag) return; $(".joe_catalog").css("top", $(".joe_header").height() + 15); $(".joe_header").removeClass("active"); $(".joe_aside .joe_aside__item:last-child").css("top", $(".joe_header").height() + 15); flag = false; } };
-
加入SCSS样式信息
在
joe.post.min.scss
文件最上方加入以下SCSS样式信息并编译该文件/* 目录样式 */ .joe_catalog { position: sticky; width: 250px; background: var(--background); margin: 15px 15px 15px 0; overflow-y: auto; transition: top 0.35s, width 0.35s, height 0.35s; &.active { width: 45px; height: 45px !important; border-radius: 3px; transition: width 0.35s, height 0.35s; .joe_aside__item-title { display: none; } .joe_aside__item-contain { display: none; } a.icon-active { display: unset; } } a.icon-active { position: absolute; margin: 9px; display: none; &:hover { color: var(--theme); } } .joe_aside__item-title { a { position: absolute; right: 20px; &:hover { color: var(--theme); } } } .joe_aside__item-contain { overflow-y: auto; margin: 0 !important; padding: 0 !important; } .list { margin: 5px 0 5px 15px; .item { margin: 2px 0; .link { display: block; position: relative; color: var(--routine); max-width: 95%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 3px 0 3px 15px; &.active { color: var(--theme); background-color: rgba(80, 80, 80, .05); border-left-color: var(--theme); border-left-style: solid; border-left-width: 3px; transition: all 0.2s; } &:hover { color: var(--theme); background-color: rgba(80, 80, 80, .05); border-left-color: var(--theme); border-left-style: solid; border-left-width: 3px; transition: all 0.2s; } } } } } /* 锚点跳转定位 */ .anchor-fix { display: block; position: relative; /* 偏移量 */ top: -120px; } /*在宽度小于 1200px的设备上隐藏 */ @media (max-width: 1200px) { .joe_catalog { display: none; } }
本文章修改自 知识分子没文化
主要修改为:把插件形式的目录修改为主题自己的目录,修改了一些CSS样式
2022年11月19日更新 -> 加入在文章滑动时,目录自动滑动到可视范围功能