前言
在咱们日常的网络社交中,@XXX
性能能够说是一个比拟常见的性能了。 本文将联合实际,介绍一种能够疾速实现 @
选人或援用数据的形式。
性能需要
简略的说一下需要:
1、在输入框中输出 @
,弹出浮窗,而后能够抉择浮窗中相干的数据;
2、在输入框中输出 #
,弹出浮窗,而后能够抉择浮窗中相干的数据;
3、@
和 #
援用的数据要蕴含名称和id等,最终要传给后端;
4、删除 @
和 #
援用的数据时,须要整体删除;
5、@
和 #
援用的数据须要被标注成不同的色彩。
大抵就是这样。
技术计划
在网上参考了不少大佬的文章,也大抵理解了一些社交平台的实现形式,有趣味的敌人能够看看文末的参考。
最终因为性能的符合度和工夫起因,我抉择了开源库: tributejs 。这个开源库有原生,Vue 等例子,就是没有 React 的例子,然而问题不大,应用形式都是大同小异。
具体实现
本文的 @XXX
性能是 tributejs + React实现的,所以 React 技术栈的同学能够间接参考前面的例子,其余技术栈的同学能够参考 tributejs 官网的实现。
@性能实现
首先当然是要下载 tributejs:
yarn add tributejs或者npm install tributejs
而后就是引入 tributejs,对想要的性能进行配置,具体各项配置的意义,能够间接到 tributejs 的 GitHub 上查看。最初能够给编辑器加一些自定义的款式:
index.tsx
import React, { useEffect, useState, useRef } from 'react';import Tribute from "tributejs";import './index.less';const AtDemo = () => { const [atList, setAtList] = useState([ { key: "1", value: "小明", position: "前端开发工程师" }, { key: "2", value: "小李", position: "后端开发工程师" } ]); const [poundList, setpoundList] = useState([ { name: "JavaScript", explain: "前端开发语言" }, { name: "Java", explain: "后端开发语言之一" } ]); useEffect(() => { renderEditor(atList, poundList); }, []) const renderEditor = (_atList: any[], _poundList: any[]) => { let tributeMultipleTriggers = new Tribute({ allowSpaces: true, noMatchTemplate: function () { return null; }, collection: [ { selectTemplate: function(item) { if (this.range.isContentEditable(this.current.element)) { return ( `<span contenteditable="false"> <span class="at-item" title="${item.original.value}" > @${item.original.value} </span> </span>` ); } return "@" + item.original?.value; }, values: _atList, menuItemTemplate: function (item) { return item.original.value; }, }, { trigger: "#", selectTemplate: function(item) { if (this.range.isContentEditable(this.current.element)) { return ( `<span contenteditable="false"> <span class="pound-item" > #${item.original.name} </span> </span>` ); } return "#" + item.original.name; }, values: _poundList, lookup: "name", fillAttr: "name" } ] }); tributeMultipleTriggers.attach(document.getElementById("editorMultiple") as HTMLElement); } return ( <div className="at-demo"> <div id="editorMultiple" className="tribute-demo-input" placeholder="请输出" ></div> </div> )}export default AtDemo;
index.less
.at-demo { background-color: #fff; padding: 24px; .at-item, .pound-item { color: #2ba6cb; }}.tribute-container { position: absolute; top: 0; left: 0; height: auto; overflow: auto; display: block; z-index: 999999; } .tribute-container ul { margin: 0; margin-top: 2px; padding: 0; list-style: none; background: #fff; border: 1px solid #3c98fa; border-radius: 4px; } .tribute-container li { padding: 5px 5px; cursor: pointer; border-radius: 4px; } .tribute-container li.highlight { background: #eee; } .tribute-container li span { font-weight: bold; } .tribute-container li.no-match { cursor: default; } .tribute-container .menu-highlighted { font-weight: bold;}.tribute-demo-input { outline: none; border: 1px solid #d9d9d9; padding: 4px 11px; border-radius: 2px; font-size: 15px; min-height: 100px; cursor: text;}.tribute-demo-input:hover { border-color: #3c98fa; transition: all 0.3s;}.tribute-demo-input:focus { border-color: #3c98fa;}[contenteditable="true"]:empty:before { content: attr(placeholder); display: block; color: #ccc;}#test-autocomplete-container { position: relative;}#test-autocomplete-textarea-container { position: relative;}.float-right { float: right;}
咱们能够看看成果,还是很不错的:
被援用的数据也是被整体删除的:
获取编辑器中的数据
咱们在编辑器中输出了咱们想要的数据,那最终都是要获取其中的数据并且传递给后端的:
...import { Button } from 'antd';// 本义HTMLconst htmlEscape = (html: string) => { return html.replace(/[<>"&]/g,function(match,pos,originalText){ switch(match){ case "<": return "<"; case ">": return ">" case "&": return "&"; case "\"": return """; default: return match; } });}const AtDemo = () => { ... const getDataOfEditorMultiple = () => { const childrenData = document.getElementById('editorMultiple')?.innerHTML; console.log('childrenData', childrenData) const toServiceData = htmlEscape(childrenData); console.log('toServiceData', toServiceData) } return ( <div className="at-demo"> <div id="editorMultiple" className="tribute-demo-input" placeholder="请输出" ></div> <Button onClick={getDataOfEditorMultiple}>获取输入框中所有元素</Button> </div> )}
咱们能够间接通过 getDataOfEditorMultiple
办法间接获取编辑器中的数据,并且本义之后发送给后端。
实时获取编辑器中被援用的数据
咱们有时候可能须要实时的监听编辑器中所数据的数据,或者是被援用的数据。这时咱们能够调用 oninput
这个办法。当然也能够在其余状况调用 onblur
和 onfocus
这两个办法,顾名思义就是失去焦点时和获取焦点时。
残缺的代码如下:
import React, { useEffect, useState, useRef } from 'react';import './index.less';import Tribute from "tributejs";import { Button } from 'antd';const htmlEscape = (html: string) => { return html.replace(/[<>"&]/g,function(match,pos,originalText){ switch(match){ case "<": return "<"; case ">": return ">" case "&": return "&"; case "\"": return """; default: return match; } });}const AtDemo = () => { const [atList, setAtList] = useState([ { key: "1", value: "小明", position: "前端开发工程师" }, { key: "2", value: "小李", position: "后端开发工程师" } ]); const [poundList, setpoundList] = useState([ { name: "JavaScript", explain: "前端开发语言" }, { name: "Java", explain: "后端开发语言之一" } ]); useEffect(() => { renderEditor(atList, poundList); }, []) const renderEditor = (_atList: any[], _poundList: any[]) => { let tributeMultipleTriggers = new Tribute({ allowSpaces: true, noMatchTemplate: function () { return null; }, collection: [ { selectTemplate: function(item) { if (this.range.isContentEditable(this.current.element)) { return ( `<span contenteditable="false"> <span class="at-item" title="${item.original.value}" data-atkey="${item.original.key}" data-atvalue="${item.original.value}" > @${item.original.value} </span> </span>` ); } return "@" + item.original?.value; }, values: _atList, menuItemTemplate: function (item) { return item.original.value; }, }, { trigger: "#", selectTemplate: function(item) { if (this.range.isContentEditable(this.current.element)) { return ( `<span contenteditable="false"> <span class="pound-item" data-poundname="${item.original.name}" > #${item.original.name} </span> </span>` ); } return "#" + item.original.name; }, values: _poundList, lookup: "name", fillAttr: "name" } ] }); tributeMultipleTriggers.attach(document.getElementById("editorMultiple") as HTMLElement); } const getDataOfEditorMultiple = () => { const childrenData = document.getElementById('editorMultiple')?.innerHTML || ''; console.log('childrenData', childrenData) const toServiceData = htmlEscape(childrenData); console.log('toServiceData', toServiceData) } const onInput = () => { const atItemList = document.getElementsByClassName('at-item'); Array.prototype.forEach.call(atItemList, function(el) { console.log(el.dataset.atkey); console.log(el.dataset.atvalue); }); } return ( <div className="at-demo"> <div id="editorMultiple" className="tribute-demo-input" placeholder="请输出" onInput={onInput} ></div> <Button onClick={getDataOfEditorMultiple}>获取输入框中所有元素</Button> </div> )}export default AtDemo;
几个关键点的实现
这里提一下几个要害性能点的实现原理。
- 编辑器的输入框利用的是一般的
div
标签,而后采纳contenteditable="true"
这个属性来实现的; - 援用数据的浮窗定位能够利用 Selection对象来获取;
- 被 @ 或 # 援用的数据,想要被一次性删除,能够在被 @ 或 #的数据外蕴含一个
<span contenteditable="false"></span>
,示意不可编辑的标签; - 把被援用的数据定义为特定的色彩,这个因为咱们在输入框中插入援用数据时,被援用的数据是被HTML标签包裹着的,所以咱们只须要对相干的HTML进行款式设置就好了;
- 想要获取被援用数据中的多个属性的值,能够和下面的例子一样,利用HTML5的自定义属性
data-xxx
来保留咱们想要的属性值,而后通过遍历标签el.dataset.xxx
获取咱们想要的属性的值。
最初
本文介绍了一种能够在前端疾速实现 @xxx
选人或援用数据的性能,在局部情景下也算是比拟好的解决方案了。有趣味的同学能够看看文末参考文章中其余大佬们的实现形式。
参考
https://github.com/zurb/tribute
https://segmentfault.com/a/11...
https://segmentfault.com/a/11...
https://juejin.cn/post/698225...
https://mp.weixin.qq.com/s/YP...