• 移动端项目适配


    前置

    希望通过这篇文章帮助你很好的适配移动端项目,如有不足,恳请指点一二!

    单位

    • 分辨率: 单位面积显示像素的数量,和 css 无关
    • DPI:图像每英寸长度内的像素点数(1 英尺=30.48 厘米)
    • css 的 px: 96 DPI 的单像素的物理大小
    • 物理像素:在由一个数字序列表示的图像中的一个最小单位
    • dpr: 一个 CSS 像素的大小相对于一个物理像素的大小的比值
    • rem: 1rem = 根元素的字体大小 * 1

    《css 权威指南》对于 css 像素的解释:

    基准值

    html {
      font-size: 12px;
    }
    div {
       2rem;
    }
    

    这里根元素的字体大小 12px,称之为基准值。这时 div 的 width 为 2rem = 12px*2 = 24px. 通常设计稿都以 px 为单位,如果一个块的高度为 50px,转换为 rem 非常简单,即 50px/12px rem。如果在不同屏幕下设置不同基准值,就能实现简单的伸缩适配的目的了。

    设计搞

    移动端设计稿通常以 iphone6 屏幕为基准。iphone6 屏幕宽度为 375px,在进行适配时通常以 37.5px 为基准值,为什么不直接以 375px 为基准值呢?因为这个值太大了,所以除以 10。为什么要以屏幕宽度为基准值呢?为了方便适配,在 iphone4/5 下以 320px / 10 = 32px 作为基准值就好了,其他设备类比。

    如何做

    在不同屏幕下设置不同基准值,就能实现缩放适配的目的。以下几种方法都能实现。

    • 利用 css 媒体查询(Media Queries)
    @media (min-device- 375px) and (max-device- 667px) and (-webkit-min-device-pixel-ratio: 2) {
      html {
        font-size: 37.5px;
      }
    }
    /* ... */
    
    • 利用简易的 JavaScript
    document.getElementsByTagName('html')[0].style.fontSize = window.innerWidth / 10 + 'px'
    
    • 使用 flexible.js,这是当前常用的方式

    flexible

    amfe-flexible

    1.使用

    • 通过内联的方式引入

    • 使用 npm: npm i amfe-flexible

      2.如果使用 webpack 构建项目,使用 px2rem-loader。这样设计稿上元素是多少 px 就写多少 px 。注意:要将基准值设置为 37.5 的二倍 75,因为设计稿的宽度一般是 iphone6 宽度的二倍。loader 配置:

    {
      loader: 'px2rem-loader',
      options: {
        remUni: 75,
        remPrecision: 8
      }
    }
    

    如果直接引入的 js,可以使用 vscode 扩展 px2rem,同样你需要打开 vscode settings 并设置基准值。书写时设计稿上元素是多少 px 就写多少 px,插件会帮你在书写时自动转换成对应的 rem。

    "px2rem.rootFontSize": 75,
    "px2rem.fixedDigits": 6,
    
    源码解释
    ;(function flexible(window, document) {
      var docEl = document.documentElement
      var dpr = window.devicePixelRatio || 1
      // Document.documentElement: 是一个会返回文档对象(document)的根元素的只读属性(如HTML文档的 <html> 元素)。
      // window.devicePixelRatio(dpr): 返回当前显示设备的物理像素分辨率与CSS像素分辨率的比值。该值也可以被解释为像素大小的比例:即一个CSS像素的大小相对于一个物理像素的大小的比值。
    
      // 调整 body 字体大小
      function setBodyFontSize() {
        if (document.body) {
          document.body.style.fontSize = 12 * dpr + 'px'
        } else {
          document.addEventListener('DOMContentLoaded', setBodyFontSize)
          // DOMContentLoaded:当纯HTML被完全加载以及解析时,DOMContentLoaded 事件会被触发
        }
      }
      setBodyFontSize()
    
      // 设置基准值
      // 1rem = clientWidth / 10
      function setRemUnit() {
        var rem = docEl.clientWidth / 10
        docEl.style.fontSize = rem + 'px'
      }
      setRemUnit()
    
      // 调整页面大小时重置 rem
      window.addEventListener('resize', setRemUnit)
      window.addEventListener('pageshow', function (e) {
        // pageshow:当一条会话历史记录被执行的时候将会触发页面显示(pageshow)事件。(这包括了后退/前进按钮操作,同时也会在onload 事件触发后初始化页面时触发)
        if (e.persisted) {
          // persisted:网页是否来自缓存
          setRemUnit()
        }
      })
    
      // 检测 0.5px 支持
      if (dpr >= 2) {
        var fakeBody = document.createElement('body')
        var testElement = document.createElement('div')
        testElement.style.border = '.5px solid transparent'
        // transparent是全透明黑色(black)的速记法,即一个类似rgba(0,0,0,0)这样的值。
        fakeBody.appendChild(testElement)
        docEl.appendChild(fakeBody)
        if (testElement.offsetHeight === 1) {
          docEl.classList.add('hairlines')
        }
        docEl.removeChild(fakeBody)
      }
    })(window, document)
    

    lib-flxible

    lib-flxible 是更为完善的版本。

    npm i lib-flxible
    
    源码
    ;(function (win, lib) {
      var doc = win.document
      var docEl = doc.documentElement
      var metaEl = doc.querySelector('meta[name="viewport"]')
      var flexibleEl = doc.querySelector('meta[name="flexible"]')
      var dpr = 0
      var scale = 0
      var tid
      var flexible = lib.flexible || (lib.flexible = {})
    
      if (metaEl) {
        console.warn('将根据已有的meta标签来设置缩放比例')
        var match = metaEl.getAttribute('content').match(/initial-scale=([d.]+)/)
        if (match) {
          scale = parseFloat(match[1])
          dpr = parseInt(1 / scale)
        }
      } else if (flexibleEl) {
        var content = flexibleEl.getAttribute('content')
        if (content) {
          var initialDpr = content.match(/initial-dpr=([d.]+)/)
          var maximumDpr = content.match(/maximum-dpr=([d.]+)/)
          if (initialDpr) {
            dpr = parseFloat(initialDpr[1])
            scale = parseFloat((1 / dpr).toFixed(2))
          }
          if (maximumDpr) {
            dpr = parseFloat(maximumDpr[1])
            scale = parseFloat((1 / dpr).toFixed(2))
          }
        }
      }
    
      if (!dpr && !scale) {
        var isAndroid = win.navigator.appVersion.match(/android/gi)
        var isIPhone = win.navigator.appVersion.match(/iphone/gi)
        var devicePixelRatio = win.devicePixelRatio
        if (isIPhone) {
          // iOS下,对于2和3的屏,用2倍的方案,其余的用1倍方案
          if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {
            dpr = 3
          } else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)) {
            dpr = 2
          } else {
            dpr = 1
          }
        } else {
          // 其他设备下,仍旧使用1倍的方案
          dpr = 1
        }
        scale = 1 / dpr
      }
    
      docEl.setAttribute('data-dpr', dpr)
      if (!metaEl) {
        metaEl = doc.createElement('meta')
        metaEl.setAttribute('name', 'viewport')
        metaEl.setAttribute('content', 'initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no')
        if (docEl.firstElementChild) {
          docEl.firstElementChild.appendChild(metaEl)
        } else {
          var wrap = doc.createElement('div')
          wrap.appendChild(metaEl)
          doc.write(wrap.innerHTML)
        }
      }
    
      function refreshRem() {
        var width = docEl.getBoundingClientRect().width
        if (width / dpr > 540) {
          width = 540 * dpr
        }
        var rem = width / 10
        docEl.style.fontSize = rem + 'px'
        flexible.rem = win.rem = rem
      }
    
      win.addEventListener(
        'resize',
        function () {
          clearTimeout(tid)
          tid = setTimeout(refreshRem, 300)
        },
        false
      )
      win.addEventListener(
        'pageshow',
        function (e) {
          if (e.persisted) {
            clearTimeout(tid)
            tid = setTimeout(refreshRem, 300)
          }
        },
        false
      )
    
      if (doc.readyState === 'complete') {
        doc.body.style.fontSize = 12 * dpr + 'px'
      } else {
        doc.addEventListener(
          'DOMContentLoaded',
          function (e) {
            doc.body.style.fontSize = 12 * dpr + 'px'
          },
          false
        )
      }
    
      refreshRem()
    
      flexible.dpr = win.dpr = dpr
      flexible.refreshRem = refreshRem
      flexible.rem2px = function (d) {
        var val = parseFloat(d) * this.rem
        if (typeof d === 'string' && d.match(/rem$/)) {
          val += 'px'
        }
        return val
      }
      flexible.px2rem = function (d) {
        var val = parseFloat(d) / this.rem
        if (typeof d === 'string' && d.match(/px$/)) {
          val += 'rem'
        }
        return val
      }
    })(window, window['lib'] || (window['lib'] = {}))
    

    缺陷

    • 建议字体大小依然使用 px,由于使用 rem 将导致在不同移动设备中字体大小不同,可能出现 13px 和 15px 这样奇怪的字体尺寸
    • 难以处理点阵字体,点阵字体也叫位图字体。由于位图的缘故,点阵字体很难进行缩放,特定的点阵字体只能清晰地显示在相应的字号下,否则文字只被强行放大而失真字形,产生成马赛克式的锯齿边缘。

    那么如何适配字体呢?当你不使用任何 css 预处理器时这样做:

    .item {
      font-size: 12px;
    }
    
    [data-dpr='2'] .item {
      font-size: 24px;
    }
    
    [data-dpr='3'] .item {
      font-size: 36px;
    }
    

    如果使用 scss,其他 css 预处理器类比即可:

    // 定义一个混合宏(或者使用函数),在设置字体大小时直接引入即可
    @mixin font-dpr($font-size) {
      font-size: $font-size;
    
      [data-dpr='2'] & {
        font-size: $font-size * 2;
      }
    
      [data-dpr='3'] & {
        font-size: $font-size * 3;
      }
    }
    
    .item {
      @include font-dpr(16px);
    }
    

    除此之外,可能会在移动端项目中出现很少见的大标题,完全可以直接使用 rem,因为我们希望这样的大标题也能跟随不同的设备进行缩放。而不是使用 px,在较小尺寸的设备由于标题过大而显示不全(处理成...)或者换行。

    使用 viewport

    使用 flexible.js 我们需要在项目中嵌入 JavaScript,让我们尝试一种新的方式。

    视口单位

    • vw : 1vw 等于视口宽度的 1%
    • vh : 1vh 等于视口高度的 1%
    • vmin : 选取 vw 和 vh 中最小的那个
    • vmax : 选取 vw 和 vh 中最大的那个

    看到这里你可能对 vmin 和 vmax 有些疑问,这里给出一小段解释你会豁然开朗:使用 vw、wh 设置字体大小在竖屏和横屏状态下显示的字体大小是不一样的,使用 vmin 和 vmax 使得文字大小在横竖屏下保持一致。

    注:IE9 仅支持使用 vm 代替 vmin,IE 6-11 均不支持 vmax。

    第一种方式

    上文已经说过设计稿通常以 iphone6(375) 的尺寸作为基准。如果使用 scss:

    @function vw($px) {
      @return ($px / 375) * 100vw;
    }
    

    在使用时仅将设计稿上对应的 px 传入函数即可:

    .item {
      padding: vw(15) vw(10) vw(10);
       vw(40);
      font-size: vw(10);
    }
    

    Tip: 不用设备的宽度变化会导致自动伸缩,伸缩布局就实现了。

    第二种方式

    具体思路:给根元素打字体大小使用单位 vw, 其他地方使用 rem。设备宽度变化 -> vw 实际渲染出来的大小改变 = 根元素字体大小改变 -> rem 实际渲染出来的大小变换 = 全局根据设备宽度缩放。看到这里你又会有疑问了,vw 本身就能缩放了,何必再绕上一圈?下面有一个例子,假设设计稿依然以 iphone6 尺寸作为基准:

    例子
    @function rem($px) {
      @return ($px / 75) * 1rem;
    }
    
    html {
      font-size: (75 / (750 / 2)) * 100vw;
      @media screen and (max- 320px) {
        font-size: 64px;
      }
      @media screen and (min- 540px) {
        font-size: 108px;
      }
    }
    
    .item {
       rem(50);
      font-size: rem(10);
    }
    

    看完这段代码你就明白了,原来我们利用 rem 只会根据根元素字体大小进行缩放这一特点,给根元素通过媒体查询限制其字体的最大值与最小值,就能在全局实现修复一些元素的过大或过小产生的瑕疵。同时,这样做还有一个好处,它能使原来使用 flexible.js 的项目轻易地迁移到 vw+rem。

  • 相关阅读:
    python网络爬虫(1)静态网页抓取
    博弈论的一些例子
    虚机Linux最小系统下安装图形界面,与yum配置
    主成分分析法详解(PCA)
    吴恩达机器学习私人总结(3)神经网络
    HttpServletRequest & HttpServletResponse
    编译原理复习
    Http协议 & Servlet
    分享一下HttpWatch 10 pro 带lic激活文件
    基于Storm的WordCount
  • 原文地址:https://www.cnblogs.com/guangzan/p/12945714.html
Copyright © 2020-2023  润新知