Vditor 自定义渲染

本贴最后更新于 1694 天前,其中的信息可能已经天翻地覆

Vditor 支持自定义渲染器,如果需要修改渲染结果的话可考虑使用该特性。

渲染流程

Vditor 通过 Lute 引擎进行 Markdown 解析,解析后将生成语法树,最后通过遍历语法树进行渲染。根据不同的渲染场景,Vditor 会触发不同的自定义渲染器回调。

目前支持的渲染回调场景如下:

  • HTML2Md:HTML 转 Markdown,v3.2.0 后改为 HTML2VditorSVDOM。使用场景:
    • options.modesv 时,初始化编辑器内容
    • 调用 html2md 方法
    • sv 模式粘贴复制好的 HTML 时触发
  • HTML2VditorDOM:HTML 转 Vditor DOM,在 wysiwyg 模式粘贴 HTML 时触发
  • HTML2VditorIRDOM:HTML 转 Vditor IR DOM,在 ir 模式粘贴 HTML 时触发
  • HTML2VditorSVDOM:HTML 转 Vditor SV DOM,在 sv 模式粘贴 HTML 时触发
  • Md2HTML:Markdown 转 HTML,v3.2.0 后改为 Md2VditorSVDOM。使用场景:
    • 调用静态方法 md2html
    • 调用静态方法 previewRender
  • Md2VditorDOM:Markdown 转 Vditor DOM,在 wysiwyg 模式粘贴纯文本时触发
  • Md2VditorIRDOM:Markdown 转 Vditor IR DOM,在 ir 模式粘贴纯文本时触发
  • Md2VditorSVDOM:Markdown 转 Vditor SV DOM,在 sv 模式粘贴纯文本时触发

自定义渲染器

通过接口 vditor.lute.SetJSRenderers 来设置自定义渲染器,参数格式如下:

(options?: {
        renderers: {
            HTML2VditorDOM?: ILuteRender,
            HTML2VditorIRDOM?: ILuteRender,
            HTML2Md?: ILuteRender,
            Md2HTML?: ILuteRender,
            Md2VditorDOM?: ILuteRender,
            Md2VditorIRDOM?: ILuteRender,
        },
    }): void;

interface ILuteRender {
    renderHeading?: ILuteRenderCallback;
    renderLinkDest?: ILuteRenderCallback;
    ... // 参见渲染器函数列表
}

type ILuteRenderCallback = (node: {
    TokensStr: () => string;
    __internal_object__: {
        Parent: {
            Type: number,
        },
        HeadingLevel: string,
    }
},                          entering: boolean) => [string, number];

其中 Md2HTML 渲染回调场景中,渲染器函数是带有两个输入参数和两个返回值(合并在一个数组中)的函数,两个参数:

  • node:当前遍历到的节点。比如渲染器函数是 renderHeading 则 node 为标题节点,如果渲染器函数是 renderLink 则 node 为链接节点
  • entering:遍历是否为进入节点。我们按深度优先算法来遍历语法树,在进入节点时为 true,离开节点时为 false

函数返回值为一个数组,包含两个元素:

  • 第一个元素为 string:渲染的字符串,比如 renderHeading 返回 <h2>foo</h2>
  • 第二个元素为 number:遍历状态,0:停止遍历,1:跳过子节点遍历,2:继续遍历

为了方便说明,下面插入 Lute 遍历实现部分的代码来帮助理解:

// WalkStatus 描述了遍历状态。
type WalkStatus int

const (
	// WalkStop 意味着不需要继续遍历。
	WalkStop = iota
	// WalkSkipChildren 意味着不要遍历子节点。
	WalkSkipChildren
	// WalkContinue 意味着继续遍历。
	WalkContinue
)

// Walker 函数定义了遍历节点 n 时需要执行的操作,进入节点设置 entering 为 true,离开节点设置为 false。
// 如果返回 WalkStop 或者 error 则结束遍历。
type Walker func(n *Node, entering bool) WalkStatus

// Walk 使用深度优先算法遍历指定的树节点 n。
func Walk(n *Node, walker Walker) {
	var status WalkStatus

	// 进入节点
	status = walker(n, true)
	if status == WalkStop {
		return
	}

	if status != WalkSkipChildren {
		// 递归遍历子节点
		for c := n.FirstChild; nil != c; c = c.Next {
			Walk(c, walker)
		}
	}

	// 离开节点
	status = walker(n, false)
	return
}

其中 Walker 就是 renderXXX 渲染器函数内部实现的签名,在外部我们进行了包装,以方便返回渲染结果字符串,所以自定义渲染器函数的返回值是包含两个值的一个数组。

渲染器函数列表

Vditor 支持自定义如下渲染器函数,我们按元素类型分组介绍。

