uni-app 微信小程序 Markdown 美化指南:从原生丑到高级感
前言
在 uni-app 开发微信小程序时,我们经常会遇到需要渲染 Markdown 内容的场景——技术文档、博客文章、专栏内容、帮助中心……但小程序原生并不支持直接渲染 Markdown,默认渲染出来的效果往往是"原生丑":标题没有层级感、代码块没有高亮、表格横向溢出、图片拉伸变形。
本文将带你从零实现一套高颜值的 Markdown 渲染方案,涵盖组件选型、样式美化、性能优化、踩坑指南四个维度,看完就能直接复制到项目中使用。
一、方案选型:mp-html vs towxml
uni-app 生态中主流的 Markdown 渲染组件有两个,各有侧重,选错了会浪费大量时间。
1.1 mp-html(推荐:普通图文、多端兼容)
适合场景:博客文章、产品介绍、帮助中心、普通图文内容
优点:
- 体积小、配置简单,上手成本极低
- 支持 H5 / 小程序 / App 多端统一渲染
- 样式自定义灵活,改 CSS 就能出效果
- 社区活跃,遇到问题容易搜到答案
缺点:
- 代码高亮效果一般,不如 towxml 专业
- 不支持自动生成目录(TOC)
- 不支持数学公式、脚注等高级语法
1.2 towxml(推荐:技术文档、重度内容)
适合场景:技术专栏、API 文档、教程类内容、代码量大的文章
优点:
- 代码高亮专业级,支持几十种语言
- 自带 TOC 目录生成,长文章阅读体验好
- 支持提示块(tip / warning / danger)、数学公式、脚注
- 表格、引用、列表等元素样式更精致
缺点:
- 主要面向小程序,H5 / App 端需要额外适配
- 配置相对复杂,体积比 mp-html 大
- 自定义样式需要穿透更多层级
1.3 快速决策表
| 你的需求 | 推荐组件 |
|---|---|
| 普通图文、博客文章、多端兼容 | ✅ mp-html |
| 技术文档、大量代码、需要目录 | ✅ towxml |
| 极简纯文字展示 | ✅ mp-html |
| 需要数学公式、脚注 | ✅ towxml |
二、方案一:mp-html 完整实现(全端通用)
这是最通用的方案,90% 的场景都够用。
2.1 安装与引入
方式一:插件市场导入(推荐 uni-app 项目)
直接在 uni-app 插件市场搜索mp-html,点击"导入插件"即可,无需额外配置。
方式二:npm 安装
npminstallmp-html安装后在pages.json或组件内引入即可。
2.2 页面完整代码(Vue 3 + Setup)
直接复制就能用,包含渲染、图片预览、完整美化样式:
<template> <view class="article-container"> <!-- 文章头部卡片 --> <view class="article-header"> <text class="article-title">{{ title }}</text> <view class="article-meta"> <text class="meta-item">{{ author }}</text> <text class="meta-divider">·</text> <text class="meta-item">{{ date }}</text> <text class="meta-divider">·</text> <text class="meta-item">{{ readTime }} 阅读</text> </view> </view> <!-- Markdown 正文 --> <view class="md-content"> <mp-html :content="mdContent" selectable @imgtap="handleImgTap" /> </view> </view> </template> <script setup> import { ref } from 'vue' // 文章元信息 const title = ref('uni-app Markdown 美化指南') const author = ref('技术博主') const date = ref('2026-06-23') const readTime = ref('5 分钟') // Markdown 内容(实际项目中从接口获取) const mdContent = ref(` # 一级标题示例 ## 二级标题示例 这是一段普通正文内容,**加粗文字**,*斜体文字*,\`行内代码\` 混合排版。 > 这是一段引用文字,通常用于强调重要观点或引用他人话语。 ### 三级标题示例 - 无序列表项一 - 无序列表项二 - 无序列表项三 1. 有序列表项一 2. 有序列表项二 3. 有序列表项三 \`\`\`javascript // 代码块示例 function greet(name) { console.log(\`Hello, \${name}!\`) return \`Welcome, \${name}\` } greet('uni-app') \`\`\` | 功能 | mp-html | towxml | |------|---------|--------| | 代码高亮 | ⭐⭐ | ⭐⭐⭐⭐⭐ | | 目录生成 | ❌ | ✅ | | 多端兼容 | ✅ | ⚠️ | | 体积 | 小 | 较大 | `) // 图片点击预览 const handleImgTap = (e) => { uni.previewImage({ urls: [e.src], current: e.src }) } </script> <style scoped> /* 外层容器:卡片化设计 */ .article-container { min-height: 100vh; background: #f5f7fa; padding-bottom: 40rpx; } /* 文章头部 */ .article-header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 60rpx 40rpx 50rpx; color: #fff; } .article-title { font-size: 44rpx; font-weight: 600; line-height: 1.4; display: block; margin-bottom: 20rpx; } .article-meta { display: flex; align-items: center; font-size: 26rpx; opacity: 0.9; } .meta-item { font-size: 26rpx; } .meta-divider { margin: 0 16rpx; opacity: 0.6; } /* Markdown 内容区 */ .md-content { margin: -30rpx 24rpx 0; background: #fff; border-radius: 20rpx; padding: 40rpx 32rpx; box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.06); } /* ========== 核心:穿透美化 mp-html 样式 ========== */ /* 全局基础样式 */ :deep(.mp-html) { font-size: 32rpx; line-height: 1.85; color: #303133; font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", "Microsoft YaHei", sans-serif; word-break: break-word; } /* 标题美化:层级分明 */ :deep(h1) { font-size: 48rpx; font-weight: 700; color: #1a1a1a; border-left: 8rpx solid #667eea; padding-left: 20rpx; margin: 48rpx 0 28rpx; line-height: 1.4; } :deep(h2) { font-size: 42rpx; font-weight: 600; color: #2c2c2c; padding-bottom: 16rpx; border-bottom: 2rpx solid #e8eaed; margin: 40rpx 0 24rpx; line-height: 1.4; } :deep(h3) { font-size: 36rpx; font-weight: 600; color: #333; margin: 32rpx 0 18rpx; line-height: 1.5; } :deep(h4), :deep(h5), :deep(h6) { font-size: 32rpx; font-weight: 600; color: #444; margin: 24rpx 0 14rpx; } /* 段落间距 */ :deep(p) { margin: 18rpx 0; text-align: justify; } /* 引用块:柔和灰色调 */ :deep(blockquote) { background: #f6f8fa; border-left: 6rpx solid #94a3b8; padding: 20rpx 24rpx; margin: 28rpx 0; color: #555; border-radius: 0 12rpx 12rpx 0; font-size: 30rpx; line-height: 1.8; } :deep(blockquote p) { margin: 0; } /* 行内代码:红色高亮 */ :deep(code:not(pre code)) { background: #fef2f2; color: #dc2626; padding: 6rpx 12rpx; border-radius: 8rpx; font-family: "SF Mono", Menlo, Monaco, Consolas, monospace; font-size: 28rpx; margin: 0 4rpx; } /* 代码块:深色主题 */ :deep(pre) { background: #1e1e1e; padding: 28rpx; border-radius: 12rpx; overflow-x: auto; margin: 24rpx 0; position: relative; } :deep(pre code) { color: #d4d4d4; font-size: 26rpx; line-height: 1.7; font-family: "SF Mono", Menlo, Monaco, Consolas, monospace; background: transparent; padding: 0; margin: 0; } /* 列表优化 */ :deep(ul), :deep(ol) { padding-left: 48rpx; margin: 18rpx 0; } :deep(li) { margin: 10rpx 0; line-height: 1.8; } :deep(li > p) { margin: 8rpx 0; } /* 表格:横向滚动 + 斑马纹 */ :deep(table) { width: 100%; display: block; overflow-x: auto; border-collapse: collapse; margin: 28rpx 0; font-size: 28rpx; } :deep(th), :deep(td) { border: 1rpx solid #e5e7eb; padding: 16rpx 20rpx; text-align: left; min-width: 140rpx; white-space: nowrap; } :deep(th) { background: #f6f8fa; font-weight: 600; color: #374151; } :deep(tr:nth-child(even) td) { background: #fafbfc; } /* 图片:圆角 + 阴影 + 居中 */ :deep(img) { max-width: 100%; height: auto; border-radius: 12rpx; margin: 24rpx auto; display: block; box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08); } /* 分割线 */ :deep(hr) { border: none; height: 2rpx; background: linear-gradient(to right, transparent, #e5e7eb, transparent); margin: 40rpx 0; } /* 链接样式 */ :deep(a) { color: #667eea; text-decoration: none; border-bottom: 1rpx solid rgba(102, 126, 234, 0.3); } /* 加粗强调 */ :deep(strong) { color: #1a1a1a; font-weight: 600; } </style>三、方案二:towxml 技术文档级渲染
如果你的内容以技术文档为主,代码量大、需要目录,towxml 是更好的选择。
3.1 安装步骤
- 在 uni-app 插件市场搜索
towxml3并导入 - 将 towxml 目录放到项目的
wxcomponents或组件目录下 - 在页面的
style中引入组件样式
3.2 核心使用代码
<template> <view class="article-page"> <!-- 目录侧边栏(可选) --> <view class="toc-sidebar" v-if="showToc"> <view class="toc-title">目录</view> <scroll-view scroll-y class="toc-list"> <view v-for="(item, index) in tocList" :key="index" class="toc-item" :class="'toc-level-' + item.level" @tap="scrollToTitle(item)" > {{ item.text }} </view> </scroll-view> </view> <!-- 正文内容 --> <view class="content-wrap"> <towxml :nodes="articleNodes" :base="baseUrl" @tap="handleTap" /> </view> </view> </template> <script setup> import { ref, computed } from 'vue' import { parse } from 'towxml' const mdContent = ref('') // 从接口获取的 markdown 文本 const baseUrl = ref('https://your-cdn.com/') // 图片基础路径 // 解析 markdown 为渲染节点 const articleNodes = computed(() => { return parse(mdContent.value, { theme: 'light', // light / dark highlight: true, // 开启代码高亮 markdown: true // 指定为 markdown 格式 }) }) // 提取目录(从解析结果中获取标题层级) const tocList = computed(() => { const titles = [] // 遍历 nodes 提取 h1-h6 标题 // 具体实现根据 towxml 返回结构调整 return titles }) // 点击事件处理:代码复制、图片预览等 const handleTap = (e) => { // 代码块点击复制 if (e.detail?.data?.type === 'code') { uni.setClipboardData({ data: e.detail.data.text, success: () => { uni.showToast({ title: '代码已复制', icon: 'success' }) } }) } // 图片点击预览 if (e.detail?.data?.type === 'image') { uni.previewImage({ urls: [e.detail.data.src] }) } } </script> <style scoped> /* 提示块美化 */ :deep(.towxml .tip) { background: #e6f7ff; border-left: 6rpx solid #1890ff; padding: 24rpx; border-radius: 8rpx; margin: 24rpx 0; } :deep(.towxml .warning) { background: #fff7e6; border-left: 6rpx solid #fa8c16; padding: 24rpx; border-radius: 8rpx; margin: 24rpx 0; } :deep(.towxml .danger) { background: #fff2f0; border-left: 6rpx solid #f5222d; padding: 24rpx; border-radius: 8rpx; margin: 24rpx 0; } /* 代码块深色主题覆盖 */ :deep(.towxml .hljs) { background: #1e1e1e !important; border-radius: 12rpx; padding: 28rpx; } </style>四、关键优化:解决 uni-app 特有痛点
4.1 样式穿透问题(最常见的坑)
问题:uni-app 的<style scoped>会给所有选择器加上data-v-xxx属性前缀,导致第三方组件内部的样式不生效。
解决方案:使用:deep()穿透选择器
/* ❌ 错误:scoped 下不生效 */.mp-html h1{color:red;}/* ✅ 正确:使用 :deep 穿透 */:deep(.mp-html h1){color:red;}/* 或者批量穿透 */:deep(.mp-html){/* 所有子选择器自动穿透 */h1{...}p{...}}4.2 图片路径处理
问题:Markdown 中的相对路径图片在小程序中无法显示。
解决方案:
- 统一使用网络图片:所有图片上传 CDN,使用绝对路径
- 使用 base 属性:towxml 支持配置
base路径自动补全 - 后端统一处理:接口返回前将相对路径替换为完整 CDN 地址
// 前端处理示例:批量替换图片路径constfixImagePath=(md,baseUrl)=>{returnmd.replace(/!\[([^\]]*)\]\((?!http)([^)]+)\)/g,(match,alt,src)=>``)}4.3 表格横向溢出
问题:小程序屏幕窄,表格列多会撑破布局。
解决方案:给 table 加横向滚动
:deep(table){width:100%;display:block;/* 关键:转为块级元素 */overflow-x:auto;/* 关键:横向滚动 */border-collapse:collapse;-webkit-overflow-scrolling:touch;/* iOS 顺滑滚动 */}4.4 深色 / 浅色主题切换
用 CSS 变量实现一键切换:
/* 默认浅色主题 */.md-content{--bg-color:#ffffff;--text-color:#303133;--border-color:#e5e7eb;--code-bg:#1e1e1e;--code-text:#d4d4d4;background:var(--bg-color);color:var(--text-color);}/* 深色主题 */.md-content.dark{--bg-color:#16161a;--text-color:#e2e8f0;--border-color:#333;--code-bg:#0d1117;--code-text:#c9d1d9;}/* 引用变量 */:deep(blockquote){background:var(--border-color);border-left-color:var(--primary-color);}切换逻辑:
constisDark=ref(false)consttoggleTheme=()=>{isDark.value=!isDark.value// 同步保存到本地存储uni.setStorageSync('theme',isDark.value?'dark':'light')}五、提升颜值的 6 个通用技巧
技巧 1:卡片化布局
不要让内容贴满屏幕,加一层卡片容器,瞬间提升质感:
.article-card{margin:24rpx;background:#fff;border-radius:20rpx;padding:40rpx 32rpx;box-shadow:0 4rpx 24rpxrgba(0,0,0,0.06);}技巧 2:标题视觉分层
- H1:左侧彩色竖条 + 大字号 + 粗体
- H2:底部分割线 + 中字号
- H3:纯文字加粗 + 较小字号
三级标题视觉差异明显,文章结构一目了然。
技巧 3:统一字体栈
:deep(.mp-html){font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Helvetica Neue","Microsoft YaHei",sans-serif;}确保 iOS 用苹方、安卓用系统默认中文字体,避免字体错乱。
技巧 4:合理的行高与字间距
- 正文字号:30-32rpx
- 行高:1.75-1.85 倍
- 段落间距:18-24rpx
太挤阅读累,太松不紧凑,1.8 倍行高是阅读舒适区。
技巧 5:代码块加复制功能
用户看到好代码就想复制,加个复制功能体验翻倍:
// mp-html 方式:通过长按或自定义按钮实现constcopyCode=(codeText)=>{uni.setClipboardData({data:codeText,success:()=>uni.showToast({title:'复制成功',icon:'success'})})}技巧 6:图片懒加载 + 占位图
长文章图片多的时候,开启懒加载减少首屏压力:
<mp-html:content="mdContent"lazy-load:loading-img="placeholderImg"/>六、性能优化:长文档不卡顿
6.1 分段渲染
超过 5000 字的长文章,不要一次性渲染:
// 后端分段返回,滚动加载constloadMoreContent=async()=>{if(loading.value||noMore.value)returnloading.value=trueconstres=awaitapi.getArticlePart(articleId,page.value)if(res.data.length>0){mdContent.value+=res.data page.value++}else{noMore.value=true}loading.value=false}6.2 关闭不必要的功能
mp-html 提供了很多开关,不需要的关掉:
<mp-html:content="mdContent":selectable="true":lazy-load="true":ad-title="false"<!--关闭广告标签-->:anchor="false"<!-- 关闭锚点 -->/>6.3 图片统一尺寸
大图会导致布局抖动,给图片加最大高度限制:
:deep(img){max-width:100%;max-height:800rpx;object-fit:contain;}七、常见踩坑与解决方案
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 样式不生效 | scoped 限制 | 使用:deep()穿透 |
| 图片不显示 | 相对路径 | 统一用 CDN 绝对路径 |
| 表格撑破屏幕 | 缺少滚动 | table 加display:block; overflow-x:auto |
| 代码块换行错乱 | 字体问题 | 统一设置等宽字体 |
| 长文档卡顿 | 节点太多 | 分段渲染 + 懒加载 |
| H5 端样式不一致 | 多端差异 | 用条件编译单独适配 |
| 小程序审核不通过 | 外链跳转 | 关闭 a 标签跳转或加白名单 |
八、总结
uni-app 微信小程序的 Markdown 美化,核心思路是:
- 选对组件:普通图文用 mp-html,技术文档用 towxml
- 样式穿透:用
:deep()解决 scoped 问题 - 视觉分层:标题、引用、代码、表格各有各的样式
- 细节打磨:卡片化、圆角、阴影、间距统一
- 性能优化:长文档分段渲染、图片懒加载
把本文的代码复制到你的项目中,稍作调整就能得到一套专业级的 Markdown 渲染效果。如果有定制化需求,在此基础上扩展即可。
下期预告:《uni-app 小程序富文本编辑器实现方案对比》,关注我不迷路。