Three.js Canvas底层HTML可视实现及滚动异常问题咨询
Hey there! Let's fix those two issues you're facing with your Three.js lens effect, and get it matching your Figma prototype perfectly.
1. Fixing the Irregular Movement of the White Text Area on Scroll
Root Cause
The hardcoded lens position and size in your ScrollingText component didn't account for the lens mesh's rotation (rotation-x={Math.PI/2}) and scaling. This meant the text's "inside lens" detection used incorrect coordinates, leading to misalignment when scrolling or animating.
Solution
Instead of guessing the lens bounds, we'll calculate the actual world-space boundary of the lens mesh, then use that for accurate collision detection with the text.
Key Code Changes:
- Update the
Lenscomponent to compute the lens's bounding box and pass it toScrollingText:// Inside Lens component's useFrame hook if (ref.current) { const box = new THREE.Box3().setFromObject(ref.current); setLensBounds(box); } - Modify
ScrollingTextto use the passed bounds for detection:// Inside ScrollingText's useFrame hook const textBox = new THREE.Box3().setFromObject(ref.current); const inside = lensBounds.intersectsBox(textBox);
This ensures the text's color change only triggers when it's actually within the lens's visible area, no matter how the lens is rotated or scaled.
2. Making the Lens Transparent to See HTML Content Below the Canvas
Root Cause
Three.js can't natively render or "see" DOM elements behind its canvas. To achieve this effect, we need to capture the DOM content as a texture and use it in the lens material, while keeping the canvas itself fully transparent.
Solution
- Capture the DOM content behind the canvas using
html2canvas(install it first withnpm install html2canvas). - Pass the captured texture to the
MeshTransmissionMaterialto simulate the lens refracting the underlying content. - Set canvas pointer events to none so the underlying DOM can receive user interactions.
Key Code Changes:
- Add a ref to the DOM element you want to see through the lens in your
Appcomponent:const contentRef = useRef(null); // ... <div ref={contentRef} style={{ backgroundColor: '#cbdbd5', height: '200px' }}> <p>此处Canvas下方可见背景</p> </div> - Update the
Canvascomponent to allow interactions with underlying DOM:<Canvas // ... existing props style={{ width: '100%', height: '100px', display: 'block', pointerEvents: 'none' }} /> - In the
Lenscomponent, capture the DOM and pass it to the material (add throttle for performance):import html2canvas from 'html2canvas'; import throttle from 'lodash.throttle'; // Optional, for smoother performance function Lens({ children, contentRef, ...props }) { const [domTexture, setDomTexture] = useState(null); const captureDom = useCallback(throttle(async () => { if (!contentRef.current) return; const canvas = await html2canvas(contentRef.current); const texture = new THREE.CanvasTexture(canvas); texture.needsUpdate = true; setDomTexture(texture); }, 100), [contentRef]); useEffect(() => { captureDom(); window.addEventListener('scroll', captureDom); return () => window.removeEventListener('scroll', captureDom); }, [captureDom]); // ... rest of the component <MeshTransmissionMaterial buffer={buffer.texture} backside={domTexture} // Use the captured DOM texture for refraction ior={1.2} thickness={1.5} anisotropy={0.1} chromaticAberration={0.04} color={'#ffffff'} transparent={true} roughness={0} /> }
Final Notes
- Performance: Throttling the DOM capture prevents excessive re-renders during scrolling. Adjust the throttle delay (100ms in the example) based on your needs.
- html2canvas Compatibility: Some CSS properties (like complex gradients or transforms) might not capture perfectly. You can tweak html2canvas options (e.g.,
useCORS: true) if you run into issues. - Interaction: If you need the lens to respond to clicks, set
pointerEvents: 'auto'on the lens mesh to override the canvas'snonesetting.
内容的提问来源于stack exchange,提问作者Roma N

