移动端1px的问题到底应该谁背锅?产品?UI?前端?

514 阅读5分钟

前端

好多做过移动端的同学都知道著名的1px问题,但是为什么会产生这个问题你真的知道么?看完本篇文章,你心里一定会有一个明确的答案

为什么会有1px问题?

要想弄懂这个问题,我们首先要弄懂2个概念,视口和分辨率?

什么是视口(设备独立像素)?

简单来说视口就是显示页面的那部分区域。

  • 浏览器:在浏览器中通常与浏览器窗口相同,但不包括浏览器的 UI,菜单栏
  • 移动端: 打开chrome的开发者工具,我们可以模拟各个手机型号的显示情况,每种型号上面会显示一个尺寸,比如iPhone X显示的尺寸是375x812,实际iPhone X的分辨率会比这高很多,这里显示的就是视口大小。

image.png

什么是屏幕分辨率(物理像素)

屏幕分辨率指一个屏幕具体由多少个像素点组成。

下面是apple的官网上对手机分辨率的描述:

iPhone XS Max 和 iPhone SE的分辨率分别为2688 x 12421136 x 640。这表示手机分别在垂直和水平上所具有的像素点数。

当然分辨率高不代表屏幕就清晰,屏幕的清晰程度还与尺寸有关。

其实上面两个概念是我们经常接触到的,他们还有另外的名字设备独立像素(视口)和设备像素(屏幕分辨率)

设备独立像素(视口)和设备像素(屏幕分辨率)?

  • 设备像素(物理像素)(device pixel->DP)单位:pt,是绝对像素
  • 设备独立像素(css像素)(device independent pixel -> DIP)单位:px,是相对像素(相对于设备像素)

设备像素比(DPR)?

DPR(设备像素比)如何计算?

dpr = dp / dip;  
dpr=1; 代表一倍屏 1pt = 1px  
dpr=2; 代表两倍屏,两倍屏比一倍屏要清楚 2pt = 1px  

接下来我们来回答为什么会产生1px的问题

设计同学给我们设计图的时候,往往是按屏幕分辨率给的,比如iphone6的分辨率为750*1334,但是他的设备独立像素为375*667,那么设计图上的每1px我们都要除以2,如果设计图上的border1px,那么我们要写成0.5px,可是写成0.5px以后,你发现在安卓低端机上不显示,因为0.5px在安卓低端机上相当于0, 所以你写了一个1px,那设计同学一看,肯定是比较粗的呀,让你拿回去改。这就是这个问题的由来。

我们所说的1px和设计同学口中的1px是一样的么?

不一样,设计同学不知道dp,dip,dpr这些,他是按照屏幕分辨率画的图,那么他口中的1px就是1pt, 并不是我们知道的1px(这是重中之重,凡是解决此类问题,只要记住以下公式 1pt = 1px/dpr)

1px解决方案有哪些?他们的优缺点是什么?

所以解决1px的问题其实就是如何在2dpr的设备上显示0.5px,在3drp的设备上显示0.333px。 我们先来想一下如何实现0.5px

0.5px实现

.border-1px { border: 1px solid #333 }
@media screen and (min-device-pixel-ratio: 2) {
    .border-1px { border: 0.5px solid #333 }
}
/* dpr=2 和 dpr=3 情况下 border 相差无几,下面代码可以省略*/
@media screen and (min-device-pixel-ratio: 3) {
    .border-1px { border: 0.333333px solid #333 }
}

优点: 代码简单,使用css即可
缺点: IOS及Android老设备不支持

viewport + rem 实现

通过设置缩放,让 CSS 像素等于真正的物理像素。

const scale = 1 / window.devicePixelRatio;
const viewport = document.querySelector('meta[name="viewport"]');
if (!viewport) {
    viewport = document.createElement('meta');
    viewport.setAttribute('name', 'viewport');
    window.document.head.appendChild(viewport);
}

viewport.setAttribute('content', 'width=device-width,user-scalable=no,initial-scale=' + scale + ',maximum-scale=' + scale + ',minimum-scale=' + scale);

// 设置根字体大小
var docEl = document.documentElement; 
var fontsize = 10 * (docEl.clientWidth / 350) + 'px'; 
docEl.style.fontSize = fontsize;

// 在CSS中用rem单位就行了

缺点:

  • 适合新项目一开始就使用,如果是老项目,修改成本比较大

伪元素 + transform 实现

为什么用伪元素? 因为伪元素 ::after::before 是独立于当前元素,可以单独对其缩放而不影响元素本身的缩放

基于 media 查询判断不同的设备像素比对线条进行缩放:

.border-1px:before{
    content: '';
    position: absolute;
    top: 0;
    height: 1px;
    width: 100%;
    background-color: #999;
    transform-origin: 50% 0%;
}
@media (min-device-pixel-ratio:2){
    .border-1px:before{
        transform: scaleY(0.5);
    }
}
@media (min-device-pixel-ratio:3){
    .border-1px:before{
        transform: scaleY(0.33);
    }
}

注意如果需要满足圆角,需要给伪类也加上 border-radius

优点: 兼容性好

综上,推荐使用:

  • 伪元素 + transform 实现
  • 新项目可以尝试使用 viewport 方案

总结

  1. 我们在从设计师那边拿设计图的时候一定要问清,他是不是按照屏幕分辨率画的,如果是的话,他的1px就是1pt
  2. 在做移动端问题的时候,可能你的设备并不是iphone等这些知道视口尺寸的设备,也许是天猫精灵,小度音响等这些IOT设备,这些设配你并不知道他的视口大小,你可能只知道屏幕分辨率,所以这个时候你先通过获取宽高,来知道视口尺寸,方便你知道屏幕分辨率和视口的一个关系,也方便你后面做媒体查询
  3. 在做混合应用的时候,你获取宽高,一定要看清文档,因为你获取的宽高的单位有可能不是px而是pt,所以一定要弄清,方便测试的时候写对尺寸

最后,你看完了本篇文章你觉得1px问题应该谁背锅?

参考