I'm trying to run a pixi.js script in a react project but I'm blocked with this error:
Cannot read property 'appendChild' of null
I don't know why this error happens. My script must create a canvas element in the div to display an image with a distortion effect: http://guillaumeduclos.fr/ripple-effect/ It works great in a basic HTML and JS environment.
My code:
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import image from './image.png';
import * as PIXI from 'pixi.js'
var width = window.offsetWidth;
var height = window.offsetHeight;
var playground = document.getElementById('pxrender');
var canvas;
var ratio = 150 / 830;
var count = 0;
var raf;
var renderer = PIXI.autoDetectRenderer(width, height,{transparent:true});
renderer.autoResize = true;
var tp, preview;
var displacementSprite,
displacementFilter,
stage;
class App extends Component {
state = {
playground: null
}
componentDidMount() {
this.setState({playground: this.refs.pxrender});
}
setScene = (url) => {
playground.appendChild(renderer.view);
stage = new PIXI.Container();
tp = PIXI.Texture.fromImage(url);
preview = new PIXI.Sprite(tp);
preview.anchor.x = 0;
displacementSprite = PIXI.Sprite.fromImage('https://res.cloudinary.com/dvxikybyi/image/upload/v1486634113/2yYayZk_vqsyzx.png');
displacementSprite.texture.baseTexture.wrapMode = PIXI.WRAP_MODES.REPEAT;
displacementFilter = new PIXI.filters.DisplacementFilter(displacementSprite);
displacementSprite.scale.y = 0.6;
displacementSprite.scale.x = 0.6;
stage.addChild(displacementSprite);
stage.addChild(preview);
this.animate();
}
removeScene = () => {
cancelAnimationFrame(raf);
stage.removeChildren();
stage.destroy(true);
playground.removeChild(canvas);
}
animate = () => {
raf = requestAnimationFrame(this.animate);
displacementSprite.x = count*10;
displacementSprite.y = count*10;
count += 0.05;
stage.filters = [displacementFilter];
renderer.render(stage);
canvas = playground.querySelector('canvas');
}
render() {
this.setScene(image);
return (
<div ref="pxrender" id="pxrender">
</div>
);
}
}
export default App;
Thank you for your help.
You need to start up your Pixi application in componentDidMount(), not render().
Move this.setScene(image); to componentDidMount(), like this:
componentDidMount() {
this.setScene(image);
}
For a fuller example of a pixi.js scene in React, look here: https://github.com/ccnmtl/astro-interactives/blob/master/sun-motion-simulator/src/DatePicker.jsx
Related
First time working with canvas in react. I am wondering what the best way to provide methods with the canvas context is (like paint below). Should I use Refs? State? Or is there another way. Thanks
Here is what I have for using refs so far:
class Canvas extends React.Component{
canvasRef = React.createRef();
contextRef = React.createRef();
componentDidMount() {
const canvas = this.canvasRef.current;
const context = canvas.getContext("2d");
this.contextRef.current = context;
}
paint = (e) => {
this.contextRef.current.lineTo(e.clientX, e.clientY);
this.contextRef.current.stroke();
};
render() {
return ( <
canvas id = "canvas"
ref = {
this.canvasRef
}
onMouseMove = {
this.paint
}
/>
);
}
}
Refs are correct for getting the canvas, but I'd suggest getting the 2D context each time you need it (getContext just returns the same context as the previous time).
You have a few other minor issues with that code, here's an example putting the context in an instance property, and fixing various issues called out inline.
class Canvas extends React.Component { // *** Note: You need to extend `React.Component`
canvasRef = React.createRef(); // *** Note: You shouldn't have `this.` here
paint = (e) => {
// ** Get the canvas
const canvas = this.canvasRef.current;
if (!canvas) {
return; // This probably won't happen
}
// Get the context
const context = canvas.getContext("2d");
// Get the point to draw at, allowing for the possibility
// that the canvas isn't at the top of the doc
const { left, top } = canvas.getBoundingClientRect();
const x = e.clientX - left;
const y = e.clientY - top;
context.lineTo(x, y);
context.stroke();
}; // *** Added missing ; here
render() {
return <canvas ref={this.canvasRef} onMouseMove={this.paint} />;
}
}
(I removed the id on the canvas, since your component might get used in more than one place.)
In a comment you mentioned issues with state changes causing trouble (I saw that as well when trying to get and keep the context rather than getting it each time), so I've added state both to a parent container element and also to the Canvas element in the following example to check that it continues to work, which it does:
const { useState } = React;
class Canvas extends React.Component { // *** Note: You need to extend `React.Component`
canvasRef = React.createRef(); // *** Note: You shouldn't have `this.` here
state = {
ticker: 0,
};
paint = (e) => {
// ** Get the canvas
const canvas = this.canvasRef.current;
if (!canvas) {
return; // This probably won't happen
}
// Get the context
const context = canvas.getContext("2d");
// Get the point to draw at, allowing for the possibility
// that the canvas isn't at the top of the doc
const { left, top } = canvas.getBoundingClientRect();
const x = e.clientX - left;
const y = e.clientY - top;
context.lineTo(x, y);
context.stroke();
}; // *** Added missing ; here
increment = () => {
this.setState(( { ticker }) => ({ ticker: ticker + 1 }));
};
render() {
const { ticker } = this.state;
return <div>
<canvas ref={this.canvasRef} onMouseMove={this.paint} />
<div onClick={this.increment}>Canvas ticker: {ticker}</div>
</div>;
}
}
class Example extends React.Component {
state = {
ticker: 0,
};
increment = () => {
this.setState(({ ticker }) => ({ ticker: ticker + 1 }));
};
render() {
const { ticker } = this.state;
return <div>
<div onClick={this.increment}>Parent ticker: {ticker}</div>
<Canvas />
</div>;
}
}
ReactDOM.render(<Example />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
I am using Javascript in a Django project to make an audio player/visualiser but I am having a problem getting the audio to play when the link is clicked. Having tested a few small functions in app.js I can see it is working in the HTML file but for some reason when I try to play the audio it says no supported source found and that my audio file was not found. The Javascript works fine in another project but I cannot get it to work within Django. I am new to Django so any help would be brilliant, thanks.
export default class AudioPlayer {
constructor(selector = '.audioPlayer', audio = []) {
this.playerElement = document.querySelector(selector);
this.audio = audio;
this.currentAudio = null;
this.createPlayerElements();
this.audioContext = null;
}
createVisualiser() {
this.audioContext = new AudioContext();
const src = this.audioContext.createMediaElementSource(this.audioElement);
const analyser = this.audioContext.createAnalyser();
const canvas = this.visualiserElement;
const ctx = canvas.getContext('2d');
src.connect(analyser);
analyser.connect(this.audioContext.destination);
analyser.fftSize = 128;
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
const barWidth = (canvas.width / bufferLength) * 2.5;
let barHeight;
let bar;
function renderFrame() {
requestAnimationFrame(renderFrame);
bar = 0;
analyser.getByteFrequencyData(dataArray);
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < bufferLength; i++) {
barHeight = dataArray[i] - 100;
const r = barHeight + (50 * (i / bufferLength));
ctx.fillStyle = `rgb(${r}, 100, 50)`;
ctx.fillRect(bar, canvas.height - barHeight, barWidth, barHeight);
bar += barWidth + 2;
}
}
renderFrame();
}
createPlayerElements() {
this.audioElement = document.createElement('audio');
this.audioElement.crossOrigin = "anonymous";
const playlistElement = document.createElement('div');
playlistElement.classList.add('playlist');
this.visualiserElement = document.createElement('canvas');
this.playerElement.appendChild(this.audioElement);
this.playerElement.appendChild(playlistElement);
this.playerElement.appendChild(this.visualiserElement);
this.createPlaylistElement(playlistElement);
}
createPlaylistElement(playlistElement) {
this.audio.forEach(audio => {
const audioItem = document.createElement('a');
audioItem.classList.add('musicA');
audioItem.href = audio.url;
audioItem.innerHTML = `<i class="fa fa-play"></i>${audio.name}`;
this.setupEventListener(audioItem);
playlistElement.appendChild(audioItem);
});
}
setupEventListener(audioItem) {
audioItem.addEventListener('click', (e) => {
e.preventDefault();
if (!this.audioContext) {
this.createVisualiser();
}
const isCurrentAudio = audioItem.getAttribute('href') == (this.currentAudio && this.currentAudio.getAttribute('href'));
if (isCurrentAudio && !this.audioElement.paused) {
this.setPlayIcon(this.currentAudio);
this.audioElement.pause();
console.log('paused');
}
else if (isCurrentAudio && this.audioElement.paused) {
this.setPuaseIcon(this.currentAudio);
this.audioElement.play();
}
else {
if (this.currentAudio) {
this.setPlayIcon(this.currentAudio);
}
this.currentAudio = audioItem;
this.setPuaseIcon(this.currentAudio);
this.audioElement.src = this.currentAudio.getAttribute('href');
this.audioElement.play();
}
});
}
setPlayIcon(element) {
const icon = element.querySelector('i');
icon.classList.remove('fa-pause');
icon.classList.add('fa-play');
}
setPuaseIcon(element) {
const icon = element.querySelector('i');
icon.classList.remove('fa-play');
icon.classList.add('fa-pause');
}
}
I have then created a new AudioPlayer in my app.js:
import AudioPlayer from './AudioPlayer.js';
const audioPlayer = new AudioPlayer('.audioPlayer', [
{ url: "musicPlayer/static/songs/song1.mp3", name: "abc" },
]);
The musicPlayer.urls:
urlpatterns = [
path('', views.home, name="home")
]
And the project .urls:
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('musicPlayer.urls')),
]
The musicPlayer file:
musicPlayer
|
|-__pycache__
|-migrations
|-static
| |
| |-css
| |
| |-javascript
| | |
| | |-app.js
| | |-AudioPlayer.js
| |-songs
|-templates
|-urls.py
|-views.py
| ...
I'm not fully sure because I can't see your project structure, but is the audioPlayer urls inside of the musicPlayers app? or no?
it looks like you haven't included the audioPlayer urls anywhere...They're not in the musicPlayers.urls or the project urls, so effectively, you are trying to go to a url that they project can't find, because you haven't imported it properly.
You either need to import the audioPlayer.urls into the musicPlayer urls if the audioPlayer is in that app, or, if it's it's own app, then it needs to be imported into the project urls.
hopefully that's solve the 404, you might also have to make a view, and then connect the view to the url. I'm not entirely sure how the pure js functions work with urls.
I'm trying to implement web sockets in my react frontend to stream some frames to it with canvas.
First the code was working fine and I could receive the data very well ... but when I needed to add a function call from the parent component, I got an error:
videoSocket.js:51 Uncaught TypeError: Cannot read property 'getContext' of null
This is the code :
import React, { Component } from "react";
import "./canvas.css";
function b64toBlob(dataURI) {
var byteString = atob(dataURI.split(",")[0]);
var ab = new ArrayBuffer(byteString.length);
var ia = new Uint8Array(ab);
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
return new Blob([ab], { type: "image/jpeg" });
} /*
ws.onmessage = function(event) {
var img=document.getElementById("video_frames");
var urlObject = URL.createObjectURL(event.data);
img.src = urlObject;
document.body.appendChild(img);
}*/
class VideoSocket extends Component {
state = {
websocket: null,
};
constructor(props) {
super(props);
var ws = new WebSocket("ws://" + props.link);
this.state = {
websocket: ws,
};
}
render() {
const self = this;
this.state.websocket.onmessage = function (event) {
var js = JSON.parse(atob(event.data.split(",")[0]));
var data = b64toBlob(js["data"]);
var personinfo = { infos: js["infos"] };
console.log(self.props);
self.props.setdata(personinfo);
var urlObject = URL.createObjectURL(data);
var canvas = document.getElementById("tools_sketch");
/*
canvas.width = window.innerWidth; // equals window dimension
canvas.height = window.innerHeight;*/
var ctx = canvas.getContext("2d");
var image = new Image();
image.onload = function () {
ctx.drawImage(
image,
0,
0,
image.width,
image.height, // source rectangle
0,
0,
canvas.width,
canvas.height
);
};
image.src = urlObject;
};
return (
<div>
<canvas
id="tools_sketch"
width={this.props.width}
height={this.props.height}
>
Sorry, your browser doesn't support the <canvas> element.
</canvas>
</div>
);
}
}
export default VideoSocket;
When I remove
self.props.setdata(personinfo);
it works fine but when I add it no matter what I tried it's not working .
I tried to add the function onmessage to componentdidmount like below but I get the same error.
import React, { Component } from "react";
import "./canvas.css";
function b64toBlob(dataURI) {
var byteString = atob(dataURI.split(",")[0]);
var ab = new ArrayBuffer(byteString.length);
var ia = new Uint8Array(ab);
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
return new Blob([ab], { type: "image/jpeg" });
} /*
ws.onmessage = function(event) {
var img=document.getElementById("video_frames");
var urlObject = URL.createObjectURL(event.data);
img.src = urlObject;
document.body.appendChild(img);
}*/
class VideoSocket extends Component {
state = {
websocket: null,
};
constructor(props) {
super(props);
var ws = new WebSocket("ws://" + props.link);
this.state = {
websocket: ws,
};
}
componentDidMount() {
const self = this;
this.state.websocket.onmessage = function (event) {
var js = JSON.parse(atob(event.data.split(",")[0]));
var data = b64toBlob(js["data"]);
var personinfo = { infos: js["infos"] };
console.log(self.props);
self.props.setdata(personinfo);
var urlObject = URL.createObjectURL(data);
var canvas = document.getElementById("tools_sketch");
/*
canvas.width = window.innerWidth; // equals window dimension
canvas.height = window.innerHeight;*/
var ctx = canvas.getContext("2d");
var image = new Image();
image.onload = function () {
ctx.drawImage(
image,
0,
0,
image.width,
image.height, // source rectangle
0,
0,
canvas.width,
canvas.height
);
};
image.src = urlObject;
};
}
render() {
return (
<div>
<canvas
id="tools_sketch"
width={this.props.width}
height={this.props.height}
>
Sorry, your browser doesn't support the <canvas> element.
</canvas>
</div>
);
}
}
export default VideoSocket;
This is the server side code in python :
#!/usr/bin/env python
import random
import websockets
import asyncio
import os
import numpy as np
import cv2
import json
import base64
#!/usr/bin/env python
# WS server that sends messages at random intervals
class VideoCamera(object):
def __init__(self):
self.video = cv2.VideoCapture('video2.mp4')
def __del__(self):
self.video.release()
def get_frame(self):
while True:
ret, image = self.video.read()
ret, jpeg = cv2.imencode('.jpg', image)
base6 = base64.b64encode(jpeg.tobytes())
yield base6.decode('utf-8')
def gen(camera):
while True:
image = next(camera.get_frame())
yield(image)
async def time(websocket, path):
camera = VideoCamera()
i = 0
while True:
i = i+1
data = next(gen(camera))
js = {'data': data, "infos": [
{'id': 1, 'name': 'name1'}, {'id': 2, 'name': 'name2'}]}
res_bytes = json.dumps(js).encode('utf-8')
base6 = base64.b64encode(res_bytes)
message = base6.decode('utf-8')
await websocket.send(message)
start_server = websockets.serve(time, "127.0.0.1", 5678)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
Edit : the parent component code :
import React, { Component, useState } from "react";
import "antd/dist/antd.css";
import VideoSocket from "../components/videoSocket";
import MyTable from "../components/table";
class Home extends Component {
state = { personinfo: {} };
constructor(props) {
super(props);
let links = {
1: "127.0.0.1:5678",
2: "127.0.0.1:5679",
3: "127.0.0.1:5680",
4: "127.0.0.1:5681",
};
const CameraId = props.match.params.cameraId;
console.log(CameraId);
let link = links[CameraId];
if (link == null) {
link = "127.0.0.1:5678";
}
let curent = link;
this.state = {
links: links,
curent: curent,
};
console.log(link);
}
setData = (personinfo) => {
this.setState({
personinfo: personinfo,
});
};
render() {
return (
<div className=".container-fluid overflow-auto" width="100%">
<div></div>
<VideoSocket
className=""
width={(window.innerWidth * 75) / 100}
height="400px"
link={this.state.curent}
setdata={this.setData}
key="1"
></VideoSocket>
<MyTable personinfo={this.state.personinfo}></MyTable>
</div>
);
}
}
export default Home;
Edit: I tried to add
shouldComponentUpdate() {
return false;
}
to the component videosocket but it still do the same problem
I have problem with importing JS scripts from code. I have a canvas game, and want to import my classes rather than define they in HTML. But when I run my code in Edge, it throws me an error.
init.js
import {Tram} from "./tram/tram"
var body;
var curScreen = "menu";
var canvas = document.createElement("canvas");
canvas.width = 1920;
canvas.height = 1080;
var ctx = canvas.getContext("2d");
var tex = {};
var corrHeight =
loadTextures();
window.onload = function () {
body = document.body;
body.appendChild(canvas);
window.onclick = function () {
if (body.requestFullscreen) {
body.requestFullscreen();
}
else if (body.msRequestFullscreen) {
body.msRequestFullscreen();
}
else if (body.mozRequestFullScreen) {
body.mozRequestFullScreen();
}
else if (body.webkitRequestFullscreen) {
body.webkitRequestFullscreen();
}
};
mainLoop();
};
function updateCanvasRect() {
canvas.width = brect.width;
canvas.height = brect.height;
var prop = 16 / 9;
tex.sky.img.height = brect.height;
tex.sky.img.width = brect.height * prop;
tex.sky.patt = ctx.createPattern(tex.sky.img, "repeat-x");
}
function loadTextures() {
tex.sky = importTexture("base/sky");
}
I have following folder structure:
ts2d/
img/
...
base/
...
tram/
tram.js
...
init.js
load.js
main.js
index.html
Solved! The problem was in HTML script declaration:
I used this code:
<script src="base/init.js"></script>
But I don't knew, that I need to specify type attribute, with code below import works excellent!
<script src="base/init.js" type="module"></script>
UPD: variables just are file-scoped, to use in another file export variables:
var myVar = "string";
export {myVar};
and then in your file
import {myVar} from "<file>";
Struggling a bit here. If I'm just filling or doing anything else to the canvas - no issue. I get the div without the external image. Tried local image file as well as URL... Thanks!
import React, { Component, PropTypes } from 'react';
export default class CanvasCreator extends Component {
componentDidMount() {
this.updateCanvas();
}
updateCanvas() {
const ctx = this.refs.canvas.getContext('2d');
var imageObj1 = new Image();
imageObj1.src = 'https://s-media-cache-ak0.pinimg.com/236x/d7/b3/cf/d7b3cfe04c2dc44400547ea6ef94ba35.jpg'
ctx.drawImage(imageObj1,0,0);
}
render() {
return (
<canvas ref="canvas" width={300} height={300}> </canvas>
);
}
};
You are missing the onload method. This will work for you:
updateCanvas() {
const ctx = this.refs.canvas.getContext('2d');
var imageObj1 = new Image();
imageObj1.src = 'https://s-media-cache-ak0.pinimg.com/236x/d7/b3/cf/d7b3cfe04c2dc44400547ea6ef94ba35.jpg'
imageObj1.onload = function() {
ctx.drawImage(imageObj1,0,0);
}
}
For me in order to work was also necessary to add the width and height!
Example:
imageObj1.onload = function() {
ctx.drawImage(imageObj1, 0, 0, 500, 500)
}