最近在仿浮墨笔记 flomo 学习,碰到在富文本编辑器中展示已有的标签 tag,苦恼于如何定位光标输入位置,直到看见大神的代码,恍然大悟。
预期目标
浮墨效果
鼠标点击 #
就会联想已经存在的标签,弹出框位置是根据现在的光标位置变化的
自己查到 textarea
标签定位光标位置的API只能查到是第几个字,而不能查到具体像素位置
代码是这样的:
const textarea = document.getElementById('textarea');
textarea.oninput = function () {
console.log(textarea.selectionEnd)
}
下面看看大佬的实现方法吧!
实现方法
拷贝一个和输入框相同样式的容器 div,并且将内容也填充进去,然后在内容后面加一个 span 用来计算光标位置,光标位置即 span 距离容器 div 的 offsetTop, offsetLeft
// 获取光标位置 !!!核心代码
const getCursorPostion = (input: HTMLTextAreaElement) => {
// 获取 “基于 textarea” 的坐标
const { offsetLeft: inputX, offsetTop: inputY, selectionEnd: selectionPoint } = input;
const div = document.createElement("div");
//复制 input 所有样式并添加溢出换行,不显示在dom中
const copyStyle = window.getComputedStyle(input);
for (const item of copyStyle) {
div.style.setProperty(item, copyStyle.getPropertyValue(item));
}
div.style.position = "fixed";
div.style.visibility = "hidden";
div.style.whiteSpace = "pre-wrap"
//将 textarea 光标之前的内容拷贝到div上
const swap = ".";
const inputValue = input.tagName === "INPUT" ? input.value.replace(/ /g, swap) : input.value;
const textContent = inputValue.substring(0, selectionPoint || 0);
div.textContent = textContent;
if (input.tagName === "TEXTAREA") {
div.style.height = "auto";
}
//为 div 文字后面添加 span
const span = document.createElement("span");
span.textContent = inputValue.substring(selectionPoint || 0) || ".";
div.appendChild(span);
document.body.appendChild(div);
//获取 span 距离顶部,左侧的距离
const { offsetLeft: spanX, offsetTop: spanY } = span;
document.body.removeChild(div);
//标签联想框的位置 = 初始位置 + span 位置
return {
x: inputX + spanX,
y: inputY + spanY,
};
};
为了看的方便,将代码中的从 body 移出 div 代码先注释了
上面一堆属性全是复制 textarea
的,因为有一些默认样式也给复制过来了
文档中的 span
在 div
的内容之后(因为行内元素),所以光标位置等于 span
的位置,然后因为 offsetTop
等属性是根据元素左上角定位的,还需要加点偏移,实际获取一下位置试一试
结果非常的 Amazing 啊,这正是输入光标的位置,不得不佩服大佬的思维
这样最后添加点偏移,设置 联想输入框 的位置就可以了
这是整体的一点点代码
// 更新联想框位置
const updateTagSelectorPopupPosition = useCallback(() => {
if (!editorRef.current || !tagSeletorRef.current) {
return;
}
const seletorPopupWidth = 128;
const editorWidth = editorRef.current.element.clientWidth;
// 计算光标位置
const { x, y } = getCursorPostion(editorRef.current.element);
// 一些情况需要添加偏移
const left = x + seletorPopupWidth + 16 > editorWidth ? editorWidth + 20 - seletorPopupWidth : x + 2;
const top = y + 32 + 6;
// 设置联想框位置
tagSeletorRef.current.scroll(0, 0);
tagSeletorRef.current.style.left = `${left}px`;
tagSeletorRef.current.style.top = `${top}px`;
}, []);
总结
学习到了解决问题的新思路,之前还遇到过一些 JQuery 大佬的花式操作,越来越证明学框架仅仅只是框架而已,编程思维也要跟上来