为何contenteditable元素的blur事件会触发两次?与flex布局相关
Flex布局下contenteditable元素blur事件重复触发的原因解析
问题现象
- 当父元素设置
display: flex时,点击子contenteditable元素的文本区域后再点击父元素绿色区域,子元素的blur事件会触发两次 - 点击子元素非文本区域或移除父元素的flex布局后,该现象消失
复现代码
import { useEffect, useRef, useState } from 'react'; export default function App() { const parentElementRef = useRef(); const childElementRef = useRef(); const [focusedElement, setFocusedElement] = useState(); useEffect(() => { const clickEventListener = (event) => { event.stopPropagation(); setFocusedElement(event.currentTarget); console.log('click'); console.log(event.target === childElementRef.current); console.log(event.currentTarget === childElementRef.current); }; const blurEventListener = (event) => { event.stopPropagation(); console.log('blur'); console.log(event.target === childElementRef.current); console.log(event.currentTarget === childElementRef.current); }; parentElementRef.current.addEventListener('click', clickEventListener); parentElementRef.current.addEventListener('blur', blurEventListener); childElementRef.current.addEventListener('click', clickEventListener); childElementRef.current.addEventListener('blur', blurEventListener); return () => { childElementRef.current.removeEventListener('click', clickEventListener); childElementRef.current.removeEventListener('blur', blurEventListener); parentElementRef.current.removeEventListener('click', clickEventListener); parentElementRef.current.removeEventListener('blur', blurEventListener); }; }, []); useEffect(() => { focusedElement?.focus(); }, [focusedElement]); return ( <div ref={parentElementRef} style={{ display: 'flex', flexDirection: 'column', width: '100vw', height: '100vh', backgroundColor: 'green', }} > <div ref={childElementRef} contentEditable={focusedElement === childElementRef.current} suppressContentEditableWarning style={{ width: '200px', height: '200px', backgroundColor: 'red', }} > Hello world! </div> </div> ); }
控制台输出对比
异常场景(点击文本区域后点父元素)
click true true blur true true click false false blur true true
正常场景(点击非文本区域后点父元素)
click true true blur true true click false false
期望输出
click true true blur true true click false false
原因分析
1. Flex布局对contenteditable焦点的影响
当父元素使用flex布局时,浏览器会为contenteditable元素的文本内容创建独立的匿名inline焦点容器:
- 点击文本区域时,焦点实际落在这个匿名容器上,而非外层的div元素
- 点击父元素时,首先是匿名容器失去焦点,事件冒泡到外层div触发第一次
blur;随后外层div本身失去焦点,触发第二次blur
2. 非文本区域的焦点行为
点击子元素非文本区域时,焦点直接绑定在外层div上,不存在匿名焦点容器的中间层,因此点击父元素时只会触发一次div的blur事件。
3. 非Flex布局的渲染差异
非flex布局下,浏览器不会为contenteditable的文本内容拆分出独立的焦点容器,焦点始终直接关联外层div,因此无论点击文本还是空白区域,都只会触发一次blur。
4. React状态更新的叠加影响
代码中focusedElement的状态更新会触发useEffect调用focus(),在异常场景中,第一次blur后焦点的短暂丢失会被focus()尝试恢复,但此时父元素的点击事件已经触发,进一步导致第二次blur的产生。
内容的提问来源于stack exchange,提问作者Adrian
相关产品推荐
相关产品推荐

