项目场景:
公司使用无界微前端集成ERP项目应用(可惜没跟着走一边无界,难受),某些子应用使用时,发现antd的弹窗弹出的位置不对。如下图:
问题描述
无界微前端嵌入的子应用中的antd的下拉框位置计算不正确。
无界常见问题有类似的有关问题,说是子应用的body标签增加position:relative即可
使用了此方法,倒是解决了某些子应用的同类情况,但是还有一些并没有生效。
解决方案:
方案一
先是排查了antd相关位置计算的源码,发现位置的计算是基于dom-align实现的,阅读到了最后的相关方法。
方法的算法计算没有细看,经过控制台的debugger,发现是出现错误的下拉框自适应弹窗的计算的方法没有走。
自适应弹窗的计算依赖于:窗口可视区域、target区域(select框)、origin区域(下拉面板)三部分的位置做计算。因为无界微前端的模式可能导致了未知的尺寸的错误计算。使得最后一行没有执行自适应的代码。
通过代码的阅读,发现参数alwaysByViewport可以控制始终重计算自适应弹窗,因此就有了第一个解决方案,把这个参数置为true。
继续阅读antd源码,发现他并没有抛出这个属性,因此需要我们重写一下。
下列代码块为重写的方法(其实就是多加了一个属性)和dom-align计算的源码
export function transPlacement2DropdownAlign(direction, placement) {
let overflow = {
adjustX: 1,
adjustY: 1,
alwaysByViewport: true, // 始终允许自适应
};
switch (placement) {
case 'bottomLeft': {
return {
points: ['tl', 'bl'],
offset: [0, 4],
overflow: overflow,
};
}
case 'bottomRight': {
return {
points: ['tr', 'br'],
offset: [0, 4],
overflow: overflow,
};
}
case 'topLeft': {
return {
points: ['bl', 'tl'],
offset: [0, -4],
overflow: overflow,
};
}
case 'topRight': {
return {
points: ['br', 'tr'],
offset: [0, -4],
overflow: overflow,
};
}
default: {
return {
points: direction === 'rtl' ? ['tr', 'br'] : ['tl', 'bl'],
offset: [0, 4],
overflow: overflow,
};
}
}
}
/**
* 获得元素的显示部分的区域
*/
function getVisibleRectForElement(element, alwaysByViewport) {
var visibleRect = {
left: 0,
right: Infinity,
top: 0,
bottom: Infinity
};
var el = getOffsetParent(element);
var doc = utils.getDocument(element);
var win = doc.defaultView || doc.parentWindow;
var body = doc.body;
var documentElement = doc.documentElement;
// Determine the size of the visible rect by climbing the dom accounting for
// all scrollable containers.
while (el) {
// clientWidth is zero for inline block elements in ie.
if ((navigator.userAgent.indexOf('MSIE') === -1 || el.clientWidth !== 0) &&
// body may have overflow set on it, yet we still get the entire
// viewport. In some browsers, el.offsetParent may be
// document.documentElement, so check for that too.
el !== body && el !== documentElement && utils.css(el, 'overflow') !== 'visible') {
var pos = utils.offset(el);
// add border
pos.left += el.clientLeft;
pos.top += el.clientTop;
visibleRect.top = Math.max(visibleRect.top, pos.top);
visibleRect.right = Math.min(visibleRect.right,
// consider area without scrollBar
pos.left + el.clientWidth);
visibleRect.bottom = Math.min(visibleRect.bottom, pos.top + el.clientHeight);
visibleRect.left = Math.max(visibleRect.left, pos.left);
} else if (el === body || el === documentElement) {
break;
}
el = getOffsetParent(el);
}
// Set element position to fixed
// make sure absolute element itself don't affect it's visible area
// https://github.com/ant-design/ant-design/issues/7601
var originalPosition = null;
if (!utils.isWindow(element) && element.nodeType !== 9) {
originalPosition = element.style.position;
var position = utils.css(element, 'position');
if (position === 'absolute') {
element.style.position = 'fixed';
}
}
var scrollX = utils.getWindowScrollLeft(win);
var scrollY = utils.getWindowScrollTop(win);
var viewportWidth = utils.viewportWidth(win);
var viewportHeight = utils.viewportHeight(win);
var documentWidth = documentElement.scrollWidth;
var documentHeight = documentElement.scrollHeight;
// scrollXXX on html is sync with body which means overflow: hidden on body gets wrong scrollXXX.
// We should cut this ourself.
var bodyStyle = window.getComputedStyle(body);
if (bodyStyle.overflowX === 'hidden') {
documentWidth = win.innerWidth;
}
if (bodyStyle.overflowY === 'hidden') {
documentHeight = win.innerHeight;
}
// Reset element position after calculate the visible area
if (element.style) {
element.style.position = originalPosition;
}
if (alwaysByViewport || isAncestorFixed(element)) {
// Clip by viewport's size.
visibleRect.left = Math.max(visibleRect.left, scrollX);
visibleRect.top = Math.max(visibleRect.top, scrollY);
visibleRect.right = Math.min(visibleRect.right, scrollX + viewportWidth);
visibleRect.bottom = Math.min(visibleRect.bottom, scrollY + viewportHeight);
} else {
// Clip by document's size.
var maxVisibleWidth = Math.max(documentWidth, scrollX + viewportWidth);
visibleRect.right = Math.min(visibleRect.right, maxVisibleWidth);
var maxVisibleHeight = Math.max(documentHeight, scrollY + viewportHeight);
visibleRect.bottom = Math.min(visibleRect.bottom, maxVisibleHeight);
}
return visibleRect.top >= 0 && visibleRect.left >= 0 && visibleRect.bottom > visibleRect.top && visibleRect.right > visibleRect.left ? visibleRect : null;
}
方案二
究其原因,是窗口可视区域、target区域(select框)、origin区域(下拉面板)三部分的位置做计算。因为无界微前端的模式可能导致了未知的尺寸的错误计算。使得最后一行没有执行自适应的代码。
因此,我尝试将子应用的高度变成100vh。发现也可以解决问题,但是这样导致了一些其他未知的错误,比如挡住了菜单,antd的弹窗出现问题。