近来为完成一个新功用弄的焦头烂额 @xxx
的完成,在完成后写下些心得,供以后会跳入这坑的同志们参考。
起首,当让是斟酌运用范围,因为项目仅仅须要斟酌在 WEBKIT
环境下运用,所以能够不必斟酌 IE
这也使得代码少了许多的 if(){}else{}
推断。在Mozilla 开发者收集上发明 selection
和 range
这两个关于选区对象和光标对象,连系 Caret(一个用于推断当前光标位置的JS插件)后,一个大抵的雏形就显现出来。
也许就长如许:
先整顿思绪,捋一捋完成步骤。
大抵思绪以下:
- 键入
@
后将挑选框显现出来 - 将核心定位在弹出框中的搜刮框中
- 点击挑选框中的选项时,返回输入框
- 输入框中显现
@xxx
- 将光标定位在
@xxx
以后 - 删除
@xxx
时须要全部@xxx
一同删除
因为项目运用了 angular
来构建,所以给的 demo
也是用 angular
来搭建的,然则不管用什么框架,主意有了,那末统统就好办了。
selection
和 range
对象的详细运用请参考 MDN
上的相干文章:
重要触及的几个要领:
- getSelection(window.getSelectio):猎取光标地点的地区(一个div或是一个textarea);
- selection.getRangeAt:猎取光标地点地区中光标选区的信息;
- range.setStart:设置光标选区的肇端位置;
- range.setEnd:设置光标选区的完毕位置;
- range.deleteContents:将光标选区选中的内容删除;
- range.insertNode:在光标选区中增加内容;
- selection.extend:将选区的核心移动到一个特定的位置;
- selection.collapseToEnd:将当前的选区摺叠到最末端的一个点。
html 构造
<div class="demo-wrap" ng-controller="Controller">
<!-- 文本输入框 -->
<div class="demo" id="demo" contenteditable="true" ng-keydown="keyIn($event)"></div>
<!-- 带有输入框的选人框 -->
<div class="select-person" id="selectPerson" ng-show="showSelect" ng-style="sPersonPosi">
<input type="text" id="searchPersonInput" ng-model="personSearchText" ng-blur="missFocus()">
<ul class="person-wrap">
<li class="row" ng-click="sPersonDone({fullName:'一切人'})">
<div class="col-1">
<div class="img-wrap">
<portrait src="" text="'一切'"></portrait>
</div>
</div>
<div class="col-2">一切人</div>
</li>
<li class="row" ng-click="sPersonDone(item)" ng-repeat="item in atList | filter :{fullName: personSearchText}">
<div class="col-1">
<div class="img-wrap">
<portrait src="item.img" text="item.fullName.slice(-2)"></portrait>
</div>
</div>
<div class="col-2" ng-bind="item.fullName"></div>
</li>
</ul>
</div>
</div>
款式相干的CSS
代码就不放上来了,扼要剖析下页面构造,一个 contenteditable="true"
的输入框和一个 id="selectPerson"
的选人框。
- 输入框运用
contenteditable="true"
重要是因为想在输入框中插进去标签,将@xxx
内容显现出差别的色彩(这就须要将@xxx
放在一个标签中),绑定keyIn
的键盘输入事宜,用于检索用户输入@
和backspace
,并做出响应的行动; - 选人框运用
showSelect
来掌握是不是显现,遍历显现须要显现的选人,以及运用input
中的内容来过滤选人。
完成 @ 挑选
相干代码以下:
$scope.keyIn = function(e) {
var selection = getSelection();
var ele = $('#demo');
if (e.code == 'Digit2' && e.shiftKey) {
$scope.showSelect = true;
var offset = ele.caret('offset');
$scope.sPersonPosi = {
left: offset.left - 10 + 'px',
top: offset.top + 20 + 'px'
};
// 让选人框中的搜刮框猎取核心
$timeout(function(){
$('#searchPersonInput')[0].focus();
})
}
}
完成起来挺简朴,代码也不庞杂,应用 caret
插件猎取到光标位置,将选人框在 @
标记的下方显现出来,并同时完成了步骤中的第二步:将核心放在搜刮框中。
选人完成
重要触及步骤为:3、4、5
。
当鼠标点击备选项时须要按递次举行 3、4、5
步骤,所以需将 3、4、5
这 3
个步骤放在一同。
相干代码以下:
$scope.sPersonDone = function(person) {
// 胜利选人后,封闭挑选框,让输入框猎取核心。
$scope.showSelect = false;
var ele = $('#demo')[0];
ele.focus();
// 猎取之前保留先来的信息。
// 须要修正 keyIn 的代码,保留选区以及光标信息,用于猎取在光标核心脱离前,光标的位置
var selection = lastSelection.selection;
var range = lastSelection.range;
var textNode = range.startContainer;
// 删除 @ 标记。
range.setStart(textNode, range.endOffset);
range.setEnd(textNode, range.endOffset + 1);
range.deleteContents();
// 天生须要显现的内容,包含一个 span 和一个空格。
var spanNode1 = document.createElement('span');
var spanNode2 = document.createElement('span');
spanNode1.className = 'at-text';
spanNode1.innerHTML = '@' + person.fullName;
spanNode2.innerHTML = ' ';
// 将天生内容打包放在 Fragment 中,并猎取天生内容的末了一个节点,也就是空格。
var frag = document.createDocumentFragment(),
node, lastNode;
frag.appendChild(spanNode1);
while ((node = spanNode2.firstChild)) {
lastNode = frag.appendChild(node);
}
// 将 Fragment 中的内容放入 range 中,并将光标放在空格以后。
range.insertNode(frag);
selection.extend(lastNode, 1);
selection.collapseToEnd();
};
我们须要的效果是在 @
选人后,将整顿好的 @xxx
包装成一个标签,放在本来 @
的位置,所以我们须要对本来的 $scope.keyIn
要领举行革新,保留本来的光标信息,轻易在上面的要领中运用。
革新后的 $scope.keyIn
要领以下:
$scope.keyIn = function(e) {
var selection = getSelection();
var ele = $('#demo');
if (e.code == 'Digit2' && e.shiftKey) {
$scope.showSelect = true;
// 保留光标信息
lastSelection = {
range: selection.getRangeAt(0),
offset: selection.focusOffset,
selection: selection
};
$scope.showSelect = true;
// 设置弹出框位置
var offset = ele.caret('offset');
$scope.sPersonPosi = {
left: offset.left - 10 + 'px',
top: offset.top + 20 + 'px'
};
$timeout(function(){
$('#searchPersonInput')[0].focus();
})
}
}
这里预计挺多人会有疑问,为啥要在天生的标签背面加一个空格,而且这个空格要经由过程
如许的体式格局完成。
起首,先诠释第一个题目:为啥要在标签后加一个空格?
假如不加空格的话,以后在输入笔墨会增加在我们天生的标签中,也就是说假如不加空格来间隔我们天生的标签,我们在文本框里所做的操纵就是在我们天生的标签中举行。而加了个空格就为了防止该题目的发作,使得文本编辑在准确的编辑框中举行。
第二个题目:为啥不能直接加空格 ' '
,而是经由过程
,不得不说这是个过个伤心的现实,照样碰到了兼容性的题目,在 chrome
下运转好好的代码,在 node-webkit
中就会种种报错。原因在不停的 defug
后发明了: node-webkit
中,将一个 ' '
增加到 contenteditable="true"
的 div
中会没有啊,坑爹啊有木有!!!呈上之前的代码来祭奠下。
var spanNode1 = document.createElement('span');
var node = document.createTextNode(' ');
spanNode1.className = 'at-text';
spanNode1.innerHTML = '@' + person.fullName;
var frag = document.createDocumentFragment();
frag.appendChild(spanNode1);
frag.appendChild(node);
range.insertNode(frag);
selection.extend(node, 1);
效果一上 node-webkit
环境种种报错。真是坑了个大爹。原因是光标定位不准,指定位置超越现实位置,然则 node-webkit
环境确实是能够输入空格的,一看原来是
而
不能经由过程 createTextNode
来建立,所以就有了之前的哪一个曲线救国的战略了。
删除完成
终究捋到末了一个步骤了,删除时,须要将一全部标签一同删除。因为须要监听键盘的输入,所以便可与之前 keyIn
的代码写在一同。
终究的 keyIn
代码为:
$scope.keyIn = function(e) {
var selection = getSelection();
var ele = document.getElementById('demo');
if (e.code == 'Digit2' && e.shiftKey) {
// 保留光标信息
lastSelection = {
range: selection.getRangeAt(0),
offset: selection.focusOffset,
selection: selection
};
$scope.showSelect = true;
// 设置弹出框位置
var offset = $(ele).caret('offset');
$scope.sPersonPosi = {
left: offset.left + 'px',
top: offset.top + 30 + 'px'
};
$timeout(function(){
$('#searchPersonInput')[0].focus();
})
} else if (e.code == 'Backspace') {
// 删除逻辑
// 1 :因为在建立时默许会在 @xxx 后增加一个空格,
// 所以当得知光标位于 @xxx 以后的一个第一个字符后并按下删除按钮时,
// 应当将光标前的 @xxx 给删除
// 2 :当光标位于 @xxx 中心时,按下删除按钮时应当将全部 @xxx 给删除。
var range = selection.getRangeAt(0);
var removeNode = null;
if (range.startOffset <= 1 && range.startContainer.parentElement.className != "at-text")
removeNode = range.startContainer.previousElementSibling;
if (range.startContainer.parentElement.className == "at-text")
removeNode = range.startContainer.parentElement;
if (removeNode)
ele.removeChild(removeNode);
}
};
代码的逻辑都写在解释里了,这里就不多说了。
如许就完成 @
这一功用了。