关于虚幻引擎 UnrealEngine5 加载 Web 页面,div 元素鼠标事件穿透问题总结(pointer-events: none)

275 阅读4分钟

背景

Unreal Engine 5 做的一个 Windows 应用程序里内嵌了一个 web 页面。

实现的效果是 UE5 进行 3D 动态地图场景展示,可进行点击,拖拽,旋转等操作。

Web 页面负责左右图表和 title 等其他内容的显示。大概如下图所示。左右两侧黑色框是图表。现在要加一个渐变的灰色背景,如下图灰色框所示,将 3D 地图和图表从视觉上分割开来。

Pasted Graphic 2.png

鼠标穿透问题

加了渐变区域之后,鼠标点击和拖拽事件在灰色区域不生效了,只有在非灰色框区域才能生效。应该是渐变元素导致的。调整 z-index 和 div 布局也无法解决问题。

pointer-events

因为元素挡住了,所以首先想到使用 pointer-events: none。使这个渐变元素的点击事件被忽略掉,继而可以触发他的父元素的点击事件。但是尝试了之后发现还没有解决。

实际代码如下:

const MetaMapProMask = () => {
  return (
    <>
      <div className="mask-left"></div>
      <div className="mask-right"></div>
    </>
  );
};
export default MetaMapProMask;
.mask-left {
  position: absolute;
  top: 0;
  left: 0;
  width: 700px;
  height: 100%;
  background: linear-gradient(to right, rgba(1, 4, 8, 0.8), rgba(4, 17, 26, 0));
  pointer-events: none;
}
.mask-right {
  position: absolute;
  top: 0;
  right: 0;
  width: 700px;
  height: 100%;
  background: linear-gradient(to left, rgba(1, 4, 8, 0.8), rgba(4, 17, 26, 0));
  pointer-events: none;
}

elementFromPoint

于是询问 ChatGPT,除了 css 的方案有没有其他方案解决这个问题。于是看到 elementFromPoint。

document.elementFromPoint 返回给定相对于视口的坐标点下最上层的元素。

代码修改如下:

import React, { useEffect } from "react";
const MetaMapProMask = () => {
  useEffect(() => {
    const handleMouseEvent = (event: MouseEvent) => {
      const { clientX, clientY } = event;
      // 获取底层 3D 场景元素
      const targetElement = document.elementFromPoint(clientX, clientY);
        if (targetElement && targetElement !== event.target) {
        // 创建新的鼠标事件
        const newEvent = new MouseEvent(event.type, {
          bubbles: true,
          cancelable: true,
          clientX,
          clientY,
        });
        // 触发事件到 3D 场景
        targetElement.dispatchEvent(newEvent);
      }
      // 阻止遮罩层的默认事件处理
      event.stopPropagation();
      event.preventDefault();
    };
    // 监听鼠标事件
    const mask = document.getElementById("mask-wrap");
    if (mask) {
      mask.addEventListener("mousedown", handleMouseEvent);
      mask.addEventListener("mousemove", handleMouseEvent);
      mask.addEventListener("mouseup", handleMouseEvent);
      mask.addEventListener("click", handleMouseEvent);
    }
    return () => {
      if (mask) {
        mask.removeEventListener("mousedown", handleMouseEvent);
        mask.removeEventListener("mousemove", handleMouseEvent);
        mask.removeEventListener("mouseup", handleMouseEvent);
        mask.removeEventListener("click", handleMouseEvent);
      }
    };
  }, []);
  return (
    <div id="mask-wrap" className="mask-wrap">
      <div className="mask-left"></div>
      <div className="mask-right"></div>
    </div>
  );
};
.mask-wrap {
  pointer-events: none; /* 遮罩层不拦截鼠标事件 */
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
.mask-left,
.mask-right {
  position: absolute;
  top: 0;
  height: 100%;
  width: 700px;
  pointer-events: none; /* 继续保证鼠标穿透 */
}
.mask-left {
  left: 0;
  background: linear-gradient(to right, rgba(1, 4, 8, 0.8), rgba(4, 17, 26, 0));
}
.mask-right {
  right: 0;
  background: linear-gradient(to left, rgba(1, 4, 8, 0.8), rgba(4, 17, 26, 0));
}

问题还没有解决。

引擎问题

最后才开始考虑是不是在 UE5 引擎中加载 web 页面和 chrome 环境不一样,所以某些属性不支持。

于是询问 3D 的同事用的什么引擎,得知是 CefBrowser,然后问 GPT 这个 CefBrowser 是不是不支持 pointer-events。但经过简单测试,是支持的。(具体测试方法这里先省略,不重要)。

后来经过询问开发过 3D 前端的同事才知道。pointer-events 在这个引擎中,只有透明的元素才能穿透点击事件,如果有了颜色就无法穿透事件,这是这个插件引擎决定的。但如果在正常Chrome浏览器端,不管有没有颜色都是可以穿透事件的。

为了证明「在正常Chrome浏览器端,不管有没有颜色都是可以穿透事件的」。可以查看下面这个例子:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Pointer Events Test</title>
  <style>
      .interactive-div {
          width: 300px;
          height: 300px;
          background-color: lightblue;
          text-align: center;
          line-height: 300px;
          border: 2px solid #000;
      }
      .non-interactive-div {
          width: 300px;
          height: 300px;
          background-color: rgba(0, 0, 0, 0.3); /* 半透明黑色背景 */
          text-align: center;
          line-height: 300px;
          border: 2px dashed #f00;
          pointer-events: none; /* 禁用鼠标事件 */
      }
  </style>
</head>
<body>
<div onclick="alert('bg')">
  <div class="interactive-div" onclick="alert('Interactive div clicked!')">
    Click Me (Interactive)
  </div>
  <div class="non-interactive-div" onclick="alert('Non-interactive div clicked!')">
    Can't Click Me (Non-interactive)
  </div>
</div>
<script>
  // 确保两个 div 上的点击事件能正确触发
  *console*.log('If you click on the first box, you\'ll see an alert.');
  *console*.log('The second box (non-interactive) won\'t trigger the alert due to pointer-events: none.');
</script>
</body>
</html>

点击non-interactive-div的时候,发现正常浏览器就可以弹出 bg。但是在 UE5 的 CefBrowser 引擎的 Web 页面就弹不出来。

这还是用的比较好的引擎,如果用其他一般的引擎,可能透明的元素,设置pointer-events 为none也无法穿透事件。

经历了一波三折,最后发现是第三方引擎的问题。

总结

收获是:还是得多和有经验的同事交流。可能有经验的大佬一两句话就能解决自己摸索了半天的问题。经验这个东西太宝贵了。

现在这个问题有两个解决方案:

  1. 产品层面,将阴影部分减小到不影响点击拖拽事件,这样即使有些部分响应不了也问题不大
  2. 在开始进行某些交互之前,将渐变层隐藏,在交互结束后再将渐变层显示,就防止了遮挡问题

虽然最后没有通过技术问题解决,但是因为这个问题还是花了一些时间,踩了一些坑,所以记录下,有遇到类似问题的小伙伴可以参考