如何通过方向键在div元素间移动光标(仿Notion编辑器)
实现方向键在div间移动光标(仿Notion编辑器)
要实现你需要的效果,核心是拦截方向键事件,手动计算光标跳转位置并更新选区。以下是具体实现方案:
第一步:调整HTML结构
首先给每个可编辑的div加上contenteditable="true",让光标能进入并编辑:
<div class="editor-block" contenteditable="true"> hello word </div> <div class="editor-block" contenteditable="true">hi</div> <div class="editor-block" contenteditable="true">notion</div>
第二步:JavaScript逻辑实现
通过监听键盘事件,处理上下方向键的光标跳转逻辑:
const blocks = document.querySelectorAll('.editor-block'); // 全局监听键盘按下事件 document.addEventListener('keydown', (e) => { // 只处理上下方向键 if (!['ArrowUp', 'ArrowDown'].includes(e.key)) return; e.preventDefault(); // 阻止浏览器默认的光标移动/滚动行为 const selection = window.getSelection(); if (!selection.rangeCount) return; const currentRange = selection.getRangeAt(0); const currentBlock = currentRange.commonAncestorContainer.closest('.editor-block'); if (!currentBlock) return; e.key === 'ArrowDown' ? handleDown(currentRange, currentBlock) : handleUp(currentRange, currentBlock); }); // 处理向下方向键 function handleDown(range, currentBlock) { const text = currentBlock.textContent; const currentOffset = range.startOffset; const nextLinePos = text.indexOf('\n', currentOffset); // 同一div内有下一行,跳转到下一行第一个非空白字符 if (nextLinePos !== -1) { let nextOffset = nextLinePos + 1; // 跳过换行后的空格/制表符 while (nextOffset < text.length && /\s/.test(text[nextOffset])) { nextOffset++; } updateCursor(currentBlock.firstChild, nextOffset); return; } // 跳转到下一个div的开头 const currentIndex = Array.from(blocks).indexOf(currentBlock); if (currentIndex >= blocks.length - 1) return; const nextBlock = blocks[currentIndex + 1]; updateCursor(nextBlock.firstChild, 0); nextBlock.focus(); } // 处理向上方向键 function handleUp(range, currentBlock) { const text = currentBlock.textContent; const currentOffset = range.startOffset; const prevLinePos = text.lastIndexOf('\n', currentOffset - 1); // 同一div内有上一行,跳转到上一行第一个非空白字符 if (prevLinePos !== -1) { let prevOffset = prevLinePos + 1; while (prevOffset < text.length && /\s/.test(text[prevOffset])) { prevOffset++; } updateCursor(currentBlock.firstChild, prevOffset); return; } // 跳转到上一个div的末尾 const currentIndex = Array.from(blocks).indexOf(currentBlock); if (currentIndex <= 0) return; const prevBlock = blocks[currentIndex - 1]; updateCursor(prevBlock.firstChild, prevBlock.textContent.length); prevBlock.focus(); } // 通用更新光标位置的函数 function updateCursor(textNode, offset) { const newRange = document.createRange(); newRange.setStart(textNode, offset); newRange.collapse(true); const selection = window.getSelection(); selection.removeAllRanges(); selection.addRange(newRange); }
关键逻辑说明
- 拦截默认行为:阻止浏览器自带的方向键滚动或光标移动,让我们能完全控制光标位置
- 选区获取:通过
window.getSelection()获取当前光标所在的Range对象,定位到当前编辑块 - 行内跳转:通过文本中的换行符
\n判断是否有上下行,跳转时自动跳过行首的空白字符,模拟Notion的排版逻辑 - 块间跳转:当光标在块的最后一行/第一行时,按下方向键会跳转到相邻块的开头/末尾
注意事项
- 如果div内存在多个文本节点(比如插入了其他元素),需要调整
currentBlock.firstChild的逻辑,遍历找到对应的文本节点 - 可以根据需求修改块间跳转的目标位置(比如跳转到上一个块的开头而非末尾)
内容的提问来源于stack exchange,提问作者Taylous
相关产品推荐
相关产品推荐

