viewport和1px | 工具人: 这是1px,设计师: 不,这不是

大家都不看的总集篇: 从零开始的大前端筑基之旅(深刻浅出,持续更新~)
以为不错就点个赞吧~javascript

目前多端运行的项目愈来愈多,设计师对于UI的要求也愈来愈高。css

设计师:你这字体大小不太对html

工具人:你看代码,就是16px前端

设计师:确实跟设计稿不一致(拿出iphone 11 max)java

工具人:我通常称这种状况为 有钱人的烦恼(拿出5寸安卓)git

设计师:好吧github

请原谅工具人吧。web

高清屏下,不少设计稿上的参数就不能像web同样直接拿来用了,不论是H5页面仍是RN应用,都须要进行下适配。例如,一样是1px,移动端的1px 就会显得很粗。浏览器

原由

那么为何会产生这个问题呢?主要是跟一个东西有关,DPR(devicePixelRatio) 设备像素比,它是默认缩放为100%的状况下,设备像素和CSS像素的比值。简单地说,这告诉浏览器应该使用多少个屏幕的实际像素来绘制单个 CSS 像素。app

还有一个因素也会引发css中px的变化,那就是用户缩放。例如,当用户把页面放大一倍,那么css中1px所表明的物理像素也会增长一倍;反之把页面缩小一倍,css中1px所表明的物理像素也会减小一倍。

value = window.devicePixelRatio;
复制代码

在早先的移动设备中,屏幕像素密度都比较低,如iphone3,它的分辨率为320x480,在iphone3上,一个css像素确实是等于一个屏幕物理像素的。后来随着技术的发展,移动设备的屏幕像素密度愈来愈高,从iphone4开始,苹果公司便推出了所谓的Retina屏,分辨率提升了一倍,变成640x960,但屏幕尺寸却没变化,这就意味着一样大小的屏幕上,像素却多了一倍,这时,一个css像素是等于两个物理像素的。

解决方案:

用 0.5px 解决

既然1px表明2像素,那用0.5px 不就完美了么

在 WWDC大会上,给出了1px方案,当写 0.5px的时候,就会显示一个物理像素宽度的 border。

retina 屏的浏览器可能不认识0.5px的边框,将会把它解释成0px,没有边框。包括 iOS 7 和 以前版本,OS X Mavericks 及之前版本,还有 Android 设备。

经过 JavaScript 检测浏览器可否处理0.5px的边框,若是能够,给<html>元素添加个class

if (window.devicePixelRatio && devicePixelRatio >= 2) {
  var testElem = document.createElement('div');
  testElem.style.border = '.5px solid transparent';
  document.body.appendChild(testElem);
  if (testElem.offsetHeight == 1)
  {
    document.querySelector('html').classList.add('hairlines');
  }
  document.body.removeChild(testElem);
}
复制代码

而后,极细的边框样式就容易了:

div {
  border: 1px solid #bbb;
}
 
.hairlines div {
  border-width: 0.5px;
}
复制代码

顺便踩了一脚其余的解决方案。原文:

unlike previous solutions involving SVG or GIF or transforms or linear-gradient1, you can have retina hairlines on elements with rounded corners (border-radius).

对于不支持的设备:

This is supported by most desktop browsers, and now by Safari 8 on both iOS and OS X. Chrome on Android is a notable absent, but no doubt it will eventually follow suit. When a browser doesn’t support it, it just displays a regular border. No big deal.

下面介绍下被鄙视的三种方法

使用图片实现

6x6 的 一张图片

能够用 gif,png,或 base64 图片

.border{
    border-width: 1px;
    border-image: url(border.gif) 2 repeat;
}
复制代码

缺点是改边框颜色时要改图片,不是很方便。

用多背景渐变实现的

设置1px的渐变背景,50%有颜色,50%透明,可是没法实现圆角

.border {
    background:
    linear-gradient(180deg, black, black 50%, transparent 50%) top    left  / 100% 1px no-repeat,
    linear-gradient(90deg,  black, black 50%, transparent 50%) top    right / 1px 100% no-repeat,
    linear-gradient(0,      black, black 50%, transparent 50%) bottom right / 100% 1px no-repeat,
    linear-gradient(-90deg, black, black 50%, transparent 50%) bottom left  / 1px 100% no-repeat;
}
复制代码

伪类 + transform

结合 JS 代码,判断是否 Retina 屏

if(window.devicePixelRatio && devicePixelRatio >= 2){
    document.querySelector('ul').className = 'hairlines';
}
复制代码

把原先元素的 border 去掉,而后利用 :before 或者 :after 重作 border ,并 transform 的 scale 缩小一半。原先的元素相对定位,新作的 border 绝对定位

若是是上下边框,scaleY 设置为0.5,左右边框 scaleX 设置为0.5

.hairlines li{
    position: relative;
    border:none;
}
.hairlines li:after{
    content: '';
    position: absolute;
    left: 0;
    background: #000;
    width: 100%;
    height: 1px;
    -webkit-transform: scaleY(0.5);
            transform: scaleY(0.5);
    -webkit-transform-origin: 0 0;
            transform-origin: 0 0;
}
复制代码

能够支持圆角,可是 <td> 用不了。

上面三种方法与最开始的相似,既然1个css像素表明两个物理像素,设备又不认0.5px的写法,那就画1px,而后再想尽各类办法将线宽减小一半。

经过 viewport + rem 实现

var viewport = document.querySelector("meta[name=viewport]");

//下面是根据设备像素设置viewport
if (window.devicePixelRatio == 1) {
  viewport.setAttribute('content', 'width=device-width,initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no');
}
if (window.devicePixelRatio == 2) {
  viewport.setAttribute('content', 'width=device-width,initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no');
}
if (window.devicePixelRatio == 3) {
  viewport.setAttribute('content', 'width=device-width,initial-scale=0.3333333333333333, maximum-scale=0.3333333333333333, minimum-scale=0.3333333333333333, user-scalable=no');
}