块级渲染器

  • renderDocument:渲染整个文档
  • renderParagraph:渲染段落
  • renderText:渲染文本
  • renderCodeBlock:渲染代码块,相关渲染器包括 renderCodeBlockOpenMarkerrenderCodeBlockInfoMarkerrenderCodeBlockCode renderCodeBlockCloseMarker
  • renderMathBlock:渲染数学公式块,相关渲染器包括 renderMathBlockOpenMarkerrenderMathBlockContentrenderMathBlockCloseMarker
  • renderBlockquote:渲染块引用,相关渲染器包括 renderBlockquoteMarker
  • renderHeading:渲染标题,相关渲染器包括 renderHeadingC8hMarker
  • renderList:渲染列表
  • renderListItem:渲染列表项,相关渲染器包括 renderTaskListItemMarker
  • renderThematicBreak:渲染分隔线
  • renderHTML:渲染 HTML 块
  • renderTable:渲染表格,相关渲染器包括 renderTableHeadrenderTableRowrenderTableCell
  • renderFootnotesDef:渲染脚注定义块

行级渲染器

  • renderCodeSpan:渲染代码,相关渲染器包括 renderCodeSpanOpenMarkerrenderCodeSpanContentrenderCodeSpanCloseMarker
  • renderInlineMath:渲染行级数学公式,相关渲染器包括 renderInlineMathOpenMarkerrenderInlineMathContentrenderInlineMathCloseMarker
  • renderEmphasis:渲染强调,相关渲染器包括 renderEmAsteriskOpenMarkerrenderEmAsteriskCloseMarkerrenderEmUnderscoreOpenMarkerrenderEmUnderscoreCloseMarker
  • renderStrong:渲染加粗,相关渲染器包括 renderStrongA6kOpenMarkerrenderStrongA6kCloseMarkerrenderStrongU8eOpenMarkerrenderStrongU8eCloseMarker
  • renderStrikethrough:渲染删除线,相关渲染器包括 renderStrikethrough1OpenMarkerrenderStrikethrough1CloseMarkerrenderStrikethrough2OpenMarkerrenderStrikethrough2CloseMarker
  • renderHardBreak:渲染硬换行
  • renderSoftBreak:渲染软换行
  • renderInlineHTML:渲染行级 HTML
  • renderLink:渲染链接,相关渲染器包括 renderOpenBracketrenderCloseBracketrenderOpenParenrenderCloseParenrenderLinkTextrenderLinkSpacerenderLinkDestrenderLinkTitle
  • renderImage:渲染图片,相关渲染器包括 renderBangrenderLink
  • renderEmoji:渲染 Emoji,相关渲染器包括 renderEmojiUnicoderenderEmojiImgrenderEmojiAlias
  • renderToC:渲染页内目录
  • renderFootnotesRef:渲染脚注引用
  • renderBackslash:渲染反斜杆,相关渲染器包括 renderBackslashContent

以上中的“相关渲染器”中的“相关”代表语法树节点结构的相关性,具体节点结构描述请参考 Markdown 解析原理详解和 Markdown AST 描述

示例

完整示例可参见 demo。下面我们来看一下自定义渲染的代码示例。

const renderers = {
  renderHeading: (node, entering) => {
    if (entering) {
      return [`<h${node.__internal_object__.HeadingLevel} class="vditor__heading"><span class="prefix"></span><span>`, Lute.WalkContinue];
    } else {
      return [`</span></h${node.__internal_object__.HeadingLevel}>`, Lute.WalkContinue];
    }
  },
}
  • Vditor

    Vditor 是一款浏览器端的 Markdown 编辑器,支持所见即所得、即时渲染(类似 Typora)和分屏预览模式。它使用 TypeScript 实现,支持原生 JavaScript、Vue、React 和 Angular。

    354 引用 • 1823 回帖 • 1 关注
  • 开发指南
    8 引用 • 767 回帖
