export const cursors = [] export class Cursor { constructor(targetNode = null, insertIndex = null) { this.targetNode = targetNode // 初始时可以直接指定目标文本节点 this.insertIndex = insertIndex // 初始时可以直接指定插入位置索引 this.range = document.createRange() this.cursorElement = this.createCursorElement() document.body.appendChild(this.cursorElement) } // 创建光标元素 createCursorElement() { const cursorElement = document.createElement("div") Object.assign(cursorElement.style, { position: "absolute", width: "2px", height: "1em", backgroundColor: "black", zIndex: "9999", pointerEvents: "none", display: "none", // 初始隐藏 transition: "all 0.1s ease" }) return cursorElement } // 设置目标文本节点和插入位置 setTarget(node, index) { this.targetNode = node this.insertIndex = index this.updateRange() } // 更新范围 updateRange() { if (this.targetNode && this.insertIndex !== null) { this.range.setStart(this.targetNode, this.insertIndex) this.range.setEnd(this.targetNode, this.insertIndex) } } // 插入文本 insertText(text) { const insertionNode = document.createTextNode(text) this.updateRange() this.range.insertNode(insertionNode) // 更新插入位置 this.insertIndex += text.length this.updateRange() // 更新光标范围 return insertionNode } // 获取当前光标的矩形位置 getBoundingClientRect() { this.range.setStart(this.targetNode, this.insertIndex) this.range.setEnd(this.targetNode, this.insertIndex) return this.range.getBoundingClientRect() } // 更新光标位置 updatePosition({ left, top }) { this.cursorElement.style.left = `${left}px` this.cursorElement.style.top = `${top}px` this.cursorElement.style.display = "block" } // 移除光标元素 remove() { this.cursorElement.remove() } oninput({ value }) { const text = this.targetNode.textContent const left = text.slice(0, this.insertIndex) const right = text.slice(this.insertIndex) this.insertIndex += value.length this.targetNode.textContent = left + value + right this.updatePosition(this.getBoundingClientRect()) } onkeydown({ key }) { if (key === "ArrowUp" && this.targetNode.parentNode.previousElementSibling) { // 先取兄弟元素的最后一个子元素, 没有则取兄弟元素, 没有则向上回溯 this.targetNode = Array.from(this.targetNode.parentNode.previousElementSibling.childNodes).find(node => node.nodeType === Node.TEXT_NODE) this.insertIndex = Math.max(0, Math.min(this.targetNode.textContent.length, this.insertIndex)) this.updatePosition(this.getBoundingClientRect()) } if (key === "ArrowDown" && this.targetNode.parentNode.nextElementSibling) { // 先取当前子元素, 没有则取下一个兄弟元素, 没有则向上回溯 this.targetNode = Array.from(this.targetNode.parentNode.nextElementSibling.childNodes).find(node => node.nodeType === Node.TEXT_NODE) this.insertIndex = Math.max(0, Math.min(this.targetNode.textContent.length, this.insertIndex)) this.updatePosition(this.getBoundingClientRect()) } if (key === "ArrowLeft") { this.insertIndex = Math.max(0, Math.min(this.targetNode.textContent.length, this.insertIndex - 1)) return this.updatePosition(this.getBoundingClientRect()) } if (key === "ArrowRight") { this.insertIndex = Math.max(0, Math.min(this.targetNode.textContent.length, this.insertIndex + 1)) return this.updatePosition(this.getBoundingClientRect()) } if (key === "Backspace") { const text = this.targetNode.textContent const left = text.slice(0, Math.max(0, this.insertIndex - 1)) const right = text.slice(this.insertIndex) this.targetNode.textContent = left + right this.insertIndex = Math.max(0, this.insertIndex - 1) return this.updatePosition(this.getBoundingClientRect()) } } } export default { cursors, Cursor }