移动端适配方案和概念
in Web with 0 comment

移动端适配方案和概念

in Web with 0 comment

概念

为了在移动端让页面获得更好的显示效果,我们必须让布局视口、视觉视口都尽可能等于理想视口。

device-width就等于理想视口的宽度,所以设置 width=device-width就相当于让布局视口等于理想视口。

由于 initial-scale=理想视口宽度/视觉视口宽度,所以我们设置 initial-scale=1;就相当于让视觉视口等于理想视口。

这时,1个 CSS像素就等于1个设备独立像素,而且我们也是基于理想视口来进行布局的,所以呈现出来的页面布局在各种设备上都能大致相似。

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

ps: initial-scale| 0.0-10.0|定义页面初始缩放比率,设置值越大,看到的同个文字会越大。

这里的width可以决定布局视口的宽度,实际上它并不是布局视口的唯一决定性因素,设置 initial-scale也有肯能影响到布局视口,因为布局视口宽度取的是 width和视觉视口宽度的最大值。

例如:若手机的理想视口宽度为 400px,设置 width=device-width, initial-scale=2,此时 视觉视口宽度=理想视口宽度/initial-scale即 200px,布局视口取两者最大值即 device-width 400px。

若设置 width=device-width, initial-scale=0.5,此时 视觉视口宽度=理想视口宽度/initial-scale即 800px,布局视口取两者最大值即 800px。

1px问题

为了适配各种屏幕,我们写代码时一般使用设备独立像素来对页面进行布局。而在设备像素比大于 1的屏幕上,我们写的 1px实际上是被多个物理像素渲染,这就会出现 1px在有些屏幕上看起来很粗的现象。

border-image

基于 media查询判断不同的设备像素比给定不同的 border-image:

.border_1px{
    border-bottom:1px solid #000;
}

@media only screen and (-webkit-min-device-pixel-ratio:2){
    .border_1px{
        border-bottom:none;
        border-width:0 0 1px 0;
        border-image:url(../img/1pxline.png) 0 0 2 0 stretch;
    }
}

background-image

和 border-image类似,准备一张符合条件的边框背景图,模拟在背景上。

.border_1px{
    border-bottom:1px solid #000;
}

@media only screen and (-webkit-min-device-pixel-ratio:2){
    .border_1px{
        background:url(../img/1pxline.png) repeat-x left bottom;
        background-size:100% 1px;
    }
}

上面两种都需要单独准备图片,而且圆角不是很好处理,但是可以应对大部分场景。

伪类 + transform

基于 media查询判断不同的设备像素比对线条进行缩放:

.border_1px:before{
    content:'';
    position:absolute;
    top:0;
    height:1px;
    width:100%;
    background-color:#000;
    transform-origin:50% 0%;
}

@media only screen and (-webkit-min-device-pixel-ratio:2){
    .border 1px:before{
        transform: scaleY(0.5);
    }
}


@media only screen and (-webkit-min-device-pixel-ratio:3){
    .border 1px:before{
        transform: scaleY(0.33);
    }
}

这种方式可以满足各种场景,如果需要满足圆角,只需要给伪类也加上 border-radius即可。

svg

借助 PostCSS的 postcss-write-svg我们能直接使用 border-image和 background-image创建 svg的 1px边框:

@svg border_1px{
    height:2px;
    @rect{
        fill:var(--color,black);
        width:100%;
        height:50%;
    }
}

