响应式布局适配方案
- 传统 float 浮动布局
知识储备:侯策《前端开发核心知识进阶》读书笔记——html与css要点
可以看出来,这种实现方式比较传统,且能力较弱。
- 相对单位布局
css中相对的布局单位有以下几种:em、rem、vh、vw、vmin、vmax、%、calc()
em 相对于当前元素或当前元素继承来的字体的宽度,但是每个字母或汉字的宽度有可能是不一样的,那么一般来说,就是一个大写字母 M 的宽度(事实上,规范中有一个 x-height 概念,建议取 X 的高度,但并没有推荐绝对的计算执行标准,还需要看浏览器的实现,也有的地方采用 O 的高度);一个非常容易出错的点在于:很多同学会认为 em 相对于父元素的字体大小,但是实际上取决于应用在什么 CSS 属性上。对于 font-size 来说,em 相对于父元素的字体大小;line-height 中,em 却相对于自身字体的大小。
rem 相对于根节点(html)的字体大小,根节点一个大写字母 M 的宽度(同上)
以 rem 为核心,诞生了淘宝的 flexible 响应式布局的方案。
vw 相对于视口宽度,100vw 就相当于一个视口宽度
vh 同理,1vh 表示视口高度的 1/100,100vh 就是一个视口高度
vmin 相对于视口的宽度或高度中较小的那个,也就是 1vw 和 1vh 取最小(Math.min(1vw, 1vh));vmax 相对于视口的宽度或高度中较大的那个,(Math.max(1vw, 1vh))
calc 也是一个响应式布局神器,它使得 CSS 有了运算的能力
calc(100vw - 80px)
- 媒体查询
- 基于相对单位 rem 的 flexible 布局
- flex 布局
- grid 布局
- 借助 JavaScript
p { height: var(--test-height); } function changePHeight (height) document.documentElement.style.setProperty('--test-height', `${height}px); }
响应式布局的概念
- 屏幕分辨率
- 像素
- PPI(Pixel Per Inch):每英寸包括的像素数
- DPI(Dot Per Inch):即每英寸包括的点数
- 设备独立像素
- 设备像素比(dpr)
- Meta Viewport
响应式布局案例
淘宝
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover">
禁用了用户缩放功能,使页面宽度和设备宽度对齐,一般这种操作也是移动端的响应式适配的标配。
页面根节点 HTML 元素上,显式设置了 font-size:
当改变浏览器大小时,html 的 font-size 会动态变化。采用 rem 作为相对单位的长宽数值,都会随着 resize 事件进行变化。
!function(e, t) { var n = t.documentElement, d = e.devicePixelRatio || 1; function i() { var e = n.clientWidth / 3.75; n.style.fontSize = e + "px" } if (function e() { t.body ? t.body.style.fontSize = "16px" : t.addEventListener("DOMContentLoaded", e) }(), i(), e.addEventListener("resize", i), e.addEventListener("pageshow", function(e) { e.persisted && i() }), 2 <= d) { var o = t.createElement("body"), a = t.createElement("div"); a.style.border = ".5px solid transparent", o.appendChild(a), n.appendChild(o), 1 === a.offsetHeight && n.classList.add("hairlines"), n.removeChild(o) } }(window, document)
这是一个 IIFE,在 DOMContentLoaded、resize、pageshow 事件触发时,进行对 html 的 font-size 值设定,计算方式为:
font-size = document.documentElement.clientWidth / 3.75
当然淘宝实现响应式布局除了依靠 rem 以外,还大量运用了 flex 布局:
网易
网易的做法大体类似,同样采用了 rem 布局,但区别是网易并没有 JavaScript 介入计算 html 的 font-size,而是通过媒体查询和 calc 手段,“枚举”了不同设备下不同的 HTML font-size 值。
slider 宽度明显是 JavaScript 获取设备宽度后动态赋值的(图中为 414px),而高度采用了 rem 布局: 3.7 rem = 55.3px(calc(13.33333333vw) * 3.7)
Bootstrap 栅格
直接进行代码分析:
<div class="col-6 col-sm-3"> ... </div> <div class="col-6 col-sm-3"> ... </div> <div class="col-6 col-sm-3"> ... </div> <div class="col-6 col-sm-3"> ... </div>
.col-6 class 和 .col-sm-3 class样式在源码里面可以简单归纳(不完全)为:
.col-6 { -webkit-box-flex: 0; -webkit-flex: 0 0 50%; -ms-flex: 0 0 50%; flex: 0 0 50%; max-width: 50%; } .col-sm-3 { -webkit-box-flex: 0; -webkit-flex: 0 0 25%; -ms-flex: 0 0 25%; flex: 0 0 25%; max-width: 25%; }
类似 flex: 0 0 25%
的声明,为了理解它,我们从 flex 属性入手:flex 属性是 flex-grow、flex-shrink 和 flex-basis 的简写(类似 backgroud 是很多背景属性的简写一样),它的默认值为 0 1 auto,后两个属性可选。语法格式如下:
.item { flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ] }
- flex-grow:属性定义项目的放大比例,默认为 0。我们看到 Bootstrap 代码里这个值一直为 0,即如果存在剩余空间,也不放大。
- flex-shrink:属性定义了项目的缩小比例,默认为 1,即如果空间不足,该项目将缩小。
- flex-basis:属性定义了在分配多余空间之前,项目占据的主轴空间(main size)。
浏览器根据这个属性,计算主轴是否有多余空间,它可以设为跟 width 或 height 属性一样的值(比如 350px),则项目将占据固定空间。
Bootstrap 这里对 flex 设置为比例值,这也是响应式自然而然实现的基础。
如何做到在不同的屏幕比例下,只有一个属性发生作用呢?
我们在源码里发现 .col-sm-* 的样式声明全部在
@media (min- 576px) {...}
的媒体查询中,这就保证了在 576px 宽度以上的屏幕,只有在媒体查询之外的 .col-* 样式声明发挥了作用。
在屏幕宽度小于 576px 时候,命中媒体查询,命中 .col-sm-3 的样式声明。它的优先级一定大于 .col-6(媒体查询优先级高),这时候就保证了移动端的样式“占上风”。
再结合 col-6 col-sm-3 的样式声明,我们可以简单总结一下:Bootstrap 主要是通过百分比宽度(max- 50%; max- 25%;),以及 flex 属性,再加上媒体查询,“三管齐下”实现了栅格化布局的主体。
横屏适配
横竖屏适配代码之javascript检查
window.addEventListener("resize", () => { if (window.orientation === 180 || window.orientation === 0) { console.log('竖屏') }; if (window.orientation === 90 || window.orientation === -90 ){ console.log('横屏') } })
横竖屏适配代码之css实现
@media screen and (orientation: portrait) { /*竖屏样式代码*/ } @media screen and (orientation: landscape) { /*横屏样式代码.*/ }
其他常见的响应式布局话题:
- 1px 问题
由于不同的手机有不同的像素密度导致的。如果移动显示屏的分辨率始终是普通屏幕的2倍,1px的边框在devicePixelRatio=2的移动显示屏下会显示成2px,所以在高清屏下看着1px总是感觉变胖了。
解决方法有以下几个:
1、在ios8+中当devicePixelRatio=2的时候使用0.5px
p{ border:1px solid #000; } @media (-webkit-min-device-pixel-ratio: 2) { p{ border:0.5px solid #000; } }
2、伪元素 + transform 实现
.scale-1px{ position: relative; border:none; } .scale-1px:after{ content: ''; position: absolute; bottom: 0; background: #000; width: 100%; height: 1px; -webkit-transform: scaleY(0.5); transform: scaleY(0.5); -webkit-transform-origin: 0 0; transform-origin: 0 0; }
3、viewport + rem 实现
在devicePixelRatio = 2 时,输出viewport:
在devicePixelRatio = 3 时,输出viewport:
使用box-shadow模拟边框
利用css 对阴影处理的方式实现0.5px的效果
.box-shadow-1px { box-shadow: inset 0px -1px 1px -1px #c8c7cc; }
- 适配 iPhoneX 齐刘海
#define TabbarHeight ([[UIApplication sharedApplication] statusBarFrame].size.height>20?83:49) // 适配iPhone x 底栏高度 _tabBarView.frame = CGRectMake(0, CurrentScreenHeight - TabbarHeight, CurrentScreenWidth, TabbarHeight);
- 图片自适应
inline-block设置
.img-responsive { display: inline-block; height: auto; max-width: 100%; }
flex设置
div { width: 500px; height: 500px; /* 如果需要让图片各方位居中 */ display: flex; justify-content: center; align-items: center; } /* 让图片自适应父级大小 */ img { width: auto; height: auto; max-width: 100%; max-height: 100%; }
%的相对元素
- position: absolute 中的 %
对于设置绝对定位 position absolute 的元素,我们可以使用 left right 表示其偏移量,我们把这个元素的祖先元素中第一个存在定位属性的元素成为参照物元素,其中的 % 是相对于参照物的,left 相对于参照物的 width,top 相对于这个参照物的 height。
- position: relative 中的 %
对于设置相对定位 position relative 的元素,% 的数值是相对与自身的,left 相对于自己的 width,top 相对于自己的 height。
- position: fixed 中的 %
对于设置固定定位 position fixed 的元素,% 的数值是相对于视口的,left 相对于视口的 width,top 相对于视口的 height。
- margin 和 padding 的 %
margin 和 padding 当中的 % 非常特殊,它是相对于父元素的宽度。与父元素的高度无关。
- border-radius 的 %
% 是相对于自身宽高的
- background-size 的 %
background-size 的百分比和 border-radius 一样,也是相对于自身的宽高。
- transform: translate
transform 的 translate 属性 % 是相对于自身的宽高,这也是我们上述代码能够实现居中的原因。
- text-indent 的 %
text-indent 这个属性可以设置首行缩进,当使用 % 时,它是相对于父元素的 width。
- font-size 的 %
相对于父元素的字体大小。
- line-height 的 %
line-height 设置行高时,如果单位为 %,则相对于该元素的 font-size 数值。
参考资料:
https://www.jianshu.com/p/3a262758abcf
https://blog.csdn.net/sunny5319/article/details/79966033
https://blog.csdn.net/weixin_45548211/article/details/100299087