移动端适配·

前言:最近公司官网项目需要做移动端适配,所以借此机会对移动端的适配方案进行调研。本期分享内容主要包括一下几点:

  1. 我们为什么要做适配?
    • 这里涉及适配的前置概念:例如像素、dpr等
  2. 如何适配?
    • 都有哪些适配方案?
    • 它们是如何工作的?
  3. 对不同的项目需求,我们做技术评审时如何选择合适的方案?

1、前置知识·

1.1 尺寸、像素·

英寸:·
  • 描述屏幕尺寸的物理单位,且是屏幕对角线的长度
  • 英寸和厘米的换算单位是:1英寸 = 2.54厘米
像素:·
  • 具有特定的位置和颜色的小方块,而图片、电子屏幕是由无数个像素拼接而成的
  • 可以说,作为图片或电子屏幕的最小组成单位

1.2 分辨率·

1.2.1 屏幕分辨率:·
  • 指一个屏幕具体由多少个像素点组成,比如:iPhone XS MaxiPhone SE的分辨率分别为2688 x 12421136 x 640。这表示手机分别在垂直和水平上所具有的像素点数。
  • 分辨率高不代表屏幕就清晰,屏幕的清晰程度还与尺寸有关。这是很好理解的嘛~必须以用一尺寸在有比较的意义
1.2.2 图像分辨率:·
  • 是指图片含有的像素数,比如一张图片的分辨率为800 x 400。这表示图片分别在垂直和水平上所具有的像素点数为800400
  • 同一尺寸的图片,分辨率越高,图片越清晰
1.2.3 PPI·
  • PPI(Pixel Per Inch):每英寸包括的像素数,描述屏幕的清晰度以及一张图片的质量

  • PPI越高,屏幕、图片越清晰:

    iPhone XS MaxiPhone SEPPI分别为458326,前者屏幕更清晰

1.2.4 DPI·
  • DPI(Dot Per Inch):即每英寸包括的点数。
  • 这里的点是一个抽象的单位,它可以是屏幕像素点、图片像素点、打印机的墨点
  • 常用于描述打印机:
    • 当打印时,打印机可能不会规则的将这些点打印出来,而是使用一个个打印点来呈现这张图像,这些打印点之间会有一定的空隙,这就是DPI所描述的:打印点的密度
    • 打印机的DPI越高,打印图像的精细程度就越高,会消耗更多的墨点和时间

1.3 像素·

1.3.1 物理像素·
  • 其实就是设备上真实的物理单元,上面我们描述的像素都是物理像素
    • 显示屏是由一个个物理像素点组成的,通过控制每个像素点的颜色使屏幕显示出不同的图像
  • 从屏幕出厂的那天起,它的物理像素点就确定了,
1.3.2 设备独立像素·

智能机初期,分辨率低:

  • 比如iPhone3,分辨率是320x480,看到的内容都是“真实”的,1px = 1pxiPhone3屏幕上最多只能展示一个320px宽的元素
  • 但仔细观察,会在iPhone3的屏幕上发现有很多细小的发光点,也即是“颗粒感”。这一颗一颗的发光点就是真实的像素物理单元,也就是物理像素

后期高分辨率手机出现:

iPhone3和iPhone4对比
  • iPhone4分辨率是640*960,正好是iPhone3的两倍。然而尺寸是一样的
  • iPhone4是怎么做到把两倍的分辨率使用在同一物理尺寸的屏幕上的呢?
    • 按理说,因为它的分辨率提高了一倍:白色手机上相同大小的图片和文字,在黑色手机上会被缩放一倍。但事实并不是
    • 不管分辨率多高,他们所展示的界面比例都是基本类似的

原因:

  • iPhone4使用的视网膜屏幕中,把2x2个像素当1个像素使用,这样让屏幕看起来更精致,但是元素的大小却不会改变。
  • 比如列表的宽度为300个像素:
  • 在一条水平线上,白色手机会用300个物理像素去渲染它,而黑色手机实际上会用600个物理像素去渲染它

因此:设备独立像素的概念出来了

  • 我们需要用一种单位来同时告诉不同分辨率的手机,它们在界面上显示元素的大小是多少。设备独立像素(Device Independent Pixels)简称DIPDP就是做这件事的
  • 列表的宽度为300个像素,实际上可以说:列表的宽度为300个设备独立像素

chrome的devtools:

  • 模拟各个手机型号的显示情况,每种型号上面会显示一个尺寸;
  • 比如iPhone X显示的尺寸是375x812,实际iPhone X的分辨率会比这高很多,这里显示的就是设备独立像素。
