娓娓道来为什么移动端会有 1px 问题以及解决方案(附demo)

2,165

工作中接触到了移动端的开发,所以最近学习一下移动端相关内容。

为什么会有1px问题

在开发移动端项目时,针对屏幕分辨率为 375*667 的设备,UI设计师给的设计稿是 750*1334 像素的,对于UI来说,这 1px 是相对于 750*1334,而我们写的 1px 的 css 是相对于 375*667 的,所以映射到 750*1334 的设计稿就是 2px

此时我们会发出两个问题:

  1. 为什么UI针对 375*667 的设备设计 750*1334 的图
  2. 为什么不写 0.5px

为什么要设计 750*1334 的图

先看一张图:

下面简单介绍一下这些概念:屏幕尺寸、物理分辨率(px)、逻辑分辨率(pt)、设备像素比(DPR)、像素密度(PPI)

屏幕尺寸

英寸(inch,缩写为in)在荷兰语中的本意是大拇指,一英寸就是指甲底部普通人拇指的宽度。

英寸和厘米的换算:1英寸 = 2.54 厘米

物理分辨率(px)

首先需要了解像素和分辨率

  • 什么是像素(px):

px 全称 pixel,像素,是屏幕上显示数据的最基本的点。它不是自然界的长度单位,可以画的很小,也可以很大。如果点很小,那画面就清晰,我们称它为“分辨率高”,反之,就是“分辨率低”。所以,“点”的大小是会“变”的,也称为“相对长度”。从计算机技术的角度来解释,像素是硬件和软件所能控制的最小单位。一幅图像通常包含成千上万个像素,每个像素都有自己的颜色信息,它们紧密地组合在一起。由于人眼的错觉,这些组合在一起的像素被当成一幅完整的图像。

  • 什么是分辨率

屏幕分辨率是指纵横向上的像素点数,单位是px。屏幕分辨率确定计算机屏幕上显示多少信息的设置,以水平和垂直像素来衡量。就相同大小的屏幕而言,当屏幕分辨率低时(例如 640 x 480),在屏幕上显示的像素少,单个像素尺寸比较大。屏幕分辨率高时(例如 1600 x 1200),在屏幕上显示的像素多,单个像素尺寸比较小。如图所示:

物理分辨率就是屏幕纵横向上的像素点数

逻辑分辨率(pt)

pt 全称为 point,是独立像素的意思,它是绝对长度,不随屏幕像素密度变化而变化(和我们日常用到的毫米、厘米是一个意思,只是它要小得多),查金山词霸可以看到,确切的说法是一个专用的印刷单位“磅”,大小为1/72英寸(1pt = 1/72inc = 0.035694cm)。在非视网膜的 iPhone 上(iPhone 3G),苹果规定 1px=1pt,也就是说 pt 和像素点是一一对应的。

  • 什么是逻辑分辨率

对于物理分辨率,是硬件实实在在可以用更多的像素显示图像,每个像素点的显示都是由软件一一控制的,这个就是逻辑分辨率,由于历史原因(开个玩笑),逻辑分辨率的 1px 需要使用该方向上物理像素的 2px 或者更多像素来显示,逻辑分辨率就是软件层面映射到屏幕纵横向上像素点数

设备像素比(DPR)

设备像素比即 device pixel ratio 简称 dpr,即物理像素和设备独立像素的比值,是处理逻辑像素映射到物理像素的比值,通过底层软件实现

在几年前,我们还用着分辨率非常低的手机,每个物理像素都是由软件控制的,物理像素与逻辑像素一一对应,设备像素比为 1,比如下面左侧的白色手机,它的分辨率是320x480,我们可以在上面浏览正常的文字、图片等等。

但是,随着科技的发展,低分辨率的手机已经不能满足我们的需求了。很快,更高分辨率的屏幕诞生了,比如下面的黑色手机,它的分辨率是640x940,正好是白色手机的两倍。

理论上来讲,在白色手机上相同大小的图片和文字,在黑色手机上会被缩放一倍,因为它的分辨率提高了一倍。这样,岂不是后面出现更高分辨率的手机,页面元素会变得越来越小吗?

然而,事实并不是这样的,我们现在使用的智能手机,不管分辨率多高,他们所展示的界面比例都是基本类似的。乔布斯在iPhone4的发布会上首次提出了Retina Display(视网膜屏幕)的概念,它正是解决了上面的问题,这也使它成为一款跨时代的手机。

在iPhone4使用的视网膜屏幕中,把2x2个像素当1个像素使用,这样让屏幕看起来更精致,但是元素的大小却不会改变

通过上文我们可以知道,iPhone4 把 2x2 个像素当 1 个像素使用,则设备像素比为 2

像素密度

PPI(Pixel Per Inch):每英寸包括的像素数

PPI可以用于描述屏幕的清晰度以及一张图片的质量

使用PPI描述图片时,PPI越高,图片质量越高,使用PPI描述屏幕时,PPI越高,屏幕越清晰

问题回答

由此可见,设计稿为 750*1334 一是为了让UI稿更清晰,图片图标更加清晰(@2x、@3x图片的由来),二是更加符合屏幕物理像素

为什么不写 0.5px:

其中 Chrome 把 0.5px 四舍五入变成了 1px,而 firefox/safari 能够画出半个像素的边,并且 Chrome 会把小于 0.5px 的当成 0,而 Firefox 会把不小于 0.55px 当成 1px,Safari 是把不小于0.75px当成1px,进一步在手机上观察 iOS 的 Chrome 会画出 0.5px 的边,而安卓(5.0)原生浏览器是不行的。所以直接设置 0.5px 不同浏览器的差异比较大,并且我们看到不同系统的不同浏览器对小数点的px有不同的处理。所以如果我们把单位设置成小数的px包括宽高等,其实不太可靠,因为不同浏览器表现不一样。如下图所示:

你可以输入链接地址:service-ph6ff7lw-1257620930.gz.apigw.tencentcs.com/1px.html

解决方案

方案分析

由上述可知,我们 css 写的 1px 会被映射为物理像素的 2px,那我们可以通过哪些方法可以将这 2px 转换成 1px 呢?

  1. 直接写 0.5px
  2. 通过缩放实现将 2px 转换为 1px
    • 通过 transform:scale(0.5)
    • 通过 viewport 中的 initial-scale=0.5
  3. 通过图片模拟实现 1px
  4. 裁剪
    • 通过 box-shadown
    • 通过渐变百分比裁剪
  5. 使用 svg

1、0.5px

在 WWDC大会上,给出来了1px方案,当写 0.5px 的时候,就会显示一个物理像素宽度的 border,而不是一个css像素的 border。 所以在iOS下,你可以这样写

border: 1px solid #999;
  • 优点:简单,没有副作用
  • 缺点:支持iOS 8+,安卓待兼容

2、伪类搭配 transform:scale(0.5)

1条边

实现原理: 设置背景颜色为边框颜色,设置宽高为 1px,使用 scale 实现缩放

注意:也可以使用 scalcX 或者 scaleY 实现缩放

/* 1条边 */
.scale {
    position: relative;
}
.scale::after {
    position: absolute;
    content: "";
    width: 200%; /* 宽度 2 倍 */
    height: 1px; /* 高度为 1px */
    transform: scale(0.5); /* 缩小1倍 */
    transform-origin: left top; /* 以左上角为基准点进行缩放 */
    background-color: #999;
    top: 0;
    left: 0;
}

4条边

/* 4条边 */
.scale-4 {
    position: relative;
}
.scale-4::after {
    position: absolute;
    content: "";
    /* 宽高为2倍 */
    width: 200%;
    height: 200%;
    transform-origin: left top; /* 以左上角为基准点进行缩放 */
    border: 1px solid #999; /* 设置 border */
    box-sizing: border-box; /* 实现将 border 放到元素内 */
    transform: scale(0.5);
    border-radius: 12px; /* 圆角为 2 倍 */
    top: 0;
    left: 0;
}
  • 优点:兼容性好,无副作用,推荐使用

3、使用 viewport initial-scale

实现原理: 设置整个页面进行缩小,并设置用户不能对页面进行缩放,然后写 1px 页面即可展示 1px 的边框

<meta name="viewport" content="width=device-eidth, initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no" >
  • 优点:可以直接写 1px,兼容性好,适合搭配新项目使用
  • 缺点:会让页面缩小,如果是老项目,需要全部的更改 css 样式

4、通过图片模拟实现 1px

实现原理: 因为 1px 会被渲染为 2px,故我们可以设计一个 2*2px 的图,如果需要设置上边框,则把下面 1px 设置为透明,将上面 1px 设置为我们需要的颜色即可,如下图所示

@media screen and (-webkit-min-device-pixel-ratio: 2){ 
    .border{ 
        border-top: 1px solid transparent;
        border-image: url(border.png) 2 repeat;
    }
}
  • 优点:没有优点
  • 缺点:更换颜色需要更换图片,圆角模糊

5、使用 box-shadown

box-shadow: 0  -1px 1px -1px #999, 
            1px  0  1px -1px #999, 
            0  1px  1px -1px #999, 
            -1px 0  1px -1px #999;
  • 优点:实现简单
  • 缺点:很容易看出来是阴影,而不是边框

6、background-img渐变裁剪

实现原理: 将元素设置为 1px,再通过背景图片渐变裁剪实现

.linear-gradient {
    position: relative;
}
.linear-gradient::after {
    position: absolute;
    content: "";
    top: 0;
    left: 0;
    height: 1px;
    width: 100%;
    /* background: linear-gradient(180deg, #999, #999 50%, transparent 50%); */
    background: linear-gradient(180deg, transparent, transparent 50%, #999 50%);
}
  • 优点:实现简单,无副作用
  • 缺点:不能设置四边,无法设置圆角

7、通过 svg 实现

实现原理: 因为 svg 是矢量图形,它的 1px 对应的物理像素就是 1px

可以搭配 PostCSSpostcss-write-svg 使用:

@svg border_1px { 
  height: 2px; 
  @rect { 
    fill: var(--color, black); 
    width: 100%; 
    height: 50%; 
    } 
  } 
.svg { border: 1px solid transparent; border-image: svg(border_1px param(--color #00b1ff)) 2 2 stretch; }

编译后:

.svg { border: 1px solid transparent; border-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' height='2px'%3E%3Crect fill='%2300b1ff' width='100%25' height='50%25'/%3E%3C/svg%3E") 2 2 stretch; }
  • 优点:实现简单,可以实现圆角,
  • 缺点:需要学习 svg 语法

总结

上述方法实现的 1px 可以打开链接进行查看:service-ph6ff7lw-1257620930.gz.apigw.tencentcs.com/release/all…

实现 1px 的方法很多:

  1. 直接写 0.5px
  2. 通过缩放实现将 2px 转换为 1px
    • 通过 transform:scale(0.5)
    • 通过 viewport 中的 initial-scale=0.5
  3. 通过图片模拟实现 1px
  4. 裁剪
    • 通过 box-shadown
    • 通过渐变百分比裁剪
  5. 使用 svg

推荐通过缩放实现将 2px 转换为 1px 的两种方法

以上即是全文,希望能对你有帮助,如果有错的,还请指正

参考

移动端1px问题解决方法

移动端1px解决方案

抖音--关于移动端适配,你必须要知道的

iPhone屏幕尺寸、分辨率及适配

百度--在html里 pt 和px的区别是?