实时获取光标位置

// 创建全局唯一 marker
const globalMarker = (() => {
    const marker = document.createElement('span');
    marker.textContent = '\u200b';
    marker.style.cssText = 'position: absolute; visibility: hidden; pointer-events: none;';
    return marker;
})();

// 获取光标位置
function getCursorPosition() {
    const sel = window.getSelection();
    if (!sel.rangeCount) return null;

    // 克隆并 collapse Range
    const range = sel.getRangeAt(0).cloneRange();
    range.collapse(true);

    // 找到可编辑容器,用于取行高
    let hitNode = sel.focusNode;
    if (hitNode.nodeType === Node.TEXT_NODE) hitNode = hitNode.parentElement;
    const editableEl = hitNode.closest('[contenteditable="true"]');
    const style = editableEl ? window.getComputedStyle(editableEl) : null;
    // 优先用 line-height,否则 fallback 到 font-size * 1.625 或 26px
    const rawLineH = style && parseFloat(style.lineHeight)
        || (style && parseFloat(style.fontSize) * 1.625)
        || 26;
    const lineH = rawLineH;
    const paddingLeft = style ? parseFloat(style.paddingLeft) || 0 : 0;

    // 尝试浏览器原生的 clientRects
    const rects = Array.from(range.getClientRects());
    let baseRect, x;
    if (rects.length) {
        baseRect = rects[rects.length - 1];
    } else {
       // 先判断是否是段落空行,段落空行直接通过段落获取
       const paragraph = findParentParagraph(range.startContainer);
        if (paragraph && !paragraph.textContent.replace(/[\u200B-\u200D\uFEFF]/g, '').trim()) {
            baseRect = paragraph.getBoundingClientRect();
            const style = window.getComputedStyle(paragraph);
            x = baseRect.left + parseFloat(style.paddingLeft);
        } else {
            // 回退:插 marker 测一次
            range.insertNode(globalMarker);
            baseRect = globalMarker.getBoundingClientRect();
            globalMarker.remove();
        }
    }

    // 计算高度:统一用行高 * 比例
    const height = lineH * cursorHeightRelativeToLineHeight;

    // 计算 y:把原生/marker 获取的 rect.top 对齐到行高
    // rectTop + (rect.height - height)/2  可能让光标在垂直居中
    const gap = (baseRect.height - height) / 2;
    const y = baseRect.top + gap;

    // x 贴在文字末尾
    x = x ? x : baseRect.right;

    return baseRect.width + baseRect.height > 0 ? { x, y, height } : null;
}

image.png

留下你的脚步
推荐阅读