布局方式 (上) - 总结与对比
页面布局的方式总是随着技术的更新和创意的涌现不断的更新换代。从最开始的 固定布局「Fixed」到因为宽屏设备和移动互联网的普及产生的响应式和自适应,再到「Mobile First」的设计理念,前端们在可用性和适配性上做的贡献越来越多
页面布局是css的一个重点应用
网页布局类型常见的布局方式大致有以下几种:
- 固定布局 【Fixed】
- 流式布局 【Fluid】
- rem布局 【Rem】
- 弹性布局 【Flexbox】
- 响应式布局 【Responsive】
1. 固定布局 [px]
顾名思义,页面上的所有元素的尺寸一律使用px,pt
这种固定单位编写,在<head>
里把
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0">
然后根据设计稿的宽高设定实现稿即可。
这种方式简单易上手,缺点显而易见,不能根据不同的屏幕做出不同的表现。例如直接设定主体宽度,然后居中展示,两边留白。宽屏两边留白多,窄屏两边留白少。
移动端使用固定布局
- 在
viewport meta
标签上设置content = “width=320”
, 页面的各个元素也采用px
作为单位。通过用JS动态修改标签的initial-scale使得页面等比例缩放, 从而刚好占满整个屏幕。这种方案类似弹性布局的rem。 - 在
viewport meta
标签上设置content=“width=640,user-scalable=no”
,页面的各个元素采用px作为单位。由于手机浏览器的宽度小于640px(逻辑像素)(iphone plus系列为414px),所以浏览器viewport
会自动缩放至全屏大小。经典案例:荔枝FM,人人都是播客的移动站
优势:
- 开发简单: 缩放交给浏览器,开发人员不需要自己操心,完全按照视觉稿切图
- 还原精准: 绝对等比例缩放,可以精准的还原原始视觉效果(会有清晰度的失真)
- 测试方便: 可以在pc端实现大部分的测试工作,即开发人员自己就能完成测试的大部分工作,手机端只要修改一些细节。
劣势:
- 像素丢失: 对于一些分辨率较低的手机,可能设备像素还未达到viewport指定的宽度,此时可能出现边框丢失等问题。现代手机基本不会发生这种情况。
- 缩放失效: 早期android手机不能根据meta中的width来进行缩放,需要配合initial-scale。这种完全依赖浏览器的方式也难免可能发生异常。
- 文本折行: 会发生在缩放失效的机型中。
2. 流式布局 [%]
网页中主要使用百分比(%)来划分区域,往往配合max-*,min-*
等属性控制尺寸流动范围以免过大或者过小影响阅读,让不同分辨率屏幕尽可能实现自适应。
例如,设置网页主题宽度为80%,min-width
为960;图片也做类似处理(例如 width:100%; max-width:图片原始尺寸,防止拉伸
)。这种开发方式在早期的web开发时用来对应不同的PC屏幕(那时不会相差太大),现在也常用在移动端开发上。
缺点:
- 宽度使用百分比定义,但高度和文字大小都是用px来固定,所以在大屏幕手机下的显示效果有些元素(例如
width=100%
)会被拉伸的很长,但是高度,文字大小还是和原来一样(即有些元素无法变得流式,显得很不协调)。
3. rem布局
REM,即font size of the root element,使用 rem 单位进行相对布局,通过根元素进行适配,它好比是一个中介,通过它计算出页面真正要展示的大小, 达到自适应效果。(有时需要配合媒体查询),常用于移动端开发。
rem
避免了根据 px 布局
在高分辨率下几乎无法辨认的缺点,又相对 % 百分比
更加灵活,同时可以支持浏览器的字体大小调整和缩放等的正常显示。一般编写时需要对UI稿做转换,也有编辑器可以安装单位转换插件。例如 ATOM 的 px2rem 插件
实现 rem
的核心,就是针对并监听不同尺寸的手机屏幕计算出相应的比例,以下代码通常写在app.js
(第一个加载的js文件)最上方:
// rem布局的核心代码。 此例默认UI稿按 1080 * 1920 提供
(function(win, doc){
var docEl = doc.documentElement,
resizeEvt = 'oritationchange' in window ? 'oritationchange' : 'resize',
recalc = function(){
var clientWidth = docEl.clientWidth;
if (!clientWidth) return;
if (clientWidth < 640) {
docEl.style.fontSize = 100 * (clientWidth / 1080) + 'px';
} else {
docEl.style.fontSize = 100 * (clientWidth / 1920) + 'px';
}
if (!doc.addEventListener) return;
win.addEventListener(resizeEvt, recalc, false);
doc.addEventListener('DomContentLoaded', recalc, false);
})(window, document)
解释一下:
- 当浏览器将我们的项目下载完成时,开始解析DOM树,触发
doc.addEventListener('DomContentLoaded', recalc, false)
,执行recalc
方法,计算出当时条件下的适配比rem,执行页面渲染。当用户手机屏幕发生横竖屏翻转时,BOM捕捉到事件变化, 触发win.addEventListener(resizeEvt, recalc, false)
执行recalc
方法,重新计算新的适配比rem,页面响应变化。 -
if (clientWidth < 640 )
监控并区分用户手机屏幕是处在横竖屏什么状态下,640px是手机屏幕的最大安全宽度,超过640就认为是横屏状态。 - 至于缩放比是要设置 100,10 还是 24(移动端常用大小),你开心就好。 总之,计算规则就是 【 1px * 缩放比 == 1rem 】
这种布局方式编写简单,适配能力强,容易理解,易于维护。
# 但 rem也不是万能的,比如:
- 用户修改字体大小,使得某些元素不能展示原有效果出现换行现象。
- 用户修改显示模式,触发逻辑像素被改变,rem布局不能感知这种状态,会出现元素偏移的现象
- 当用户切换横竖屏需要展示不同效果时,rem无法实现。
解决办法:问题1 可以配合css位置属性 或局部使用其他的的布局方式(如以下将要介绍的 flex布局)调整恢复;问题2可以使用vw/vh单位局部切换实现样式;问题3 可以使用媒体查询 或通过js动态修改样式。
4. 弹性布局 [flex]
flex布局是W3C在2009年提出的方案。 传统的布局方式,基于盒模型,依赖于display
+ position
+ float
等属性,他们对于特殊布局非常不方便,比如垂直居中。而flex可以为盒模型提供最大的灵活性。
任何一个容器都可以指定为flex布局,包括行内布局。如果是webkit内核,必须加-webkit-
前缀
<div class="box">
<span class="item"></span>
</div>
.box {
-webkit-display: flex;
display: flex
}
span {
display: inline-flex;
}
注意:设置为Flex布局以后,子元素的float,clear
和vertical-align
属性将会失效。
# flex基本概念: ‘容器’ - ’项目’ - ’轴’
flex布局结构示意图-
轴【axis】: 水平的主轴
main axis
的起始位置为main start
,结束的位置为main end
; 垂直的交叉轴cross axis
的起始位置为cross start
,结束位置为cross end
; -
容器【flex container】和 项目【flex item】: 项目在容器内部,默认沿着主轴排列,单个项目占据主轴为
main size
, 占据交叉轴为cross size
;容器可以简单理解为父元素,项目可以简单理解为子元素
项目其实可以理解为 子容器,对于flex,核心点只有两个:容器 和 轴
# 为容器增加属性 -【父元素】
1. flex-direction (元素排列方向),可选值:(默认)水平、水平右到左、垂直、垂直下到上
.box { flex-direction: row | row-reserve| column | column-reserve }
2. flex-wrap (元素的换行方式),可选值:(默认)不换行、往下换行、往上换行(第一行在下方)
.box { flex-wrap: nowrap | wrap | wrap-reserve }
3. flex-flow (以上两者的简写),默认为: 水平 || 不换行
.box { flex-flow: flex-direction || flex-wrap }
// 例
.box { flex-flow: row nowrap; }
4. justify-content (主轴对齐方式),可选值:(默认)左对齐、右对齐、居中、充满两边靠边、充满两边留边
.box { justinfy-content: flex-start | flex-end | center | space-between | space-around }
5. align-items (交叉轴对齐方式),可选值:上对齐、下对齐、中部对齐、(默认)拉伸至上下占满、首行文字的基线对齐
.box { align-items: flex-start | flex-end | center | stretch | baseline }
6. align-content (多根轴线的对齐方式) 可选值:上对齐、下对其、中部对齐、(默认)均匀拉伸至上下占满、充满上下中部均分、充满上下两边留空中部均分
.box { align-content: flex-start | flex-end | center | stretch | space-between | space-around }
# 为项目增加属性 -【子元素】
1. order (项目的排列顺序),可选值: 数值(越小越靠前,相同按文档流顺序展示)
,item { order: <integer>; } /* default 0 */
2. flex-grow (项目的放大比例),可选值:数值(0表示按原大小展示;都定义为1表示所有项目均分可用空间;定义某个为2其余都为1,则2的占用1的两倍然后所有项目占满空间)
.item { flex-grow: <number>; } /* default 0 */
3. flex-shrink (项目的缩小比例),可选值:数值(0表示按原大小展示;都定义为1表示空间不足时等比缩小;定义某个为0其余都为1,空间不够时0的不缩小,其余按等比缩小。负值无效)
.item { flex-shrink: <number>; } /* default 1 */
4. flex-basic (项目占用主轴空间), 可选值:(根据flex-direction
定义的主轴(水平或垂直)来定义项目本来的大小,跟width/height
一样;如果设定跟width
和height
一样的值,则项目将占据固定空间)
.item { flex-basic: <length> | auto; } /* default auto */
5. flex (前三者的简写【推荐】),可选值:auto
(1 1 auto) 、none
(0 0 auto)、自定义值(n, n, length);auto
表示空间大了自动放大,空间小了自动缩小;none
表示无论空间如何,大小固定不变。推荐优先使用auto
或none
/* default 0 1 auto ; 空间大了保持不变,小了自动缩小*/
.item { flex: auto | none | [<'flex-grow'> <'flex-shrink'>? || <'flex-basic'>] }
flex
5. align-self (某项目和其他项目不一样的对齐方式,覆盖align-item
),可选值:auto
(继承父元素align-items
)、上对齐、下对齐、中部对齐、首行文字基线对拉伸;要区分align-items
是设置在容器上的属性,align-self
是设置在项目上的属性。
/* default auto */
.item { align-self: auto | flex-start | flex-end | center | baseline | stretch; }
# flex 总结
flex属性树5. 响应式布局 [media query]
响应式布局的设计思路和其他布局思路大同小异,都是为了同一套代码适配不同媒体设备。因为由于越来越多的移动设备加入到互联网大军中,移动端将不能再被忽视,移动终端的分辨率和pc端有较大差异,为了让用户的体验更好,代码就需要兼容不同的屏幕。
主要分两类: RWD响应式 和 AWD自适应,
- RWD = (Responsive Web Design):media query + 流体布局 + 自适应图片/视频资源
-
AWD = (Adaptive Web Design):media query + js 操作dom + 在服务端操作dom
这里主要介绍响应式:
很粗糙的解释,步骤大致是: - 编写非响应式代码(适配主要屏幕的设备代码,遵循 Mobile First)
- 加工成响应式代码 (增加媒体查询语句)
- 响应式细节处理,代码自测
- 完成响应式开发
# 实现响应式布局设计具体步骤
1.布局及设置meta标签
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="HandheldFriendly" content="true">
主要是为了让代码适应移动端的屏幕。解释一下,viewport
是指需要加载meta标签时读取的名字为‘视口’,其中,width
等于设备的宽度device-width
,是为了告诉浏览器,将布局视口的宽度设置为设备理想的宽度,initial-scale=1
(缩放比例)保证加载的时候,要获取设备的宽度,同时要保持原始大小,便于媒体查询。user-scalable=no
指定不允许用户缩放屏幕。
2. 通过媒体查询来设置响应式样式
media query 是响应式布局的核心,他们够和浏览器进行沟通,告诉浏览器页面改如何呈现,假如要适配一个大小为414逻辑像素的屏幕(iphone6 plus),应该这么写:
我喜欢设置范围,也可以设置具体值:(max-width: 414px)
@media screen and (max-width < 500) {
.my_class {
font-size: 24px;
color: #3d77e0;
}
}
然后我们将该片段放置在样式文件的尾部,以保证覆盖非响应代码的样式。再如:假如我们要兼容iPad和iphone,应该这么设置:
@media screen and (min-width>768px) and (max-width<1024px) { } // ipad
@media screen and (min-width>320px) and (max-width<767px) { } // iphone
3. 字体设置-推荐rem
在css3之前,大部分开发人员使用的都还是px,但px是绝对单位,并不能响应随着屏幕变化而变化的父容器,css3引入了rem,完美解决此问题。注意不要忘记响应变化时重置根元素的大小。
html { font-size: 24px; }
@media ( min-width>640px ) { html {font-size: 28px} body { font-size: 1.2rem } }
@media ( min-width>767px ) { html {font-size: 32px} body { font-size: 1.2rem } }
@media ( min-width>960px ) { html {font-size: 36px} body { font-size: 1.2rem } }
其实这里就是使用的rem布局的知识,不理解的建议回头看看rem布局。
4.需要注意的点
(1):宽度不能固定,可以使用百分比,即流体(fluid grids)
body { width: 100%; }
.content { width: 50%; }
(2):图片处理
html中的图片,为了最大化让图片保持原始效果,可以如下这么设置:
.wrap img {
width: auto;
max-width: 100%;
height: auto;
}
这么设计是为了:当图片小于给定空间时按原始尺寸显示,当超出时就缩小至容器大小。
- 除了img标签的图片外我们经常会遇到背景图片,比如logo做背景,可以这么设置:
.box {
background: url('icon/logo.png') no-repeat;
background-size: 100% 100%; // css3
}
background-size
是css3新增的属性,用来设置背景图片的大小,有两个可选值,表示width
和height
,如果只指定一个,另一个则为auto
。设置100% 100%
使图片总是充满元素容器,这有可能使得图片变形。另外
background-size: cover;
表示等比扩充比例用来填满元素,这有可能使得图片内容显示不全,但不会被拉伸或挤压。
background-size: contain;
表示等比缩小比例来适应元素的尺寸,则有可能使得元素容器部分区域空白。
- 还可以使用
::before
和::after
伪元素 +content
属性来动态显示一些内容。
6. 最后 但同样重要
自习对比几种布局方式可以发现,各大布局并不独立,反而相辅相成,互相渗透,想单独用某种布局来实现一个产品很难,我们也没必要这么做。
目前采用最多的是 rem布局
+ 响应式布局的 media query
,基本能实现较为复杂的页面效果,当然还有一些效果如垂直居中,也能引入弹性布局flex
来实现;容器、图片、背景图片设置,流式布局的效果相对好一些;如果没有特殊需求,尽量还是少用绝对布局,坑太多不好维护。
总之,遵循一个原则,结合具体需求,那种适配性高些,哪种后期维护容易些,就使用哪种。
# 最后的最后,但不重要
我的下一篇文章: 《布局的具体设计与实现》,如果觉得我写的还不错的朋友,可以关注我,我们一起成长。