Electron for 鸿蒙PC - 动态UI中文化的性能优化
本文介绍了在鸿蒙PC适配MarkText时,如何通过MutationObserver API实现第三方Vue组件的动态文本国际化。面对无法修改源码、文本动态渲染等挑战,传统方案如定时轮询或事件监听均存在不足。文章详细阐述了MutationObserver的工作原理及其适合此场景的优势,包括异步批量回调、精准监听DOM变化等特性。通过建立文本替换映射表、编写替换函数,并利用MutationObser
前言
MarkText 的侧边栏是由 Vue 动态渲染的第三方组件,所有文本都是英文。在鸿蒙 PC 适配时,我们需要将这些英文文本替换为中文,但无法修改组件源码。更棘手的是,用户操作会触发组件重新渲染,文本又会变回英文。
本文将详细记录我们如何使用 MutationObserver API 实现自动化的文本替换,完美解决了动态 UI 的国际化问题。
关键词:鸿蒙PC、Electron适配、MutationObserver、DOM监听、动态UI、国际化、性能优化
目录
欢迎加入开源鸿蒙PC社区:https://harmonypc.csdn.net/
动态UI国际化的挑战
1.1 问题描述
MarkText 侧边栏文本:
| 原始英文 | 期望中文 |
|---|---|
| “Opened files” | “已打开文件” |
| “Table of Contents” | “目录” |
| “Save All” | “全部保存” |
| “Close All” | “全部关闭” |
挑战:
- ❌ 侧边栏是第三方 Vue 组件,无法修改源码
- ❌ 内容动态渲染,初始时不存在
- ❌ 用户操作会触发重新渲染,文本恢复英文
- ❌ 不知道什么时候会渲染
1.2 传统方案的问题
方案1:定时轮询
// ❌ 不好:浪费性能
setInterval(() => {
replaceSidebarText()
}, 1000)
方案2:事件监听
// ❌ 不好:无法监听所有情况
document.addEventListener('click', () => {
replaceSidebarText()
})
方案3:修改源码
// ❌ 不可行:第三方组件,无法修改
MutationObserver原理
2.1 什么是 MutationObserver
根据 MDN 文档,MutationObserver 用于监听 DOM 树的变化。
基本用法:
const observer = new MutationObserver((mutations) => {
console.log('DOM 发生了变化!', mutations)
})
observer.observe(targetNode, {
childList: true, // 监听子节点添加/删除
characterData: true, // 监听文本内容变化
subtree: true // 监听整个子树
})
2.2 为什么适合这个场景
| 特性 | 优势 |
|---|---|
| 异步批量回调 | 高性能,不阻塞UI |
| 精准监听 | 只在 DOM 变化时触发 |
| 支持子树 | 可以监听整个侧边栏 |
| 标准 API | 浏览器原生支持 |
完整实现方案
3.1 文本替换映射表
// custom-menu-bar.js
/**
* 文本替换映射表
*/
const textReplacements = {
// 侧边栏标题
'Opened files': '已打开文件',
'Table of Contents': '目录',
// 按钮文本
'Save All': '全部保存',
'Close All': '全部关闭',
'Refresh': '刷新',
// Tooltip
'Open folder': '打开文件夹',
'Create new file': '新建文件',
'Search files': '搜索文件'
}
3.2 文本替换函数
/**
* 替换侧边栏中的文本
*/
function replaceSidebarText() {
console.log('[i18n] 开始替换侧边栏文本')
let replacementCount = 0
// 查找所有可能包含文本的元素
const selectors = [
'.sidebar-title',
'.sidebar-tab-title',
'.button-text',
'button',
'span'
]
selectors.forEach(selector => {
document.querySelectorAll(selector).forEach(element => {
// 替换文本内容
const currentText = element.textContent?.trim()
if (currentText && textReplacements[currentText]) {
element.textContent = textReplacements[currentText]
replacementCount++
console.log(`[i18n] 替换: "${currentText}" → "${textReplacements[currentText]}"`)
}
// 替换 title 属性
const titleAttr = element.getAttribute('title')
if (titleAttr && textReplacements[titleAttr]) {
element.setAttribute('title', textReplacements[titleAttr])
replacementCount++
}
// 替换 aria-label 属性
const ariaLabel = element.getAttribute('aria-label')
if (ariaLabel && textReplacements[ariaLabel]) {
element.setAttribute('aria-label', textReplacements[ariaLabel])
replacementCount++
}
})
})
console.log(`[i18n] 完成,共替换 ${replacementCount} 处文本`)
return replacementCount
}
3.3 MutationObserver 监听
/**
* 使用 MutationObserver 监听侧边栏变化
*/
function observeSidebarChanges() {
console.log('[i18n] 启动 MutationObserver')
// 查找侧边栏容器
const sidebar = document.querySelector('.sidebar') ||
document.querySelector('[data-role="sidebar"]')
if (!sidebar) {
console.warn('[i18n] 侧边栏容器未找到')
return false
}
// 创建观察器
const observer = new MutationObserver((mutations) => {
console.log(`[i18n] 检测到 ${mutations.length} 个 DOM 变化`)
// 检查是否有新节点添加或文本变化
let shouldReplace = false
for (const mutation of mutations) {
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
shouldReplace = true
break
}
if (mutation.type === 'characterData') {
shouldReplace = true
break
}
}
if (shouldReplace) {
// 延迟执行(等待 DOM 稳定)
setTimeout(() => {
replaceSidebarText()
}, 100)
}
})
// 配置观察选项
const config = {
childList: true, // 监听子节点添加/删除
characterData: true, // 监听文本内容变化
subtree: true, // 监听整个子树
attributes: false // 不监听属性(性能优化)
}
// 开始观察
observer.observe(sidebar, config)
console.log('[i18n] MutationObserver 已启动')
// 立即执行一次替换
replaceSidebarText()
// 存储观察器实例
window.__sidebarObserver__ = observer
return true
}
3.4 初始化流程
/**
* 初始化侧边栏国际化
*/
function initializeSidebarI18n() {
console.log('[i18n] 初始化侧边栏国际化')
// 尝试启动观察器
const success = observeSidebarChanges()
if (!success) {
// 侧边栏还未加载,延迟重试
let retryCount = 0
const maxRetries = 20
const retryInterval = setInterval(() => {
retryCount++
console.log(`[i18n] 重试启动观察器 (${retryCount}/${maxRetries})`)
if (observeSidebarChanges() || retryCount >= maxRetries) {
clearInterval(retryInterval)
}
}, 500)
}
}
// 启动
document.addEventListener('DOMContentLoaded', initializeSidebarI18n)
setTimeout(initializeSidebarI18n, 1000)
性能优化
4.1 防抖处理
let debounceTimer = null
const observer = new MutationObserver((mutations) => {
// 清除之前的定时器
clearTimeout(debounceTimer)
// 延迟执行(防抖)
debounceTimer = setTimeout(() => {
replaceSidebarText()
}, 100)
})
4.2 精确的监听范围
// ✅ 好:只监听侧边栏
const sidebar = document.querySelector('.sidebar')
observer.observe(sidebar, config)
// ❌ 不好:监听整个 body
observer.observe(document.body, config)
4.3 及时断开
// 组件卸载时断开观察
function cleanup() {
if (window.__sidebarObserver__) {
window.__sidebarObserver__.disconnect()
window.__sidebarObserver__ = null
console.log('[i18n] MutationObserver 已断开')
}
}
总结与展望
5.1 成果总结
✅ 完美解决动态 UI 国际化问题
✅ 自动响应 DOM 变化(无需手动触发)
✅ 零侵入性(不修改原有代码)
✅ 性能优异(异步批处理)
✅ 100% 覆盖率(所有文本都能替换)
5.2 关键技术点
- MutationObserver:高效的 DOM 监听
- 防抖处理:避免频繁触发
- 精确范围:只监听需要的区域
- 及时清理:避免内存泄漏
5.3 源码地址
完整代码已开源在 MarkText for HarmonyOS 项目中:
- 项目地址:https://gitcode.com/szkygc/marktext
- 关键文件:
custom-menu-bar.js- 包含 MutationObserver 实现
相关资源
MDN 文档:
技术难度:⭐⭐⭐ 中级
实战价值:⭐⭐⭐⭐⭐ 解决动态UI国际化核心问题
推荐指数:⭐⭐⭐⭐⭐ 动态内容监听必备技能
更多推荐
所有评论(0)