背景
Unreal Engine 5 做的一个 Windows 应用程序里内嵌了一个 web 页面。
实现的效果是 UE5 进行 3D 动态地图场景展示,可进行点击,拖拽,旋转等操作。
Web 页面负责左右图表和 title 等其他内容的显示。大概如下图所示。左右两侧黑色框是图表。现在要加一个渐变的灰色背景,如下图灰色框所示,将 3D 地图和图表从视觉上分割开来。
鼠标穿透问题
加了渐变区域之后,鼠标点击和拖拽事件在灰色区域不生效了,只有在非灰色框区域才能生效。应该是渐变元素导致的。调整 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也无法穿透事件。
经历了一波三折,最后发现是第三方引擎的问题。
总结
收获是:还是得多和有经验的同事交流。可能有经验的大佬一两句话就能解决自己摸索了半天的问题。经验这个东西太宝贵了。
现在这个问题有两个解决方案:
- 产品层面,将阴影部分减小到不影响点击拖拽事件,这样即使有些部分响应不了也问题不大
- 在开始进行某些交互之前,将渐变层隐藏,在交互结束后再将渐变层显示,就防止了遮挡问题
虽然最后没有通过技术问题解决,但是因为这个问题还是花了一些时间,踩了一些坑,所以记录下,有遇到类似问题的小伙伴可以参考