Vue组件中如何在contenteditable div光标位置插入span元素
解决contenteditable元素中光标位置插入动态变量标签的问题
你的核心问题是打开下拉菜单时,contenteditable元素失去焦点,导致原光标位置的Selection/Ranges丢失,所以点击变量时无法获取到之前的光标位置。下面是修复方案:
关键修改点
- 保存光标位置:在打开下拉菜单前,记录当前contenteditable元素的光标范围
- 插入标签时使用保存的范围,而不是实时获取Selection(此时焦点在下拉菜单上,Selection已经改变)
修改后的完整代码
<template> <div class="relative inline-flex items-center"> <button type="button" @click="toggleDropdown" class="inline-flex items-center gap-x-1.5 rounded-full bg-green-100 hover:bg-green-50 px-2 py-1 text-xs font-medium text-green-700"> <BoltIcon class="h-3 w-3 fill-green-700" viewBox="0 0 12 12" aria-hidden="true"></BoltIcon> Add Dynamic Variable </button> <div v-if="dropdownVisible" class=""> <div class="py-1 absolute right-0 z-10 mt-4 origin-top-right rounded-md bg-green-100 shadow-lg ring-1 ring-white/20 focus:outline-none"> <div v-for="variable in variables" :key="variable" class="pl-3 pr-4 py-2 text-xs flex flex-row font-medium text-green-700 cursor-pointer hover:bg-green-50" @click="selectVariable(variable)"> <BoltIcon class="h-3 w-3 mr-1 fill-green-700" viewBox="0 0 12 12" aria-hidden="true"></BoltIcon> {{ variable }} </div> </div> </div> </div> </template> <script setup> import { ref, onMounted } from 'vue'; import { BoltIcon } from '@heroicons/vue/24/solid'; const props = defineProps({ variables: { type: Array, required: true, }, targetElementId: { type: String, required: true, }, }); const dropdownVisible = ref(false); const inputField = ref(null); const savedRange = ref(null); // 用于保存光标位置的Range对象 const toggleDropdown = () => { if (inputField.value) { // 打开下拉前保存当前光标位置 const selection = window.getSelection(); if (selection.rangeCount > 0) { savedRange.value = selection.getRangeAt(0); } // 如果是关闭下拉,恢复焦点到输入框 if (!dropdownVisible.value) { inputField.value.focus(); // 恢复光标位置(可选,提升体验) if (savedRange.value) { const selection = window.getSelection(); selection.removeAllRanges(); selection.addRange(savedRange.value); } } } dropdownVisible.value = !dropdownVisible.value; }; const selectVariable = (variable) => { const inputEl = inputField.value; if (!inputEl) { console.error(`Input field with id ${props.targetElementId} not found.`); return; } const variableLabel = document.createElement('span'); variableLabel.className = 'inline-flex items-center gap-x-0.5 rounded-md bg-green-100 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-600/20'; variableLabel.innerHTML = `${variable} <button type="button" class="group relative -mr-1 h-3.5 w-3.5 rounded-sm hover:bg-green-600/20"> <span class="sr-only">Remove</span> <svg viewBox="0 0 14 14" class="h-3.5 w-3.5 stroke-green-700/50 group-hover:stroke-green-700/75"> <path d="M4 4l6 6m0-6l-6 6" /> </svg> <span class="absolute -inset-1"></span> </button>`; variableLabel.setAttribute('contenteditable', 'false'); variableLabel.querySelector('button').addEventListener('click', () => { variableLabel.remove(); }); // 使用保存的光标位置插入标签 if (savedRange.value) { const range = savedRange.value; range.deleteContents(); range.insertNode(variableLabel); // 插入后将光标移到标签后面 range.collapse(false); const selection = window.getSelection(); selection.removeAllRanges(); selection.addRange(range); savedRange.value = null; // 清空保存的范围 } else { // 没有保存的光标位置时,默认插入到末尾 inputEl.appendChild(variableLabel); } dropdownVisible.value = false; inputEl.focus(); // 插入后恢复焦点 }; onMounted(() => { inputField.value = document.getElementById(props.targetElementId); }); </script> <style scoped> </style>
核心逻辑说明
- 添加
savedRange变量存储打开下拉菜单前的光标范围,因为点击下拉项时,contenteditable元素已经失去焦点,实时获取的Selection不再指向原光标位置 - 在
toggleDropdown中,打开下拉前保存当前Range;关闭下拉时可选恢复光标位置,提升用户体验 - 在
selectVariable中,优先使用保存的savedRange插入标签,插入后将光标移到标签后方,保持输入连贯性 - 插入完成后恢复contenteditable元素的焦点,确保后续输入正常
内容的提问来源于stack exchange,提问作者M P
相关产品推荐
相关产品推荐

