React环境下如何让contentEditable div失焦时仍显示非闪烁光标
React实现contentEditable失焦时显示非闪烁光标(仿Google Docs)
核心思路
Google Docs的实现逻辑是避免编辑器真正失焦,或在失焦后立即恢复选区并通过CSS模拟静态光标。下面是具体的React实现方案:
步骤1:保存编辑器选区状态
通过监听全局selectionchange事件,实时保存编辑器内的选区位置,确保失焦后能精准恢复:
import { useRef, useState, useEffect } from 'react'; function EditableEditor() { const editorRef = useRef(null); const [savedRange, setSavedRange] = useState(null); useEffect(() => { const handleSelectionChange = () => { const selection = window.getSelection(); // 仅保存编辑器内部的选区 if (selection.rangeCount > 0 && editorRef.current?.contains(selection.anchorNode)) { setSavedRange(selection.getRangeAt(0).cloneRange()); } }; document.addEventListener('selectionchange', handleSelectionChange); return () => document.removeEventListener('selectionchange', handleSelectionChange); }, []);
步骤2:拦截页面点击,恢复选区并渲染静态光标
监听页面点击事件,当点击区域不在编辑器内时,强制编辑器重新聚焦并恢复之前的选区,同时通过CSS变量定位自定义光标:
useEffect(() => { const handleDocumentClick = (e) => { if (!editorRef.current || editorRef.current.contains(e.target)) return; // 恢复编辑器焦点与选区 editorRef.current.focus(); const selection = window.getSelection(); selection.removeAllRanges(); if (savedRange) { selection.addRange(savedRange); // 计算光标相对编辑器的位置,设置CSS变量 const rect = savedRange.getBoundingClientRect(); const editorRect = editorRef.current.getBoundingClientRect(); editorRef.current.style.setProperty('--cursor-left', `${rect.left - editorRect.left}px`); editorRef.current.style.setProperty('--cursor-top', `${rect.top - editorRect.top}px`); } // 启用静态光标样式 editorRef.current.classList.add('non-blinking-cursor'); }; document.addEventListener('click', handleDocumentClick); return () => document.removeEventListener('click', handleDocumentClick); }, [savedRange]);
步骤3:处理编辑器失焦边界情况
监听编辑器blur事件,针对Tab键切换、外部组件聚焦等场景,延迟恢复选区:
const handleBlur = () => { // 延迟执行,确保点击事件优先处理 setTimeout(() => { const selection = window.getSelection(); if (selection.rangeCount === 0 && savedRange) { editorRef.current.focus(); selection.removeAllRanges(); selection.addRange(savedRange); // 重新计算光标位置 const rect = savedRange.getBoundingClientRect(); const editorRect = editorRef.current.getBoundingClientRect(); editorRef.current.style.setProperty('--cursor-left', `${rect.left - editorRect.left}px`); editorRef.current.style.setProperty('--cursor-top', `${rect.top - editorRect.top}px`); editorRef.current.classList.add('non-blinking-cursor'); } }, 10); };
步骤4:CSS实现静态光标
隐藏默认闪烁光标,通过伪元素绘制静态光标:
.content-editable { position: relative; /* 隐藏系统默认光标 */ caret-color: transparent; padding: 8px; min-height: 200px; border: 1px solid #ddd; } /* 静态光标样式 */ .content-editable.non-blinking-cursor::after { content: ''; position: absolute; width: 2px; height: 1.2em; background-color: #222; left: var(--cursor-left, 0); top: var(--cursor-top, 0); pointer-events: none; /* 不干扰点击交互 */ }
完整组件返回部分
return ( <div ref={editorRef} contentEditable className="content-editable" onBlur={handleBlur} onClick={() => editorRef.current.classList.remove('non-blinking-cursor')} > 在这里输入内容... </div> ); } export default EditableEditor;
注意事项
- 若编辑器内容存在滚动,需在滚动事件中重新计算光标位置
- 富文本场景下,需针对不同元素(如标题、列表)调整光标高度
- 可通过修改CSS变量自定义光标的颜色、宽度和偏移量
内容的提问来源于stack exchange,提问作者Lorenz
相关产品推荐
相关产品推荐