1.3.3 设备像素比(dpr)·
  • 即物理像素和设备独立像素的比值,获取方法:

    1. window.devicePixelRatio的api

    2. 通过媒体查询min-device-pixel-ratio,区分dpr

      @media (-webkit-min-device-pixel-ratio: 2),(min-device-pixel-ratio: 2){ }
  • 从苹果提出视网膜屏幕开始,才出现设备像素比这个概念。由于Android屏幕尺寸、分辨率高低跨度非常大,所以,为保证各种设备的显示效果,Android按照设备的像素密度将设备分成了几个区间:

    img
    • 所有的Android设备不一定严格按照上面的分辨率,每个类型可能对应几种不同分辨率,所以各种Android设备仍然不能做到在展示上完全相等
1.3.4 像素 px·

在CSS规范中,长度单位可以分为两类,绝对单位、相对单位。

  • px是一个相对单位,相对的是设备像素(device pixel),满足:
    1. 同一设备,每1个CSS像素代表的物理像素是可以变化的(即CSS像素的第一方面的相对性)
    2. 不同设备间,每1个CSS像素所代表的物理像素是可以变化的(即CSS像素的第二方面的相对性)
  • css像素是一种虚拟像素,可以理解为“直觉”像素,单位是px
  • 浏览器内的一切长度都是以CSS像素为单位的,所以CSS和JS使用的抽象单位

在移动端浏览器中以及某些桌面浏览器中,window对象有一个devicePixelRatio(设备像素比)属性,而它的官方的定义为:设备物理像素和设备独立像素的比例: dpr = dp / dips

  • 设备独立像素,dips = css像素 / scale(缩放比例),在没有缩放的情况下,1个css像素等同于一个设备独立像素
  • CSS像素在视觉上可以改变大小:比如缩放浏览器页面,就是改变的CSS像素:
    • 当放大一倍,那么一个CSS像素在横向、纵向上会覆盖两个设备独立像素
    • 例如宽度100px,当页面放大一倍,它会在横向上由原本占据100个设备独立像素,变成占据200个设备独立像素;如果缩小,则恰好相反,只能占据50个设备独立像素
  • 无论CSS像素是缩小还是放大,它是像素数目是不变的,比如100px,无论缩放,它依然是100px,只不过它占据的设备独立像素发生了变化(体积发生了变化,视觉大小上发生了变化而已)

1.4 视口viewport:·

表示的是用户网页的可视范围(指页面能够被浏览的范围)

1.4.1 布局视口·
img

为什么会有布局视口?

  • 移动设备上的浏览器认为自己必须能让所有的网站都正常显示,即使是那些不是为移动设备设计的网站。但如果以浏览器的可视区域作为viewport的话,那些为桌面浏览器设计的网站放到移动设备上显示时,必然会因为移动设备的viewport太窄,而挤作一团,甚至布局什么的都会乱掉
  • 如果把移动设备上浏览器的可视区域设为viewport的话,某些网站就会因为viewport太窄而显示错乱
  • 所以这些浏览器就决定**默认情况下把viewport设为一个较宽的值,比如980px,**这样的话即使是那些为桌面设计的网站也能在移动浏览器上正常显示了
  • ppk把这个浏览器默认的viewport叫做layout viewport(布局视口)
  • 获取:document.documentElement.clientWidth / clientHeight

布局视口是网页布局的基准窗口:

  • PC上,布局视口就等于当前浏览器的窗口大小(不包括bordersmargins、滚动条)

  • 在移动端,布局视口被赋予一个默认值,大部分为980px,这保证PC的网页可以在手机浏览器上呈现,但是非常小,用户可以手动对网页进行放大

1.4.2 视觉视口:·

layout viewport想象为一个不会改变大小或形状的大图像。现在想象你有一个较小的框架,通过它你可以看到这个大图片。小框架由不透明的材料构成,通过它你只能看到大图像的一部分,这一部分就叫做 visual viewport .你可以拿着这个框架和图像拉开一定的距离(zoom out)看到整个图像,你也可以让自己离图像更近(zoom in)看到其中的一部分。你也可以旋转这个框架的方向,但是这个图片(layout viewport)的大小和形状永远不会改变。

img

布局视口宽度是大于浏览器可视区域的宽度的,所以还需要一个viewport来代表 浏览器可视区域的大小视觉视口

  • 用户可以缩放来查看网站的内容。
    • 缩小网站,看到的网站区域将变大,此时视觉视口也变大了,反之亦然
    • 不管用户如何缩放,都不会影响到布局视口的宽度
  • 调用window.innerWidth / innerHeight来获取
1.4.3 理想视口·

