移动端H5开发之页面适配篇

最近开发并上线了一款H5项目,在这里想和大家分享一下关于项目中使用到的移动端适配技巧,如果对你们有所帮助的话,就多多点赞收藏?

各位看官老爷别着急,在讲页面适配之前,我们先来捋一捋viewport(视口)的概念~

在Web浏览器术语中,通常与浏览器窗口相同,但不包括浏览器的UI, 菜单栏等——即指你正在浏览的文档的那一部分。

一般我们所说的视口共包括三种:布局视口、视觉视口和理想视口

1.1 布局视口

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

我们可以通过调用 document.documentElement.clientWidth / clientHeight来获取布局视口大小。

1.2 视觉视口

视觉视口,用户通过屏幕真实看到的区域。

我们可以通过调用 window.innerWidth / innerHeight 来获取视觉视口大小。

1.3 理想视口

视觉视口,用户通过屏幕真实看到的区域

我们可以通过调用 window.screen.width / height 来获取视觉视口大小

1.4 页面适配方法

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

我们可以借助<meta>元素的viewport来帮助我们设置视口、缩放等

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

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

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

这样我们就实现了布局视口 = 理想视口。下面附上完整的viewport取值介绍。

1.4.1 通过设置initial-scal来适配

通过上文了解到,viewport有个initial-scale属性,用来定义页面初始缩放比率,我们是否可以通过动态的改变这个缩放值来进行适配呢,答案是可以的!

function viewPort(){
  const meta = document.querySelector('meta[name="viewport"]');
  const scale = window.screen.width / 750 > 1 ? 1 : window.screen.width / 750;
  meta.content = `width=device-width,initial-scale=${scale}`
}
viewPort()
window.onresize = viewPort;

通过这段代码就实现了适配,我们来分析一下:

我们设置UI提供的设计稿为750,当然也可以是别的大小,假定我们就根据750px的UI图来写css,当用户的设备理想视图 window.screen.width 大于750时,我们就把页面整体缩放,如果小于750时,就把页面整体放大,缩小和放大的比例,我们通过 window.screen.width / 750 (设计稿大小) 来获取,然后动态的设置到meta标签上。

下图为实现效果

这种方式虽然能够解决适配问题,但也会过于简单粗暴,主要有以下两个问题
1. 全局缩放,把不需要缩放的也影响了;
2. 如果有第三方UI库,会影响了第三方库的显示效果;

1.4.2 通过rem来适配

rem(font size of the root element)是指相对于根元素的字体大小的单位。简单的说它就是一个相对单位。看到rem大家一定会想起em单位,em(font size of the element)是指相对于父元素的字体大小的单位。它们之间其实很相似,只不过一个计算的规则是依赖根元素一个是依赖父元素计算。

下图所示,如果根元素html的字体大小为100px的话,1rem也就等于100px。

html{
 font-size: 100px;
}
#root {
 font-size: 1rem; /* 100px */
}

基于这个原理,可以推导出,如果设计稿宽是750px,且以设计图为标准的 html标签的font-size为100px,那么这个设计图总宽就有 7.5rem,如果动态的更改根元素的字体大小,是不是就可以实现动态改变元素的大小了?答案是的!

实现这个只要简单的3步即可,首先设置viewport为理想窗口,并且初始化缩放为0

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

然后书写js逻辑,监听页面resize事件,动态的设置html的字体大小

(function (window, html) {
  // 规定默认的设计稿宽度720px
  const designWidth = 720;

  function recalc() {
    const windowWidth = html.clientWidth < designWidth ? html.clientWidth : designWidth;

    // *100 之后,则样式中rem的值就需要相应的缩小100倍
    // 即:设计稿中的20px,在样式中就要写成0.2rem
    const fontSize = windowWidth / designWidth * 100;

    setFontSize(fontSize);
  }

  function setFontSize(fontSize) {
    html.style.fontSize = `${fontSize}px`;
  }

  // 监听resize
  window.addEventListener('resize', recalc);
  recalc();
}(window, document.documentElement));

上述代码中,核心代码const fontSize = windowWidth / designWidth * 100;规定1rem等于设计稿的宽度,也就是720px,最后乘100是为了写rem时方便计算

比如720px设计稿上的某个元素宽度为50px,那我在写rem单位时可以直接除以100写成0.5rem即可,方便计算。

下图为实现效果

上面的代码会有一个最大变化的阀值,为designWidth也就是720px,当浏览器窗口的大小超过这个值时,就不再动态变化了,这个可以保证在pc上也能正常显示。

