一、效果预览
先看demo:https://zhangxinxu.gitee.io/okr-at-mention/
效果如下视频所示(不动点击播放),输入 @ 字符会出现匹配的人名列表,由于是静态页面,所以请求的数据是死的,实际开发是后端根据搜索关键字动态返回的。
鼠标hover悬停在人名上,会出现 popover 提示浮层,可以查看人物相关的信息。
另外,本 JS 项目对复制或者拖拽进来的富文本进行了过滤处理,保证了了输入框里面内容的纯粹。
以及对回车行为进行了劫持,例如你希望回车不换行,而是做其他事情,也是可以的。
项目地址
项目地址:https://gitee.com/zhangxinxu/okr-at-mention
欢迎 Star,欢迎 fork,也欢迎关注我的 gitee 账号,会不定期更新一些工作中遇到的小玩意,或者突然萌发的有趣的想法。
二、使用说明
使用很简单,引入对应的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>
就可以实现对应的功能了。需要注意的是,本 JS 依赖 tributejs 这个知名的原生AT提及 JS,所以,使用的时候,需要保证 atWakaka.js 同目录下有 tributejs。
当然,不同项目对交互细节的要求也不一样,因此,也提供了对于的 API 参数接口。
语法和参数
语法为:
atWakaka(container, options, optionsTribute);
之所以叫做"wakaka",没有什么特别的原因,纯粹是当时我就有这股莫名的冲动。
其中:
container : 可输入的编辑框容器元素,可以是 DOM 元素本身,也可以是元素的 id 字符串。
options
: 可选参数,下面有注释说明。
```
{
url: '',
// 按下回车键后,如果希望阻止默认的回车换行
// 并做一些事情,这个参数就可以用到
pressEnter: null,
// 鼠标经过的提示元素,默认本组件会自己创建
// 也可以可以自己指定具体的元素
popOver: 'auto',
// 鼠标经过和移出 @ 元素的处理
// event 是事件对象
// data 是 @元素 对应的请求数据
// popover 是浮层元素
onMouseOver: function (event, data, popover) {},
onMouseOut: function (event, data, popover) {}
}
```
optionsTribute : 可选参数。参见 https://github.com/zurb/tribute 中的参数设置,或者源码中对应参数的使用(如下所示)。
<br />
```
{
// 下拉容器类名
containerClass: 'ui-at-drop-x',
// 前面不需要有空格
requireLeadingSpace: false,
// 是否高亮匹配,这里走的是自己匹配
// 这里其实仅 skip 有效
searchOpts: {
pre: '<mark>',
post: '</mark>',
// 是否服务端搜索数据
skip: true
},
// 动态获取匹配的值
values: valuesTribute,
// 没数据时候返回的HTML内容
noMatchTemplate: function () {},
// 选中返回到输入框的HTML内容
selectTemplate: function(item) {},
// 下拉菜单每一项的HTML内容
menuItemTemplate: function (item) {}
};
```
大家如果希望改变下拉列表的元素结构,就是使用 `optionsTribute` 中的可选参数进行设置。
三、实现技巧
这里有三个实现技巧我觉得值得和大家分享下。
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标签实现分隔线如何玩出花"。
2. hover出现浮层交互
Hover出现浮层的交互并不难实现,可如果是在可编辑的 div 内部呢?以及,要是是在 Vue 或者 React 等框架中的。
如果还是按照传统的实现,找到对应的 trigger 元素,然后使用组件包一下,那可能就会出现很多的问题,比方说包不了,又比方说事件绑定不上。
面对这样的场景,解决方法都是类似的,那就是委托。
将mouseover/mouseout的行为绑定在容器上,然后进行定位处理。
因为容器元素是固定的,而里面的元素是多变的,绑定在容器上就能以不变应万变,性能也更好。
具体实现参见 JS 源码。
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 中的"选区"和"光标"。
四、结语
使用<hr>
来模拟 @xxx 效果也并非完美无瑕,也是有所牺牲的,首先就是 @xxx 这样的文字内容是无法框选复制的,因为伪元素生成的文本是无法选择的。
其次,就是数据提交的时候,直接 div.textContent 是不行的,因为会丢失 @xxx 这样的信息,需要在额外处理下。
不过相比弊,带来的利想让是更大的。
好,就说这么多的,希望对遇到类似需求的小伙伴有所帮助。
......
明天9月1号小朋友开学了,本应开心才对,可惜每天要各种码,各种上报,还要天天核酸,口罩长带,想想就脑壳疼。
(本篇完)