Hello
I’m building a React App which includes a Playcanvas project via iFrame. Which is the best way to connect these 2 together? I’m basically looking for a communication between React UI and Playcanvas 3D scene.
Hello
I’m building a React App which includes a Playcanvas project via iFrame. Which is the best way to connect these 2 together? I’m basically looking for a communication between React UI and Playcanvas 3D scene.
Hi @Christina,
The most efficient way is to use the window.postMessage API, to communicate between the parent and iframe instances.
Here is some sample code on how I implement this on React/Playcanvas apps.
On the Playcanvas side:
var IframeManager = pc.createScript('iframeManager');
// initialize code called once per entity
IframeManager.prototype.initialize = function() {
// --- execute
this.connect();
// --- events
this.app.on('iFrame:sendMessage', function(type, data){
this.sendMessage(type, data);
}, this);
};
IframeManager.prototype.connect = function(){
// --- postMessage event handlers
window.addEventListener("message", function(event){
this.parseMessage(event);
}.bind(this));
// --- inform parent window that we are ready
this.sendMessage('IFrameReady');
};
IframeManager.prototype.sendMessage = function(type, data){
if( !type ) return;
parent.postMessage({
type: type,
data: data
}, '*');
};
IframeManager.prototype.parseMessage = function(event){
if (!event.data) return;
var data = event.data.data;
// use app events to communicate with the rest of the app
switch (event.data.type) {
case "ParseData":
this.app.fire('ParseData', data);
break;
case "StartGame":
this.app.fire('StartGame', data);
break;
}
};
On the React side, using the React Context API to have your components consume the same instance of this provider (code in Typescript):
declare var document: any;
export enum EPlaycanvasEvents {
IFrameReady = "IFrameReady",
ParseData = "ParseData",
StartGame = "StartGame",
}
class Playcanvas {
private iframeContent: any;
private iframeReady: boolean;
private messagesQueue: any[];
constructor() {
this.iframeReady = false;
this.messagesQueue = [];
}
connectToIframe() {
// @ts-ignore
this.iframeContent = document.getElementById("game-iframe").contentWindow;
// --- postMessage event handlers
window.addEventListener("message", this.parseMessage.bind(this));
}
parseMessage(event: any) {
if (!event.data || !event.data.type) return;
switch (event.data.type) {
case EPlaycanvasEvents.IFrameReady:
this.iframeReady = true;
// --- send queue messages
this.messagesQueue.forEach(message => {
this.sendMessage(message.type, message.data);
});
break;
case EPlaycanvasEvents.StartGame:
break;
}
}
sendMessage(type: EPlaycanvasEvents, data?: any) {
// --- if we aren't ready, queue messages for sending later
if (this.iframeReady === false) {
this.messagesQueue.push({
type: type,
data: data
});
return;
}
this.iframeContent.postMessage(
{
type: type,
data: data
},
"*"
);
}
}
export default Playcanvas;
@Leonidas Ok, I have another question for you. Do you have any tip on how to set up the Context API to use the Playcanvas class in my components?
Sure, it can be tricky initially but here is a way to do it. Create a simple React Context and export it:
import React from "react";
const PlaycanvasContext = React.createContext<any | null>(null);
export default PlaycanvasContext;
In your index.tsx file import the Context provider before you boot your app:
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import PlaycanvasContext from "./components/playcanvasContext";
import Playcanvas from "./components/playcanvas";
ReactDOM.render(
<PlaycanvasContext.Provider value={new Playcanvas()}>
<App />
</PlaycanvasContext.Provider>,
document.getElementById("root")
);
Now the class is a singleton (one instance app wide) and you can easily reference it in any of your components like this:
import React, { useContext, useEffect } from "react";
import Playcanvas, { PlaycanvasContext } from "../components/user";
const MyComponent: React.FC = () => {
const playcanvas: Playcanvas = useContext(PlaycanvasContext);
useEffect(() => {
playcanvas.connectToIframe();
}, []);
return (
<div className="my-component">
</div>
);
};
export default MyComponent;