How to use canvas HTML5 on smartphone - reactjs

I have code written on react js, using canvas html 5. It is image eraser: it should properly erase front image and when only some persent of it left, it trigger another action(i haven not done another action yet). It works fine in browser, but when i choose adaptive for any smatphone, it stops working. I tried to change mouse events to touch events but it didn't help.
Here is the code:
import './DrawingCanvas.css';
import {useEffect, useRef, useState} from 'react';
const DrawingCanvas = () => {
const canvasRef = useRef(null);
const contextRef = useRef(null);
const [isDrawing, setIsDrawing] = useState(false);
const [persent, setPersent] = useState(100);
useEffect(() => {
const url = require('../../images/one.jpg');
const canvas = canvasRef.current;
const img = new Image();
img.src = url;
const context = canvas.getContext("2d");
img.onload = function () {
let width = Math.min(500, img.width);
let height = img.height * (width / img.width);
canvas.width = width;
canvas.height = height;
context.drawImage(img, 0, 0, width, height);
contextRef.current = context;
context.lineCap = "round";
context.lineWidth = 350;
contextRef.current.globalCompositeOperation = 'destination-out';
};
}, []);
useEffect(() => {
console.log(persent);
if(persent<=0.1) return alert('hello')
}, [persent]);
const startDrawing = ({nativeEvent}) => {
const {offsetX, offsetY} = nativeEvent;
contextRef.current.beginPath();
contextRef.current.moveTo(offsetX, offsetY);
contextRef.current.lineTo(offsetX, offsetY);
// contextRef.current.stroke();
setIsDrawing(true);
nativeEvent.preventDefault();
};
const draw = ({nativeEvent}) => {
if (!isDrawing) {
return;
}
const {offsetX, offsetY} = nativeEvent;
contextRef.current.lineTo(offsetX, offsetY);
contextRef.current.stroke();
nativeEvent.preventDefault();
let pixels = contextRef.current.getImageData(0, 0, canvasRef.current.width, canvasRef.current.height);
let transPixels = [];
for (let i = 3; i < pixels.data.length; i += 4) {
transPixels.push(pixels.data[i]);
}
let transPixelsCount = transPixels.filter(Boolean).length;
setPersent((transPixelsCount / transPixels.length) * 100);
};
const stopDrawing = () => {
contextRef.current.closePath();
setIsDrawing(false);
};
return (
<div>
<canvas className="canvas-container"
ref={canvasRef}
onMouseDown={startDrawing}
onMouseMove={draw}
onMouseUp={stopDrawing}
onMouseLeave={stopDrawing}
>
</canvas>
</div>
)
}
export default DrawingCanvas;
.canvas-container {
background-image: url("https://media.istockphoto.com/id/1322277517/photo/wild-grass-in-the-mountains-at-sunset.jpg?s=612x612&w=0&k=20&c=6mItwwFFGqKNKEAzv0mv6TaxhLN3zSE43bWmFN--J5w=");
}
Maybe it all can be done through touch events, but idk how to do that properly

Related

react canvas rectangle not aligned with mouse

