Customise cursor pointer using a react component - javascript

I'm trying to customize the cursor pointer so I used a hook to get the mouse position and then I placed the component in absolute in that coordinates.
Here a working code.
There is a flag (USE_PNG) that you can toggle to test if to use a React component or a png (I would prefer the first idea but I'm interested also to the second one if it has some advantages).
USE_PNG = false -> use a React component
As you can see, the code is very simple and it works enough but it has some problems:
when the mouse is on the left side of the window, the left half of the cursor is cut off, but when is on the right then it's not and the horizontal bar appears
it seems not so fluid. Are there some tricks I can use to optimize the code?
USE_PNG = true -> use a png
I tried also to use a png (simpler maybe) but I can't see the cursor anymore
What's the problem?
I use a ref instead of a state and performance have improved.
The first problem remains: the horizontal and vertical scroll when the cursor is on the right or bottom side of the window
I don't think simply hiding the scrollbars is an optimal solution because the window size has changed and the user can scroll.
I think we need to find a cleaner solution.

Edit - Overflowing body (AKA third problem)
if you'll add this to your body tag it should solve it:
margin: 0;
height: 100%;
overflow: hidden
edit - Regarding your second problem
to prevent scroll bars to appear, you can use overflow-y: hidden; (to disable on the x-axis just change the overflow-y to overflow-x, overflow: hidden; for both)
BUT if you would like to enable scrolling but just hide the scrollbar, use the following code:
/* hide scrollbar but allow scrolling */
body {
-ms-overflow-style: none; /* for Internet Explorer, Edge */
scrollbar-width: none; /* for Firefox */
overflow-y: scroll;
}
body::-webkit-scrollbar {
display: none; /* for Chrome, Safari, and Opera */
}
here is a gif of a working example on my browser:
https://imgur.com/a/wOV7car
It doesn't get cut off for me on the right side (see image below). It sounds like the second problem happens because your cursor gets re-rendered every time you move it, and that's a ton of work for your site!
you should remove the style attributes from the Cursor component and adjust the code inside your event listener for a mousemove event.
it will look like this:
onMouseMove = {e => {
const cursor = document.querySelector('.cursor')
cursor.style.top = ׳${e.pageY}px׳
cursor.style.left = ׳${e.pageX}px׳
}}

Flickering:
#01:
Simply introduce a transition style on the Cursor component, eg transition: "all 50ms". It makes the position change much more smoother and cancels the flickering.
#02:
However as Guy mentioned above, handling the cursor's position in a state means a lot of re-rendering for your component which makes you app slower in the end.
I'd recommend making a ref for the cursor and update it directly from an event listener. With that change you can even remove the useMouse hook:
const App = () => {
const containerNodeRef = useRef(null);
const cursorRef = useRef(null)
const updateMouse = (e) => {
// you can directly access the mouse's position in `e`
// you don't even need the useMouse hook
cursorRef.current.style.top = `${e.y - SIZE / 2}px`
cursorRef.current.style.left = `${e.x - SIZE / 2}px`
}
useEffect(() => {
window.addEventListener('mousemove', updateMouse)
return () => {
window.removeEventListener('mousemove', updateMouse)
}
})
return (
<div
ref={containerNodeRef}
style={{
width: window.innerWidth,
height: window.innerHeight,
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
cursor: 'none'
}}
>
<Cursor ref={cursorRef} />
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
const Cursor = forwardRef(({ size = SIZE }, ref) => (
<div
ref={ref}
style={{
position: "absolute",
width: size,
height: size,
backgroundColor: "black",
color: "white",
fontWeight: "bold",
display: "flex",
justifyContent: "center",
alignItems: "center"
}}>
Hello
</div>
)
)
Cut off issue:
Since you're moving around an actual div as your cursor, when it reaches the border of your document, it stretches it to make the div fit -> this is why the document doesn't fit into your window anymore thus the scrollbars are rendered.
You can fix it via css:
body { overflow: hidden }
+1:
I'm not sure how your cursor has to look like in the end, but if it'd be an image, there is an other possible solution. Add this css rule to your container, which loads an image as a cursor and then the browser takes care about the rendering automatically:
<div
ref={containerNodeRef}
style={{
// ...
cursor: 'url("url"), auto'
}}
>
If you'd use this solution then the whole Cursor component and position calculation wouldn't be needed anymore. However the downside is, that it only works with images.

There are already 2 good answers, but I'll add this one too, because other answers are overcomplicating the solution
Flickering
it doesn't matter if you use ref or state, you should just extract
your cursor to separate Component, that way your App component will not rerender
Scrollbars
as other anwers mentioned, using body { overflow: hidden; } will solve this problem, but partially. Your cursor is trying to go beyond page size, hence page is showing scrollbars, adding limitation for cursor position will solve this: cursor.y = Math.max(0, Math.min(currentPositionY, page.width)) (pseudo-code) now cursor.y will not exceed 0 or page.width

Related

React how to get the scroll amount on an element?

I've made a react application where I have a component that has a scroll bar at the bottom of it. It's a horizontal scroll bar and I want to get how much the user has scrolled in terms of x position.
When I look it up on internet, such as this question, I usually see results that show how to get the scroll amount on the window.
However I'm looking for the scroll amount on a component that I have created called <Timeline/>, not the window.
Here is what my component looks like on the console:
When a user scrolls on it, I need to see where exactly the current visible part of it stands so I can make calculations with it.
On a given time only a certain part of the div is visible (As big as the window) and the rest is hidden on both left and right. I want to see, when scrolled, how far the user has scrolled and how much is invisible on the left side of the screen.
How can I get that?
This is one of those situations where you use a ref. Then you can get the scrollLeft from the element. Here's a basic example:
const { useRef } = React;
const Example = () => {
const elementRef = useRef(null);
const showScroll = () => {
console.log(`scrollLeft = ${elementRef.current.scrollLeft}`);
};
return (
<div>
<input type="button" value="Show Scroll" onClick={showScroll} />
<div className="container" ref={elementRef}>
<div className="wide" />
</div>
</div>
);
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example />);
.container {
border: 1px solid black;
overflow: auto;
}
.wide {
width: 400vw;
height: 50px;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
If you want to access the scrolling position during an event handler, then simply using a ref and element.scrollLeft is the correct way.
But note that this will not trigger a rerender, so it will not work if you want to use the scrolling position to determine what/how to render.
In such cases it depends on what you are trying to do. If for instance you want to use the scrolling position in order to position another element, I would first try doing it with CSS only, as it's going to be a lot more efficient. Otherwise you can use an event listener on the scroll event.
const [scrollPosition, scrollPosition] = React.useState(0);
const handleScroll = () => {
setScrollPosition(elementRef.current.scrollLeft);
}
return (
<div onScroll={handleScroll} ref={elementRef}>
[...]
</div>
);

p5.js: Render canvas on top of a specific div and make it not block anything ( whisps following cursor )

I'm trying to use this codepen for my project:
https://codepen.io/scorch/pen/QQxEdr
( in order to make it follow the cursor go to line 108 and change this if(mouseIsPressed){ to this if(!mouseIsPressed){ )
but when I edit this codes into my project it puts it below everything, all the existing website stays the same and when you scroll to the bottom there's an area with that result.
I want to make it appear on top of one of my sections. that's 1.
second I want after it will appear on top of the specific section that it will not block the existing background and wont block mouse interactions with the section, just make the whisps appear and follow the mouse ( when the mouse hover on that specific section only ).
if you need any additional information, comment and I'll add it.
The solution I got that works perfect for me:
as #Kevin Workman suggested, I did needed the parent() function and more:
I created a container (div) with and id="container" and than in the script part I edited the setup function to this:
function setup() {
(1) var canvas = createCanvas(window.innerWidth, window.innerHeight);
(2) canvas.parent('container');
frameRate(conf.FRAME_RATE)
pos = {x: Math.random() * window.innerWidth + 1, y:Math.random() * window.innerHeight + 1}
}
The edited lines are (1) and (2).
this will make the whisps from the code be inside a div or any container inside your existing html.
I wanted that will be on top of an existing elements without interrupting with the mouse events, so I also added this code to my CSS file:
.container{
width: 100%;
height: 100%;
pointer-events: none;
position: absolute;
z-index: 0;
top: 0;
left: 0;
}
And I added z-index: 2; to all the rest of the elements in the section in order to make them look above the whisps when the mouse hover them.
for me I Wanted it to be in the first section of my website, you may want it in a different situation, just most the container position with the CSS code and the whisps will "follow".

Keep scroll position when adding element to the top

I'm trying to create a calendar that can be infinitely scrolled to the left and to the right. It has to load new content dynamically as the user scrolls forward (easy) or backward (the problem is here).
When I add content to the end of the page, it works fine - the scrollbar adjusts to the new container.scrollWidth. But when I have to add content to the start, the whole calendar moves to the right in a huge 400px jump, because the container.scrollLeft property hasn't changed and there's now a new element at the start.
I'm trying to mitigate this by increasing the container.scrollLeft by 400px - width of the newly created element. This approach works, but only when scrolling with mousewheel (shift+mousewheel to scroll sideways) or mobile touchscreen.
If I use my mouse to drag the scrollbar, it kind of glitches out - my guess is it keeps trying to scroll to the old scrollLeft position and disregards that I increased it.
Could you please suggest a way to achieve this behavior for all ways of scrolling?
It would also be great if you could just point me to a site that uses this technique so I could investigate myself.
Here's my semi-working example:
function Container() {
const rectangleWidth = 400;
const container = React.useRef(null);
const [leftRectangles, setLeftRectangles] = React.useState([0]);
const [rightRectangles, setRightRectangles] = React.useState([1, 2, 3, 4]);
// When we just rendered a new rectangle in the left of our container,
// move the scroll position back
React.useEffect(() => {
container.current.scrollLeft += rectangleWidth;
}, [leftRectangles]);
const loadLeftRectangle = () => {
const newAddress = leftRectangles[0] - 1;
setLeftRectangles([newAddress, ...leftRectangles]);
};
const loadRightRectangle = () => {
const newAddress = rightRectangles[rightRectangles.length - 1] + 1;
setRightRectangles([...rightRectangles, newAddress]);
};
const handleScroll = (e) => {
// When there is only 100px of content left, load new content
const loadingOffset = 100;
if (e.target.scrollLeft < loadingOffset) {
loadLeftRectangle(e.target);
} else if (e.target.scrollLeft > e.target.scrollWidth - e.target.clientWidth - loadingOffset) {
loadRightRectangle(e.target);
}
};
return (
<div
className="container"
onScroll={handleScroll}
ref={container}
>
{leftRectangles.map((address) => (
<div className="rectangle" key={address}>
{address}
</div>
))}
{rightRectangles.map((address) => (
<div className="rectangle" key={address}>
{address}
</div>
))}
</div>
);
}
ReactDOM.render(<Container />, document.querySelector("#app"))
.container {
display: flex;
overflow-x: scroll;
}
.rectangle {
border: 1px solid #000;
box-sizing: border-box;
flex-shrink: 0;
height: 165px;
width: 400px;
font-size: 50px;
line-height: 165px;
text-align: center;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>
I think this is a case where one should not use native browser scroll areas. If you think about it, scrollbars have no meaning if they continue to get longer infinitely in both directions. Or their only meaning is "area which has been viewed". A scrollbar is not a good metaphor for an infinite area, and therefore a scrollbox is a poor way to do what you want to do here.
If you think of it another way, you are only showing a set of months which fit within a known screen width. The way I would approach this would be to absolute position each calendar inside a container and calculate their positions in a render loop based on where in a virtual view the user is shuttling. This also would allow you to remove calendars once they go too far off screen and create/buffer them offscreen for display before the user scrolls to their position. It would prevent you from having an arbitrarily wide object which would eventually slow down rendering.
One approach to this would be to simply number each month since 1970 and treat your view as having a fractional position viewX along this line. The month for x should have a position of viewX - x. When the user is dragging you move viewX inversely and reposition the buffered elements. When the user stops dragging, you take the last velocity and slow it until viewX - x is an integer.
This would avoid most cross-browser issues and offset problems. It only requires a render loop while the display is in motion.
Use the read-write scrollLeft to get the scroll position prior to dynamically adding content then use the .scrollLeft method to reposition the scroll position back to where you want it. You might need fire off a dialog displaying an indeterminant progress indicator ( or simply display text "working..." ) which displays during the process to prevent janking.
The trick for cross browser functionality is that dialog element which is well known to be challenging regarding consistency across device types so I would recommend using a UI library for your dialog or build your own but keep it super simple. That progress indicator will be the fix for screen jank.
Another feature to consider regarding janking would be CSS transitions where the addition of content (e.g. a block element) would gradually fade/animate in to the viewport.

Resizable div that doesn't change "left" property

I'm trying to use jQuery UI's resizable() method with flexbox (Maybe that's the problem? But I'd say not likely).
I have 3 columns and I want the left and right columns to be resizable and have the center column take up any remaining space. My CSS looks like this:
.container {
display: flex;
}
.col-left,
.col-right {
width: 200px;
}
.col-center {
flex: 1;
}
The left column works fine with this jquery:
$('.col-left').resizable({
handles: 'e'
});
And the only property that gets added when resized is width, but when I use this for the right column:
$('.col-right').resizable({
handles: 'w'
});
The width AND left properties get added. The left property really messes up the layout. It pulls the left column over the center column while also squishing it with the width, so the end result is really wonky. In my inspector, if I remove the left property it seems to work just fine.
I've read through the documentation here a few times, but I don't see anywhere how I would turn this off.
Here's a pen where I've recreated the problem:
http://codepen.io/dustindowell/pen/NPyaBL
A workaround would be to listen for the resize event and just overwrite the CSS but this seems bad.
$('.col-right').resizable({
handles: 'w'
}).on('resize', function() {
$(this).css({
left: 0
});
});
UPDATE:
After some digging around I found out that this is a jQuery UI bug.
Bug Ticket:
http://bugs.jqueryui.com/ticket/4985#comment:1
I've encountered the same issue too.
I found another work-around: Overriding inline styles in CSS. Since resizable() changes the inline styles, you can do something along the lines of:
.col-right[style]{
left:0 !important;
}
This will override it permanently; you will not not need to call .css() every time the size changes.

Chrome Extension Scrollbar Placement

After dabbling in Chrome Extensions I've noticed that when the data inside the Page Action gets to a certain point the scroll bars automatically attach themselves to the popup, this I expect. However, instead of pushing the content to the left of the scroll bar it overlays the content causing a horizontal scrollbar to become active. I ended up just adding a check on my data and applying a css class to push the content to the left more to run parallel to the scroll bar and beside it not under it. What is the correct way to handle this besides my hackish solution?
I was wondering this myself too. Currently I just don't put anything important closer than 20px to the right side of a popup and disable horizontal scrollbars:
body {overflow-x:hidden;overflow-y:auto;}
So when a vertical scrollbar appears the content at least doesn't jump.
Perhaps you need to specify a width on the scrollbar.
::-webkit-scrollbar {
width: 42px; //Do not know actual width, but I assume you do
}
I haven't found a way to do this that isn't a hack, but here's the simplest hack I could think of:
<script type="text/javascript">
function tweakWidthForScrollbar() {
var db = document.body;
var scrollBarWidth = db.scrollHeight > db.clientHeight ?
db.clientWidth - db.offsetWidth : 0;
db.style.paddingRight = scrollBarWidth + "px";
}
</script>
...
<body onresize="tweakWidthForScrollbar()">
The idea is to detect whether the vertical scrollbar is in use, and if it is, calculate its width and allocate just enough extra padding for it.

Categories