React/Material UI项目如何让Panel响应式充满屏幕剩余宽度
问题背景
现有基于React+Material UI开发的应用,Portal组件负责展示Panel和Widget两个子组件,主应用代码如下:
export default function App() { return ( <div style={{ backgroundColor: "blue" }} > <Portal></Portal> </div> ); }
核心逻辑集中在Portal.js和Layout.js两个文件中:
- Layout定义各断点响应式适配参数,实现Portal对窗口尺寸的响应式适配
- Portal将Widget固定在左上角,xs、sm断点下Panel排布在Widget下方,md、lg、xl断点下排布在Widget右侧
- Widget保持固定宽高比,示例中宽为400、高为800,支持动态修改
核心代码
Layout断点适配逻辑
const Layout = (aspect_ratio) => { const theme = useTheme(); const xsMatches = useMediaQuery(theme.breakpoints.up("xs")); const smMatches = useMediaQuery(theme.breakpoints.up("sm")); const mdMatches = useMediaQuery(theme.breakpoints.up("md")); const lgMatches = useMediaQuery(theme.breakpoints.up("lg")); const xlMatches = useMediaQuery(theme.breakpoints.up("xl")); var sizingDetails = { xl: { widgetGridCells: 10, bmkGridCells: 2 }, lg: { widgetGridCells: 8, bmkGridCells: 4 }, md: { widgetGridCells: 9, bmkGridCells: 3 }, sm: { widgetGridCells: 12, bmkGridCells: 12 }, xs: { widgetGridCells: 12, bmkGridCells: 12 } }; if (xlMatches) { sizingDetails.selWidgetWidth = (theme.breakpoints.values.xl * 10.0) / 12.0; sizingDetails.viewWidth = theme.breakpoints.values.xl; } else if (lgMatches) { sizingDetails.selWidgetWidth = (theme.breakpoints.values.lg * 8.0) / 12.0; sizingDetails.viewWidth = theme.breakpoints.values.lg; } else if (mdMatches) { sizingDetails.selWidgetWidth = (theme.breakpoints.values.md * 9) / 12.0; sizingDetails.viewWidth = theme.breakpoints.values.md; } else if (smMatches) { sizingDetails.selWidgetWidth = theme.breakpoints.values.sm; sizingDetails.viewWidth = theme.breakpoints.values.sm; } else if (xsMatches) { sizingDetails.selWidgetWidth = theme.breakpoints.values.xs; sizingDetails.viewWidth = theme.breakpoints.values.xs; } else { sizingDetails.selWidgetWidth = 0; sizingDetails.viewWidth = 0; } sizingDetails.selWidgetHeight = sizingDetails.selWidgetWidth * aspect_ratio; return sizingDetails; }; export default Layout;
Portal组件
函数逻辑
export default function Portal() { const selWidHeight = 400; const selWidWidth = 800; const layout = Layout(selWidHeight / selWidWidth); const sizes = { containerWidth: layout.selWidgetWidth, containerHeight: layout.selWidgetHeight, bookmarkPanelContainerHeight: "600px", widgetWidth: "90%", widgetHeight: "90%", viewWidth: layout.viewWidth }; const classes = useStyles(sizes); return ( <> {layout.viewWidth > 0 ? ( <Grid container direction="row" alignItems="flex-start" alignContent="flex-start" className={classes.root} > <Grid item xs={layout.xs.widgetGridCells} sm={layout.sm.widgetGridCells} md={layout.md.widgetGridCells} lg={layout.lg.widgetGridCells} xl={layout.xl.widgetGridCells} > <Container className={classes.widgetContainer} disableGutters={true} maxWidth={false} > <Widget className={classes.widget} width={sizes.widgetWidth} height={sizes.widgetHeight} /> </Container> </Grid> <Grid item xs={layout.xs.bmkGridCells} sm={layout.sm.bmkGridCells} md={layout.md.bmkGridCells} lg={layout.lg.bmkGridCells} xl={layout.xl.bmkGridCells} > <Container className={classes.bookmarkPanel} disableGutters={true} > <Panel /> </Container> </Grid> </Grid> ) : ( <div>窗口尺寸过小</div> )} </> ); }
样式逻辑
const useStyles = makeStyles((theme) => { return { root: { width: (props) => props.viewWidth, height: "100%", margin: "0px" }, rootContainer: { display: "flex", justifyContent: "flex-start", flexDirection: "row" }, widgetContainer: { width: (props) => props.containerWidth, height: (props) => props.containerHeight, minWidth: (props) => props.containerWidth, backgroundColor: "orange", display: "flex", flexDirection: "row", flexWrap: "wrap", justifyContent: "center", alignItems: "center", position: "relative" }, bookmarkPanel: { overflowY: "scroll", border: "1px solid green", float: "left", height: (props) => props.bookmarkPanelContainerHeight, position: "relative", background: "repeating-linear-gradient(17deg, #ebf8e1 15%, #f69d3c 20%)" }, widget: { width: (props) => { return props.containerWidth; }, height: (props) => { return props.widgetHeight; }, background: "repeating-linear-gradient(45deg, #3f87a6, #ebf8e1 15%, #f69d3c 20%)", position: "absolute" } }; });
Panel组件
const useStyles = makeStyles((theme) => ({ root: { backgroundColor: "red" } })); const Panel = () => { const classes = useStyles(); return <div className={classes.root}>Panel </div>; };
Widget组件
const useStyles = makeStyles((theme) => ({ root: { width: (props) => props.width, height: (props) => props.height, background: "repeating-linear-gradient(45deg, #3f87a6, #ebf8e1 15%, #f69d3c 20%)" } })); const Widget = ({ width, height }) => { const props = { width: width, height: height }; const classes = useStyles(props); return <div className={classes.root}>Widget </div>; };
问题描述
- xs、sm断点下表现正常:Panel排布在Widget下方,宽度符合预期
- md、lg、xl断点下Widget展示正常,Panel位置和高度符合预期,但无法填满蓝色父容器的剩余宽度,固定为240px宽
- 需求:同一断点下,浏览器窗口宽度增加时,Panel宽度动态扩展,填满蓝色父容器的剩余空间
解决方案
问题出在两处样式配置错误,修改即可实现需求:
- 修改Grid容器的宽度配置,将固定断点宽度改为占满父容器100%宽度
// 修改前root样式 root: { width: (props) => props.viewWidth, height: "100%", margin: "0px" } // 修改后root样式 root: { width: "100%", height: "100%", margin: "0px" }
- 给Panel外层容器添加100%宽度配置,确保占满所在Grid单元格的全部宽度
// 修改前bookmarkPanel样式 bookmarkPanel: { overflowY: "scroll", border: "1px solid green", float: "left", height: (props) => props.bookmarkPanelContainerHeight, position: "relative", background: "repeating-linear-gradient(17deg, #ebf8e1 15%, #f69d3c 20%)" } // 修改后bookmarkPanel样式 bookmarkPanel: { width: "100%", overflowY: "scroll", border: "1px solid green", float: "left", height: (props) => props.bookmarkPanelContainerHeight, position: "relative", background: "repeating-linear-gradient(17deg, #ebf8e1 15%, #f69d3c 20%)" }
内容的提问来源于stack exchange,提问作者David Sargrad
相关产品推荐
相关产品推荐