i got a canvas in react that i want to allow users to draw rectangles, ive searched through stackoverflow and youtube videos regarding this but my rectangle cant seem to align with the crusor. everything works fine when the canvas is the only thing on the page, as in that its positioned on the top left. but when i include my other components everything does wrong in the canvas. please help, its my first time using canvas and ive only been following tutorials, trying to understand each functions but i wasnt able to find a solution.
const HomeCanvas = () => {
const canvasRef = useRef(null);
const contextRef = useRef(null);
const [isDrawing, setIsDrawing] = useState(false);
const canvasOffSetX = useRef(null);
const canvasOffSetY = useRef(null);
const startX = useRef(null);
const startY = useRef(null);
useEffect(() => {
const canvas = canvasRef.current;
canvas.width = 500;
canvas.height = 500;
const context = canvas.getContext("2d");
context.lineCap = "round";
context.strokeStyle = "black";
context.lineWidth = 2;
contextRef.current = context;
const canvasOffSet = canvas.getBoundingClientRect();
canvasOffSetX.current = canvasOffSet.top;
canvasOffSetY.current = canvasOffSet.left;
console.log(canvasOffSet.left);
console.log(canvasOffSet.top);
}, []);
const startDrawingRectangle = ({ nativeEvent }) => {
nativeEvent.preventDefault();
nativeEvent.stopPropagation();
startX.current = nativeEvent.clientX - canvasOffSetX.current;
startY.current = nativeEvent.clientY - canvasOffSetY.current;
setIsDrawing(true);
};
const drawRectangle = ({ nativeEvent }) => {
if (!isDrawing) {
return;
}
nativeEvent.preventDefault();
nativeEvent.stopPropagation();
const newMouseX = nativeEvent.clientX - canvasOffSetX.current;
const newMouseY = nativeEvent.clientY - canvasOffSetY.current;
const rectWidth = newMouseX - startX.current;
const rectHeight = newMouseY - startY.current;
contextRef.current.clearRect(
0,
0,
canvasRef.current.width,
canvasRef.current.height
);
contextRef.current.strokeRect(
startX.current,
startY.current,
rectWidth,
rectHeight
);
};
const stopDrawingRectangle = () => {
setIsDrawing(false);
};
return (
<div className="home-canvas">
<div className="canvas-sidebar">
<div>Merge Table</div>
</div>
<div style={{ width: "100%", paddingLeft: "5%" }}>
<canvas
className="canvas-area"
ref={canvasRef}
onMouseDown={startDrawingRectangle}
onMouseUp={stopDrawingRectangle}
onMouseMove={drawRectangle}
onMouseLeave={stopDrawingRectangle}
></canvas>
</div>
</div>
);
};
what did i do wrong? here is the video of it.
https://youtu.be/ioGINfUKBgc

Three js animation creating multiple blender models

I am trying to play an animation that comes from a blender model while simultaneously having the model auto-rotate from OrbitControls. However I am getting the model to be duplicated, one model is animated on top of a stationary copy of the model that is unanimated:
I've tried removing the orbit controls code and update however I am still getting this result. Any advice would be greatly appreciated.
import {
AmbientLight,
Color,
DirectionalLight,
Mesh,
AnimationMixer,
MeshPhongMaterial,
PerspectiveCamera,
Scene,
SphereBufferGeometry,
UniformsUtils,
Vector2,
WebGLRenderer,
sRGBEncoding,
PointLight,
Clock,
} from 'three';
import { cleanRenderer,
cleanScene,
removeLights,
modelLoader,
textureLoader, } from 'utils/three';
import { GLTFLoader, OrbitControls, RenderPixelatedPass } from 'three-stdlib';
import { render } from 'react-dom';
export const ScifiWorkerRobot = props => {
const theme = useTheme();
const [loaded, setLoaded] = useState(false);
const { rgbBackground, themeId, colorWhite } = theme;
const start = useRef(Date.now());
const canvasRef = useRef();
const mouse = useRef();
const renderer = useRef();
const camera = useRef();
const scene = useRef();
const lights = useRef();
const uniforms = useRef();
const material = useRef();
const geometry = useRef();
const sphere = useRef();
const reduceMotion = useReducedMotion();
const isInViewport = useInViewport(canvasRef);
const windowSize = useWindowSize();
const rotationX = useSpring(0, springConfig);
const rotationY = useSpring(0, springConfig);
const { measureFps, isLowFps } = useFps(isInViewport);
const cameraXSpring = useSpring(0, cameraSpringConfig);
const cameraYSpring = useSpring(0, cameraSpringConfig);
const cameraZSpring = useSpring(0, cameraSpringConfig);
const controls = useRef();
const animations = useRef();
const mixer = useRef();
const clock = useRef();
const animationFrame = useRef();
const mounted = useRef();
const sceneModel = useRef();
//Setting up initial scene
useEffect(() => {
//mounted.current = true;
const { innerWidth, innerHeight } = window;
mouse.current = new Vector2(0.8, 0.5);
renderer.current = new WebGLRenderer({
canvas: canvasRef.current,
antialias: false,
alpha: true,
powerPreference: 'high-performance',
failIfMajorPerformanceCaveat: true,
});
renderer.current.setSize(innerWidth, innerHeight);
renderer.current.setPixelRatio(1);
renderer.current.outputEncoding = sRGBEncoding;
camera.current = new PerspectiveCamera(54, innerWidth / innerHeight, 0.1, 100);
camera.current.position.z = 5;
camera.current.position.x = 0;
camera.current.position.y = 0;
scene.current = new Scene();
clock.current = new Clock();
const ambientLight = new AmbientLight(colorWhite, 0.9);
const dirLight = new DirectionalLight(colorWhite, 0.8);
dirLight.position.set(0,0,0);
const lights = [ambientLight, dirLight];
lights.forEach(light => scene.current.add(light));
controls.current = new OrbitControls(camera.current, canvasRef.current);
controls.current.enableZoom = true;
controls.current.enablePan = true;
controls.current.enableDamping = true;
controls.current.rotateSpeed = 0.5;
controls.current.autoRotate = true;
const loader = new GLTFLoader();
loader.load(veribot, function ( gltf ) {
scene.current.add( gltf.scene );
animations.current = gltf.animations;
mixer.current = new AnimationMixer(gltf.scene);
mixer.current.timeScale = 0.8;
animations.current.forEach((clip, index) => {
const animation = mixer.current.clipAction(clip);
animation.play();
});
}, undefined, function ( error ) {
console.error( error );
} );
return () => {
//mounted.current = false;
cancelAnimationFrame(animationFrame.current);
removeLights(lights);
cleanScene(scene.current);
cleanRenderer(renderer.current);
};
}, []);
useEffect(() => {
let animation;
const animate = () => {
animation = requestAnimationFrame(animate);
const delta = clock.current.getDelta();
if (mixer.current){
mixer.current.update(delta);
}
controls.current.update();
renderer.current.render(scene.current, camera.current);
measureFps();
};
animate();
return () => {
cancelAnimationFrame(animation);
};
}, [isInViewport, measureFps, reduceMotion, isLowFps, rotationX, rotationY]);
return (
<Transition in timeout={2000}>
{visible => (
<canvas
aria-hidden
className={styles.canvas}
data-visible={visible}
ref={canvasRef}
{...props}
/>
)}
</Transition>
);
};

