所谓移动端响应式布局,就是最终布局适配不同的手机型号,针对不同的屏幕分辨率的终端上能够浏览网页展示的不同方式,我们也可以称为移动端适配布局。
总结就是一个移动端网站能够兼容多个终端——而不是为每个终端做一个特定的版本。
优点:
当浏览器的宽度或者高度发生变化时,通过百分比单位可以使得浏览器中的组件的宽和高随着浏览器的变化而变化,从而实现响应式的效果。
width属性的百分比依托于父标签的宽高。height属性一般不设置,文档流正常排版。
目前这种方式布局很少了。
通用解决方案代码:
DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta content="yes" name="apple-mobile-web-app-capable">
<meta content="black" name="apple-mobile-web-app-status-bar-style">
<meta content="telephone=no" name="format-detection">
<meta content="email=no" name="format-detection">
<title>viewport通用title>
<script type="text/javascript">
var phoneWidth = parseInt(window.screen.width);
var phoneScale = phoneWidth / 750;
var ua = navigator.userAgent;
if (/Android (\d+\.\d+)/.test(ua)) {
var version = parseFloat(RegExp.$1);
if (version > 2.3) {
document.write('+ phoneScale + ', maximum-scale = ' + phoneScale + ', target-densitydpi=device-dpi">');
} else {
document.write('');
}
} else {
document.write('');
}
script>
<link style="text/css" rel="stylesheet" href="home.css">
<script type="text/javascript" src="js/app.js">script>
head>
<body>
body>
html>
实现说明:强制宽度,然后viewport缩放,这种模式有个弊端,就是在高清屏上会没那么清晰(相对)。
target-densitydpi 属性的取值范围:
device-dpi: 使用设备原本的 dpi 作为目标 dp。 不会发生默认缩放。
high-dpi: 使用hdpi 作为目标 dpi。 中等像素密度和低像素密度设备相应缩小
medium-dpi: 使用mdpi作为目标 dpi。 高像素密度设备相应放大, 像素密度设备相应缩小。这是默认的target density。
优点:开发流程很简单,工程师只需根据设计稿标注还原页面,不需要额外计算。适配范围广。
缺点:页面整体放大缩小,对于不想缩放的元素无法控制。比如边框在大屏手机下显得很粗,在小屏手机下显得很细。
扩展:
我们先来看几个常见的概念:
注:设备像素比(dpr) 是指在移动开发中1个css像素占用多少设备像素,比如dpr=2就代表1个css像素用2x2个设备像素来绘制。
手机屏幕尺寸大全参考地址:https://www.strerr.com/screen.html
示例:在一台普通屏幕上的像素(设备独立像素,也即是逻辑像素),可以当成正常的像素(css像素),比如画10px的元素,那么它就是10px的元素。但是如果在高清屏下就会发生变化,有一个属性叫做设备像素比devicePixelRatio
。在高清屏下,比如我们的设备像素比devicePixelRatio
为2,那么在css设置100px(逻辑像素
),实际渲染的是200px的物理像素
。
注:不管是否是高清屏,canvas中的单位1(逻辑像素),就是1物理像素。所以在高清屏下,canvas绘制的内容就变得模糊了。
媒体查询:实现在不同设备宽度下渲染不同布局和样式。
媒体查询的语法:
// 当页面宽度等于320px的时候,使用媒体查询中的CSS属性
@media (width: 320px) {
html{
font-size: 32px;
}
}
// 当也宽度大于320px的时候,使用媒体查询中的CSS属性
@media (min-width: 320px) {
html{
font-size: 32px;
}
}
//媒体查询
@media (媒体特性) {
选择器{
CSS属性;
}
}
常见的设计规范下的屏幕尺寸范围:
@media screen and (min-1400px){ ... }/* 超大型设备(超大台式电脑,1400px 起) */
@media screen and (min-1200px){ ... }/* 大型设备(大台式电脑,1200px 起) */
@media screen and (min-992px){ ... }/* 中型设备(台式电脑,992px 起) */
@media screen and (min-768px) { ... } /* 小型设备(平板电脑,768px 起) */
@media screen and (min-576px){ ... }/* 超小设备(手机,小于 768px) */
参考bootstra的v5.3版本:
Bootstrap v5新功能:
rem是一个相对的单位,是相对于HTML标签字号来计算结果。
注:大家公认在rem布局方案中,将网页分为10等份,HTML标签字号为视口宽度的1/10
从上面几张图可以看出,随着分辨率的增大,页面的效果会发生明显变化,主要体现在各个元素的宽高与间距。375680的比320680的导航栏明显要高。能够达到这种效果的根本原因就是因为网易页面里除了font-size之外的其它css尺寸都使用了rem作为单位。
当分辨率发生变化时,html的font-size就会变,不过这得在你调整分辨率后,刷新页面才能看得到效果。
它是根据什么计算的,这就跟设计稿有关了,拿网易来说,它的设计稿应该是基于iphone4或者iphone5来的,所以它的设计稿竖直放时的横向分辨率为640px
,为了计算方便,取一个100px
的font-size为参照,那么body元素的宽度就可以设置为width: 6.4rem
,于是html的font-size=deviceWidth / 6.4
。这个deviceWidth就是viewport设置中的那个deviceWidth。根据这个计算规则,可得出上图不同分辨率下的font-size大小如下:
deviceWidth = 320,font-size = 320 / 6.4 = 50px
deviceWidth = 375,font-size = 375 / 6.4 = 58.59375px
deviceWidth = 414,font-size = 414 / 6.4 = 64.6875px
deviceWidth = 500,font-size = 500 / 6.4 = 78.125px
事实上网易就是这么干的,你看它的代码就知道,body元素的宽是:
根据这个可以肯定它的设计稿竖着时的横向分辨率为640。然后你再看看网易在分辨率为320680,375680,414680,500680时,html的font-size是不是与上面计算的一致:
这个deviceWidth通过document.documentElement.clientWidth就能取到了,所以当页面的dom ready后,做的第一件事情就是:
document.documentElement.style.fontSize = document.documentElement.clientWidth / 6.4 + 'px';
这个6.4怎么来的,当然是根据设计稿的横向分辨率/100得来的。下面总结下网易的这种做法:
<meta name="viewport" content="initial-scale=1,maximum-scale=1, minimum-scale=1">
如果设计稿基于iphone6,横向分辨率为750,body的width为750 / 100 = 7.5rem
如果设计稿基于iphone4/5,横向分辨率为640,body的width为640 / 100 = 6.4rem
例如:高度为210px,写样式的时候css应该这么写:height: 2.1rem。
document.documentElement.style.fontSize = document.documentElement.clientWidth / 6.4 + 'px';
var deviceWidth = document.documentElement.clientWidth;
if(deviceWidth > 640) deviceWidth = 640;
document.documentElement.style.fontSize = deviceWidth / 6.4 + 'px';
之所以这么干,是因为当deviceWidth大于640时,则物理分辨率大于1280(这就看设备的devicePixelRatio这个值了),应该去访问pc网站了。事实就是这样,你从手机访问网易,看到的是触屏版的页面,如果从pad访问,看到的就是电脑版的页面。
! function(e, t) {
var n, i = t.documentElement,
d = e.devicePixelRatio || 1;
function o() {
var e = Math.min(i.clientWidth, 750) / 10;
i.style.fontSize = e + "px"
}! function e() {
t.body ? t.body.style.fontSize = 12 * d + "px" : t.addEventListener("DOMContentLoaded", e)
}(), o(), e.addEventListener("resize", o), e.addEventListener("pageshow", (function(e) {
e.persisted && setTimeout(o, 0)
})), 2 <= d && (n = t.createElement("body"), (e = t.createElement("div")).style.border =
".5px solid transparent", n.appendChild(e), i.appendChild(n), 1 === e.offsetHeight && i.classList.add(
"hairlines"), i.removeChild(n))
}(window, document)
翻译后:
!function flexible(window,document){
var docEL = document.documentElement;
var dpr = window.devicePixelRatio || 1;
// 设置我们body的字体大小
function setBodyFontSize(){
//如果页面中有body这个元素,就设置body的字体大小
if(document.body){
document.body.style.fontSize = (12 * dpr) + 'px';
}else{
//如果页面中没有body这个元素,则等我们页面主要的DOM元素加载完毕再去设置body的字体大小
document.addEventListener('DOMContentLoaded',setBodyFontSize);
}
}
setBodyFontSize();
function setRemUnit(){
var rem = Math.min(docEL.clientWidth, 750) / 10;
docEL.style.fontSize = rem + 'px';
}
setRemUnit();
// 当页面尺寸大小发生变化时,重新设置rem大小
window.addEventListener('resize',setRemUnit);
//往返缓存:保存这页面的数据,DOM和JavaScript的状态,实际来说是将整个页面都保存在了内存里
//pageshow是我们重新加载页面触发的事件,如果使用load的话,火狐浏览器的往返缓存是不会改变rem大小的
window.addEventListener('pageshow',function(e){
//pageshow的persisted值返回true,就是说如果页面是从缓存取过来的页面,也需要重新计算一下rem大小
if(e.persisted){
setRemUnit();
}
})
// 有些移动端的浏览器不支持0.5像素的写法
if(dpr >= 2){
var fakeBody = document.createElement('body');
var testElement = document.createElement('div');
testElement.style.border = '5px solid transparent';
fakeBody.appendChild(testElement);
docEL.appendChild(fakeBody);
if(testElement.offsetHeight === 1){
docEL.classList.add('hairlines');
}
docEL.removeChild(fakeBody);
}
}(window,document);
淘宝的效果跟网易的效果其实是类似的,随着分辨率的变化,页面元素的尺寸和间距都相应变化,这是因为淘宝的尺寸也是使用了rem的原因。
头部设置:
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
这样整个网页在设备内显示时的页面宽度就会等于设备逻辑像素大小,也就是device-width。这个device-width的计算公式为:
设备的物理分辨率/(devicePixelRatio * scale),在scale为1的情况下,device-width = 设备的物理分辨率/devicePixelRatio 。
每款设备的devicePixelRatio都是已知,并且不变的,目前高清屏,普遍都是2,不过还有更高的,比如2.5, 3 等,魅族note的手机的devicePixelRatio就是3。淘宝触屏版布局的前提就是viewport的scale根据devicePixelRatio动态设置:
在devicePixelRatio为2的时候,scale为0.5
在devicePixelRatio为3的时候,scale为0.3333
这么做目的当然是为了保证页面的大小与设计稿保持一致了,比如设计稿如果是750的横向分辨率,那么实际页面的device-width,以iphone6来说,也等于750(如果没有设置scale,那么就是device-width=375),这样的话设计稿上标注的尺寸只要除以某一个值就能够转换为rem了。通过js设置viewport的方法如下:
淘宝布局的第二个要点,就是html元素的font-size的计算公式,font-size = deviceWidth / 10:
接下来要解决的问题是,元素的尺寸该如何计算,比如说设计稿上某一个元素的宽为150px,换算成rem应该怎么算呢?这个值等于设计稿标注尺寸/该设计稿对应的html的font-size。拿淘宝来说的,他们用的设计稿是750的,所以html的font-size就是75,如果某个元素时150px的宽,换算成rem就是150 / 75 = 2rem。总结下淘宝的这些做法:
(1)动态设置viewport的scale
var scale = 1 / devicePixelRatio;
document.querySelector('meta[name="viewport"]').setAttribute('content','initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no');
(2)动态计算html的font-size
document.documentElement.style.fontSize = document.documentElement.clientWidth / 10 + 'px';
(3)布局的时候,各元素的css尺寸=设计稿标注尺寸/设计稿横向分辨率/10
最后还有一个情况要说明,跟网易一样,淘宝也设置了一个临界点,当设备竖着时横向物理分辨率大于1080时,html的font-size就不会变化了,原因也是一样的,分辨率已经可以去访问电脑版页面了。
关于这种做法的具体实现,淘宝已经给我们提供了一个开源的解决方案,具体请查看:
https://github.com/amfe/lib-flexible
注:网易的做法,rem值很好计算,淘宝的做法需要借助less和sass这样的css处理器
function(e, t) {
var n = t.documentElement,
r = e.devicePixelRatio || 1;
function o() {
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)
}(), o(), e.addEventListener("resize", o), e.addEventListener("pageshow", function(e) {
e.persisted && o()
}), r >= 2) {
var i = t.createElement("body"),
a = t.createElement("div");
a.style.border = ".5px solid transparent", i.appendChild(a), n.appendChild(i), 1 === a
.offsetHeight && n.classList.add("hairlines"), n.removeChild(i)
}
}(window, document);
翻译后:
(function flexible(window,document){
var docEL = document.documentElement;
var dpr = window.devicePixelRatio || 1;
function setBodyFontSize(){
//如果页面中有body这个元素,就设置body的字体大小
if(document.body){
document.body.style.fontSize = '16px';
}else{
//如果页面中没有body这个元素,则等我们页面主要的DOM元素加载完毕再去设置body的字体大小
document.addEventListener('DOMContentLoaded',setBodyFontSize);
}
}
setBodyFontSize();
function setRemUnit(){
var rem = docEL.clientWidth / 3.75;
docEL.style.fontSize = rem + 'px';
}
setRemUnit();
// 当页面尺寸大小发生变化时,重新设置rem大小
window.addEventListener('resize',setRemUnit);
//往返缓存:保存这页面的数据,DOM和JavaScript的状态,实际来说是将整个页面都保存在了内存里
//pageshow是我们重新加载页面触发的事件,如果使用load的话,火狐浏览器的往返缓存是不会改变rem大小的
window.addEventListener('pageshow',function(e){
//pageshow的persisted值返回true,就是说如果页面是从缓存取过来的页面,也需要重新计算一下rem大小
if(e.persisted){
setRemUnit();
}
})
// 有些移动端的浏览器不支持0.5像素的写法
if(dpr >= 2){
var fakeBody = document.createElement('body');
var testElement = document.createElement('div');
testElement.style.border = '5px solid transparent';
fakeBody.appendChild(testElement);
docEL.appendChild(fakeBody);
if(testElement.offsetHeight === 1){
docEL.classList.add('hairlines');
}
docEL.removeChild(fakeBody);
}
}(window,document));
(function(base, min, max, scaling){
var cacheWidth = 0;
var timer;
var docEl = document.documentElement;
var recalc = function () {
var clientWidth = docEl.clientWidth;
if (!clientWidth) return;
clientWidth = Math.max(Math.min(clientWidth, max), min);
if(cacheWidth !== clientWidth) {
clearInterval(timer);
cacheWidth = clientWidth;
docEl.style.fontSize = scaling * (clientWidth / base) + 'px';
}
}
recalc();
setTimeout(recalc, 300);
if (!window.addEventListener) return;
var resizeWithTimer = function() { timer = setInterval(recalc, 10); }
if ('onorientationchange' in window) window.addEventListener('orientationchange', resizeWithTimer);
if ('onresize' in window) window.addEventListener('resize', resizeWithTimer);
})(375, 300, 768, 100);
首先设置meta属性,如下代码:
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
使用如下代码就能实现移动端的适配:
html {
font-size: -webkit-calc(13.33333333vw);
font-size: calc(13.33333333vw);
}
100vw相当于浏览器的window.innerWidth,是浏览器的内部宽度,注意,滚动条宽度也计算在内!那么1vw就是表示1%的屏幕宽度。
其中的13.33333333vw是怎么来的呢?就是你的设计稿是750px,那么设计稿的1px就是0.133333333vw,那么100px就是13.33333333vw。也即是html的font-size设置为100px相当于1rem(设计稿为750px)。那么我们就可以很轻松的换算设计稿中的单位为rem了,比如一个元素宽度为150px,转换为rem就是1.5rem。其他尺寸设计稿的计算方式依次类推。
参考网易新闻(某版本)移动端的写法:
/**
* view-port list:
320x480
320x568
320x570
360x592
360x598
360x604
360x640
360x720
375x667
375x812
393x699
412x732
414x736
480x854
540x960
640x360
720x1184
720x1280
800x600
1024x768
1080x1812
1080x1920
*/
html {
font-size: -webkit-calc(13.33333333vw);
font-size: calc(13.33333333vw);
}
@media screen and (max-width: 320px) {
html {
font-size: 42.667px;
font-size: -webkit-calc(13.33333333vw);
font-size: calc(13.33333333vw);
}
}
@media screen and (min-width: 321px) and (max-width: 360px) {
html {
font-size: 48px;
font-size: -webkit-calc(13.33333333vw);
font-size: calc(13.33333333vw);
}
}
@media screen and (min-width: 361px) and (max-width: 375px) {
html {
font-size: 50px;
font-size: -webkit-calc(13.33333333vw);
font-size: calc(13.33333333vw);
}
}
@media screen and (min-width: 376px) and (max-width: 393px) {
html {
font-size: 52.4px;
font-size: -webkit-calc(13.33333333vw);
font-size: calc(13.33333333vw);
}
}
@media screen and (min-width: 394px) and (max-width: 412px) {
html {
font-size: 54.93px;
font-size: -webkit-calc(13.33333333vw);
font-size: calc(13.33333333vw);
}
}
@media screen and (min-width: 413px) and (max-width: 414px) {
html {
font-size: 55.2px;
font-size: -webkit-calc(13.33333333vw);
font-size: calc(13.33333333vw);
}
}
@media screen and (min-width: 415px) and (max-width: 480px) {
html {
font-size: 64px;
font-size: -webkit-calc(13.33333333vw);
font-size: calc(13.33333333vw);
}
}
@media screen and (min-width: 481px) and (max-width: 540px) {
html {
font-size: 72px;
font-size: -webkit-calc(13.33333333vw);
font-size: calc(13.33333333vw);
}
}
@media screen and (min-width: 541px) and (max-width: 640px) {
html {
font-size: 85.33px;
font-size: -webkit-calc(13.33333333vw);
font-size: calc(13.33333333vw);
}
}
@media screen and (min-width: 641px) and (max-width: 720px) {
html {
font-size: 96px;
font-size: -webkit-calc(13.33333333vw);
font-size: calc(13.33333333vw);
}
}
@media screen and (min-width: 721px) and (max-width: 768px) {
html {
font-size: 102.4px;
font-size: -webkit-calc(13.33333333vw);
font-size: calc(13.33333333vw);
}
}
@media screen and (min-width: 769px) {
html {
font-size: 102.4px;
font-size: -webkit-calc(13.33333333vw);
font-size: calc(13.33333333vw);
}
}
body {
font-family: "PingFangSC-Regular", "Microsoft YaHei", Helvetica;
color: #333333;
background: #f5f7f9;
}
html a {
color: #333333;
}
这样写法也是为了兼容老版本手机不支持vw以及calc语法。
如果页面使用px单位,怎样进行rem的适配,把px转换为rem单位:
注:由于viewport单位得到众多浏览器的兼容,lib-flexible这个过渡方案已经可以放弃使用,不管是现在的版本还是以前的版本,都存有一定的问题。建议大家开始使用viewport来替代此方。
由于我们要去考虑用户的需求,用户之所以去买大屏手机,不是为了看到更大的字,而是为了看到更多的内容,这样直接使用 px
是最明智的方案,然后再配合flex进行布局即可。
flex布局:
1.flex-direction: 决定主轴的方向(即项目的排列方向)
flex-direction || flex-wrap || flex-flow || justify-content || align-items || align-content
- row(默认值):主轴为水平方向,起点在左端。
- row-reverse:主轴为水平方向,起点在右端。
- column:主轴为垂直方向,起点在上沿。
- column-reverse:主轴为垂直方向,起点在下沿。
2.flex-wrap:默认情况下,项目都排在一条线(又称”轴线”)上。flex-wrap属性定义,如果一条轴线排不下,如何换行。
- nowrap(默认):不换行。
- wrap:换行,第一行在上方。
- wrap-reverse:换行,第一行在下方。
3.flex-flow:flex-flow属性是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap。
.box {
flex-flow: || ;
}
4.justify-content:定义了项目在主轴上的对齐方式。
可能取5个值,具体对齐方式与轴的方向有关。下面假设主轴为从左到右。
- flex-start(默认值):左对齐
- flex-end:右对齐
- center: 居中
- space-between:两端对齐,项目之间的间隔都相等。
- space-around:每个项目两侧的间隔相等。所以,项目之间的间隔比项目与边框的间隔大一倍。
5.align-items:定义项目在交叉轴上如何对齐。
它可能取5个值。具体的对齐方式与交叉轴的方向有关,下面假设交叉轴从上到下。
- flex-start:交叉轴的起点对齐。
- flex-end:交叉轴的终点对齐。
- center:交叉轴的中点对齐。
- baseline: 项目的第一行文字的基线对齐。
- stretch(默认值):如果项目未设置高度或设为auto,将占满整个容器的高度。
6.align-content:定义了多根轴线的对齐方式。如果项目只有一根轴线,该属性不起作用。
- flex-start:与交叉轴的起点对齐。
- flex-end:与交叉轴的终点对齐。
- center:与交叉轴的中点对齐。
- space-between:与交叉轴两端对齐,轴线之间的间隔平均分布。
- space-around:每根轴线两侧的间隔都相等。所以,轴线之间的间隔比轴线与边框的间隔大一倍。
- stretch(默认值):轴线占满整个交叉轴。
以下6个属性设置在项目上。
- order
- flex-grow
- flex-shrink
- flex-basis
- flex
- align-self
1.order属性
order属性定义项目的排列顺序。数值越小,排列越靠前,默认为0。
.item {
order: ;
}
2.flex-grow属性
flex-grow属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。
.item {
flex-grow: ; /* default 0 */
}
如果所有项目的flex-grow属性都为1,则它们将等分剩余空间(如果有的话)。如果一个项目的flex-grow属性为2,其他项目都为1,则前者占据的剩余空间将比其他项多一倍。
3.flex-shrink属性
flex-shrink属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。
.item {
flex-shrink: ; /* default 1 */
}
如果所有项目的flex-shrink属性都为1,当空间不足时,都将等比例缩小。如果一个项目的flex-shrink属性为0,其他项目都为1,则空间不足时,前者不缩小。
负值对该属性无效。
4.flex-basis属性
flex-basis属性定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。
.item {
flex-basis: | auto; /* default auto */
}
它可以设为跟width或height属性一样的值(比如350px),则项目将占据固定空间。
5.flex属性
flex属性是flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto。后两个属性可选。
.item {
flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]
}
该属性有两个快捷值:auto (1 1 auto) 和 none (0 0 auto)。
建议优先使用这个属性,而不是单独写三个分离的属性,因为浏览器会推算相关值。
6.align-self属性
align-self属性允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。
.item {
align-self: auto | flex-start | flex-end | center | baseline | stretch;
}
该属性可能取6个值,除了auto,其他都与align-items属性完全一致。
思考: 为什么移动端组件,例如vant、weui(一套同微信原生视觉体验一致的基础样式库,由微信官方设计团队为微信内网页和微信小程序量身设计)等用到的单位是px?
详细可参考地址: