惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
DataBreaches.Net
Microsoft Security Blog
Microsoft Security Blog
大猫的无限游戏
大猫的无限游戏
B
Blog RSS Feed
MyScale Blog
MyScale Blog
博客园_首页
S
SegmentFault 最新的问题
WordPress大学
WordPress大学
小众软件
小众软件
V
Visual Studio Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Vercel News
Vercel News
Hugging Face - Blog
Hugging Face - Blog
The GitHub Blog
The GitHub Blog
D
Docker
宝玉的分享
宝玉的分享
博客园 - 【当耐特】
F
Fortinet All Blogs
V
V2EX
Last Week in AI
Last Week in AI
Blog — PlanetScale
Blog — PlanetScale
Microsoft Azure Blog
Microsoft Azure Blog
IT之家
IT之家
雷峰网
雷峰网
博客园 - 叶小钗
月光博客
月光博客
J
Java Code Geeks
量子位
爱范儿
爱范儿
阮一峰的网络日志
阮一峰的网络日志
Martin Fowler
Martin Fowler
H
Help Net Security
酷 壳 – CoolShell
酷 壳 – CoolShell
腾讯CDC
Latest news
Latest news
Recent Announcements
Recent Announcements
Google DeepMind News
Google DeepMind News
美团技术团队
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
T
The Blog of Author Tim Ferriss
T
Troy Hunt's Blog
B
Blog
T
Tenable Blog
S
Schneier on Security
L
LangChain Blog
L
LINUX DO - 热门话题
博客园 - 司徒正美
I
InfoQ
P
Privacy International News Feed
P
Privacy & Cybersecurity Law Blog

wuxinhua.com Blog | RSS Feed

写作者与不写作者:AI 时代的思考分水岭 - wuxinhua.com 一文搞懂 Prompt Engineering - wuxinhua.com #30 AI 颠覆我对传统创业团队的认知 - wuxinhua.com 前端面试系列 - 手写代码 - wuxinhua.com #29 微软与 OpenAI 合作内幕 - wuxinhua.com #28 离 30 岁越来越远 - wuxinhua.com Add AI Chatbox to your VitePress document site - wuxinhua.com Building a Blog Subscription and Pusher with AirCode and Resend - wuxinhua.com #27 创始人是公司灵魂的守护者 - wuxinhua.com 快速上手 Midjourney AI 做图 - wuxinhua.com Building your first Discord ChatGPT Bot - wuxinhua.com ChatGPT 机器人系列 - 用 JavaScript,五分钟将 Siri 接入 ChatGPT - wuxinhua.com ChatGPT 机器人系列 - 用 JavaScript 五分钟开发一个飞书 ChatGPT 机器人 - wuxinhua.com ChatGPT 机器人系列 - 用 JavaScript 开发企业微信 ChatGPT 聊天应用 - wuxinhua.com #26 SVB 硅谷银行为什么倒闭 - wuxinhua.com #25 人人都可以成为 AI 工程师 - wuxinhua.com #24 关注你身体的变化 - wuxinhua.com #23 Be positive - wuxinhua.com 前端面试系列 - 前端八股文 - wuxinhua.com #22 AI 在殖民,社交媒体在衰退 - wuxinhua.com #21 我了解到的新冠后遗症 - wuxinhua.com #20 关于死亡和告别 - wuxinhua.com #19 永远无法准备好 - wuxinhua.com macOS 前端开发环境配置 - wuxinhua.com 我的 Newsletter 321来信 - wuxinhua.com Node.js 包管理及 npm 依赖安装机制 - wuxinhua.com 知识的碎片(2021) - wuxinhua.com 放弃 newsfeed 拥抱 newsletter - wuxinhua.com 混迹于 Clubhouse 两周后的一些想法🤔 - wuxinhua.com 认识Docker - wuxinhua.com 知识的碎片(2020) - wuxinhua.com 初探 Web Components - wuxinhua.com Linux 命令行之 Crontab - wuxinhua.com 今年看的书📚(2019) - wuxinhua.com 适配 iOS 13 - wuxinhua.com React Native 新手常见问题及体验总结 - wuxinhua.com ES10(ECMAScript2019)特征预览 - wuxinhua.com Flexbox布局完全指南 - wuxinhua.com 今年看的书📚(2018) - wuxinhua.com 《从0到1 - 如何创造未来 》书评 - wuxinhua.com www背后的故事 - wuxinhua.com Good Code vs Bad Code in Golang - wuxinhua.com 关于React的几道面试题 - wuxinhua.com SVG动画实践 - wuxinhua.com 今年看的书📚(2017) - wuxinhua.com 对比几种常见的排序算法 - wuxinhua.com React服务端渲染(SSR)初探 - wuxinhua.com 从面试题“输入URL...发生了什么”学到的(下) - wuxinhua.com 从面试题“输入URL...发生了什么?”学到的(上) - wuxinhua.com ES6(ECMAScript 6.0)新特征 - wuxinhua.com 对React-redux中connect方法的理解 - wuxinhua.com 我的 RSS 订阅列表 - wuxinhua.com 《空谷幽兰》书评 - wuxinhua.com 你可能不知道git rerere - wuxinhua.com webpack v2升级踩坑笔记 - wuxinhua.com
contenteditable 踩坑记 - wuxinhua.com
2018-07-05 · via wuxinhua.com Blog | RSS Feed

关于富文本编辑器

知乎上有个问题为什么都说富文本编辑器是天坑?,很早就听说了实现一个富文本编辑器需要填很多的坑,“有幸”接触到富文本编辑器,记录下遇到的一些问题及解决方案.

