我试图让这个光标效果对反应

I'm trying to get this cursor effect on react

我是 React 的新手,我正在尝试在我的登录页面上获得这种光标效果,但我无法在不使用 jquery 的情况下实现...我已经看过反应 SyntheticEvents,但我不知道如何正确使用它们。

这是我想要实现的效果,但是在反应中:

$(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>

首先,注意几点:

我假设您使用 Babel 来转换 JSX 并能够使用 ES2015 箭头函数。如果没有,请更新您的问题,我会更新我的答案。

接下来,你不需要游标class的三个元素。该代码建议您只使用两个元素。蓝色的我称之为 mainCursor 和白色的 - trailingCursor.

此外,我没有实现 jQuery 中的 eq 函数,但在这个例子中我们确定 document.getElementByClassName 会 return 2 个元素,所以我不包括 null 检查。


React 实现请求行为的方式是:

  1. 创建一个 cursor 元素,该元素使用 state 中保存的位置来呈现自身
  2. 将 onMouseMove 侦听器附加到鼠标在其中移动的父元素
  3. 当鼠标移动时,它会调用处理函数,在其中我们使用 setState 将鼠标位置保存在 state 中,这使用新的鼠标位置
  4. 触发 cursor 元素的重新渲染

话虽如此,这是您问题中功能的移植版本。我提供了一个可运行的片段。

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>

我的解决方案使用 requestAnimationFrame 而不是 setTimeout 和 refs 来实现更短的绘制时间和更流畅的动画,此外,使用变换而不是绝对位置通常会提供更好的 FPS。

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      mouseX: 0,
      mouseY: 0,
      trailingX: 0,
      trailingY: 0,
    };
    this.cursor = React.createRef();
    this.cursorTrailing = React.createRef();
    this.animationFrame = null;
  }
  
  componentDidMount() {
    document.addEventListener('mousemove', this.onMouseMove);
    this.moveCursor();
  }
  
  componentWillUnmount() {
    document.removeEventListener('mousemove', this.onMouseMove)
    cancelAnimationFrame(this.animationFrame);
  }
  
  onMouseMove = (evt) => {
    const { clientX, clientY } = evt;
    this.setState({
      mouseX: clientX,
      mouseY: clientY,
    });
  }
  
  moveCursor = () => {
    const { mouseX, mouseY, trailingX, trailingY } = this.state;
    const diffX = mouseX - trailingX;
    const diffY = mouseY - trailingY;
    //  Number in expression is coeficient of the delay. 10 for example. You can play with it. 
    this.setState({
      trailingX: trailingX + diffX / 10,
      trailingY: trailingY + diffY / 10,
    },
    () => {
    // Using refs and transform for better performance.
      this.cursor.current.style.transform = `translate3d(${mouseX}px, ${mouseY}px, 0)`;
      this.cursorTrailing.current.style.transform = `translate3d(${trailingX}px, ${trailingY}px, 0)`;
      this.animationFrame = requestAnimationFrame(this.moveCursor);
    });
  }
  
  render = () => {
    return (
      <div className="container">
        <div className="cursors">
          <div 
            className="cursor"
            ref={this.cursor}
          />
          <div 
            className='cursor'
            ref={this.cursorTrailing}
          />
        </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>