文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

如何使用Vue3实现文章目录功能

2023-06-29 12:41

关注

这篇文章主要为大家展示了“如何使用Vue3实现文章目录功能”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“如何使用Vue3实现文章目录功能”这篇文章吧。

前言

这一段时间一直在做一个博客项目 Kila Kila Blog,找了一圈发现没有特别满足自己需求的目录组件,所以决定自己动手,完成一个满足以下预期目标的目录组件:

完成后的目录组件如下图左侧所示:

如何使用Vue3实现文章目录功能

实现过程

由于标题之间有父子的关系,所以我们应该用树数据结构来解决这个问题。我们遍历文章容器中的所有标签,如果遇到 <h2><h3>这类标签,就创建一个节点,将其放到列表中,之后使用 v-for 指令来生成目录就行了。下面分析一下每个节点需要有哪些属性。

一个树的节点,应该具有的属性包括:父节点的指针 parent、子节点的指针列表 children,因为一个节点代表一个标题,所以还要包含:标题的 ID号 id(用于 v-for 的 key),标题名 name(添加了标题的序号)、原始标题名 rawName 和标题的可见性 isVisible,当我们点击标题时,应该滚动到标题的位置,所以还要有 scrollTop 属性。在我们遍历文章容器中的所有标签时,需要判断当前遇到的标签和上一个标签之间的父子关系,所以要有一个 level 属性代表每一个节点的等级。下面是具体实现代码:

<template>    <div class="catalog-card" v-if="Object.keys(titles).length > 0">        <div class="catalog-card-header">            <div>                <span                    ><font-awesome-icon                        :icon="['fas', 'bars-staggered']"                        class="catalog-icon"                /></span>                <span>目录</span>            </div>            <span class="progress">{{ progress }}</span>        </div>        <div class="catalog-content">            <div                v-for="title in titles"                :key="title.id"                @click="scrollToView(title.scrollTop)"                :class="[                    'catalog-item',                    currentTitle.id == title.id ? 'active' : 'not-active',                ]"                :                v-show="title.isVisible"                :title="title.rawName"            >                {{ title.name }}            </div>        </div>    </div></template><script>import { reactive, ref } from "vue";export default {    name: "KilaKilaCatalog",    setup(props) {        let titles = reactive(getTitles());        let currentTitle = reactive({});        let progress = ref(0);        // 获取目录的标题        function getTitles() {            let titles = [];            let levels = ["h2", "h3", "h4"];            let articleElement = document.querySelector(props.container);            if (!articleElement) {                return titles;            }            let elements = Array.from(articleElement.querySelectorAll("*"));            // 调整标签等级            let tagNames = new Set(                elements.map((el) => el.tagName.toLowerCase())            );            for (let i = levels.length - 1; i >= 0; i--) {                if (!tagNames.has(levels[i])) {                    levels.splice(i, 1);                }            }            let serialNumbers = levels.map(() => 0);            for (let i = 0; i < elements.length; i++) {                const element = elements[i];                let tagName = element.tagName.toLowerCase();                let level = levels.indexOf(tagName);                if (level == -1) continue;                let id = tagName + "-" + element.innerText + "-" + i;                let node = {                    id,                    level,                    parent: null,                    children: [],                    rawName: element.innerText,                    scrollTop: element.offsetTop,                };                if (titles.length > 0) {                    let lastNode = titles.at(-1);                    // 遇到子标题                    if (lastNode.level < node.level) {                        node.parent = lastNode;                        lastNode.children.push(node);                    }                    // 遇到上一级标题                    else if (lastNode.level > node.level) {                        serialNumbers.fill(0, level + 1);                        let parent = lastNode.parent;                        while (parent) {                            if (parent.level < node.level) {                                parent.children.push(node);                                node.parent = parent;                                break;                            }                            parent = parent.parent;                        }                    }                    // 遇到平级                    else if (lastNode.parent) {                        node.parent = lastNode.parent;                        lastNode.parent.children.push(node);                    }                }                serialNumbers[level] += 1;                let serialNumber = serialNumbers.slice(0, level + 1).join(".");                node.isVisible = node.parent == null;                node.name = serialNumber + ". " + element.innerText;                titles.push(node);            }            return titles;        }        // 监听滚动事件并更新样式        window.addEventListener("scroll", function () {            progress.value =                parseInt(                    (window.scrollY / document.documentElement.scrollHeight) *                        100                ) + "%";            let visibleTitles = [];            for (let i = titles.length - 1; i >= 0; i--) {                const title = titles[i];                if (title.scrollTop <= window.scrollY) {                    if (currentTitle.id === title.id) return;                    Object.assign(currentTitle, title);                    // 展开节点                    setChildrenVisible(title, true);                    visibleTitles.push(title);                    // 展开父节点                    let parent = title.parent;                    while (parent) {                        setChildrenVisible(parent, true);                        visibleTitles.push(parent);                        parent = parent.parent;                    }                    // 折叠其余节点                    for (const t of titles) {                        if (!visibleTitles.includes(t)) {                            setChildrenVisible(t, false);                        }                    }                    return;                }            }        });        // 设置子节点的可见性        function setChildrenVisible(title, isVisible) {            for (const child of title.children) {                child.isVisible = isVisible;            }        }        // 滚动到指定的位置        function scrollToView(scrollTop) {            window.scrollTo({ top: scrollTop, behavior: "smooth" });        }        return { titles, currentTitle, progress, scrollToView };    },    props: {        container: {            type: String,            default: ".post-body .article-content",        },    },};</script><style lang="less" scoped>.catalog-card {    background: white;    border-radius: 8px;    box-shadow: 0 3px 8px 6px rgba(7, 17, 27, 0.05);    padding: 20px 24px;    width: 100%;    margin-top: 25px;    box-sizing: border-box;}.catalog-card-header {    text-align: left !important;    margin-bottom: 15px;    display: flex;    justify-content: space-between;    align-items: center;}.catalog-icon {    font-size: 18px;    margin-right: 10px;    color: dodgerblue;}.catalog-card-header div > span {    font-size: 17px;    color: #4c4948;}.progress {    color: #a9a9a9;    font-style: italic;    font-size: 140%;}.catalog-content {    max-height: calc(100vh - 120px);    overflow: auto;    margin-right: -24px;    padding-right: 20px;}.catalog-item {    color: #666261;    margin: 5px 0;    line-height: 28px;    cursor: pointer;    transition: all 0.2s ease-in-out;    font-size: 14px;    padding: 2px 6px;    display: -webkit-box;    overflow: hidden;    text-overflow: ellipsis;    -webkit-line-clamp: 1;    -webkit-box-orient: vertical;    &:hover {        color: #1892ff;    }}.active {    background-color: #;    color: white;    &:hover {        background-color: #0c82e9;        color: white;    }}</style>

以上是“如何使用Vue3实现文章目录功能”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注编程网行业资讯频道!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     807人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     351人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     314人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     433人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     221人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