前端 rem适配方案
rem方案
原理
rem是相对长度单位,rem方案中的样式设计为相对于根元素
font-size
计算值的倍数。根据 屏幕宽度 设置html
标签的font-size
,在布局时使用 rem 单位布局,达到自适应的目的,是 弹性布局 的一种实现方式。
实现过程: 首先获取文档根元素和设备
dpr
,设置 rem,在html
文档加载和解析完成后调整body
字体大小; 在页面缩放 / 回退 / 前进的时候, 获取元素的内部宽度 (不包括垂直滚动条,边框和外边距),重新调整 rem 大小。
实现方法:用 css 处理器或 npm 包将页面 css 样式中的
px
自动转换成 rem。在整个 flexible 适配方案中,文本使用px
作为单位,使用[data-dpr]
属性来区分不同dpr
下的文本字号。由于手机浏览器对字体显示最小是8px
,因此对于小尺寸文字需要采用px
为单位,防止通过 rem 转化后出现显示问题。手机淘宝 中的字体使用px
为单位,腾讯新闻中的字体使用rem
为单位。
贴上 源码 分析
(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) {
//当meta中viewport的标签设置了scale时,将根据scale手动设置dpr
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) {
//当meta中flexible的标签存在时,据此设置dpr
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) {
//根据js获取到的devicePixelRatio设置dpr及scale,scale是dpr的倒数
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和3倍方案
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);
//文本字号不建议使用rem,flexible适配方案中,文本使用px作为单位,使用[data-dpr]属性来区分不同dpr下的文本字号
if (!metaEl) {
//添加meta标签,设置name为viewport,content根据scale设置缩放比(默认、最大、最小缩放比)
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(){
//更新rem值
var width = docEl.getBoundingClientRect().width;
if (width / dpr > 540) {
width = 540 * dpr;
}
var rem = width / 10; //1rem = viewWidth / 10
docEl.style.fontSize = rem + 'px';
flexible.rem = win.rem = rem;
}
//resize与pageshow延时300ms触发refreshRem(),使用防抖函数,防止事件被高频触发可能引起性能问题
win.addEventListener('resize', function() {
clearTimeout(tid);
tid = setTimeout(refreshRem, 300);
}, false);
win.addEventListener('pageshow', function(e) {
//当一条会话历史纪录被执行的时候触发事件,包括后退/前进按钮,同时会在onload页面触发后初始化页面时触发
if (e.persisted) {//表示网页是否来自缓存
clearTimeout(tid);
tid = setTimeout(refreshRem, 300);
}
}, false);
//在html文档加载和解析完成后设置body元素字体大小
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);
}
//浏览器有最小字体限制,css在pc上font-size是12px(移动端最小是8px), 也就是css像素是12,其DPR为1,在移动端dpr有可能为2和3,为了保证字体不变小,需要用12*dpr进行换算。
refreshRem();
//实现rem与px相互转换
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'] = {}));
优势
兼容性好
ios: 6.1系统以上都支持
android: 2.1系统以上都支持
大部分主流浏览器都支持

1.png
- 相较于之前的静态布局和百分比方案,页面不会因为伸缩发生变形,自适应效果更佳。
不足
- 不是纯css移动适配方案,需要引入js脚本 在头部内嵌一段
js
脚本 监听分辨率的变化来动态改变根元素的字体大小,css
样式和js
代码有一定 耦合性,并且必须将改变font-size
的代码放在 css 样式之前。
...- 小数像素问题,浏览器渲染最小的单位是像素,元素根据屏幕宽度自适应,通过 rem 计算后可能会出现小数像素,浏览器会对这部分小数四舍五入,按照整数渲染。浏览器在渲染时所做的摄入处理只是应用在元素的尺寸渲染上,其真实占据的空间依旧是原始大小。也就是说如果一个元素尺寸是
0.625px
,那么其渲染尺寸应该是1px
,空出的0.375px
空间由其临近的元素填充;同样道理,如果一个元素尺寸是0.375px
,其渲染尺寸就应该是0
,但是其会占据临近元素0.375px
的空间。会导致:缩放到低于1px
的元素时隐时现(解决办法:指定最小转换像素,对于比较小的像素,不转换为 rem 或 vw);两个同样宽度的元素因为各自周围的元素宽度不同,导致两元素相差1px
;宽高相同的正方形,长宽不等了;border-radius: 50%
画的圆不圆。
...- Android 浏览器下 line-height 垂直居中偏离的问题。常用的垂直居中方式就是使用line-height,这种方法在Android设备下并不能完全居中。
...- cursor: pointer 元素点击背景变色的问题,对添加了cursor:pointer属性的元素,在移动端点击时,背景会高亮。为元素添加
tag-highlight-color:transparent
属性可以隐藏背景高亮。
作者:jluemmmm
链接:https://www.jianshu.com/p/2c33921d5a68
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