React Portal无法在指定位置生成元素,如何让子元素不被模糊?
问题:实现模糊背景+非模糊子元素的Overlay组件
我开发了一个可模糊屏幕全部内容的组件,但目前传入的子元素也被模糊了——因为Portal把子元素挂载到了#blur-overlay内部,而这个元素已经被应用了模糊样式。
我的需求是:模糊所有内容的同时,让传入BlurOverlay的子元素显示在模糊层上方(类似模态框与背景的关系)。
当前组件代码:
const SVGBlur = () => ( <svg style={{position: 'absolute', top: '-99999px'}} xmlns="http://www.w3.org/2000/svg"> <filter id="svgBlur" x="-5%" y="-5%" width="150%" height="150%"> <feGaussianBlur in="SourceGraphic" stdDeviation={5}/> </filter> </svg> ) const BlurOverlay = ({children}) => { const [domNode, setDomNode] = useState(null); useEffect(() => { const blurContainer = document.createElement('div') const childrenContainer = document.getElementById('root') const elementToApplyBlurTo = document.getElementById('blur-overlay') if (elementToApplyBlurTo && childrenContainer) { childrenContainer.appendChild(blurContainer) elementToApplyBlurTo.setAttribute('style', 'filter: url(#svgBlur); filter: blur(5px);') setDomNode(childrenContainer) } return () => { if (elementToApplyBlurTo && childrenContainer) { childrenContainer.removeChild(blurContainer) elementToApplyBlurTo.setAttribute('style', '') } } }, []) return domNode && ReactDOM.createPortal( <> {children} <SVGBlur /> </>, domNode, ) } export default BlurOverlay;
当前DOM结构(Portal创建后):
<div id='root'> <div className="App" id={'blur-overlay'}> <- 已应用模糊样式 ... <div>此处包含传入BlurOverlay的子元素</div> <svg .../> </div> </div>
期望的DOM结构:
<div id='root'> <div>此处包含传入BlurOverlay的子元素</div> <div className="App" id={'blur-overlay'}> <- 已应用模糊样式 ... <svg .../> </div> </div>
解决方案
核心问题是Portal挂载位置错误,导致子元素被包含在模糊容器内。我们需要调整Portal容器的插入位置,确保子元素脱离模糊容器的层级范围。
修改后的组件代码:
import { useState, useEffect } from 'react'; import ReactDOM from 'react-dom'; const SVGBlur = () => ( <svg style={{position: 'absolute', top: '-99999px'}} xmlns="http://www.w3.org/2000/svg"> <filter id="svgBlur" x="-5%" y="-5%" width="150%" height="150%"> <feGaussianBlur in="SourceGraphic" stdDeviation={5}/> </filter> </svg> ) const BlurOverlay = ({children}) => { const [portalContainer, setPortalContainer] = useState(null); useEffect(() => { const root = document.getElementById('root'); const blurTarget = document.getElementById('blur-overlay'); if (root && blurTarget) { // 创建Portal专属容器 const container = document.createElement('div'); // 将容器插入到模糊目标元素的前方,避免被模糊样式影响 root.insertBefore(container, blurTarget); // 为目标元素添加模糊样式 blurTarget.style.cssText = 'filter: url(#svgBlur); filter: blur(5px);'; setPortalContainer(container); // 组件卸载时清理 return () => { blurTarget.style.cssText = ''; root.removeChild(container); } } }, []); return portalContainer && ReactDOM.createPortal( <> {children} <SVGBlur /> </>, portalContainer ); } export default BlurOverlay;
关键改动说明:
- 调整容器插入位置:使用
root.insertBefore(container, blurTarget)替代appendChild,让子元素容器直接放在#blur-overlay的同级前方,彻底脱离模糊容器的层级范围。 - 简化DOM操作逻辑:去掉多余的
blurContainer变量,直接创建Portal专用容器,逻辑更简洁。 - 优化样式控制:用
style.cssText替代setAttribute('style'),更精准地添加和清除样式,避免样式冲突。
修改后,子元素会作为#root的直接子元素存在于模糊层之前,不会继承模糊样式,同时背景内容保持模糊效果,完全符合模态框+模糊背景的需求。
内容的提问来源于stack exchange,提问作者four-eyes
相关产品推荐
相关产品推荐