.exapmle{border:1px solid transparent;border-image:svg(border_1px param(--color #00b1ff)) 2 2 stretch;}

适配方案

flexible方案

我们在页面上统一使用 rem来布局,核心代码:

//set 1rem = viewwidth / 10;
function setRemUnit(){
    var rem = documentElement.clientWidth / 10;
    documentElement.style.fontSize = rem + 'px'
}
setRemUnit();

//reset rem unit on page resize
window.addEventListener('resize',setRemUnit);
window.addEventListener('pageshow',function(e){
    if(e.persisted){
        setRemUnit();
    }
})

由于 viewport单位得到众多浏览器的兼容,上面这种方案现在已经被官方弃用.

vh、vw方案

vh、vw方案即将视觉视口宽度 window.innerWidth和视觉视口高度 window.innerHeight 等分为 100 份。
如果视觉视口为 375px,那么 1vw=3.75px,这时 UI给定一个元素的宽为 75px(设备独立像素),我们只需要将它设置为 75/3.75=20vw。
这里的换算可以借助 PostCSS的 postcss-px-to-viewport 插件帮我们完成这个过程。

适配iPhoneX

在 iPhoneX发布后,许多厂商相继推出了具有边缘屏幕的手机,三个改动:圆角( corners)、刘海( sensor housing)和小黑条( HomeIndicator)。

viewport-fit

iOS11 新增特性,苹果公司为了适配 iPhoneX 对现有 viewport meta 标签的一个扩展,用于设置网页在可视窗口的布局方式,可设置三个值:

注意:网页默认不添加扩展的表现是 viewport-fit=contain,需要适配 iPhoneX 必须设置 viewport-fit=cover,这是适配的关键步骤。

env() 和 constant()

iOS11 新增特性,Webkit 的一个 CSS 函数,用于设定安全区域与边界的距离,有四个预定义的变量:

这里我们只需要关注 safe-area-inset-bottom 这个变量,因为它对应的就是小黑条的高度(横竖屏时值不一样)。
注意:当 viewport-fit=contain 时 env() 是不起作用的,必须要配合 viewport-fit=cover 使用。对于不支持env() 的浏览器,浏览器将会忽略它。

在这之前,笔者使用的是 constant(),后来,官方文档加了这么一段注释(坑):

The env() function shipped in iOS 11 with the name constant(). Beginning with Safari Technology Preview 41 and the iOS 11.2 beta, constant() has been removed and replaced with env(). You can use the CSS fallback mechanism to support both versions, if necessary, but should prefer env() going forward.

这就意味着,之前使用的 constant() 在 iOS11.2 之后就不能使用的,但我们还是需要做向后兼容,像这样:

padding-bottom: constant(safe-area-inset-bottom); /* 兼容 iOS < 11.2 */
padding-bottom: env(safe-area-inset-bottom); /* 兼容 iOS >= 11.2 */

注意:env() 跟 constant() 需要同时存在,而且顺序不能换。

如何适配

第一步:设置网页在可视窗口的布局方式

新增 viweport-fit 属性,使得页面内容完全覆盖整个窗口:

<meta name="viewport" content="width=device-width, viewport-fit=cover">

第二步:页面主体内容限定在安全区域内

这一步根据实际页面场景选择,如果不设置这个值,可能存在小黑条遮挡页面最底部内容的情况。

body {
  padding-bottom: constant(safe-area-inset-bottom);
  padding-bottom: env(safe-area-inset-bottom);
}

第三步:fixed 元素的适配

类型一:fixed 完全吸底元素(bottom = 0),比如下图这两种情况:
22222.png

可以通过加内边距 padding 扩展高度:

{
  padding-bottom: constant(safe-area-inset-bottom);
  padding-bottom: env(safe-area-inset-bottom);
}

或者通过计算函数 calc 覆盖原来高度:

{
  height: calc(60px(假设值) + constant(safe-area-inset-bottom));
  height: calc(60px(假设值) + env(safe-area-inset-bottom));
}

注意,这个方案需要吸底条必须是有背景色的,因为扩展的部分背景是跟随外容器的,否则出现镂空情况。
还有一种方案就是,可以通过新增一个新的元素(空的颜色块,主要用于小黑条高度的占位),然后吸底元素可以不改变高度只需要调整位置,像这样:

{
  margin-bottom: constant(safe-area-inset-bottom);
  margin-bottom: env(safe-area-inset-bottom);
}

空的颜色块:

{
  position: fixed;
  bottom: 0;
  width: 100%;
  height: constant(safe-area-inset-bottom);
  height: env(safe-area-inset-bottom);
  background-color: #fff;
}

类型二:fixed 非完全吸底元素(bottom ≠ 0),比如 “返回顶部”、“侧边广告” 等
像这种只是位置需要对应向上调整,可以仅通过外边距 margin 来处理:

{
  margin-bottom: constant(safe-area-inset-bottom);
  margin-bottom: env(safe-area-inset-bottom);
}

或者,你也可以通过计算函数 calc 覆盖原来 bottom 值:

{
  bottom: calc(50px(假设值) + constant(safe-area-inset-bottom));
  bottom: calc(50px(假设值) + env(safe-area-inset-bottom));
}

使用 @supports 隔离兼容样式
如果我们只希望 iPhoneX 才需要新增适配样式,我们可以配合 @supports 来隔离兼容样式

@supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) {
  div {
    margin-bottom: constant(safe-area-inset-bottom);
    margin-bottom: env(safe-area-inset-bottom);
  }
}

横屏适配

JavaScript检测横屏

window.orientation:获取屏幕旋转方向

window.addEventListener("resize",()=>{
    if(window.orientation === 180 || window.orientation ===0){
        //正常方向或 屏幕旋转180度
        console.log('竖屏');
    } 

    if(window.orientation === 90 || window.orientation === -90){
        //竖屏顺时钟旋转90度或屏幕逆时针旋转90度
        console.log('横屏');
    }   
})

CSS检测横屏

@media screen and (orientation:portrait){
    /*竖屏...*/
}

@media screen and (orientation:landscape){
    /*横屏...*/
}

图片模糊问题

产生原因

理论上,位图的每个像素对应在屏幕上使用一个物理像素来渲染,才能达到最佳的显示效果。而在 dpr>1的屏幕上,位图的一个像素可能由多个物理像素来渲染,然而这些物理像素点并不能被准确的分配上对应位图像素的颜色,只能取近似值,所以模糊

解决方案

针对不同 DPR的屏幕,我们需要展示不同分辨率的图片。
在 dpr=2的屏幕上展示两倍图 (@2x),在 dpr=3的屏幕上展示三倍图 (@3x)。

media查询

使用 media查询判断不同的设备像素比来显示不同精度的图片(只适用于背景图):

.avatar{
    background-image:url(conardLi_1x.png);
}
@media only screen and (-webkit-min-device-pixel-ratio:2)
{
    .avatar{
        background-image:url(conardLi_2x.png);
    }
}
@media only screen and (-webkit-min-device-pixel-ratio:3)
{
    .avatar{
        background-image:url(conardLi_3x.png);
    }
}

使用image-set

只适用于背景图

.avatar{
    background-image:-webkit-image-set("conardLi_1x.png" 1x, "conardLi_2x.png" 2x);
}

img标签的srcset属性

使用 img标签的 srcset属性,浏览器会自动根据像素密度匹配最佳显示图片:

<img scr="conardLi_1x.png" scrset=" conardLi_2x.png 2x, conardLi_3x.png 3x">

JavaScript拼接图片url

使用 window.devicePixelRatio获取设备像素比,遍历所有图片,替换图片地址:

const dpr = window.devicePixelRatio;
const images = document.querySelectorAll('img');
images.forEach((img)=>{
    img.src.replace(".",`@${dpr}x.`);
})

使用svg

<img src="conardLi.svg">

<img src="data:image/svg+xml;base64,[data]">

.avatar{
    backgground:url(conardLi.svg);
}

转发参考:
https://aotu.io/notes/2017/11/27/iphonex/index.html
https://mp.weixin.qq.com/s/hnJqHd-cWzdFbj0QH1e7UQ

Responses