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

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.

Related

How to use canvas HTML5 on smartphone

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

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>
);
};

merging custom hooks for handling scroll

I am trying to merge these two hooks to get the direction and the percentage of the scroll.
useScrollPercentage.js
import { useRef, useState, useEffect } from "react";
export default function useScrollPercentage() {
const scrollRef = useRef(null);
const [scrollPercentage, setScrollPercentage] = useState(NaN);
const reportScroll = e => {
setScrollPercentage(getScrollPercentage(e.target));
};
useEffect(
() => {
const node = scrollRef.current;
if (node !== null) {
node.addEventListener("scroll", reportScroll, { passive: true });
if (Number.isNaN(scrollPercentage)) {
setScrollPercentage(getScrollPercentage(node));
}
}
return () => {
if (node !== null) {
node.removeEventListener("scroll", reportScroll);
}
};
},
[scrollPercentage]
);
return [scrollRef, Number.isNaN(scrollPercentage) ? 0 : scrollPercentage];
}
function getScrollPercentage(element) {
if (element === null) {
return NaN;
}
const height = element.scrollHeight - element.clientHeight;
return Math.round((element.scrollTop / height) * 100);
}
useScrollDirection.js
import {useState, useEffect} from 'react';
import _ from 'lodash';
export default function useScrollDirection({
ref,
threshold,
debounce,
scrollHeightThreshold,
}) {
threshold = threshold || 10;
debounce = debounce || 10;
scrollHeightThreshold = scrollHeightThreshold || 0;
const [scrollDir, setScrollDir] = useState(null);
const debouncedSetScrollDir = _.debounce(setScrollDir, debounce);
useEffect(() => {
let lastScrollY = ref?.current?.scrollTop;
let lastScrollDir;
let ticking = false;
const hasScrollHeightThreshold =
ref?.current?.scrollHeight - ref?.current?.clientHeight >
scrollHeightThreshold;
const updateScrollDir = () => {
const scrollY = ref?.current?.scrollTop;
if (
Math.abs(scrollY - lastScrollY) < threshold ||
!hasScrollHeightThreshold
) {
ticking = false;
return;
}
const newScroll = scrollY > lastScrollY ? 'Down' : 'Up';
if (newScroll !== lastScrollDir) {
debouncedSetScrollDir(newScroll);
}
lastScrollY = scrollY > 0 ? scrollY : 0;
lastScrollDir = newScroll;
ticking = false;
};
const onScroll = () => {
if (!ticking) {
window.requestAnimationFrame(updateScrollDir);
ticking = true;
}
};
ref?.current?.addEventListener('scroll', onScroll);
return () => window.removeEventListener('scroll', onScroll);
}, []);
return scrollDir;
}
Currently I am using it like this
App.js
const [scrollRef, scrollPercentage] = useScrollPercentage();
const scrollDirection = useScrollDirection({ref: scrollRef});
const handleScroll = () => {
console.log(scrollPercentage, scrollDirection);
}
return (
<Modal ref={scrollRef} onScroll={handleScroll}>
// Scrollable Content
</Modal>
)
The problem is that since I don't want to trigger useScrollDirection for every scroll percentage unless the scroll direction changed only it should trigger. Also is it possible to merge these hooks?
Any help is appreciated

How to test carousel in RTL?

