4

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:

    1. 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
    2. 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

enter image description here


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.

enter image description here

whitecircle
  • 237
  • 9
  • 34

3 Answers3

1

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://i.stack.imgur.com/q7gtO.jpg


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׳
}}

not getting cut off

Guy Nachshon
  • 2,411
  • 4
  • 16
  • Thank you, I edit my main message because you suggest me how to solve the second problem and it works but the first one remains. Can you help me? – whitecircle Jan 20 '22 at 20:16
  • sure. but i still cant reproduce your problem (as you can see in the image in my comment) – Guy Nachshon Jan 20 '22 at 20:27
  • I've edited my answer to fix the first one also :) – Guy Nachshon Jan 20 '22 at 20:34
  • are you using a mac? I think you can reproduce the problem by enabling always the scroll bars (System preferences -> General -> Show scroll bars -> Always). I'm not sure but I think that this is also the default behavior on Window – whitecircle Jan 20 '22 at 20:36
  • ho. cool! lmk if my solution worked – Guy Nachshon Jan 20 '22 at 20:38
  • your idea is interesting but too general because applied to the body node. can I use it only on the parent div of the App component (so only to the div with ref `containerNodeRef`). Image if my website have a router and I need this behavior only in a rout and not in all the site – whitecircle Jan 20 '22 at 20:43
  • yeah of course. I don't know the architecture of your site, so I assumed that it was a one-pager. basically you can use it on any element that overflows – Guy Nachshon Jan 20 '22 at 20:47
  • You are right. I tried assigning an id to the div and then applying the CSS rules to that element but nothing changed. I still see the scrollbars – whitecircle Jan 20 '22 at 20:51
  • that's odd. let me check – Guy Nachshon Jan 20 '22 at 20:51
  • @whitecircle I changed my settings as you suggested and was able to reproduce your problem. however, my solution solved it (I added a gif showing it to my answer) – Guy Nachshon Jan 20 '22 at 20:56
  • There is another problem Guy, take a look at the main message. I added a gif – whitecircle Jan 20 '22 at 21:14
  • @whitecircle I've edited my answer. it should work now seamlessly – Guy Nachshon Jan 21 '22 at 06:45
  • Thank you again but I can't assign rules to the body and assigning them to the div seems not to work. I think I will adjust the top and left coordinates and crop them to the window dimensions..It's the only idea it comes to me – whitecircle Jan 21 '22 at 07:47
  • I'm not sure I'm following you - you want to restrict that from the cursor itself? Its hard to help you without your final code (I belive the fiddle is just a minimum reproduction) – Guy Nachshon Jan 21 '22 at 12:08
1

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.

Daniel Sz
  • 96
  • 5
  • Thank you Daniel, I edit my main message because you suggest me how to solve the second problem and it works but the first one remains. Can you help me? – whitecircle Jan 20 '22 at 20:16
  • No problem! I've edited my answer with two possible solutions for the scrollbar flickering! – Daniel Sz Jan 21 '22 at 21:41
  • Hi Daniel, I tried using a png, the problem is with png larger than 32px (example of ref: https://stackoverflow.com/questions/6648279/cursor-256x256-px-size) – whitecircle Jan 21 '22 at 22:30
0

There are already 2 good answers, but I'll add this one too, because other answers are overcomplicating the solution

  1. 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

  1. 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

Medet Tleukabiluly
  • 11,662
  • 3
  • 34
  • 69