https://www.hdzikao.com

关于selection和range的一些认识(聊天框(番外篇)—如何实现@功能的整体删除)【selection和range的一些认识聊天框】

[导读] 大家好,今天小热关注到一个比较有意思的话题,就是关于DocumentRange的问题,于是小编就整理了2个相关介绍DocumentRange的解答,让我们一起看看吧。 文章目录: 关于selection和range的一些认识

关于selection和range的一些认识(聊天框(番外篇)—如何实现@功能的整体删除)【selection和range的一些认识聊天框】

大家好,今天小热关注到一个比较有意思的话题,就是关于DocumentRange的问题,于是小编就整理了2个相关介绍DocumentRange的解答,让我们一起看看吧。

文章目录:

  1. 关于selection和range的一些认识
  2. 聊天框(番外篇)—如何实现@功能的整体删除

一、关于selection和range的一些认识

了解编辑器中 selection 和 range 概念的关键在于理解它们在文本编辑操作中的角色以及如何通过 API 来控制这些操作。以下内容将详细阐述 selection 和 range 的属性、方法以及在实际应用中的用途。

**selection 对象** 是在用户操作(如选择文本、插入文本等)时由编辑器创建的对象。通过 `document.getSelection()` 可以获取当前 selection 的状态。在 selection 对象中,有以下几个重要属性和方法:

1. **anchorNode**:返回选区的起点所在节点。

2. **anchorOffset**:表示 anchorNode 中选区起点的偏移量。

3. **focusNode**:返回选区的终点所在节点。

4. **focusOffset**:表示 focusNode 中选区终点的偏移量。

5. **isCollapsed**:判断选区的起点和终点是否重合。

6. **rangeCount**:返回当前 selection 中包含的连续范围的数量。

7. **sel.getRangeAt(index)**:获取 selection 中的单个 range 对象,index 指定获取的范围索引。

8. **collapse()**:使 selection 收缩至单点,文档内容不会改变。

9. **addRange()**:添加新的 range 到 selection 中。

10. **removeRange()**:移除 selection 中的特定 range。

**range 对象** 代表一个连续的选中区域。可通过 `selection.getRangeAt(index)` 或 `document.createRange()` 获得。range 对象提供了以下属性和方法:

1. **Range.collapsed**:判断 range 的起点和终点是否相同。

2. **Range.commonAncestorContainer**:返回包含 startContainer 和 endContainer 的最深节点。

3. **Range.endContainer** 和 **Range.startContainer**:分别返回 range 终点和起点所在的节点。

4. **Range.endOffset** 和 **Range.startOffset**:分别返回 endContainer 和 startContainer 中的偏移量。

**操作方法**:

- **Range.setStart()** 和 **Range.setEnd()**:用于设置 range 的起点和终点。

- **Range.selectNode()**:将 range 包含在特定节点下。

- **Range.selectNodeContents()**:将 range 包含在特定节点的内容内,但不包含节点本身。

- **range.cloneContents()**:返回 range 中节点的文档片段。

- **Range.deleteContents()**:从文档中移除 range 的内容,常用于删除特定字符。

- **Range.extractContents()**:将 range 的内容移动到文档片段中。

- **Range.insertNode()**:在 range 的起点插入节点。

- **Range.surroundContents()**:将 range 的内容移动到新的节点中。

- **Range.getBoundingClientRect()**:返回元素的大小及其相对于视口的位置,但在 iOS 下,以光标作为选区时,参数显示为 0。

了解这些概念及 API 可以帮助开发者更高效地控制文本编辑过程,实现如选择文本、插入文本、删除文本等操作。通过正确使用 selection 和 range,可以为用户提供更灵活、直观的编辑体验。

二、聊天框(番外篇)—如何实现@功能的整体删除

上一篇文章中,我们已经初步实现了聊天输入框,但其@功能是不完善的,例如无法整体删除、无法获取除用户名以外的数据(假设用户名不是唯一的)。有问题就要想办法解决,在网上百度了一圈后,倒是有一些收获。本文就着重解决@的整体删除以及获取额外数据。

准备工作聊天框的实现是基于div+contenteditable的。一旦元素开启了可编辑属性,就可以像输入框一样输入内容。不同的是,我们可以传入HTML格式的代码,这也是可以渲染出来的。

<divcontenteditable><pstyle="color:red;">hello</p></div><divcontenteditable>hello</div>

回到我们之前实现的@功能,是直接插入的特殊字符串,删除时也只能一个一个字符的删除,当然也可以通过监听backspace和delete按键,结合正则表达式,手动移动光标来删除,这种方式过于复杂,笔者也没有搞明白要怎么操作;根据上述的渲染结果,我们是不是可以考虑将@xxx也当作一个HTML标签插入到输入框中,然后保证删除时能整体删除就可以了。

初步解决方案在插入HTML标签前,我们先来了解替换元素和非替换元素。

替换元素是指浏览器会元素的标签和属性,来决定元素的具体显示内容,如果未指定属性则显示的将是一个空标签,传入不同的属性值其在页面上渲染出来的结果也不一样。常见的替换元素包括:img、input、textarea、select、object

非替换元素是指其内容可以直接展现给浏览器,HTML中大多数元素是不可替换元素。

