做了一些改进。
当前行无内容时,隐藏提示框。
存在的问题,如果某一行的文字过多,则会自动换行,但是这种换行只是在视觉上的,html代码中没有体现。所以可能会导致高度计算不够。
效果:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>div提示框跟随光标移动</title>
<link rel="stylesheet" href="index.css">
</head>
<body>
<div id="editorDiv" contenteditable="true" oninput="handleInput(this)" class="editorDiv" style="font-family: 黑体;font-size: 20px"></div>
<div>
<span id="fakeContent"></span>
</div>
<div id="info" class="info">Demo</div>
<script src="index.js"></script>
</body>
</html>
index.css
* {
margin: 0;
padding: 0;
}
.editorDiv {
width: 340px;
height: 210px;
color: #606266;
resize:vertical;
overflow: hidden;
border: 1px solid #DCDFE6;
border-radius: 4px;
transition: border-color .2s cubic-bezier(.645,.045,.355,1);
margin: 50px;
padding: 20px;
}
.editorDiv:focus {
border:1px solid #409EFF;
outline: none;
}
.info {
width: 47px;
height: 20px;
position: absolute;
border:1px solid crimson;
top: 0;
left: 0;
display: none;
}
#fakeContent {
visibility: hidden;
display: inline-block;
background-color: #409EFF;
}
index.js
let editorDiv = document.querySelector('#editorDiv');
let fakeContent = document.querySelector('#fakeContent');
let info = document.querySelector('#info');
/**
* 输入事件
* */
function handleInput(that) {
info.style.display = 'inherit';
const parentOffset = getParentOffset();
const cursorCoordinate = getCursorCoordinate(that);
//文本数组,div每一行都是一个string
const contentStrList = getNodeList(that).map(node => node.textContent);
//光标y坐标也就是contentStrList的索引
if(cursorCoordinate.x === 0) {
info.style.display = 'none';
return;
}
const y = cursorCoordinate.y;
//按照光标偏移量截取字符串
fakeContent.textContent = contentStrList[y].substr(0,cursorCoordinate.x);
const fakeContentStyle = getComputedStyle(fakeContent,null);
info.style.left = (parseFloat(fakeContentStyle.width)+parentOffset.leftOffset)+'px';
info.style.top = ((y+1)*parseFloat(fakeContentStyle.height)+parentOffset.topOffset)+'px';
}
/**
* 获取div内容并转为Node数组
* @param elem
* @returns {ChildNode[]}
*/
function getNodeList(elem) {
return Array.from(elem.childNodes);
}
/**
* 获取光标的坐标
* @param elem
* @returns {{x: number 光标偏移量, y: number 光标在div子元素数组中的索引}}
*/
function getCursorCoordinate(elem) {
const selection = window.getSelection();
const cursorIndex = getNodeList(elem).findIndex(node =>
node.firstChild === selection.anchorNode|| node === selection.anchorNode);
const cursorOffset = selection.anchorOffset;
return {
x: selection.anchorOffset,
y: cursorIndex
}
}
/**
* 克隆字体和字号
* @param src
* @param dest
*/
function cloneFont(src,dest) {
let srcStyle = getComputedStyle(src);
dest.style.fontFamily = srcStyle.fontFamily;
dest.style.fontSize = srcStyle.fontSize;
}
/**
* 获取div编辑框编辑区域左上角距离屏幕可视区域的偏移量
* @returns {{topOffset: number, leftOffset: number}}
*/
function getParentOffset() {
let boundingClientRect = editorDiv.getBoundingClientRect();
let computedStyle = getComputedStyle(editorDiv,null);
return {
leftOffset: boundingClientRect.left+parseFloat(computedStyle.paddingLeft)+parseFloat(computedStyle.borderLeftWidth),
topOffset: boundingClientRect.top+parseFloat(computedStyle.paddingTop)+parseFloat(computedStyle.borderTopWidth)
}
}
cloneFont(editorDiv,fakeContent);