移动端的兴起才导致前端开发行业的大热,但是也带来了不少新的挑战。

最近我们新做了一个微商城的移动主题,主打移动体验,设计稿借鉴了一些知名的移动商城。但是最后做出来的时候,发现一个很奇怪的地方:我们的成品网站不如其他商城看起来清新。此话怎么说呢,我们当时对比的是基于有赞的一个卖书的店铺,发现他们的商品陈列,中间的分割线看起来更舒服一些,尤其是在 iPhone 上。于是设计找我撕逼,说你这根线给我再细一些,我果断反击说这已经是1像素的最小的线了。设计不服,于是拿出有赞这个店铺跟我做的成品对比,不比不知道,果然,同样是 iPhone6,人家的看起来确实更清新,更细的样子。我当时还以为是视觉的原因,但是都是同样的配色,理论上不应该有如此大的视觉差。于是就有了关于这篇文章的探索。

普及意义和基础

在具体说明技术的实现,我觉得有必要说一下我对这个技术的看法和普及的意义,其实这个技术就基于一个目标——画一根 只有 0.5px 粗的线

自然,在我们的认知中,网页设计的最小单元就是一个像素了,实现一条不到 1px 粗的线听起来有点不可思议。但实际上,随着 LED 显示技术的进步和 GPU 芯片的性能日益增进,我们在常见设备中已经具备这个条件去应用了。

首先,了解一下 Retina 屏幕的发展史

2010年6月8日,苹果公司正式发布了 iPhone 4,这意味着智能移动设备进入了一个全新的时代。对于前端开发而言,iPhone 4 的发布带来的一个新挑战,是其被称为 Retina 的屏幕技术 —— 640x960个像素,塞进了 3.5 英寸的显示屏幕,每英寸的面积里有 327 像素。以此带来的是异常清晰的显示体验,由于正常使用完全看不到像素点的存在,因此又称之为“视网膜屏幕”

题外话:其实我们看高清电视时,如果客厅够大,距离足够远,也是看不到像素点的……所谓的三米变高清,五米抗锯齿。因此在某种使用条件下,分辨率达到一定的标准就足够细腻了,不是说越高越好(因为显卡性能很可能跟不上)

几年过去了,我们现在市面上的手机,基本最低分辨率也达到了 720P 的标准,更多的是 1080P 和 iPhone 系列的超高清屏幕。
那么苹果是如何解决如此细腻的物理像素带来的网页设计的问题呢?

这里引入了两个新概念:一个叫 缩放比例(DPR,Device Pixel Ratio),另一个叫 CSS像素,先看下表

设备名 DPR 物理像素 CSS像素
iPhone 4 2.0 960 x 640 480 x 320
iPhone 5 2.0 1136 x 640 568 x 320
iPhone 6 2.0 1334 x 750 667 x 375
iPhone 6 Plus 3.0 1920 × 1080 736 x 414
Android-720P 3.0 1920 × 1080 640 x 360
Android-1080P 3.0 1920 × 1080 640 x 360
Android-1440P 3.0 2560 x 1440 854 x 480

可以很明确的看到,物理像素的高和宽分别除以缩放比例,就得到了 CSS 像素,下图是更清晰的说明(图源自网络):
半个像素的情怀-CSS像素

更简单地说,DPR 为 2.0 的设备,实际上是用四个像素点来模拟我们网页中的一个点,自然清晰度翻了四倍。

不过这里有一个小小的例外,如果你注意到的话:iPhone 6 Plus 的物理像素除以 DPR 得到的是 640x360,跟表中的并不一致。这里有一个小彩蛋,其实除了 Plus,iPhone 其他均使用 326 的DPI,缩放比例是 2.0。因此同一张图片在 iPhone 的浏览器打开,在 iPhone4/iPhone5/iPhone6 中大小是一样的(这里指实际大小,即拿尺子直接在屏幕量,长度都是 X 厘米),为保持这个特性,基于 iPhone 6 Plus DPI 是 401,理论上苹果的缩放比例应该是 401 / 326 x 2 = 2.46,但是非整数会给开发和设计带来很大的不方便,因此考虑的是向下/向上取整,考虑屏幕尺寸最后取得缩放比例为 3.0,因此进行来适当的缩放,也就是虚拟像素为 2208 × 1242(也就是截图得到的尺寸),再根据上表就得到正确的计算啦。

其实很多安卓设备即便使用 2K 屏幕,但是如果屏幕有一部分用来做虚拟按键,那么实际上可用物理像素是会比屏幕标注的要“矮”一些的

这样一来就知道在超高清屏幕中,实现半个 CSS 像素,就是只显示物理像素的一个像素,是有其实际的场景的。那么现在可以着手怎么使用代码实现了。

基于 viewport

前面有提到,CSS 像素其实是一种虚拟的像素,那么基于 viewport 的技术实现就像是把这个虚拟层用代码定义了出来,参考以下代码:

1
<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no">

这个 meta 标签其实非常常见,我们一般都会加上用来指定在移动浏览器不要进行缩放。但如果你指定了特殊但缩放比例,会出现什么情况呢?如:

1
<meta name="viewport" content="initial-scale=2.0, maximum-scale=2.0, user-scalable=no">

打开浏览器可以看到,平时正常但页面,现在全部元素都比正常要大一倍,那么反过来呢?道理很明白了,就是让浏览器指定这个虚拟视窗是要缩放的,一个像素粗的元素你只显示成半个像素就好,再配合 CSS 像素的相关约束,于是……

实际上,淘宝触屏版<head> 标签中就指定了这个

1
<meta name="viewport" content="initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no">

于是淘宝就做到了单线条的显示非常细腻的效果,但是这里要说以下,因为是整体的缩放,所以手淘的在 iPhone 5 页面宽度用 CSS 描述是 640px 完全跟物理像素对应了起来,高!

