Typecho Joe 主题 添加访目录

本文介绍如何在Typecho博客系统中自定义实现目录功能,包括解析文章目录、生成目录树、添加开关选项及响应式设计等步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Typecho 添加访目录

作者博客迁移到 Halo ,并使用从Joe 迁移过来的 halo-theme-joe2.0

  1. 效果图如下

    image-20221017205443351

  2. 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;
    }
    
  3. core/core.php 文件的 themeInit 函数中解析目录的代码

    # 当前载入的是文章阅读页面(包括主页相关文章)
    if ($self->is('single')) {
        /* 添加目录标签 */
        if (!_isMobile() && Helper::options()->JCatalog === "on") {
            $self->content = _createCatalog($self->content);
        }
        ...
    }
    
  4. 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);
    

    效果图如下:

    image-20221005213207230

  5. 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; ?>
    
  6. 加入 一些特别的 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;
      }
    };
    
  7. 加入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日更新 -> 加入在文章滑动时,目录自动滑动到可视范围功能

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

憶夣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值