ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

DIV+CSS布局核心原理与实战技巧:浮动、定位、Flex与Grid选型指南

DIV+CSS布局核心原理与实战技巧:浮动、定位、Flex与Grid选型指南 接手过一个老项目打开样式文件全是一水儿的float:left、position:relative当时第一反应是这代码该重写了。可等我把业务逻辑啃完才发现这些老布局其实稳稳当当地跑了好多年该自适应的时候自适应该换行的时候换行除了维护起来费点眼并没有想象中那么不堪。其实这恰恰说明DIVCSS 里的「DIV」是结构「CSS」是造型而浮动、定位这些属性就是让页面元素按我们想法各就各位的基础工具。我一直觉得不管现在 flex 和 grid 多香浮动与定位的底层逻辑仍然是必须掌握的东西。这篇就把 DIVCSS 布局的整体思路、核心属性、常见布局类型以及我这些年踩坑踩出来的实用技巧一次说清楚适合刚接触前端的初学者也适合那些一直在用框架、但很少回头看原生 CSS 的开发者。1. DIVCSS 布局的核心思路与底层逻辑1.1 为什么叫 DIVCSS而不是「网页布局」先扯个背景。早年网页布局用表格table非常普遍一个页面经常是table套table改一列宽度能把人看晕。后来大家发现把表现和结构分开会更灵活于是div这个无语义的块级容器配合 CSS 样式成了主流慢慢就有了「DIVCSS 布局」这个叫法。时至今日HTML5 已经给了我们header、main、footer、section等语义化标签严格说再用「DIVCSS」这个词已经不完全准确。但你随便打开一个后台管理系统的模板十有八九还是div满天飞。为什么因为div就像一块空白积木没有默认样式没有语义包袱你想让它充当什么角色都行。CSS 负责给它宽高、内外边距、浮动、定位最终拼出一个符合设计稿的页面。理解这件事有个好处当你遇到.container、.wrapper、.row、.col这类命名时不用纠结它们到底是不是div只要抓住「它们都是布局容器」这个本质就行了。真正决定元素位置的是盒模型、文档流、浮动和定位。1.2 布局三要素盒子模型、文档流、包含块我在带人的时候反复强调CSS 布局的第一课不是 flex也不是 grid而是盒子模型。每一个元素在页面上都会被渲染成一个矩形盒子从内到外依次是内容区content、内边距padding、边框border和外边距margin。实际项目里最容易被坑的就是宽度计算。默认盒模型下width: 300px只指定内容区宽度如果再给padding: 20px、border: 2px这个元素实际占用的宽度就是300 20*2 2*2 344px。所以很多老项目一上来就是全局重置* { box-sizing: border-box; }让width直接包含 padding 和 border这样写布局时心智负担会小很多。虽然通配符选择器确实有性能上的小开销但对现代浏览器来说可以忽略用它的好处远大于坏处。第二个底层概念是文档流。正常情况下块级元素从上往下排行内元素从左往右排这就是普通流。float和position: absolute会让元素脱离普通流这也是布局发生重叠、错位、塌陷的根源。第三个是包含块简单理解就是一个元素的定位参照物absolute定位往哪个方向偏移、百分比宽度按谁计算都取决于包含块是谁。后面定位这一章会专门展开。2. 浮动Float属性详解与清除浮动实战2.1 float 到底在干什么很多新手以为float是专门用来做页面布局的其实它最早的设计初衷是让文字环绕图片。在报纸排版里图片左边一张、右边一张文字会绕着图片排过去。网页里的float: left就是让元素向左浮动后面的内容、文字会从它的右侧绕过去。这一特性后来被用在了多列布局上给左边的侧边栏float: left再给右边的主体内容float: left两个盒子就能并排。但浮动有个非常折磨人的副作用父容器不再认为浮动的子元素还在「站队」里于是父元素高度会塌陷。我最初接手项目时看到一堆div styleclear:both/div就是当时流行的做法。要彻底理解浮动记住三句话浮动元素脱离普通流但保留部分参与文本流浮动元素会向左或向右靠拢直到碰到父容器边缘或另一个浮动元素浮动元素会让其父容器的高度失去对它的感知。2.2 清除浮动的几种方式既然浮动会导致父容器塌陷那就要「清除浮动」。网上方案很多我自己最推荐用伪元素的方式不增加多余 DOM 节点代码也干净.clearfix::after { content: ; display: block; clear: both; }只要给需要包裹浮动子元素的父容器加上clearfix这个类父容器就能重新把浮动子元素的高度计算进来。原理其实不复杂clear: both表示元素左右两边都不能出现浮动元素这个伪元素被排到所有浮动子元素的下面硬生生把父容器的高度「撑」回来。还有种常见方式是给父容器加overflow: hidden也能触发 BFC块级格式化上下文让父容器包住浮动元素。但它有隐患一旦父容器里出现需要溢出的内容比如下拉菜单就会被裁掉。所以我只在确定不会有溢出内容时才用。2.3 一个浮动两栏布局小示例假设要做一个左侧 220px 固定、右侧自适应的两栏布局最传统的写法是这样的div classwrap clearfix div classsidebar侧边栏/div div classmain主内容/div /div.wrap { max-width: 1200px; margin: 0 auto; } .sidebar { float: left; width: 220px; background: #eee; } .main { float: left; width: calc(100% - 220px); background: #fafafa; }这里我给右侧同样用了浮动。如果不给右侧浮动它默认会占满整行但内容区域会从侧边栏右侧开始绕排文字排列没问题背景和边框却会铺满整个父容器宽度看起来像是布局错乱。用calc()计算剩余宽度时要注意运算符两边必须有空格否则某些浏览器会识别失败。浮动布局虽然老但并没有彻底退休。很多邮件客户端对 flex 和 grid 支持不好写邮件模板时依然要靠 table 和 float 撑场面。将来你遇到这类需求浮动就是救命稻草。3. 定位Position属性从相对到粘性3.1 五种定位值详解定位属性position是布局里跟浮动并列的核心。它的取值分别是static、relative、absolute、fixed和sticky。我给新手的解释方式很简单static默认值元素就在文档流里老老实实待着。relative元素还在文档流里原来的位置保留但可以通过top、right、bottom、left让它相对于原本位置移动。移动后原来占的位置不会空出来其他元素不会被挤动。absolute元素彻底脱离文档流相对于最近的「已定位」祖先元素定位。如果祖先都没有定位就相对初始包含块通常是浏览器窗口定位。fixed脱离文档流相对于视口定位页面滚动时它纹丝不动。典型应用是吸顶导航和悬浮按钮。sticky这是一个相对新一点的值元素先按普通的文档流排列滚动到指定阈值后就被「粘」在某个位置相当于relative和fixed的结合体。举个例子一个返回顶部的按钮通常就这么写.back-top { position: fixed; right: 20px; bottom: 40px; width: 48px; height: 48px; z-index: 99; }sticky这些年浏览器支持已经相当好用来做吸顶表头很方便.table-header { position: sticky; top: 0; }要注意的是sticky的父容器必须有足够的高度供它「粘」而且它不能是父容器的最后一个子元素否则没有滚动余量。3.2 定位元素的层级与包含块用absolute最常犯的错是明明写了对的left和top元素却跑到了浏览器左上角。原因多半是它找不到一个已定位祖先。所以标准做法是.modal-wrap { position: relative; } .modal { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }relative在这里的意义不是为了让模态框自己动而是给内部的absolute元素提供一个包含块。这个技巧在写弹窗、菜单、气泡提示时非常常用也是后台页面的高频需求。z-index是另一个容易迷惑的点。它不是简单按数值大小排优先级还要看元素是否创建了层叠上下文。position: relative配合z-index: 1就能让某个元素盖过另一个没定位的兄弟元素。但如果两个父级各自形成了层叠上下文子元素的z-index只能在各自的父级上下文里做比较。所以页面里没事别轻易给太多元素加position和z-index否则后面弹窗层级会越来越难控制。3.3 弹窗与遮罩的实现一个最常见的div弹窗结构是遮罩层加弹窗主体。遮罩要求覆盖全屏弹窗要求居中显示并盖在遮罩上面div classmask div classdialog这是弹窗内容/div /div.mask { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.5); z-index: 1000; display: flex; align-items: center; justify-content: center; } .dialog { width: 320px; padding: 24px; background: #fff; border-radius: 8px; }inset: 0是top/right/bottom/left的简写遮罩层被拉伸到视口每个角落。再把display: flex放到遮罩层上弹窗就能水平和垂直同时居中不用再靠transform去猜。这个写法兼容性和可读性都不错我现在依然推荐。4. 布局类型传统布局、Flex 与 Grid 该怎么选4.1 传统 DIVCSS 布局固定宽度与流式布局传统布局大致可以分成固定宽度和流式两种。固定宽度布局喜欢用width: 1200px; margin: 0 auto把整个页面框在中间设计时按桌面端来简单直接但在小屏上会出现横向滚动条体验比较差。流式布局则用百分比宽度比如左侧25%、右侧75%窗口变大变小元素跟着变但尺寸在小屏下又会变得很难控制。后来流行过一段时间的「响应式布局」本质还是这两类布局思想的延伸只不过用media媒体查询在不同断点下切换布局规则。我在老项目里经常见到这种写法media (max-width: 768px) { .sidebar { float: none; width: 100%; } .main { float: none; width: 100%; } }这样小屏下两栏会变成上下堆叠视觉上不会有挤压问题。今天再做这类布局我更建议直接上 flex 或者 grid但理解百分比和浮动的关系对排查旧项目依然很有用。4.2 Flex 布局一维排列的救星Flex 布局弹性盒子解决的是「在一条线上怎么摆」的问题它替代了大量过去用浮动和 inline-block 硬凑的写法。核心思路是给父容器设置display: flex子元素自动变成弹性项目排列方式由flex-direction决定。.card-list { display: flex; gap: 16px; flex-wrap: wrap; } .card { flex: 1 1 240px; }flex: 1 1 240px表示项目基础宽度 240px可以放大也可以缩小有多余空间时大家按比例分空间不足时自动换行。用gap设置间距比挨个设置margin省事得多还能避免最后一个元素的右边距多出来。做两栏自适应布局时flex 也远比浮动直观.layout { display: flex; } .sidebar { width: 220px; flex-shrink: 0; } .main { flex: 1; }flex-shrink: 0保证了侧边栏不会被压缩。右侧只需要写flex: 1就能把剩余空间全部吃掉。这种代码读起来基本不用过脑子所以我跟新人讲布局都是先讲 flex 再补 float 和 position。4.3 Grid 布局二维平面直接画格子Grid 是真正为二维布局而生的。它把容器划分成行和列子元素可以直接放进对应的格子。如果你要做一个三行两列的卡片面板用 flex 需要各种嵌套和换行控制用 grid 却只需几行.dashboard { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }repeat(3, 1fr)表示三列等分1fr是剩余空间的一等份。想要侧边栏宽 220px、剩余给主区域可以写.app-layout { display: grid; grid-template-columns: 220px 1fr; grid-template-rows: auto 1fr; min-height: 100vh; }这算是 admin 后台最常见的整体骨架左边固定侧边栏右边顶部导航加内容区底部分栏自动撑满。Grid 在二维规则的页面结构里属于降维打击但如果你只是想让一个按钮组水平居中用 grid 就有点大材小用了。我做一个简单的对比表布局方式适用场景核心优势常见痛点浮动布局邮件模板、老项目维护兼容性极好原理简单高度塌陷清除浮动繁琐Flex 布局导航栏、卡片列表、一维排列书写简单子项分配能力强二维布局需要嵌套Grid 布局页面骨架、仪表盘、二维区域直接定义行列语义清晰知识量稍大老浏览器不支持Position 定位弹窗、悬浮按钮、下拉菜单脱离文档流层级可控需要明确包含块易重叠5. 常见布局技巧整理从水平垂直到粘性底部5.1 水平垂直居中的三种经典方式「怎么把 input 居中」几乎是知乎上问烂了的问题。先说结论要看它的父容器是不是设置了高度、它是行内元素还是块级元素不能套一个万能答案。第一种绝对定位加 transform.parent { position: relative; height: 300px; } .child { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }这种方式不关心子元素宽高但transform会创建层叠上下文如果子元素里面有 fixed 定位的内容可能会受影响。第二种Flex 居中.parent { display: flex; align-items: center; justify-content: center; }这算是我目前最常用的方式简单、可靠、不破坏文档结构。input 里直接套居中同样适用。第三种Grid 居中.parent { display: grid; place-items: center; }place-items: center是align-items: center和justify-items: center的简写。Grid 居中代码量更少但浏览器要求更高一点现代项目完全可以用。5.2 左右两栏与中间自适应除了上面提到的 flex 写两栏还有一种古老的「圣杯布局」或者「双飞翼布局」目标都是中间列优先加载并且自适应左右两列固定宽度。用 float 实现时中间列的 DOM 要放在最前面同时用负 margin 把它拉回正常位置理解成本很高。现在用 flex 做兄弟布局就清爽多了。左边和右边固定宽度中间flex: 1甚至两边都可以根据内容自动收缩。我实际写页面时会加一行min-width: 0到中间列防止里面的长文本或表格把 flex 子项撑破这是很多人容易忽略的。5.3 吸底 Footer 与页面高度问题很多后台页面的布局是头部、内容、底部三块希望内容不足一屏时底部仍然贴着视口底部。传统做法需要算min-height现在用 flex 或 grid 轻轻松松html, body { height: 100%; } .page { display: flex; flex-direction: column; min-height: 100vh; } .content { flex: 1; }min-height: 100vh保证页面容器至少和视口一样高内容区域flex: 1再去挤占剩余空间底部自然被推到页面最底。这套方案在移动端也适用只要注意不要给 body 额外加height: 100%导致某些浏览器滚动异常即可。另外一个小技巧很多页面顶部是position: sticky的吸顶导航滚动时希望它吸附在视口顶部。给导航加position: sticky; top: 0; z-index: 100;就够不需要用 JS 监听滚动事件。这个属性对父容器层级有要求如果中间隔了overflow: hidden吸附就会失效需要把overflow放在导航容器外层而不是祖先上。5.4 用状态伪类切换布局观感布局并不只是静态的。常见的菜单展开、按钮悬停变色背后其实是「CSS 鼠标移入事件」这类状态伪类在起作用。比如一个下拉菜单过去要写一大串 JS现在只需要.nav-item:hover .dropdown { display: block; } .dropdown { display: none; }在移动端上hover的行为不稳定所以更稳的做法是配合focus-within.dropdown-wrap:focus-within .dropdown { display: block; }这个属性在今天已经相当普及用来做纯 CSS 的展开折叠、Tab 切换都很合适。布局不光是写死的位置利用状态改变元素的display、visibility、transform会让页面交互层次丰富起来。6. 布局错乱问题排查手册6.1 绝对定位的父级不是父级症状子元素position: absolute; top: 10px结果它跑到了浏览器窗口某处而不是父元素左上角。原因几乎都是父元素没有设置position: relative。排查方法很简单在浏览器开发者工具里选中子元素看它的「包含块」区域到底是谁。修复时在父元素上加position: relative即可。如果父元素本身也要随滚动移动加relative不会对它产生明显的副作用所以很多团队干脆把布局容器一律设置成position: relative。6.2 flex 子项被压缩与换行问题症状一行放了四个卡片到了小屏上每个卡片都挤成了细条或者高度被压扁。原因很可能是子项默认的flex-shrink: 1在起作用。解决方式不需要压缩的固定栏目加flex-shrink: 0。希望多个子项按比例缩放用flex: 1 1 0%或者flex: 1。希望自动换行让flex-wrap: wrap开启并给子项设置合理的基础宽度。flex 的默认行为里flex: 0 1 auto其实是「不放大、可缩小、以内容为基准」。新手看到元素宽度总和自己设定不一致八成是没搞懂这三个值的组合方式。6.3 浮动高度塌陷与重叠症状父容器背景没有包住浮动的子元素或者后面一个模块跑到了浮动元素后面、甚至叠到了一起。要么给父容器加clearfix要么用overflow: hidden触发 BFC。如果用户遇到的是两个浮动模块之间的间距不对多半是父容器宽度不够、或某个子元素的margin穿过了父容器边界需要给父容器加overflow: hidden或者padding。浮动布局里负 margin 的使用要格外谨慎它能让元素叠在一起也最容易引发错乱。6.4 布局错乱速查表以下是我在实际项目中整理出来的一张排查速查表遇到问题可以按症状对号入座症状可能原因处理方向页面出现横向滚动条某个元素宽度超出视口检查固定宽度、长单词、表格min-width子元素位置不对父元素没设置relative补上定位祖先并确认包含块弹窗盖不住其他元素层级上下文被兄弟节点隔断检查各层z-index和transformflex 布局间隙异常使用了margin而没用gap优先用gap或计算好负 margin滚动时吸顶失效祖先元素有overflow: hidden/auto将滚动属性放在独立容器图片放大后挤压文字没有设置max-width: 100%给图片加全局响应式样式页面底部空白body 或根节点height: 100%设置不对用min-height: 100vh替代6.5 关于布局重叠与输入框居中的补充「布局重叠」这个词我在排查时经常遇到很多情况下是absolute定位的元素互相盖住。我的建议是能用 flex 和 grid 排的位置就不要轻易用绝对定位去「硬摆」。绝对定位更适合弹窗、角标、悬浮按钮这类脱离文档流场景。如果一个页面里绝对定位的数量超过了五个说明布局思路可能出了问题需要重新设计结构。至于 input 居中其实要区分两层input 居中的是它那一个盒子还是它相对父容器居中。前者把 input 设置成块级后加margin: 0 auto或者直接给父容器text-align: center因为 input 默认是行内块元素可以被文本对齐属性影响。后者刚才已经说过用 flex 或 grid 都能解决。别为了一个居中写十几行 CSS简单问题要用简单方案。我个人的习惯是新项目默认 flex 为主、grid 做骨架、position 处理浮动层、float 只在必须兼容老客户端时才碰。但我也绝不会删掉老项目里那些好看的浮动写法因为「能用」和「好维护」有时候是两回事。布局的最终目标从来不是炫技而是让页面在不同屏幕、不同交互状态下都表现稳定。每次布局错乱问题的答案几乎都藏在这几个基础属性里把它们吃透比收藏一百个工具函数都管用。
返回列表