ReactJS add Iframe inside Canvas context - javascript

I have a ReactJS code like this
let context = canvas.getContext('2d');
let renderSize = (video1ScaleMode === 'fill') ? fillSize(video1Size, canvasSize, 1) : fitSize(video1Size, canvasSize, 1);
let xOffset = (canvasSize.width - renderSize.width) / 2;
let yOffset = (canvasSize.height - renderSize.height) / 2;
context.drawImage(video1, xOffset, yOffset, renderSize.width, renderSize.height);
This loads the camera inside the canvas. This is working fine.
I want to add a webpage (with animations) behind the camera view. For that I tried to add IFrame (because the URL is from the server)
For that I tried the following code
iframe.js
import React, { Component } from 'react'
import { createPortal } from 'react-dom'
export class IFrame extends Component {
constructor(props) {
super(props)
this.state = {
mountNode: null
}
this.setContentRef = (contentRef) => {
this.setState({
mountNode: contentRef?.contentWindow?.document?.body
})
}
}
render() {
const { children, ...props } = this.props
const { mountNode } = this.state
return (
<iframe
{...props}
ref={this.setContentRef}
>
{mountNode && createPortal(children, mountNode)}
</iframe>
)
}
}
My usage is like this
import { IFrame } from './iframe'
const MyComp = () => (
<IFrame>
https://www.helloworld.com
</IFrame>
)
context.drawImage(MyComp, xOffset, yOffset, renderSize.width, renderSize.height);
For that I got error like this
**
Uncaught TypeError: Failed to execute 'drawImage' on
'CanvasRenderingContext2D': The provided value is not of type
'(CSSImageValue or HTMLCanvasElement or HTMLImageElement or
HTMLVideoElement or ImageBitmap or OffscreenCanvas or SVGImageElement
or VideoFrame)'.
at renderFrame
**
I understand that IFrame component is not like HTMLCanvasElement or ImageElement or VideoElement.
I would like to know how I can add the IFrame or some other way to bring image from URL as background of Video.

You could visually position your iframe behind the canvas using css. Perhaps like this:
<div class="container">
<canvas></canvas>
<iframe></iframe>
</div>
.container {
position: relative;
}
iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
}

Related

Passing variables to child component doesn't update reactively when dragging parent

