来这里找pc端最好的适配方法

646 阅读7分钟

1. 常见的适配方法

先说我用过的几种方法

自己计算vw vh

flexiable px2rem-loader

scss 或者 less的函数

postcss-px-to-viewport 插件

transform: scale()

自己根据postcss-px-to-viewport改写的postcss 插件

自己计算 vw vh就不在讲,接下来我们说下其他的几种方法

1.1 flexiable + px2rem-loader

1 、安装flexible和px2rem-loader

2、 在main.js 引入lib-flexible import 'lib-flexible' 3、 删除public/index.html 中的meta标记(非必要操作,如果不生效可以尝试下)

4、 修改lib-flexible/flexible.js(node_modules) 将屏幕最大宽度改为设备宽度*dpr, 当然也可以在 main.js 中粘贴flexiable.js文件,然后进行修改如下:

function refreshRem(){
  var width = docEl.getBoundingClientRect().width;
  if (width / dpr > 540) {
    // width = 540 * dpr;
    console.log('refreshRem')
    width = width * dpr;
  }
  var rem = width / 10;
  docEl.style.fontSize = rem + 'px';
  flexible.rem = win.rem = rem;
}

5、配置px2rem 配置px2rem 有两种方法 1、 放在css文件中的loader中,这里就不展示但是要注意放在postcss-loader之前 2、 放在vueconfig.js中,如下

chainWebpack: (config) => {
  // ... 
  config.module.rule('scss').oneOf('vue').use('px2rem-loader').loader('px2rem-loader')
  .before('postcss-loader') // this makes it work 
  .options({ remUnit: 192, remPrecision: 5 })
  .end()
}

优点:

缺点:

1.2 scss 或者 less的函数

可以在style中写一个函数文件这里我们以less为例,scss可以网上找下 取名为 adapter.less 文件内容

.vw(@name, @px) {
  @{name}: (@px / 1920) * 100vw
}
.vh(@name, @px) {
  @{name}: (@px / 1080) * 100vh
}

然后将此文件引入到 每个页面文件中,可以直接按照px写,比如

font-size:24px, left: 24px, top: 24px, margin-top:24px height:24px width: 24px
// 可以写成如下
vw(font-size,24) .vw(left,24) .vh(top,24) .vh(margin-top,24) .vw(width,24) .vh(height,24)

优点: 精确控制每个文件,按需手动引入

缺点:每个文件需要单独引入,并且写法比较麻烦,而且css中太多计算影响性能(这个不确定)

1.3 postcss-px-to-viewport 插件

1、安装,安装前要有postcss 应该都有这个

npm i postcss-px-to-viewport --save-dev

2、 使用

