项目背景

最近又做了个企业官网的项目;基本都是些静态界面,对于这种看起来没什么难度的项目,这个时候就要追求点有难度的,才能学习到东西。所以摒弃了常规的一个一个html写的方式,vue组件化+响应式布局才有点意思,正好之前也做过一个自己公司的响应式的官网,这次更加加深了理解。

架构想法

基本的想法都已经确定过了,css配合media query引入,js则根据screen.width动态处理逻辑。这个有鉴于张鑫旭的基于screen.width的伪响应式开发,这篇文章也解释了使用screen.width可以明确地区分不同的js逻辑,而不会耦合在一起。如果为了实现动态拉伸窗口切换样式的需求,可以监听window.resize事件,但是实际这没有什么必要,因为用户不会无聊到不断改变浏览器窗口大小。

对于在不同屏幕下的适配问题,首选自然是rem。以往我们公司采用的却不是这种方式。
1.设计师给750宽的图,我们也设定页面宽度为750。
2.动态调整viewport的值,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<style>
html,body{margin:0;}
</style>
<script>
var w = document.documentElement.getBoundingClientRect().width;
var meta = document.createElement('meta');
meta.name="viewport";
meta.content="initial-scale=" + w/750 + ",maximum-scale=" + w/750 + ",user-scalable=no";
var head = document.querySelector('head');
head.appendChild(meta);
</script>
</head>
</script>

想法很简单,取物理像素(比如iphone6设备像素750px,物理像素375px,devicePixelRatio为2;这些就不解释了)除750。得到百分比。这样在不同的屏幕上永远固定页面宽度为750像素。其实这里有几个问题:
1.使用getBoundingClientRect(),而不使用screen.width。因为在实际测试发现:有些机型screen.width取得是设备像素,有些取的又是物理像素,兼容性很差;这个在张鑫旭的设备像素比devicePixelRatio简单介绍里面也有体现。
这个时候查看淘宝聚划算(这个网站就是一个典型的rem网站)的源码发现如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
~function(){
function e(){
r.innerText="html{font-size:"+(a.style.fontSize=a.getBoundingClientRect().width/o*d+"px")+" !important;}"
}
var t=navigator.userAgent,
n=(t.match(/(iPhone|iPad|iPod)/),t.match(/Android/i),window),
i=document,
a=i.documentElement,
o=(n.devicePixelRatio||1,375),
d=100,
r=(i.head.querySelector('[name="viewport"]'),
i.createElement("style"));
r.innerText="html{font-size:100px !important}",
i.head.appendChild(r),
e(),
n.addEventListener("resize",e,!1),
a.className+=t.match(/ucbrowser/i)?" app-uc ":""
}();

可见getBoundingClientRect()获取的基本上应该是物理像素,实际测试也证明了这个

2.必须得首先使用进行初始化,否则getBoundingClientRect()取值会不正确,所以先要初始化一次viewport,再动态计算出缩放比,添加一个新的viewport覆盖

3.这种方式直接缩放整个页面,得到的文字字体缩放效果其实是不对的。

新的实践

既然上面的策略有瑕疵,那就采用新的方式吧。看了下alloyteam的移动端适配利器-rem; 基本这可能就是最佳的方案吧。一言不合上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<style>
html,body{margin:0;}
</style>
<script>
//设置html font-size
var w = document.documentElement.getBoundingClientRect().width, html = document.querySelector('html');
html.style.fontSize = devicePixelRatio*w/10+'px';
// 设置对应的缩小比,可以实现1px设备像素
var meta = document.createElement('meta');
meta.name="viewport";
meta.content="initial-scale="+1/devicePixelRatio+",maximum-scale="+1/devicePixelRatio+",user-scalable=no";
var head = document.querySelector('head');
head.appendChild(meta);
</script>
<style>
.div{
background-color:black; width:10rem; height:10rem;
}
.font{
font-size:1rem;
}
.onepxline{
width:5rem; height:1px; margin:1rem 0;
background-color: red;
}
</style>
</head>
<body>
<div class="div"></div>
<span class="font">测试字体大小</span>
<div class="onepxline"></div>
<script type="text/javascript">
// alert(document.documentElement.getBoundingClientRect().width+'-----------'+screen.width+'----------'+document.querySelector('.div').offsetWidth);
</script>
</body>
</html>

总结下来就是几点:
1.动态设置html的font-size:其实也就是动态设置1rem的值,当然这里也是有相关规律的,就是永远设置为设备像素/10;以iphone6为例,这里的font-size会被设置为750px/10 = 75px。这样就保证了10rem永远是整个屏幕的设备像素,刚好占满整个屏幕。

2.因为是按照设备像素设计和写的css所以,设置viewport缩放1/devicePixelRatio,刚好就是物理像素。

3.这样也就很好的解决了1px设备像素的问题和图片高清问题。

实际处理还有点小技巧,我们不可能总是去计算所有的rem,这样太累。幸好有css预处理器,比如saas,写个自定义函数自动转换px到rem。就可以完全按照设计图来写css了

1
2
3
4
5
6
7
@function px2rem($px){
$rem: 75px;
@return $px/$rem;
}
body{
font-size: px2rem(10px);
}