这里对开发设计的要求是,所以的素材和 CSS 尺寸都是所以浏览设备的 DPR 倍,这就要求一开始要考虑到不同缩放比例的设备兼容性,同时开发时就必须采用百分比布局,否则不同设备兼容性会很糟糕

基于 background-image

这里主要是用到了渐变背景

我们知道,渐变背景是可以指定渐变颜色从哪里开始变化的,那么考虑以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<style>
.half-pixel {
height: 10px;
background-image: linear-gradient(to bottom, red 50%, transparent 100%);
background-size: 100% 1px;
background-repeat: no-repeat;
}
.hole-pixel {
border-top: 1px solid red;
}
</style>
<div class="half-pixel"></div>
<div class="hole-pixel"></div>

我们这里指定背景的高度为 1px,同时在渐变中指定前半部分为红色,后半部分为透明,那么,由于总共高度就只有一个像素,于是渐变从开始为红色,到半个像素的时候开始变为透明。于是最终显示的效果就是看起来好像只有半个像素的红色。

对比第二条使用边框实现的红线,可以发现确实第一条线确实要更细一些。在如此细腻的屏幕中,肉眼是很难具体看到渐变的具体转换的,因此即便不是非常完美的半个像素,却达到了欺骗眼球的效果

这个方法尤其局限性,首先要生成一个完整的元素来使用(虽然通常使用伪元素保证 HTML 的干净),其次渐变背景的兼容性基本只只有现代浏览器支持,最后是,毕竟还是有渐变的

基于 border-image

我们知道,高级浏览器是支持自定义边框背景的,这边跟前面使用背景图片一样,同样利用来背景图片可以被拉伸的特性,考虑以下代码:

1
2
3
4
5
6
7
8
9
10
<style>
.half-pixel {
border-top: 1px solid transparent;
border-image-source: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGAQMAAADaAn0LAAAABlBMVEUAAAC/v79T5hyIAAAAAXRSTlMAQObYZgAAAA5JREFUCNdj+MPQAoV/ABg8BAlEsqabAAAAAElFTkSuQmCC);
border-image-slice: 2;
border-image-repeat: stretch;
}
</style>
<div class="half-pixel"></div>

其中这里我们的背景图片应该是这样子的:
半个像素的情怀-边框图

同样,由于边框高度设定为一个 CSS像素,而背景图只有一半是可以被看到的,因此在拉伸后看起来就像是只有半个像素。有赞商城就是采用这种方式做的。

实际上这张图就来自有赞商城,我们可以探讨一下为什么这个图片做成 4x4 的可见部分,四周还围绕一个透明像素,其他尺寸不行么?

我个人的分析是:第一点. 做成总宽度为 6 个像素,是考虑到缩放比例不管是 2 还是 3 都可以公用这个代码(公倍数);第二点,四周围绕一圈透明度,是希望可以边框不管是水平方向还是垂直方向都可以使用同样的代码(图片对称);第三点,最巧妙的是,设备想要展示的其实是虚线,但我们还是认为完美地模仿来半个像素,看下图:
半个像素的情怀-边框图2

其实我们看到的是多个红色点排列而成的虚线,但是由于单个点红色部分的面积实际上约一半(4 x 4 / 6 x 6 = 0.45),因此最后排成这样的的时候视觉上刚刚感觉是半个像素!真是美妙的视觉欺骗呀

基于 transform

其实前面我们都是通过背景图的拉伸实现的,那么其实考虑以下 CSS3 本身也有拉伸的功能,考虑以下代码:

1
2
3
4
5
6
7
8
9
10
<style>
.half-pixel:after {
content: '';
display: block;
border-top: 1px solid #f00;
transform: scaleY(0.5);
}
</style>
<div class="half-pixel"></div>

没错,前面代码通过定义一个伪元素并使用 transform: scaleY(0.5) 来拉伸达到让 1px 瘦身的目的,手机京东 就是使用来这个方法的。

实际上这个方法我个人认为是最合适的,因为不用繁琐的背景图方式,理论上可以缩小放大任意的倍数做到各种粗细的视觉效果,付出的代价仅仅是多一个伪元素,何乐而不为呢?

结语

其实经过测试可以知道,半个像素并不只是在 Retina 级别的屏幕上才能看得到,有些浏览器为了兼容 4K 显示器,顺便在普通 1080P 笔记本显示器这种缩放比例为 1.0 的显示器中也会进行显示优化,显示效果是边缘不够清晰,看起来比正常一个像素的要淡一些。不过前面提到,我们可以使用 CSS 媒体查询来知道当前设备的缩放比例,从而写不同的 CSS 规则来进行适配。

加入以下查询活得半个像素体验 @media only screen and (-webkit-min-device-pixel-ratio: 1.5)

因此 Retina 技术不仅给消费者带来新的视觉体验,也给 Web 开发领域带来新的挑战和创意,友好速搭的设计师们,之前的移动设计稿标准是 640px 横向宽度,前端开发也会考虑到 Retina 屏幕中使用 CSS 媒体查询适配高清大图。但随着 iPhone 6 Plus 系列的推出和 1080P 手机的普及,我们开发的时候已经是按照 DPR 3.0 的标准在进行了,设计稿宽度指导尺寸上升到了 960px(虽然前端开发还没有上升到 @3X,不过这在未来是必然会发生的事情)

任何技术的出现都是因为原有技术的局限性,在可预见的未来,超高清显示器的普及是必然的事情,电视机也从之前的 720P 进化到现在基本上都在热卖 4K 屏,因此是不是有那么一个属性,可以直接控制 CSS像素与物理像素的对应关系呢?