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 安装
bash
npm install mp-html
安装后在 pages.json 或组件内引入即可。
2.2 页面完整代码(Vue 3 + Setup)
直接复制就能用,包含渲染、图片预览、完整美化样式:
vue
<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 核心使用代码
vue
<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() 穿透选择器
css
/* ❌ 错误: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 地址
javascript
// 前端处理示例:批量替换图片路径
const fixImagePath = (md, baseUrl) => {
return md.replace(/!\[([^\]]*)\]\((?!http)([^)]+)\)/g,
(match, alt, src) => ``)
}
4.3 表格横向溢出
问题:小程序屏幕窄,表格列多会撑破布局。
解决方案:给 table 加横向滚动
css
:deep(table) {
width: 100%;
display: block; /* 关键:转为块级元素 */
overflow-x: auto; /* 关键:横向滚动 */
border-collapse: collapse;
-webkit-overflow-scrolling: touch; /* iOS 顺滑滚动 */
}
4.4 深色 / 浅色主题切换
用 CSS 变量实现一键切换:
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);
}
切换逻辑:
javascript
const isDark = ref(false)
const toggleTheme = () => {
isDark.value = !isDark.value
// 同步保存到本地存储
uni.setStorageSync('theme', isDark.value ? 'dark' : 'light')
}
五、提升颜值的 6 个通用技巧
技巧 1:卡片化布局
不要让内容贴满屏幕,加一层卡片容器,瞬间提升质感:
css
.article-card {
margin: 24rpx;
background: #fff;
border-radius: 20rpx;
padding: 40rpx 32rpx;
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.06);
}
技巧 2:标题视觉分层
- H1:左侧彩色竖条 + 大字号 + 粗体
- H2:底部分割线 + 中字号
- H3:纯文字加粗 + 较小字号
三级标题视觉差异明显,文章结构一目了然。
技巧 3:统一字体栈
css
: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:代码块加复制功能
用户看到好代码就想复制,加个复制功能体验翻倍:
javascript
// mp-html 方式:通过长按或自定义按钮实现
const copyCode = (codeText) => {
uni.setClipboardData({
data: codeText,
success: () => uni.showToast({ title: '复制成功', icon: 'success' })
})
}
技巧 6:图片懒加载 + 占位图
长文章图片多的时候,开启懒加载减少首屏压力:
xml
<mp-html
:content="mdContent"
lazy-load
:loading-img="placeholderImg"
/>
六、性能优化:长文档不卡顿
6.1 分段渲染
超过 5000 字的长文章,不要一次性渲染:
javascript
// 后端分段返回,滚动加载
const loadMoreContent = async () => {
if (loading.value || noMore.value) return
loading.value = true
const res = await api.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 提供了很多开关,不需要的关掉:
xml
<mp-html
:content="mdContent"
:selectable="true"
:lazy-load="true"
:ad-title="false" <!-- 关闭广告标签 -->
:anchor="false" <!-- 关闭锚点 -->
/>
6.3 图片统一尺寸
大图会导致布局抖动,给图片加最大高度限制:
css
: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 小程序富文本编辑器实现方案对比》,关注我不迷路。