Updating component on mouseMove event very slow - reactjs

I'm working on a project where I've got a div that is having it's location update with onMouseMove events of it's parent. The code below shows how I'm doing it:
function Area () {
const [mousePos, setMousePos] = useState({ x: 0, y: 0, })
const handleMouseMove = event => {
setMousePos({
x: event.clientX - event.currentTarget.offsetLeft,
y: event.clientY - event.currentTarget.offsetTop,
})
}
const getStyle = () => { top: mousePos.y, left: mousePos.x, };
return(
<div className='main-area' onMouseMove={handleMouseMove}>
<div style={getStyle()}></div>
</div>
);
}
There are some css rules that help with the positioning etc. but I think they are irrelevant to the question (let me know if not?).
Anyway, my problem is that I'm getting really high CPU usage (~50% with intel i5-3570k) when moving my mouse inside the 'main-area' div, particularly when there are many other children components involved (not sure if this is because of event bubbling or the way the ReactDOM updates?). This is in the development build of react (I've not tried in production build).
Is this way of updating the position just inherently flawed and slow? Or is there a way to modify it slightly to make it faster?
Cheers

I did some refactoring:
function Area () {
const [mousePosX, setMousePosX] = useState(0);
const [mousePosY, setMousePosY] = useState(0);
const handleMouseMove = event => {
setMousePosX(event.clientX - event.currentTarget.offsetLeft)
setMousePosY(event.clientY - event.currentTarget.offsetTop)
}
return(
<div className='main-area' onMouseMove={handleMouseMove}>
<div style={{top: mousePosY, left: mousePosX}}></div>
</div>
);
}
Can you check if the performance is better?

Related

Should I be using useEffect to animate on prop value change with React Native Reanimated?

I need a view to animate in to place when the value of some props change using Reanimated in React Native
I've only been able to find animating on, for example, a user tap using an onPress function to change the value of useSharedValue values. But I can't seem to find how to animate when props change. So currently I'm using useEffect.
export const SomeComponent = ({
top,
left
}) => {
const animation = useSharedValue({
top,
left
});
useEffect(() => {
animation.value = { top, left };
}, [top, left]);
const animationStyle = useAnimatedStyle(() => {
return {
top: withTiming(animation.value.top, {
duration: 1000
}),
left: withTiming(animation.value.left, {
duration: 1000
})
};
});
}
In the above code the props can change value, should I be triggering the animation with useEffect like I am currently? Or is there a better way to do it using just Reanimated.
Interesting. This works? I haven't seen it done this way.
Yes, useEffect is a good way to trigger animations. Usually I would do it like this:
const duration = 1000;
export const SomeComponent = ({
top: topProp,
left: leftProp
}) => {
const top = useSharedValue(topProp);
const left = useSharedValue(leftProp);
useEffect(() => {
top.value = withTiming(topProp, { duration });
left.value = withTiming(leftProp, { duration });
}, [topProp, leftProp]);
const animationStyle = useAnimatedStyle(() => ({
top: top.value,
left: left.value,
}));
return <Animated.View style={animationStyle}>
//...
}
The values are split up so that they're primitives. I don't know if this affects performance - I've never used an object as a shared value before.
The styles come straight from the shared values.
The useEffect waits for props to change and then runs the animations.
I've never tried running the animations within the styles - it seems like it shouldn't work to me, but I could be wrong!

Best way to make a custom smooth scroll and scrollto coexist (ReactJs - Framer motion)

Made a smoothscroll component using framer motion that's working well :
export default function SmoothScroll({ children }: Props) {
const { width } = useWindowSize();
const scrollContainer = useRef() as RefObject<HTMLDivElement>;
const [pageHeight, setPageHeight] = useState(0);
useEffect(() => {
setTimeout(() => {
// added a setTimeout so the page has the time to load and it still fits
const scrollContainerSize =
scrollContainer.current?.getBoundingClientRect();
scrollContainerSize && setPageHeight(scrollContainerSize.height);
}, 500);
}, [width]);
const { scrollY } = useScroll(); // measures how many pixels user has scrolled vertically
// as scrollY changes between 0px and the scrollable height, create a negative scroll value...
// ... based on current scroll position to translateY
const transform = useTransform(scrollY, [0, pageHeight], [0, -pageHeight]);
const physics = { damping: 15, mass: 0.17, stiffness: 55 }; // easing of smooth scroll
const spring = useSpring(transform, physics); // apply easing to the negative scroll value
return (
<>
<motion.div
ref={scrollContainer}
style={{ y: spring }} // translateY of scroll container using negative scroll value
className="app fixed overflow-hidden w-screen"
>
{children}
</motion.div>
<motion.div style={{ height: pageHeight }} />
</>
);
}
The thing is, I'd like to scrollTo sections of my page upon click on the navbar but don't really know how to implement it without removing the smoothScroll ...
Tried the following logic but obviously it did not work as the vanilla scroll has been hijacked :
const scrollToSection = (
e: React.MouseEvent<HTMLLIElement, globalThis.MouseEvent>,
anchor?: string
) => {
e.preventDefault();
if (!anchor) return;
const section = document.querySelector(anchor);
section?.scrollIntoView({ behavior: "smooth" });
};
Is it doable ?