当然,作为一名合格的前端,怎么可以自己计算写rem呢,我们可以借助使用postcss-pxtorem插件来自动完成这项工作。

在你的项目webpack配置文件中,针对less文件增加postcss-loader

{
  test: /\\.less$/,
  use: [
    { loader: 'style-loader' },
    {
      loader: 'css-loader',
    },
    {
      loader: 'postcss-loader',// 增加postcss-loader
    },
    {
      loader: 'less-loader',
      options: {
        javascriptEnabled: true,
      },
    },
  ],
},

然后在根目录中创建postcss.config.js文件

module.exports = {
  plugins: [
    require('postcss-pxtorem')({ 
      // 750宽度的设计稿
      rootValue: 100,
      unitPrecision: 5,
      propList: ['*', '!letter-spacing'],
      selectorBlackList: [],
      replace: true,
      mediaQuery: false,
      minPixelValue: 1,
      exclude: /node_modules/i,
    }),
  ],
};

这里可以设置转换的比率、类名、小数点数量等,具体的配置可以参考配置项

如果不想被转换的话,px可以写成大写,这样就不会进行转换了

// `Px` or `PX` is ignored by `postcss-pxtorem` but still accepted by browsers
.ignore {
    border: 1Px solid; // ignored
    border-width: 2PX; // ignored
}

这样就实现了px单位向rem单位的自由转换,就不需要一个个的计算手写了。

1.4.3 flexible1.0方案

既然说到了rem的适配方案,那我们就来聊一下淘宝团队早期的flexible方案。

淘宝的手淘团队,在做移动端适配时,使用的flexible方案核心就是rem适配,打开他们的github源码,会发现比rem逻辑多了一些dpr的处理。

所以他们的方案是rem+dpr,既然说到了这里,我们再来讲一下dpr是个什么东西?

1.4.3.1 dpr是什么

dpr全名叫device pixel ratio,是设备上物理像素和设备独立像素的比率,公式表示就是:window.devicePixelRatio = 物理像素 /设备独立像素

此值也可以解释为像素大小的比率:一个CSS像素的大小与一个物理像素的大小。 简单来说,它告诉浏览器应使用多少屏幕实际像素来绘制单个CSS像素。

这个最早还是乔布斯提出来的,之前一个像素被一个物理像素渲染,但是iphone4出来之后,一个像素被两个物理像素渲染

如下图所示,左边为iphone4之前设备处理像素的逻辑,右边为iphone4处理的逻辑,可以看到一个像素被两个物理像素渲染,这样会让显示更加清楚,没有像素感。

所以一倍的图片,在dpr为2的设备上会显示小一倍,然后flexible会针对这种情况整体缩放0.5,也就使图片正常显示。

但是上文1.4.1中也说了,直接改变initial-scale的值,会产生一些问题,所以后来flexible2.0也放弃了计算dpr这部分逻辑。

在说viewport方案之前,我们先来解决移动端dpr普遍>=2的问题。

1.4.3.2 解决dpr带来的问题

上文中讲的dpr带来的问题主要有两个:1px问题和图片模糊的问题,我们先来看看如何解决1px的问题

这是一些目前主流的解决1px问题的方案,但是看现在的淘宝、京东等一些网站,对于1px也没有进行任何处理,估计是解决这类问题的成本/收益大于1。

再来看下图片模糊的问题,大多发生在高dpr的设备上使用低倍图场景,所以图片我们直接使用2倍图即可,因为大部分的设备dpr为2,对于一些dpr>=3的特殊设备,我们也可以使用媒体查询来适配即可

.bg {
   background-image: url('bg@2x.png);
   @media (-webkit-min-device-pixel-ratio: 3), (min-device-pixel-ratio: 3) {
      background-image: url('bg@3x.png');
   }
}

1.4.4 viewport方案

viewport方案就是使用css3的计算单位vw、vh等来进行适配,关于这种css3单位的官方解释点这里查看。

下图形象的解释了这个单位的定义:

再来看一下浏览器的兼容

可以看到对于现代浏览器,这种单位已经兼容。

我们根据上面关于vw的定义,以及rem方案的了解,如果设计稿为750px,那么我们把屏幕平均分成750份,所以1px就等于 100vw / 750的vw,举个例子,如果设计稿上的某个元素为50px,那么对于的vw为 50 * 100vw / 750。

我们就得到了px和vw的转换公式:px * 100vw / 750

在项目中具体的实现方式,首先和rem一样也需要设置meta标签

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

然后直接写css

<style type="text/css" media="screen">
 #root {
  width: 53.33vw;/* 400px -> 400 * 100vw / 750 */
  height: 6.66vw;/* 50px -> 50 * 100vw / 750*/
  margin: auto;
  background-color: red;
 }