了解了这个有什么用呢,我们可以试试在可编辑元素中插入一个替换元素标签:

<divcontenteditable><inputvalue="@xxx"readonly/></div><divcontenteditable><imgsrc="xxx"alt="@xxx"/></div>

可以发现,在输入框内是可以整体删除@xxx,基本功能是可以实现的,但是有几个问题需要解决:

input有默认的宽度,并且需要指定为只读,由于我们的@xxx宽度是不定的,需要使得input的宽度自适应,实现起来比较负责,笔者未实现

img标签需要指定有效的src属性,如果指定的src无效,即使有alt属性值,也会有一个裂开的图片。为了解决这个裂开的图片,笔者尝试了多种方法都没有解决,真是要裂开了。解决不了裂开的图片,笔者就尝试将@xxx通过html2canvas和canvas2img的方式将其转换为base64的图片,效果也还行,就是转换的过程有点慢,也被pass掉了。

难道除了这种方式就没有别的了嘛,答案是有的。

最终解决方案不是说使用input、img标签不行,只是用起来有点麻烦,于是乎笔者就将目光转向了非替换元素。先来一个a标签试试水:

<divcontenteditable><acontenteditable="false">@xxx</a></div>

可编辑元素的子元素默认也是可编辑的,因此需要设置a标签为不可编辑,不然就无法实现整体删除。运行上面的例子,可以发现基本上符合我们的预期效果,想要完美实现,还得考虑几点:

除了@xxx本身外,还应该携带唯一的标识,这样才能区分艾特了哪些人

如果是输入@,然后选择了具体的成员,那么之前输入的@应该被删除掉

如何保证光标是在@xxx后面

有了具体的问题,我们就来逐一解决:

额外参数

a标签可以在value中携带唯一标识,然后我们在发送文本时,从文本内容中取出被艾特的人即可。

<divcontenteditable><aname="at":value="username"contenteditable="false">@xxx</a></div>

删除之前输入的@

因为我们在a标签中已经包含了@,因此之前输入的@就需要删除。也许有人会想,我要通过调用backspce或者delete来实现,可惜这种方式并不可行。正确的思路应该是通过字符串的替换,来模拟实现删除功能。我们这里采用正则表达式的方式来处理,因为我们只需要将@xxx左边最近的一个@删除即可:

if(/@<aname="at"/.test(this.$refs.editor.innerHTML)){this.$refs.editor.innerHTML=this.$refs.editor.innerHTML.replace(/@<aname="at"/,'<aname="at"');}

我们使用name="at"只是替换在输入框中的@,如果有其他的a标签我们将不处理。使用直接替换的方式,会导致光标默认跑到开头,这显然不符合要求,接下来处理光标

处理光标位置

我们需要将光标插入到@xxx后面,具体是哪一个@xxx就需要通过getElementById()来查找,然后将光标移动到此元素后面,因此我们在插入a标签时,还应该指定每一个a标签的id,使用一个递增的全局变量即可。

(/@<aname="at"/.test(this.$refs.editor.innerHTML)){//使用正则替换,将已经输入的@替换掉//如果直接赋值修改innerHTML,则光标默认会回到开头。因此需要额外处理光标this.$refs.editor.innerHTML=this.$refs.editor.innerHTML.replace(/@<aname="at"/,'<aname="at"');//id表示哪一个@letel=document.getElementById(id);range=document.createRange();sel=window.getSelection();//将光标重新定位到自定义的a标签后面range.setStartAfter(el);range.setEndAfter(el);sel.removeAllRanges();sel.addRange(range);}

(item){this.atIndex++;//使用a标签表示@的成员letat=`<aname="at"value="${item.userName}"tabindex="-1"id="${this.atIndex}"contenteditable="false"href="javascript:void(0)">@${item.name}</a>&#x200B;`;this.$refs.inputBox.insertContent(at,this.atIndex);console.log('onSelect',item);//this.$refs.inputBox.insertContent(`${item.name}`);//有空格this.isShowAt=false;},

获取@的成员

我们通过正则表达式来获取

letatIds=[];this.$refs.editor.innerHTML.replace(/<a[^>]*value=['"]([^'"]+)[^>]*/gi,function(match,capture){atIds.push(capture);})

这样我们就基本实现了使用a标签来完成@的整体删除,这里有一个小细节。一般我们都会在@xxx后面有一个空格,我们可以使用&nbsp;,也可以使用零宽字符&#x200B;。笔者发现在不同的浏览器上,使用空格和零宽字符的效果还是有所差异的。

总结本文介绍了使用a标签来完成@功能的整体删除,当然除了使用a标签,span、button、img等标签都是可以选择的技术方案,实现的原理都差不多。不管是采用哪种方案,都需要注意几点:

可编辑元素的子元素默认也是可编辑的,因此插入标签时需要设置为不可编辑

插入标签时,需要将已经输入的@字符删除

注意光标位置的处理

标签自身的样式需要部分覆盖,具体的看使用情况

若有其他解决方案,欢迎在评论区补充。最后,完整代码可参考项目地址

原文:

到此,以上就是小编对于DocumentRange的问题就介绍到这了,希望介绍关于DocumentRange的2点解答对大家有用。

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。

相关文章阅读