Is there any better way to avoid stale closures in React hooks?

Let's say we have these requirements to implement:
Display the cursor position as the mouse moves while being held down.
Next time the mouse is down, log to the console the last cursor position when the mouse was previously released.
Here is my solution and it works fine. But I'm not quite happy with it:
https://codesandbox.io/s/funny-orla-kdx19?file=/src/App.js
import React, { useState, useEffect } from "react";
import "./styles.css";
const html = document.documentElement;
export default function App() {
const [cursorPos, setCursorPos] = useState({});
useEffect(() => {
const pointerMove = (e) => {
setCursorPos({ x: e.clientX, y: e.clientY });
};
const pointerUp = (e) => {
html.removeEventListener("pointermove", pointerMove);
html.removeEventListener("pointerup", pointerUp);
};
const pointerDown = () => {
console.log("Last position when pointer up", cursorPos);
html.addEventListener("pointermove", pointerMove);
html.addEventListener("pointerup", pointerUp);
};
console.log("registering pointerdown callback");
html.addEventListener("pointerdown", pointerDown);
return () => {
html.removeEventListener("pointerdown", pointerDown);
};
}, [cursorPos]);
return (
<div className="App">
Tracked mouse position: ({cursorPos.x}, {cursorPos.y})
</div>
);
}
As you can see, the cursorPos dependency is crucial. Without it, the pointerDown listener won't read the latest tracked cursor position. However, it causes the effect callback to be called so often since the cursorPos is updated by the pointermove event. And, the pointerdown listener is deregister/register as a result.
This frequent reregistering of the listener may not pose a practical problem. But it's not conceptually a neat way of doing things. It's an overhead to avoid if possible. That's why I'm not satisfied.
I actually come up with some alternatives which didn't make me fulfilled, either.
Solution 1:
https://codesandbox.io/s/mystifying-lamport-nr3jk?file=/src/App.js
We can log down the cursor position every time the mouse is up and store it in another state variable (cursorPosWhenMouseUp) that is to be read by the next mouse down event. Since the mouse up event is not that frequently triggered, the reregistering frequency is acceptable. The downside of this solution is that the cursorPosWhenMouseUp variable is redundant to my eye because the last cursorPos value should be the same. Its sole purpose is to mitigate a technical issue. It's not elegant to let it stick around in the code.
Solution 2:
https://codesandbox.io/s/upbeat-shamir-rhjs4?file=/src/App.js
We can store another copy of cursorPos in a ref from useRef()(e.g. cursorPosRef). Since ref is stable across rendering and no local variable involved, we can rest assured to safely read it from the pointerDown callback. This solution has similar issues to Solution 1 because we have to attentively make sure the cursorPosRef is all the time mirroring the cursorPos value.
Solution 3:
https://codesandbox.io/s/polished-river-psvz7?file=/src/App.js
In regard to the redundancy issue presented above, we can abandon the cursorPos state variable and only use cursorPosRef. We can directly read it in the JSX. We just need a way to force update the component from the pointermove callback. React Hooks FAQ sheds light on this. The problem with this solution is that the forceUpdate stuff is not welcome in the React methodology.
So, is there any better way to achieve these requirements?
I don't see the need of passing cursorPos as dependency, here is my try:
import React, { useEffect, useState } from 'react';
import './styles.css';
const html = document.documentElement;
export default function App() {
const [cursorPos, setCursorPos] = useState({});
const pointerMove = e => {
setCursorPos({ x: e.clientX, y: e.clientY });
};
useEffect(() => {
html.addEventListener('pointerdown', e => {
html.addEventListener('pointermove', pointerMove);
});
html.addEventListener('pointerup', e => {
html.removeEventListener('pointermove', pointerMove);
});
return () => {
html.removeEventListener('pointerdown');
html.removeEventListener('pointerup');
};
}, []);
return (
<div className="App">
Tracked mouse position: ({cursorPos.x}, {cursorPos.y})
</div>
);
}
https://codesandbox.io/s/eloquent-mendeleev-wqzk4?file=/src/App.js
UPDATE:
To keep track of the previous position I would use useRef:
import React, { useEffect, useRef, useState } from 'react';
const html = document.documentElement;
export default function Test() {
const [cursorCurrentPos, setCursorCurrentPos] = useState({});
const cursorPreviousPos = useRef({});
const pointerMove = e => {
setCursorCurrentPos({ x: e.clientX, y: e.clientY });
};
useEffect(() => {
html.addEventListener('pointerdown', e => {
console.log('Last position when pointer up', cursorPreviousPos.current);
html.addEventListener('pointermove', pointerMove);
});
html.addEventListener('pointerup', e => {
cursorPreviousPos.current = { x: e.clientX, y: e.clientY };
html.removeEventListener('pointermove', pointerMove);
});
return () => {
html.removeEventListener('pointerdown');
html.removeEventListener('pointerup');
};
}, []);
return (
<div className="App">
Tracked mouse position: ({cursorCurrentPos.x}, {cursorCurrentPos.y})
</div>
);
}

