前言

移动端的崛起,给了我们前端更大的舞台,与此同时,也给我们带来了一系列头疼的问题,移动端适配就是其中之一,目前市面上最常用的方案即是REM适配。

为什么说她是一个磨人的小妖精?因为她确实让人又爱又恨,灵活的自适应布局再搭配上css单位转换工具,让人爱不释手;另一方面,由于移动端的机型和表现千奇百怪,想要达到完美的兼容又让人头疼。

即使如此,依然阻止不了笔者对于她的痴迷。本文将会围绕REM适配这一话题进行讨论,同时也会将笔者个人的经验以及自己目前在用的一套代码分享给大家。另外,如今移动端的兼容性越来越好,因此衍生出了一些其他的适配方案,这点不在本文的讨论范围之内。

实例解析

全局变量

const docEl = document.documentElement
const metaEl = document.querySelector('meta[name="viewport"]')const maxWidth = window.__MAX_WIDTH__ || 750
const divPart = window.__DIV_PART__ || 15
const bodySize = window.__BODY_SIZE__ || 12let scale = 1
let dpr = 1
let timer = null
复制代码
  • metaEl:抓取现有viewport,以支持使用者自定义页面实际缩放比例,通过设置viewport可以实现视觉上的实际物理像素。例如initial-scale=0.5,即二倍屏,假设根节点的font-size=100px,那么0.01rem就是物理像素1px;而initial-scale=1.0,虽然在css单位中,0.01rem=1px,但我们知道,在二倍屏中,1px实际有4个物理像素。
  • maxWidth:UI稿宽度,一般以iphone6为基准,即750。
  • divPart:将设备宽度划分为多少份,上述代码中,750/15=50,意思是750宽度的屏幕,1rem=50px,划分多少份实际上没有固定规定,看个人习惯。
  • bodySize:初始化时,设置body的字体大小。
  • scale、dpr分别是页面缩放比例、设备像素比。

初始化设置

if (metaEl) {console.warn('根据已有的meta标签来设置缩放比例')const match = metaEl.getAttribute('content').match(/initial-scale=([\d.]+)/)if (match) {scale = parseFloat(match[1])dpr = parseInt(1 / scale)}
} else {if (window.navigator.appVersion.match(/iphone/gi)) {dpr = parseInt(window.devicePixelRatio) || 1scale = 1 / dpr}const newMetaEl = document.createElement('meta')newMetaEl.setAttribute('name', 'viewport')newMetaEl.setAttribute('content', `width=device-width, initial-scale=${scale}, maximum-scale=${scale}, minimum-scale=${scale}, user-scalable=no`)docEl.firstElementChild.appendChild(newMetaEl)
}// 设置根节点dpr
docEl.setAttribute('data-dpr', dpr)
复制代码

这里要重点将一下为什么要区分安卓和IOS设备,很多人可能会说因为IOS有多倍屏。实际上,安卓也有多倍屏,那为什么我们不考虑呢?

  • 有些安卓机的设备像素比很奇怪,比如2.5、3.8等一些奇怪的数字;
  • 部分安卓机表现很奇怪,比如页面宽度比屏幕宽度多一点,出现横向滚动条(具体原因不详,已排除所有css干扰),兼容起来成本太高。

核心代码

function bodyLoaded (cb) {if (document.body) {cb && cb()} else {document.addEventListener('DOMContentLoaded', function () {cb && cb()}, false)}
}// 窗口宽度改变时,刷新rem
function refreshRem () {let width = docEl.clientWidthif (width / dpr > maxWidth) {width = maxWidth * dpr}// 设置根节点font-sizewindow.remUnit = width / divPartdocEl.style.fontSize = window.remUnit + 'px'bodyLoaded(() => {// 测试rem的准确性,如果和预期不一样,则进行缩放let noEl = document.createElement('div')noEl.style.width = '1rem'noEl.style.height = '0'document.body.appendChild(noEl)let rate = noEl.clientWidth / window.remUnitif (Math.abs(rate - 1) >= 0.01) {docEl.style.fontSize = (window.remUnit / rate) + 'px'}document.body.removeChild(noEl)})
}// 初始化
refreshRem()bodyLoaded(() => {document.body.style.fontSize = bodySize * dpr + 'px'document.body.style.maxWidth = maxWidth * dpr + 'px'
})
复制代码

refreshRem函数是整个rem适配的核心,每次需要更新都会调用此函数,我们还限定了页面的最大宽度,可以保证在pc端打开也能看到不错的视觉效果。

但是有一部分的安卓机,1rem并不等于根节点的font-size,举个例子:html的font-size=20px,正常情况下1rem也应该是20px,但在部分机型中,它可能是22px或18px等等(笔者怀疑上文中提到的页面宽度溢出也是这个问题)。因此,笔者加上了bodyLoaded这段代码,在rem设置完成后,再与实际视觉上的1rem进行比较,若偏差超过1%,则认为需要重新定义rem,这样就能100%保证1rem就是我们期望的大小。

页面宽度监听

window.addEventListener('resize', function () {clearTimeout(timer)timer = setTimeout(refreshRem, 200)
}, false)// window.addEventListener('pageshow', function (e) {//   if (e.persisted) {//     refreshRem()
//   }
// }, false)
复制代码

这段代码用于监听resize事件,以此来重新计算根节点的font-size,定时器用来防止频繁计算(实际上在手机中,也不会有频繁触发resize的机会,因此定时器也可以不加)。有些读者可能会问题,为什么不监听横竖屏事件(onorientationchange),其实没有必要,横竖屏切换本质也是resize的一种,我们已经监听了resize事件,这里就没有必要再次监听了。

那注释掉的这段代码是什么意思呢?它是用来监听浏览器返回,但是这段代码在iPhone8、iPhoneX上会有问题,在返回的时候,我们拿到的document.documentElement.clientWidth是其实际的大小(没有乘上设备像素比),因此整个页面布局都乱了。笔者经过深思熟虑,决定删掉这段代码,因为在返回的时候,会保留和离开时一摸一样的状态,没有必要重新再计算一遍。

工具函数

window.px2rem = function (d) {let val = parseFloat(d) / window.remUnitif (typeof d === 'string' && d.match(/px$/)) {val += 'rem'}return val
}window.rem2px = function (d) {let val = parseFloat(d) * window.remUnitif (typeof d === 'string' && d.match(/rem$/)) {val += 'px'}return val
}
复制代码

暴露全局函数,方便使用js来控制尺寸大小。

CSS重置样式

篇幅所限,样式代码就不在这里贴了,感兴趣可以在这里看:reset.css

总结

这一套rem适配代码是笔者日常开发中总结提炼出来,不能说是100%完美,但是也足够适配市面上的主流机型了。再配合构建工具,自动转换为rem单位,省心又省力。

最后推荐一个好用的全局构建工具fle-cli,帮你从复杂繁琐的构建配置中解放出来。

本文源码地址:github.com/ansenhuang/…

