首行缩进处理方案总结
核心问题
- 要求:首行缩进固定为两个中文字符宽度
- 难点:字号变化、编辑器自动添加样式、增加/减少缩进功能冲突
解决思路
1. 精确测量方案
- 问题:em、ic 等单位在字号变化时不够精确
-
方案:使用 Canvas 动态测量字符宽度,转换为像素值
// 测量中文字符宽度 function measureCjkCharWidthPx(element) { // 查找第一个文本节点的实际字体大小 // 使用 Canvas 测量字符宽度 // 返回像素值 }
2. 动态样式覆盖机制
- 问题:编辑器自动添加 text-indent: 2em 行内样式,优先级高
-
方案:使用 setProperty + !important 强制覆盖
p.style.setProperty('text-indent', `${indentPx}px`, 'important');3. MutationObserver 监听策略
-
监听内容:
-
text-indent 变化(覆盖编辑器自动添加)
-
font-size 变化(段落本身和段落内元素如 span)
-
class 变化(可能通过 CSS 类改变字号)
-
防抖:延迟 50ms 处理,避免频繁触发
-
识别变化类型:
-
段落本身变化
-
段落内元素(span)变化,需找到对应段落
4. 避免干扰
- 问题:增加/减少缩进功能被干扰
- 方案:
- 不在 handleChange 中自动处理缩进
- 只在 handleBlur 时处理
- MutationObserver 不监听 margin-left/padding-left(编辑器的块级缩进)
5. 处理逻辑分层
function applyFirstLineIndentPx() {
// 1. 排除列表、标题等
// 2. 检测用户是否点击"减少缩进"(text-indent: 0)
// 3. 检测是否有块级缩进(margin-left/padding-left > 0)
// 4. 默认情况:测量字符宽度,设置为两字宽
}
关键代码结构
// 1. 测量函数:动态获取字符宽度
measureCjkCharWidthPx(element) → 像素值
// 2. 应用函数:根据规则设置缩进
applyFirstLineIndentPx() → 更新所有段落
// 3. 监听机制:MutationObserver + 防抖
observer.observe(editable, {
attributes: true,
attributeFilter: ['style', 'class'],
attributeOldValue: true,
subtree: true
})
// 4. 事件处理:只在失焦时处理
handleBlur() → 延迟应用规则