I would like to use the babylon viewer npm package in my react project. However React complains about the recommended <babylon> tag.
Is there a correct practice of using it in react?
My current code looks like this:
// I need this otherwise TSlint complains already that the `Property 'babylon' does not exist on type 'JSX.IntrinsicElements'`
declare global {
namespace JSX {
interface IntrinsicElements {
babylon: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
}
}
}
export const FurnitureThumbnail: React.FC<{ furnituretype: IFurnitureType }> = ({ furnituretype }) => {
useEffect(() => {
BabylonViewer.viewerManager.getViewerPromiseById(furnituretype._id).then(function (viewer) {
// this will resolve only after the viewer with this specific ID is initialized
viewer.onEngineInitObservable.add(function (scene) {
viewer.loadModel({
title: 'Helmet',
subtitle: 'BabylonJS',
thumbnail: 'https://www.babylonjs.com/img/favicon/apple-icon-144x144.png',
url: 'https://www.babylonjs.com/Assets/DamagedHelmet/glTF/DamagedHelmet.gltf',
});
});
});
}, [furnituretype]);
return <babylon id={furnituretype._id}></babylon>;
};
The viewer was not designed to be intergrated within react, as the tag is not a react component (but a pseudo-html element). There is a react integration (GitHub - brianzinn/react-babylonjs: React for Babylon 3D engine) by the amazing @brianzinn, i would recommend using this one instead of the viewer, if using react.
I imagine you could get around that element limitation in react by using a ref to a div and then doing some DOM manipulation like appendChild to your ref node. Another way is with a React portal, but I haven’t tried either of those myself with the viewer.
Another thing worth trying is to create the elements yourself:
Just need to make sure on re-renders that you aren’t creating new DOM elements. I’m really busy today, but if you want to go that route we could probably figure out a way on code sandbox. The playground isn’t setup for React.
Sorry @ccc159 I can try more later, but am really busy with work today. Codepen does work with React - you can use a script in the html or use babel as the js preprocessor. I was using code sandbox and it was clearing the body just loading the viewer. I can try with CRA tonight.
Sorry again @ccc159 - I thought the viewer worked on dynamic HTML, but I don’t think the viewer works with dynamically added DOM (I may be wrong). Here was my test case in CRA:
import React, { useRef, useEffect } from 'react';
import './App.css';
function App() {
const divRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
if (divRef.current) {
divRef.current!.innerHTML = '<babylon model="model.gltf"></babylon>'
}
}, [divRef])
return (
<div className="App">
<header className="App-header">
header here
</header>
<div ref={divRef}></div>
</div>
);
}
console.log(((window as any).BabylonViewer as any)!.viewerManager);
((window as any).BabylonViewer as any)!.viewerManager.onViewerAddedObservable.add(function(viewer: any) {
console.log('viewer', viewer);
})
const observer = new MutationObserver((mutationsList, observer) => {
for(const mutation of mutationsList) {
if (mutation.type === 'childList') {
const addedNodes = mutation.addedNodes as NodeList;
if (addedNodes.length > 0 && addedNodes[0].nodeName.toLowerCase() === 'babylon') {
console.log('babylon node detected', mutation.addedNodes);
observer.disconnect();
}
}
}
});
observer.observe(document.body, {attributes: true, childList:true, subtree:true})
export default App;
This does work, though
useEffect(() => {
if (divRef.current) {
// you may need to clean that up on unmount as well as make sure it's not called again
let viewer = new (window as any).BabylonViewer.DefaultViewer(divRef.current, {
scene: {
debug: true
},
camera: {
behaviors: {
autoRotate: 0
}
},
model: {
url: "https://playground.babylonjs.com/scenes/Rabbit.babylon"
}
});
}
}, [divRef])
I wrote the viewer and its documentation. I am still not sure this is the best course of action for react applications, but it will work for sure. There might be a few issues when destructing the component, since some dom elements might be manipulated, and react will not like it one bit.
I am very happy every time the viewer is used, I still think that it is not 100% ready for react applications
The same reason you need a react renderer to many different applications - react requires react components instead of pure html manipulations.
Yes, thanks Raanan for your work on the viewer - I did know that you wrote it The model code in react-babylonjs was originally inspired and then parts borrowed from the viewer - how models are loaded and attached to a root parent, cleanup, etc… So, the actual model loading code across the different projects has more in common that you might think.
@ccc159 - I’ll try to offer a comparison, since you asked.
For me to compare them would be more on the tighter integration to React offered by a specialized integration, whereas the Viewer is more portable and easier to setup, configure and use.
A viewer basic scene may end up something like this:
The React equivalent code for the default viewer would then be something like this:
There’s another way to load models using hooks and React.Suspense, but that’s the declarative way. Notice that the model position is controlled by props, so you could have DOM buttons in your React application to control the model scaling and position (or any properties, even lights, etc.). Those props will flow directly to your BabylonJS objects from React state. You can also do things outside of the React render directly on BabylonJS and compose your scene graph in more customizable ways…
I’m glad we could get the viewer running in React - if you are happy with that setup then that is great! If you have specific questions ask away!!