const path = require('path');
module.exports = () => {
  return {
    plugins: {
      autoprefixer: {},
      "postcss-px-to-viewport": {
        unitToConvert: "px", // 要转化的单位
        viewportWidth: 1920, // UI设计稿的宽度
        unitPrecision: 6, // 转换后的精度,即小数点位数
        propList: ["*"], // 指定转换的css属性的单位,*代表全部css属性的单位都进行转换
        viewportUnit: "vw", // 指定需要转换成的视窗单位,默认vw
        fontViewportUnit: "vw", // 指定字体需要转换成的视窗单位,默认vw
        selectorBlackList: ["wrap"], // 指定不转换为视窗单位的类名,
        minPixelValue: 1, // 默认值1,小于或等于1px则不进行转换
        mediaQuery: true, // 是否在媒体查询的css代码中也进行转换,默认false
        replace: true, // 是否转换后直接更换属性值
        exclude: [/node_modules/], // 设置忽略文件,用正则做目录名匹配
        include: [/\/src\/pages\/DigitalBoard\//],
        landscape: false // 是否处理横屏情况
      }
    }
  }
}

include 这个属性在文档中没有找到,但是发现配置后可以使用,在指定文件夹中生效了 然后就可以基本按照UI开发了

优点: 配置简单,开发方便

缺点:

1.4 transform: scale()

某些大厂使用的方法觉得还不错

1、 以vue 为例,命名为ScreenAdapter.vue的组件文件 这里要注意 getScale 返回的是[w, h] ,所以这里是横向根据1920来缩放,纵向是1080来缩放

<template>
  <div
    class="ScreenAdapter"
    :style="style"
  >
    <slot />
  </div>
</template>
<script>

export default {
  name: '',
  // 参数注入
  props: {
    width: {
      type: String,
      default: '1920'
    },
    height: {
      type: String,
      default: '1080'
    }
  },
  data () {
    return {
      style: {
        width: this.width + 'px',
        height: this.height + 'px',
        transform: 'scale(1) translate(-50%, -50%)'
      }
    }
  },
  computed: {
    contentHeight () {
      return document.documentElement.clientHeight
    }
  },
  mounted () {
    this.init()
  },
  watch: {
  },
  methods: {
    init () {
      this.setScale()
      window.onresize = this.Debounce(this.setScale, 1000)
    },
    Debounce: (fn, t) => {
      const delay = t || 500
      let timer
      return function () {
        const args = arguments
        if (timer) {
          clearTimeout(timer)
        }
        const context = this
        timer = setTimeout(() => {
          timer = null
          fn.apply(context, args)
        }, delay)
      }
    },
    getScale () {
      // document.documentElement.clientWidth、document.documentElement.clientHeight
      // console.log(window.innerHeight, window.screen.height, document.body.scrollHeight, document.body.clientHeight)
      const w = window.innerWidth / this.width
      const h = window.innerHeight / this.height
      // console.log(w, h, 'swsssssss')
      // return w < h ? w : h
      return [w, h]
    },
    setScale () {
      // this.style.transform = 'scale(' + this.getScale() + ') translate(-50%, -50%)'
      this.style.transform = 'scale(' + this.getScale()[0] + ',' + this.getScale()[1] + ') translate(-50%, -50%)'
      // console.log('任你千变万化,我都不会影响性能')
      // 这里的问题是 1. 文字会变形或者放大(css 有锯齿的属性)  2. canvas图大小可能不变(这个目前没有问题没有深入尝试)  3. 行内标签,这个做了下实验觉得没有问题
      // console.log(document.documentElement.clientHeight, window.innerHeight, window.screen.height, document.body.scrollHeight)
    }
  },
  beforeDestroy () {
    // 记得销毁 监听 window.resize 事件
  }
}

</script>

<style lang="scss" scoped>
.ScreenAdapter {
  transform-origin: 0 0;
  position: absolute;
  left: 50%;
  top: 50%;
  transition: 0.3s;
}
</style>

2、 将此组件包在我们搭建的页面上,如下是我的代码

<template>
  <Screen-Adapter >
    <div class="content">
      <div class="left-part">
        <!-- <div class="test" style="height: 108px; width:192px; background: red;"></div> -->

        <canvas id="canvasId"></canvas>
      </div>
      <div class="center"></div>
      <div class="right-part"></div>
    </div>
  </Screen-Adapter>
</template>

<script>
import ScreenAdapter from '@/components/ScreenAdapter'
export default {
  components: {
    ScreenAdapter
  },
  data () {
    return {
      // clientWidth: document.documentElement.clientWidth + '',
      // clientHeight: document.documentElement.clientHeight + ''
    }
  },
  computed: {
    clientWidth () {
      return document.body.clientWidth + ''
    },
    clientHeight () {
      return document.body.clientHeight + ''
    }
  },
  mounted () {
    this.drawCanvas()
  },
  methods: {
    drawCanvas () {
      let canvasId = document.getElementById('canvasId')
      console.log(canvasId, 'canvasIdcanvasId')
      // 获取canvas绘图上下文(canvas绘图的相关API)
      let ctx = canvasId.getContext('2d')
      console.log(ctx, 'ccccccccc')

      ctx.fillStyle = '#FF0000'
      ctx.fillRect(0, 0, 100, 100)
    }
  }
}

</script>

<style lang="scss" scoped>
.content {
  width: 100%;
  height: 100%;
  position: fixed;
  overflow: hidden;
  background-color: rgba(3, 17, 41, 1);
  .left-part {
    background: url('../../assets/images/DigitalBoard/left-bg.png') no-repeat center / contain;
    background-size: 100% 100%;
    width: 363px;
    height: 957px;
    position: absolute;
    top: 79px;
    bottom: 44px;
    left: 10px;
    // .test {
    //   height: 108px;
    //   width: 192px;
    //   line-height: 108px;
    //   margin-right: 10px;
    //   margin-top: 10px;
    //   padding-right: 10px;
    //   padding-bottom: 10px;
    //   background: red;
    // }
  }
  .right-part {
    background: url('../../assets/images/DigitalBoard/left-bg.png') no-repeat center / contain;
    background-size: 100% 100%;
    width: 363px;
    height: 957px;
    position: absolute;
    transform: rotateY(180deg);
    top: 79px;
    bottom: 44px;
    right: 10px;
  }
}

</style>

优点:测试后发现,除了特殊情况下的文字变形,只要是按照正常的1920x1080的比例来的基本没有问题,包括测试了canvas 行间样式 和 图片

缺点:

1.5 根据postcss-px-to-viewport改写的postcss 插件

1、 找到node_modules里的postcss-px-to-viewport 的 index.js文件,进行改写,其它配置和postcss-px-to-viewport的保持一致 文章先发出来,后续整理好插件后会补充到npm库里,然后会补上链接

2、改写的主要思想是,找出 高度和宽度对应的需要改写的属性,判断这些属性进行转换

heightList = ['top', 'bottom', 'height', 'margin-top', 'margin-bottom', 'padding-top', 'padding-bottom', 'line-height', 'max-height']
widthList = ['left', 'right', 'width', 'font-size', 'margin-left', 'margin-right', 'padding-left', 'padding-right', 'max-width']
'use strict';
var postcss = require('postcss');
var objectAssign = require('object-assign');
var { createPropListMatcher } = require('./src/prop-list-matcher');
var { getUnitRegexp } = require('./src/pixel-unit-regexp');

var defaults = {
  unitToConvert: 'px',
  viewportWidth: 1920,
  viewportHeight: 1080, // not now used; TODO: need for different units and math for different properties
  unitPrecision: 6,
  viewportUnit: 'vw',
  fontViewportUnit: 'vw',  // vmin is more suitable.
  selectorBlackList: [],
  propList: ['*'],
  minPixelValue: 1,
  mediaQuery: false,
  replace: true,
  landscape: false,
  landscapeUnit: 'vw',
  landscapeWidth: 568
};

module.exports = postcss.plugin('postcss-px-to-viewport', function (options) {
  var opts = objectAssign({}, defaults, options);
  var satisfyPropList = createPropListMatcher(opts.propList);
  var landscapeRules = [];
  return function (css) {
    css.walkRules(function (rule) {
      var file = rule.source && rule.source.input.file;
      // 找到对应的
      if (opts.exclude && file) {
        if (Object.prototype.toString.call(opts.exclude) === '[object RegExp]') {
          if (isExclude(opts.exclude, file)) return;
        } else if (Object.prototype.toString.call(opts.exclude) === '[object Array]') {
          for (let i = 0; i < opts.exclude.length; i++) {
            if (isExclude(opts.exclude[i], file)) return;
          }
        } else {
          throw new Error('options.exclude should be RegExp or Array.');
        }
      }

      // if (opts.landscape && !rule.parent.params) {
      //   var landscapeRule = rule.clone().removeAll();

      //   rule.walkDecls(function(decl) {
      //     if (decl.value.indexOf(opts.unitToConvert) === -1) return;
      //     if (!satisfyPropList(decl.prop)) return;

          
      //     landscapeRule.append(decl.clone({
      //       value: decl.value.replace(pxRegex, createPxReplace(opts, opts.landscapeUnit, opts.landscapeWidth))
      //     }));
      //   });
        
      //   if (landscapeRule.nodes.length > 0) {
      //     landscapeRules.push(landscapeRule); 
      //   }
      // }

      rule.walkDecls(function(decl, i) {
        if (decl.value.indexOf(opts.unitToConvert) === -1) return;
        if (!satisfyPropList(decl.prop)) return;
        
          // console.log(decl.prop, 'decl.propdecl.propdecl.prop')
          var heightList = ['top', 'bottom', 'height', 'margin-top', 'margin-bottom', 'padding-top', 'padding-bottom', 'line-height', 'max-height']
          var widthList = ['left', 'right', 'width', 'font-size', 'margin-left', 'margin-right', 'padding-left', 'padding-right', 'max-width']
          if (heightList.includes(decl.prop)) {
            // console.log(decl.prop, decl.value, 'decl.propdecl.propdecl.prop')
            if (decl.value && decl.value.indexOf('px' > -1)) {
              // decl.value = decl.value 取出数字然后 转换高度
              var value1 = decl.value && decl.value.substring(0,decl.value.length -2)
              var value2 = Number(value1)
              var parsedVal = toFixed((value2 / 1080 * 100), opts.unitPrecision)
              var lastVal = parsedVal === 0 ? '0' : parsedVal + 'vh'
              // console.log(lastVal, 'lastVallastVal')
              // return parsedVal === 0 ? '0' : parsedVal + 'vh'
              if (declarationExists(decl.parent, decl.prop, lastVal)) return;
        
              if (opts.replace) {
                decl.value = lastVal;
              } else {
                decl.parent.insertAfter(i, decl.clone({ value: lastVal }));
              }
            }

          }
          if (widthList.includes(decl.prop)) {
            if (decl.value && decl.value.indexOf('px' > -1)) {
              // decl.value = decl.value 取出数字然后 转换宽度
              var value1 = decl.value && decl.value.substring(0,decl.value.length -2)
              var value2 = Number(value1)
              var parsedVal = toFixed((value2 / 1920 * 100), opts.unitPrecision)
              var lastVal = parsedVal === 0 ? '0' : parsedVal + 'vw'
              // console.log(lastVal, 'lastVallastVal')
              // return parsedVal === 0 ? '0' : parsedVal + 'vh'
              if (declarationExists(decl.parent, decl.prop, lastVal)) return;
        
              if (opts.replace) {
                decl.value = lastVal;
              } else {
                decl.parent.insertAfter(i, decl.clone({ value: lastVal }));
              }
            }
          }
      });
    })
    
  }
})

function getUnit(prop, opts) {
  return prop.indexOf('font') === -1 ? opts.viewportUnit : opts.fontViewportUnit;
}

function createPxReplace(opts, viewportUnit, viewportSize) {
  return function (m, $1) {
    if (!$1) return m;
    var pixels = parseFloat($1);
    if (pixels <= opts.minPixelValue) return m;
    var parsedVal = toFixed((pixels / viewportSize * 100), opts.unitPrecision);
    return parsedVal === 0 ? '0' : parsedVal + viewportUnit;
  };
}

function toFixed(number, precision) {
  var multiplier = Math.pow(10, precision + 1),
    wholeNumber = Math.floor(number * multiplier);
  return Math.round(wholeNumber / 10) * 10 / multiplier;
}

function blacklistedSelector(blacklist, selector) {
  if (typeof selector !== 'string') return;
  return blacklist.some(function (regex) {
    if (typeof regex === 'string') return selector.indexOf(regex) !== -1;
    return selector.match(regex);
  });
}

function isExclude(reg, file) {
  if (Object.prototype.toString.call(reg) !== '[object RegExp]') {
    throw new Error('options.exclude should be RegExp.');
  }
  return file.match(reg) !== null;
}

function declarationExists(decls, prop, value) {
  return decls.some(function (decl) {
      return (decl.prop === prop && decl.value === value);
  });
}

function validateParams(params, mediaQuery) {
  return !params || (params && mediaQuery);
}

2. 自己开发面对的问题

1、 其实在开发过程中,我们本地页面都是有标签页和书签栏的,然后用户的页面的展示也不会刚进来就是全屏的,为了兼容这种情况,所以才会有后边那两种方式。

2、 我们部门的UI给的高度都是固定的 px,所以我们开发起来不是很爽

3、 使用rem 或者 postcss-px-to-viewPort 都是以宽度为准,然而我们开发的实际高度是929px 对于一些图片或者UI比较紧密的情况下高度的转化显得也很重要

3. 测试

一般情况下很少人想到测试这几种方法的通过性,说实话,作为一个优秀的开发者,要在开发之前就要想到使用某种方法适合什么情况和不适合什么情况。

这里将从 div canvas ecahrt 图片 背景图 文字 这几个方面来看通过情况 通过:√ 未通过:x 未测试:o (这里本应该是个表格, 表示想偷懒)

ecahrt 没有测试,但是在新版的echart基本都有resize方法,记得监听调用

canvas 大部分没有通过,大家可以提出自己的解决方法和意见

3.1 flexiable px2rem-loader

div (√) canvas (x) ecahrt (o) 图片 (√) 背景图 (√) 文字 (√)

3.1 scss 或者 less的函数

div (√) canvas (x) ecahrt (o) 图片 (√) 背景图 (√) 文字 (√)

3.1 postcss-px-to-viewport 插件

div (√) canvas (x) ecahrt (o) 图片 (√) 背景图 (√) 文字 (√)

3.1 transform: scale()

div (√) canvas (√) ecahrt (o) 图片 (√) 背景图 (√) 文字 (x)

3.1 根据postcss-px-to-viewport改写的postcss 插件

div (√) canvas (x) ecahrt (o) 图片 (√) 背景图 (√) 文字 (√)

4. 插件内容讲解 如何开发 postcss 插件

后续会继续写

5. 写在最后

本文是自己整理的文章,转载记得说明出处

如果对文章内容有什么意见或者建议欢迎讨论,我会尽快回复和改正!