真正可用的摘要截断方案是服务端返回摘要字段或前端用canvas精确测量后截断,因中英文、emoji、全角标点视觉宽度不一且系统渲染差异导致substring等固定字数截取不可控。

不能靠字符数截取摘要,尤其在中英文混排、emoji、全角标点场景下会错乱;真正可用的方案是服务端返回摘要字段,或前端用 canvas 精确测量后截断。
为什么不能直接用 substring 或 slice 截固定字数
中文字、英文字母、数字、emoji 占据的视觉宽度不同;iOS 和 Android 渲染引擎对字体度量的处理也不一致。比如「??」一个 emoji 占两个字符位,但实际渲染宽度≈2个汉字;用 content.slice(0, 100) 在不同机型上可能显示 3 行或 6 行,根本不可控。
常见错误现象:
- 安卓低端机上截 100 字,结果只显示半行就换行
- iOS 上因系统字体缩放,截取后末尾多出空白或挤出省略号
- 用户切换系统字号后,摘要突然“多出一行”或“少显示一词”
推荐方案:用 canvas 测量真实宽度再截断
这是目前跨端最稳的纯前端摘要生成方式,不依赖 CSS 渲染时机,也不受字体加载延迟影响。
实操要点:
- 必须在
onReady或nextTick后执行,确保 DOM 已挂载 - 创建临时
canvas元素,设置与目标文本相同的font(如"14px system-ui") - 用
canvas.getContext('2d').measureText(text).width获取逐字累计宽度 - 设定容器最大宽度(如
300px),循环累加直到超限,回退一位即为安全截断点 - 结尾手动拼接
'…',避免text-overflow: ellipsis在小程序里失效
示例关键逻辑:
function truncateByWidth(text, maxWidth = 300, font = '14px system-ui') {
const canvas = uni.createCanvasContext('temp-canvas', this)
const ctx = canvas.getContext('2d')
ctx.font = font
let width = 0
let i = 0
for (; i maxWidth) break
}
return i > 0 ? text.slice(0, i - 1) + '…' : ''
}
更轻量但需配合服务端的方案:过滤器只做“安全兜底”
如果你的接口能返回 summary 字段,前端过滤器就不该承担“智能截取”职责,而应专注格式化和防错。
实操建议:
- 定义过滤器时接受两个参数:
value(原始内容)和fallbackLen(备用字数阈值,默认 80) - 优先返回
value.summary || value.abstract,不存在时才 fallback 到truncateByWidth - 过滤器内不做异步操作(
createSelectorQuery或canvas初始化必须同步完成) - 避免在
v-for列表中高频调用——每个 item 都测一遍 canvas 会明显卡顿
定义示例(@/common/filters/index.js):
import Vue from 'vue'
Vue.filter('smartSummary', function(value, fallbackLen = 80) {
if (typeof value !== 'string') return ''
if (value.summary != null) return value.summary
if (value.abstract != null) return value.abstract
return value.length > fallbackLen
? value.slice(0, fallbackLen - 1) + '…'
: value
})
微信小程序基础库低于 2.11.0 时的兼容性处理
这个版本以下,canvas 的 measureText 在某些安卓机型上返回 0,导致全文被截成空字符串。
必须加降级判断:
- 先用
uni.getSystemInfoSync().SDKVersion检查基础库版本 - 若
SDKVersion ,直接走字符数 fallback,不调用 <code>canvas - 不要试图用
setTimeout多次重试 canvas —— 失效就是失效,重试无意义
容易被忽略的一点:哪怕你只在 H5 平台用 canvas 测量,也要在小程序平台预留降级路径,因为 uni-app 编译时无法按平台拆分过滤器逻辑。所有分支都得共存。











