移动端布局适配hotcss+postcss-pxtorem

背景

原本团队是搞PC端开发的,前段时间架构调整,移动端人力不足,因此团队把移动端的工做也一块儿接过来了。css

不过因为咱们团队之前没开发过移动端的东西,技术积累较少,所以花费了很多时间在基础技术积累上面。前端

此次介绍一下咱们的移动端适配方案的改进过程。webpack

hotcss库

最开始进行移动端开发时,因为工期比较紧张,为了快速构建整个项目,我最后选择使用了hotcss 这个库。 这个库的优势其实挺多的:git

  1. 使用方便
  2. 支持不一样的设计稿尺寸
  3. 支持在js代码中换算适配后的像素
  4. 有效解决1px问题

最开始选用这个库的缘由其实也是由于使用方便,当初通过测试以后发现能够比较好的还原设计稿的尺寸后,咱们就选择使用这个方案了。github

不过因为担忧cdn访问延迟和dns解析能问题致使hotcss.js资源加载失败,再加上自己源码也不长,咱们直接将源码压缩后内联进模板里面。web

不过使用一段时间以后,发现hotcss方案确实也有很多的坑。数组

1px/0.5px问题

通过咱们QA同窗的真机测试,发如今部分安卓4手机dpr=1的状况下,1px0.5px的样式通过px2rem的转换后,仍是会有问题,在dpr=1的低端机上,跟标签font-size会被计算成20px(仍是22.5px有点忘记了),在这种状况下某些很小的像素如1px或者0.5px就有可能由于宽度小于了浏览器显示的最小宽度,因此咱们为了兼容1dpr的手机,不得不专门给这种小像素的样式写了一个dpr的选择器的兼容方案(其余css样式同理):浏览器

@mixin border-bottom($width: 1) {
  border-bottom: px2rem($width) solid map-get($defaultColor, borderColor);
  [data-dpr='1'] & {
    border-bottom-width: $width + 'px';
  }
}
复制代码

这个问题我的感受更多的仍是0.5px致使的,可是架不住UI就是喜欢用0.5px的分割线和border...sass

设计稿尺寸问题

其实这个问题并非hotcss库自己的问题,应该让UI同窗出设计稿的时候,给前端同窗一套统一的尺寸。架构

可是咱们最开始的UI同窗因为我的习惯问题,有的给咱们的是iOS尺寸的设计稿,这种的尺寸是375px的,有的是安卓尺寸的设计稿360px的。而hotcss这个库须要提早声明$designWidth这个变量,以后使用px2rem()方法的时候才会进行替换,因为css没有做用域的概念,因此须要使用sass和less。

sass文件编译时候也是从上往下顺序执行的,变量声明能够覆盖,所以若是使用了sass或者less这种css扩展语言的话,这个地方应该是没有问题的,不过要记得给每个sass和less文件都声明$designWidth,不然可能会由于某些文件没有声明变量致使使用了其余页面声明的尺寸而出现异常。

因此最佳的解决办法仍是让UI统一尺寸,。

书写繁琐

说实话虽然使用起来很方便,只须要引入一个官方的sdk就完成了移动端适配方案。可是当写起业务css代码的时候,仍是感受hotcss使用起来很繁琐。

当UI稿中的尺寸是这样的时候:

font-size: 16px;
font-height: 20px;
height: 50px;
width: 200px;
复制代码

咱们就不能直接copy过来了,须要对其进行单位转换:

// 这一块代码能够放到common.scss中
@function px2rem($px) {
  @return $px * 320 / $designWidth/20 + rem;
}

$designWidth: 360;

font-size: px2rem(16);
font-height: px2rem(20);
height: px2rem(50);
width: px2rem(200);
复制代码

总之当业务稳定以后,咱们慢慢才发现用这个方案仍是很是麻烦的,就常常被同事吐槽。

基于以上种种缘由,咱们团队在升级打包工具webpack->razzle的过程当中,改进了适配方案,采用了hotcss+postcss-pxtorem结合的方案。

