移动端布局
除了百分比流式布局之外,rem布局占据了目前移动端布局的热潮。那么究竟这几种布局差别在哪里,对应的有什么效果,希望本文能给你一些有益的启示。除此之外还有响应式布局,固定宽度布局等。
百分比流式布局
这里面最知名的当属bootstrap框架的思路,他所有的组件以及模板ui均是百分比流式布局,单位为px.并且我们看到的大部分对移动端适配的页面也均是采用这种核心思想去做的,方法简单,多端共用,可以针对不同临界值做额外处理。比如百度,天猫,知乎,京东,微信一些文章等。所以无论怎么看,百分比流式布局都是一种常规+主流的布局方案。
核心原则:文字为流式布局,宽度100%按照屏幕宽度缩放,高度固定px,水平采用百分比,或者固宽+变宽,图片固定大小或者百分比缩放,辅助flex布局 。有使用的最大媒体查询临界值(一般为640)。
通过下图去理解下:
缺点:在大屏幕的手机下显示效果会变成有些页面元素宽度被拉的很长,但是高度还是和原来一样,实际显示非常的不协调,这就是流式布局的最致命的缺点
随着分辨率的增大,页面的效果会发生明显变化,主要体现在各个元素的宽高与间距。375_680的比320_680的导航栏明显要高。能够达到这种效果的根本原因就是因为网易页面里除了font-size之外的其它css尺寸都使用了rem作为单位,比如你看导航栏的高度设置代码:
header,footer{
height:.90rem;
}
网易页面上html的font-size不是预先通过媒介查询在css里定义好的,而是通过js计算出来的,所以当分辨率发生变化时,html的font-size就会变,不过这得在你调整分辨率后,刷新页面才能看得到效果。你看代码就知道为啥font-size是直接写到html的style上面的了(js设置的原因).
这样的话,每个页面中的宽高以及具体值只要除以100即可,而根节点的字号则是font-size=deviceWidth / 6.4,页面宽度如果是640px,则转换之后是6.4rem。
需要注意的是,字号需要额外的媒体查询,而不是rem定的。
//字号单独用px即可 ,这里建议大家用变量定义小中大正常集中字号常量就可以,不用每次去针对具体样式做字号调整。
@media screen and (max-width:321px){
.m-navlist{font-size:15px}
}
@media screen and (min-width:321px) and (max-width:400px){
.m-navlist{font-size:16px}
}
@media screen and (min-width:400px){
.m-navlist{font-size:18px}
}
//设置基本的字号,基本元素取材除100即可得到rem的大小,当然也可以用16px换算改变根字号为6.25rem即可。因为还有很大比例的手机设计为320的,所以这里考虑为640的。当屏幕大于640的时候,不再放大,让页面处于水平居中640px显示。
function fontAuto(){
var deviceWidth = document.documentElement.clientWidth;
if(deviceWidth > 640) deviceWidth = 640;
document.documentElement.style.fontSize = deviceWidth / 6.4 + 'px';
}
fontAuto();
window.onresize=function(){
fontAuto();
}
淘宝的效果跟网易的效果其实是类似的,随着分辨率的变化,页面元素的尺寸和间距都相应变化,这是因为淘宝的尺寸也是使用了rem的原因。在介绍它的做法之前,先来了解一点关于viewport的知识,通常我们采用如下代码设置viewport。
<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称为设备像素比,每款设备的devicePixelRatio都是已知,并且不变的,目前高清屏,普遍都是2,不过还有更高的,比如2.5, 3 等,魅族note的手机还有6p的devicePixelRatio就是3。淘宝触屏版布局的前提就是viewport的scale根据devicePixelRatio动态设置。js中可以通过devicePixelRatio拿到具体的设备数值。
(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)动态设置font-size
document.documentElement.style.fontSize = document.documentElement.clientWidth / 10 + 'px';
(3)布局的时候,各元素的css尺寸=设计稿标注尺寸/设计稿横向分辨率/10
(4)font-size可能需要额外的媒介查询,并且font-size不使用rem,这一点跟网易是一样的。
备注:你也可以通过阿里的现成的解决方案去实现,然后在预处理器中去处理px单位的字号
<script src="http://g.tbcdn.cn/mtb/lib-flexible/%7B%7Bversion%7D%7D/??flexible_css.js,flexible.js"/>
@function s($px) {
<a href="http://www.jobbole.com/members/wx1409399284">@return</a> ($px / 75) * 1rem;
}
p{
font-size:s(40);padding-left: s(52);
}
- flexible
flexible.js
rem + simple-flexible 适配方案
simple-flexible 是根据手淘团队 lib-flexible.js,比较,改写的一个插件,兼容 UC 竖屏转横屏出现的 BUG,自定义视觉设计稿的宽度:designWidth,设定最大宽度:maxWidth
这里有 simple-flexible的 Github 地址,下载下来用即可;
第一个参数是视觉设计稿的宽度,一般视觉设计稿有 750px,可以根据实际调整
第二个参数则是设置制作稿的最大宽度,超过 750px,则以 750px 为最大限制;
使用时候的换算比例,是 1:100, 即 1rem = 100px;
使用步骤:
1 复制 simple-flexible 的 flexible.min.js 或 flexible.js 代码到页面的 <head>
的 <script>
标签里面;
2 然后视觉设计稿大小,调整里面的最后两个参数值;
3 根据设计像素,使用 rem 单位转换的视觉设计稿里面的 px 单位,例: 750px = 7.5rem;
rem + lib-flexible 适配方案(重要)
lib-flexible.js 是手淘团队制作的一个 Js 插件,实际上就是能过 JS 来动态改写 <meta>
标签;
lib-flexible.js 基本原理是模拟 vw 把视觉稿分为 100份,以单位 a 来说,1rem = 10a;
以视觉稿 750px 为例子
1a = 7.5px, 1rem = 75px
在 UC 浏览器上发现了,横批竖屏转换不过来的情况,已经找到兼容方案,是通过js在页面的 <head>
里生成定义了<html>
元素 font-size 的 style 元素来解决!
<style id="rootFontSize">html{font-size: 100px !important;}</style>
postCss+vw
解析:
PostCSS 将 CSS 变成 JavaScript 的数据,使它变成可操作;
VW 是基于 Viewpost 视窗的长度单位;
Viewpost 是指浏览器可视化的区域,而可视化区域即是 window.innerWidth/window.innerHeight 的大小;
与 Viewpost 相关的单位有以下四个:
vw : 是 Viewport width 的简写 1vw = window.innerWidth的 1%;
vh : 是 Viewport height 的简写 1vw = window.innerHeight 1%;
vmin : vw 和 vh 之间的较小值
vmax : vw 和 vh 之间的较大值使用步骤
假设视觉设计稿的宽度是 750px 即 1vw = 7.5px,那么就得根据设计图的 px 值来转换 vw单位,为了避免这样的计算,当然就需要使用到 PostCSS ,以及 postcss-px-to-viewport 一个 PostCSS 的插件。
有过使用前端脚手架的童鞋,应该都有看到过项目根目录下面都会有一个 .postcssrc 文件,它里面都是一些配置选项比较著名的 autoprefixer,cssnano,px2rem,cssnext…等等好玩的配置插件,但是这里不作这些说明,只介绍 postcss-px-to-viewport 配合 vw 使用
npm i -S postcss-px-to-viewport
//打开 .postcssrc,假定视觉设计稿的宽度为 750px 改写配置如下:
"plugins": {
"postcss-px-to-viewport": true
},
"rule": {
"postcss-px-to-viewport": {
"viewportWidth": 750,
"viewportHeight": 1334,
"unitPrecision": 5,
"viewportUnit": "vw",
"selectorBlackList": [],
"minPixelValue": 1,
"mediaQuery": false
}
}
配置完成之后,在项目中直接使用 px ,构建的时候就会自动转换为 vw 单位了,简直不要太爽了;
postcss-px-to-viewport 配置项说明
"viewportWidth" //设置视觉设计稿的宽度
"viewportHeight" //设置视觉设计稿的高度
"unitPrecision": //单位的精度,即保留多少位小数
"viewportUnit": //转换的单位
"selectorBlackList": //需要忽略的选择器
"minPixelValue": //最小像素值
"mediaQuery": //是否允许媒体查询转换为 px
可能遇到的问题:
vw 的兼容性貌似还没那么好,有可能需要做降级处理,需要使用到 CSS Houdini 和 CSS Polyfill 上一些针对 vw 单位做一个降级处理;
vw 在混合使用到 margin 的 px 时候 有可能超出 100vw ,目前使用 padding 来代替 marging 再配置上 box-sizing 可以解决,亦可以使用 css 的 calc() 函数来做一个计算;
转换的时候多少还存在一点像素差,无法完全还原;
美团rem布局
- 综合使用scale以及font-size的方案,更加简单。其中个元素的css尺寸为px/100,字号也使用rem作为单位。(参考地址:http://i.meituan.com/?adap=auto)
//根据屏幕大小及dpi调整缩放和大小 (function() { var scale = 1.0; var ratio = 1; if (window.devicePixelRatio >= 2) { scale *= 0.5; ratio *= 2; } var text = '<meta name="viewport" content="initial-scale=' + scale + ', maximum-scale=' + scale +', minimum-scale=' + scale + ', width=device-width, user-scalable=no" />'; document.write(text); document.documentElement.style.fontSize = 50*ratio + "px"; })();
响应式布局
响应式这种方式在国内很少有大型企业的复杂性的网站在移动端用这种方法去做,主要原因是工作大,维护性难,所以一般都是中小型的门户或者博客类站点会采用响应式的方法从web page到web app直接一步到位,因为这样反而可以节约成本,不用再专门为自己的网站做一个web app的版本。
@media (max-width:768px){
//css
}
备注 :响应式布局还可以根据设备宽度选择按需加载不同情况下的样式,可以加在样式link标签中。
<link rel="stylesheet" href="css/1.css" media="(max-width:500px)"/>
固定宽度布局
主体页面固定宽度,两边留白。
.main{ width:980px; margin:0 atuo; }
设置viewport进行缩放
仅仅靠这个实现适配是远远不够的,页面载入的时候会有缩放的情况,所以尽量使用rem等常规布局方式去做布局比较好。
如果你想使用这个方案,可以使用以下代码实现,针对部分安卓不适配进行了一定的修正。(这种方式还是px为单位)
function changeViewport(){
// UI-width :WebApp布局宽度
// device-width :屏幕分辨率宽度
// target-densitydpi = UI-width / device-width * window.devicePixelRatio * 160;
var uiWidth = 750,
deviceWidth = (window.orientation == 90 || window.orientation == -90) ? window.screen.height : window.screen.width;
devicePixelRatio = window.devicePixelRatio || 1;
var myScale = deviceWidth / uiWidth;
var targetDensitydpi = uiWidth / deviceWidth * devicePixelRatio * 160;
//alert(uiWidth+","+deviceWidth+","+devicePixelRatio+","+myScale+","+targetDensitydpi);
var viewportContent = "initial-scale=" + myScale + ", maximum-scale=" + myScale + ", minimum-scale=" + myScale + ',target-densitydpi=' + targetDensitydpi + ', width=device-width, user-scalable=no';
//var viewportContent = "initial-scale=" + myScale + ", maximum-scale=" + myScale + ", minimum-scale=" + myScale + ', width=device-width, user-scalable=no';
document.querySelector('meta[name=viewport]').attributes['content'].value = viewportContent;
}
window.addEventListener("orientationchange", function() {
// Announce the new orientation number
changeViewport();
}, false);
changeViewport();
媒体类型
@meida还可以根据媒体类型进行断点。
px与rem取值关系
如果你确定用rem进行布局,请查看你的设计稿是375标准的还是750标准的,因为这个将影响你使用rem时的取值。
如果你是375 ,那么正常情况下你的font-size应该为100 ,对应你取rem值得时候就可以直接px/100得到对应的rem值;
如果你是375.却在375的设备上,根字号为50,那么你的rem就是px/50了,这就不太好写了;
同理,如果你是按照750的设计稿进行设计的,那么你取到的px实际是比较大一倍的,此时,你的font-size为50,rem值为px/100,如果不幸你又是按照根字号100来算的,那么此时你的rem值为px/200。
所以你的html根字号 font-size取值应该为 100*(windowWidth/UIWidth ) ,而为了保证其准确的设置,还需要监听窗口的旋转和大小重置事件(备注:以上不考虑设备像素比的情况)。
var resetFontSize = function(){
var winWidth = document.documentElement.clientWidth ;
if(winWidth>750) {
winWidth = 750 ;}
document.style.fontSize = 100 * (winWidth / UIWidth) + 'px'
}
var resizeEvent = 'orientationchange' in window ? 'orientationchange' : 'resize';
window.add(resizeEvent,resetFontSize,false)
dpr设置
dpr为设备像素比,一般情况下我们需要设置这个设备像素比,比如我们熟悉的苹果56的dpr为2,而6plus为3,现在知道的已有的设备像素比最高为3,pc为1.
那么如果我们需要设置手机的dpr,可以参考的代码如下:这句代码的意思就是取设ios设备的较小的值,如果不存在,那么设置为1 。需要注意的是设备像素比不会影响你rem的取值。(非苹果设备默认dpr为1)
dpr = isIOS ? Math.min(win.devicePixelRatio, 3) : 1
document.dataset.dpr = dpr