diff --git a/README.md b/README.md
index 3d7dd2e18..bc3d4c93d 100644
--- a/README.md
+++ b/README.md
@@ -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
```
-
-The Bounds component also acts as a context provider, use the `useBounds` hook to refresh the bounds, fit the camera, clip near/far planes or focus objects. `refresh(object?: THREE.Object3D | THREE.Box3)` will recalculate bounds. Since this can be expensive only call it when you know the view has changed. `clip` sets the cameras near/far planes. `fit` zooms and centers the view.
+The Bounds component also acts as a context provider, use the `useBounds` hook to refresh the bounds, fit the camera, clip near/far planes, go to camera orientations or focus objects. `refresh(object?: THREE.Object3D | THREE.Box3)` will recalculate bounds, since this can be expensive only call it when you know the view has changed. `clip` sets the cameras near/far planes. `to` sets a position and target for the camera. `fit` zooms and centers the view.
```jsx
function Foo() {
@@ -2567,10 +2572,13 @@ function Foo() {
useEffect(() => {
// Calculate scene bounds
bounds.refresh().clip().fit()
+
// Or, focus a specific object or box3
// bounds.refresh(ref.current).clip().fit()
// bounds.refresh(new THREE.Box3()).clip().fit()
+ // Or, send the camera to a specific orientatin
+ // bounds.to({position: [0, 10, 10], target: {[5, 5, 0]}})
```
diff --git a/src/core/Bounds.tsx b/src/core/Bounds.tsx
index 9dd6856e5..edcc9fc86 100644
--- a/src/core/Bounds.tsx
+++ b/src/core/Bounds.tsx
@@ -94,7 +94,7 @@ export function Bounds({ children, damping = 6, fit, clip, observe, margin = 1.2
}
if (controls?.constructor.name === 'OrthographicTrackballControls') {
- // Put camera on a sphere along which it should moves
+ // Put camera on a sphere along which it should move
const { distance } = getSize()
const direction = camera.position.clone().sub(controls.target).normalize().multiplyScalar(distance)
const newPos = controls.target.clone().add(direction)
@@ -113,6 +113,25 @@ export function Bounds({ children, damping = 6, fit, clip, observe, margin = 1.2
invalidate()
return this
},
+ to({ position, target }: { position: [number, number, number]; target?: [number, number, number] }) {
+ current.camera.copy(camera.position)
+ const { center } = getSize()
+ goal.camera.set(...position)
+
+ if (target) {
+ goal.focus.set(...target)
+ } else {
+ goal.focus.copy(center)
+ }
+
+ if (damping) {
+ current.animating = true
+ } else {
+ camera.position.set(...position)
+ }
+
+ return this
+ },
fit() {
current.camera.copy(camera.position)
if (controls) current.focus.copy(controls.target)
diff --git a/src/web/View.tsx b/src/web/View.tsx
index eb40c27b3..7cdc68738 100644
--- a/src/web/View.tsx
+++ b/src/web/View.tsx
@@ -6,6 +6,26 @@ 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): 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}>
}
diff --git a/yarn.lock b/yarn.lock
index 0c2094c36..2b29d1cff 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5043,9 +5043,9 @@ deep-is@^0.1.3, deep-is@~0.1.3:
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
deep-object-diff@^1.1.0:
- version "1.1.7"
- resolved "https://registry.yarnpkg.com/deep-object-diff/-/deep-object-diff-1.1.7.tgz#348b3246f426427dd633eaa50e1ed1fc2eafc7e4"
- integrity sha512-QkgBca0mL08P6HiOjoqvmm6xOAl2W6CT2+34Ljhg0OeFan8cwlcdq8jrLKsBBuUFAZLsN5b6y491KdKEoSo9lg==
+ version "1.1.9"
+ resolved "https://registry.yarnpkg.com/deep-object-diff/-/deep-object-diff-1.1.9.tgz#6df7ef035ad6a0caa44479c536ed7b02570f4595"
+ integrity sha512-Rn+RuwkmkDwCi2/oXOFS9Gsr5lJZu/yTGpK7wAaAIE75CC+LCGEZHpY6VQJa/RoJcrmaA/docWJZvYohlNkWPA==
deepmerge@^4.2.2:
version "4.2.2"
@@ -8199,9 +8199,9 @@ loader-runner@^2.4.0:
integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==
loader-utils@^1.1.0, loader-utils@^1.2.3:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613"
- integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==
+ version "1.4.2"
+ resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.2.tgz#29a957f3a63973883eb684f10ffd3d151fec01a3"
+ integrity sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==
dependencies:
big.js "^5.2.2"
emojis-list "^3.0.0"
@@ -8634,11 +8634,16 @@ minimist-options@4.1.0:
is-plain-obj "^1.1.0"
kind-of "^6.0.3"
-minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6:
+minimist@^1.1.1, minimist@^1.2.5, minimist@^1.2.6:
version "1.2.6"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
+minimist@^1.2.0:
+ version "1.2.7"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18"
+ integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==
+
minipass-collect@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617"
@@ -11291,9 +11296,9 @@ terser-webpack-plugin@^4.2.3:
webpack-sources "^1.4.3"
terser@^4.1.2, terser@^4.6.3:
- version "4.8.0"
- resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17"
- integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==
+ version "4.8.1"
+ resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.1.tgz#a00e5634562de2239fd404c649051bf6fc21144f"
+ integrity sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==
dependencies:
commander "^2.20.0"
source-map "~0.6.1"