鼠标悬停效果:onHover事件实现PC端特有的交互反馈(71)

在鸿蒙(HarmonyOS)应用向 PC 端和平板端扩展时,鼠标悬停(Hover)是提升桌面级交互体验的关键。开发者可以通过 onHover 事件、hoverEffect 属性以及 onMouse 高级事件,实现丰富的悬停反馈。

以下是实现 PC 端特有交互反馈的核心策略与代码示例:

一、 基础悬停状态反馈(onHover 事件)

通过 onHover 回调,可以精准感知鼠标进入或离开组件的瞬间。常用于自定义背景色变化、文字变色或触发预加载逻辑。

核心代码示例:

javascript 复制代码
Button('提交')
  .onHover((isHover: boolean) => {
    if (isHover) {
      this.bgColor = '#e1e1e1'; // 鼠标进入时变色
    } else {
      this.bgColor = '#ffffff'; // 鼠标离开时恢复
    }
  })

二、 系统级标准悬浮态(hoverEffect 属性)

如果希望应用遵循鸿蒙 PC 端原生的视觉规范,可以直接使用 hoverEffect 属性。系统会自动处理按下与松开时的状态切换(按下时效果消失,松开时恢复)。

  • HoverEffect.Scale:悬停时组件轻微放大。
  • HoverEffect.Highlight:悬停时组件高亮。

核心代码示例:

javascript 复制代码
Column() {
  Text('系统标准缩放效果')
}
.hoverEffect(HoverEffect.Scale) // 自动应用系统级缩放反馈

三、 区分输入源:鼠标 vs 手写笔

在鸿蒙中,onHover 支持传入 HoverEvent 对象。通过 event.sourceTool 属性,可以区分当前是鼠标还是手写笔触发的悬停,从而提供差异化的 UI 反馈。

核心代码示例:

javascript 复制代码
Button('悬停测试')
  .onHover((isHover: boolean, event: HoverEvent) => {
    if (isHover) {
      if (event.sourceTool == SourceTool.Pen) {
        this.hoverText = '手写笔悬停';
        this.color = Color.Pink;
      } else if (event.sourceTool == SourceTool.MOUSE) {
        this.hoverText = '鼠标悬停';
        this.color = Color.Red;
      }
    } else {
      this.hoverText = '无悬停';
      this.color = Color.Blue;
    }
  })

四、 进阶:防抖处理与右键菜单(PC 专属)

在 PC 端,鼠标快速滑动时可能会频繁触发 onHover。为了防止性能抖动,建议结合定时器进行防抖处理;同时可结合 onContextMenu 实现 PC 端专属的右键菜单。

核心代码示例:

javascript 复制代码
Column() {
  Text(this.doc.title)
    .fontColor(this.isHovered ? '#007DFF' : '#333')
    .backgroundColor(this.isHovered ? '#E6F7FF' : '#FFFFFF')
    // 鼠标悬停事件(带防抖)
    .onHover((isHover: boolean) => {
      if (!DeviceDetector.isDesktop()) return; // 仅PC环境启用
      if (this.hoverTimer !== -1) clearTimeout(this.hoverTimer);
      this.hoverTimer = setTimeout(() => {
        this.isHovered = isHover;
        if (isHover) {
          Document.preloadDetail(this.doc.id); // 悬停时预加载详情
        }
      }, 50); // 50ms 防抖
    })
    // 右键点击触发菜单
    .onContextMenu(() => {
      if (DeviceDetector.isDesktop()) {
        this.showContextMenu = true;
        setTimeout(() => { this.showContextMenu = false; }, 3000);
      }
    })
}

💡 PC 端悬停交互开发建议

  1. 防抖机制 :在列表或瀑布流中,务必对 onHover 内的复杂逻辑(如网络请求、预加载)加上 50ms 左右的防抖,避免快速滑动时引发 UI 卡顿。
  2. 设备隔离 :使用 DeviceDetector.isDesktop() 等判断,确保悬停特效仅在 PC 或平板端生效,避免影响手机端的触摸体验。
  3. 自动隐藏:对于悬停触发的右键菜单或 Tooltip,建议设置 3 秒左右的自动隐藏逻辑,防止焦点丢失后 UI 元素残留。

五、 精细坐标追踪:onHoverMove 事件

在 PC 端,有时不仅需要知道"是否悬停",还需要知道鼠标在组件内的"精确位置"。从 API version 15 开始,鸿蒙提供了 onHoverMove 事件,常用于实现光效跟随、自定义 Tooltip 定位等高级视觉反馈。

