在JavaScript中,我们通常使用this.select()实现可编辑文本框中的全选操作,但对于通过添加contenteditable="true"参数来实现可编辑的span标签,这个方案却并不能奏效,需要多写一些代码来实现相同的功能。这篇文章为大家分享一种在可编辑的span标签内实现全选的方法。

0x01 做个小实验

首先我们来做个小实验。

实验源码如下所示:

<html>
    <head>
        <meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0, minimum-scale=1.0">
        <title>实验</title>
    </head>
    <body>
        <h1>实验</h1>
        <input type="text" placeholder="在这里输入文字">
        <br/>
        <span contenteditable="true">在这里输入文字</span>
    </body>
</html>

可以在这里在线运行该实验源码:实验源码

在浏览器中运行该段代码后,会出现两个编辑框,一个是<input>标签所提供的,另一个则是设置了可编辑的<span>标签所提供。

我们在<input>标签中输入一点什么,如下图所示:

然后我们打开控制台,输入以下代码:

let input = document.getElementsByTagName("input")[0];
let span = document.getElementsByTagName("span")[0];

接下来我们分别输入input.select()span.select(),会发现前者让输入框变成全选状态,后者却只能得到报错:Uncaught TypeError: span.select is not a function

出现图中的报错,说明span标签尽管可以通过配置参数实现可编辑,但却依旧无法使用正常可编辑标签可以使用的select()方法。

0x02 前置知识

那么应该如何让span标签也支持这一方法呢?

这里要首先介绍JavaScript中SelectionRange两个对象。

Selection对象

相关文档

这个对象用于描述用户选择文本的范围或者插入符号的位置,如果你遇到一些网站在你选择文本后弹出快捷菜单(例如百度文库),那么在大部分情况下,它们都通过这个对象来获取你的选择。此外,Selection对象除了能获取你的选择项以外,也可以进行选择操作。

Range对象

相关文档

这个对象用于表示一个包含节点与文本节点的一部分的文档片段。其用途非常广泛,例如你要从文本中剔除某个字符,除了使用String.prototype.replace()方法以外,你也可以使用Range对象实现——只是会更复杂,除非有极为特殊的需要,否则大部分时候不需要这么做。

在继续阅读前,我推荐你先稍微查看一下以上两个对象的相关文档,以加深印象,避免后续代码给你带来困惑。

0x03 实际操作

了解完这两个对象之后,我们来实际写一段代码实现select()方法吧。

我们还是回到第一节的实验,但愿你没有关闭那个窗口,否则还需要重新输入一遍实验代码。

我们在Console中输入以下代码:

let range = document.createRange();
range.selectNodeContents(span);

let sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);

然后你会惊喜的发现——span标签被选中了!

就是这么简单,五行代码就能实现!

0x04 原理解释

尽管在第二节我给了前置知识,但我还是打算讲解一下每行代码的含义,避免基础不太扎实的读者迷惑于其中。

第一行代码

let range = document.createRange();

这行代码的作用是创建一个Range对象,可以参考这篇文档

第二行代码

range.selectNodeContents(span);

这行代码使用我们刚刚简历的Range对象选中了我们的span节点对象,可以参考这篇文档

但需要注意的是,目前我们只是将其选中在了Range对象中,而非显示在了屏幕上。如果想要看到它选择的文本,可以通过Range.toString()方法将其输出为字符串。

第三行代码

let sel = window.getSelection();

这行代码新建了一个Selection对象,可以参考这篇文档

第四行代码

sel.removeAllRanges();

这行代码的目的是取消当前的选择,但考虑到你在实际用鼠标选择span标签的时候就已经做了取消选择的操作,你也可以不使用这段代码,不过以防万一,在这里我们还是加上它: )。关于这个API可以参考这篇文档

第五行代码

sel.addRange(range);

这行代码的目的是将之前我们选中的Range对象变成实际在屏幕上可以显示的Selection对象。可以参考这篇文档

0x05 简化API

尽管以上五行代码已经足够简单,但如果你的项目中需要大量使用它,将它包装成一个方法也许是不错的选择。

但如果别人也要用呢?为了避免让大家也遇到Uncaught TypeError: span.select is not a function的错误,将它扩展成<span>标签的方法如何?

其实很简单!

HTMLSpanElement.prototype.select = function() {
    let range = document.createRange();
    range.selectNodeContents(this);

    let sel = window.getSelection();
    sel.removeAllRanges();
    sel.addRange(range);
}

将这行代码放在你的JavaScript代码之前,你的所有<span>标签就可以拥有select()方法啦!