为什么会有它?

  • 越来越多的网站都会为移动设备单独设计,所以必须还要有一个能完美适配移动设备的viewport

  • 理想视口并没有固定的尺寸,不同的设备理想视口不同:

    只要在css中把某一元素的宽度设为ideal viewport的宽度(单位用px),那么这个元素的宽度就是设备屏幕的宽度了,也就是宽度为100%的效果

  • deal viewport 的意义在于,无论在何种分辨率的屏幕下,那些针对ideal viewport 而设计的网站,不需要用户手动缩放,也不需要出现横向滚动条,都可以完美的呈现给用户

  • window.screen.width / height来获取

完美适配指的是:

  1. 首先不需要用户缩放和横向滚动条就能正常的查看网站的所有内容;
  2. 显示的文字的大小是合适,比如一段14px大小的文字,不会因为在一个高密度像素的屏幕里显示得太小而无法看清,理想的情况是这段14px的文字无论是在何种密度屏幕,何种分辨率下,显示出来的大小都是差不多的。当然,不只是文字,其他元素像图片什么的也是这个道理。
1.4.4 Meta viewport·

元素表示那些不能由其它HTML元相关元素之一表示的任何元数据信息,它可以告诉浏览器如何解析页面

借助<meta>元素的viewport来设置视口、缩放等,从而让移动端得到更好的展示效果

<meta name="viewport" 
content="width=device-width; initial-scale=1; maximum-scale=1; minimum-scale=1; user-scalable=no;">
  • 让当前viewport的宽度等于设备的宽度,同时不允许用户手动缩放。如果你不这样的设定的话,那就会使用那个比屏幕宽的默认viewport,也就是说会出现横向滚动条

  • 缩放:

    • 缩放是相对于理想视口来缩放的,缩放值越大,当前viewport的宽度就会越小

      例如在iphone中,理想视口的宽度是320px,如果设置 initial-scale=2 ,此时viewport的宽度会变为只有160px了

      • 原来1px的东西变成2px了,但是1px变为2px并不是把原来的320px变为640px了,而是在实际宽度不变的情况下,1px变得跟原来的2px的长度一样了,所以放大2倍后原来需要320px才能填满的宽度现在只需要160px就做到了
    • 另一点,initial-scale:在iphone和ipad上,无论你给viewport设的宽的是多少,如果没有指定默认的缩放值,则iphone和ipad会自动计算这个缩放值,以达到当前页面不会出现横向滚动条(或者说viewport的宽度就是屏幕的宽度)的目的。

  • 这样写也能达到同样达到了理想视口的效果:为什么?

    <meta name="viewport" content="initial-scale=1"> // IE 会横竖屏不分
    • 因为缩放是相对于 理想视口来缩放的,当缩放值为1,不就是理想视口吗
  • **但如果 width 和 initial-scale=1 同时出现,并且还出现了冲突呢?**比如:

    <meta name="viewport" content="width=400, initial-scale=1">
    //iphone、ipad会横竖屏不分
    • width=400:把当前viewport的宽度设为400px
    • initial-scale=1:把当前viewport的宽度设为理想视口的宽度,= 理想视口宽度 / 视觉视口宽度
    • **当遇到这种情况时,浏览器会取它们两个中较大的那个值:
    • width=400,理想视口宽度为320时,取的是400;理想视口宽度为480时,取的是480
