此篇总结是在学习了 viewport 基础知识,再参考了淘宝的 lib.flexible 可伸缩布局这个库,自己推演了 lib.flexible 是怎么作出这个解决方案的。
上一篇我们说过,对于 device-width 相同但是分辨率不同的手机我们可以通过设置 meta viewport 把移动页面的宽度归一到一个统一的宽度(这样一套布局就可以适用不同分辨率的手机)。但是现在不但 iphone 阵营自己出了好几个 device-width (320px,375px,414px),android 阵营更时百花齐放。那么我们对于不同的宽度的页面我们希望如果能用一套 css 搞定。最容易想到的就是使用百分比来设置尺寸。但是 css 百分比是根据父元素的尺寸来计算,而不是根元素譬如 viewport,这样对嵌套过深的元素计算尺寸非常不友好。同理使用 em 单位也会产生同样的问题。幸好 css3 出了一个新单位 rem,我简单的介绍下 rem 的规则:根据根元素(html)的字体大小来计算当前尺寸。譬如说 html 这个元素的 font-size 设了 10px,那么当前页面 1rem 就是 10px,2rem 就是 20px,如果 html 元素的 font-size 设置了 75px,则当前页面 1rem = 75px,2rem = 150px。
如果我们把页面宽度分成 100 份,把 html 的 font-size 设置成 viewport-width/100(px),则当前页面 1rem 就等于 1% 页面宽度,这样使用 rem 作为单位开发就相当于用百分比单位来设置尺寸了。如果愿意可以把所有的尺寸都转成百分比布局,那么所有不同宽度的页面都可以用一套 css 搞定。这个方案是可以实现的,只要把 html 的 font-size 设置成 document.documentElement.clientWidth/100(px)。
那是不是简单的写下如下的代码就搞定了多屏适配呢?
<html style="font-size:(document.documentElement.clientWidth/100)px">
<head>
<meta name="viewport" content="width=device-width">
<style>
div {
width: 50rem;
}
</style>
<head>
</html>
这样的方案在高分辨率的手机上会有一系列的问题:
- 在大屏幕高分辨率的手机上,以 320px 为页面宽度布局,元素过大,而且对于设计不友好(空间太小)。
- 会出现 1px 占2个或者多个物理像素的情况,无法做到对设计稿高度还原。
- 譬如在 640x960 分辨率的手机上如果用 320px 布局,页面上有
《img src="xxx.png" style="width:25px;height:25px"/》
,如果切图使用 25x25 的图片,会产生模糊的情况,因为其实高分辨率手机是把图片放大了,原因大家网上搜下,这不是这篇主要总结的问题。
针对第一个问题高分辨率手机我们可以设置 initial-scale 来把初始化页面缩小。打个比方 640x960 分辨率的手机设置 <meta name="viewport" content="width=device-width, initial-scale=0.5">
,那么页面初始化宽度就是 750px。
针对第二个问题也可以通过把页面宽度设置成同手机分辨率宽度一样,来做到 css 像素和物理像素 1:1 来真实还原设计稿。问题是我怎么知道手机分辨率是多少,且来看 window.devicePixelRatio: 他是密度无关像素(dip)和物理像素比(我们俗称的 dpr)。举个例子如果 device-width 是 320,window.devicePixelRatio = 2 说明手机分辨率是 640xYYY,window.devicePixelRatio = 3 说明手机分辨率是 960xYYY。那么我们根据自己的项目需求针对不同的分辨率的手机对上面的方案可以做一个改进,这次我们要动态生成 meta(下面是伪代码,只是为了说明):
<html style="font-size:(document.documentElement.clientWidth/100)px">
<head>
<script>
var deviceWidth = document.documentElement.clientWidth;
var dpr = window.devicePixelRatio;
var scale = 1 / dpr; // 如果我们做到 dip 和物理像素 1:1
var metaEl = document.createElement('meta');
metaEl.setAttribute('name', 'viewport');
metaEl.setAttribute('content', 'width=device-width, initial-scale=' + scale );
document.firstElementChild.appendChild(metaEl);
</script>
<style>
div {
width: 50rem;
}
</style>
<head>
</html>
当然这个 scale 怎么设置可以根据项目具体调整。
针对第三个问题我们的解决方案是不同分辨率,加载不同的图片。还是那上面第三点问题中的例子,如果 dpr = 2,那么我就提供一个 50x50 的图片放在你 25x25 的 img 元素里面,这样就能解决图片模糊的问题。但是如果每个图片都需要判断 dpr 动态设置 img 的 src,那么写起来是很麻烦,是否能有方案统一处理?有!把 img 全部转换成 background-image 然后用 css 来统一处理,看下代码:
<html>
<head>
<script>
var docEl = document.documentElement
var deviceWidth = docEl.clientWidth;
var dpr = window.devicePixelRatio;
var scale = 1 / dpr; // 如果我们做到 dip 和物理像素 1:1
var metaEl = document.createElement('meta');
metaEl.setAttribute('name', 'viewport');
metaEl.setAttribute('content', 'width=device-width, initial-scale=' + scale );
docEl.firstElementChild.appendChild(metaEl);
// 给 html 添加 font-size 和 data-dpr
docEl.style.fontSize = document.documentElement.clientWidth/100 + 'px';
docEl.setAttribute('data-dpr', dpr);
</script>
<style>
div {
width: 50rem;
}
.page {
width: 90rem;
height: 100rem;
background-image: url(bg.png) /* 25x25 图片 */
}
[data-dpr="2"] .page {
background-image: url(bg@2x.png) /* 50x50 图片 */
}
[data-dpr="3"] .page {
background-image: url(bg@3x.png) /* 75x75 图片 */
}
</style>
<head>
</html>
这样不同分辨率的页面自动加载不同的图片,解决了第三个图片模糊的问题。多屏适配的方案大致的内容就都在这里了,但是我们参看 lib.flexible 库,它对字体推荐是使用 px 而非 rem,那么针对字体 css 同样需要:
div {
width: 1rem;
height: 0.4rem;
font-size: 12px; /* 默认写上dpr为1的fontSize */
}
[data-dpr="2"] div {
font-size: 24px;
}
[data-dpr="3"] div {
font-size: 36px;
}
网上搜了下,看到给出的理由是 :
设计师原本的要求是这样的:任何手机屏幕上字体大小都要统一 (注意,字体不可以用rem,误差太大了,且不能满足任何屏幕下字体大小相同)
我觉得这个说法也是合理的,所以最终多屏适配的方案的细节还是需要大家根据自己的项目进行微调。
移动H5页面开发多屏适配的方案内容总结到这里,我了解了大致的原理就可以放心使用 lib.flexible 了。