Skip to content

markdown渲染

日常文章写作中,markdown格式是一种常用的格式,在web端展示markdown内容,可以使用markdown.js来实现。

1. markdown.js集成

1.1 安装marked.js

bash
npm install marked

1.2 使用marked.js

把markdown内容渲染到页面中分为两步:

  • 定义markdown内容 在setup中定义markdownContent ref,用于存储markdown内容。同时定义一个渲染方法renderedContent,用于讲markdown内容转换成html内容。

  • 把html内容渲染到页面中 在template中定义一个div,id为markdown-container,v-html绑定渲染方法renderedContent。

具体代码如下:

vue
<template>
  <div id="markdown-container" v-html="renderedContent"></div>
</template>

<script>
import { marked } from 'marked';
import { ref, onMounted, computed, watch, nextTick } from 'vue';

export default {
  name: 'MarkdownRenderer',
  setup() {
        const markdownContent = ref(`# 标题1
        ## 标题2
        ### 标题3
        \`\`\`python
        def hello():
            print('hello')
        \`\`\`
        <br>
        ## 标题4
        `);
        const renderedContent = computed(() => {
            if (!markdownContent.value) {
                return '暂无文档'
            }
            marked.setOptions({
                renderer: new marked.Renderer(),
                breaks: true,
                highlight: function (code, lang) {
                    return prism.highlight(code, prism.languages[lang], lang);
                }
            })
            return marked.parse(markdownContent.value);
        });
        return {
            markdownContent,
            renderedContent
        }
    },
  data() {
    return {
    };
  },
};

完成以上代码,就可以在页面中看到markdown内容了。但是我们发现H1,H2,H3等标题都没有加粗效果,这是因为markdown.js默认的渲染器没有对标题进行加粗处理。

2.增加madown样式

增加样式,我们使用github样式库github-markdown-css.

2.1 安装github-markdown-css

bash
npm install github-markdown-css

2.2 使用github-markdown-css

  • div标签增加样式id标签 class="markdown-body"
vue
<div id="markdown-container" v-html="renderedContent" class="markdown-body"></div>
  • style标签引入github-markdown-css

    注意这一步,不要添加scoped,否则样式不会生效。

vue
<style>
@import 'github-markdown-css/github-markdown.css';
</style>

至此,markdown内容样式就可以在页面中正常显示了。但是我们发现代码块没有高亮效果,继续给代码块增加高亮效果。

3. 增加代码高亮效果

这里使用prismjs来实现代码高亮效果。也可以使用其他的代码高亮库,比如highlight.js。

3.1 安装prismjs

bash
npm install prismjs

由于对默认的主题样式不太满意,多安装一个prism-themes库,来实现代码高亮效果。

bash
npm install prism-themes

3.2 使用prismjs

  • 在script中引入prismjs头文件
vue
<script>
import { marked } from 'marked';
import { ref, computed } from 'vue';

import Prism from "prismjs";
import "prismjs/components/prism-cshtml";
import "prismjs/components/prism-css";
import "prismjs/components/prism-javascript";
import "prismjs/components/prism-python";
import "prismjs/components/prism-json";
import "prismjs/components/prism-jsx";
import "prismjs/components/prism-typescript";
</script>
  • 在style中引入prism-themes主题样式atom-dark

    注意:这一步不要添加scoped,否则样式不会生效。

vue
<style>
@import 'prism-themes/themes/prism-atom-dark.css'
</style>

到这一步,代码高亮效果已经实现了。

由于我们是markdown编辑器,在编辑的时候,发现代码块高亮丢失。在setup中增加以下代码触发编辑重渲染。

vue
setup() {
    watch(renderedContent, () => {
        nextTick(() => {
            Prism.highlightAll();
        });
    });
}

4. 代码块增加复制按钮和行号

4.1 增加复制按钮和行号

vue

import "prismjs/plugins/line-numbers/prism-line-numbers.js";
import "prismjs/plugins/line-numbers/prism-line-numbers.css";

setup() {
        const articleContent = ref(`# 标题1`);
        const renderedContent = computed(() => {
            if (!articleContent.value) {
                return '暂无文档'
            }
            console.log('articleContent:', articleContent.value);
            console.log('articleContent type:', typeof articleContent.value);
            marked.setOptions({
                breaks: true,
            })
            return marked.parse(articleContent.value);
        });

        const initPrism = () => {
            nextTick(() => {
                addLineNumbersClass();
                Prism.highlightAll();
                addCopyButtons();
            });
        };

        watch(renderedContent, initPrism);

        onMounted(initPrism);

        const addLineNumbersClass = () => {
            const codeBlocks = document.querySelectorAll('pre');
            codeBlocks.forEach((block) => {
                if (!block.classList.contains('line-numbers')) {
                    block.classList.add('line-numbers');
                }
            });
        };

        const addCopyButtons = () => {
            const codeBlocks = document.querySelectorAll('pre');
            codeBlocks.forEach((block) => {
                if (!block.querySelector('.copy-button')) {
                    const button = document.createElement('button');
                    button.className = 'copy-button';
                    button.textContent = '复制';
                    button.addEventListener('click', () => copyCode(block));
                    block.appendChild(button);
                }
            });
        };

        const copyCode = (block) => {
            const code = block.querySelector('code');
            const range = document.createRange();
            range.selectNode(code);
            window.getSelection().removeAllRanges();
            window.getSelection().addRange(range);
            try {
                document.execCommand('copy');
                alert('代码已复制到剪贴板');
            } catch (err) {
                console.error('复制失败:', err);
            }
            window.getSelection().removeAllRanges();
        };

        return {
            articleContent,
            renderedContent,
            addCopyButtons,
            addLineNumbersClass
        }
    },

4.2 行号和复制按钮样式

<style>
@import 'github-markdown-css/github-markdown.css';
@import 'prism-themes/themes/prism-atom-dark.css';

pre {
    position: relative;
}

.copy-button {
    position: absolute;
    top: 5px;
    right: 5px;
    color: black;
    padding: 5px 10px;
    background-color: #f0f0f0;
    border: none;
    border-radius: 3px;
    cursor: pointer;
}

.copy-button:hover {
    background-color: #e0e0e0;
}

pre[class*="language-"] {
    position: relative;
    padding-left: 3.8em;
    counter-reset: linenumber;
}

.line-numbers .line-numbers-rows {
    position: absolute;
    pointer-events: none;
    top: 0;
    font-size: 100%;
    left: -3.8em;
    width: 3em;
    letter-spacing: -1px;
    border-right: 1px solid #999;
    user-select: none;
}

.line-numbers-rows>span {
    display: block;
    counter-increment: linenumber;
}

.line-numbers-rows>span:before {
    content: counter(linenumber);
    color: #999;
    display: block;
    padding-right: 0.8em;
    text-align: right;
}
</style>

上次更新于: