问题现象
问题的根源:一切始于 zoom
- 主应用的全局缩放:在主应用的
mounted
钩子中,有一段核心代码document.getElementById('main').style.zoom = scaleW;
。为了让页面能自适应不同分辨率的屏幕,主应用对整个容器(包括未来所有子应用)应用了一个zoom
缩放。例如,当scaleW
是0.75
时,整个页面都被浏览器缩小到了 75%。 - JavaScript 的“视觉欺骗”:
zoom
是一个比较特殊的 CSS 属性。当用 JavaScript 去测量一个被缩放的元素的宽度时(例如element.offsetWidth
),得到的是它在屏幕上看起来的宽度(视觉宽度),而不是它在 CSS 中定义的原始宽度。
为什么会“越点越小”?—— 灾难性的“双重缩放”
当点击子应用中的 el-cascader
时,一场混乱的计算就开始了:
- 第一次错误计算:
el-cascader
的下拉框(由 Popper.js 库控制)需要知道它应该有多宽。于是它去测量输入框的宽度。假设输入框原始宽度是200px
,因为被主应用zoom: 0.75
了,Popper.js 测量到的结果是150px
。 - 第一次错误应用:Popper.js 拿到这个
150px
的值,就把它设置给了下拉框的style="width: 150px;"
。 - 第二次灾难性缩放:Element UI 出于某种原因,又给下拉框内部的面板
.el-cascader-panel
也加上了style="zoom: 0.75;"
。 - 恶性循环:这就导致了“双重缩放”。下拉框容器的宽度被错误地设为了
150px
,而它里面的内容又被再次缩放了 75%。这导致布局彻底混乱。当再次点击时,下一次计算又基于这个已经混乱的布局,得到一个更小的值,如此往复,就出现了“越点越小”的现象。
解决方案的原理:在主应用中“反向解题”
对抗来自主应用的全局 zoom
。
最终的解决方案是在主应用里,通过提供的那个全局函数 window.computedPopperPosition
,从根源上修正这个计算过程。它的原理就像“反向解题”:
- 获取缩放比例:函数首先获取到主应用当前设置的全局
zoom
值scaleW
(例如0.75
)。 - 获取错误结果:它拿到 Popper.js 测量出的那个被缩小的视觉宽度
visualWidth
(例如150px
)。 - 反向计算出正确答案:这是最关键的一步。通过公式
真实宽度 = 视觉宽度 / 缩放比例
(realWidth = visualWidth / scaleW
),它用150 / 0.75
计算出了输入框在缩放前真正的宽度200px
。 - 强行修正:
- 它把这个正确的
200px
强行设置给下拉框的style.width
,覆盖掉 Popper.js 的错误计算结果。 - 同时,它找到下拉框内部那个被 Element UI 再次缩放的面板
.el-cascader-panel
,并强行将它的zoom
设回1
,彻底打破了“双重缩放”的循环。
- 它把这个正确的
qiankun主应用代码
window.computedPopperPosition = function (dataParam) {
// 1. 计算主应用设置的 zoom 缩放比例
const scaleW = Number(parseFloat(Math.max(window.innerWidth, 1366) / 1920).toFixed(2));
// 如果没有缩放,则直接返回,不做任何处理
if (scaleW >= 1) {
return dataParam;
}
const instance = dataParam.instance;
// 确保 Popper 实例和相关 DOM 元素都存在
if (instance && instance._reference && instance._popper) {
const referenceEl = instance._reference; // 这是被缩放的输入框
const popperEl = instance._popper; // 这是未被缩放的下拉框
// 确认下拉框是挂载到 body 下的,这是 el-cascader 的默认行为
const appendToBody = popperEl.getAttribute('appendToBody');
if (appendToBody === 'true' || appendToBody === true) {
// 2. 测量输入框在缩放后的视觉宽度
const visualWidth = referenceEl.offsetWidth;
// 3. 计算出输入框在缩放前的“真实”宽度
const realWidth = visualWidth / scaleW;
// 4. 将这个“真实”宽度强行赋值给下拉框的 style.width
popperEl.style.width = `${realWidth}px`;
// 5. 【关键】强制将下拉框自身的 zoom 设置为 1,防止双重缩放
popperEl.style.zoom = 1;
// 遍历并重置子元素的 zoom,确保内容也恢复正常
for (let i = 0; i < popperEl.children.length; i++) {
if (popperEl.children[i].style) {
popperEl.children[i].style.zoom = 1;
}
}
}
}
return dataParam;
}