Skip to content

Commit

Permalink
feat: html occlude, onOcclude, as (#422)
Browse files Browse the repository at this point in the history
* feat: html occlue, onOcclude, as

* go back to el.display

* fix style propagation
  • Loading branch information
drcmda authored May 29, 2021
1 parent 46b56bd commit 2a55474
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 30 deletions.
6 changes: 3 additions & 3 deletions .storybook/stories/HTML.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,13 @@ function HTMLRaycastOccluderScene() {
<group ref={turntableRef}>
<Icosahedron ref={occluderRef} name="pink" args={[5, 5]} position={[0, 0, 0]}>
<meshBasicMaterial attach="material" color="hotpink" wireframe />
<Html position={[0, 0, -6]} className="html-story-label" occluder={turntableRef}>
<Html position={[0, 0, -6]} className="html-story-label" occlude={[turntableRef]}>
A
</Html>
</Icosahedron>
<Icosahedron name="yellow" args={[5, 5]} position={[16, 0, 0]}>
<meshBasicMaterial attach="material" color="yellow" wireframe />
<Html position={[0, 0, -6]} className="html-story-label" occluder={turntableRef}>
<Html position={[0, 0, -6]} className="html-story-label" occlude={[turntableRef]}>
B
</Html>
</Icosahedron>
Expand All @@ -152,5 +152,5 @@ function HTMLRaycastOccluderScene() {
)
}

export const HTMLRaycastOccluderSt = () => <HTMLRaycastOccluderScene className="html-story-block" />
export const HTMLRaycastOccluderSt = () => <HTMLRaycastOccluderScene />
HTMLRaycastOccluderSt.storyName = 'Raycast occluder'
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,7 @@ Allows you to tie HTML content to any object of your scene. It will be projected
```jsx
<Html
as='div' // Wrapping element (default: 'div')
prepend // Project content behind the canvas (default: false)
center // Adds a -50%/-50% css transform (default: false) [ignored in transform mode]
fullscreen // Aligns to the upper-left corner, fills the screen (default:false) [ignored in transform mode]
Expand All @@ -657,7 +658,8 @@ Allows you to tie HTML content to any object of your scene. It will be projected
transform // If true, applies matrix3d transformations (default=false)
sprite // Renders as sprite, but only in transform mode (default=false)
calculatePosition={(el: Object3D, camera: Camera, size: { width: number; height: number }) => number[]} // Override default positioning function. (default=undefined) [ignored in transform mode]
checkDepth // Checks visibility with raycasting (default=false)
occlude={[ref]} // Can be true or a Ref<Object3D>[], true occludes the entire scene (default: undefined)
onOcclude={(visible) => null} // Callback when the visibility changes (default: undefined)
{...groupProps} // All THREE.Group props are valid
{...divProps} // All HTMLDivElement props are valid
>
Expand All @@ -666,6 +668,30 @@ Allows you to tie HTML content to any object of your scene. It will be projected
</Html>
```
Html can hide behind geometry using the `occlude` prop.
```jsx
// Raytrace the entire scene
<Html occlude />
// Raytrace only specific elements
<Html occlude={[ref1, ref2]} />
```
When the Html object hides it sets the opacity prop on the innermost div. If you want to animate or control the transition yourself then you can use `onOcclude`.
```jsx
const [hidden, set] = useState()

<Html
occlude
onOcclude={set}
style={{
transition: 'all 0.5s',
opacity: hidden ? 0 : 1,
transform: `scale(${hidden ? 0.5 : 1})`
}} />
```
#### Shadow
[![](https://img.shields.io/badge/-storybook-%23ff69b4)](https://drei.vercel.app/?path=/story/misc-shadow--shadow-st)
Expand Down
62 changes: 36 additions & 26 deletions src/web/Html.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,15 @@ function isObjectBehindCamera(el: Object3D, camera: Camera) {
return deltaCamObj.angleTo(camDir) > Math.PI / 2
}

function isObjectVisible(el: Object3D, camera: Camera, raycaster: Raycaster, occluder: Object3D) {
function isObjectVisible(el: Object3D, camera: Camera, raycaster: Raycaster, occlude: Object3D[]) {
const elPos = v1.setFromMatrixPosition(el.matrixWorld)

const screenPos = elPos.clone()
screenPos.project(camera)

raycaster.setFromCamera(screenPos, camera)
const intersects = raycaster.intersectObject(occluder, true)

const intersects = raycaster.intersectObjects(occlude, true)
if (intersects.length) {
const intersectionDistance = intersects[0].distance
const pointDistance = elPos.distanceTo(camera.position)

return pointDistance < intersectionDistance
} else {
return true
Expand Down Expand Up @@ -100,10 +96,11 @@ export interface HtmlProps
distanceFactor?: number
sprite?: boolean
transform?: boolean
checkDepth?: boolean
zIndexRange?: Array<number>
occluder?: React.RefObject<Object3D>
occlude?: React.RefObject<Object3D>[] | boolean
onOcclude?: (visible: boolean) => null
calculatePosition?: CalculatePosition
as?: string
}

export const Html = React.forwardRef(
Expand All @@ -120,9 +117,11 @@ export const Html = React.forwardRef(
distanceFactor,
sprite = false,
transform = false,
occluder,
occlude,
onOcclude,
zIndexRange = [16777271, 0],
calculatePosition = defaultCalculatePosition,
as = 'div',
...props
}: HtmlProps,
ref: React.Ref<HTMLDivElement>
Expand All @@ -133,12 +132,12 @@ export const Html = React.forwardRef(
const size = useThree(({ size }) => size)
const raycaster = useThree(({ raycaster }) => raycaster)

const [el] = React.useState(() => document.createElement('div'))
const group = React.useRef<Group>(null)
const [el] = React.useState(() => document.createElement(as))
const group = React.useRef<Group>(null!)
const oldZoom = React.useRef(0)
const oldPosition = React.useRef([0, 0])
const transformOuterRef = React.useRef<HTMLDivElement>(null)
const transformInnerRef = React.useRef<HTMLDivElement>(null)
const transformOuterRef = React.useRef<HTMLDivElement>(null!)
const transformInnerRef = React.useRef<HTMLDivElement>(null!)
const target = portal?.current ?? gl.domElement.parentNode

React.useEffect(() => {
Expand Down Expand Up @@ -189,16 +188,16 @@ export const Html = React.forwardRef(
}, [style, center, fullscreen, size, transform])

const transformInnerStyles: React.CSSProperties = React.useMemo(
() => ({ position: 'absolute', pointerEvents: 'auto', ...style }),
[style]
() => ({ position: 'absolute', pointerEvents: 'auto' }),
[]
)

React.useLayoutEffect(() => {
if (transform) {
ReactDOM.render(
<div ref={transformOuterRef} style={styles}>
<div ref={transformInnerRef} style={transformInnerStyles}>
<div ref={ref} className={className} children={children} />
<div ref={ref} className={className} style={style} children={children} />
</div>
</div>,
el
Expand All @@ -208,6 +207,8 @@ export const Html = React.forwardRef(
}
})

const visible = React.useRef(true)

useFrame(() => {
if (group.current) {
camera.updateMatrixWorld()
Expand All @@ -219,13 +220,27 @@ export const Html = React.forwardRef(
Math.abs(oldPosition.current[0] - vec[0]) > eps ||
Math.abs(oldPosition.current[1] - vec[1]) > eps
) {
const isBehindCamera = isObjectBehindCamera(group.current, camera)
let isBehindCamera = isObjectBehindCamera(group.current, camera)
let raytraceTarget: null | undefined | boolean | Object3D[] = false
if (typeof occlude === 'boolean') {
if (occlude === true) {
raytraceTarget = [scene]
}
} else if (Array.isArray(occlude)) {
raytraceTarget = occlude.map((item) => item.current) as Object3D[]
}

if (occluder?.current) {
const visible = isObjectVisible(group.current, camera, raycaster, occluder?.current)
el.style.display = visible && !isBehindCamera ? 'block' : 'none'
const previouslyVisible = visible.current
if (raytraceTarget) {
const isvisible = isObjectVisible(group.current, camera, raycaster, raytraceTarget)
visible.current = isvisible && !isBehindCamera
} else {
el.style.display = !isBehindCamera ? 'block' : 'none'
visible.current = !isBehindCamera
}

if (previouslyVisible !== visible.current) {
if (onOcclude) onOcclude(!visible.current)
else el.style.display = visible.current ? 'block' : 'none'
}

el.style.zIndex = `${objectZIndex(group.current, camera, zIndexRange)}`
Expand Down Expand Up @@ -263,8 +278,3 @@ export const Html = React.forwardRef(
return <group {...props} ref={group} />
}
)

export const HTML = React.forwardRef((props: HtmlProps, ref: React.Ref<HTMLDivElement>) => {
React.useEffect(() => void console.warn('The <HTML> component was renamed to <Html>'), [])
return <Html {...props} ref={ref} />
})

1 comment on commit 2a55474

@vercel
Copy link

@vercel vercel bot commented on 2a55474 May 29, 2021

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.