概念理解
CSS像素(逻辑像素/设备独立像素/设备无关像素)
CSS像素是一个抽像的单位,主要使用在浏览器上,用来精确度量Web页面上的内容。
当页面缩放比例为100%时,一个CSS像素等于一个设备独立像素。
物理像素
是显示设备中一个最微小的物理部件。每个像素可以根据操作系统设置自己的颜色和亮度。正是这些设备像素的微小距离欺骗了我们肉眼看到的图像效果。
CSS像素和物理像素的联系
早期手机分辨率较低的时候,一个CSS像素等于一个物理像素,没有设备独立像素。苹果公司便推出了所谓的Retina屏,比如iphone4的逻辑分辨率320x480,物理分辨率640x960,但屏幕尺寸却没变化,这就意味着同样大小的屏幕上,物理像素却多了一倍,这时,1 个独立像素 == 2 个物理像素,屏幕不缩放的情况下,一个css像素是等于两个物理像素的。
设备像素比(dpr) 被用来描述物理像素和设备独立像素之间的关系:
| 设备像素比=物理像素/设备独立像素 |
|---|
dpr的值越大,css中1px代表的物理像素就会越多,屏幕也就看起来越清楚。
通过window.devicePixelRatio可以获取到dpr,但devicePixelRatio在手机的火狐浏览器和IE浏览器还存在着问题。
更多详情参考MDN上的接口文档:Window.devicePixelRatio - Web API 接口参考 | MDN
3个viewport
layout viewport: 浏览器默认的viewport,通常较宽,远大于屏幕本身的宽度。可以通过document.documentElement.clientWidth来获取。
visual viewport:浏览器可视区域的大小,可以通过window.innerWidth来获取,但在Android 2, Oprea mini 和 UC 8中无法正确获取。
ideal viewport:理想状态的viewport,首先不需要用户缩放和横向滚动条就能正常的查看网站的所有内容;第二,显示的文字的大小是合适。iOS的都是,Android各有不同,在css中把元素宽度设成ideal viewport的大小,那这个元素刚好占满屏幕宽度而且没滚动条。
移动端开发中想要获取的就是这个ideal viewport的值。
<meta name="viewport" content="width=device-width, initial-scale=1">
-
第一种理论: width=device-width和initial-scale=1都可以将默认的layout viewport设置成ideal viewport。前者在iphone和ipad上,无论是竖屏还是横屏,宽度都是竖屏时ideal viewport的宽度,后者在windows phone 上的IE 无论是竖屏还是横屏都把宽度设为竖屏时ideal viewport的宽度。所以一般两个都写上。
-
第二种理论:
width=device-width:将layout viewport设置成ideal viewport。
initial-scale=1:initial-scale = 理想视口宽度 / 视觉视口宽度,此时visual viewport=ideal viewport,1个CSS像素=1个设备独立像素(注意:并不是物理像素)。这样在各种设备上呈现出来的页面布局能大体相似。
所以结合两者,能让layout viewport=ideal viewport=visual viewport。
移动端适配方案
Flexible方案
这也是canary项目中使用的方案,缩放页面,并且dpr的值来修改html的font-size,从而使用rem实现等比缩放。
虽然在server/views/njk/layout.njk文件中写了"initial-scale=1/dpr"缩放了页面,但是在server/views/njk/dpmmweb/layout.njk中又写了initial-scale=1 不缩放页面,所以dpmmweb下的njk页面默认缩放比例仍然是1.0。
参考链接:
使用vw、vh
Flexible方案是通过JavaScript来模拟vw的特性,那么到今天为止,vw已经得到了众多浏览器的支持,也就是说,可以直接考虑将vw单位运用于我们的适配布局中。
vw是基于Viewport视窗的长度单位,这里的视窗(Viewport)指的就是浏览器可视化的区域。而这个可视区域是window.innerWidth/window.innerHeight的大小.
比如iPhone6的innerWidth是375,那么1vw=3.75px

