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

Using drei HTML is not showing anything #21

Closed
robin22d opened this issue Dec 12, 2020 · 5 comments
Closed

Using drei HTML is not showing anything #21

robin22d opened this issue Dec 12, 2020 · 5 comments

Comments

@robin22d
Copy link

I have tried to add html when using react-xr. I am using HTML from drei but cannot get anything to show up.

import React from "react";
import { VRCanvas, DefaultXRControllers } from "@react-three/xr";
import { Html } from "drei";

function App() {
  const style = {
    heigh: "100px",
    width: "100px",
    backgroundColor: "red",
  };
  return (
    <div className="App">
      <VRCanvas>
        <ambientLight intensity={0.5} />
        <pointLight position={[5, 5, 5]} />
        <mesh position={[1, 1, 1]}>
          <Html
            prepend
            center
            fullscreen
            scaleFactor={10}
            zIndexRange={[100, 0]}
          >
            <h1 style={style}>hello</h1>
            <p>world</p>
          </Html>
        </mesh>
        <DefaultXRControllers />
      </VRCanvas>
    </div>
  );
}

export default App;
@sniok
Copy link
Member

sniok commented Dec 14, 2020

Hi, drei Html component will not work in VR/AR. It works by adding DOM node over canvas and not in a threejs scene so when you're in a VR session you only see what is being rendered in 3D scene.

If you want to render text in VR I recommend using drei's Text component that works in VR/AR

@robin22d
Copy link
Author

Hi thanks for such a quick reply. Do you know how would I embed an image into the scene?

@sniok
Copy link
Member

sniok commented Dec 15, 2020

Yeah for that you can place a Plane with an image texture

@enijar
Copy link

enijar commented Nov 30, 2021

You can also render textures dynamically by rendering HTML to a canvas using a library like html2canvas, then using that canvas as a texture on a plane.

Here's an example of how to do that:

import React from "react";
import html2canvas from "html2canvas";
import { renderToString } from "react-dom/server";
import * as THREE from "three";
import { useTexture } from "@react-three/drei";
import { useThree } from "@react-three/fiber";

// Prevents html2canvas warnings
// @todo maybe remove this if it causes performance issues?
HTMLCanvasElement.prototype.getContext = (function (origFn) {
  return function (type, attribs) {
    attribs = attribs || {};
    attribs.preserveDrawingBuffer = true;
    return origFn.call(this, type, attribs);
  };
})(HTMLCanvasElement.prototype.getContext);

let container = document.querySelector("#htmlContainer");
if (!container) {
  const node = document.createElement("div");
  node.setAttribute("id", "htmlContainer");
  node.style.position = "fixed";
  node.style.opacity = "0";
  node.style.pointerEvents = "none";
  document.body.appendChild(node);
  container = node;
}

export default function Html({
  children,
  width,
  height,
  color = "transparent",
}) {
  const { camera, size: viewSize, gl } = useThree();

  const sceneSize = React.useMemo(() => {
    const cam = camera as THREE.PerspectiveCamera;
    const fov = (cam.fov * Math.PI) / 180; // convert vertical fov to radians
    const height = 2 * Math.tan(fov / 2) * 5; // visible height
    const width = height * (viewSize.width / viewSize.height);
    return { width, height };
  }, [camera, viewSize]);

  const lastUrl = React.useRef(null);

  const [image, setImage] = React.useState(
    "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="
  );
  const [textureSize, setTextureSize] = React.useState({ width, height });

  const node = React.useMemo(() => {
    const node = document.createElement("div");
    node.innerHTML = renderToString(children);
    return node;
  }, [children]);

  React.useEffect(() => {
    container.appendChild(node);
    html2canvas(node, { backgroundColor: color }).then((canvas) => {
      setTextureSize({ width: canvas.width, height: canvas.height });
      if (container.contains(node)) {
        container.removeChild(node);
      }
      canvas.toBlob((blob) => {
        if (blob === null) return;
        if (lastUrl.current !== null) {
          URL.revokeObjectURL(lastUrl.current);
        }
        const url = URL.createObjectURL(blob);
        lastUrl.current = url;
        setImage(url);
      });
    });
    return () => {
      if (!container) return;
      if (container.contains(node)) {
        container.removeChild(node);
      }
    };
  }, [node, viewSize, sceneSize, color]);

  const texture = useTexture(image);

  const size = React.useMemo(() => {
    const imageAspectW = texture.image.height / texture.image.width;
    const imageAspectH = texture.image.width / texture.image.height;

    const cam = camera as THREE.PerspectiveCamera;
    const fov = (cam.fov * Math.PI) / 180; // convert vertical fov to radians

    let h = 2 * Math.tan(fov / 2) * 5; // visible height
    let w = h * imageAspectH;

    if (width !== undefined) {
      w = width;
    }
    if (height !== undefined) {
      h = height;
    }

    if (height === undefined) {
      h = width * imageAspectW;
    }
    if (width === undefined) {
      w = h * imageAspectH;
    }
    return {
      width: w,
      height: h,
    };
  }, [texture, width, height, camera]);

  React.useMemo(() => {
    texture.matrixAutoUpdate = false;
    const aspect = size.width / size.height;
    const imageAspect = texture.image.width / texture.image.height;
    texture.anisotropy = gl.capabilities.getMaxAnisotropy();
    texture.minFilter = THREE.LinearFilter;
    if (aspect < imageAspect) {
      texture.matrix.setUvTransform(0, 0, 1, imageAspect / aspect, 0, 0.5, 0.5);
    } else {
      texture.matrix.setUvTransform(0, 0, aspect / imageAspect, 1, 0, 0.5, 0.5);
    }
  }, [texture, size, textureSize]);

  return (
    <mesh>
      <planeBufferGeometry args={[size.width, size.height]} />
      <meshBasicMaterial map={texture} side={THREE.DoubleSide} transparent />
    </mesh>
  );
}

Then you can use it like the Drei Html component:

function App() {
  return (
    <Html width={2} height={2}>
      <div
        style={{
          fontFamily: "Arial, sans-serif",
          fontSize: "100px",
          margin: "1px",
        }}
      >
        <ol style={{ marginLeft: "1em" }}>
          <li>
            <strong>bold</strong>
          </li>
          <li>
            <em>italics</em>
          </li>
          <li>
            <span style={{ color: "red" }}>red</span>
          </li>
          <li>
            <span style={{ color: "green" }}>green</span>
          </li>
          <li>
            <span style={{ color: "blue" }}>blue</span>
          </li>
        </ol>

        <img
          src="/assets/test.png"
          alt=""
          crossOrigin="anonymous"
          style={{ border: "0.1em solid red" }}
        />
      </div>
    </Html>
  );
}

It should look something like this:
Example

@FradSer
Copy link

FradSer commented Oct 22, 2022

Thanks @enijar .

What if someone is using Next.js with r3f, here is a practice (FradSer/frad-me@3c17443) that hopefully helps.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants