Akiq2016/blog

device-adaptation

Opened this issue · 0 comments

移动端适配

  • 基于 CSS 的适配
    • 媒体查询
    • 视口相关的CSS单位
  • 响应式图像
    • HTML 5.2中的 picture 元素
    • HTML 5.2中的 srcset 属性
    • SVG

首先先捋清楚一些概念

分辨率

分辨率是显示器中设备物理像素的度量,通常以宽度x高度的测量值表示。 例如,1920 x 1080的显示器横跨1920像素,向下1080像素。

DPI / PPI

DPI: Dot’s per inch. The number of dots in a printed inch. The more dot’s the higher the quality of the print (more sharpness and detail).

PPI: Pixels per inch. Most commonly used to describe the pixel density of a screen (computer monitor, smartphone, etc…) but can also refer to the pixel density of a digital image.

不同物理设备屏幕,有不同的像素密度。比如对比不同屏幕上,同等一英寸的空间中,可以显示多少像素。会发现像素密度越大,显示的内容越清晰。因此存在单位 DPI (dots-per-inch) 以及 PPI (pixels-per-inch)。

不同设备可以存在不同的DPI值,但太多不同的DPI会导致设计开发的适配变得繁琐。为简化起见,设备屏幕被分组到称为 logic pixel densities 的类别中。比如一个设备的实际DPI为165,他会映射为MDPI。也就是设计者只需要把这种特殊尺寸归类为某一种 logic pixel density 去设计就好了。

LOGICAL DENSITY(DPI) FRIENDLY NAME SCALE RATIO
160 MDPI 1x 4
240 HDPI 1.5x 6
320 XHDPI 2x 8
480 XXHDPI 3x 12

160 DPI 是一个“特殊值”,在安卓中他被称为 baseline density。我们之后讲到的DIP单位,也是基于这个baseline density的。

DIP / DP

对 Android 而言至关重要的东西:DP / DIP(Density-independent pixels)。虚拟像素单位。等价于在MDPI设备上的 1 个物理像素,在或高或低的密度屏幕中等比缩放。也就是说,假如你在MDPI设备上有个dp,那它意味着是1个物理像素。又假如,这个dp是指在XHDPI上,那它意味着4个物理像素(two across and two down)。

为什么我们需要 dp 呢,当我们创建布局时,我们希望UI元素能在不同 DPI 设备上,保持大致相同的物理大小,而不是为每个单独的DPI去指定UI元素的大小。也就是说,dp可以让元素在不同DPI中保持大致相同的物理尺寸。

举例,假设我们有一个200dp高的块,它在MDPI设备下显示为200像素高(MDPI下,1dp == 1Pixel),而在XHDPI设备下,200dp则是400pixels高。而这些都是内置行为:当你定义某样东西应该为200dp,它能在不同设备显示成基本一致的物理尺寸。

正由于dp单位能够在不同设备上表达相同的物理尺寸,因此,我们可以开始用DPs来表示物理对象。比如,人的手指点触大小约50dps宽,这意味着当你设置按钮或其他可点击元素时,大小不应该小于50dps。以此来确保,元素不会因为过小导致普通用户误触。

接着我们看一个帮助我们理解物理像素与DP单位之间关系的转换公式。

 1 pixel    160dpi
-------- = --------- = 1x scale
   1 dp     160dpi

如果是在320DPI屏幕上,也就是XHDPI屏幕,则一个dp实际是4个物理像素(two across and two down)。

 2 pixel   320dpi
-------- = --------- = 2x scale
   1 dp     160dpi

做个测验:2013 Nexus 7's 屏幕的DP是多少?已知信息是:屏幕对角线7inchs;是XHDPI屏幕(320DPI);屏幕分辨率为 1920 x 1200
答案:960 x 600 DP。

那么这个计算的重点是什么呢?DP,即与密度无关的像素,能更好的表达物理像素大小。比如你有个MDPI的平板,屏幕分辨率 1280 x 800,还有个XHDPI的手机,屏幕分辨率 1280 x 720。他们的屏幕分辨率几乎是一样的,但是XHDPI手机的物理尺寸要小的多。

MDPI 1280 x 800 => 1280 x 800 DP
XHDPI 1280 x 720 => 640 x 360 DP

DPR

web开发中,DPR (device pixel ratio) 返回显示器在物理像素中的分辨率与CSS像素中的分辨率之比(双精度浮点值)。简单来说,DPR 告诉浏览器应该使用多少屏幕的物理像素来绘制单个CSS像素。

CSS像素可以类比一下DP相关的概念。DP的存在让设计师能用DP单位来描述物理尺寸。CSS像素同理,1个CSS像素在不同设备下,实际占有的物理像素不同。这个不同,我们可以通过 window.devicePixelRatio 来知晓。

DPR使用场景,比如canvas画布中的内容,显示模糊,可以配合使用 window.devicePixelRatio 确定应添加多少额外像素密度来获得更清晰的图像。主要的使用场景还在于网页内容中图片的适配。在不同DPR的设备下,开发者不希望强迫低DPR设备用户下载高分辨率图片,也不想高DPR设备用户下载一个低分辨率图片。为了提高用户体验,开发者可以使用CSS媒体查询,以满足不同设备需求。

#element { background-image: url('lores.png'); }

@media only screen and (min-device-pixel-ratio: 2) {
    #element { background-image: url('hires.png'); }
}

@media only screen and (min-device-pixel-ratio: 3) {
    #element { background-image: url('superhires.png'); }
}

随着越来越多的设备类型出现,为它们提供足够的位图资源变得更加棘手。在CSS3中,媒体查询是目前兼容性最好的方式(含IE9以上、主流浏览器),在HTML5中,picture元素允许您为不同的媒体查询使用不同的源,但是IE11不兼容。

如果开发者需要给用户呈现清晰的图像,图标,线条艺术,非照片的设计元素,应该使用SVG,它可以精确地扩展到所有分辨率,且兼容性很好(含IE9以上的主流浏览器)。

viewport

现在有很多手机分辨率都非常大,比如 768x1024,1080x1920 等,那在这样的手机上显示为桌面浏览器设计的网站似乎是没问题的。但是,如同前面讲到的 DIP / DPR 的概念,css中的 1px 并不是代表屏幕上的1px,屏幕密度越大,css中1px代表的物理像素就会越多,DPR 的值也越大,这样才能保证1px的东西在屏幕上的大小与那些低分辨率的设备差不多。

因此在移动设备上,浏览器的可视区域,远小于这些为桌面浏览器设计的网站所需要的视口大小。所以一般来讲,移动设备上的 viewport 都是要大于浏览器可视区域的。毕竟以浏览器的可视区域作为 viewport 的话,那些不是为移动设备设计的网站,在移动设备上显示时,会因为移动设备的viewport太窄,而挤作一团,甚至布局会乱掉。

为了防止某些网站因为 viewport 太窄而显示错乱,所以这些浏览器就决定默认情况下把 viewport 设为一个较宽的值,比如980px,这样的话即使是那些为桌面设计的网站也能在移动浏览器上正常显示了。这个浏览器默认的 viewport 被称为 layout viewport。这个 layout viewport 的宽度可以通过 document.documentElement.clientWidth 来获取。

然而,layout viewport 的宽度是大于浏览器可视区域的宽度的,所以我们还需要一个 viewport 来代表 浏览器可视区域的大小,我们叫他 visual viewport。visual viewport 的宽度可以通过 window.innerWidth 来获取。

现在大部分网站都会为移动端进行设计,所以必须还要有一个能完美适配移动设备的 viewport。所谓的完美适配指的是,不需要用户缩放和横向滚动条就能正常的查看网站的所有内容:ideal viewport 。ideal viewport 并没有一个固定的尺寸,不同的设备拥有有不同的 ideal viewport。

  • layout viewport 表示的是浏览器默认的viewport。
  • visual viewport 表示浏览器可视区域的大小。
  • ideal viewport 的宽度等于移动设备的屏幕宽度,也就是宽度为100%的效果

我们的html一般带有以下一条定义了viewport的元数据。

<meta name="viewport" content="width=device-width, initial-scale=1.0">

先看一下两个content值的定义:

  • width A positive integer number, or the text device-width: Defines the pixel width of the viewport that you want the web site to be rendered at.
  • initial-scale A positive number between 0.0 and 10.0: Defines the ratio between the device width (device-width in portrait mode or device-height in landscape mode) and the viewport size.

width 不设置时,则使用浏览器自己默认的layout viewport值,设置为 device-width 时,意味着使用ideal viewport。而看过定义后不难发现 initial-scale=1.0 也是把当前的渲染视口变成 ideal viewport。因为一些历史兼容问题,这两个值需要都写上。

CSS适配

媒体查询

https://www.w3.org/TR/css3-mediaqueries/

https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries

  1. 使用 @media@import
  2. 使用 <link> 元素的 media 属性指定外部样式表的目标媒体:

媒体查询由媒体类型(media type)和零至多个表达式(expressions),组成一个true或false的逻辑表达式,以此来检查特定的媒体功能(media features)的满足条件。如果媒体查询的媒体类型与运行中的设备类型匹配,并且媒体查询中的所有表达式都为真,则媒体查询为true。在CSS中使用媒体查询:

/* 
    The complete list of media types in HTML4 is:
    ‘aural’, ‘braille’, ‘handheld’, ‘print’, ‘projection’, ‘screen’, ‘tty’, ‘tv’.
    CSS2 defines the same list, deprecates ‘aural’ and adds ‘embossed’ and ‘speech’.
*/
@media <media-query-list> {
  <stylesheet>
}