React - How to get client coordinates on canvas properly? (drawing a straight line with mouse)

I got to make "drawing a line" works, but this line wouldn't start from where my mouse starts, rather it'd start from very beginning {x: 0, y:0}. How to draw a line starting from where my mouse clicks?
I already used e.nativeEvent.offsetX and e.nativeEvent.offsetY for getting client X and Y, and this is used for just drawing feature, but I'm stuck how I can utilize it for drawing line, or changing my draw a line code for making it work. Thank you in advanced!
const canvasRef = useRef(null);
const ctxRef = useRef(null);
const [isDrawing, setIsDrawing] = useState(false);
const [lineWidth, setLineWidth] = useState(5);
const [lineColor, setLineColor] = useState("black");
let startPosition = {x: 0, y: 0};
let lineCoordinates = {x: 0, y: 0};
const getClientOffset = (e) => {
const {pageX, pageY} = e.touches ? e.touches[0] : e;
const x = pageX - canvasRef.offsetLeft;
const y = pageY - canvasRef.offsetTop;
return {
x,
y
}
}
const drawLine = () => {
ctxRef.current.beginPath();
ctxRef.current.moveTo(startPosition.x, startPosition.y);
ctxRef.current.lineTo(lineCoordinates.x, lineCoordinates.y);
ctxRef.current.stroke();
}
useLayoutEffect(() => {
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
ctx.strokeStyle = lineColor;
ctx.lineWidth = lineWidth;
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctxRef.current = ctx;
}, [lineColor, lineWidth]);
const handleMouseDown = (e) => {
setIsDrawing(true);
// const posX = e.nativeEvent.offsetX;
// const posY = e.nativeEvent.offsetY;
// ctxRef.current.beginPath();
// ctxRef.current.moveTo(
// posX,
// posY
// );
startPosition = getClientOffset(e);
}
const handleMouseMove = (e) => {
if (!isDrawing) {
return;
}
ctxRef.current.lineTo(
e.nativeEvent.offsetX,
e.nativeEvent.offsetY
);
ctxRef.current.stroke();
lineCoordinates = getClientOffset(e);
drawLine();
}
const handleMouseUp = () => {
ctxRef.current.closePath();
setIsDrawing(false);
}
The issue is let startPosition and let lineCoordinates – on each render update React will reinitialize these variables with {x: 0, y:0}. In order to remember any value you must use useState().
I tried it out in a demo
https://codesandbox.io/s/crazy-breeze-xj26c?file=/src/App.js