</style>>

实现效果:

所以这种适配方式的好处是我们只写css就可以了,不需要再写额外的逻辑,但是每次手动计算确实很麻烦,我们同样可以借助自动化工具postcss-px-to-viewport来实现,具体的实现方式类似上文中rem转px,对于不想转换的,可以增加ignore注释来跳过。

/* example input: */
.class {
  /* px-to-viewport-ignore-next */
  width: 10px;
  padding: 10px;
  height: 10px; /* px-to-viewport-ignore */
  border: solid 2px #000; /* px-to-viewport-ignore */
}

/* example output: */
.class {
  width: 10px;
  padding: 3.125vw;
  height: 10px;
  border: solid 2px #000;
}

但是viewport的适配方案也有一定的缺点,那就是不能设置一个最大宽度的阀值,只能跟着浏览器视图大小的改变而变化,这样对于一些想要在pc和h5都要正常展示的项目不太友好

1.4.5 针对刘海屏的兼容

针对iphoneX以上具有刘海屏的机型,也有对应的适配方案,那就是viewport-fit 和Safe Area,先来看一下viewport-fit

<meta name="viewport" content="viewport-fit=cover">

contain: 可视窗口完全包含网页内容

cover:网页内容完全覆盖可视窗口

默认情况下或者设置为auto和contain效果相同。

下图中,左边为contain,右边为cover

Safe Area是iphoneX之后引入的新概念,指的是一个可视窗口范围,下图可以看到相关区域的定义

constant(safe-area-inset-top)在Viewport顶部的安全区域内设置量(CSS像素)

constant(safe-area-inset-bottom)在Viewport底部的安全区域内设置量(CSS像素)

constant(safe-area-inset-left)在Viewport左边的安全区域内设置量(CSS像素)

constant(safe-area-inset-right)在Viewport右边的安全区域内设置量(CSS像素)

然后给body设置一下安全区域

body {
  padding:
    constant(safe-area-inset-top)
    constant(safe-area-inset-right)
    constant(safe-area-inset-bottom)
    constant(safe-area-inset-left); /* 兼容 iOS < 11.2 */
  padding:
    env(safe-area-inset-top)
    env(safe-area-inset-right)
    env(safe-area-inset-bottom)
    env(safe-area-inset-left); /* 兼容 iOS >= 11.2 */
}

再来说一下env这个css函数:env()函数以类似于var函数的方式将用户代理定义的环境变量值插入到你的 CSS 中去。这个函数最初由 iOS 浏览器提供,用于允许开发人员将其内容放置在视口的安全区域中,该规范中定义的 safe-area-inset-* 值用于确保内容即使在非矩形的视区中也可以完全显示。

但是对于ios < 11.2的系统来说,需要使用constant函数来替代env进行兼容。

通过设置viewport-fit 和 安全区域,就能完美对刘海屏进行适配了。

1.5 总结

说了这么多,我们简单的来总结一下:

1.对于需要移动端、PC端都正常展示的项目推荐使用rem布局;

2.对于只在移动端展示,且内容量较少的页面推荐使用vw布局;

当然媒体查询@media也可以用来进行页面适配,但是相对于我讲的这几种显得不够灵活,针对各种条件来写css也显得不太友好,可以作为一种辅助方法来填补以上几种方法覆盖不了的地方。

下一篇会将移动端调试方法?

参考文档:

https://www.w3.org/TR/css-round-display-1/

https://cloud.tencent.com/developer/article/1637066

https://juejin.cn/post/6844903951012200456

https://juejin.cn/post/6953091677838344199

本站文章资源均来源自网络,除非特别声明,否则均不代表站方观点,并仅供查阅,不作为任何参考依据!
如有侵权请及时跟我们联系,本站将及时删除!
如遇版权问题,请查看 本站版权声明
THE END
分享
二维码
海报
移动端H5开发之页面适配篇
最近开发并上线了一款H5项目,在这里想和大家分享一下关于项目中使用到的移动端适配技巧,如果对你们有所帮助的话,就多多点赞收藏?
<<上一篇
下一篇>>