要实现设计图给的px单位到vw单位的转换,可以使用PostCSS的插件. 官方文档:postcss-px-to-viewport
参考链接:再聊移动端页面的适配
实际应用
在项目中实际应用vw方案时,使用vh做为容器的高度单位,会让使用transform方案的1px边框展示出现部分变粗的问题,但是如果使用vw来作为高度的单位,展示正常,不会出现上述问题。

现在另外为了和其他端保持一致,决定还是使用rem方案,但可以考虑统一使用vw作为单位。
文字较多的部分,字体大小仍然使用px作为单位,px可以让大屏幕手机展示出更多的内容,更符合人们的阅读习惯。
参考链接:rem, vw, 还是...? 各凭本事的移动端适配方案
1px解决方案
目前项目中的解决方案
在outline.scss中设置了一些通用的边框类,这些边框已经实现了1px,在njk页面引入这些类就可以达到1px边框的效果。
outline.scss中实现1px的方法是:
1.准备一张符合条件的边框背景图,模拟在背景上。
伪类加transform
原理:把原先元素的 border 去掉,然后利用 :before 或者 :after 重做 border ,根据媒体查询结合tranform缩放为相应尺寸
实现关键代码:
.border-bottom {
position: relative;
}
/* 2倍屏 */
@media only screen and (-webkit-min-device-pixel-ratio: 2.0) {
.border-bottom::after {
position: absolute;
left: 0;
bottom: 0;
height: 1px;
width: 100%;
background: #333333;
content: '';
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
}
}
/* 3倍屏 */
@media only screen and (-webkit-min-device-pixel-ratio: 2.0) {
.border-bottom::after {
...//其他和2倍屏类似
-webkit-transform: scaleY(0.33);
transform: scaleY(0.33);
}
}
效果对比图:
动态修改initial-scale
原理:在运行的时候拿到设备的devicePixelRatio,动态改变viewport的initial-scale为 1/devicePixelRatio,这样就能保证1px的宽度就是真正的1物理像素宽。
实现关键代码:
function changeView(){
const clientWidth = window.screen.width;
const dpr = window.devicePixelRatio;
const vp = document.createElement('meta');
document.documentElement.style.fontSize = clientWidth > 414 ? '20px' : 20 * dpr * clientWidth / 360 + 'px';
vp.name = 'viewport';
vp.content = `initial-scale=${1.0 * 1 / dpr}, user-scalable=no, width=device-width`;
var m = document.getElementsByTagName('meta')[0];
m.parentNode.insertBefore(vp, m);
}
changeView();
效果对比图: 原始:
执行后:
1px总结
最终决定选择伪元素+transform方案,这种方式比较灵活,可以现在公用样式文件中创建基本的1px写法,后续使用时只需给元素加上相应class,并且可以根据需要修改颜色。
适配iphoneX
目前layout.njk页面已经使用viewport-fit,也有许多页面使用了env和constant两个函数设定安全区域与边界的距离。
<meta name="viewport" content="viewport-fit=cover">
<!-- 设置吸底按钮适配可以这么写 -->
<style>
.jump-container {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 44px;
height: calc(44px + constant(safe-area-inset-bottom));
height: calc(44px + env(safe-area-inset-bottom));
background: #FFFFFF;
border-radius: 10px 10px 0 0;
}
</style>
横屏适配
目前只有极少量页面做了。可以使用媒体特性@media,orientation: portrait是竖屏,orientation: landscape是横屏;也可以通过在js文件中监听resize事件,再根据的window.orientation属性判断旋转的角度得出是横屏还是竖屏。
图片模糊问题
1.尽量使用svg,这样无论放大多少倍都不会失真
2.上传多张图片进行适配。推荐使用img的srcset属性让浏览器自动根据像素密度匹配图片,比较简单明了。
<img src="source.jpg" srcset="source_2x.jpg 2x, source_3x.jpg 3x">
其实我们项目里现在一般直接用的都是3倍图。