Rendering Threejs in React dissapears my root element in HTML

I'm trying to use Three.js in Typescript react, I render Dodecahedron figure and random stars, I want to add some mark up to my three.js with React but when I render Three.js canvas into HTML it dissapears my root div, and I'm not able to add some other components
THREE.JS
import * as THREE from "three";
export function ThreeCanvas() {
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.innerHTML = "";
document.body.appendChild(renderer.domElement);
const geometry = new THREE.DodecahedronBufferGeometry(1.7, 0);
const material = new THREE.MeshStandardMaterial({
color: "#00FF95",
});
const Stars = () => {
const starGeometry = new THREE.SphereGeometry(0.1, 24, 24)
const starMaterial = new THREE.MeshStandardMaterial({color: 0xffffff})
const star = new THREE.Mesh(starGeometry, starMaterial)
const [x, y, z] = Array(3).fill(1).map(() => THREE.MathUtils.randFloatSpread(70))
star.position.set(x, y, z)
scene.add(star)
}
Array(200).fill(100).forEach(Stars)
const light = new THREE.PointLight(0xffffff)
light.position.set(20, 20, 20)
scene.add(light)
camera.position.z = 5;
camera.position.x = 3.5
const Figure = new THREE.Mesh(geometry, material);
scene.add(Figure);
const animate = () => {
requestAnimationFrame(animate);
Figure.rotation.x += 0.01;
Figure.rotation.y += 0.01;
renderer.render(scene, camera);
};
animate();
window.addEventListener("resize", () => {
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
});
return null
}
this is my three.js code what I do in react
React
import ReactDOM from "react-dom"
import { ThreeCanvas } from "./Components/Three";
import Landing from "./Components/Landing";
import "./Style/Style.css"
const FirstSection = () => {
return (
<div className="container">
<Landing />
<ThreeCanvas />;
</div>
);
}
ReactDOM.render(<FirstSection />, document.getElementById("root"));
Landing is my markup component when I open console I dont see anywhere my Landing element but in react tools I see, how to fix that issue I have no idea
You're removing document.body in Three.JS component which is why the body only contains the canvas. You might want to use a reference to the element instead of targeting document.body so that it's not disturbing the DOM structure which is why your markdown does not show. As a rule of thumb, you should never be interacting with the DOM via the document.
document.body.innerHTML = "";
I've quickly refactored the Three.JS component to use a React element reference so that you can add additional markup.
Refactored ThreeCanvas Component
import * as React from "react";
import * as THREE from "three";
export function ThreeCanvas() {
const ref = React.useRef();
const [loaded, setLoaded] = React.useState(false);
React.useEffect(() => {
if (!loaded && ref) {
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
const geometry = new THREE.DodecahedronBufferGeometry(1.7, 0);
const material = new THREE.MeshStandardMaterial({
color: "#00FF95"
});
const Stars = () => {
const starGeometry = new THREE.SphereGeometry(0.1, 24, 24);
const starMaterial = new THREE.MeshStandardMaterial({
color: 0xffffff
});
const star = new THREE.Mesh(starGeometry, starMaterial);
const [x, y, z] = Array(3)
.fill(1)
.map(() => THREE.MathUtils.randFloatSpread(70));
star.position.set(x, y, z);
scene.add(star);
};
Array(200).fill(100).forEach(Stars);
const light = new THREE.PointLight(0xffffff);
light.position.set(20, 20, 20);
scene.add(light);
camera.position.z = 5;
camera.position.x = 3.5;
const Figure = new THREE.Mesh(geometry, material);
scene.add(Figure);
const animate = () => {
requestAnimationFrame(animate);
Figure.rotation.x += 0.01;
Figure.rotation.y += 0.01;
renderer.render(scene, camera);
};
const resize = () => {
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
};
animate();
ref.current.appendChild(renderer.domElement);
window.addEventListener("resize", resize);
setLoaded(true);
return () => window.removeEventListener("resize", resize);
}
}, [ref, loaded]);
return <div ref={ref} />;
}
export default ThreeCanvas;
Typescript Version: ThreeCanvas.tsx
import * as React from "react";
import * as THREE from "three";
export function ThreeCanvas() {
const ref = React.useRef<HTMLDivElement>(null);
const [loaded, setLoaded] = React.useState(false);
React.useEffect(() => {
if (!loaded && ref.current) {
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
const geometry = new THREE.DodecahedronBufferGeometry(1.7, 0);
const material = new THREE.MeshStandardMaterial({
color: "#00FF95"
});
const Stars = () => {
const starGeometry = new THREE.SphereGeometry(0.1, 24, 24);
const starMaterial = new THREE.MeshStandardMaterial({
color: 0xffffff
});
const star = new THREE.Mesh(starGeometry, starMaterial);
const [x, y, z] = Array(3)
.fill(1)
.map(() => THREE.MathUtils.randFloatSpread(70));
star.position.set(x, y, z);
scene.add(star);
};
Array(200).fill(100).forEach(Stars);
const light = new THREE.PointLight(0xffffff);
light.position.set(20, 20, 20);
scene.add(light);
camera.position.z = 5;
camera.position.x = 3.5;
const Figure = new THREE.Mesh(geometry, material);
scene.add(Figure);
const animate = () => {
requestAnimationFrame(animate);
Figure.rotation.x += 0.01;
Figure.rotation.y += 0.01;
renderer.render(scene, camera);
};
const resize = () => {
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
};
animate();
ref.current.appendChild(renderer.domElement);
window.addEventListener("resize", resize);
setLoaded(true);
return () => window.removeEventListener("resize", resize);
}
}, [ref, loaded]);
return <div ref={ref} />;
}
export default ThreeCanvas;

