動態

詳情 返回 返回

使用marked.min.js編寫Markdown文檔頁面,可做知識庫、技術文檔、使用文檔、教程文檔等! - 動態 詳情

摘要

marked.min.js 是一個高效的 JavaScript Markdown 解析器,它能夠將 Markdown 格式的文本轉換為 HTML。作為一個輕量級的庫,marked 在處理大規模的 Markdown 內容時表現出色,並且具備廣泛的兼容性和可定製性。

本文將深入探討如何使用 marked.min.js 來構建一個自定義的 Markdown 解析器,涵蓋其核心功能、配置選項以及如何在不同的應用場景中進行優化與集成。此外,還會介紹如何通過自定義渲染器、擴展功能來增強 marked 的解析能力,使其更適應複雜的 Markdown 語法和特殊需求。

index.html

加載本地data.md文件:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Markdown 解析器</title>
    <style>
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            margin: 0;
            padding: 20px;
            background: #f6f6f6;
            line-height: 1.6;
            color: #333;
        }
        .container {
            display: flex;
            max-width: 1200px;
            margin: 0 auto;
            gap: 20px;
            height: calc(100vh - 40px);
        }
        .left-panel {
            width: 300px;
            background: #fff;
            border-radius: 8px;
            box-shadow: 0 1px 3px rgba(0,0,0,0.1);
            overflow-y: auto;
            padding: 20px;
        }
        .right-panel {
            flex: 1;
            background: #fff;
            border-radius: 8px;
            box-shadow: 0 1px 3px rgba(0,0,0,0.1);
            padding: 20px;
            overflow-y: auto;
            position: relative;
        }
        /* 滾動條美化 - Webkit 瀏覽器 */
        .left-panel::-webkit-scrollbar,
        .right-panel::-webkit-scrollbar {
            width: 8px; /* 滾動條寬度 */
        }
        .left-panel::-webkit-scrollbar-track,
        .right-panel::-webkit-scrollbar-track {
            background: #f6f6f6; /* 軌道背景色 */
            border-radius: 4px;
        }
        .left-panel::-webkit-scrollbar-thumb,
        .right-panel::-webkit-scrollbar-thumb {
            background: #bbbbbb; /* 滾動條滑塊顏色 */
            border-radius: 4px;
            transition: background 0.2s;
        }
        .left-panel::-webkit-scrollbar-thumb:hover,
        .right-panel::-webkit-scrollbar-thumb:hover {
            background: #999999; /* 懸停時顏色加深 */
        }
        
        /* 滾動條美化 - Firefox */
        .left-panel {
            scrollbar-width: thin; /* 細滾動條 */
            scrollbar-color: #bbbbbb #f6f6f6; /* 滑塊顏色 軌道顏色 */
        }
        .right-panel {
            scrollbar-width: thin;
            scrollbar-color: #bbbbbb #f6f6f6;
        }
        
        /* 滾動條美化 - Webkit 瀏覽器 (for code blocks) */
        pre::-webkit-scrollbar {
            width: 8px; /* 滾動條寬度 */
            height: 8px; /* 水平滾動條高度 */
        }
        pre::-webkit-scrollbar-track {
            background: #f6f6f6; /* 軌道背景色,與面板一致 */
            border-radius: 4px;
        }
        pre::-webkit-scrollbar-thumb {
            background: #bbbbbb; /* 滑塊顏色,與面板一致 */
            border-radius: 4px;
            transition: background 0.2s;
        }
        pre::-webkit-scrollbar-thumb:hover {
            background: #999999; /* 懸停時顏色加深,與面板一致 */
        }
        
        /* 滾動條美化 - Firefox (for code blocks) */
        pre {
            scrollbar-width: thin; /* 細滾動條 */
            scrollbar-color: #bbbbbb #f6f6f6; /* 滑塊顏色 軌道顏色,與面板一致 */
        }
        .toc {
            list-style: none;
            padding: 0;
            margin: 0;
        }
        .toc li {
            margin: 10px 0;
            display: flex;
            align-items: center;
        }
        .toc .level-tag {
            display: inline-block;
            width: 28px;
            text-align: center;
            font-size: 12px;
            color: #888;
            margin-right: 8px;
        }
        .toc a {
            text-decoration: none;
            color: #555;
            font-size: 14px;
            transition: color 0.2s;
            cursor: pointer;
        }
        .toc a:hover {
            color: #2c82ff;
        }
        .toc .level-2 { margin-left: 16px; }
        .toc .level-3 { margin-left: 32px; }
        h1, h2, h3, h4, h5, h6 {
            color: #2c3e50;
            margin: 1.5em 0 0.5em;
            font-weight: 600;
        }
        h1 { font-size: 26px; border-bottom: 1px solid #eee; padding-bottom: 8px; }
        h2 { font-size: 22px; }
        h3 { font-size: 18px; }
        h4 { font-size: 16px; }
        h5 { font-size: 14px; }
        h6 { font-size: 12px; color: #777; }
        blockquote {
            margin: 1em 0;
            padding: 10px 15px;
            background: #f9f9f9;
            border-left: 4px solid #2c82ff;
            color: #555;
            border-radius: 4px;
            word-break: break-all;
            overflow-wrap: break-word;
            max-width: 100%;
        }
        blockquote p { margin: 0; }
        ul, ol {
            padding-left: 24px;
            margin: 1em 0;
        }
        ul li {
            list-style: none;
            position: relative;
        }
        ol li { margin: 5px 0; }
        code {
            font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
            background: #f5f5f5;
            padding: 2px 6px;
            border-radius: 4px;
            color: #e96900;
            font-size: 0.9em;
        }
        pre {
            position: relative;
            background: #2d2d2d;
            color: #f8f8f2;
            padding: 15px;
            border-radius: 6px;
            overflow-x: auto;
            margin: 1em 0;
            font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
            box-shadow: 0 1px 3px rgba(0,0,0,0.1);
        }
        pre code {
            background: none;
            padding: 0;
            color: inherit;
        }
        .copy-btn {
            position: absolute;
            top: 10px;
            right: 10px;
            padding: 4px 8px;
            background: #444;
            color: #fff;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 12px;
            transition: background 0.2s;
        }
        .copy-btn:hover {
            background: #666;
        }
        p { margin: 1.2em 0; }
        a { color: #2c82ff; text-decoration: none; }
        a:hover { text-decoration: underline; }
        .error { color: #e74c3c; padding: 10px; }
        img {
            max-width: 100%;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="left-panel">
            <h2>目錄</h2>
            <ul id="toc" class="toc"></ul>
        </div>
        <div id="markdownOutput" class="right-panel"></div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
    <script>
        const output = document.getElementById('markdownOutput');
        const toc = document.getElementById('toc');

        function generateTOC(html) {
            output.innerHTML = html;
            toc.innerHTML = '';

            const headings = output.querySelectorAll('h1, h2, h3, h4, h5, h6');
            if (headings.length === 0) {
                toc.innerHTML = '<li>未找到標題</li>';
                console.warn('未在內容中找到任何標題');
                return;
            }

            headings.forEach((heading, index) => {
                const level = parseInt(heading.tagName.charAt(1));
                const id = `heading-${index}`;
                heading.id = id;

                const titleText = heading.textContent.trim() || heading.innerText.trim();
                if (!titleText) {
                    console.warn(`標題 ${id} 無內容,已跳過`);
                    return;
                }

                const li = document.createElement('li');
                li.className = `level-${level}`;
                const levelTag = document.createElement('span');
                levelTag.className = 'level-tag';
                levelTag.textContent = `H${level}`;
                const a = document.createElement('a');
                a.href = `#${id}`;
                a.textContent = titleText;
                a.addEventListener('click', (e) => {
                    e.preventDefault();
                    const target = output.querySelector(`#${id}`);
                    if (target) {
                        const offsetTop = target.offsetTop - 20;
                        output.scrollTo({
                            top: offsetTop,
                            behavior: 'smooth'
                        });
                    } else {
                        console.error(`無法找到目標標題: #${id}`);
                    }
                });
                li.appendChild(levelTag);
                li.appendChild(a);
                toc.appendChild(li);
            });

            // 添加複製按鈕到代碼塊
            const codeBlocks = output.querySelectorAll('pre');
            codeBlocks.forEach((pre, index) => {
                const btn = document.createElement('button');
                btn.className = 'copy-btn';
                btn.textContent = '複製';
                btn.addEventListener('click', () => {
                    const code = pre.querySelector('code')?.textContent || pre.textContent;
                    navigator.clipboard.writeText(code).then(() => {
                        btn.textContent = '已複製';
                        setTimeout(() => { btn.textContent = '複製'; }, 2000);
                    }).catch(err => console.error('複製失敗:', err));
                });
                pre.appendChild(btn);
            });

            console.log(`共渲染 ${headings.length} 個標題`);
        }

        fetch('data.md')
            .then(response => {
                if (!response.ok) {
                    throw new Error('無法加載 data.md 文件');
                }
                return response.text();
            })
            .then(markdownText => {
                const html = marked.parse(markdownText);
                generateTOC(html);
            })
            .catch(error => {
                output.innerHTML = `<div class="error">錯誤: ${error.message}</div>`;
                console.error('加載文件失敗:', error);
            });

        marked.setOptions({
            breaks: true,
            gfm: true
        });
    </script>
</body>
</html>

解析效果

  • 左側導航欄自動獲取md文件的標題作為節點;
  • 好看的樣式,對引用塊、代碼塊、鏈接等常用語法樣式進行優化;
  • 代碼塊支持一鍵複製;
  • 點擊導航欄的標題可以滾動到指定位置;
  • 導航欄的標題顯示當前的類型(H1-H5);
  • 滾動條美化過了

image.png

演示

https://demo.likeyunba.com/md-marked/

本文作者

TANKING

user avatar banana_god 頭像 u_16281588 頭像 ligaai 頭像 lovecola 頭像 yuxl01 頭像 gomi 頭像 tssc 頭像 jianghushinian 頭像 lizhiqianduan 頭像 changlina 頭像 zengjingaiguodekaomianbao 頭像 tongbo 頭像
點贊 32 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.