前端最基础的就是 HTML+CSS+Javascript。掌握了这三门技术就算入门,但也仅仅是入门,现在前端开发的定义已经远远不止这些。前端小课堂(HTML/CSS/JS),本着提升技术水平,打牢基础知识的中心思想,我们开课啦(每周四)。
初级阶段已经完结,之后会针对之前提到过的内容,对实际场景进行分享。正好前段时间我不是一直在加班做聊天的功能嘛。今天我们就来说一说其中遇到的东西。
我们要讲什么?
聊天功能中的发送框的实现及一些常规操作的实现。
- contentEditable
- Node 与 Element
- 插入功能(表情、截图等等)
- 粘贴功能
- 拖入功能
input | √ | × | × |
textarea | √ | √ | × |
contentEditable | √ | √ | √ |
contentEditable
你会说这东西我知道,给元素加上就可以编辑内容。老铁,这么简单当然不行了。
- 当你按下Enter/Return键在可编辑区域中创建一个新的文本行时,不同主流浏览器对此有不同处理(Firefox 插入
、IE/Opera将使用<p>、 Chrome/Safari 将使用 <div>) css 也可以支持同样的功能
-webkit-user-modify
,值有- inherit(继承);
- initial(默认);
- read-only(只读);
- read-write(读写);
- read-write-plaintext-only(读写、非富文本);
- unset(未设置);当一个属性定义了unset值,如果该属性是默认继承属性,该值等同于inherit,如果该属性是非继承属性,该值等同于initial
- 同上 contentEditable 属性支持的也不是逻辑值。plaintext-only 就是其中最亮的仔。
最早还是在张鑫旭大佬-小tip: 如何让contenteditable元素只能输入纯文本哪里看的到的
当然我们用的还是富文本的样式。因为我们里面需要表情
Node 与 Element
Node
- Node.nodeName
返回一个包含该节点名字的DOMString。节点的名字的结构和节点类型不同。HTMLElement
的名字跟它所关联的标签对应,比如HTMLAudioElement
对应的是'audio'
Text
节点对应'#text'
。Document
节点对应'#document'
。 Node.nodeType
ELEMENT_NODE 1 ATTRIBUTE_NODE 2 warn TEXT_NODE 3 CDATA_SECTION_NODE 4 ENTITY_REFERENCE_NODE 5 warn ENTITY_NODE 6 warn PROCESSING_INSTRUCTION_NODE 7 COMMENT_NODE 8 DOCUMENT_NODE 9 DOCUMENT_TYPE_NODE 10 DOCUMENT_FRAGMENT_NODE 11 NOTATION_NODE 12 warn
上面两个属于比较重要的
Element
Node 与 Element 差别
Node 中是会包含文本节点的。比如Text。
而 Element 包含的都是标签节点。
插入功能
如果换成 DOM 操作,好像功能还蛮简单的 appendChild
、insertBefore
、replaceChild
好像就基本可以搞定了。但是换到富文本中呢?我们需要解决几个问题
- 获取当前焦点位置
- 在文本内容中插入内容
- 在节点内容中插入内容
- 其实还有一点,插入的时候会单击其他位置,导致焦点丢失,所以我们需要记住,然后设置到指定位置。
获取当前的焦点位置
window.getSelection();
这个可以用来获取焦点位置。anchorNode
指向用户开始选择(按下鼠标键)的地方,而 focusNode
指向用户结束选择(松开鼠标键)的地方。
注意不能与选区的起始位置和终止位置混淆,因为开始选择的位置可能在结束选择位置的前面,也可能在结束选择位置的后面,这取于选择文本时鼠标移动的方向(也就是按下鼠标键和松开鼠标键的位置)。isCollapsed
布尔值,用于判断选区的起始点和终点是否在同一个位置。
//可以用这段代码来观察
setInterval(()=>{
var selection=window.getSelection();
console.log(selection)
},1000)
文本内容中插入内容
从上面的内容中,我们可以看到,在文本中是一个 Node
节点,那我应该如何插入节点呢?这其实就是调用 insertData
这个api就好了。但是,怎么能如此简单呢?我们插入的是 DOM 而不是简单的文本,所以这个操作不能用。
这里我们要用的其实是splitText
,用于在焦点处分割开内容。然后再after
增加内容。
在节点中插入内容
这个就比较简单了。节点的话,直接加入进去就好了。因为节点不存在说中间插入。但是呢,当你在节点之后的时候,你需要获取其中所有的 nodes
然后根据 offset
找到点。childNodes
和children
该使用那个呢?这个就涉及到上面说到的 Node
和 Element
的区别,留个小作业,自己试一下吧。友情提示,报错的时候记得看是不是 anchorNode
为 Text
节点。
记录焦点位置&整合应用
既然点击会失去焦点,那么我们在blur的时候记录。然后给显示回去就OK了。
测试地址,其实还有一个效果没处理,那就是选区处理。可以当做一个课后作业。
粘贴功能
这个功能很坑,因为还是富文本。所以粘贴进来的是有样式的。但是我们不需要样式,所以我们要过滤掉所有的标签。但是又因为我们可以复制粘贴图片。所以我们需要过滤出来我们的图片。
方案有两种
- 获取文本,图片会变成alt属性标识的值(我用的是这个)
- 获取HTML,需要自己去过滤处理。
粘贴进来的内容,我们需要处理 paste
事件。 e.clipboardData
是获取剪贴板对象。提供了 getData
来获取剪贴板内容。比如 getData('text');
获取文字内容。getData('text/html');
获取html格式内容。
tips:
- 可以复制单图片。会在粘贴板中
files
里面。 - 不可以执行 setData 方法,看上去是浏览器厂商出于安全方向考虑。所以不能直接人为替换剪贴板内容达到目的。那么我们要注意
e.preventDefault();
,用来阻止默认的事件。
拖拽功能
这个功能没实现,感觉上很难,达不到原生的那种感觉,也有可能是我api没找到,希望会的人告我一下。
说一下我能给出的方案,因为不能 setData
所以方案还是考虑阻止默认的
- 分开处理,内部拖拽使用系统操作。外部拖拽禁止。
- 都使用自己的处理但是拿不到焦点,针对这个问题,可以做到增加提示,然后追加。
- 拖拽的话,可以有文件级别的。判断如果是文本文件之类的,可以读取文件内容。
后记
主讲人文章-浏览器科普
感谢评论区的回复, document.execCommand --MDN 的支持用起来的确会少控制一些逻辑。