You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Konva:移除上方图形后onMouseOver未触发的问题解决

React Konva重叠矩形悬停触发问题解决

我有一个包含两个重叠<Rect/>组件的React Konva项目:

  • 底部的<Rect/>组件在鼠标悬停时会改变颜色,代码如下:
export const BottomSquare = () => {
  const [color, setColor] = useState("green");
  return (
    <Rect
      width={100}
      height={100}
      opacity={1}
      fill={color}
      onMouseOver={() => {
        setColor("blue");
      }}
      onMouseLeave={() => {
        setColor("green");
      }}
    />
  );
};
  • 上方的<Rect/>组件(尺寸小于底部组件)点击后会移动到远离底部组件的位置,代码如下:
export const TopSquare = () => {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  return (
    <Rect
      {...position}
      width={50}
      height={50}
      opacity={1}
      fill="grey"
      onClick={(event) => {
        setPosition({ x: 100, y: 100 });
      }}
    />
  );
};

悬停在上方<Rect/>时不会触发底部组件的变色逻辑,这符合预期。但点击上方组件使其移开后,光标处于底部组件区域内时,onMouseOver逻辑不会触发,除非移动至少1像素光标,这更像是onMouseMove的行为。尝试使用onMouseEnter和onMouseMove,问题依旧。


解决方案

这个问题的核心是Konva的事件系统依赖于鼠标位置的变化来触发悬停类事件。当上方矩形移开后,光标位置没有改变,Konva不会主动检测当前光标下的新元素,自然不会触发onMouseOver或onMouseEnter。

我们可以通过两种思路解决:


思路一:手动触发底部组件的悬停事件

通过组件间通信,在上方矩形移动后主动触发底部组件的悬停逻辑:

修改后的代码
BottomSquare.tsx
import { useRef, useState, forwardRef, useImperativeHandle } from "react";
import { Rect } from "react-konva";

// 使用forwardRef暴露内部方法给父组件
const BottomSquare = forwardRef((_, ref) => {
  const [color, setColor] = useState("green");
  const rectRef = useRef<Rect>(null);

  const handleHover = () => {
    setColor("blue");
  };

  const handleLeave = () => {
    setColor("green");
  };

  // 暴露触发悬停的方法
  useImperativeHandle(ref, () => ({
    triggerHover: () => {
      if (rectRef.current) {
        rectRef.current.fire("mouseenter");
      }
    },
  }));

  return (
    <Rect
      ref={rectRef}
      width={100}
      height={100}
      opacity={1}
      fill={color}
      onMouseOver={handleHover}
      onMouseLeave={handleLeave}
      onMouseEnter={handleHover} // 同时监听mouseenter增强兼容性
    />
  );
});

export default BottomSquare;
TopSquare.tsx
import { useState } from "react";
import { Rect } from "react-konva";

interface TopSquareProps {
  onMove: () => void;
}

export default function TopSquare({ onMove }: TopSquareProps) {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  const handleClick = () => {
    setPosition({ x: 100, y: 100 });
    onMove();
  };

  return (
    <Rect
      {...position}
      width={50}
      height={50}
      opacity={1}
      fill="grey"
      onClick={handleClick}
    />
  );
}
App.tsx
import { useRef } from "react";
import { Stage, Layer } from "react-konva";
import BottomSquare from "./BottomSquare";
import TopSquare from "./TopSquare";

export default function App() {
  const bottomSquareRef = useRef<{ triggerHover: () => void }>(null);

  return (
    <Stage width={window.innerWidth} height={window.innerHeight}>
      <Layer>
        <BottomSquare ref={bottomSquareRef} />
        <TopSquare
          onMove={() => {
            bottomSquareRef.current?.triggerHover();
          }}
        />
      </Layer>
    </Stage>
  );
}

思路二:模拟鼠标移动事件

不需要修改底部组件,在上方矩形移动后,模拟一次鼠标移动事件,让Konva重新检测光标下的元素:

// 修改TopSquare的点击事件
const handleClick = (e) => {
  setPosition({ x: 100, y: 100 });
  // 获取当前光标位置
  const { clientX, clientY } = e.evt;
  // 触发舞台的mousemove事件
  e.target.getStage().container().dispatchEvent(
    new MouseEvent('mousemove', {
      clientX,
      clientY,
      bubbles: true,
      cancelable: true
    })
  );
};

内容的提问来源于stack exchange,提问作者Riexn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 13:15:40