移动端rem适配方案实战踩坑总结
核心代码就这么几行
先上核心代码,这个rem适配方案我已经用了好几年,亲测有效。整个方案的核心就是这几十行JavaScript代码:
(function() {
var docEl = document.documentElement;
var clientWidth = docEl.clientWidth;
if (clientWidth >= 750) {
// 设计稿宽度750px,按照iPhone X的标准来计算
docEl.style.fontSize = '100px';
} else {
// 动态计算字体大小
docEl.style.fontSize = 100 * (clientWidth / 750) + 'px';
}
// 监听屏幕旋转和resize事件
window.addEventListener('resize', function() {
var newClientWidth = docEl.clientWidth;
if (newClientWidth >= 750) {
docEl.style.fontSize = '100px';
} else {
docEl.style.fontSize = 100 * (newClientWidth / 750) + 'px';
}
});
// 针对移动端设备做额外处理
window.addEventListener('orientationchange', function() {
setTimeout(function() {
var newClientWidth = docEl.clientWidth;
if (newClientWidth >= 750) {
docEl.style.fontSize = '100px';
} else {
docEl.style.fontSize = 100 * (newClientWidth / 750) + 'px';
}
}, 100); // 延迟执行,确保屏幕尺寸已更新
});
})();
这段代码的作用是动态设置html根元素的font-size,让1rem始终等于设计稿的1/7.5。比如设计稿是750px宽,那1rem就等于100px。这样在写CSS的时候,设计师量出来的像素值除以100就能得到对应的rem值。
这个场景最好用
rem方案特别适合移动端H5页面开发,尤其是需要适配各种尺寸手机的那种。我之前做过一个电商活动页,从iPhone SE到iPhone 14 Pro Max都能完美显示,完全不用关心具体的像素值。
CSS代码长这样:
/* 基于750px设计稿 */
.container {
width: 7.5rem; /* 750px */
height: 1.33rem; /* 133px */
margin: 0.2rem auto; /* 20px */
padding: 0.3rem; /* 30px */
}
.header {
height: 1.2rem; /* 120px */
line-height: 1.2rem; /* 120px */
font-size: 0.36rem; /* 36px */
background-color: #ff6b35;
}
.content {
padding: 0.24rem; /* 24px */
font-size: 0.28rem; /* 28px */
}
你看,这里的数值都是直接从设计稿量出来除以100的结果。如果设计稿是375px宽,那就除以37.5,以此类推。不过我个人还是推荐750px的设计稿尺寸,因为现在高分辨率手机太多了,750px能保证足够的精度。
踩坑提醒:这三点一定注意
用rem方案最容易踩的坑就是viewport设置。很多人直接复制网上的meta标签,结果导致适配出问题:
<!-- 错误做法 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<!-- 正确做法 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
记住,initial-scale一定要设置为1,不能让浏览器自动缩放。我之前就是因为这个参数没设置对,导致在某些Android机型上显示异常,折腾了整整一天才发现问题。
第二个坑是dpr处理。有些高端机dpr是3,如果不处理的话可能会出现字体模糊的问题:
// 改进版的rem设置,考虑了dpr因素
(function() {
var dpr = window.devicePixelRatio || 1;
var scale = 1 / dpr;
var metaEl = document.querySelector('meta[name="viewport"]');
if (!metaEl) {
metaEl = document.createElement('meta');
metaEl.setAttribute('name', 'viewport');
document.head.appendChild(metaEl);
}
metaEl.setAttribute('content',
'width=device-width,' +
'initial-scale=' + scale + ',' +
'maximum-scale=' + scale + ',' +
'minimum-scale=' + scale + ',' +
'user-scalable=no'
);
// 设置data-dpr属性,方便CSS中针对不同dpr做特殊处理
document.documentElement.setAttribute('data-dpr', dpr);
var docEl = document.documentElement;
var clientWidth = docEl.clientWidth;
if (clientWidth >= 750) {
docEl.style.fontSize = 100 * dpr + 'px';
} else {
docEl.style.fontSize = 100 * dpr * (clientWidth / 750) + 'px';
}
window.addEventListener('resize', setRem);
window.addEventListener('orientationchange', function() {
setTimeout(setRem, 100);
});
function setRem() {
var clientWidth = docEl.clientWidth;
if (clientWidth >= 750) {
docEl.style.fontSize = 100 * dpr + 'px';
} else {
docEl.style.fontSize = 100 * dpr * (clientWidth / 750) + 'px';
}
}
})();
第三个坑是边界值处理。有时候屏幕刚好是750px宽,但是由于四舍五入或者小数点精度问题,可能会出现微小的差异,所以要做边界判断。上面的代码里都有体现。
配合Sass/Less提高效率
手动换算px到rem确实挺烦人的,我们可以用预处理器来简化这个过程。我平时用Sass比较多,写了个mixin:
// 定义设计稿基准宽度
$design-width: 750px;
// px转rem函数
@function px2rem($px) {
@return $px / ($design-width / 7.5) * 1rem;
}
// 使用示例
.container {
width: px2rem(750px); // 输出: 7.5rem
height: px2rem(133px); // 输出: 1.33rem
margin: px2rem(20px); // 输出: 0.2rem
padding: px2rem(30px); // 输出: 0.3rem
}
// 如果想一次性设置多个值,可以写个mixin
@mixin size($width, $height: $width) {
width: px2rem($width);
height: px2rem($height);
}
.card {
@include size(375px, 200px);
}
这样写起来就方便多了,直接用px值,编译后自动转换成rem。不过要注意,在涉及到border、阴影等效果时,可能需要保持1px的物理像素,这时候就不能用rem了:
.border-hairline {
border: 1px solid #ddd;
}
// 在高DPR设备上,可以用媒体查询处理
@media (-webkit-min-device-pixel-ratio: 2) {
.border-hairline {
border-width: 0.5px;
}
}
@media (-webkit-min-device-pixel-ratio: 3) {
.border-hairline {
border-width: 0.33px;
}
}
线上验证和调试
在实际项目中,我还写了一个简单的调试工具,方便查看当前页面的rem计算情况:
// 调试工具,生产环境记得注释掉
if (process.env.NODE_ENV === 'development') {
var debugInfo = document.createElement('div');
debugInfo.innerHTML =
<div style="position: fixed; top: 10px; left: 10px; z-index: 9999;
background: rgba(0,0,0,0.7); color: white;
padding: 10px; font-size: 12px; border-radius: 5px;">
<div>Screen: ${screen.width} × ${screen.height}</div>
<div>Client: ${document.documentElement.clientWidth} × ${document.documentElement.clientHeight}</div>
<div>DPR: ${window.devicePixelRatio}</div>
<div>FontSize: ${document.documentElement.style.fontSize}</div>
<div>1rem = ${parseFloat(document.documentElement.style.fontSize)}px</div>
</div>
;
document.body.appendChild(debugInfo);
}
这样可以在开发时实时看到各种参数,遇到适配问题能快速定位。不过记得上线前要把这个调试信息注释掉。
兼容性和性能考虑
rem方案在现代浏览器中兼容性还不错,iOS Safari 6+,Android Browser 4.4+都支持。不过在一些老版本Android浏览器上可能会有问题,可以通过Feature Detection来做降级处理:
// 检测是否支持rem
function isRemSupported() {
var testElement = document.createElement('div');
testElement.style.cssText = 'width: 1rem;';
document.body.appendChild(testElement);
var isSupported = testElement.style.width === '1rem';
document.body.removeChild(testElement);
return isSupported;
}
if (!isRemSupported()) {
// fallback到px或者其他方案
console.warn('REM is not supported, using fallback...');
// 这里可以加载备用的适配方案
}
性能方面,频繁的resize事件监听确实会影响性能。我的建议是在resize事件上加个防抖,减少不必要的计算:
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// 应用防抖
window.addEventListener('resize', debounce(setRem, 100));
以上是我个人对这个rem适配方案的完整讲解,有更优的实现方式欢迎评论区交流。这个技巧的拓展用法还有很多,后续会继续分享这类博客。

暂无评论