How to pass the useRef hook value from one component to another?

I am currently learning React and specifically hooks. I am trying to create a canvas where I am using the hook 'useRef'. I get this point that whenever I have to create a reference to my Canvas I can use the 'useRef' hook and by can also use it to point to other canvas properties.
My question is in the code below I have created a component named "UseWBoard" and I want to pass my ctxReference.current value to the "Marker" component.
Here is the code:
import React, { useRef, useEffect } from "react";
const UseWBoard = () => {
const canvasReference = useRef(null);
const ctxReference = useRef(null);
let initPaint = false;
useEffect(() => {
const canvas = canvasReference.current;
const ctx = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx.lineCap = "round";
ctx.lineWidth = "25";
ctx.strokeStyle = "black";
ctxReference.current = ctx;
}, []);
function startDraw({ nativeEvent }) {
const { offsetX, offsetY } = nativeEvent;
ctxReference.current.beginPath();
ctxReference.current.moveTo(offsetX, offsetY);
initPaint = true;
}
function stopDraw() {
ctxReference.current.closePath();
initPaint = false;
}
function sketch({ nativeEvent }) {
if (!initPaint) {
return;
}
const { offsetX, offsetY } = nativeEvent;
ctxReference.current.lineTo(offsetX, offsetY);
ctxReference.current.stroke();
}
function Marker() {
ctxReference.current.lineCap = "round";
ctxReference.current.lineWidth = "1";
ctxReference.current.strokeStyle = "black";
}
return (
<div>
<button onClick={Marker}>abc</button>
<canvas
ref={canvasReference}
onMouseDown={startDraw}
onMouseUp={stopDraw}
onMouseMove={sketch}
></canvas>
</div>
);
};
const Mark = (ctxReference) => {
ctxReference.current.lineCap = "round";
ctxReference.current.lineWidth = "1";
ctxReference.current.strokeStyle = "black";
};
export { UseWBoard, Mark };
Thanks in advance.

Resources