移动端的头号问题:就是要去兼容适配不同的屏幕, 以获得好的用户体验. 移动端的web页面走到现在, 已经有了很多成熟的方案, 也是各有千秋, 这里总结下:
一些概念
rem
font size of the root element 会根据html元素的font-size值做相应的计算, 1rem = font-size px, 所以如果rem=75, 1rem=75px
物理像素
一个物理像素是指显示器上的最小物理显示单元, 在操作系统的调度下, 每一个设备都有自己的颜色值和亮度值
设备独立像素 (density-independent pixel)
可以认为是计算机系坐标系中的一点, 是由程序使用的虚拟像素点, css使用的就是这种像素. 最后转成物理像素
dpr (devicePixelRatio)
设备像素比, 设备像素比 = 物理像素 / 设备独立像素
dpr = 2 代表分辨率提高了一倍 javascript 属性: window.devicePixelRatio css 中可以通过-webkit-device-pixel-ratio, -webkit-min-device-pixel-ratio和 -webkit-max-device-pixel-ratio进行媒体查询, 对不同dpr的设备, 做一些样式适配(这里只针对webkit内核的浏览器和webview).
布局视口(layout viewport)
以看作是html元素的上一级容器即顶级容器,默认情况或者将html元素的width属性设为100%时, 会占满这个容器
这个宽度可以通过下面获得
document.documentElement.clientWidth
// or
document.documentElement.getBoundingClientRect().width
布局视口的宽度有一个默认值, 一般在 768px ~ 1024px 之间,最常见的宽度是 980px
这里的1px等于设备独立像素
使用媒体查询时 max-width 和 min-width 的值指的也是布局视口的宽
可视视口(visual viewport)
是指用户可见页面区域, 其宽度值为横向可见css像素数, 及1px=设备独立像素
通过 window.innerWidth
获得
理想视口 (ideal viewport)
<neta name="viewport" content="width=device-width" >
这里的device-width就是 理想视口, 使用 screen.width
, 这里1px不一定等于css的1px
那么, 可视视口 == 布局视口??
不一定
可视视口受到缩放比例的影响, 手动缩放或设置initial-scale都会改变, 没有设置inital-scale时, 浏览器也会取适当的缩放比例来是布局视口巧好铺面屏幕, 即: 布局视口尺寸 = 可视视口尺寸
布局视口的宽度受到 meta 标签中的 width 和 initial-scale 的影响 仅设置 width 的值时,这个值就是布局视口的宽度,width的值可以为正整数或特殊值 device-width 设置 initial-scale 的值时,布局视口的尺寸与可视视口的计算方式相同,但不受手动缩放的影响 同时设置 width 和 intial-scale 的值时,布局视口的宽取上述两个值中较大的一个
总结:
- 将 meta 标签中的 width 设为 device-width 同时禁用手动缩放可以使 布局视口尺寸 = 可视视口尺寸 = 理想视口尺寸,此时 设备像素比 = 物理像素数 / 理想视口尺寸 = 物理像素数 / 布局视口尺寸,对iphone5,一个CSS像素对应4个物理像素
- 为 initial-scale 设置任意合法的值同时禁用手动缩放就可以使 布局视口尺寸 = 可视视口尺寸
- initial-scale=1, 可视视口=理想视口=布局视口
- 布局视口宽 = 可视视口宽时 html 元素正好横向铺满窗口(但其后代元素若有横向 overflow 的情况,仍然会出现滚动条)
-
布局视口宽 > 可视视口宽时,出现横向滚动, 比如iphone6下 如下设置会有横向滚动条
<meta name="viewport" content="width=800, initial-scale=0.5">
这里布局视口800, 可视视口750
面临的问题
在不同的大小和高清的屏幕下:
保证布局的一致性:
总不能你的界面在iphone是看着完美, 在android上, 不同宽度的屏幕上布局错位吧..
保证字体大小的一致性:
对于字体, 设计师的要求是这样的: 任何屏幕上字体大小都要统一, 意味着: 不同屏幕的字体用尺子量是一样大的.
而且, 还有保证字体清晰显示, 不要出现13.3333px这种小数点的字号
保证1px边框一致性:
由于设计师的设计稿一般会基于iphone系列的手机出, 一般是2倍图, 比如iphone6屏幕是375px这么宽, 但视觉稿会有750px这么宽, 在这里面有1px, 最后还原到视觉稿时(缩放到1倍), 实际显示只有0.5px. iphone系列是支持0.5px的, 但…android就不一定了, 有些1px直接不显示了
有很多方法来解决这个问题
-
想办法然retina屏幕显示0.5px, 而普通屏显示1px, 比如检查屏幕支持0.5px不
-
使用border-image, 图片是2px高, 但一半是透明的… 最后border-width: 1px.
但在普通屏有些显示不出来, 圆角需要特殊处理
- 使用background-image, 原理同上, 2px高的背景显设置background-size: 100% 1px; 也可以用css将背景设置成1半有色一半透明
- 使用box-shadow模拟边框
.box-shadow-1px {
box-shadow: inset 0px -1px 1px -1px #c8c7cc;
}
但始终是模拟, 边框有颜色渐变
- viewport + rem实现, 后面会提到
- 伪类+transform, 使用scaleY(0.5)来将1px缩放成.5px
/**关键代码*/
.scale-1px:after{
content: '';
width: 100%;
height: 1px;
transform: scaleY(0.5);
}
保证图片清晰:
要求很简单: 不同大小和高清屏下能看到高清图片
因为我们的图片(png,jpg,gif)是位图的, 每个位图像素对应一个物理像素, 图片才能完美的清晰的显示出来
但, 我们的retina屏幕就出问题了
- 1个位图对应到4个物理像素
这种情况出现在普通屏清晰的图片显示在retina屏幕(图片放大), 由于单个位图像素不可以再进一步分割, 所以只能就近取色, 从而导致图片模糊(注意上述的几个颜色值).
解决办法就是对于retina用x2,x3的图,让1个位图对应到1个物理像素
- 4个位图显示在1个物理像素
这种请款出现在retina屏高清的图片显示在普通屏上(图片缩放), 缩放的结果使取色只能通过一定的算法(显示结果就是一张只有原图像素总数四分之一, 我们称这个过程叫做downsampling), 肉眼看上去虽然图片不会模糊, 但是会觉得图片缺少一些锐利度, 或者是有点色差(但还是可以接受的).
解决办法就是对应普通屏用1倍图, 让1个位图对应到1个物理像素
- 最后给个图片分别展示 放大, 清晰, 缩小 的图片的效果
- 最好的方案是
不同的dpr下, 加载不同的图片, 这又引出另一个问题: 图片服务器, 通过url参数来控制输出的图片质量
在实际项目中使用的方案
手淘的方案flexible
手淘现在都还是这个方案…
原理
这个方案依赖viewport, 如上面说的布局视口
首先了解下, 在移动端viewport的一些东西, 他在移动端有这样些性质:
设置成下面这样, 页面的总宽度会默认设置成980px(safari下, 其它浏览器还不一样), 不管你是不是retina屏, 都是980px
<meta name="viewport" content="">
设这成下面这样, 页面的总宽度就和你屏幕宽度一样了, 比如iphone6, dpr=2, 但这是页面总宽度=375px
<meta name="viewport" content="initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
<!-- or -->
<meta name="viewport" content="width=device-width">
还可以设置成这样, 页面宽度就放到了一倍, iphone6的页面宽度此时就是750px, 恰好css的1px等于物理像素的1px
<meta name="viewport" content="initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no">
当然, 并不是所有的手机都这么听话, 比如我天朝的vivo(照亮你的美!)要各种hack, 才能兼容
思路
然后, 知道上面这些后, 我们就有思路来做适配了
思路是:
- 根据dpr来修改viewport来缩放页面, 达到css的1px就等于物理像素的1px, 解决1px问题, 图片高清问题
- 根据dpr来修改html的font-size, 利用rem单位等比的缩放, 保证布局一致
- 根据dpr来设置html的date-dpr, 通过css来控制font-size, 达到字体大小一致,并高清显示
但现实是残酷的:
- 并不是所有的手机的viewport都那么的听话, 屏幕不会安预期缩放, 目前的flexible的方案是放弃android, 不管android的dpr是多少, 都按一倍屏处理, 自然1px问题, 图片高清问题没法解决. 但在ios上表现良好
使用
- 将flexible.js引入到head里
<script src="http://g.tbcdn.cn/mtb/lib-flexible/0.3.4/??flexible_css.js,flexible.js"></script>
- 使用rem做长度单位
计算1rem = ?px, 设这个值是r, r就是html上对应的font-size的值, 一般这个值是可视视口宽度乘以一个系数, 如果可视视口是750, r = 750 * m;
如果设计稿是以iphone6 750px出的, 这时是2倍稿, 理论上r可以等于任何值
我们假设:iphone6下视觉稿是750出的, m 取1, 0.1, 0.05的情况:
-
r = 750
视觉稿上75px, css中就要写成0.1rem, 保正不同retina屏幕显示的长度一致:
- 在dpr = 1, 屏幕宽为375的屏幕下, html 的 font-size会变成750/2, 最后在浏览器显示成 375*0.1= 37.5px
- 在dpr = 2, 屏幕宽为375的屏幕下, html 的 font-size会变成750, 最后在浏览器显示成 750*0.1= 750px
- 在dpr = 3, 屏幕宽为375的屏幕下, html 的 font-size会变成750/23=1125, 最后在浏览器显示成 11250.1= 112.5px
-
r = 75
视觉稿上75px, css中就要写成1rem, 保正不同retina屏幕显示的长度一致:
- 在dpr = 1, 屏幕宽为375的屏幕下, html 的 font-size会变成75/2, 最后在浏览器显示成 37.5*1= 37.5px
- 在dpr = 2, 屏幕宽为375的屏幕下, html 的 font-size会变成75, 最后在浏览器显示成 75*1= 75px
- 在dpr = 3, 屏幕宽为375的屏幕下, html 的 font-size会变成75/23=112.5, 最后在浏览器显示成 112.51= 112.5px
-
r = 37.5
视觉稿上75px, css中就要写成2rem, 保正不同retina屏幕显示的长度一致:
- 在dpr = 1, 屏幕宽为375的屏幕下, html 的 font-size会变成37.5/2, 最后在浏览器显示成 16.75*2= 37.5px
- 在dpr = 2, 屏幕宽为375的屏幕下, html 的 font-size会变成37.5, 最后在浏览器显示成 37.5*2= 75px
- 在dpr = 3, 屏幕宽为375的屏幕下, html 的 font-size会变成37.5/23=75, 最后在浏览器显示成 752= 112.5px
但Flexible rem的设置是:
function refreshRem(){
var width = docEl.getBoundingClientRect().width;
if (width / dpr > 540) {
width = 540 * dpr;
}
var rem = width / 10;
docEl.style.fontSize = rem + 'px';
flexible.rem = win.rem = rem;
}
The Element.getBoundingClientRect() method returns the size of an element and its position relative to the viewport.
那iphone6下视觉稿是750出的视觉稿的rem就是75, 据说这样为了更好兼容以后的vw
但在开发时你还要去算rem, 是不是很麻烦?
为了便于开发, 希望设计稿是多少像素, css就写多少像素, 那你可以结合webpack的px2rem-loader 这个css loader来使用
{
loaders: [{
test: /\.css$/,
loader: 'style!css!px2rem?remUnit=75&remPrecision=8'
}]
}
记得把remUnit设置成你UI设计稿相应的大小(比如设计稿是1125, 3倍图, 此时的remUtil=112.5, css的长度按3倍图的像素写)
-
字体不要用rem做单位
- 不希望文本在retina屏幕下变小
- 希望在大屏幕下看到更多的文本
- 大多数字体文件自带一些点阵尺寸, 通常是16px或24px, 不希望出现13px或15px或带小数小数的尺寸
px2rem中可以
- /*no*/ 表示不转
- /*px*/ 表示根据[data-dpr=”1/2/3”] 分别设置px (字体一般不用rem作单位),最后生成:
[data-dpr="1"] .selector { font-size: 14px; } [data-dpr="2"] .selector { font-size: 28px; } [data-dpr="3"] .selector { font-size: 42px; }
优缺点
优点
- 巧妙的使用缩放, 使css的1px==视觉稿的1px, 完成的高清屏的适配, 虽然中间要借助于工具来开发
缺点
- 使用复杂些
- 如果你的webview里出现iframe, 里面的viewport是不起作用的…我就遇到了
- 这种方案没办法让android的也高清显示..毕竟android的奇葩比较多, 但也有些hack可以做到
网易的实现
思路
使用相对单位 rem 并将设备的可视视口宽度乘以一个系数得到 html 元素的 font-size,元素布局时不超出可视视口宽度即可, 这和手淘一样
理论上如果设置了 meta 中的 width 值且大于可视视口的计算结果,会出现横向滚动条,但网易和淘宝的方案都没有设置 width,此时布局视口的宽等于可视视口,只要没有超出可视视口宽度的元素,就不会出现滚动条
实现
-
如何为设备选择可视视口尺寸, 网易新闻的方案相对简单,采用理想视口尺寸作为可视视口尺寸,代码也十分简单,只需要将缩放比定为 1
<meta name="viewport" content="initial-scale=1,maximum-scale=1, minimum-scale=1">
-
使用rem做长度单位, 适配各个屏幕
那么1rem=?px
网易估计为了方便计算, 在页面 初始化是设置
document.documentElement.style.fontSize = window.innerWidth / 7.5 + 'px';
因为这个时侯理想视口等于可视视口, 所以假如在iphone6的机子上, window.innerWidth=375, 按上面的公式算出fontSize=50, 1rem = 50px,
往往设计稿是按2倍理想视口画的, 那么按上面公式算, window.innerWidth=750, fontSize=100;1rem = 设计稿的100px, 开发时就方便视觉还原, 比如视觉高24px, css就写0.24rem
然后适配其它屏幕就根据设备的理想视口重新计算font-size, 达到屏幕适配
优缺点
这应该是手淘方案的降级版….因为他有好几个问题没有解决
- 1px border问题, 如果程序员css写0.01rem, 在android下可能显示不出来
- 图片高清, 没有处理各个高清屏,当然没发做图片高清
- 字体统一大小及高清问题,网页新闻字体也使用了rem做单位, 意味这不同屏幕字号大小不统一, 会有小数点字号, 不高清
唯一的好处就是, css 0.01rem就是视觉稿1px, 方便开发
类似的实现
网上我还找到一个实现, 也是放弃高清屏, 但更简单
思路是: 固定宽度, viewport缩放
<meta name="viewport" content="width=640,initial-scale=0.5,maximum-scale=0.5,minimum-scale=0.5,user-scalable=no">
640就是视觉稿的宽度, 这里将其设为布局视口宽度, 后面的0.5这个比例是根据实际屏幕大小, 比如实际屏幕理想视口只有320宽, initial-scale设为0.5, 使可视视口=布局视口
效果:
Media Queries的方式
这是最早一批的适配方案了
思路:
通过媒体查询, 对不同布局视口宽度的屏幕, 使用不同的css
实现
<meta name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
@media screen and (max-width: 600px) { /*当屏幕尺寸小于600px时,应用下面的CSS样式*/
/*你的css代码*/
}
可以参考14年的时侯比较火的响应式amazeui
优缺点
优点
- media query可以做到设备像素比的判断, 方法简单, 成本低, 特别是对移动和PC维护同一套代码的时候.目前像Bootstrap等框架使用这种方式布局
- 图片便于修改, 只需修改css文件
- 调整屏幕宽度的时候不用刷新页面即可响应式展示
缺点
- 代码量比较大, 维护不方便
- 为了兼顾大屏幕或高清设备, 会造成其他设备资源浪费, 特别是加载图片资源
- 为了兼顾移动端和PC端各自响应式的展示效果, 难免会损失各自特有的交互方式
vw 的方式
这个是利用css支持的, 先对于可视视口的百分比的方式来实现布局适配, 以vw做单位
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no" />
如果设计稿是750px, 那么 1vw = 7.5px; 意思是1vw代表1/100的viewport的宽度(设计稿是750)
但这个方案很多浏览器不支持, retina屏幕也没做处理, 我持观望态度