/* 
    ‘all’ is used to indicate that the style sheet applies to all media types.
    If the media type is not explicitly given it is ‘all’.
    The most commonly used feature is to distinguish between ‘screen’ and ‘print’.
 */
@media { }
@media all { }

/* 
    Several media queries can be combined in a media query list.
    In the media queries syntax,
    the comma: ‘,’ expresses a logical OR,
 */
@media screen, print { }

/* 
    while the ‘and’ keyword expresses a logical AND.
 */
@media screen and (color), print and (color) { }

/* 
    the keyword ‘not’ at the beginning of the media query negates the result.(does not support in all user agent)
    Note that ‘not’ only works for the current media query, if you comma separate, it only affects the media query it is within.
    Also note that ’not‘ reverses the logic for the entire media query as a whole, not individual parts of it.
    That's said, not x and y = not (x and y) ≠ (not x) and y
    *However, if you use ‘not’ or ‘only’, you must also specify a media type.
 */
@media not all and (max-width: 600px) {
  html { background: red; }
}

/* 
    The keyword ‘only’ can also be used to hide style sheets from older user agents.
    User agents must process media queries starting with ‘only’ as if the ‘only’ keyword was not present.
    So it has no effect on modern browsers.
    *However, if you use ‘not’ or ‘only’, you must also specify a media type.
 */

视口相关CSS单位

https://www.w3.org/TR/css-values-3/#viewport-relative-lengths

https://caniuse.com/#search=vw

与视口(viewport)相关的CSS单位:vwvhvminvmax 。假定任何滚动条都不存在时,视口百分比长度是相对于 初始包含块 的大小。当 初始包含块 的高度或宽度改变时,视口百分比长度相应地缩放。

  • vw
    Equal to 1% of the width of the initial containing block.
  • vh
    Equal to 1% of the height of the initial containing block.
  • vmin
    Equal to the smaller of vw or vh.
  • vmax
    Equal to the larger of vw or vh.

响应式图像

https://www.w3.org/TR/html52/semantics-embedded-content.html

https://dvcs.w3.org/hg/html-proposals/raw-file/9443de7ff65f/responsive-images/responsive-images.html

当前的img元素仅允许图像有单个资源。但是,在许多情况下,开发者希望设置多个图像资源,根据不同场景需求进行选择。比如:

  1. 用户的物理屏幕大小(physical screen size)可能彼此不同。
  2. 用户的屏幕像素密度(physical pixels density)可能彼此不同。
  3. 用户的缩放级别(zoom level)可能彼此不同。
    用户可以放大特定图像以获得更详细的外观。
    用户的缩放级别和屏幕物理像素密度都能影响“每个CSS像素的物理像素数”,即DPR。
  4. 用户的屏幕方向可能彼此不同:纵向(portrait)、横向(landscape)。
  5. 用户的网络速度,网络延迟和带宽成本可能彼此不同。
  6. 开发者希望根据视口宽度(the width of the viewport)来显示相同图像内容,不同大小的资源。这通常被称为基于 viewport-based 的选择。
  7. 开发者对于不同尺寸设备下,图片的布局模式可能不同。
    网页可能包含以列布局的图像,其中一列用于物理尺寸较小的屏幕,两列用于具有中等物理尺寸的屏幕,三列用于具有较大物理尺寸的屏幕,每种情况下图像的渲染大小各不相同。在这种情况下,尽管屏幕较小,但与双列布局相比,单列布局中图像的渲染大小可能更大。
  8. 开发者可能希望根据图像的渲染大小,显示不同的图像内容。这通常被称为 art direction (艺术指导)。
    当在具有大物理尺寸的屏幕上观看网页时,开发者可能希望图像上呈现更多的内容。当在具有小物理尺寸的屏幕上查看相同的网页时,开发者可能希望仅显示图像的关键部分。
  9. 开发者可能希望根据用户代理所支持的图像格式,显示相同的图像内容,不同的图像格式。这通常被称为基于 image format-based selection

上述情况并非相互排斥。另外,虽然可以使用脚本来解决这些问题,但这样做会引入一些其他问题:

  1. 一些用户代理在脚本运行前,下载了HTML中指定的图像,以便网页更快地完成加载。如果脚本更改了要下载的图像,则相当于做了两次下载资源动作,这可能会导致页面加载性能下降。
  2. 可以避免在HTML中指定任何图像,仅在脚本中进行图像实例化,避免问题1中的多次下载的问题。但是对于禁用脚本的用户来说,则体验不好,因为他根本不会下载任何图像。

在线例子 todo

https://www.youtube.com/watch?v=zhszwkcay2A
https://www.youtube.com/watch?v=g48Rxf22hrE
https://www.captechconsulting.com/blogs/understanding-density-independence-in-android
https://microscope-microscope.org/microscope-info/image-resolution/