最近想要自己手写一个博客,一开始还挺好,可是当我做锚点目录的时候,却无从下手,经过几天的尝试,也找到了比较简单的解决方法。
安装 markedjs
之所以用marked,而不是markdown-it,是因为markdown-it 无法渲染内容过长的md文件内容。
npm install marked
Vue 中使用 marked 渲染 md文件
<template>
<div v-html="content"></div>
</template>
<script setup>
import { ref, nextTick } from 'vue'
// 配置 mared
import { marked } from 'marked'
const render = new marked.Renderer()
marked.setOptions({
renderer: render, // 这是必填项
gfm: true, // 启动类似于Github样式的Markdown语法
pedantic: false, // 只解析符合Markdwon定义的,不修正Markdown的错误
sanitize: true // 原始输出,忽略HTML标签(关闭后,可直接渲染HTML标签)
})
// 1. 文章内容
const content = ref('')
const initContent = () => {
content.value = marked(/* 渲染的md文本,例如 '# hello\r\nworld!' */)
}
nextTick(() => {
initContent()
})
</script>
当内容为 '# hello\r\nworld!' 时,渲染的页面如图所示,同时注意到 marked 自动为标题设置id属性。
生成 toc 目录树
<template>
<!-- 这些标题是为了演示用的,实际是用 marked 渲染的 -->
<h1 id="item-1">item-1</h1>
<h2 id="item-1.1">item-1.1</h2>
<h3 id="item-1.1.1">item-1.1.1</h3>
<h1 id="item-2">item-2</h1>
<h2 id="item-2.1">item-2.1</h2>
<h2 id="item-2.2">item-2.2</h2>
<h3 id="item-2.2.1">item-2.2.1</h3>
<h3 id="item-2.2.2">item-2.2.2</h3>
<ul>
<!-- 这里为了设置各级标题的不同样式,添加了类,h1标签类为item-1,h2标签类为item-2 -->
<li
v-for="item in tocData"
:key="item.id"
:class="`item-${item.tagName.charAt(1)}`"
>
{{item.id}}
</li>
</ul>
</template>
<script setup>
import { ref, nextTick, watch } from 'vue'
// TOC 目录
function initToc() {
// 获取所有h1 h2 h3 标签
let all_headings = document.querySelectorAll('h1, h2, h3')
return all_headings
}
// 存放 toc 目录数据
const tocData = ref(null)
nextTick(() => {
tocData.value = initToc()
})
</script>
<style>
ul {
border: 1px solid #000;
}
ul li {
list-style: none;
}
.item-1 {
font-weight: 700;
padding-left: 0;
}
.item-2 {
padding-left: 20px;
}
.item-3 {
padding-left: 40px;
}
</style>
生成的页面样式如下
当然,此时是无法跳转的,如果你把li标签改为a标签,由于vue可能使用 hash路由模式,会产生冲突,那怎么呢?很简单,既然我们知道了标题对应内容的id值,那么我们就可以利用此 id跳转到对应的内容位置。
<template>
<-- 定义 anchor 函数 -->
<li
v-for="item in tocData"
:key="item.id"
:class="`item-${item.tagName.charAt(1)}`"
@click="anchor(item.id)"
>
{{item.id}}
</li>
</template>
<script setup>
const anchor = (id) => {
let anchorElement = document.getElementById(anchorId)
if (anchorElement) {
anchorElement.scrollIntoView()
}
}
</script>
注意:marked 添加的id属性一定不会包含空格和 '.' 等特殊符号,要么被移除,要么被自动转换成 '-' 符号。