如何做一个高级的文本编辑器 textarea,拥有快捷键操作
最近想做一个高级点的 textarea
,支持 JetBrains 系列软件的快捷键,比如:
- CTRL+ D 复制当前行。
- Tab 在前面插入 4 个空格。
- Shift + Tab 删除行前的空格,多于4个,一次性删除4个,小于四个,清除前面的空格。
- CTRL+ ← 移动到行最左端
- CTRL+ → 移动到行最右端
一步一步来,你需要知道一些关于 input textarea 的基础知识
一、重点: input
textarea
基础知识
要想做好一个文本编辑器,就需要知道输入元素 input textarea 的基础知识,不是一般人都知道的知识。
- 获取 textarea 中光标的位置:
textarea.selectionStart
、event.target.selectionStart
- 设置 textarea 中的光标位置:
textarea.setSelectionRange()
本文中的通用方法:
/**
* 去除前面的空格
* @param initSpaceCount 初始空格数
* @param lineContent 行的内容
* @returns {*}
*/
removeSpaceBeforeLine(initSpaceCount, lineContent){ // 去除字符行中前面的空格
let countSpace = initSpaceCount
if (lineContent.substring(0,1) === ' '){
countSpace = countSpace + 1
lineContent = lineContent.substring(1)
return this.removeSpaceBeforeLine(countSpace, lineContent)
} else {
return {countSpace, lineContent}
}
},
getTextareaInfo(textarea, textContent){
let cursorPosition = textarea.selectionStart // cursorPos
let cursorLineIndex = textContent.substring(0, cursorPosition).split('\n').length - 1 // 光标所在行
let textLineArray = textContent.split('\n') // 原始文字 行数组
let cursorLineContent = textLineArray[cursorLineIndex] // 光标所在行的内容
return {
cursorPosition,
cursorLineIndex,
textLineArray,
cursorLineContent
}
},
二、CTRL + D 复制当前行
如何实现
- 确定光标位置
- 获取光标所在行的内容
- 截取
0-光标位置
的字符串,substring
substring.split('\n')
- 得到光标在第 n 行
- 切割整个文本,
split('\n')
,获得splitLineArray
- 在获得的字符串数组中取到上面第 n 行的内容,
newLineContent = splitLineArray[n]
- 截取
- 在取得的内容行数组中,插入刚才那行的内容
splitLineArray.splice(n, 1, newLineContent )
- 生成最终字符串
splitLineArray.join('\n')
- 定位光标到原来的光标位置
代码
// CTRL + D 复选行
if ((event.ctrlKey || event.metaKey) && event.key === 'd') {
event.preventDefault()
let textarea = this.$refs.textarea // dom
let textAreaInfo = this.getTextareaInfo(textarea, this.diary.content)
textAreaInfo.textLineArray.splice(textAreaInfo.cursorLineIndex, 0, textAreaInfo.cursorLineContent)
this.diary.content = textAreaInfo.textLineArray.join('\n')
this.$nextTick(_=>{
textarea.setSelectionRange(textAreaInfo.cursorPosition, textAreaInfo.cursorPosition) // 定位光标
})
}
三、tab 在光标后面插入 4 个空格
如何实现
- 得到 textarea 中光标的位置
index
。 - 字符串根据
index
位置切割成前后两块。 - 最终结果:前半部分 + 4个空格 + 后半部分
- 把光标移动到
index + 4
的位置
四、shift + tab 删除行内前面的空格
如何实现
- 获取光标位置 index
- 获取光标所在行的内容
- 判断行内容前面的空格,如果前四个字符 === 4个空格,就 substring(4)
- 如果不是,就判断第一个字符是否为空格
- 如果是 就 substring(1),去除第一个空格,记录已去除的空格数量,+1 ,接着执行 第4步,此处为递归。
- 如果不是说明已经没有空格可删了,就返回最终结果。
- 移动光标到
-4
或-spaceCount
的位置
代码
// shift + tab
if (event.shiftKey && event.key === 'Tab'){
event.preventDefault()
let textarea = this.$refs.textarea // dom
let textAreaInfo = this.getTextareaInfo(textarea, this.diary.content)
let tempLine = textAreaInfo.cursorLineContent
let deleteSpaceCount = 0
if (tempLine.substring(0,4) === ' '){
tempLine = tempLine.substring(4)
deleteSpaceCount = 4
} else {
let trimSpaceResult = this.removeSpaceBeforeLine(0, tempLine)
tempLine = trimSpaceResult.lineContent
deleteSpaceCount = trimSpaceResult.countSpace
}
textAreaInfo.textLineArray.splice(textAreaInfo.cursorLineIndex,1, tempLine)
this.diary.content = textAreaInfo.textLineArray.join('\n')
this.$nextTick(_=>{
textarea.setSelectionRange(textAreaInfo.cursorPosition - deleteSpaceCount, textAreaInfo.cursorPosition - deleteSpaceCount)
})
} else if (event.key === 'Tab'){
// tab
event.preventDefault()
let textarea = this.$refs.textarea // dom
let textAreaInfo = this.getTextareaInfo(textarea, this.diary.content)
let contentBeforeCursor = this.diary.content.substring(0,textAreaInfo.cursorPosition)
let contentAfterCursor = this.diary.content.substring(textAreaInfo.cursorPosition)
this.diary.content = contentBeforeCursor + ' ' + contentAfterCursor
this.$nextTick(_=>{
textarea.setSelectionRange(textAreaInfo.cursorPosition + 4, textAreaInfo.cursorPosition + 4)
})
}
五、CTRL+ ← 移动到行最左端,CTRL+ → 移动到行最右端
1. CTRL+ ← 移动到行最左端 如何实现
- 获取光标位置
- 获取光标位置行的行号
lineIndex
- 截取
lineIndex
之前的行数组 join('\n')
前面的数组,取到前面内容的 index + 1- 就是 ctrl + ← 的位置,设置光标到这个位置即可
2. CTRL+ → 移动到行最右端 如何实现
- 获取光标位置
- 获取光标位置行的行号
lineIndex
- 截取
lineIndex + 1
之前的行数组 join('\n')
前面的数组,取到前面内容的 index- 就是 ctrl + → 的位置,设置光标到这个位置即可
代码:
// CTRL + ArrowLeft 移到最左端
if ((event.ctrlKey || event.metaKey) && event.key === 'ArrowLeft') {
event.preventDefault()
let textarea = this.$refs.textarea // dom
let textAreaInfo = this.getTextareaInfo(textarea, this.diary.content)
let linesBefore = textAreaInfo.textLineArray.slice(0, textAreaInfo.cursorLineIndex)
let textBefore = linesBefore.join('\n')
let newCursorLocation = textBefore.length + 1 // -1行末尾 + 1
this.$nextTick(_=>{
textarea.setSelectionRange(newCursorLocation, newCursorLocation)
})
}
// CTRL + ArrowRight 移到最右端
if ((event.ctrlKey || event.metaKey) && event.key === 'ArrowRight') {
event.preventDefault()
let textarea = this.$refs.textarea // dom
let textAreaInfo = this.getTextareaInfo(textarea, this.diary.content)
let linesBefore = textAreaInfo.textLineArray.slice(0, textAreaInfo.cursorLineIndex + 1)
let textBefore = linesBefore.join('\n')
let newCursorLocation = textBefore.length
this.$nextTick(_=>{
textarea.setSelectionRange(newCursorLocation, newCursorLocation) // 定位光标
})
}