diff --git a/README.md b/README.md index dcd8911e9..84ee21cb4 100644 --- a/README.md +++ b/README.md @@ -1844,7 +1844,13 @@ useBVH(mesh) Views use gl.scissor to cut the viewport into segments. You tie a view to a tracking div which then controls the position and bounds of the viewport. This allows you to have multiple views with a single, performant canvas. These views will follow their tracking elements, scroll along, resize, etc. -It is advisable to re-connect the event system to a parent that contains both the canvas and the html content. This ensures that both are accessible/selectable and even allows you to mount controls or other deeper integrations into your view. +It is advisable to re-connect the event system to a parent that contains both the canvas and the html content. +This ensures that both are accessible/selectable and even allows you to mount controls or other deeper +integrations into your view. + +> Note that `@react-three/fiber` newer than `^8.1.0` is required for `View` to work correctly if the +> canvas/react three fiber root is not fullscreen. A warning will be logged if drei is used with older +> versions of `@react-three/fiber`. ```tsx def && (def as THREE.OrthographicCamera).isOrthographicCamera const col = new THREE.Color() +/** + * In `@react-three/fiber` after `v8.0.0` but prior to `v8.1.0`, `state.size` contained only dimension + * information. After `v8.1.0`, position information (`top`, `left`) was added + * + * @todo remove this when drei supports v9 and up + */ +type LegacyCanvasSize = { + height: number + width: number +} + +type CanvasSize = LegacyCanvasSize & { + top: number + left: number +} + +function isNonLegacyCanvasSize(size: Record): size is CanvasSize { + return 'top' in size +} + export type ContainerProps = { scene: THREE.Scene index: number @@ -13,7 +33,7 @@ export type ContainerProps = { frames: number rect: React.MutableRefObject track: React.MutableRefObject - canvasSize: Size + canvasSize: LegacyCanvasSize | CanvasSize } export type ViewProps = { @@ -27,6 +47,30 @@ export type ViewProps = { children?: React.ReactNode } +function computeContainerPosition( + canvasSize: LegacyCanvasSize | CanvasSize, + trackRect: DOMRect +): { + position: CanvasSize & { bottom: number, right: number } + isOffscreen: boolean +} { + const { right, top, left: trackLeft, bottom: trackBottom, width, height } = trackRect + const isOffscreen = trackRect.bottom < 0 || top > canvasSize.height || right < 0 || trackRect.left > canvasSize.width + + if (isNonLegacyCanvasSize(canvasSize)) { + const canvasBottom = canvasSize.top + canvasSize.height + const bottom = canvasBottom - trackBottom + const left = trackLeft - canvasSize.left + + return { position: { width, height, left, top, bottom, right }, isOffscreen } + } + + // Fall back on old behavior if r3f < 8.1.0 + const bottom = canvasSize.height - trackBottom + + return { position: { width, height, top, left: trackLeft, bottom, right }, isOffscreen } +} + function Container({ canvasSize, scene, index, children, frames, rect, track }: ContainerProps) { const get = useThree((state) => state.get) const camera = useThree((state) => state.camera) @@ -41,9 +85,11 @@ function Container({ canvasSize, scene, index, children, frames, rect, track }: } if (rect.current) { - const { left, right, top, bottom, width, height } = rect.current - const isOffscreen = bottom < 0 || top > canvasSize.height || right < 0 || left > canvasSize.width - const positiveYUpBottom = canvasSize.height - bottom + const { + position: { left, bottom, width, height }, + isOffscreen, + } = computeContainerPosition(canvasSize, rect.current) + const aspect = width / height if (isOrthographicCamera(camera)) { @@ -61,8 +107,8 @@ function Container({ canvasSize, scene, index, children, frames, rect, track }: camera.updateProjectionMatrix() } - state.gl.setViewport(left, positiveYUpBottom, width, height) - state.gl.setScissor(left, positiveYUpBottom, width, height) + state.gl.setViewport(left, bottom, width, height) + state.gl.setScissor(left, bottom, width, height) state.gl.setScissorTest(true) if (isOffscreen) { @@ -84,6 +130,16 @@ function Container({ canvasSize, scene, index, children, frames, rect, track }: return () => setEvents({ connected: old }) }, []) + React.useEffect(() => { + if (isNonLegacyCanvasSize(canvasSize)) { + return + } + console.warn( + 'Detected @react-three/fiber canvas size does not include position information. may not work as expected. ' + + 'Upgrade to @react-three/fiber ^8.1.0 for support.\n See https://github.com/pmndrs/drei/issues/944' + ) + }, []) + return <>{children} }