I'm creating a portfolio website using React and I decided to replace the default cursor with a personalized one. I used GSAP TweenMax to animate my custom mouse.
Everything works fine except for some weird cases.
At certain times, when the user clicks on an element or scrolls through a component, the mouse position animates by itself to the 0,0 position (upper left corner of the screen).
I tried console.log in the only function that animated the mouse position but It's not even fired when I click or scroll. (See gifs in the bottom for illustration of the problem)
This is the mouse function used to animate the position of the custom mouse.
export const handleMouseMove = (bigMouseCircle, smallMouseCircle) => e => {
TweenMax.to(bigMouseCircle.current, 0.3, {
x: e.pageX - 16,
y: e.pageY - 16
});
TweenMax.to(smallMouseCircle.current, 0.1, {
x: e.pageX - 4,
y: e.pageY - 4
});
};
Where its used:
App.js
(...)
useEffect(() => {
document.addEventListener(
"mousemove",
handleMouseMove(bigMouseCircle, smallMouseCircle)
);
(...)
return () => {
document.removeEventListener(
"mousemove",
handleMouseMove(bigMouseCircle, smallMouseCircle)
);
};
(...)
});
(...)
<div className="cursor">
<div ref={bigMouseCircle} className="cursor__ball cursor__ball--big" />
<div
ref={smallMouseCircle}
className="cursor__ball cursor__ball--small"
/>
</div>
(...)
Other mousehandlers are used (mousedown, mouseup, mouseout, mouseenter, mouseover) but none are fired when the bug occurs.
GIF of the bug:
preview 1: https://i.imgur.com/YvJC3gM.gif
preview 2: https://i.imgur.com/yyd7ZrQ.gif
Live website: http://adamchami.com/
Related
I need to know how to assign a function to an expo-av Video component's onPress event, or how to make a useRef reference trigger an onPress event on an expo-av Video component.
I have a lot of requirements for a React Native video component and I am trying to work out how to best achieve them all. The main issue is the user needs to be able to double-tap the video and have an animation fire. We have a Pressable component that renders over the video that handles a double tap. The animation works fine. But the problem is there's also a requirement that the video uses native controls, and the Pressable blocks those controls.
I have two strategies but I haven't been able to achieve either. Plan A, someone on StackOverflow tells me that expo-av Video components have some kind of onPress function not listed in their docs, and all I have to do is trigger the double tap animation using that function. So far I've tried onPress, onTouchEnd, onTouchStart, and onClick, and all are undefined and not listed in the docs. No dice.
Plan B, I can create a Pressable component that renders over the Video component that is short enough not to block the native controls, but tall enough to catch all other taps. Then, when a user double-taps the Pressable, the animation fires and everything's good. This is what I initially submitted as a solution, but our QA folks said they need the native controls to show on the first press. Normally the native controls are not visible and the expo-av Video shows them on any press, but the Pressable blocks those presses, and I cannot find anything in the docs that allows you to referentially trigger the expo-av press event. I have a useRef() working and have tried videoRef.current.press, videoRef.current.click, and videoRef.current.setStatusAsync({ progressUpdateIntervalMillis: 0 }). No dice. I just need the control buttons to show up like they normally do on a tap, nothing else.
So in summary, does anyone A: Know how to assign a function to the expo-av Video component's onPress event? or B: Know how to make a useRef reference trigger an onPress event on an expo-av Video component? or C: Some other solution I haven't thought of?
Here are some code snippets if it helps:
The Video instantiation:
<Video
ref={(video) => videoRef.current = video}
style={[styles.postMedia, { width: videoDimensions.width, height: videoDimensions.height }]}
source={{ uri: videoUrl }}
rate={1.0}
volume={1.0}
isMuted={isMuted}
resizeMode={Video.RESIZE_MODE_CONTAIN}
shouldPlay={shouldPlay || screenIsFocussed && (keyInView === _id)}
isLooping
useNativeControls
onError={(e) => setVideoUrl(`${getS3BaseUrl(video.s3Bucket)}/${video.url}`)}
onReadyForDisplay={(response) => {
const { width, height } = response.naturalSize;
if (width >= height) return setVideoDimensions({ width: screenWidth, height: screenWidth * height / width });
if (height > width) return setVideoDimensions({ height: screenWidth, width: screenWidth * width / height });
}}
/>
The handleDoubleTap function:
let lastTap = null;
const handleDoubleTap = () => {
const now = Date.now();
const DOUBLE_PRESS_DELAY = 300;
if (lastTap && (now - lastTap) < DOUBLE_PRESS_DELAY) {
toggleLike();
} else {
if (videoRef) videoRef.current.setStatusAsync({ progressUpdateIntervalMillis: 0 }); // <-- insert something that actually works here
lastTap = now;
}
};
The Pressable component instantiation:
{(video || image) && (
<Pressable
onPress={handleDoubleTap}
style={video ? {
...styles.videoLikePostFromFeedPressable,
height: IS_IOS ? DEVICE_WIDTH * 0.75 : DEVICE_WIDTH * 0.66,
} : {
...styles.likePostFromFeedPressable,
height: DEVICE_WIDTH,
}}
>
{renderOverlay()} {/* <-- This is just the animation render, works fine */}
</Pressable>
)}
I'm trying to animate a stack of cards moving up as the cards on the top get swiped off the deck but I cannot make the animation to run.
I try to run the animation when the number of cards on the deck changes
useEffect(() => {
LayoutAnimation.spring();
},[currentCard]);
Also, I try to run it on the function that is called on swiped completion:
function onSwipeComplete(direction) {
const item = data[currentCard];
direction === 1 ? onSwipeRigt(item) : onSwipeLeft(item);
pan.setValue({ x: 0, y: 0 });
setCurrentCard(index => index + 1);
LayoutAnimation.configureNext({
duration: 5000,
update: {
type: LayoutAnimation.Types.spring,
springDamping: 0.7
}
});
}
On the attached sandbox you will find both solutions implemented but of course I try to use one at the time.
To see the problem jump into the sandbox if you swipe the top card left or right you will notice that the cards bellow snap to the top, instead I want to slowly animate it to the top.
https://codesandbox.io/s/react-native-deckj-z1nwb?file=/src/Deck.js
I'm trying to put Material-UI Button inside Popup (Leaflet library).
When I'm doing it outside Popup => everything works fine, each button click triggers ripple effect.
When I'm trying to put the same code inside marker popup, something destroyes (overrides) ripple style and its's no longer visible.
Is it possible to somehow fix this problem?
Here's my codesandbox: https://codesandbox.io/s/react-leaflet-ripple-ivsxy
I have two buttons here:
(1) Outside the popup - works OK
(2) In the popup (popup is visible after click on marker) - button is visible but ripple effect is broken
Regarding why, in Material-UI ripple handler is triggered on mousedown event (from ButtonBase.js):
const handleMouseDown = useRippleHandler('start', onMouseDown);
but in leaflet mousedown event inside popup doesn't propagate outside the map in leaflet and this is by design (refer this thread for some details), that's the reason why ripple effect is missing.
To circumvent this behavior, one option would be to restore mousedown event propagation as demonstrated in the following Popup custom component:
class MyPopup extends Component {
componentDidMount() {
const { map } = this.props.leaflet;
map.on("popupopen", e => {
L.DomEvent.off(
this.getWrapper(),
"mousedown",
L.DomEvent.stopPropagation
);
});
map.on("popupclose", e => {
L.DomEvent.on(this.getWrapper(), "mousedown", L.DomEvent.stopPropagation);
});
}
getWrapper() {
return document.querySelector(".leaflet-popup-content-wrapper");
}
render() {
return <Popup {...this.props} />;
}
}
Here is an updated CodeSandbox
Those days I watch in youtube great video how to create custom cursor CSS and JS.
So I wrote this code in ReactJS and create almost similar custom cursor effect.But there and small bug when user scroll (without move mouse only scroll) my custom cursor not moving.
Codesandbox
Please help how to solve this issue.
state = {
xMain: 0,
yMain: 0,
expand: false
};
componentDidUpdate() {
if (this.state.expand) {
setTimeout(() => {
this.setState({ expand: false });
}, 500);
}
}
handleMouseMove = e => {
const { pageX, pageY } = e;
this.setState({
xMain: pageX,
yMain: pageY
});
};
expandCursor = () => {
this.setState({
expand: true
});
};
render = () => {
const { xMain, yMain } = this.state;
return (
<div
className="container"
onClick={this.expandCursor}
onMouseMove={e => this.handleMouseMove(e)}
>
<div
className={this.state.expand ? "cursor expand" : "cursor"}
style={{
left: xMain,
top: yMain
}}
/>
</div>
);
};
I have write this demo.
It has a little delay while scrolling, which is about the performance issue. I haven't come up the way to fix.
Anyway, still hope this helps!
In my opinion, to achieve this needs to consider 2 steps:
Add a scroll event listener
In your code, to add onScroll in .container will not working, because when you scroll, only body element can detect this event.
Hence we have two ways available:
a. Bind vanilla Javascript eventList in lifecycles like this.
b. Make your element scrollable, and bind onScroll in JSX.
In my code I tried b method. The key is to create a parent wrapper(position: relative; width: 100%;) for container, and add the style height: 100%; to html, body, #root. I have to say I don't know the real reason why add height: 100%; would make this work. I learn this by react-sprint example.
Get the scroll offset for the event, and calculate the position of cursor.
Once the onScroll works, the second thing is : how to calculate the cursor position form scroll event?
I notice that previous wrap approach made the pageX, pageY value changes.
The value is not the offset to the document anymore, it's the offset to the viewport. (I guess it only count in the scroll offset with body.)
And we can use scrollTop value of scroll event to get the scroll offset from the document:
scrollTop
The Element.scrollTop property gets or sets the number of pixels that an element's content is scrolled vertically.
https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTop
Last step, defined the x,y properties for scroll event and mouse event individually, then sum up the scrollX and mouseY as left; sum up the scrollY(the scrolled height) and mouseY (the y offset of mouse from viewport) as top.
You need to handle the scroll event. Try something like this.
componentDidUpdate() {
if (this.state.expand) {
setTimeout(_ => {
this.setState({ expand: false });
}, 500);
}
window.addEventListener('scroll', this.handleScroll);
}
handleScroll = e => {
console.log('x');
this.setState({
xMain: e.pageX,
yMain: e.pageY
});
};
In react native there is a scrollTo function which programatically scrolls to a given x and y coordinate, animated or not.
https://facebook.github.io/react-native/docs/scrollview.html#scrollto
scrollTo({x: 0; y: 0; animated: true})
In some cases this is useful, but there are some cases in which I only want to scroll down for 100 pixels or scroll up for 200 pixels regardless of the position x,y on the screen. Is there a way to programatically do this in react-native currently.
You can keep track of your current X and Y scroll position in state by doing something like what is mentioned in this answer. So for example:
<ScrollView
ref={(scrollView) => { this._scrollView = scrollView; }}
onScroll={this.handleScroll}
/>
handleScroll = (event: Object) => {
this.setState({scrollX: event.nativeEvent.contentOffset.x, scrollY: event.nativeEvent.contentOffset.y})
}
Then you can move your scroll using scrollTo and the current position +/- whatever you wish. Example:
moveScroll = () => {
this._scrollView.scrollTo({y: this.state.scrollY + 100});
}
Do read through the linked StackOverflow answer though to understand the caveats of how onScroll works. I'm also not checking to see if you hit the end/beginning of the scroll window so check your bounds as well.