I have a component ("Mover.svelte") that's responsible for providing mouse-drag functionality to whatever component is in its ("Button.svelte" in this case.)
I'm trying to display the current X/Y position in the child object, which works, but doesn't update reactively as the object moves. (There's no error message, just not working as expected.)
I have tried a variety of ways to do this, including Svelte's context API, but none of the approaches I've tried seem to enable the reactivity.
REPL:
https://svelte.dev/repl/1d2b72dbf8aa465fa60b76f2e93fb0cc?version=3.49.0
app.svelte:
<script>
import Mover from "./Mover.svelte";
import Button from "./Button.svelte";
</script>
<Mover>
<Button />
</Mover>
Mover.svelte:
<script>
import { setContext } from 'svelte';
let moving = false;
export let x = 120;
export let y = 130;
setContext('x', x);
setContext('y', y);
function dragItem(e) {
if (moving) {
x += e.movementX;
y += e.movementY;
}
}
</script>
<div on:mousedown={() => moving = true} on:mouseup={() => moving = false} style="left: {x}px; top: {y}px;">
<slot />
</div>
<svelte:window on:mousemove={dragItem} />
<style>
div {
margin: 0;
padding: 0;
position: absolute;
border: 4px solid green;
}
</style>
Button.svelte:
<script>
import { getContext } from 'svelte';
let x = getContext('x')
let y = getContext('y')
</script>
<button>
X/Y should update on drag:
{x}, {y}
</button>
<style>
button {
border: 4px solid blue;
margin: 0;
padding: 12;
}
</style>
Any help would be greatly appreciated.
If you want to use a context, you have to use a store, as setting/getting the context is a one-time operation. Reactivity does not transfer between files except through props; stores can bridge that gap.
E.g. in Mover:
import { setContext } from 'svelte';
import { writable } from 'svelte/store';
let moving = false;
export let x = 120;
export let y = 130;
const xStore = setContext('x', writable(x));
const yStore = setContext('y', writable(y));
// Synch the stores on change:
$: $xStore = x;
$: $yStore = y;
In Button add $ prefix to read from the stores:
<button>
X/Y should update on drag:
{$x}, {$y}
</button>
REPL
As alternative to contexts, using slot props would also be possible here.
In mover set the props on the slot:
<slot {x} {y} />
Pass them down in App:
<Mover let:x let:y>
<Button {x} {y} />
</Mover>
(Button then needs to declare props for x/y)
REPL

scale html canvas (html / reactjs)

I am trying to scale a canvas:
class DungeonMap extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
const canvas = this.refs.canvas;
const ctx = canvas.getContext('2d');
ctx.scale(2, 2)
this.setState({})
}
render() {
console.log("render")
return (
<div className="DungeonMap">
<canvas ref="canvas" style={canvasStyle}/>
</div>
);
}
}
The code compiles but the canvas scaling is not applied, i've tried different values for the scaling.
As you can see I also tried putting this.setState({}) to force a re-render but the canvas is still not scaled.
I am pretty sure that componentDidMount() is the right method to use because i have to make sure the scaling is applied after the HTML is loaded: Cannot read property 'getContext' of null, using canvas
How can i apply the scaling for the canvas?
Try setting a width and height attribute on the <canvas> element:
class DungeonMap extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
const canvas = this.refs.canvas;
// to scale the <canvas> element itself
canvas.width = 500;
canvas.height = 500;
// to scale the drawings, use the context
const ctx = canvas.getContext('2d');
ctx.scale(2, 2);
}
render() {
console.log("render")
return (
<div className="DungeonMap">
<canvas ref="canvas" style={canvasStyle}/>
</div>
);
}
};
By the way: I'm not really sure, but shouldn't that <canvas> element in your JSX have a closing tag? Or does JSX handle that different? Not using JSX that much myself.
try to set a property canvas in a state of component and in the componentDidMount makes setState({width:600,heigth:500})
class DungeonMap extends Component {
constructor(props) {
super(props);
this.state={
width:200,
height:300
}
}
componentDidMount() {
const canvas = this.refs.canvas;
// to scale the <canvas> element itself
canvas.width = this.state.widht;
canvas.height = this.state.height;
// to scale the drawings, use the context
const ctx = canvas.getContext('2d');
ctx.scale(2, 2);
this.setState({widht:1000,height:800})
}
render() {
console.log("render")
return (
<div className="DungeonMap">
<canvas ref="canvas" style={canvasStyle}/>
</div>
);
}
};
this must found

Rewrite module scripts to classic scripts in JavaScript

