ロゴWeb開発ブログ

Material UIで固定ヘッダーとドロワーナビゲーション、フッターを備えたレイアウトを作成する

作成
  • 使用したバージョン
  • mui/material 5.13.6

Material UIで当サイトのようなレイアウトを作成する。

  • 上部に固定されるヘッダー
  • PCでは常に表示され、スマホではドロワーとなるナビゲーション
  • 要素の高さが足りなくても画面最下部に配置されるフッター

ヘッダー


// ヘッダーの高さを指定
export headerHeight = '40px';
export function Header() {
return (
<AppBar enableColorOnDark >
<Toolbar variant="dense" disableGutters sx={{ minHeight: headerHeight }}>
<OpenNavButton />
// ヘッダーの内容
...
</Toolbar>
</AppBar>
);
}

<OpenNavButton />はのちのち作成する。

Toolbarは必ずvariant="dense"にしなければならない。

これがないとメディアクエリにminHeightが指定されるので、minHeightが上書きされてしまう。

ナビゲーション

基本的に公式サイトのresponsive-drawerを参考にしている。

変更点として、

  • ヘッダーの下にナビゲーションを配置
  • ナビゲーションの開け閉めの状態管理はRecoilを使用
  • Next.jsを使用しているため、一部のコンポーネントを'use client'にする

これらをする必要がない場合は公式サイトのものを参照したほうがシンプルにできる。

Material UI Responsive drawer

まず、atomの作成。


export const isOpenNav = atom<boolean>({
key: 'isOpenNav',
default: false,
});

開け閉めするボタン。

画面サイズがmd以上では表示させない。

Next.jsを使用している場合は先頭に'use client'を書く。


export function OpenButton() {
const [isOpenNav, setIsOpenNav] = useRecoilState(isOpenNavAtom);
const toggleNav = () => setIsOpenNav(!isOpenNav);
return (
<IconButton
color="inherit"
aria-label="ナビを開く"
onClick={toggleNav}
sx={{ visibility: { md: 'hidden' } }}
>
<MenuIcon />
</IconButton>
);
};

ナビゲーション機能を有するコンポーネント。

Next.jsを使用している場合は先頭に'use client'を書く。


// ナビの幅を指定
export const navWidth = '200px';
export function NavWrapper({ children }: PropsWithChildren) {
return (
<Box component="nav" sx={{ width: { md: navWidth } }}>
<FixedNav>{children}</FixedNav>
<DrawerNav>{children}</DrawerNav>
</Box>
);
};
// 大きい画面で表示される固定されたナビゲーション
// ヘッダーの下に配置させるため、高さからheaderHeightを差し引く
function FixedNav({ children }: PropsWithChildren) {
return (
<Drawer
variant="permanent"
open={true}
// こうすると影の大きさがヘッダーと同じになり自然な感じになる
PaperProps={{ elevation: 4 }}
sx={{
display: { xs: 'none', md: 'block' },
'& .MuiDrawer-paper': {
boxSizing: 'border-box',
width: navWidth,
top: headerHeight
height: `calc(100% - ${headerHeight})`,
}
}}
children={children}
/>
);
};
// 小さい画面で表示されるドロワーナビゲーション
function DrawerNav({ children }: PropsWithChildren) {
const [isOpenNav, setIsOpenNav] = useRecoilState(isOpenNavAtom);
const closeNav = () => setIsOpenNav(false);
return (
<Drawer
variant="temporary"
open={isOpenNav}
onClose={closeNav}
ModalProps={{ keepMounted: true, }}
sx={{
display: { xs: 'block', md: 'none' },
'& .MuiDrawer-paper': {
boxSizing: 'border-box',
width: navWidth
}
}}
children={children}
/>
);
};

ナビゲーションの本体。


export function Nav() {
return (
<NavWrapper>
// ナビゲーションの内容
...
</NavWrapper>
);
};

コンテンツエリア

ヘッダーとナビゲーションに囲まれた内側のコンポーネント。

スマホ画面であれば画面幅いっぱいに表示され、それ以上であれば画面サイズからnavWidthを差し引いたwidthとなる。

フッターを最下部に配置させるため、flexにしてjustifyContent='space-between'を設定している。


export function ContentsArea(props: PropsWithChildren) {
return (
<Box
marginTop={headerHeight}
width={{ xs: '100%', md: `calc(100% - ${navWidth})` }}
display='flex'
flexDirection='column'
justifyContent='space-between'
children={props.children}
// 必要であればpaddingも設定する
// paddingX={{ xs: 0, sm: 2 }}
// paddingTop={2}
/>
);
};

フッター

flexGrowを0にすることでフッターを伸長させることなく最下部に配置できる。


export function Footer() {
return (
<Box component='footer' flexGrow={0}>
// フッターの内容
...
</Box>
);
};

レイアウト

全体をflexで囲むことでNavとContentsAreaを横並びにしている。

Headerはdisplay: fixedになっているため上部に固定される。


export function Layout(props: PropsWithChildren) {
return (
<Box display='flex' height='100%'>
<Header />
<Nav />
<ContentsArea>
<Box component='main'>
{props.children}
</Box>
<Footer />
</ContentsArea>
</Box>
);
};

このLayoutコンポーネントで囲むことでレイアウトが完成する。

Next.jsを使用している場合は、layout.tsxに書く。


export default function RootLayout({ children }: PropsWithChildren) {
return (
<html lang="ja">
<body>
<Provider>
<CssBaseline />
<Layout>
{children}
</Layout>
</Provider>
</body>
</html>
);
}