微信小程序:如何优雅地修改富文本(u-parse/rich-text)内部样式?

0 阅读4分钟

在微信小程序开发中,渲染后端返回的富文本内容是一个非常常见的需求。为了获得更好的渲染效果(比如图片自适应、图片预览、更全的标签支持),我们通常会放弃原生的 <rich-text> 组件,转而使用第三方富文本解析组件,例如基于 mp-html 封装的 <u-parse>(uView 框架内置组件)。

但在实际开发中,我们经常会遇到修改富文本内部默认样式不生效的“坑”。本文将通过一个真实案例(修改无序列表的默认缩进),带你剖析问题的原因及最终的解决方案。

场景重现:被“无视”的 CSS 样式

最近接到一个需求:产品经理希望页面中通过富文本渲染的 <ul>(无序列表)和 <ol>(有序列表)不要有默认的缩进,而是让列表的点/数字和正文保持左对齐

正常在 Web 开发中,我们只需要几行 CSS 就能搞定:

ul, ol {
  padding-left: 0 !important;
  margin-left: 0 !important;
}
li {
  list-style-position: inside !important;
}

考虑到 Vue 的作用域,我在组件的 <style scoped> 中加上了深度选择器 ::v-deep

::v-deep ul,
::v-deep ol {
  padding-left: 0 !important;
  margin-left: 0 !important;
}
::v-deep li {
  list-style-position: inside !important;
}

结果:页面上的文字依然雷打不动地缩进,样式完全没有生效!

抽丝剥茧:为什么 CSS 选不中元素?

打开微信开发者工具的 WXML 面板审查元素,我发现了第一个盲点:

u-parse(或 mp-html)在解析富文本时,并没有把 <ul><ol> 渲染成真正的 HTML 标签,而是将其转换成了带有特定 class 的 <rich-text> 节点,类名类似于 ._ul._ol.__ul 等。

既然类名变了,那我针对这些特定的 class 再次修改 CSS 规则:

::v-deep ._ul,
::v-deep ._ol,
::v-deep .__ul,
::v-deep .__ol {
  padding-left: 0 !important;
  margin-left: 0 !important;
}

结果:样式在开发者工具的 Styles 面板中成功匹配到了对应的元素,但页面渲染的文字依然存在缩进!

终极真相:原生 <rich-text> 的样式隔离黑盒

这就触及到了小程序的底层机制。

为了提高渲染效率,u-parse 在处理嵌套的富文本时,会将解析后的 DOM 树转换为一个 JSON 节点数组(nodes),并将其整体丢给小程序的原生 <rich-text> 组件去渲染。

而原生的 <rich-text> 存在一个致命的特性:它的内部是一个样式隔离的“黑盒”

<rich-text nodes="{{nodes}}"></rich-text> 内部渲染出的标签,完全不受外部 .wxss.css 样式表的影响(即使你用了 ::v-deep 且选择器成功匹配)。内部节点能且只能读取 JSON 节点树中通过 attrs.style 传递进去的 内联样式(inline-style)

解决方案:从源头注入内联样式

既然外部的 CSS 无法穿透,我们就必须改变思路:让富文本解析器在解析 HTML 字符串时,就直接把我们想要的样式以 style="..." 的形式注入到标签中。

大多数优秀的富文本解析组件都提供了这种能力。在 u-parse (或 mp-html) 中,这个属性叫 tag-style

最终代码实现

1. 在 JS 的 data 中定义标签基础样式

export default {
  data() {
    return {
      htmlContent: '<ul><li>列表项1</li><li>列表项2</li></ul>',
      // 为 u-parse 注入标签默认样式,解决 rich-text 内部无法被外部 CSS 穿透的问题
      parseTagStyle: {
        ul: 'padding-left: 0; margin-left: 0;',
        ol: 'padding-left: 0; margin-left: 0;',
        li: 'list-style-position: inside;'
      }
    };
  }
}

2. 在模板中绑定 tag-style 属性

<template>
  <view class="content-wrapper">
    <u-parse
      :html="htmlContent"
      :tag-style="parseTagStyle"
    ></u-parse>
  </view>
</template>

通过这种方式,解析器会在生成 nodes 树时,自动将 padding-left: 0; margin-left: 0; 写入到 <ul><ol>style 属性中。最终原生 <rich-text> 渲染时,完美实现了列表符号和文字靠左对齐的需求。

总结

在微信小程序中处理富文本渲染样式时,请记住这个避坑指南:

  1. 优先避免使用 CSS 深度选择器(::v-deep)去强行修改富文本内部的样式,因为一旦底层交给了 <rich-text> 渲染,CSS 就大概率会失效。
  2. 永远优先使用解析组件提供的 tag-style(标签样式)功能,在解析阶段通过内联样式的形式注入,这才是小程序环境下最稳定、最正确的做法。