由于项目需要一个类似微信、QQ等聊天软件中输入@弹出人员列表的功能;现将基础代码贴在这里。

1、输入@,弹出人员列表展示面板,失去焦点;得到焦点后关闭面板;

2、面板的位置跟随光标位置;

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>contenteditable</title>
    <style>
        body {
            background: #e3e3e3;
        }

        .edit-box {
            position: absolute;
            left: 50%;
            top: 50%;
            transform: translate(-50%, -50%);
            width: 300px;
            height: 100px;
            background: #FFFFFF;
        }

        #inputRef {
            background: #FFFFFF;
            width: 300px;
            height: 100px;
            overflow-y: auto;
        }

        #inputRef:focus-visible {
            outline: none;
        }

        .input-placeholder {
            position: absolute;
            left: 10px;
            top: 10px;
            color: #e3e3e3;
            user-select: none;
        }

        .choose-panel {
            position: absolute;
            display: none;
            left: 0;
            top: 0;
            width: 150px;
            height: 150px;
            background: gray;
        }
    </style>
</head>

<body>
    <div class="edit-box">
        <!-- 输入框 -->
        <div id="inputRef" contenteditable="true"></div>
        <!-- 占位符 -->
        <div id="placeholder" class="input-placeholder">请输入内容</div>
        <!-- 面板 -->
        <div id="choosePanel" class="choose-panel"></div>
    </div>
    <script>
        const inputRef = document.getElementById('inputRef')
        const placeholder = document.getElementById('placeholder')
        const choosePanel = document.getElementById('choosePanel')
        // 点击输入框后,关闭面板
        inputRef.onfocus = () => {
            choosePanel.style.display = 'none'
        }
        // 输入@展示面板和设置面板位置
        inputRef.oninput = () => {
            const value = inputRef.innerText.trim();
            if (value) {
                placeholder.style.display = 'none'
                if (value.endsWith('@')) {
                    // 失去焦点
                    inputRef.blur()
                    const selection = getSelection()
                    const range = selection.getRangeAt(0)
                    // 获取光标所在节点相对文档的偏移量和宽度
                    const refPos = inputRef.getBoundingClientRect()
                    const refX = refPos.x
                    const refY = refPos.y
                    const refClientWidth = inputRef.clientWidth
                    // 获取光标偏移量
                    const pos = range.getBoundingClientRect()
                    const x = pos.x
                    const y = pos.y
                    // 根据相对节点偏移量和宽度和与节点宽度差来判断面板位置(光标左侧或者右侧)
                    if ((x - refX + 150) >= refClientWidth) {
                        choosePanel.style.left = `${x - refX - 150}px`
                        choosePanel.style.top = `${-150 + (y - refY)}px`
                        choosePanel.style.display = 'block'
                    } else {
                        choosePanel.style.left = `${x - refX}px`
                        choosePanel.style.top = `${-150 + (y - refY)}px`
                        choosePanel.style.display = 'block'
                    }
                } else {
                    choosePanel.style.display = 'none'
                }
            } else {
                placeholder.style.display = 'block'
            }
        }
    </script>
</body>

</html>