REM,你这磨人的小妖精!相关推荐

  1. 磨人的小妖精 别让我遇到你

    我叫颈椎病,今年有5000多岁了,我的子孙散布在世界各地,每当我看到人类颈背痛苦.上肢无力.手指发麻.下肢乏力.行走艰难.头晕.厌恶.吐逆,乃至视物含糊.心动过速及吞咽艰难的时候我都很开心.因为任何医 ...

  2. 「磨人的小妖精」JavaWeb如何学习?先肝了这套教程

    都说一入Java深似海,从此代码是爱人,但是学习的过程却从来都不轻松. 当下,越来越多的互联网企业,招聘Java工程师时,明确要求需熟练掌握JavaWeb技术.作为衔接前后端的重要一环,JavaWeb ...

  3. theano 这磨人的小妖精

    Theano升级到1.0后,在import theano 时总会出现如下提醒,在没升级之前之前是没有的. >> import theano Using cuDNN version 5110 ...

  4. 你这磨人的小妖精——选中文本并标注的实现过程

    需求背景:给现有的页面加上标注解读功标注一段文本的功能:选中一段文字,在光标结束位置旁边弹出小tips,有一个按钮表示添加解读.添加了解读后,那段文字高亮(加上下划线).此后每次页面loaded,被加 ...

  5. 聊聊「游戏」这个磨人的小妖精

    引言:此文是我的「读书会」里一份优秀作业,刘昊男同学作品.对于游戏,我记得之前看过「六原则」,不妨放在开头: 研究不熟悉的东西使之变为熟悉的东西: 将熟悉的东西作有规律的重复: 在重复的过程中尽可能作 ...

  6. 烦人的“小妖精”:浅谈小红点的设计

    导语 随着各类App的业务功能不断丰富,小红点已经成为互联网从业者们最常使用的运营工具之一,每当有新的运营内容或新功能上线,大家的做法往往是"在那放个红点就行了".然而简单粗暴的形 ...

  7. 递归这个磨机的小妖精

    神奇的递归 费了好大的劲才对递归算法有了点浅显的理解,在这与大家分享一下典型例子的代码. 九九乘法表 经典循环算法: for i in range(1,10):for j in range(1,i+1 ...

  8. 蜜糖?砒霜? 区块链真skr磨人小妖精!

    哪里有天才,我是把别人喝咖啡的工夫都用在工作上的.──鲁迅  中国软件网每日精选文集 "大家都在讨论区块链, 但我还是搞不懂它是什么, 和我又有什么关系." 今天各位"吃 ...

  9. Tensflow版本升级就是磨人的妖精——regular能用吗?

    磨人的妖精,版本带来的伤筋动骨,有发展就有版本升级,自己模块有方法变化.不同模块之间还有相互依赖变化,coding中有1/3的时间都是在解决版本升级后新特性学习问题解决,1/3是在debug,真正写核 ...

最新文章

  1. Spring Cloud(九)高可用的分布式配置中心 Spring Cloud Config 集成 Eureka 服务
  2. 漫话:如何给女朋友解释什么是云计算?
  3. 搜索(DFS)---好友关系的连通分量数目
  4. 爬虫:通过滑动或者点触验证码的方法及实现(点触+滑动)
  5. SpringBoot 项目使用 SLF4J+logback 进行日志记录,来增强可维护性
  6. 【Python基础】惊叹,Pyecharts绘制饼图原来可以如此漂亮!
  7. Linux磁盘管理与文件系统(实验详解,一看就懂)
  8. 软件构造 第五章第一节 可复用性的度量、形态和外部观察
  9. php rpc调用,PHP远程调用, 为什么需要使用RPC
  10. 计算机视觉论文-2021-06-01
  11. signature=caf1e99ac9ac407b9c928d57b6757f28,恶意软件分析 URL链接扫描 免费在线病毒分析平台 | 魔盾安全分析...
  12. 江阴:智慧融入城市血液,打造创新发展新名片
  13. element-ui表格表头内容 限制不换行
  14. 微信小程序开发(4)--发布评论
  15. PDPS软件:带颜色的机器人工作站二维布局图JT格式文件转换方法
  16. RabbitMQ 安装与web后台管理界面开启
  17. 红警ol服务器维护中1003,【图片】红警ol心灵终结3单位全面解析_红警ol吧_百度贴吧...
  18. 【PWA】响应式开发
  19. 首席谈判官的定义和职责
  20. 2007年河北省高校计算机一级考试大纲及心得

热门文章

  1. Android学习之SQLite
  2. python一行行写文件_python一行行写文件-女性时尚流行美容健康娱乐mv-ida网
  3. cstring判断是否包含子串_leetcode76. 最小覆盖子串
  4. java强制执行方法_java – 在多台机器上强制执行单一速率限制的好方法是什么?...
  5. 只有一个显示器但是显示两个显示器_小米34寸曲面显示器深度体验 办公体验极佳 但是还有个大弱点...
  6. checksum命令 linux_关于Linux操作系统的一些命令是什么?
  7. signature=42f2498bc8fd40eb63568566c79f37e7,新思维综合英语Ⅰ学习指导
  8. 微服务feignclient_微服务-(声明式调用feign)
  9. 树莓派i2c python_树莓派2 python i2cPython中chr、unichr、ord字符函数之间的对比
  10. hashmap为什么线程不安全_面试官:你说 HashMap 线程不安全,它为啥不安全呢?...