I developed a game using module scripts in JavaScript. Basically import and export statement inside my code. The game is only available running local server but I want it available through index.html. If I open it using index.html it throws
Access to Script at path from origin 'null' has been blocked by CORS policy: Invalid response. Origin 'null' is therefore not allowed access.
<!DOCTYPE html>
<html>
<head>
<title>My Game</title>
<style>
body{
margin: 0;
height: 100%;
width: 100%;
}
#screen {
display: block;
height: 100vh;
width: 100vw;
margin: 0;
image-rendering: pixelated;
}
</style>
<script type="module" src="js/main.js"></script>
</head>
<body>
<canvas id="screen" width="250" height="330"></canvas>
</body>
</html>
I think I need to rewrite my JS scripts to classic scripts.
Main.js
import Camera from './Camera.js';
import Entity from './Entity.js';
import PlayerController from './traits/PlayerController.js';
import Timer from './Timer.js';
import { loadFont } from './loaders/font.js';
import { loadEntities } from './entities.js';
import { createLevelLoader, createRandomEntity } from './loaders/level.js';
import { setupKeyboard } from './input.js';
import { createCollisionLayer } from './layers/collision.js';
import { createDashboardLayer } from './layers/dashboard.js';
import { createGameOverLayer } from './layers/gameover.js';
function createPlayerEnv(playerEntity) {
const playerEnv = new Entity();
const playerControl = new PlayerController();
playerControl.checkpoint.set(64, 64);
playerControl.setPlayer(playerEntity);
playerEnv.addTrait(playerControl);
return playerEnv;
}
async function main(canvas) {
const context = canvas.getContext('2d');
const [entityFactory, font] = await Promise.all([loadEntities(), loadFont()]);
const loadLevel = await createLevelLoader(entityFactory);
const level = await loadLevel('level');
const camera = new Camera();
const user = entityFactory.user();
const playerEnv = new createPlayerEnv(user);
level.entities.add(playerEnv);
//Bounding Box:
level.comp.layers.push(createCollisionLayer(level));
level.comp.layers.push(createDashboardLayer(font, playerEnv));
timer.start();
}
const canvas = document.getElementById('screen');
main(canvas);
Script with exports
import Keyboard from './KeyboardState.js';
export function setupKeyboard(pacman) {
const input = new Keyboard();
input.addMapping('KeyP', keyState => {
if (keyState) {
pacman.jump.start();
} else {
pacman.jump.cancel();
}
});
input.addMapping('KeyO', keyState => {
pacman.turbo(keyState);
});
input.addMapping('KeyD', keyState => {
pacman.go.dir += keyState ? 1 : -1;
});
input.addMapping('KeyA', keyState => {
pacman.go.dir += keyState ? -1 : 1;
});
return input;
}
Could you provide an example on how to rewrite module scripts to classic scripts? Thanks!

How to get height and width of the video in Angular 2, TypeScript

I need to check if the video is portrait or landscape, for that I am trying to get the height, width of the video this way,
import { Component, OnInit, AfterViewInit } from '#angular/core';
#Component({
selector: 'app-videplayer',
template: `<video id="singleVideo">
<source src="/assets/videoname.mp4" type="video/mp4">
</video>`
})
export class VideplayerComponent implements OnInit, AfterViewInit {
constructor() {}
ngOnInit() {
}
ngAfterViewInit() {
const videoElem: HTMLVideoElement = <HTMLVideoElement>document.getElementById('singleVideo');
const vidH = videoElem.videoHeight;
const vidW = videoElem.videoWidth;
console.log(vidH, vidW); // this returns: 0, 0
}
}
I tried getting the height, width of the video within constructor, ngOnInit and ngAfterViewInit functions but I'm a getting 0 height and with all the time.
Try with offsetHeight and offsetWidth as below:
const vidH = videoElem.offsetHeight;
const vidW = videoElem.offsetWidth;
And, if it still does not work then try with some timeout in ngAfterViewInit() as below:
ngAfterViewInit() {
setTimeout(function () {
const videoElem: HTMLVideoElement = <HTMLVideoElement>document.getElementById('singleVideo');
const vidH = videoElem.offsetHeight;
const vidW = videoElem.offsetWidth;
console.log(vidH, vidW); // this returns: 0, 0
}, 500);
}
From MDN, on the videoHeight/-Width properties:
If the element's ready state is HAVE_NOTHING, the value is 0
https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement
I think your best option here is to set up a readystatechange event handler, and then inside that check if the state is > 0 (HAVE_NOTHING), and then read the dimensions. (Next readyState would be 1/HAVE_METADATA, and from there on the video dimensions should be available.)
https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/readyState

Canvas flickering when reactjs re-renders