I want to test carousel in React-testing-library.
I can't check whether state is changed in RTL like enzyme does, so...
What I've done is just give carousel a scrollX and check whether it is changed
fireEvent.scroll(element, { target: { scrollX: 100 } });
// check
element.scrollLeft...
Is it collect or any ideas?
import React, { useRef } from "react";
import "./Carousel.css";
export interface CarouselProps {
children: React.ReactNode;
}
const Carousel = ({ children }: CarouselProps) => {
const carouselRef = useRef<HTMLDivElement>(null);
let isDown = false;
let startX = 0;
let scrollLeft = 0;
const onMouseDownCarousel: React.MouseEventHandler<HTMLDivElement> = (e) => {
if (!carouselRef || !carouselRef.current) {
return;
}
isDown = true;
carouselRef.current.classList.add("active");
startX = e.pageX - carouselRef.current.offsetLeft;
scrollLeft = carouselRef.current.scrollLeft;
};
const onMouseLeaveCarousel: React.MouseEventHandler<HTMLDivElement> = (e) => {
if (!carouselRef || !carouselRef.current) {
return;
}
isDown = false;
carouselRef.current.classList.remove("active");
};
const onMouseUpCarousel: React.MouseEventHandler<HTMLDivElement> = (e) => {
if (!carouselRef || !carouselRef.current) {
return;
}
isDown = false;
carouselRef.current.classList.remove("active");
};
const onMouseMoveCarousel: React.MouseEventHandler<HTMLDivElement> = (e) => {
if (!carouselRef || !carouselRef.current) {
return;
}
if (!isDown) return;
e.preventDefault();
const x = e.pageX - carouselRef.current.offsetLeft;
const walk = (x - startX) * 3;
carouselRef.current.scrollLeft = scrollLeft - walk;
};
return (
<div
className="carousel"
onMouseDown={onMouseDownCarousel}
onMouseLeave={onMouseLeaveCarousel}
onMouseUp={onMouseUpCarousel}
onMouseMove={onMouseMoveCarousel}
ref={carouselRef}
>
{children}
</div>
);
};
export default Carousel;

Not able to click marker created using PIXI.Sprite on leaflet map despite being interactive property true and click event is added

This is my simple code. And I am just rendering a single marker and try to click it.
But it is not clickable and nothing happens.
import React, { PureComponent } from 'react';
import ReactDOMServer from 'react-dom/server';
import * as PIXI from 'pixi.js';
import 'leaflet-pixi-overlay';
import L from 'leaflet';
import { SvgMarker } from '../../../utils/mapIcon';
const pixiMarkerContainer = new PIXI.Container();
let markerTextures = {};
class MapRouteMarkers extends PureComponent {
constructor(props) {
super(props);
this.state = { markerTexturesLoaded: false };
}
This is my markerOverlay, I have made it interactive by adding newMarker.interactive = true; and also registered click event on newMarker.
markerOverlay = L.pixiOverlay(utils => {
const map = utils.getMap();
const scale = utils.getScale();
const renderer = utils.getRenderer();
const container = utils.getContainer();
if (map && Object.keys(markerTextures).length !== 0) {
if (container.children.length) container.removeChildren();
const newMarker = new PIXI.Sprite(markerTextures.default);
const newMarkerPoint = utils.latLngToLayerPoint([50.63, 13.047]);
newMarker.x = newMarkerPoint.x;
newMarker.y = newMarkerPoint.y;
newMarker.scale.set(1 / scale);
newMarker.interactive = true;
newMarker.buttonMode = true;
newMarker.on('click', () => {
console.log('gggg');
});
container.addChild(newMarker);
renderer.render(container);
}
}, pixiMarkerContainer);
Here I am redrawing markers
componentDidUpdate() {
if (this.state.markerTexturesLoaded) {
const map = this.props.mapRef;
this.markerOverlay.addTo(map);
this.markerOverlay.redraw();
}
}
Here I am loading baseTexture from svg.
componentDidMount() {
if (Object.keys(markerTextures).length === 0) {
const loader = new PIXI.Loader();
const svgString = ReactDOMServer.renderToString(<SvgMarker iconColor="yellow" />);
const DOMURL = self.URL || self.webkitURL || self;
const img = new Image();
const svg = new Blob([svgString], { type: 'image/svg+xml;charset=utf-8' });
const url = DOMURL.createObjectURL(svg);
img.src = url;
const texture = new PIXI.Texture.from(img);
loader.load((thisLoader, resources) => {
markerTextures = { default: texture };
this.setState({
markerTexturesLoaded: true,
});
});
}
}
render() {
return null;
}
}
export default MapRouteMarkers;
here is the image attached for the same
Do anyone know what I am doing wrong here?
Try to handle the event differently, this worked for me:
newMarker.click = (event) => {
console.log(`Click, info=${JSON.stringify(event.data.global)}`);
};
You can also use the 'hover' and 'mouseout' events the same way for instance.

Resources