contenteditable div达字符限制后删中间内容光标跳末尾如何修复
问题根源
你当前的光标跳转问题核心是checkMessageLength函数的逻辑存在两个缺陷:
- 没有区分用户操作类型:不管是输入新增内容还是删除内容,只要当前内容长度超过限制,就会重写可编辑div的
innerHTML,并强制把光标移到末尾 - 删除操作本身会让内容总长度减少,正常删除后长度会落到限制以内,根本不需要走截断、重写DOM、移动光标的逻辑
修复方案
步骤1:新增光标位置保存/恢复工具方法
如果确实需要修改innerHTML,我们要先保存当前光标位置,修改后恢复,而不是直接移到末尾:
// 新增:保存当前光标在可编辑元素内的偏移量 saveCursorPosition = (contentEditableElement) => { let start = 0, end = 0; const doc = contentEditableElement.ownerDocument || document; const win = doc.defaultView || window; const sel = win.getSelection(); if (sel.rangeCount > 0) { const range = sel.getRangeAt(0); const preCaretRange = range.cloneRange(); preCaretRange.selectNodeContents(contentEditableElement); preCaretRange.setEnd(range.endContainer, range.endOffset); end = preCaretRange.toString().length; start = end - range.toString().length; } return { start, end }; } // 新增:根据偏移量恢复光标位置 restoreCursorPosition = (contentEditableElement, pos) => { const range = document.createRange(); const sel = window.getSelection(); let currentNode = contentEditableElement.firstChild; let charCount = 0; while (currentNode) { const nodeLength = currentNode.nodeType === 3 ? currentNode.length : currentNode.textContent.length; const nextCount = charCount + nodeLength; if (pos.end <= nextCount) { range.setStart(currentNode, pos.end - charCount); range.collapse(true); break; } charCount = nextCount; currentNode = currentNode.nextSibling; } sel.removeAllRanges(); sel.addRange(range); }
步骤2:修改emitChange函数,获取操作类型和光标位置
在emitChange最开头新增逻辑:
emitChange=(event)=>{ // 新增:获取操作类型+提前保存光标位置 const inputType = event.nativeEvent.inputType; const editableEle = event.target; const cursorPos = this.saveCursorPosition(editableEle); // 原有代码不变 if(!this.props.auth.user){ this.setState({warning:"Please sign in to post your message"}) this.refs['newmessage'].innerHTML = ''; return } // ... 中间原有代码全部保留 ... // 修改:调用checkMessageLength的时候传入操作类型和光标位置 this.checkMessageLength(editableEle, inputType, cursorPos); // ... 后面原有代码全部保留 ... }
步骤3:修改checkMessageLength函数,调整触发截断+移光标的逻辑
// 修改:新增两个入参 checkMessageLength=(contentEditableElement, inputType, cursorPos)=>{ let value =contentEditableElement.innerText const user = this.props.auth.user let tags= [...this.state.multiAssets,...this.state.multiUsers]; if(value.length==0){ contentEditableElement.classList.add('is-placeholder') this.setState({ warning:'' }) }else{ contentEditableElement.classList.remove('is-placeholder') } let newvalue=value; let count = 0 tags.forEach(x => { let pos = value.indexOf(x.value) while(pos !==-1 ){ count += x.value.length pos = value.indexOf(x.value,pos+1) } }) // 新增:计算当前实际有效长度+限制,判断操作类型 const validLength = value.length - count; const limit = user ? userPermissions[user.role].limit : 150; const isDeleteOp = inputType.includes('delete'); // 删除操作且长度已经降到限制内,直接返回,不需要截断和移动光标 if (isDeleteOp && validLength <= limit) { this.setState({ warning: '', message: newvalue, previousValue: value }) return; } if(user){ let role=user.role let permission = userPermissions[role] if( validLength > permission.limit){ this.setState({ warning:'Maximum allowed character limit is '+permission.limit }) newvalue = value.slice(0,permission.limit+count) + '<span class="excess-content">'+value.slice(permission.limit+count,permission.limit+count+20)+'</span>' newvalue = this.highlightTags(newvalue,tags) contentEditableElement.innerHTML = newvalue // 修改:只有插入操作才把光标移到末尾,删除操作恢复原来的光标位置 if (isDeleteOp) { // 防止删除后光标偏移超过新内容长度,取最小值 const safePos = Math.min(cursorPos.end, contentEditableElement.innerText.length); this.restoreCursorPosition(contentEditableElement, {start: safePos, end: safePos}); } else { this.changeCursor(contentEditableElement); } }else{ this.setState({ warning:'' }) } }else{ if(validLength > 150){ this.setState({ warning:'You have reached maximum character limit' }) newvalue = value.slice(0,140+count) + '<span class="excess-content">'+value.slice(140+count,count+160)+'</span>' newvalue = this.highlightTags(newvalue,tags) contentEditableElement.innerHTML = newvalue // 同上修改光标逻辑 if (isDeleteOp) { const safePos = Math.min(cursorPos.end, contentEditableElement.innerText.length); this.restoreCursorPosition(contentEditableElement, {start: safePos, end: safePos}); } else { this.changeCursor(contentEditableElement); } } else{ this.setState({ warning:'' }) } } this.setState({ message:newvalue, previousValue:value }) }
额外优化建议
- 建议把截断逻辑里的
excess-content冗余内容直接去掉,不要额外插入span标签,contenteditable内的额外元素可能会导致光标定位异常 - 可以提前拦截输入:当已经达到字数限制时,禁止非删除类的输入操作,从根源避免超长度的情况出现
内容的提问来源于stack exchange,提问作者punit das
相关产品推荐
相关产品推荐

