前言
页面布局也是在实际开发中经常用到的技术。
- 在大的方面,可以实现整个页面的布局,比如左侧导航、header、footer...
- 在小的方面,可以是内容布局,比如文章。
- 考虑布局时,需要兼顾桌面浏览器和移动端的情况,也就是常说的 responsive。
在 react 中实现,和经典实现其实没什么大的区别。
实现布局有这几种方式:
- 从 0 开始使用 CSS 实现,这是必须掌握的技能
- 使用 CSS Grid 系统(网状页面),可以使用不同尺寸的屏幕
- 使用组件库,例如
antd
Grid
:24 栅格系统Layout
:页面级整体布局
CSS 实现基础布局
上中下布局
.app-layout1 {
width: 500px;
height: 400px;
position: relative;
text-align: center;
}
.app-layout1 .header {
line-height: 60px;
}
.app-layout1 .content {
position: absolute;
bottom: 60px;
top: 60px;
left: 0;
right: 0;
}
.app-layout1 .footer {
line-height: 60px;
bottom: 0;
left: 0;
right: 0;
position: absolute;
}
Sider + 上中下布局
使用 left: 150px,留出 Sider 的位置
.app-layout2 {
width: 500px;
height: 400px;
position: relative;
text-align: center;
}
.app-layout2 .header {
position: absolute;
left: 150px;
top: 0;
right: 0;
}
.app-layout2 .content {
position: absolute;
bottom: 60px;
top: 60px;
left: 150px;
right: 0;
}
.app-layout2 .footer {
bottom: 0;
left: 150px;
right: 0;
position: absolute;
}
.app-layout2 .sider {
width: 150px;
position: absolute;
top: 0;
left: 0;
bottom: 0;
}
进阶:侧边栏宽度可拖拽
样式布局
style.css
.layout {
position: relative;
width: 100%;
height: 400px;
}
.sider {
position: absolute;
top: 0;
left: 0;
bottom: 0;
background-color: #ddd;
}
.header {
background-color: #aaa;
height: 60px;
}
index.js
通过状态 siderWidth
控制 layout
的 paddingLeft
import { useState } from 'react';
import './style.css';
export default function ResizeLayout() {
const [siderWidth, setSiderWidth] = useState(150);
const pxWidth = `${siderWidth}px`;
return (
<div className="layout" style={{ paddingLeft: pxWidth }}>
<div className="sider" style={{ width: pxWidth }}>
sider
</div>
<div className="header">header</div>
<div className="content">content</div>
</div>
);
}
拖放逻辑
视觉上,我们拖拽的是侧边栏 Sider 的右边框,其实并不是。我们会在右边框位置放置一个“隐身”的 bar -- sider-resizer
:
.sider-resizer {
position: absolute;
width: 6px;
top: 0;
bottom: 0;
cursor: col-resize;
}
当鼠标在 sider-resizer
上,触发 onMouseDown
时,记录下鼠标的初始位置,同时标记 dragging
状态为 true
:
const handleMouseDown = (event) => {
setStartPageX(event.pageX);
setDragging(true);
};
伴随着 dragging
状态的改变,我们会为页面铺上一层遮罩 -- resize-mask
,以便后续 事件的监听:
{
dragging && (
<div
className="resize-mask"
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
/>
);
}
.resize-mask {
background: rgba(0, 0, 0, 0);
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
cursor: col-resize;
}
onMouseMove
的过程中,实时更新 siderWidth
以及对 startPageX
的更新,就会产生拖拽的效果。
最终在 onMouseUp
时,结束拖拽状态。
const handleMouseMove = (event) => {
const currentSiderWidth = siderWidth + event.pageX - startPageX;
setSiderWidth(currentSiderWidth);
setStartPageX(event.pageX);
};
const handleMouseUp = () => {
setDragging(false);
};
localStorage 存储宽度位置
在拖拽结束时,保存 siderWidth
到 localStorage
;初始化 siderWidth
时,检查 localStorage
是否有值。
const [siderWidth, setSiderWidth] = useState(
parseInt(localStorage.getItem('siderWidth')) || 150,
);
const handleMouseUp = () => {
setDragging(false);
localStorage.setItem('siderWidth', siderWidth);
};
完整代码
style.css
.layout {
position: relative;
width: 100%;
height: 400px;
}
.sider {
position: absolute;
top: 0;
left: 0;
bottom: 0;
background-color: #ddd;
}
.header {
background-color: #aaa;
height: 60px;
}
.sider-resizer {
position: absolute;
width: 6px;
top: 0;
bottom: 0;
cursor: col-resize;
}
.resize-mask {
background: rgba(0, 0, 0, 0);
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
cursor: col-resize;
}
index.js
import { useState } from 'react';
import './style.css';
export default function ResizeLayout() {
const [siderWidth, setSiderWidth] = useState(
parseInt(localStorage.getItem('siderWidth')) || 150,
);
const [dragging, setDragging] = useState(false);
const [startPageX, setStartPageX] = useState(0);
const pxWidth = `${siderWidth}px`;
const handleMouseDown = (event) => {
setStartPageX(event.pageX);
setDragging(true);
};
const handleMouseMove = (event) => {
const currentSiderWidth = siderWidth + event.pageX - startPageX;
setSiderWidth(currentSiderWidth);
setStartPageX(event.pageX);
};
const handleMouseUp = () => {
setDragging(false);
localStorage.setItem('siderWidth', siderWidth);
};
return (
<div className="layout" style={{ paddingLeft: pxWidth }}>
<div className="sider" style={{ width: pxWidth }}>
sider
</div>
<div className="header">header</div>
<div className="content">content</div>
<div
className="sider-resizer"
style={{ left: pxWidth }}
onMouseDown={handleMouseDown}
>
{dragging && (
<div
className="resize-mask"
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
/>
)}
</div>
</div>
);
}