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)
}
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'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'm trying to add text in canvas ( bottom right ) with react and canvg lib
This is what i try :
DownloadImage = (i) => {
var _this = this;
this.modeler.saveSVG(function (err, svg) {
if (err) console.log(err)
_this.setState({ datasvg: svg }, function () {
const canvas = _this.refs.canvasforimage;
console.log(canvas)
const options = {
log: false,
ignoreMouse: true
};
canvg(canvas, this.state.datasvg, options);
const image = canvas.toDataURL("image/png").replace("image/png", "image/octet-stream");
const element = document.createElement("a");
element.setAttribute('download', 'diagram.png');
element.setAttribute('href', image);
element.click();
})
})
}
render = () => {
return (
<canvas ref="canvasforimage" id="canvasforimage">
{/* <div style="position:absolute;right:0;bottom:0">Text</div> */} Not working
</canvas>
)
function above is working... i can download as image but code at render the one i commented not working
thanks for the comment #Chris G
so i already solve the problem by adding code after canvg
canvg(canvas, this.state.datasvg, options);
const ctx = canvas.getContext('2d');
ctx.fillText('Hello world',50, 50);
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>";
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