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