核心代码示例:

javascript 复制代码
@Entry
@Component
struct OnHoverMoveEventExample {
  @State hoverMoveText: string = '';

  build() {
    Column({ space: 20 }) {
      Button('onHoverMove 追踪')
        .width(200).height(80)
        .onHoverMove((event: HoverEvent) => {
          // 实时获取鼠标在组件内、窗口内、屏幕内的绝对坐标
          this.hoverMoveText = `组件内XY: (${event.x}, ${event.y})\n` +
                               `窗口XY: (${event.windowX}, ${event.windowY})`;
        })
      Text(this.hoverMoveText).fontSize(14).fontColor(Color.Gray)
    }
    .padding(30)
  }
}

六、 复杂交互联动:onMouse 综合事件

当需要在同一个组件上同时处理"悬停、按下、移动、抬起"等多种鼠标动作时,使用 onHover 会显得力不从心。此时应使用 onMouse 事件,它通过 MouseEvent 对象提供了全方位的鼠标状态反馈。

核心代码示例:

javascript 复制代码
Button('综合鼠标事件')
  .width(200).height(100)
  .onMouse((event?: MouseEvent) => {
    if (event) {
      // 1. 判断鼠标按键(左键、右键、中键等)
      if (event.button === MouseButton.Left && event.action === MouseAction.Press) {
        console.log('鼠标左键按下');
      }
      // 2. 判断鼠标动作(Hover, Move, Press, Release)
      if (event.action === MouseAction.Hover) {
        console.log('鼠标悬停');
      }
      // 3. 获取屏幕绝对坐标
      console.log(`屏幕坐标: (${event.screenX}, ${event.screenY})`);
    }
  })

七、 嵌套组件的事件冒泡控制

在 PC 端复杂的嵌套布局中(例如:列表项内部包含一个"更多操作"按钮),鼠标悬停在内部按钮时,外部的列表项也会触发 onHover。为了隔离交互,需要调用 stopPropagation() 阻止事件冒泡。

核心代码示例:

javascript 复制代码
Column() {
  // 内部操作按钮
  Button('更多')
    .onMouse((event?: MouseEvent) => {
      if (event) {
        // 【关键】阻止事件向上冒泡,防止外层列表项同时触发高亮
        event.stopPropagation(); 
      }
    })
    .onClick(() => { /* 处理点击 */ })
}
.onHover((isHover: boolean) => {
  // 只有当鼠标悬停在列表项空白处时才会触发,悬停在"更多"按钮上时不会触发
  this.isItemHovered = isHover;
})

八、 滚轮缩放与键盘快捷键联动

PC 端的交互不仅限于鼠标悬停,滚轮(Mouse Wheel)和键盘快捷键(Key Events)同样是桌面级体验的核心。

核心代码示例:

javascript 复制代码
Column() {
  // 1. 鼠标滚轮缩放
  Image($r('app.media.pcImage'))
    .width(400 * this.scale).height(300 * this.scale)
    .onMouseWheel((event) => {
      if (event.deltaY < 0) {
        this.scale = Math.min(this.scale + 0.1, 2); // 向上滚放大
      } else {
        this.scale = Math.max(this.scale - 0.1, 0.5); // 向下滚缩小
      }
      return true; // 阻止事件继续冒泡
    })

  // 2. 键盘快捷键监听
  TextInput({ placeholder: '输入内容' })
    .onKeyEvent((event) => {
      // 监听 Ctrl+S 触发保存
      if (event.keyCode === KeyCode.KEY_S && event.ctrlKey) {
        promptAction.showToast({ message: '触发 Ctrl+S 保存' });
        return true; // 阻止系统默认行为
      }
      return false;
    })
}

💡 高阶交互架构建议

  1. 事件归一与职责分离 :简单的背景色切换使用 onHover;需要坐标追踪使用 onHoverMove;涉及复杂按键和动作组合使用 onMouse。避免在一个组件上滥用多个同类事件。
  2. 性能保护onHoverMoveonMouse 的触发频率极高。在这些回调中绝对不要进行耗时操作(如 DOM 重排、复杂计算或网络请求),仅用于更新轻量级的 UI 状态(如坐标文本、光效位置)。
  3. 多模态兼容 :在编写 PC 端交互时,始终通过 event.sourceToolDeviceDetector 做好环境隔离。确保鼠标专属的 Hover 效果不会在移动端或触屏设备上产生误触或视觉残留。