I've got a problem using React JS and canvas together.
everything seems to work smooth, but when render method is called due to changes in other part of my app canvas flicker. It seems that react is re-appending it into the DOM (maybe because I'm playing an animation on it and its drawn frame is different each time).
This is my render method:
render: function() {
var canvasStyle = {
width: this.props.width
};
return (
<canvas id="interactiveCanvas" style={canvasStyle}/>
);
}
As a temporal solution I append my canvas tag manually, without reactjs:
render: function() {
var canvasStyle = {
width: this.props.width
};
return (
<div id="interactivediv3D">
</div>
);
}
And then:
componentDidMount: function() {
var canvasStyle = {
width: this.props.width
};
var el = document.getElementById('interactivediv3D');
el.innerHTML = el.innerHTML +
'<canvas id="interactive3D" style={'+canvasStyle+'}/>';
},
And it works right. But I think this is not a clean way and it's better to render this canvas tag using reactjs.
Any idea of how can I solve the problem? or at least avoiding reactjs re-rendering this canvas when only the drawn frame changed.
I spent time observing with your advice in mind and I find out this flickering was caused by the fact that I was accessing the canvas DOM element and changing canvas.width and canvas.height every time onComponentDidUpdate was called.
I needed to change this property manually because while rendering I don't know canvas.width yet. I fill style-width with "100%" and after rendering I look at "canvas.clientWidth" to extract the value in pixels.
As explained here: https://facebook.github.io/react/tips/dom-event-listeners.html I attached resize event and now I'm changing canvas properties only from my resizeHandler.
Re-rendering the entire tree shouldn't cause your canvas to flicker. But if you think that re-rendering is causing this issue, you could use shouldComponentUpdate lifecycle function and return false whenever you don't want to to update.
https://facebook.github.io/react/docs/component-specs.html#updating-shouldcomponentupdate
So for example:
shouldComponentUpdate: function(nextProps, nextState) {
return false;
}
Any component with the above function defined will not ever run render more than once (at component mounting).
However I've not been able to replicate the flickering issue you complain of. Is your entire tree rendering too often perhaps? In which case the above will fix your canvas, but you should take a look at how many times and how often you are re-rendering your entire tree and fix any issues there.
I had the same problem. I used react-konva library and my component structure looked something like this: canvas had a background image <SelectedImage /> which flicked every time any state property changed.
Initial code
import React, { useState } from "react";
import { Stage, Layer, Image } from "react-konva";
import useImage from "use-image";
function SomeComponent() {
const [imgWidth, setImgWidth] = useState(0);
const [imgHeight, setImgHeight] = useState(0);
//...and other state properties
const SelectedImage = () => {
const [image] = useImage(imgSRC);
if (imgWidth && imgHeight) {
return <Image image={image} width={imgWidth} height={imgHeight} />;
}
return null;
};
return (
<div>
<Stage width={imgWidth} height={imgHeight}>
<Layer>
<SelectedImage />
{/* some other canvas components*/}
</Layer>
</Stage>
</div>
);
}
export default SomeComponent;
The solution
The problem was in a wrong structure of SomeComponent where one component was initialized inside another. In my case it was <SelectedImage /> inside of SomeComponent. I took it out to a separate file and flickering disappeared!
Here is a correct structure
SelectedImage.js
import React from "react";
import { Image } from "react-konva";
import useImage from "use-image";
function SelectedImage({ width, height, imgSRC }) {
const [image] = useImage(imgSRC);
if (width && height) {
return <Image image={image} width={width} height={height} />;
}
return null;
}
export default SelectedImage;
SomeComponent.js
import React, { useState } from "react";
import { Stage, Layer, Image } from "react-konva";
import SelectedImage from "./SelectedImage";
function SomeComponent() {
const [imgWidth, setImgWidth] = useState(0);
const [imgHeight, setImgHeight] = useState(0);
//...and other state properties
return (
<div>
<Stage width={imgWidth} height={imgHeight}>
<Layer>
<SelectedImage width={imgWidth} height={imgHeight} imgSRC={imgSRC} />
{/* some other canvas components*/}
</Layer>
</Stage>
</div>
);
}
export default SomeComponent;

Categories

Resources