富文本编辑器的实现一般有两种:

  1. 通过设置contenteditable属性,使得在HTML中的任何元素都可以编辑,加上使用一些JavaScript事件处理逻辑,可以将你的网页转换为完整且快速的富文本编辑器。
  2. 基于Draft.js实现编辑器功能,Draft.js是Facebook开源的开发React富文本编辑器开发框架。 而使用contenteditable无疑是最简单的一种方式,但是 DOM 的处理存在很多兼容性的问题,并且处理起来异常麻烦,😢详情查看为什么说contenteditable很糟糕,而这里主要记录使用contenteditable属性实现一个简单编辑器过程的一些坑。

之所以不直接使用inputtextarea,是因为考虑到实现以下功能contenteditable更具有优势:

  1. 输入框的高度无限制,并且自适应
  2. 对一些特定文本进行样式高亮调整等自定义工具栏
  3. 指定位置插入图片、表情等内容
  4. 所见即所得(What you see is what you get)

伴随而来的就是一堆需要解决的问题(这只是其中的很小的一部分...):

  1. placeholder提示语
  2. 获取输入框的内容
  3. 光标位置
  4. 使用delete缩进时,插入多余的dom节点

placeholder提示语

inputtextarea能轻松实现placeholder提示语的效果,但作用于contenteditable的元素,placeholder不起作用,可以通过css:empty解决:

1[contenteditable=true]:empty::before { 2 content: attr(placeholder); 3}

获取输入框的内容

可以利用innerHTMLinnerTexttextContent获取输入框的内容,详细对比介绍一下这几个方法:

innerHTML 返回或修改标签之间的内容,包括标签和文本信息,基本上所有浏览器都支持。

innerText 打印标签之间的纯文本信息,会将标签过滤掉,此功能最初由Internet Explorer引入,在Firefox上存在兼容问题。

innerText !== textContent

innerTexttextContent均能获取标签的内容,但二者存在差别,使用的时候还需注意浏览器兼容性:

  1. textContent会获取style元素里的文本(若有script元素也是这样),而innerText不会
  2. textContent会保留空行、空格与换行符
  3. innerText并不是标准,而textContent更早被纳入标准中
  4. innerText会忽略display: none标签内的内容,textContent则不会
  5. 性能上textContent > innerText

具体查看下面的例子:

CodePen 地址: https://codepen.io/amnEs1a/pen/ajmYXo

光标的位置

首先遇到的一个问题是利用上述方法实现placeholder后,输入框的光标在Firefox中的位置会比其它浏览器要高一截。
图片例子来自medium-editor: 请在friefox浏览器下查看这个bughttps://jsfiddle.net/wooLksnx/

尝试了很多方法来解决均无果,最终发现默认放置 <\br> 标签后,光标位置正常了。

1<div class="rich-editor" data-placeholder="Placeholder Text"><br></div>

而我的另一个需求是需要准确地在光标位置的后面插入指定的内容,获取光标位置,然后插入内容。

1// getSelection、createRange兼容 2export function isSupportRange () { 3 return typeof document.createRange === 'function' || typeof window.getSelection === 'function' 4} 5 6// 获取光标位置 7export function getCurrentRange () { 8 let range = null 9 let selection = null 10 if (isSupportRange()) { 11 selection = document.getSelection() 12 if (selection.getRangeAt && selection.rangeCount) { 13 range = document.getSelection().getRangeAt(0) 14 } 15 } else { 16 range = document.selection.createRange() 17 } 18 return range 19}

1// 插入内容 2export function insertHtmlAfterRange (html) { 3 let selection = null 4 let range = null 5 if (isSupportRange()) { 6 // IE > 9 and 其它浏览器 7 selection = document.getSelection() 8 if (selection.getRangeAt && selection.rangeCount) { 9 let fragment, node, lastNode 10 range = selection.getRangeAt(0) 11 range.deleteContents() 12 let el = document.createElement('span') 13 el.innerHTML = html 14 // 创建空文档对象,IE > 8支持documentFragment 15 fragment = document.createDocumentFragment() 16 17 while ((node = el.firstChild)) { 18 lastNode = fragment.appendChild(node) 19 } 20 range.insertNode(fragment) 21 22 if (lastNode) { 23 range = range.cloneRange() 24 range.setStartAfter(lastNode) 25 range.collapse(true) 26 selection.removeAllRanges() 27 selection.addRange(range) 28 } 29 } 30 } else if (document.selection && document.selection.type != 'Control') { 31 // IE < 9 32 document.selection.createRange().pasteHTML(html) 33 } 34} 35

使用delete缩进时,Chrome插入多余的dom节点

发现的另一个bug是在编辑器进行删除缩进操作时,浏览器会在dom节点中插入节点。

例如:

1<div contenteditable="true"> 2 <div>这是第一行的内容</div> 3 <div>这是第二行的内容</div> 4</div>

当年使用delete进行缩进成一行时,其它浏览器正常显示:

1<div contenteditable="true"> 2 <div>这是第一行的内容这是第二行的内容</div> 3</div>

而Chrome会插入span标签,并且带上继承的一些style属性,font-family, font-size, line-height等:

1<div contenteditable="true"> 2 <div>这是第一行的内容<span style="line-height: 1.5em">这是第二行的内容<span></div> 3</div>

解决方案是使用方法动态移除这些多余的标签,如http://jsfiddle.net/THPmr/6/

参考的一些资料:

  1. INNERTEXT VS. TEXTCONTENT
  2. Why ContentEditable is Terrible
  3. Working around Chrome's contenteditable span bug

几款不错的开源富文本编辑器:

  1. medium-editor
  2. wangEditor —— 轻量级web富文本框

以上。