使用ScrollControls时Html组件无法固定位置的解决咨询
解决React Three Fiber中Html组件在ScrollControls下无法固定位置的问题
核心原因
你把Html组件放在Mesh内部,ScrollControls的滚动会驱动整个3D场景的变换(比如相机位移、场景偏移),再加上transformGroup属性让Html继承了Mesh的3D变换——这会干扰CSS position: fixed的生效逻辑:fixed定位本应相对于浏览器视口,但3D变换会创建新的包含块,导致Html跟着场景滚动。而Decal是直接依附在Mesh表面的3D组件,自然能正常跟随Mesh动作。
解决方案
方案1:将Html组件移出ScrollControls层级
这是最直接的解决方法,把Html放在ScrollControls外部,脱离3D场景的滚动变换影响:
// 放在ScrollControls外层,独立于3D场景滚动逻辑 <Html portal={{ current: gl.domElement.parentNode }} scale={100} > <div className="about-wall about-wall-text" style={{ width: '40rem', position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', willChange: 'transform, opacity', pointerEvents: 'none', }} > <h2>About Us</h2> </div> </Html> <ScrollControls hideScrollbar={true} pages={35} distance={1} damping={0.5}> <Newmodel isMobile={isMobile} /> </ScrollControls>
方案2:保留在Mesh内但取消3D变换继承
如果必须让Html和Mesh关联展示,需要去掉transformGroup,并通过useFrame手动控制Html位置,确保它始终对齐视口固定点:
import { useFrame, useThree } from '@react-three/fiber' import { Html } from '@react-three/drei' import { useState, useRef } from 'react' import { Vector3 } from 'three' function FixedHtmlOnMesh() { const { camera, gl } = useThree() const htmlRef = useRef(null) useFrame(() => { if (!htmlRef.current) return // 将视口中心坐标转换为3D空间坐标 const viewportCenter = new Vector3(0, 0, 0.5) viewportCenter.unproject(camera) // 更新Html位置到视口中心对应的3D点 htmlRef.current.position.copy(viewportCenter) // 让Html始终朝向相机,避免视角翻转导致文字反向 htmlRef.current.lookAt(camera.position) }) return ( <Html ref={htmlRef} portal={{ current: gl.domElement.parentNode }} scale={100} // 关闭transformGroup,避免继承Mesh的3D变换 > <div className="about-wall about-wall-text" style={{ width: '40rem', position: 'relative', willChange: 'transform, opacity', pointerEvents: 'none', }} > <h2>About Us</h2> </div> </Html> ) } // 在Mesh组件内引入该固定Html组件 function Newmodel({ isMobile }) { return ( <mesh> {/* ...你的Mesh模型代码... */} <FixedHtmlOnMesh /> </mesh> ) }
方案3:调整CSS定位方式
如果不想改动组件层级,尝试去掉position: fixed,改用position: absolute基于canvas元素定位:
<Html portal={{ current: gl.domElement.parentNode }} scale={100} transformGroup={false} // 关闭3D变换继承 > <div className="about-wall about-wall-text" style={{ width: '40rem', position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', willChange: 'transform, opacity', pointerEvents: 'none', zIndex: 10, // 确保显示在canvas上方 }} > <h2>About Us</h2> </div> </Html>
关键注意点
- 不要在ScrollControls内部的3D元素中嵌套需要固定定位的Html,ScrollControls的滚动逻辑会修改场景/相机变换,必然带动内部Html移动。
transformGroup属性会让Html完全继承父级3D元素的变换,这和CSS固定定位逻辑冲突,需根据实际需求决定是否开启。
内容的提问来源于stack exchange,提问作者Portgas D. Ace
相关产品推荐
相关产品推荐

