React Material UI v4/v5设置zoom后下拉菜单位置偏移问题求解
React Material UI v4/v5 页面设置zoom后下拉菜单位置偏移问题
在React Material UI v4或v5版本中,页面缩放下存在一个常见问题:如果您为body或HTML标签添加如下样式:
body { zoom: 80%; /* 缩小页面 */ }
所有Material UI的下拉菜单位置都会偏离原本应在的正确位置。
- 无缩放时的正确位置展示:下拉菜单与触发组件对齐,无偏移
- 添加zoom样式后出现的问题展示:下拉菜单与触发组件位置错位,偏移距离和zoom比例正相关
- 问题复现:可通过为body设置zoom属性后触发任意MUI下拉组件复现该问题
- 关于该问题的相关说明:
- 虽然可以通过按下command + [-] 键使用浏览器自带缩放功能解决该问题,但需要纯代码层面的解决方案,不需要告知用户手动操作。
- Select2不存在该问题,缩放后菜单仍可保持在正确位置,可能是因为其下拉菜单的实现逻辑不同。
- Angular Material 也不存在该问题,缩放下无位置偏移问题。
- 核心问题:
如何让Material UI的下拉菜单表现和Select2/Angular Material或原生下拉菜单一致,无需用户手动操作?Material UI是如何计算下拉菜单位置的?我们可以如何覆写该定位逻辑?
问题原因
Material UI的下拉类组件(Menu、Select、Autocomplete等)底层依赖Popper.js库做定位计算,Popper.js默认读取getBoundingClientRect()返回的元素坐标做计算,而如果页面根节点(html/body)设置了zoom属性,getBoundingClientRect()返回的是经过缩放后的坐标值,Popper.js直接将该值作为未缩放的像素值设置定位,就会出现定位偏移。
Select2和Angular Material的定位逻辑会先获取当前页面的缩放比例,对原始坐标做修正后再设置定位,因此不会出现偏移。
解决方案
通过自定义Popper.js的定位修正规则,全局修复所有下拉组件的偏移问题,无需修改单个业务组件。
适用Material UI v5版本
- 定义缩放修正modifier:
// 获取页面设置的zoom比例 const getPageZoom = () => { return Number(getComputedStyle(document.documentElement).zoom) || Number(getComputedStyle(document.body).zoom) || 1 } // 自定义定位修正规则 const zoomFixModifier = { name: 'zoomFix', enabled: true, phase: 'beforeWrite', requires: ['computeStyles'], fn: ({ state }) => { const zoom = getPageZoom() if (zoom === 1) return // 修正定位坐标 state.styles.popper.transform = `translate(${state.rects.reference.x / zoom}px, ${(state.rects.reference.y + state.rects.reference.height) / zoom}px)` }, }
- 全局配置主题,让所有Popper组件默认加载该修正规则:
import { createTheme, ThemeProvider } from '@mui/material/styles' const theme = createTheme({ components: { MuiPopper: { defaultProps: { modifiers: [zoomFixModifier] } } } }) // 根组件包裹ThemeProvider即可全局生效 function App() { return <ThemeProvider theme={theme}> {/* 业务代码 */} </ThemeProvider> }
适用Material UI v4版本
v4版本依赖Popper.js v1,modifier的写法略有不同:
- 定义v4版本的缩放修正modifier:
const getPageZoom = () => { return Number(getComputedStyle(document.documentElement).zoom) || Number(getComputedStyle(document.body).zoom) || 1 } const zoomFixModifierV4 = { name: 'zoomFix', enabled: true, order: 800, fn: (data) => { const zoom = getPageZoom() if (zoom === 1) return data data.offsets.popper.left = data.offsets.reference.left / zoom data.offsets.popper.top = (data.offsets.reference.top + data.offsets.reference.height) / zoom return data } }
- 全局配置v4主题:
import { createMuiTheme, ThemeProvider } from '@material-ui/core/styles' const theme = createMuiTheme({ props: { MuiPopper: { modifiers: [zoomFixModifierV4] } } }) // 根组件包裹ThemeProvider即可全局生效 function App() { return <ThemeProvider theme={theme}> {/* 业务代码 */} </ThemeProvider> }
内容的提问来源于stack exchange,提问作者Tawfeeq Amro
相关产品推荐
相关产品推荐

