muwoo/blogs

px、dp、dpr、ppi、viewport 相关概念

muwoo opened this issue · 3 comments

muwoo commented

最近一直在看一些手淘移动端适配rem之类的技术方案,在研究这些技术方案之前,首先需要掌握一些基本的单位概念,所以在网上也搜了一些资料。虽然全部看下来还是存在一些疑惑的地方,在此做一下记录。

有趣的问题

人物:前端实习生「阿树」与 切图工程师「玉凤」
事件:设计师出设计稿,前端实现页面

玉凤:树,设计稿发给你啦,差那么点像素,就叼死你┏(  ̄へ ̄)=☞
阿树:(>_<)没问题啦~
阿树:哇靠,为啥你给的设计稿是750px宽 ,iPhone 6不是375px宽吗???
玉凤:A pixel is not a pixel is not a pixel, you know ?
阿树:(#‵′),I know Google。。。

为什么会出现以上的情况,难道他们当中一位出错了,摆了这样的乌龙?
事实上,他们都是对的,只是谈的不是同一个「像素」。

1. dp (设备像素)

1.1 概念

设备像素设是物理概念,指的是设备中使用的物理像素,顾名思义,显示屏是由一个个物理像素点组成的,通过控制每个像素点的颜色,使屏幕显示出不同的图像,屏幕从工厂出来那天起,它上面的物理像素点就固定不变了,单位pt。

1.2 相关说明

设备像素其实就是一个固定的尺寸,1pt = 1/72(inch),inch及英寸,而1英寸等于2.54厘米。
不同的设备,其图像基本单位是不同的,比如显示器的点距,可以认为是显示器的物理像素。现在的液晶显示器的点距一般在0.25mm到0.29mm之间。而打印机的墨点,也可以认为是打印机的物理像素,300DPI就是0.085mm,600DPI就是0.042mm。

2. px (CSS pixels)

CSS像素是Web编程的概念,指的是CSS样式代码中使用的逻辑像素。在CSS规范中,长度单位可以分为两类,绝对(absolute)单位以及相对(relative)单位。px是一个相对单位,相对的是设备像素(device pixel)。

比如iPhone 6使用的是Retina视网膜屏幕,使用2px x 2px的 device pixel 代表 1px x 1px 的 css pixel,所以设备像素数为750 x 1334px,而CSS逻辑像素数为375 x 667px。
image

3. dpr(Device Pixel Ratio) 设备像素比

设备像素比表示1个CSS像素(宽度)等于几个物理像素(宽度):

DPR = 物理像素数 / 逻辑像素数

比如dpr=2时,1个CSS像素宽度等于2个物理像素宽度。1css像素由2 * 2个物理像素点组成,见上面对CSS像素的解释。DPR不是单位,而是一个属性名,比如在浏览器中通过window.devicePixelRatio获取屏幕的DPR。

4. ppi (pixel per inch)每英寸的像素数,像素密度。

image

5. viewport 视窗

在桌面浏览器中,viewport就是浏览器窗口的宽度高度。
但移动设备的屏幕比桌面屏幕要小得多,为了要让网页在小尺寸的屏幕上显示正确,就需要对viewport做些处理。需要把viewport分成两部分:visual viewport和layout viewport。George Cummins在Stack Overflow上对这两个概念做了分析。大致意思如下:

把layout viewport想像成为一张大图。现在用一个比较小的框,通过它来看这张大图。在框内看到的部分就是visual viewport。框中的度量单位是CSS像素。可以把这个框靠近一些(放大看局部)或靠远一些(缩小看整体)。也可以改变框的方向,但是大图layout viewport的大小和形状永远不会变。

5.1 visual viewport

visual viewport是页面当前显示在屏幕上的部分。用户可以通过滚动来改变他所看到的页面的部分,或者通过缩放来改变visual viewport的大小。
image

5.2 layout viewport

layout viewport就是页面原来的大小。
image
但是我们用在手机用浏览器打开PC的网页的时候,会看到网页被浏览器自动缩小了,变的太小会导致无法浏览内容。

5.3 idea viewport

布局视口的默认宽度并不是一个理想的宽度,大家从上面的图就可以看出来了,所以苹果公司就引进了理想窗口这个概念:

它是对设备来说最理想的布局视口尺寸。显示在理想视口中的网站拥有最理想的浏览和阅读的宽度,用户刚进入页面时不再需要缩放。

注意:

  • 理想窗口的尺寸是由浏览器厂商决定的,同一设备可以有不同的尺寸
  • 不同设备的相同浏览器理想窗口也会不同,比如手机和平板
    而且会随着设备转向改变
  • 虽然有那么多不同尺寸的理想视口,但是平时开发我们只要告诉浏览器使用它的理想视口(也就是width=device-width或者initial-scale=1.0),就没问题了。

5.4 viewport meta标签

为了不让浏览器自动缩小,引入了viewport元标签。通过这个元标签控制layout viewport的宽度。

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

上面这行代码就是告诉浏览器,布局视口的宽度应该与理想视口的宽度一致。

  1. width:设置 layout viewport 的宽。
  2. initial-scale:初始缩放比例,也即是当页面第一次 load 的时候缩放比例,上面是变成设备的宽度,也就是layout viewport= DP或PT。
  3. maximum-scale:允许用户缩放到的最大比例。
  4. minimum-scale:允许用户缩放到的最小比例。
  5. user-scalable:用户是否可以手动缩放。

image

注意下图片中的红色框,第二张图片内容超了出来,应该是给文字设置了宽度,并且这个宽度大于layout viewport导致了出来。但其实在Android和IOS中会有不一样的表现。在文章《A tale of two viewports》中指出:

  1. 通过 document.documentElement.clientWidth 获取 layout viewport 的宽度
  2. 通过 window.innerWidth 获取 visual viewport 的宽度

移动端 1px 像素问题

一直以来我们实现边框的方法都是设置 border: 1px solid #ccc ,但是在 retina 屏上因为设备像素比(dpr)的不同,边框在移动设备上的表现也不相同: 1px 可能会被渲染成 2px, 3px....也就是说逻辑像素1px会被用不同大小的物理像素来表示。(这里只介绍几种常用的方式,更多可以自行Google)

rem + viewport

按照上面这种表述,首先想到的最快解决这种问题的肯定是根据dpr不同来进行缩放就好了,这种方式也就是常说的 rem + viewpor。关于rem的介绍可以参考这里。核心的实现如下:

<script>  
            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');  
            }  
            var docEl = document.documentElement;  
            var fontsize = 10 * (docEl.clientWidth / 320) + 'px';  
            docEl.style.fontSize = fontsize;   