React - assigning ref via loop to image array

I am still very new to this and probably completely overthinking/overcomplicating things.
I have an array of images which display as expected. As part of the mapping process, I create a new ref for each image, so that I can tap into the 'current' attribute to retrieve height and width, based on which I have a ternary operator to apply some styling. I doubt this is the only or best method to achieve this and I am open to suggestions...
Now to the problem. If I have one image and one ref, the above process works great, however, once I introduce more images and attempt to get 'current' it fails. But I can console log my ref array and I get everything back including the dimensions of the image.
The problem is staring me in the face but I simply cannot figure out what the problem is. It may simply be that I have misunderstood how references and current work.
Any help would be greatly appreciated.
My code as follows:
import React, { useState, useRef, useEffect, createRef } from "react";
import Images from "../images";
const IllustrationList = () => {
const [images, setImages] = useState([]);
useEffect(() => {
// populate with Images from Images Array
setImages(Images);
}, []);
// ref array
const imageRefs = [];
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
useEffect(() => {
if (imageRefs.current) {
setDimensions({
width: imageRefs.current.naturalWidth,
height: imageRefs.current.naturalHeight,
});
}
});
return (
<div className="illustration-gallery">
<ul className="cropped-images pl-0">
{images.map((image, i) => {
const newRef = createRef();
imageRefs.push(newRef);
return (
<li className="li-illustration" key={i}>
<a href={image.image} data-lightbox="mygallery">
<img
src={image.image}
alt="illustrations"
ref={newRef}
className={
dimensions.width > dimensions.height ? "imageSize" : ""
}
/>
</a>
</li>
);
})}
</ul>
</div>
);
};
export default IllustrationList;
I was able to resolve my problem.
I think I was completely overthinking the problem and came up with a much simpler solution. I don't know if this is the best way to achieve this, but it does what I want/need it to.
useEffect(() => {
let landScape = document.querySelectorAll("img");
landScape.forEach(() => {
for (var i = 0; i < Images.length; i++) {
if (landScape[i].naturalWidth > landScape[i].naturalHeight) {
landScape[i].className = "landscape";
}
}
});

How to execute two animations sequentially, using react-spring?

I tried chaining two springs (using useChain), so that one only starts after the other finishes, but they are being animated at the same time. What am I doing wrong?
import React, { useRef, useState } from 'react'
import { render } from 'react-dom'
import { useSpring, animated, useChain } from 'react-spring'
function App() {
const [counter, setCounter] = useState(0)
const topRef = useRef()
const leftRef = useRef()
const { top } = useSpring({ top: (window.innerHeight * counter) / 10, ref: topRef })
const { left } = useSpring({ left: (window.innerWidth * counter) / 10, ref: leftRef })
useChain([topRef, leftRef])
return (
<div id="main" onClick={() => setCounter((counter + 1) % 10)}>
Click me!
<animated.div id="movingDiv" style={{ top, left }} />
</div>
)
}
render(<App />, document.getElementById('root'))
Here's a codesandbox demonstrating the problem:
https://codesandbox.io/s/react-spring-usespring-hook-m4w4t
I just found out that there's a much simpler solution, using only useSpring:
function App() {
const [counter, setCounter] = useState(0)
const style = useSpring({
to: [
{ top: (window.innerHeight * counter) / 5 },
{ left: (window.innerWidth * counter) / 5 }
]
})
return (
<div id="main" onClick={() => setCounter((counter + 1) % 5)}>
Click me!
<animated.div id="movingDiv" style={style} />
</div>
)
}
Example: https://codesandbox.io/s/react-spring-chained-animations-8ibpi
I did some digging as this was puzzling me as well and came across this spectrum chat.
I'm not sure I totally understand what is going on but it seems the current value of the refs in your code is only read once, and so when the component mounts, the chain is completed instantly and never reset. Your code does work if you put in hardcoded values for the two springs and then control them with turnaries but obviously you are looking for a dynamic solution.
I've tested this and it seems to do the job:
const topCurrent = !topRef.current ? topRef : {current: topRef.current};
const leftCurrent = !leftRef.current ? leftRef : {current: leftRef.current};
useChain([topCurrent, leftCurrent]);
It forces the chain to reference the current value of the ref each time. The turnary is in there because the value of the ref on mount is undefined - there may be a more elegant way to account for this.

Resources