textarea中使用TAB --- 使用javascript触
2019-05-06 本文已影响0人
Gaarahan
代码借鉴自
MDN与
stackoverflow
想写一个markdown在线编辑器,使用textarea作为编辑器,但在使用Tab键时出了问题
2019-5-7 更新,在思否上得到了网友的帮助,问题完美解决
0. 问题
- 使用
tab
键会将当前的焦点从textarea
中移出 - 移出后,原
textarea
中也不会输入缩进
1. 关于焦点
- 按下
TAB
的焦点移出,是该键盘事件的默认行为,只要阻止默认行为即可
2. 输入
- 阻止默认行为后,虽然焦点不会切换了,但是不会输入缩进
- 思路 :
- 手动在光标所在位置插入两个空格
- 难点在于如何获取光标当前的位置,并在该位置插入空格
- 暂时没有再深入考虑
- 使用js触发两次键盘事件,输入两个空格
- 第一个想到的是使用
jquery
的trigger()
,但想使用原生的方式 - 在网上找到的都是已经过时的方法,使用
createEvent()
来创建事件,使用initKeyEvent()
来设置该键盘事件的触发按键 - 上面的方法都已经过时,MDN上提供了新的替代方法,大概思路差不多,手动创建一个事件,并使用js方法来触发该事件
- 第一个想到的是使用
3.1. 一个简单的示例
<button onclick="alert('u got it')">click me</button>
<script>
let btn = document.querySelector('button');
let e = new MouseEvent('click',{
view : window,
bubbles : true,
cancelable : true
});
btn.dispatchEvent(e);
</script>
- 相对于过时的
createEvent()
方法不同的是,现在的方法需要在创建事件时,就确定该事件的事件类型(键盘事件或者鼠标事件),并且,事件的初始化可以在创建事件的时候就通过第二个参数来确定 - 关于创建鼠标事件或键盘事件的参数,都可以在MDN中查到,不再赘述
3.2. 回到最初的问题
let text = document.querySelector('textarea');
text.onkeydown = ()=>{
if(event.key === "Tab"){
event.preventDefault();
const ke = new KeyboardEvent("keydown", {
bubbles: true, cancelable: true, keyCode: 32
});
text.dispatchEvent(ke);
}
if(event.keyCode === 32){
alert('got insert');
}
}
- 写出这样一段代码,确实可以弹窗,表示已经触发了按下空格的事件,但却并不会有空格出现。
- 问题应该是键盘按下事件,并不等于输入事件,尝试触发输入事件?
let text = document.querySelector('textarea');
text.oninput = ()=>{
console.log(event);
}
text.onkeydown = ()=>{
if(event.key === "Tab"){
event.preventDefault();
const ke = new InputEvent("input", {
data : "sss"
});
text.dispatchEvent(ke);
}
}
text.oninput = ()=>{
console.log('got insert');
}
- 改了改代码,还是一样,能够监听到
input
事件,但是仍然没有相应的输入,换个思路?
4. 回到第一个思路
上一个思路遇到了瓶颈,换回最初的思路试试
4.1. 先获取当前的光标位置
4.2. 插入字符串
- 但通过上面这两个属性,需要自己拼接字符串,
newStr = strBefore + "Tab" + strAfter
, - 找到了这个方法,可以替换选中区域的字符串为指定的字符串
MDN --- setRangeText() - 尝试一下
let text = document.querySelector('textarea');
text.onkeydown = ()=>{
if(event.key === "Tab"){
event.preventDefault();
text.setRangeText(" "); // 输入两个空格作为tab
text.selectionStart += 2; // 输入后将光标后移
}
}
5. 不完美解决
功能稍作完善,问题解决
let text = document.querySelector('textarea');
text.onkeydown = ()=>{
if(event.key === "Tab"){ // 单行或者多行缩进
event.preventDefault();
let selectionStart = text.selectionStart;
let selectionEnd = text.selectionEnd;
if(selectionEnd !== selectionStart){
let textValue = text.value;
let strBefore = textValue.slice(0,selectionStart);
let lineStart = strBefore.lastIndexOf('\n') + 1;
text.setRangeText(' ',lineStart,lineStart);
strBetween = textValue.slice(selectionStart,selectionEnd);
text.setRangeText(strBetween.replace('\n','\n '));
}
else{ // 写入一个tab
text.setRangeText(" ");
text.selectionStart += 2;
}
}
}
- 上面代码虽然实现了我想要的功能,但是有一点问题,使用
tab
之后,因为是使用js代码插入的字符,不能使用Ctrl+z
来撤销,尤其是多行缩进后,使用撤销时,很影响用户体验 - 我自己暂时想不到解决的办法,如果有谁有好的建议,欢迎提出来帮我完善这个功能,感激不尽
6. 完美解决
- 在思否上提了个问题,得到了完美的解决方案,问题在这 --- 思否
- 使用
execCommand()
进行的操作可以使用Ctrl+z
来撤销,正是我要的结果 - 修改后的代码如下:
function onKeyPress(){
if(event.code !== "Tab") return true;
event.preventDefault();
let start = this.selectionStart;
let end = this.selectionEnd;
if(start === end){
document.execCommand('insertText',false," ");
}
else{
let strBefore = this.value.slice(0,start);
let curLineStart = strBefore.includes('\n')?strBefore.lastIndexOf('\n')+1 : 0;
let strBetween = this.value.slice(curLineStart,end+1);
let newStr = " " + strBetween.replace(/\n/g,'\n ');
let lineBreakCount = strBetween.split('\n').length;
let newStart = start + 2;
let newEnd = end + (lineBreakCount + 1)*2;
this.setSelectionRange(curLineStart,end);
document.execCommand("insertText",false,newStr);
this.setSelectionRange(newStart,newEnd);
}
}