uni-app 微信小程序 Markdown 美化指南:从原生丑到高级感

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 安装步骤

  1. 在 uni-app 插件市场搜索 towxml3 并导入
  2. 将 towxml 目录放到项目的 wxcomponents 或组件目录下
  3. 在页面的 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 中的相对路径图片在小程序中无法显示。

解决方案

  1. 统一使用网络图片:所有图片上传 CDN,使用绝对路径
  2. 使用 base 属性 :towxml 支持配置 base 路径自动补全
  3. 后端统一处理:接口返回前将相对路径替换为完整 CDN 地址
javascript 复制代码
// 前端处理示例:批量替换图片路径
const fixImagePath = (md, baseUrl) => {
  return md.replace(/!\[([^\]]*)\]\((?!http)([^)]+)\)/g, 
    (match, alt, src) => `![${alt}](${baseUrl}${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 美化,核心思路是:

  1. 选对组件:普通图文用 mp-html,技术文档用 towxml
  2. 样式穿透 :用 :deep() 解决 scoped 问题
  3. 视觉分层:标题、引用、代码、表格各有各的样式
  4. 细节打磨:卡片化、圆角、阴影、间距统一
  5. 性能优化:长文档分段渲染、图片懒加载

把本文的代码复制到你的项目中,稍作调整就能得到一套专业级的 Markdown 渲染效果。如果有定制化需求,在此基础上扩展即可。


下期预告:《uni-app 小程序富文本编辑器实现方案对比》,关注我不迷路。