面试官 | 说说移动端项目适配

前置

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

单元

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

《css 权威指南》对于 css 像素的注释:
面试官 | 说说移动端项目适配

基准值

html {
  font-size: 12px;
}
div {
  width: 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-width: 375px) and (max-device-width: 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.使用

  • 下载 js 文件通过内联的方式引入

  • 使用 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'] = {}))

缺陷

想学spark但是没有集群也没有数据?没关系,我来教你白嫖一个!

  • 建议字体巨细依然使用 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);
  width: 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-width: 320px) {
    font-size: 64px;
  }
  @media screen and (min-width: 540px) {
    font-size: 108px;
  }
}

.item {
  width: rem(50);
  font-size: rem(10);
}

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

原创文章,作者:28qn新闻网,如若转载,请注明出处:https://www.28qn.com/archives/11400.html