基础来源: 通义千问
效果图
代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Markdown 渲染器</title>
<!-- 引入 marked.js 库 -->
<script src="https://cdn.jsdelivr.net/npm/marked/lib/marked.umd.min.js"></script>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
display: flex;
}
.sidebar {
width: 250px;
position: fixed;
top: 0;
bottom: 0;
padding: 20px;
border-right: 1px solid #ccc;
overflow-y: auto;
background-color: #f9f9f9;
}
.content {
flex-grow: 1;
margin-left: 270px;
/* 确保内容区域在侧边栏右侧 */
padding: 40px;
overflow-y: auto;
height: 100vh;
}
.toc ul {
list-style-type: none;
padding-left: 0;
margin: 0;
}
.toc li {
padding: 5px 0;
}
.toc li a {
text-decoration: none;
color: #007BFF;
display: block;
padding-left: 10px;
}
.toc li a:hover {
background-color: #e9e9e9;
}
h1,
h2,
h3,
h4 {
scroll-margin-top: 20px;
/* 确保滚动时标题不会紧贴顶部 */
}
/* 根据标题级别调整缩进 */
.toc .level-1>a {
padding-left: 10px;
}
.toc .level-2>a {
padding-left: 20px;
}
.toc .level-3>a {
padding-left: 30px;
}
.toc .level-4>a {
padding-left: 40px;
}
</style>
</head>
<body>
<div class="sidebar">
<h3>目录</h3>
<div class="toc"></div>
</div>
<div class="content" id="markdown-content"></div>
<script>
document.addEventListener('DOMContentLoaded', (event) => {
// Markdown文本
const markdownText = `
# 文档标题
## 第一部分
这是第一部分内容...
这是第一部分内容...
这是第一部分内容...
这是第一部分内容...
这是第一部分内容...
这是第一部分内容...
这是第一部分内容...
这是第一部分内容...
### 更多细节
更多细节内容...
更多细节内容...
更多细节内容...
更多细节内容...
更多细节内容...
更多细节内容...
更多细节内容...
更多细节内容...
#### 更深层次
更深层次的内容...
更深层次的内容...
更深层次的内容...
更深层次的内容...
更深层次的内容...
更深层次的内容...
更深层次的内容...
更深层次的内容...
## 第二部分
这是第二部分内容...
这是第二部分内容...
这是第二部分内容...
这是第二部分内容...
这是第二部分内容...
这是第二部分内容...
这是第二部分内容...
这是第二部分内容...
这是第二部分内容...
这是第二部分内容...
这是第二部分内容...
这是第二部分内容...
这是第二部分内容...
这是第二部分内容...
这是第二部分内容...
这是第二部分内容...
这是第二部分内容...
这是第二部分内容...
这是第二部分内容...
## 第三部分
这是第三部分内容...
这是第三部分内容...
这是第三部分内容...
这是第三部分内容...
这是第三部分内容...
这是第三部分内容...
这是第三部分内容...
这是第三部分内容...
这是第三部分内容...
这是第三部分内容...
这是第三部分内容...
这是第三部分内容...
这是第三部分内容...
这是第三部分内容...
`;
// 使用 marked.parse 将Markdown文本转换为HTML
const htmlContent = marked.parse(markdownText);
// 先将转换后的HTML插入到页面中
document.getElementById('markdown-content').innerHTML = htmlContent;
// 提取目录并生成菜单
const toc = [];
// 遍历实际插入页面中的标题元素
getHeadersInOrder('markdown-content').forEach((el, index) => {
//console.log(e)
console.log(el.tagName, index)
let tag = el.tagName
const level = parseInt(tag[1], 10);
const id = `heading-${tag}-${index}`;
el.setAttribute('id', id); // 为每个标题添加唯一的ID
toc.push({
level,
text: el.textContent.trim(),
id
});
})
// 构建目录结构并根据级别缩进
const tocElement = document.querySelector('.toc');
const ul = document.createElement('ul');
let currentLevel = 1;
let currentUl = ul;
toc.forEach(item => {
if (item.level > currentLevel) {
// 如果当前项的级别更高,则创建新的子列表
const newUl = document.createElement('ul');
const lastLi = currentUl.lastElementChild;
if (lastLi) {
lastLi.appendChild(newUl);
}
currentUl = newUl;
currentLevel = item.level;
} else if (item.level < currentLevel) {
// 如果当前项的级别更低,则回退到上一级列表
while (currentLevel > item.level) {
currentUl = currentUl.parentElement.parentElement;
currentLevel--;
}
}
const li = document.createElement('li');
li.classList.add(`level-${item.level}`);
const a = document.createElement('a');
a.href = `#${item.id}`; // 设置 href 属性
a.textContent = item.text;
// 添加点击事件监听器
a.addEventListener('click', (e) => {
e.preventDefault(); // 阻止默认行为
const targetElement = document.getElementById(item.id);
if (targetElement) {
// 平滑滚动到目标元素
targetElement.scrollIntoView({
behavior: 'smooth'
});
} else {
console.error(`Target element with ID ${item.id} not found.`);
}
});
li.appendChild(a);
currentUl.appendChild(li);
});
tocElement.appendChild(ul);
});
function getHeadersInOrder(divId) {
var div = document.getElementById(divId);
var headers = [];
// 遍历div下的所有子节点
function traverse(node) {
if (node.nodeType === Node.ELEMENT_NODE) {
// 检查元素是否为标题标签
var tagName = node.tagName.toLowerCase();
if (tagName.match(/^h[1-6]$/)) {
headers.push(node);
}
// 如果元素包含子元素,递归调用以处理嵌套结构
Array.from(node.children).forEach(traverse);
}
}
traverse(div);
return headers;
}
</script>
</body>
</html>