1 操作
Vanessa 在 2020-05-02 18:54:00 更新了该帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • emmm

    请教一下 renderImage 这个方法里面有图片的路径参数吗?我想自定义渲染 img,没找到图片的路径,多谢指点下

    1 回复
  • 88250

    renderLinkDest 覆写这个函数,node.tokens 就是路径了。

    1 回复
  • emmm

    node.Tokenstr()? 这个好像是的,但是我 return [<div><img src="${src}"/></div>,Lute.WalkContinue]

    好像没生效,页面上把 img 的 alt 显示了

  • emmm 1 评论

    还有个问题请教下,heading 自定义渲染,怎么获取标题的内容,因为需要做个锚点,我看这个标题示例没有锚点,内容貌似内部渲染的

    Vanessa 1
  • mtrucc 1 评论

    你好 demo 没法上了

    Vanessa
  • MrQiao 1 评论
    该回帖仅作者和楼主可见
    渲染使用的是 lute
    Vanessa
  • FbJerry 1 评论

    有点复杂,有没有简单的应用场景。

    列如:

    
    <div id="a">你好</div> 
    # 测试一级目录
    内容
    

    使用

    Vditor.preview(document.getElementById('id'),text)
    

    以 md 文本的形式渲染,如何保留里面的 div 不被转换?

    把 div 用代码块包起来
    Vanessa
  • xiaoMaYa 5 评论

    V 大可不可以帮忙看下 为什么没有进入 renderBlockquote,并且 readerHeading 中返回的内容也没有生效
    代码如下:

    image.png

    markdown 文本为

    `
    ## {.tabset}
    # 欢迎使用魔法师~
    > 您可以在这个板块了解到魔法师平台中的特有概念和名词所代表的含义,来帮助你更好地上手和使用产品。
     {.is-success}
    `
    

    生成的效果如下
    image.png

    你把源码发我,我调试看看。
    Vanessa 1 赞同
    @Vanessa after: () => { vditor.vditor.lute.SetJSRenderers({//自定义渲染 renderers: { Md2VditorDOM: { renderDocument: (node, entering) => { console.log('renderDocument', node) if (entering) { console.log('renderDocument', node) return [, Lute.WalkContinue] } return [, Lute.WalkContinue] }, renderLink: (node, entering) => { console.log('renderImage', node) if (entering) { return [, Lute.WalkContinue] } return [, Lute.WalkContinue] }, }, HTML2Md: {//当模式为分屏预览时初次进入会加载 renderDocument: (node, entering) => { if (entering) { return ['', Lute.WalkContinue] } return ['', Lute.WalkContinue] }, renderHeading: (node, entering) => {//标题 if (entering) { if (Lute.GetHeadingID(node)) { return [, Lute.WalkContinue] } return [`<ceshi>`, Lute.WalkContinue] } if (Lute.GetHeadingID(node)) { return [, Lute.WalkContinue] } return [<ceshi></ceshi>, Lute.WalkContinue] }, renderBlockquote: (node, entering) => {//块引用(未生效) console.log('renderBlockquote', node) if (entering) { return [我是引用的, Lute.WalkContinue] } return [‘我是引用的’, Lute.WalkContinue] }, renderParagraph: (node, entering) => {//块引用(无法改变内容) console.log('renderParagraph', node) if (entering) { return [我是引用的, Lute.WalkContinue] } return [‘我是引用的’, Lute.WalkContinue] }, }, } }) },
    xiaoMaYa
    @xiaoMaYa 需要调用 vditor.html2md("<h1> 1 </h1><blockquote>test</blockquote>") 就可以触发了
    Vanessa
    @Vanessa 我在 renderParagraph 中调用 html2md 这个函数可以触发 renderBlockquote 了 然后请问下渲染的方式怎么自定义我看在 render 函数中返回没有效果
    xiaoMaYa
    @xiaoMaYa 你在控制台上执行 vditor.html2md("<h1> 1 </h1><blockquote>test</blockquote>") 就可以看到渲染的最终效果了
    Vanessa 1
  • cyf783 1 评论

    image.pngimage.png

    ir 模式,自定义渲染,在初始化时可以正常按自定义的渲染,但是在修改文档内容后即时渲染没有使用自定义渲染,请问怎么解决啊。

    正文解析存在很多判断,不允许自定义。只有粘贴和预览的时候可以。
    Vanessa
  • zhuyou 2 评论

    image.png

    这个自定义渲染链接后,生成了两个链接,跳转的是上面没有前缀的链接,我想跳转的时带前缀的

    是不是标签闭合没弄对?
    Vanessa
    @Vanessa 这个问题解决了,参考了这篇文章 搞定 Vditor 自定义渲染超链接,其它原理也相同
    zhuyou
  • zhuyou 4 评论

    vditor 的 options.preview.markdown 的 linkBase 加链接前缀功能,在即时预览模式下为什么不能生效,在分屏预览和所见即所得是生效的,这是 bug 吧

    这个是 preview 上的参数,只和预览相关
    Vanessa
    @Vanessa 即使预览不也是预览么
    zhuyou
    @Vanessa 即时预览不也是预览么
    zhuyou
    @zhuyou 是编辑
    Vanessa
  • zhuyou 1 1 评论

    image.png

    在编辑模式下,正常初始化,能够正常给图片带上 linkBase 的前缀,但是来回切换编辑模式后,之前插入的图片前缀就没了,烦请解决下这个问题呢 😭

    image.png

    发现问题了,是在切换模式后,给之前的相对链接加上了/,这样 linBase 就失效了,烦请作者解决下这个问题呢

    image.png

    1 操作
    zhuyou 在 2024-06-17 20:46:47 更新了该回帖
    感谢反馈,后续改进 Issue #1636 · Vanessa219/vditor
    Vanessa
  • zhuyou 1 评论

    在详情类页面中解析 markdown 的文章使用image.png

    这里说明的方法,在详情页面中没有办法实现图片预览呢,如何像在编辑器中一样点击图片可以实现预览

  • EvanLiJun 1 评论

    能不能传入已经定义好的组件

    方法里面可以调用组件
    Vanessa
  • chaobei 1 评论

    大佬,想问一下,mermaid 相关,以及 echarts 的渲染是通过哪个接口实现的?渲染 html 吗?

  • fzycms 1 评论

    看这意思是,自定义是需要在 vditor 已有的进行修改?如果要自己定义一个没有的渲染方式怎么进行?比如我要写一个 renderFace,规避已有的 renderEmoji,我需要图片表情,所以要自定义 ,望解答一下

    使用接口就行了
    Vanessa
请输入回帖内容 ...