Skip to content

Commit

Permalink
fix: View component supports non-full screen use cases (#981)
Browse files Browse the repository at this point in the history
Resolves #944
  • Loading branch information
kmannislands committed Nov 21, 2022
1 parent 857db49 commit 35c3600
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 7 deletions.
8 changes: 7 additions & 1 deletion README.md
Expand Up @@ -2312,7 +2312,13 @@ function Effects() {

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
<View
Expand Down
68 changes: 62 additions & 6 deletions src/web/View.tsx
Expand Up @@ -6,14 +6,34 @@ const isOrthographicCamera = (def: any): def is THREE.OrthographicCamera =>
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<string, number>): size is CanvasSize {
return 'top' in size
}

export type ContainerProps = {
scene: THREE.Scene
index: number
children?: React.ReactNode
frames: number
rect: React.MutableRefObject<DOMRect>
track: React.MutableRefObject<HTMLElement>
canvasSize: Size
canvasSize: LegacyCanvasSize | CanvasSize
}

export type ViewProps = {
Expand All @@ -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)
Expand All @@ -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)) {
Expand All @@ -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) {
Expand All @@ -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. <View /> 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}</>
}

Expand Down

1 comment on commit 35c3600

@vercel
Copy link

@vercel vercel bot commented on 35c3600 Nov 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.