hotcss配合postcss-pxtorem

postcss-pxtorem这个工具库的使用介绍网上也处处都有,这个库的优势在于有很强的转换规则,能够适配各类场景。咱们在这里使用这个插件是为了自动进行px->rem的转化,其余过程仍是由hotcss来进行。

postcss-px2rem的一些参数
require('postcss-pxtorem')({
  rootValue: 75,
  unitPrecision: 5,
  propList: ['*'],
  selectorBlackList: [],
  replace: true,
  mediaQuery: false,
  minPixelValue: 12
})
复制代码
  1. rootValue是根标签的font-size大小
  2. unitPrecision是转换成rem后的小数位数
  3. propList是须要转换的属性列表
  4. selectorBlackList则是一个对css选择器进行过滤的数组,好比你设置为['fs'],那例如fs-xl类名,里面有关px的样式将不被转换,这里也支持正则写法。
  5. minPixelValue能够设置小于多少尺寸将不会进行转换。
postcss.cofnig.js
const PostCssFlexBugFixes = require('postcss-flexbugs-fixes');
const autoprefixer = require('autoprefixer');
const px2rem = require('postcss-pxtorem');
const path = require('path');
const designWidth = 360; // 统一的视觉稿尺寸
const rootValue = (designWidth * 20) / 320; // root FontSize
module.exports = ctx => {
  const remInclude = [
    /\.rem\.less$/,
    /\.rem\.scss$/,
  ]; // 白名单,命中白名单则进行px2rem转换,包括.rem.less、.rem.scss结尾的文件
  const useRem = remInclude.some(x =>
    x.test(path.join(ctx.file.dirname, ctx.file.basename))
  );
  return {
    plugins: [
      PostCssFlexBugFixes, //修复某些浏览器的flex bug
      autoprefixer({
        // autprefixer
        browsers: ['>1%', 'last 4 versions', 'Firefox ESR', 'not ie < 9'],
        flexbox: 'no-2009'
      }),
      useRem &&
        px2rem({
          rootValue,
          propList: ['*'],
          minPixelValue: 1
        })
    ].filter(Boolean)
  };
};

复制代码

postcss-pxtorem完成对rem的转换,加上以前hotcssfont-size值的计算以及对viewport的初始化,咱们最终完成了移动端的适配方案。

其中设置白名单列表是为了兼容之前老版本使用hotcss时候的写法,在升级razzle完毕以后咱们后续的样式所有统一使用.rem.scss做为文件名后缀了,这样在样式文件中正常写px单位便可,postcss会自动为咱们进行转换。

hotcss缩放scale的坑

最近使用一些第三方插件(如echarts和视频播放器等)的时候,发现hotcss根据dpr给页面设置的meta[viewport]和插件之间会有坑。

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

因为第三方插件通常读取过一次页面的scale以后,后续scale的修改不会影响这些样式。

hotcss会从新根据dpr从新设置scale,这种状况下当css加载得比js慢的时候,就会致使页面样式会闪烁,而且第三方插件的样式都是根据最开始的scale进行布局的,所以在页面放大以后会显得很小。

不过这种状况通常在本地开发的时候才会出现,线上模式静态资源通常放入cdn,访问速度很快,在css加载速度快于js的时候就不会出现这个问题了。

不过为了在本地开发的时候方便观测样式,咱们须要在客户端执行js的时候从新将font-size设置回1.0。

document.documentElement.style.fontSize = `${(window.innerWidth * 20) /
    320 / window.devicePixelRatio}px`;
let viewportEl = document.querySelector('meta[name="viewport"]');
const content = `width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no`;
if (viewportEl) {
  viewportEl.setAttribute('content', content);
} else {
  viewportEl = document.createElement('meta');
  viewportEl.setAttribute('name', 'viewport');
  viewportEl.setAttribute('content', content);
  document.head.appendChild(viewportEl);
}
复制代码

这样整个页面上的第三方插件在本地调试的时候也是正常大小了~

相关文章
相关标签/搜索