Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: View component supports non-full screen use cases #981

Merged
merged 3 commits into from Nov 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 7 additions & 1 deletion README.md
Expand Up @@ -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
<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