Value 可能值 描述
width 正整数或device-width pixels(像素)为单位, 定义布局视口的宽度。
height 正整数或device-height pixels(像素)为单位, 定义布局视口的高度。
initial-scale 0.0 - 10.0 定义页面初始缩放比率=理想视口宽度 / 视觉视口宽度
minimum-scale 0.0 - 10.0 定义缩放的最小值;必须小于或等于maximum-scale的值。
maximum-scale 0.0 - 10.0 定义缩放的最大值;必须大于或等于minimum-scale的值。
user-scalable 一个布尔值(yes或者no 如果设置为 no,用户将不能放大或缩小网页。默认值为 yes。

1.5 获取浏览器大小·

img
  • window.innerHeight:浏览器视觉视口高度(包括垂直滚动条)。
  • window.outerHeight:浏览器窗口外部的高度。表示整个浏览器窗口的高度,包括侧边栏、窗口镶边和调正窗口大小的边框。
  • window.screen.Height:获取获屏幕取理想视口高度,这个数值是固定的,`设备的分辨率/设备像素比
  • window.screen.availHeight:浏览器窗口可用的高度
  • document.documentElement.clientHeight:获取浏览器布局视口高度,包括内边距,但不包括垂直滚动条、边框和外边距
  • document.documentElement.offsetHeight:包括内边距、滚动条、边框和外边距。
  • document.documentElement.scrollHeight:在不使用滚动条的情况下适合视口中的所有内容所需的最小宽度。测量方式与clientHeight相同:它包含元素的内边距,但不包括边框,外边距或垂直滚动条

1.6 rem、em、vm·

  • px:像素,固定

  • em:

    • 相对于自己的字体大小;有继承的属性
    • 如果自己没有,相对于父元素字体大小
  • rem:相对于根元素的字体font-size大小,任意浏览器默认字体都是16px

    • html根元素的获取方式 document.getElementsByTagName('html')[0]
    • Htmlfont-size:屏幕宽度 / 划分的份数
    • 页面元素的rem值:页面元素值(px)/(屏幕宽度 / 划分的份数)
  • 百分比:

    • 用在字体中,相对于父元素字体大小
    • 用在尺寸中,相对于父元素的width和height
  • vm、vh

    • 1vw等于视觉视口的1%1vh 为视觉视口高度的1%
    • 如果视觉视口为375px,那么1vw = 3.75px,这时UI给定一个元素的宽为75px(设备独立像素),我们只需要将它设置为75 / 3.75 = 20vw

2、Why适配?·

其实,到这里已经有了第一种适配方案:meta标签的做法。但我认为这种方案只适合纯移动端web页面,并不合适与PC端的页面:尤其是pc端与web端不完全相同的布局:

  • 我们可以在官网项目中验证:

但尽管我们可以使用设备独立像素来保证各个设备在不同手机上显示的效果类似,但这并不能保证它们显示完全一致,我们需要一种方案来让设计稿得到更完美的适配

常见的布局方案·

固定布局:·
  • 做法1:以像素作为页面的基本单位,不管设备屏幕及浏览器宽度,只设计一套尺寸;
  • 做法2:同样以像素作为页面单位,参考主流设备尺寸,设计几套不同宽度的布局。通过识别的屏幕尺寸或浏览器宽度,选择最合适的那套宽度布局;
弹性布局:·
  • 以百分比作为页面的基本单位,可以适应一定范围内所有尺寸的设备屏幕及浏览器宽度,并能完美利用有效空间展现最佳效果;
混合布局:·
  • 同弹性布局类似,可以适应一定范围内所有尺寸的设备屏幕及浏览器宽度,并能完美利用有效空间展现最佳效果;只是混合像素、和百分比两种单位作为页面单位
布局响应:·
  • 对页面进行响应式的设计实现,需要对相同内容进行不同宽度的布局设计

  • 有两种方式:

    • pc优先(从pc端开始向下设计);
    • 移动优先(从移动端向上设计);

    无论基于那种模式的设计,要兼容所有设备,布局响应时不可避免地需要对模块布局做一些变化(发生布局改变的临界点称之为断点)

3、How适配-pc?·

3.1 viewport方案·

对设计稿还原时不关注屏幕尺寸的差异,而是直接按设计稿的标注来开发。

  • 比如设计稿里标注的文字字号是30px,CSS里就设置文字字号30px。

  • 页面开发好后,在HTML的head标签里加入

    • 设计稿宽度是750px,设计稿上一个标题字号标注的是32px 、margin是20px,我们以标注的大小来写CSS

    • 之后需要通过JS计算获取屏幕的宽度(假设需要适配逻辑像素宽度是428px的屏幕),在HTML的head里添加 即可(428/750 = 0.57)。

      意思是:设置布局视口(layout viewport)的宽度为750px(此时页面一般会超出屏幕),再缩放页面(initial-scale)使其恰好撑满屏幕

    <script>
    const WIDTH = 750
    const mobileAdapter = () => {
    let scale = screen.width/WIDTH
    let content = `width=${WIDTH}, initial-scale=${scale}, maximum-scale=${scale}, minimum-scale=${scale}`
    let meta = document.querySelector('meta[name=viewport]')
    if(!meta) {
    meta = document.createElement('meta')
    meta.setAttribute('name', 'viewport')
    document.head.appendChild(meta)
    }
    meta.setAttribute('content', content)
    }
    mobileAdapter();
    window.onorientationchange = mobileAdapter;
    </script>
  • 优点: 开发流程很简单,工程师只需根据设计稿标注还原页面,不需要额外计算。适配范围广。

  • 缺点: 页面整体放大缩小,对于不想缩放的元素无法控制。比如边框在大屏手机下显得很粗,在小屏手机下显得很细

    更需要一种既能整体缩放,又能个性化控制某些元素不缩放的方案:动态REM方案

3.2 动态rem方案·

只要调整html标签的font-size,就能让所有使用rem单位的元素跟随着发生变化,而使用px单位的元素不受影响。问题的关键在于如何根据屏幕尺寸跳转html标签的font-size。

  • 基于上一种方案的兼容问题,我们引入了rem+媒体查询结合来做:设置html的fontsize=100;将页面分为100份,然后媒体查询区分移动与PC,移动端单位采用rem做单位即可;

  • 此外,为了简化rem值的计算,只需要将设计稿的px移动小数点即可;

  • 由于插件不能限制生效设备,因此,没有采用插件的方式;

    rem:相对于根元素的字体font-size大小,任意浏览器默认字体都是16px

    • html根元素的获取方式 document.getElementsByTagName('html')[0]
    • Htmlfont-size:屏幕宽度 / 划分的份数
    • 页面元素的rem值:页面元素值(px)/(屏幕宽度 / 划分的份数)
    <meta name="viewport" content="width=device-width, 
    initial-scale=1, maximum-scale=1, minimum-scale=1">
    <script>
    const WIDTH = 750 //如果是尺寸的设计稿在这里修改
    const setView = () => {
    //设置html标签的fontSize
    document.documentElement.style.fontSize = (100*screen.width/WIDTH) + 'px'
    }
    window.onorientationchange = setView
    setView()
    </script>

    <style>
    div {
    /* 需要随屏幕等比缩放,使用rem单位,比如设计稿中标注的32px这里写成0.32rem */
    width: 3.75rem;
    border: 1px solid #ccc; /*不需要缩放的部分用px*/
    }
    </style>

3.3 vw方案·

不需要JavaScript即可实现动态Rem方案类似的效果

  • vh、vw方案即将视觉视口宽度 window.innerWidth和视觉视口高度 window.innerHeight 等分为 100 份。

    • 1vw等于视觉视口的1%1vh 为视觉视口高度的1%
    • vmin : vwvh 中的较小值、较大值
    • 如果视觉视口为375px,那么1vw = 3.75px,这时UI给定一个元素的宽为75px(设备独立像素),只需要将它设置为75 / 3.75 = 20vw
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
    .button {
    width: 16vw; /* 100vw*120/750 */
    font-size: 3.73vw; /* 100vw*28/750 */
    line-height: 6.4vw; /* 100vw*48/750 */
    border: 1px solid #000; /*不需要缩放的部分用px*/
    text-align: center;
    }
    </style>
  • 虽然不用写JS做适配,但标注尺寸换算为vw又麻烦又不直观。可以在CSS里使用calc来换算换,只不过需要注意新语法的兼容性

    :root {
    --ratio: calc(100vw/750);
    }
    .button {
    font-size: calc(100vw*28/750); /* 可以直接用calc */
    line-height: calc(100vw*48/750);

    width: calc(120*var(--ratio)); /* 也可以用calc配合var使用,IE不支持var */
    border: 1px solid #000; /*不需要缩放的部分用px*/
    text-align: center;
    }

    正式的项目里也可以用SCSS,把换算交给预处理器

    @function px2vw($px) {
    @return $px * 100vw / 750;
    }
    .button {
    width: px2vw(120);
    font-size: px2vw(28);
    line-height: px2vw(48);
    border: 1px solid #000;
    text-align: center;
    }
  • 缺陷:

    • px转换成vw不一定能完全整除,因此有一定的像素差。
    • 比如当容器使用vwmargin采用px时,很容易造成整体宽度超过100vw,从而影响布局效果。当然我们也是可以避免的,例如使用padding代替margin,结合calc()函数使用等等…

3.4 媒体查询·

  • 使用媒体查询的方式:

    1. 可以用 link 与 style 中定义媒体查询
    2. 也可以用 @import url(screen.css) screen 形式媒体使用的样式
  • 可以用逗号分隔同时支持多个媒体设备,未指定媒体设备时等同于all

3.4.1 style·
  • 常用的:一般并不会写media=“screen”,其实是默认了media=“all”

    <style media="screen">  //可以指定别的设备
    h1 {font-size: 3em;}
    </style>
  • link 标签中通过 media 属性设置样式使用的媒体设备

  • 不推荐使用,缺点:

    • 要写很多link标签,不太方便
    • 更多的是针对页面中某块区域,来单独写个样式控制其响应
    <link rel="stylesheet" href="common.css"> //没有指定媒体所以全局应用
    <link rel="stylesheet" href="screen.css" media="screen"> //在屏幕设备
    <link rel="stylesheet" href="print.css" media="print"> //在打印设备
    /* 多设备支持,逗号分隔 */
    <link rel="stylesheet" href="screen.css" media="screen,print">
3.4.3 @import·
  • 相比于link,更多的是针对页面中某块区域,来单独写个样式控制其响应

  • 此时就可以在文件中引入一个样式文件,在这个文件中**再引入其他媒体的样式文件。**用@import ,引入指定设备的样式规则

    <link rel="stylesheet" href="style.css">  //这样就可以只有一个link了

    style.css

    @import url(screen.css) screen;
    @import url(print.css) print;
    @import url(screen.css) screen,print; /* 多设备支持,逗号分隔 */
3.4.4 @media·
  • @media 可以更细的控制,即在一个样式表中为多个媒体设备定义样式

    @media mediatype 关键字 (media feature)  {...}
    @media screen,print {...} /* 多设备支持,逗号分隔 */

    media (min-width: 500px), (max-width: 700px) {....}
    media only screen and (min-width: 500px) and (max-width: 700px) {....}
    • mediatype:常用媒体类型还是screen

      • all:所有媒体类型
      • screen:电脑屏幕,平板电脑,智能手机等
      • print:打印设备 speech:屏幕阅读器等发声设备
    • and|or|not|only:关键字,实际使用3选1

      • not:必须将not写在查询的最前面,表示不应用样式,即所有条件都满足不应用样式
      • only:用来排除不支持媒体查询的浏览器
        • only 和 not 一样只能出现在媒体查询的开始
        • 对支持媒体查询的设备,正常调用样式,此时就当only不存在
        • 对不支持媒体查询的设备不使用样式
    • media feature:width、min-width、max-width(布局视口的宽度、最小宽度…)

    • 断点:样式切换的分界点,常用断点

      手机 ---- 768 ---- 小屏幕 ---- 992 ---- 中屏幕 ---- 1200 --- 大屏幕

3.5 Flexible方案·

3.5.1 核心思想:·
  • Flexible方案主要借助JS控制viewport的能力,用rem模拟vw的特性从而达到适配目的

  • rem是相对于html元素的font-size来做计算的计算属性值。通过设置documentElementfontSize属性值就可以统一整个页面的布局标准。

3.5.2 核心代码:·
  • Flexible将整个页面的宽度切成了10份,然后将计算出来的页面宽度的1/10设置为html节点的fontSize
  • 也就意味着,之后在当前页面的html节点的子节点上应用rem为单位时都是按照页面比例来计算的
// set 1rem = viewWidth / 10
function setRemUnit () {
var rem = docEl.clientWidth / 10;
// docEl为document.documentElement,即html元素
docEl.style.fontSize = rem + 'px';
}
setRemUnit();
3.5.3 Flexible配合的PostCSS-px2rem·
  • Flexible使用了rem作为统一页面布局标准的布局单位,且把页面宽度等分为了10份,那么在写css代码时就需要去计算当前px单位在当前设计稿上对应的rem值应该是多少

  • 以iPhone6为例:布局视口为375px,则1rem = 37.5px,这时设计稿上给定一个元素的宽为75px(设备独立像素),我们只需要将它设置为75 / 37.5 = 2rem即可

  • 当然,以上的工作方式显然低效。借助PostCSS的pxtorem插件postcss.config.js中完成这个计算过程:

    plugins: {
    ...,
    'postcss-pxtorem': {
    // 750设计标准
    rootValue: 75,
    // 转换成的rem后,保留小数点后几位
    unitPrecision: 5,
    /**
    * 将会被转换的css属性列表,
    * 设置为*表示全部,['*','*position*','!letter-spacing','!font*']
    * *position* 表示所有包含 position 的属性
    * !letter-spacing 表示非 letter-spacing 属性
    * !font* 表示非font-size font-weight ... 等的属性
    * */
    propList: ['*', '!letter-spacing'],
    // 不会被转换的class选择器名,支持正则
    selectorBlackList: ['.rem-'],
    replace: true,
    // 允许在媒体查询中转换`px`
    mediaQuery: false,
    // 小于1px的将不会被转换
    minPixelValue: 1
    }
    }
  • postcss-pxtorem可以把需要转的px值计算转换为对应的rem值,如:

    .name-item {
    font-size: 40px;
    line-height: 56px;
    margin-left: 144px;
    border-top: 1PX solid #eeeeee;
    color: #333333;
    }

    转换后是这个样子:

    .name-item {
    font-size: .53333rem;
    line-height: .74667rem;
    font-weight: 700;
    margin-left: 1.92rem;
    border-top: 1px solid #eee;
    color: #333;
    }
3.5.4 Flexible的缺陷·
  • PC与移动兼容问题:

    在写好PC端页面后,移动端设备适配时,此插件会影响到PC端,不能只限制它在移动端工作;

  • 对iframe的使用不兼容:

    iframe中展示的内容依然使用的是css像素,在高倍屏下会出问题,如我们在使用iframe引用一个腾讯视频的视频播放资源时,该视频播放器的播放按钮在不同dpr的设备上展示差异很大:

  • 对高倍屏的安卓手机没做适配处理

    lib-flexible对安卓手机的特殊处理,即:一律按dpr = 1处理

  • 不兼容响应式布局

    响应式布局,其实质性做法就是结合css3媒体查询@media对一些不同尺寸阈值做特定的布局设计,如对768px以下屏幕的使用紧凑型布局,对769px992px的屏幕做图文混排型布局,对大于992px的屏幕做富元素、多元素布局等。

    其中,@media语法中涉及到的尺寸查询语句,查询的尺寸依据是当前设备的物理像素,和Flexible的布局理论(即针对不同dpr设备等比缩放视口的scale值,从而同时改变布局视口和视觉视口大小)相悖,因此响应式布局在“等比缩放视口大小”的情境下是无法正常工作的。

  • 无法正确响应系统字体大小

    在微信环境(或其他可设置字体大小的Web浏览器中,如Safari)下,设置微信的字体大小(调大)后再打开使用Flexible技术适配的Web页面,你会发现页面布局错乱了,所有使用rem设置大小的元素都变大了,此时htmlfont-size还是原来的大小,但元素就是变大了,这是为什么呢?

    • 虽然Flexible帮我们使用<meta/>标签设置了width=device-widthuser-scalable=no以及对应的scale缩放值以保证我们的元素大小在高倍屏下(dpr >= 2 )正常展示,
    • 但在调整Web浏览器的字体大小后,我们的"视口"也响应的等比缩小了,即视觉视口(window.innerWidth)。并不是元素变大了,而是视觉视口变小了!在Flexible方案下毫无办法

4、how to pick·

那在开发中,如何为我们的项目选择合适的适配方案呢?总的来说,看适配设备、布局差异等:

  • 如果是PC与移动同时兼容,需要考虑的点:不能互相影响,所以不能粗暴的上插件

    • rem+媒体查询
    • vw+媒体查询
  • 如果是纯移动端,那可选的方案更多了,这时候目光更多的是聚焦在设备多样性中,布局差异

    • 大的话:可以直接媒体查询,不同的设备引入一套

    • 小的话:

      • viewport缩放方案

        • 适配原理简单
        • 需要使用JS
        • 直接使用设计稿标注无需换算
        • 方案死板只能实现页面级别整体缩放
      • 动态REM方案

        • 适配原理稍复杂
        • 需要使用JS
        • 设计稿标注的px换算到CSS的rem计算简单
        • 方案灵活技能实现整体缩放又能实现局部不缩放
      • vw方案

        • 适配原理简单
        • 不需要JS即可适配
        • 设计稿标注的px换算到CSS的vw计算复杂
        • 方案灵活技能实现整体缩放又能实现局部不缩放

5、可能遇到的问题·

5.1 手机1px问题·

问题及原因:·

  • 1px 问题指的是:在一些 Retina屏幕 的机型上,移动端页面的 1px 会变得很粗,呈现出不止 1px 的效果。

  • 原因——CSS 中的 1px 并不能和移动设备的 1px 划等号。它们之间的比例关系有一个属性描述

    window.devicePixelRatio = 设备的物理像素 / CSS像素。
  • 打开 Chrome 浏览器,启动移动端调试模式,在控制台去输出这个 devicePixelRatio 的值。这里选中 iPhone6/7/8 这系列的机型,输出的结果就是2: img 这就意味着设置的 1px CSS 像素,在这个设备上实际会用 2 个物理像素单元来进行渲染,所以实际看到的一定会比 1px 粗一些。 解决1px 问题的三种思路:

解决:·

解决1:直接写 0.5px·

如果之前 1px 的样式这样写:

border:1px solid #333

可以先在 JS 中拿到 window.devicePixelRatio 的值,然后把这个值通过 JSX 或者模板语法给到 CSS 的 data 里,达到这样的效果(这里用 JSX 语法做示范):

<div id="container" data-device={{window.devicePixelRatio}}></div>

然后就可以在 CSS 中用属性选择器来命中 devicePixelRatio 为某一值的情况,比如说这里尝试命中 devicePixelRatio 为2的情况:

#container[data-device="2"] {
border:0.5px solid #333
}

直接把 1px 改成 1/devicePixelRatio 后的值,这是目前为止最简单的一种方法。这种方法的缺陷在于兼容性不行,IOS 系统需要8及以上的版本,安卓系统则直接不兼容。

解决2:伪元素先放大后缩小·

  • 这个方法的可行性会更高,兼容性也更好。唯一的缺点是代码会变多。

思路是先放大、后缩小:

  • 在目标元素的后面追加一个 ::after 伪元素,让这个元素布局为 absolute 之后、整个伸展开铺在目标元素上,然后把它的宽和高都设置为目标元素的两倍,border值设为 1px。
  • 接着借助 CSS 动画特效中的放缩能力,把整个伪元素缩小为原来的 50%。此时,伪元素的宽高刚好可以和原有的目标元素对齐,而 border 也缩小为了 1px 的二分之一,间接地实现了 0.5px 的效果。
#container[data-device="2"] {
position: relative;
}
#container[data-device="2"]::after{
position:absolute;
top: 0;
left: 0;
width: 200%;
height: 200%;
content:"";
transform: scale(0.5);
transform-origin: left top;
box-sizing: border-box;
border: 1px solid #333;
}
}

思路三:viewport 缩放·

这个思路就是对 meta 标签里几个关键属性下手:

<meta name="viewport" content="initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no">
  • 这里针对像素比为2的页面,把整个页面缩放为了原来的1/2大小。这样,本来占用2个物理像素的 1px 样式,现在占用的就是标准的一个物理像素。根据像素比的不同,这个缩放比例可以被计算为不同的值,用 js 代码实现如下:
const scale = 1 / window.devicePixelRatio;
// 这里 metaEl 指的是 meta 标签对应的 Dom
metaEl.setAttribute('content', `width=device-width,user-scalable=no,initial-scale=${scale},maximum-scale=${scale},minimum-scale=${scale}`);

这样解决了,但这样做的副作用也很大,整个页面被缩放了。这时 1px 已经被处理成物理像素大小,这样的大小在手机上显示边框很合适。但是,一些原本不需要被缩小的内容,比如文字、图片等,也被无差别缩小掉了

5.2 细/1px/0.5px边框·

有几种方法能实现0.5px边框:

  1. 直接设置 border-width: 0.5px;使用方便,但兼容性很差,不推荐使用

  2. 用阴影代替边框,设置阴影box-shadow: 0 0 0 .5px #000; 使用方便,能正常展示圆角,兼容性一般。

  3. 给容器设置伪元素,设置绝对定位,高度为1px,背景图为线性渐变,一半有颜色,一半透明。视觉上宽度只有0.5px。这种方法适合设置一条边框,没法展示圆角。

  4. 给容器内设置伪元素,设置绝对定位,宽、高是200%,边框是1px,然后使用transform: scale(0.5) 让伪元素缩小原来的一半,这时候伪元素的边框和容器的边缘重合,视觉上宽度只有0.5px。这种方法兼容性最好,4个边框都能一次性设置,能正常展示圆角,推荐使用

  5. 采用meta viewport的方式

    <meta name="viewport" content="width=device-width, initial-scale=0.5, minimum-scale=0.5, maximum-scale=0.5"/>

    这样就能缩放到原来的0.5倍,如果是1px那么就会变成0.5px。viewport只针对于移动端,只在移动端上才能看到效果

5.3、12px字体问题·

chrome不支持12px以下的中文,属性-webkit-text-size-adjust也已失效

1、测试代码:·
.content {
width: 150px;
height: 150px;
border: 1px solid red;
margin: 0 auto;
text-align: center;
}
.content p {
font-size: 10px;
}
2、法1:transform·
transform:scale(0.875);
font-size: 12px;
  • 问题:如果这个

    元素有背景bgc的话,这个属性会使背景也随着变化,所以,可以给

    标签里再套个

    但此时不会生效:因为**transform:scale()**属性只为可以定义宽高的元素缩放,而span是行内元素;可以给span元素定义一个display:block、inline-block也可以

    .content p {
    background-color: skyblue;
    }
    .content p span {
    display: inline-block;
    /* font-size: 10px; */
    transform:scale(0.875);
    font-size: 12px;
    }
  • 此时PC端正常显示,但ios显示的字体比10px更小:

3、法2:svg·
  • SVG由于是矢量的,再怎么拉伸文字效果都是清晰细腻的:可能多行字体换行问题还需要解决

    div class="content">
    <svg width="150" height="14" viewBox="0 0 150 14">
    <text font-size="10" x="4" y="1em" fill="#cd0000">
    测试测试测试测试测试十四个字
    </text>
    </svg>
    </div