Search code examples
jqueryreactjsmouseevent

I'm trying to get this cursor effect on react


I'm new to react and I'm trying to get this cursor effect on my landing page, but I haven't been able to make it without using jquery... I have looked at react SyntheticEvents, but I don't know how to use them right.

Here is the effect that I want to achieve, but in react:

$(document)
  .mousemove(function(e) {
    $('.cursor')
      .eq(0)
      .css({
        left: e.pageX,
        top: e.pageY
      });
    setTimeout(function() {
      $('.cursor')
        .eq(1)
        .css({
          left: e.pageX,
          top: e.pageY
        });
    }, 100);
  })
body{
  background:black;
}

h1{
  color:white;
}

* {
    cursor: none;
}

.cursor {
    position: fixed;
    height: 10px;
    width: 10px;
    border-radius: 50%;
    transform: translateX(-50%) translateY(-50%);
    pointer-events:none;
}
.cursors .cursor:nth-child(1) {
    background-color: #3a26fd;
    z-index: 100002;
}
.cursors .cursor:nth-child(2) {
    background-color: #f3f3f3;
    z-index: 100001;
    height: 9px;
    width: 9px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="cursors">
<div class='cursor'></div>
<div class='cursor'></div>
<div class='cursor'></div>
</div>
<h1>Custom cursor</h1>


Solution

  • First, a couple of notes:

    I'm assuming you use Babel to transpile JSX and to be able to use ES2015 arrow functions. If not, please update your question and I'll update my answer.

    Next, you don't need three elements of the cursor class. The code suggests you only use two elements. The blue one which I'll call mainCursor and the white one - trailingCursor.

    Also, I didn't implement the eq function from jQuery, but in this example we are sure document.getElementByClassName will return 2 elements, so I'm not including checks for null.


    The React way of implementing requested behavior is:

    1. Create a cursor element that uses the position saved in state to render itself
    2. Attach onMouseMove listener to the parent element inside which the mouse is moved
    3. When the mouse is moved it will call the handler function, inside which we save the mouse position in state using setState, which triggers a rerender of the cursor element using the new mouse position

    That being said, here's the ported version of the functionality from your question. I've provided a runnable snippet.

    class App extends React.Component {
      // we keep track of x and y coordinates for the blue circle - the main one
      // and the trailing circle - the white one
      // for simplicity, they are initialzed to (0, 0), the top left corner of the viewport
      state = {
        xMain: 0,
        yMain: 0,
        xTrailing: 0,
        yTrailing: 0,
      }
      
      handleMouseMove = (e) => {
        // Using pageX and pageY will cause glitching when you scroll the window down
        // because it measures the distance from the top left rendered corner, not
        // top left visible corner
        const { clientX, clientY } = e;
    
        // we set the main circle coordinates as soon as the mouse is moved
        this.setState({
          xMain: clientX,
          yMain: clientY,
        }, () => {
          // this callback is invoked after the first setState finishes
          // 
          // here we schedule saving the trailing coordinates in state 100ms  
          // after the main coordinates have been set to simulate the trailing
          setTimeout(() => {
            this.setState({
              xTrailing: clientX,
              yTrailing: clientY,
            })
          }, 100);
        })
      }
    
      render = () => {
        // we retrieve coordinates from state
        const {
          xMain,
          yMain,
          xTrailing,
          yTrailing
        } = this.state;
    
        return (
          // we need a container that has a definite height, 800px in my example
          // this is to make sure it leaves enough room for mouse movement to happen and trigger the event handler
          // 
          // also, you don't need the event listener on both your cursor elements, only on the container
          <div
            className='container'
            onMouseMove={e => this.handleMouseMove(e)}
          >
            <div className='cursors'>
              // this below is the main cursor
              // we set its style inline with coordinates from state
              <div 
                className='cursor'
                style={{ 
                  left: xMain, 
                  top: yMain,
                }}
              />
              
              // this below is the trailing cursor
              <div 
                className='cursor'
                style={{ 
                  left: xTrailing, 
                  top: yTrailing,
                }}
              />
            </div>
          </div>
          )
      }
    }
    
    ReactDOM.render(<App />, document.getElementById('root'));
    * {
        cursor: none;
    }
    
    .container {
      background: black;
      min-height: 800px;
    }
    
    .cursor {
        position: fixed;
        height: 10px;
        width: 10px;
        border-radius: 50%;
        transform: translateX(-50%) translateY(-50%);
        pointer-events:none;
    }
    
    .cursors .cursor:nth-child(1) {
        background-color: #3a26fd;
        z-index: 100002;
    }
    .cursors .cursor:nth-child(2) {
        background-color: #f3f3f3;
        z-index: 100001;
        height: 9px;
        width: 9px;
    }
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>    
      </head>
      
      <body>
        <div id="root"></div>
      </body>
    </html>