跳至主要內容

输入框实现@提及项目

知识库编程技巧HTMLHTML大约 4 分钟

一、效果预览

先看demo: https://zhangxinxu.gitee.io/okr-at-mention/open in new window

项目地址

项目地址: https://gitee.com/zhangxinxu/okr-at-mentionopen in new window

使用说明

使用很简单,引入对应的CSS和JS,然后按照暴露的方法进行调用就可以了。

例如:

<link rel="stylesheet" href="./src/atMention.css">

假设有容器元素(也就是输入框元素):

<div id="container"></div>

则对应的 JavaScript 代码则可以这么使用:

<script type="module">
    import atWakaka from './src/atWakaka.js';
    atWakaka('container', {
        url: './cgi/data.json'
    });
</script>

二、实现技巧

这里有三个实现技巧我觉得值得和大家分享下。

1. @描述整删整加

在可编辑的 div 元素中,要想让里面某段文字不能编辑,有个简单的方法,就是设置 contenteditable="false",例如,下面 HTML 代码中的 <span> 元素就无法编辑,里面的文字五毒不侵。

<div contenteditable="true">
   我是文字,可逐个删除,<span style="display:inline-block;" contenteditable="false">我只能整体删除</span></div>

然后,上面的实现看似完美,实际上有个很头疼的问题,设置了 contenteditable="false" 的元素后面是不能光标定位的,这就导致我想定位在 @xxx 的后面,然后删除,做不到,要么 JS 实时观察并改变光标位置,要么在后面插入一个零宽空格。

上面无论哪个方法,成本都比较高。

在本 JS 的实现中,创新的采用了单标签元素 <hr> 来模拟 @xxx 效果,由于单标签元素本身内容 textContent 是空的,因此,无需设置 contenteditable="false",就能实现删除只能删整体。

在所有浏览器中,<hr> 元素都支持 ::before/::after 伪元素,因此,可以创建丰富的内容和图形生成,有兴趣的同学可以看看我之前的这篇文章:“666,看hr标签实现分隔线如何玩出花”[1]。

2. hover出现浮层交互

Hover出现浮层的交互并不难实现,可如果是在可编辑的 div 内部呢?以及,要是是在 Vue 或者 React 等框架中的。

如果还是按照传统的实现,找到对应的 trigger 元素,然后使用组件包一下,那可能就会出现很多的问题,比方说包不了,又比方说事件绑定不上。

面对这样的场景,解决方法都是类似的,那就是委托。

mouseover/mouseout的行为绑定在容器上,然后进行定位处理。

因为容器元素是固定的,而里面的元素是多变的,绑定在容器上就能以不变应万变,性能也更好。

3. 复制粘贴或者拖拽进去的都是纯文本

富文本编辑机纯手打应该是打不了富文本的,但是粘贴和拖拽却能将富文本弄进去。

有没有什么办法过滤富文本,让用户粘贴或拖拽的内容默认就是纯文本呢?

有的哈!

浏览器其实提供了原生的能力。

包括获取剪切板里面的文本和富文本内容,获取拖拽内容中的文本和富文本,此时,我们就可以阻止默认行为,将纯文本内容插入就可以了。

来一招神不知鬼不觉的移花接木。

相关代码如下所示(拖拽和粘贴二合一了,因为 API 类似):

const doStripHtml = function (event) {
    var dataInput = event.clipboardData || event.dataTransfer;
    // 富文本
    let htmlOrigin = dataInput.getData('text/html');
    // 纯文本
    let textOrigin = dataInput.getData('text');

    // 如果包含富文本
    if (htmlOrigin) {
        // 手动插入
        // 阻止默认的行为
        event.preventDefault();

        // 只插入纯文本
        let lastRange = window.getSelection().getRangeAt(0);
        const newNode = document.createTextNode(textOrigin);
        lastRange.deleteContents();
        lastRange.insertNode(newNode);
        lastRange.setStartAfter(newNode);
        event.target.focus();
    }
};

其中,插入内容这段代码对于任意的富文本编辑器都是受用的,关于光标和选区更多知识可以参见这篇公众号文章:Web 中的“选区”和“光标”[3]。

结语

使用 <hr> 来模拟 @xxx 效果也并非完美无瑕,也是有所牺牲的,首先就是 @xxx 这样的文字内容是无法框选复制的,因为伪元素生成的文本是无法选择的。

其次,就是数据提交的时候,直接 div.textContent 是不行的,因为会丢失 @xxx 这样的信息,需要在额外处理下。

不过相比弊,带来的利想让是更大的。