实现所见即所得编辑器的核心技术与实战经验分享

东霞 交互 阅读 1,023
赞 16 收藏
二维码
手机扫码查看
反馈

所见所得效果踩坑记:从问题到解决方案

最近在开发一个富文本编辑器的项目,需要实现所见所得(WYSIWYG)的效果。听起来不复杂对吧?但实际做的时候真是踩了不少坑。折腾了两天才搞定,这里分享下我的经历和解决方案。

实现所见即所得编辑器的核心技术与实战经验分享

先说说问题:样式总是乱窜

最开始我用的是 contenteditable 属性,直接给 div 加上这个属性确实能实现基本的编辑功能。但很快我就发现问题了:用户输入的内容样式总是乱窜。比如设置了字体加粗,结果换行后整个段落都变成了加粗;或者调整字号时,前面已经输入的文字也被影响了。

这里我踩了个坑:我以为 contenteditable 会自动处理好这些样式隔离的问题。实际上它压根不管这些,完全是“裸奔”状态。

尝试了几个方案,都不太理想

第一个想到的是用 iframe 嵌套一个独立的 document 来做编辑器容器。这样确实能解决部分样式污染的问题,但带来了新的麻烦:iframe 和主页面通信变得很麻烦,尤其是要实时同步内容变化的时候。

后来试了下用 execCommand 方法操作文档,比如:

document.execCommand('bold', false, null);
document.execCommand('fontSize', false, '7');

这种方法虽然能触发一些基础的样式命令,但现代浏览器已经逐渐废弃了 execCommand,兼容性是个大问题。而且它的功能非常有限,很多复杂的样式需求根本实现不了。

最终的解决方案:用 DOM 操作 + 样式隔离

折腾了半天发现,最好的办法还是老老实实操作 DOM,同时做好样式隔离。这里是我的核心代码:

<div id="editor" contenteditable="true" style="outline:none;"></div>

<script>
const editor = document.getElementById('editor');

// 监听输入事件
editor.addEventListener('input', () => {
    const selection = window.getSelection();
    const range = selection.getRangeAt(0);

    // 创建一个新的 span 包裹当前选中的文本
    const span = document.createElement('span');
    span.style.fontWeight = 'bold'; // 示例:加粗样式
    range.surroundContents(span);

    console.log(editor.innerHTML); // 实时获取 HTML 内容
});

// 初始化时加载样式隔离
function initEditor() {
    editor.innerHTML = &lt;p&gt;&lt;br&gt;&lt;/p&gt;; // 默认占位符
    editor.style.cssText = 
        white-space: pre-wrap;
        word-wrap: break-word;
        min-height: 100px;
        border: 1px solid #ccc;
        padding: 10px;
    ;
}

initEditor();
</script>

为什么这个方案靠谱?

上面的代码有几个关键点:

  • 通过 range.surroundContents 方法,我们可以精准地控制样式的应用范围,避免影响其他内容。
  • 样式隔离是通过动态创建 span 或其他标签来实现的,确保每个样式修改都是局部的。
  • 监听 input 事件可以实时捕获用户的操作,方便后续扩展功能,比如保存草稿或实时预览。

当然,这个方案也不是完美的。比如在某些极端情况下(快速连续输入),可能会出现光标错位的问题。不过无大碍,用户刷新一下就能恢复。

踩坑提醒:这三点一定注意

最后总结几个我踩过的坑,给大家提个醒:

  1. 不要直接依赖 contenteditable 的默认行为,它的表现因浏览器而异,尤其是在移动端。
  2. execCommand 看似简单,但已经被现代浏览器逐步废弃,尽量少用。
  3. 样式隔离一定要做好,否则用户随便改个样式就会影响到整篇文档。

以上是我踩坑后的总结

所见所得效果看似简单,实际开发中还是会遇到不少细节问题。以上就是我的完整解决方案和踩坑经验,亲测有效。如果你有更好的实现方式,欢迎评论区交流!后续我还会分享更多类似的实战经验,咱们下次见。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论