数据是一串数组,要把数组中的内容按照如上图所示展示,同时像textArea一样编辑,编辑后的结果以数组的形式给后端。
给多行p标签添加contentEditable='true'属性,实现对应界面的展示
键盘事件的点击的操作,实现对光标以及数据的处理:如点击删除、上、下、enter等按键
实现(此处只展示部分关键代码)
1.拿到对应的数据并渲染到界面上,react中需要suppressContentEditableWarning='true'排除警告,关键代码如下所示:
this.state = {
wordsList: [
'好无聊啊,我们玩点游戏吧',
'嗯好啊,玩什么呢?',
'我夸你一句,你夸我一句怎么样?',
'可以啊!你先说',
'你真漂亮!',
'你眼光真好!',
'!!!',
{this.state.wordsList.map((value, index) => {
return (
{index}
</span>
id={index}
contentEditable="true"
suppressContentEditableWarning="true"
onKeyDown={(e) => {
this.onKeyDown(e);
onKeyUp={(e) => {
this.onKeyUp(e);
onMouseDown={() => {
this.setIndex(index);
onClick={(e) => {
this.onClick(e);
{value}
</div>
</div>
2.当键盘点击时,根据keycode的值判断点击的哪个按键并处理数据和光标。
点击某一行文本时,记录当前点击时光标所在位置(为后续键盘处理上下按键时所需要的)
比如:当我点击到第四行的‘先’和‘说’中间时
再去按两次键盘‘下’方向键时,若该行文字长度小于光标所在位置,则光标处于最末尾,若该行文本内容大于所记录的光标位置,则光标处于光标所记录的位置。
onClick(e) {
let sel = window.getSelection();
let range = sel.getRangeAt(0);
this.setState({
recordIndex: range.startOffset
当点击上下(键盘码:38、40)按键时,需要获取上/下行dom元素,创建range范围对象,上/下行文本长度与当前光标所在位置进行比较,并设置光标所在行和位置
if (e.keyCode == 38) {
e.preventDefault();
this.setCursor(index - 1)
} else if (e.keyCode == 40) {
e.preventDefault();
this.setCursor(index + 1)
setCursor(domIndex) {
let txtFocus = document.getElementById(domIndex);
if (!txtFocus) {
return
const range = document.createRange()
if (txtFocus.innerText.length < this.state.recordIndex) {
range.setStart(txtFocus.firstChild, txtFocus.innerText.length)
range.setEnd(txtFocus.firstChild, txtFocus.innerText.length)
} else {
range.setStart(txtFocus.firstChild, this.state.recordIndex)
range.setEnd(txtFocus.firstChild, this.state.recordIndex)
const sel = window.getSelection()
sel.removeAllRanges()
sel.addRange(range)
this.setState({
index: domIndex,
同理,当点击左/右键(键盘码:37、39)时,需要设置对应光标所在位置。此时需要特别留意光标在该行首尾时,再次点击左/右按键需要换行
当点击enter按键时(键盘码:13),需要判断光标位置是否在这行文字的最末尾,若是在最末尾,则需在后面增加一行,若在改行文本中间,需要把光标后面的内容放到下一行,并处理光标。部分代码如下
let sel = window.getSelection();
let range = document.createRange();
range = sel.getRangeAt(0);
if (e.keyCode == 13) {
e.preventDefault();
if (range.startOffset < innerText.length) {
wordsList.splice(nextIndex, 0, innerText.substring(range.startOffset));
wordsList[index] = innerText.substring(0, range.startOffset);
} else {
wordsList.splice(nextIndex, 0, "");
let txtFocus = document.getElementById(nextIndex);
txtFocus.focus();
this.forceUpdate();
this.setState({
index: nextIndex,
当点击删除按键(按键码:8)时,需要判断当前光标所在位置是否为行首,若为行首,则需要对改行内容进行换行处理,若不在行首则删除对应的文字并处理光标,切勿照搬照抄
if (e.keyCode == 8) {
if (range.startOffset == 0) {
e.preventDefault();
if (innerText.length == 0) {
wordsList.splice(index, 1);
if (window.getSelection) {
txtFocus.focus();
let range1 = window.getSelection();
range1.selectAllChildren(txtFocus);
range1.collapseToEnd();
} else if (document.selection) {
let range1 = document.selection.createRange();
range1.moveToElementText(range1);
range1.collapse(false);
range1.select();
this.forceUpdate();
this.setState({
index: prIndex,
} else {
innerText1 = innerText1.concat(innerText);
wordsList.splice(index, 1);
this.forceUpdate();
wordsList[prIndex] = innerText1;
this.setState({
index: prIndex,
}, () => {
range.setStart(txtFocus.firstChild, innerText1.length - innerText.length)
range.setEnd(txtFocus.firstChild, innerText1.length - innerText.length)
sel.removeAllRanges()
sel.addRange(range)
3.其他按键处理:后续可以对del,pgUp,pgDn,ctrl+s,ctrl+v等键盘事件进行相应处理;ctrl+s这种多键盘事件,可以根据键盘事件中event的ctrlKey来判断是否点击了ctrl。
有些富文本编辑器的光标采用自定义光标的方式,比如掘金写文章这里。这些等待后续研究,若哪位大佬有更好的见解,也请不吝赐教!