// 设置对应viewport的rem基准值
var docEl = document.documentElement;
var fontsize = 16* (docEl.clientWidth / 375) + 'px';
docEl.style.fontSize = fontsize;
复制代码

说到viewport,就不得不详细聊聊它了。

下面内容来自参考文档4

三类viewport

移动设备上的viewport就是设备的屏幕上用来显示咱们的网页的那一块区域,但viewport又不局限于浏览器可视区域的大小。ppk把移动设备上的viewport分为layout viewport 、 visual viewport 和 ideal viewport 三类,

为了能在移动设备上正常显示那些传统的为桌面浏览器设计的网站,移动设备上的浏览器都会把本身默认的viewport设为980px或1024px。但带来的后果就是浏览器会出现横向滚动条。咱们把这个浏览器默认的viewport叫作 layout viewport,经过 document.documentElement.clientWidth 来获取

桌面浏览器中css的1个像素每每都是对应着电脑屏幕的1个物理像素,但css中的像素只是一个抽象的单位,在不一样的设备或不一样的环境中,css中的1px所表明的设备物理像素是不一样的。

如今有不少手机分辨率都很是大,好比768x1024,或者1080x1920这样。但css中的1px并非表明屏幕上的1px,你分辨率越大,css中1px表明的物理像素就会越多,devicePixelRatio的值也越大。由于你分辨率增大了,但屏幕尺寸并无变大多少,必须让css中的1px表明更多的物理像素,才能让1px的东西在屏幕上的大小与那些低分辨率的设备差很少,否则就会由于过小而看不清。

把表明浏览器可视区域的大小viewport叫作 visual viewport。visual viewport的宽度能够经过window.innerWidth 来获取。

最后有一个能完美适配移动设备的viewport。所谓的完美适配指的是,

  • 不须要用户缩放和横向滚动条就能正常的查看网站的全部内容;
  • 显示的文字的大小是合适,好比一段14px大小的文字,不会由于在一个高密度像素的屏幕里显示得过小而没法看清,理想的状况是这段14px的文字不管是在何种密度屏幕,何种分辨率下,显示出来的大小都是差很少的。

这个viewport叫作 ideal viewport,也就是第三个viewport——移动设备的理想viewport。

meta标签

移动设备默认的viewport是layout viewport,咱们须要的是ideal viewport。这时候轮到meta标签出场了。

<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
复制代码

该meta标签的做用是让当前viewport的宽度等于设备的宽度,同时不容许用户手动缩放。

meta viewport 有6个属性:

要获得ideal viewport就必须把默认的layout viewport的宽度设为移动设备的屏幕宽度。由于meta viewport中的width能控制layout viewport的宽度,因此咱们只须要把width设为width-device这个特殊的值就好了。

<meta name="viewport" content="width=device-width">
复制代码

在iphone和ipad上,仅设置content="width=device-width",不管是竖屏仍是横屏,宽度都是竖屏时ideal viewport的宽度。

<meta name="viewport" content="initial-scale=1">
复制代码

这句代码也能达到和前一句代码同样的效果,也能够把当前的的viewport变为 ideal viewport。

缩放是相对于 ideal viewport来进行缩放的,当对ideal viewport进行100%的缩放,也就是缩放值为1的时候,就获得了 ideal viewport。所以,默认的 initial-scale 不是1

仅设置content="initial-scale=1",phone、ipad以及IE 会横竖屏不分,统统以竖屏的ideal viewport宽度为准。推荐二者都写

缩放是相对于ideal viewport来缩放的,缩放值越大,当前viewport的宽度就会越小,反之亦然。例如在iphone中,ideal viewport的宽度是320px,若是咱们设置 initial-scale=2 ,此时viewport的宽度会变为只有160px了。

缩放2倍是在实际宽度不变的状况下,1px变得跟原来的2px的长度同样了.因此放大2倍后原来须要320px才能填满的宽度如今只须要160px就作到了

visual viewport宽度 = ideal viewport宽度  / 当前缩放值
复制代码

好了,如今回到1px问题上。

每一个移动设备浏览器中都有一个理想的宽度,这个理想的宽度是指css中的宽度,跟设备的物理宽度没有关系,在css中,这个宽度就至关于100%的所表明的那个宽度。咱们能够用meta标签把viewport的宽度设为那个理想的宽度,若是不知道这个设备的理想宽度是多少,那么用device-width这个特殊值就好了

如一个分辨率为320x480的手机理想viewport的宽度是320px,而另外一个屏幕尺寸相同但分辨率为640x960的手机的理想viewport宽度也是为320px。可是对于后者,1个css像素会用2个物理像素来显示

所以,对于window.devicePixelRatio = 2 的屏幕,经过设置

viewport.setAttribute('content', 'width=device-width,initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no');
复制代码

将1px变得跟原来0.5px同样,就实现来css中1px 对应屏幕1px 了。

相对的,假如本来320px能够填满屏幕的话,如今须要640px才能填满屏幕了,所以改变viewport时会同步使用rem做为像素单位。由于rem是相对大小,只与根元素font-size的值有关

若是你收获了新知识,或者收获了左侧精美图片,请点个吧~

一个赞顶100阅读量,告诉我你曾来过、看过,并在这里不枉此行吧!!

相关系列: 从零开始的大前端筑基之旅(深刻浅出,持续更新~)

参考文档:

  1. 移动端1px解决方案
  2. CSS retina hairline, the easy way.
  3. Retina屏的移动设备如何实现真正1px的线?
  4. 移动前端开发之viewport,devicePixelRatio的深刻理解
相关文章
相关标签/搜索