说到响应式布局方案,我们首先需要了解视口这个概念
视口
在早期的时候我们没有专门针对手机尺寸写的页面,所以在用手机浏览页面的时候我们看到的都是专门针对PC端的页面,在这种情况下页面会被严重压缩,而且会极大的影响页面的结构和布局,为了解决这个 问题,苹果公司提出了视口的概念,因为我们早期的PC端的页面的版心一般是960px,为了容纳这个页面,我们在手机建立一个虚拟的区域,大小一般为980px,来容纳PC的页面,以方便用户来浏览页面
但是在如今移动端快速发展的时候,我们有了专门针对手机屏幕尺寸的的移动端页面,所以在写移动端的页面的时候我们需要调整视口的宽度,保证页面内容会在手机屏幕尺寸大小的页面上进行展示
媒体查询
媒体类型
媒体查询可以为不同的设备规定不同的样式,常用的一般有三种设备 screen(计算机屏幕,该值是默认值)、print(打印预览)、all(所有设备)
@media screen
媒体属性
媒体属性用来规定在指定的符合某些条件的情况下为指定的元素设置样式,需要注意媒体属性必须用()
包起来,否则无效,常用的媒体属性有
width 可视区域宽度,取值格式为指定宽度或者范围,如 @media (width: 900px){ } height 可视区域高度,取值格式为指定宽度或者范围,如 @media (max-height: 900px){ } device-width 设备宽度,取值格式为指定宽度或者范围,如 @media (max-device-width: 5000px) { } device-height 设备高度 取值格式为指定宽度或者范围,如 @media (max-device-height: 5000px) { } orientation 设备是竖屏还是横屏模式,可选值有landscape(横屏)、portrait(竖屏),如 @media (orientation: landscape) { } aspect-ratio 可视区域宽高比,取值格式为水平像素/垂直像素,如@media (device-aspect-ratio:16/9) { } device-aspect-ratio 设备宽高比,取值格式为水平像素/垂直像素,如@media (device-aspect-ratio:16/9) { }
例如我们要针对某个设备的body
在横屏时设置的背景颜色是黑色,在竖屏的时候背景是白色,那么可以使用以下写法
html, body { height: 100%; }@media (orientation: landscape) { body{ background-color:#000; }}@media (orientation: portrait) { body{ background-color:#fff; }}
逻辑操作符
媒体查询可以使用三种操作符,通过操作符配合媒体属性来判断是否载入媒体属性下的样式表
- and 将每一个条件组合起来,只有当所有条件都成立时条件才成立,可以理解为JavaScript中的
&
- not 对媒体查询的条件取反
- or 只要有一个媒体属性条件成立就成立,可以理解为JavaScript中的
||
配合逻辑操作符设置设备在可视窗口大小变化时背景颜色发生改变
html,body{ height: 100%;} @media screen and (max-width: 500px ){ body{ background-color: skyblue; }}@media screen and (min-width: 501px) and (max-width: 800px){ body{ background-color: #ccc; }}@media screen and (min-width: 900px){ body{ background-color: yellowgreen; }}
vw、vh布局方案
在CSS3规范中引入了vw
、vh
单位,分别将视口划分为100份,一个vw单位相当于视口宽度的1%,一个vh相当于视口高度的1%,需要注意的是不同于百分比的布局方案,vw和vh不受父元素宽高的影响,只由视口的大小决定
vmax
、vmin
,vmax
表示取视口宽度和高度中比较大的值,将其等分为100份,vmin
表示取比较小的值,将其等分为100份 vw、vh
目前浏览器的支持情况
rem布局
rem是css中的一个单位,是根据根字体大小来设定的,也就是html的font-size
在正常的UI设计稿件的时候一般设置大小为640/750px大小,我们一般选择将稿件等分为20份(20份在设备大小和稿件大小一般可以除尽,我们尽量避免出现小数)
那么如果UI稿件为640px大小,每一份的大小为32px,假设UI稿件上有一个160*160大小的元素,那么该元素占整个页面的160/32份
同时我们也将屏幕分为20份,以Iphon为例(屏幕大小为320px),那么每份大小为16px,我们通过媒体查询将html根字体大小设置为16px,那么在UI稿件上160px大小的元素在Iphon5上的实际大小为160/32 rem,那么通过这个公式我们就可以通过JavaScript设置不同的根节点字体大小来进行适配
我们来写一个简单的例子
(function (doc, win) { var docEl = doc.documentElement,/* document.documentElement 返回文档的根节点 * orientationchange 在用户移动端设备屏幕垂直或水平旋转移动设备时被触发 * resize 事件,在绑定元素大小发生变化时触发该事件,例如检测屏幕变化: * window.addEventLisenter("resize",function(){ * alert("屏幕大小变化了") * }) * * resize 属性,css3新增属性,用来指定用户是否可以缩放该元素 * none:用户无法调整该元素尺寸 * both:用户可以调整元素的高度和宽度 * horizontal:用户可调整元素的宽度 * vertical:用户可调整元素的宽度 * */ resizeEvt = "orientationchange" in window ? "orientationchange" : "resize", recalc = function () { var clientWidth = docEl.clientWidth; if (clientWidth >= 640) { clientWidth = 640 } if (!clientWidth) return; docEl.body.style.fontSize = (clientWidth / 640) * 100 + "px"; }; win.addEventListener("resizeEvt", recalc, false);/* * DOMContentLoaded 该事件会在load事件之前触发,在DOM树构建完成时就触发 * 而load事件则是在DOMContentLoaded事件触发之后,继续加载图片等外部文件完成后触发 * */ doc.addEventListener("DOMContentLoaded", recalc, false) })(document, window)
之前一直在使用手淘的rem布局方案,可以大概看一下源码
;(function(win, lib) { var doc = win.document; var docEl = doc.documentElement; var metaEl = doc.querySelector('meta[name="viewport"]'); var flexibleEl = doc.querySelector('meta[name="flexible"]'); var dpr = 0; var scale = 0; var tid; var flexible = lib.flexible || (lib.flexible = {}); if (metaEl) { var match = metaEl.getAttribute('content').match(/initial\-scale=([\d\.]+)/); }if (match) { scale = parseFloat(match[1]); dpr = parseInt(1 / scale); } else if (flexibleEl) { var content = flexibleEl.getAttribute('content'); if (content) { var initialDpr = content.match(/initial\-dpr=([\d\.]+)/); var maximumDpr = content.match(/maximum\-dpr=([\d\.]+)/); if (initialDpr) { dpr = parseFloat(initialDpr[1]); scale = parseFloat((1 / dpr).toFixed(2)); } if (maximumDpr) { dpr = parseFloat(maximumDpr[1]); scale = parseFloat((1 / dpr).toFixed(2)); } } } //如果用户自定了name='viewport'的meat标签,那么通过match获取用户设置的initial-scale,也就是缩放级别,然后获得设置dpr //如果用户没有设定name='viewport'的meat标签,那么获取flexible.js自身定义的缩放级别来设置dpr if (!dpr && !scale) { //检测的当前设备的版本 var isAndroid = win.navigator.appVersion.match(/android/gi); var isIPhone = win.navigator.appVersion.match(/iphone/gi); var devicePixelRatio = win.devicePixelRatio; //window.devicePixelRatio 该属性返回当前显示设备的物理像素分辨率与css像素分辨率的比值 //可以通过重写window.devicePixelRatio来更改此属性,例如window.devicePixelRatio=2; if (isIPhone) { // iOS下,对于2和3的屏,用2倍的方案,其余的用1倍方案 if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) { dpr = 3; } else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)){ dpr = 2; } else { dpr = 1; } } else { // 其他设备下,仍旧使用1倍的方案 dpr = 1; } scale = 1 / dpr; } docEl.setAttribute('data-dpr', dpr); //判断页面是否存在 metaEl if (!metaEl) { metaEl = doc.createElement('meta'); metaEl.setAttribute('name', 'viewport'); metaEl.setAttribute('content', 'initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no'); if (docEl.firstElementChild) { docEl.firstElementChild.appendChild(metaEl); } else { var wrap = doc.createElement('div'); wrap.appendChild(metaEl); doc.write(wrap.innerHTML); } } function refreshRem(){ var width = docEl.getBoundingClientRect().width; //getBoundingClientRect //返回一个DOMRect对象,包含一组矩形的集合,该集合内是与该元素相关的css边框集合 // DOMRect // bottom:8 // height:8 // left:0 // right:520 // top:0 // width:520 // x:0 // y:0 if (width / dpr > 540) { width = 540 * dpr; } //手淘的布局方案默认将屏幕分成十份,当然,如果愿意我们可以对其进行更改 var rem = width / 10; docEl.style.fontSize = rem + 'px'; flexible.rem = win.rem = rem; } // resize 在设备宽度发生改变时触发 win.addEventListener('resize', function() { clearTimeout(tid); tid = setTimeout(refreshRem, 300); }, false); //pageshow firefox/open的一个事件,在chrome中不会触发 //在页面后退时静态资源会直接重缓存中读取 win.addEventListener('pageshow', function(e) { if (e.persisted) { clearTimeout(tid); tid = setTimeout(refreshRem, 300); } }, false); //document.readyState 描述文档的加载状态 // loding 文档仍然在加载中 // interactive 文档已经加载完成并已经被解析,但是图像,框架之类的资源仍然在加载中 // complete 说有资源都已经加载完成,load事件即将被触发 // 在状态改变时document.readyState事件将被触发 if (doc.readyState === 'complete') { doc.body.style.fontSize = 12 * dpr + 'px'; } else { //DOMContentLoaded 在文档加载完成后触发,不会等待图像,框架等资源,参考$(function(){}) / $.ready() doc.addEventListener('DOMContentLoaded', function(e) { doc.body.style.fontSize = 12 * dpr + 'px'; }, false); } refreshRem(); flexible.dpr = win.dpr = dpr; flexible.refreshRem = refreshRem; //rem 2 px 转化方法 flexible.rem2px = function(d) { var val = parseFloat(d) * this.rem; if (typeof d === 'string' && d.match(/rem$/)) { val += 'px'; } return val; } //px 2 rem 转化方法 flexible.px2rem = function(d) { var val = parseFloat(d) / this.rem; if (typeof d === 'string' && d.match(/px$/)) { val += 'rem'; } return val; } })(window, window['lib'] || (window['lib'] = {}));
手淘的布局方法主要是通过dpr
来设置不同屏幕下的不同比例关系,drp
指的是默认缩放为100%的情况下设备像素和CSS像素的比值
dpr=设备像素 / CSS像素
具体的关于dpr的解释可以看下面这篇文章
如果使用手淘的布局方案注意一个问题,我们需要去除页面中设置了name=viewport
的meta
标签,在源码中我们可以看到如果存在改标签,那么lib-flexible
不会动态改变缩放比例