移动端适配相关问题

梁柱

1.相关知识:

1.1 像素

物理像素(设备像素):是屏幕中的发光的点数。

如分辨率为:640 x 1136px。横向有640个发光的点,纵向有1136个发光的点。

逻辑像素(设备独立像素):独立于设备的用于逻辑上衡量像素的单位。在移动Web开发中就是指的CSS的逻辑像素。
css像素:web编程用到的,在JS和CSS中使用就是CSS像素,是可变的。
设备像素比(dpr):用来描述单一方向上,设备物理像素的点数/逻辑像素的点数的比率。值越高,屏幕密度越大。常说的两倍屏、三倍屏,这里面的倍数指的就是 dpr。
在JS中我们可以通过window.devicePixelRatio来得到当前设备的dpr.
在CSS中我们可以通过-webkit-device-pixel-ratio来进行媒体查询.

1.2 视口(viewport)

1.2.1 布局视口(layout viewport)

通过document.documentElement.clientWidth来获取layout viewport的宽度。
layout.png

1.2.2 视图视口(visual viewport)

通过window.innerWidth获取
visual.png

1.2.3 理想视口(ideal viewport)

在移动端布局中我们需要的是ideal viewport。

<!--这一行代码告诉浏览器,布局视口的宽度应该与理想视口的宽度一致-->
 <meta name="viewport" content="width=device-width">
或者
 <meta name="viewport" content="initial-scale=1">

2.viewport 概念

viewport 中文译作“视口”。
●计算机把图像渲染到显示器的过程中,会先把图像画在一个逻辑层的画布上,然后从这个画布中框选一部分,将其投影到显示层。
●这个选框就是视口,显示层就是窗口。
●在浏览器中,我们可以通过滚动条来移动视口以看到更多网页内容。

2.1 viewport 的缩放与平移

页面放大:视口缩小
滚动滚动条:视口移动

2.2 viewport的DOM API

document.documentElement.clientWidth(不含滚动条)
window.innerWidth(含滚动条)
注意:
在移动端的浏览器中,对页面手动捏合做放大时,document.documentElement.clientWidth 不会有任何变化。window.innerWidth在 iOS 中会等倍数缩小,在 Android 的不同浏览器中表现差异较大。

3.移动端的 viewport

3.1 窄屏设备的问题

移动互联网的早期,屏幕设备的物理像素点宽度多数在 320、480、640 等。而互联网世界的绝大多数站点又是针对 PC 设计的,其文档宽度普遍在 800px 以上。
如果浏览器和针对 PC 制作的网页都不做任何处理,那么在窄屏设备上加载网页,我们看到的效果便是默认显示网页的左上角部分,然后通过水平和竖直方向的滚动来浏览网页的其他部分。
首先,我们看不到网页全局的样子。其次,我们需要通过不断的滚动来保证阅读内容的连续性。这样的体验,有点过于糟糕了。

3.2 放大的viewport

为了优化“最初为 PC 设计的网页”在移动设备的浏览体验,移动浏览器厂商们想了一个方案,那就是增大页面载入时初始视口的宽度,比如 Android 和 iOS 都比较常见的 980px。
按照 2.1 里的 viewport 的解释,如此的设计,会把逻辑层画布中 980px 的图像投影显示到 320px 的屏幕上,看到的效果便是一个挤在一起看不清楚细节的缩小版页面。
不过,该方案依然会有很多问题:
●对缩小版页面内细节内容的浏览,依然要依靠放大和滚动,体验不好;
●如果为 PC 设计的 网页的 CSS 宽度描述大于 980px,那么在移动端展示时,初始页面依然会有滚动条;
●限制了依据视口宽度做媒体查询(Media queries)机制的有效性,因为视口宽度初始为 980px,浏览器不会以 640px、480px 或更低分辨率来启动对应的媒体查询。

注:媒体查询请注意区分"@media screen and (xxx){}"中的min-device-width 和min-width。前者依据的是设备逻辑宽度(screen.width),后者依据的是视口宽度(document.documentElement.clientWidth)。

3.3 可定制的viewport

●针对较宽(比如 2000px) PC 设计的页面,我们可以设置 viewport 宽度为 2000,以使得移动设备中初始渲染的页面效果刚好不出现滚动条;
●利用了媒体查询做了移动端适配的页面,我们可以设置 viewport 宽度为 device-width,以保证媒体查询技术的有效性,同时还能保证横竖屏切换时,px 单位做大小描述的页面元素的视觉大小一致性;
●需要充分利用屏幕物理像素点做 1 像素极细边线的页面,我们可以设置 viewport 缩放倍数为 1/dpr,以使得 css 中的 1px 刚好对应设备物理像素中的 1 个点;
●需要根据屏幕宽度弹性伸缩的页面,我们可以结合各种相对长度单位(%/rem/vw 等),设置合适的 viewport,以实现布局伸缩和内容大小固定的完美统一。

4. Viewport Meta Tag 的使用

4.1 viewport 属性表

属性 说明
width 设置视口宽度。layout viewport的宽度,数字或者device-width height
height 设置视口高度。layout viewport的高度,数字或者device-height
initial-scale 页面初始缩放值。设备逻辑宽度与视口宽度的初始缩放比值。
maximum-scale 用户最大缩放值
minimum-scale 用户最小缩放值
user-scalable 允许用户缩放,yes(默认)或no
viewport-fit 视口填充屏幕的方式,默认值为contain

4.2 viewport 属性举例

以iPhone6s手机+Safari浏览器举例,对上述属性做详细说明。
设备参数说明:
●操作系统:iOS 12.3.1
●屏幕物理分辨率:750*1334
●屏幕逻辑分辨率:375*667 (screen.width/height)
●设备像素比(dpr):2 (window.devicePixelRatio)
●浏览器默认视口宽度:980 (document.documentElement.clientWidth)

width
<metaname\="viewport"content\="width=1000" />

●document.documentElement.clientWidth 输出 1000
●div 宽度 1000px 时,横向刚好铺满屏幕,超过出现横向滚动条

<meta name="viewport" content="width=device-width" />

●效果等同于 width=375
●document.documentElement.clientWidth 输出 375
●div 宽度 375px 时,横向刚好铺满屏幕,超过出现横向滚动条

initial-scale
<meta name="viewport" content="initial-scale=1" />

●效果等同于 width=device-width

<meta name="viewport" content="initial-scale=2" />

●document.documentElement.clientWidth 输出 188 (375/2)
●div 宽度 188px 时,横向刚好铺满屏幕,超过出现横向滚动条
●scale 倍数越小,视口越大

maximum-scale / minimum-scale
<meta  name="viewport"  content="initial-scale=2,minimum-scale=1,maximum-scale=3"/>

预期页面初始 1 倍,最小可以缩小到 0.5 倍,最大放大到 2 倍。但是实际表现并非如此:
●小米 9 的系统浏览器表现符合预期;
●iOS 中 所有 Web 容器均无法缩放 到 比 initial-scale 更小的倍数,即使 minimum-scale 声明了一个更小且合理的取值;
●iOS 微信(7.0.5)的 webview 中,遵守了最大 3 倍声明,但 Safari 可以放大到比 3 倍更高的倍数。
iOS10 开始,为了提高网页在 Safari 中的可访问性,Safari 限制了最小倍数(minimum-scale)并忽略了 最大倍数(maximum-scale) 的声明。
Android 和 iOS 在不同版本不同厂商的 Web 容器中,此属性的表现可能存在较大程度的不一致,请谨慎使用。

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

●Safari 中依然无法缩小可以放大,微信中无法缩放;
●Android 未做测试。
同 4.2.3,此属性同样存在兼容问题,请谨慎使用。

viewport-fit
1<meta name="viewport" content="initial-scale=1,viewport-fit=cover" />

此属性为 2017 年 Apple 为了解决 iPhoneX 手机的刘海屏问题,增加的新属性。

4.3 width和initial-scale的取值冲突

width=device-width 和initial-scale=1 是等效的。

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

Safari 的运行结果是“按宽度大小”取更大的,但是这样的对比研究并没有任何意义。因为并没有相应的规范约束这件事情,浏览器的兼容表现肯定是千差万别。

作为开发者,我们要做的,就是避免冲突。要么只写一个,要么两个都计算正确。从语义表达角度看,建议只设置"width"。从计算方便角度看,可以只设置 initial-scale。

5.跨屏适配设计方案

跨屏适配的需求,根据业务类型,一般有两种 UI 设计方案:
●根据屏幕宽度,UI 布局弹性伸缩或流动。这种方式被称为响应式设计(_Responsive Design_);
●把屏幕按宽度范围分为有限的几个区段,为每个区段定制固定的 UI,相当于为专门的设备设计专门的 UI。这种方式被称为自适应设计(_Adaptive Design_)。
640.png

响应式和自适应的区别,国内外各种社区都有很多的讨论,甚至争议。个人认为两种方式更多是一种UI设计层面的区别。技术实现层面,区别并不明显。

响应式。屏幕适配无粒度区分,同一设备上做宽度变化时,内容布局无缝圆滑变化;技术实现通常为,一套代码适配所有屏幕。

自适应。屏幕适配有粒度区分,原则上不做过渡态的 UI 设计,同一设备上做宽度变化时,内容布局卡顿式梯级变化;技术实现通常为,多个屏幕对应多套代码。(演示如下图)

适应.gif

5.1 响应式设计

响应式设计方案,常见于 PC、移动等多端共用一套代码的场景。典型的 Web 站点如GitHub
github.gif
浏览这类站点时,随着屏幕的缩小,你会看到页面模块的布局结构在伸缩、流动或显隐变化,文字图片等主体内容在布局容器内流动填充、其大小也一直在做梯级变化。

5.2 自适应设计

为了在特定设备上实现最好的用户体验,越来越多的产品,开始针对特定屏幕设计固定的 UI,绝大多数移动端产品都有了区分于 PC 的专门的m站。
其技术实现通常为:服务器根据浏览器请求的 user-agent 判断设备类型,然后返回(或重定向)对应的站点内容。

6. 移动端跨屏适配中的 viewport

移动端的屏幕宽度差距比较小(4-8 英寸),UI 页面通常也会保持一致的布局方式,只是文字、图标、大图片等可能会根据业务需要做一些定制化的处理。

注:Pad 设备虽然也是移动设备,但是因为屏幕足够宽,所以现在多数产品(如某宝)的方案都是访问 PC 站点了。

移动端多屏适配的需求,常见主要有两类
   1、布局伸缩式(布局伸缩,内容大小固定或梯级变化);
   2、等比缩放式(布局和内容完全等比例缩放)。

注:实际业务中的适配需求可能是区分页面模块做混搭的,这里为了方便做技术阐述只讨论此两类。

对应的技术方案一般也是对视口(viewport)、媒体查询(media queries)、单位(px/%/rem/vw)的组合使用。

6.1 布局伸缩式

6.1.1 需求描述

布局伸缩式.gif
如上图,布局伸缩式适配需求,常见于排版比较简单的信息流展示类业务。
其布局特点一般为横向伸缩,竖向高度固定或由内容填充决定;文字图标等网页内容一般会固定大小,且在宽屏窄屏上的视觉大小保持一致。

6.1.2 技术方案

●设置 viewport 宽度为 device-width,以保证 px 为单位取值的一些文字图标等网页内容视觉大小符合预期且宽窄屏大小一致。(css 中的 px 取值需按一倍屏 UI 稿来写);
●布局方案灵活使用相对单位%/float/flex 等,以保证布局的横向伸缩和容器内各元素的大小间距符合预期;
●组合包裹相关元素,并相对某一方向做定位,以保证宽度变化时的定位稳定。

6.2 等比缩放式

6.2.1 需求描述

等比缩放式.gif
如上图,等比缩放式适配需求,广泛应用于各种产品类、运营类等业务场景。
其布局特点简单粗暴,就是根据屏幕宽度整个页面等比缩放。

6.2.2 技术方案

rem
rem 是 CSS3 新增的相对于根元素 html 的 font-size 计算值的大小的倍数单位。早期的移动端等比缩放的适配方案都是基于 rem。
●设置 viewport 宽度为 device-width 或其他固定值,以得到 px 为单位的文字、图标或边线等期望的渲染效果
●css 单位使用 rem,js 根据 viewport 宽度以及 css 中 rem 的换算系数,动态计算并设置 html 根节点 font-size,以实现整个页面内容的等比例缩放

注:一些文本段落展示类的需求,UI 设计师可能会希望宽屏比窄屏在一行内可以展示更多的文字。这时就需要引入媒体查询,并且对字号使用 px 单位做特殊处理。

rem 为基础的动态适配方案
设:横向满屏的 rem 个数预定为 remCount,标注稿总宽度 px 为 uiWidth,标注稿内某元素宽度为 uiEleWidth。
那么:
●设计稿中 1rem 表示的 px 数 uiPX1rem = uiWidth/remCount
●CSS 中某元素 rem 的值 cssEleWidth= uiEleWidth/uiPX1rem
●JS 中根节点的 fontSize = document.documentElement.clientWidth/remCount

viewport units

viewport units(视口单位),是 iOS8+、Android4.4+ 开始支持的新的长度单位,包括:vw/vh/vmin/vmax。目前已被广泛应用于移动端适配中。

1vw 即表示当前视口宽度的 1%,我们可以利用这一点替代“rem+根节点 font-size”的等比缩放实现。
举个例子,750px 的 UI 稿中,宽度 75px 的按钮,在 css 中的宽度描述即为:width:10vw。

viewport meta only
看起来viewport units方案是目前最简单可行的方案了,UI 稿里的标注直接都转换成 vw 单位就可以了,html 中不需要做任何 js 处理。那么,是不是还可以更简单一些呢?