</script>  

大概意思就是设置网页根字体 font-size 为 37.5 * dpr,这样根据 rem 产出的网页就会被放大 dpr 倍,此时css里面写的还是1px,然后再通过initial-scale= 1 / dpr来对网页进行缩小dpr倍。这样 1px 就会显示成 1 / dprpx。

border-image 实现

这篇文章是腾讯github上的解决方案border-image来实现的。缺点是,你需要制作图片,圆角的时候会出现模糊。

.border-image-1px {
    border-width: 1px 0px;
    -webkit-border-image: url("border.png") 2 0 stretch;
    border-image: url("border.png") 2 0 stretch;
}

border.png也可以用 base64 图片替代。

background-image 渐变实现

除啦用图片,难道纯粹的css就不能实现吗?我的确不想使用图片,感觉制作起来很麻烦,其实百度糯米团首页就是这么做的但是这种方法有个缺点,就是不能实现圆角

.border {
      background-image:linear-gradient(180deg, red, red 50%, transparent 50%),
      linear-gradient(270deg, red, red 50%, transparent 50%),
      linear-gradient(0deg, red, red 50%, transparent 50%),
      linear-gradient(90deg, red, red 50%, transparent 50%);
      background-size: 100% 1px,1px 100% ,100% 1px, 1px 100%;
      background-repeat: no-repeat;
      background-position: top, right top,  bottom, left top;
      padding: 10px;
  }

box-shadow 实现

利用阴影我们也可以实现,那么我们来看看阴影,优点是圆角不是问题,缺点是颜色不好控制。

div{
    -webkit-box-shadow:0 1px 1px -1px rgba(0, 0, 0, 0.5);
}

伪类 + transform 实现

原理是把原先元素的 border 去掉,然后利用 :before 或者 :after 重做 border ,并 transform 的 scale 缩小一半,原先的元素相对定位,新做的 border 绝对定位。
单条border样式设置(其他的类似):

.scale-1px{
  position: relative;
  border:none;
}
.scale-1px:after{
  content: '';
  position: absolute;
  bottom: 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;
}

优点可以实现圆角,京东就是这么实现的。缺点是按钮添加active比较麻烦,对于已经使用伪类的元素(例如clearfix),可能需要多层嵌套。

理想视窗的观点应该不对吧,这个应该是浏览器自己决定的,并不是计算公式出来的。另外,设置initial-scale和设置width的功能接近,都是用来设置layout viewport的宽度的,为什么要先产生visual viewport?我认为visual viewport是浏览器默认为了显示全部内容,所以第一次加载页面的时候将visual viewport设置成layout viewport的宽度,当你缩放页面后再刷新,它会保留上次visual viewport的宽度,和meta里面的值并没有什么关系。

muwoo commented

@lisalin880505 感谢,已纠正

所以开头的问题是一个是逻辑像素一个是物理像素对吗?还有一个疑问是使用安卓模拟器时,我调整不同的DPR后,页面没有任何改变