回到本文最初的起点,<meta name="viewport">的引入是为了支持开发者定制视口的大小。

我们做的 ui 稿中的 px 到 css 的 rem、vw 这些单位的转换,核心目的就是在不同的屏幕上高保真还原设计稿。

那么,我们直接把 web 容器视口的大小定为和 UI 稿一样的 px 大小不就可以了?
是的,不再需要做任何单位的转换。
而且,完美实现 UI 稿的高保证还原。完全不需要担心什么 0.5px 细线问题。
然而,当前前端圈内viewport meta only方案并未成为主流方案,个人认为,原因主要是——业务场景中,存在非等比缩放类的适配需求,比如布局只要横向伸缩、字号要 px 固定等。
如果 100%确定当前业务可以完全等比缩放式适配,那么,强烈推荐使用该方案。

注:Android 的 webview 默认未开启 viewport meta 支持,需要手动开启webView.settings.useWideViewPort = true;

8.解决移动端1px边框问题

问题描述

主要是跟一个东西有关,DPR(devicePixelRatio) 设备像素比,它是默认缩放为100%的情况下,设备像素和CSS像素的比值。

window.devicePixelRatio=物理像素 /CSS像素

目前主流的屏幕DPR=2 (iPhone 8),或者3 (iPhone 8 Plus)。拿2倍屏来说,设备的物理像素要实现1像素,而DPR=2,所以css 像素只能是 0.5。一般设计稿是按照750来设计的,它上面的1px是以750来参照的,而我们写css样式是以设备375为参照的,所以我们应该写的0.5px就好了啊! 试过了就知道,iOS 8+系统支持,安卓系统不支持。

解决方案

1. 0.5px边框

通过 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;
}

优点:简单,不需要过多代码。
缺点:无法兼容安卓设备、 iOS 8 以下设备。

2. 使用border-image实现、background-image实现
3. 使用box-shadow模拟边框

利用css 对阴影处理的方式实现0.5px的效果

.box-shadow-1px {
  box-shadow: inset 0px -1px 1px -1px #c8c7cc;
}

优点:代码量少、可以满足所有场景
缺点:边框有阴影,颜色变浅

4. viewport + rem 实现

同时通过设置对应viewport的rem基准值,这种方式就可以像以前一样轻松愉快的写1px了。
在devicePixelRatio = 2 时,输出viewport:

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

在devicePixelRatio = 3 时,输出viewport:

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

优点:所有场景都能满足
一套代码,可以兼容基本所有布局
缺点:老项目修改代价过大,只适用于新项目

5. 伪类 + transform 实现

单条border样式设置:
将伪元素设置绝对定位,并且和父元素的左上角对齐,将width 设置100%,height设置为1px,然后进行在Y方向缩小0.5倍。

.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;
}

四条boder样式设置:
为伪元素设置绝对定位,并且和父元素左上角对其。将伪元素的长和宽先放大2倍,然后再设置一个边框,以左上角为中心,缩放到原来的0.5倍。

.scale-1px{
  position: relative;
  margin-bottom: 20px;
  border:none;
}
.scale-1px:after{
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  border: 1px solid #000;
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
  width: 200%;
  height: 200%;
  -webkit-transform: scale(0.5);
  transform: scale(0.5);
  -webkit-transform-origin: left top;
  transform-origin: left top;
}
6.媒体查询 + transfrom
/* 2倍屏 */
@media only screen and (-webkit-min-device-pixel-ratio: 2.0) {
    .border-bottom::after {
        -webkit-transform: scaleY(0.5);
        transform: scaleY(0.5);
    }
}
/* 3倍屏 */
@media only screen and (-webkit-min-device-pixel-ratio: 3.0) {
    .border-bottom::after {
        -webkit-transform: scaleY(0.33);
        transform: scaleY(0.33);
    }
}

参考文章:https://juejin.im/post/5d19b7...
参考文章:https://www.jianshu.com/p/7e6...
参考文章:https://mp.weixin.qq.com/s?__biz=MzIxNjgwMDIzMA==&mid=2247485302&idx=1&sn=af11df0e8c38c99f86e2102e7acd5c73&chksm=9782c9ffa0f540e971142f771812d7d7e385be629b2c0eb87d369019350404dfc4c4ab71096c&mpshare=1&scene=23&srcid=0410ho3xxFZv3RPSo1svzhjs&sharer_sharetime=1586475970239&sharer_shareid=efadf805ae32814aa9f4196d487c55d8#rd

阅读 831

85 声望
6 粉丝
0 条评论
85 声望
6 粉丝
宣传栏