【前端样式】手写rem + flexible.js自动适配方案全解析
引言
在移动端开发中,多设备屏幕适配一直是前端工程师面临的重要挑战。传统的媒体查询方案需要针对不同屏幕宽度编写大量CSS规则,而随着设备碎片化加剧,这种方案逐渐暴露出维护成本高、适配精度不足等问题。本文将深入解析基于rem单位和动态计算根字体大小的适配方案,并手写一个简化版的flexible.js脚本,帮助开发者构建灵活高效的移动端适配体系。
一、适配方案核心原理
1.1 rem单位基础
rem(Root EM)是CSS3新增的相对长度单位,其核心特点是相对于根元素(html)的字体大小进行计算。当设置html { font-size: 16px }
时,1rem即等于16px。这种特性使得通过动态计算根字体大小来实现整体布局缩放成为可能。
1.2 视口(viewport)处理
移动端开发需要正确设置viewport的meta标签,这是实现适配的前提条件:
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
1.3 动态计算方案
核心思路是通过JavaScript动态计算根字体大小,将屏幕宽度与设计稿宽度的比例关系应用到html元素的font-size属性上。例如设计稿为750px宽时,设置屏幕宽度为10rem,则根字体大小为屏幕宽度/10。
二、手写简化版flexible.js
2.1 完整实现代码
(function(win, doc) {const docEl = doc.documentElement;let dpr = win.devicePixelRatio || 1;// 设置dpr的边界值dpr = dpr >= 3 ? 3 : (dpr >= 2 ? 2 : 1);// 设置data-dpr属性docEl.setAttribute('data-dpr', dpr);// 调整viewport scaleconst scale = 1 / dpr;const viewportMeta = doc.querySelector('meta[name="viewport"]');if (viewportMeta) {viewportMeta.content = `width=device-width, initial-scale=${scale}, maximum-scale=${scale}, minimum-scale=${scale}, user-scalable=no`;} else {const meta = doc.createElement('meta');meta.name = 'viewport";meta.content = `width=device-width, initial-scale=${scale}, maximum-scale=${scale}, minimum-scale=${scale}, user-scalable=no`;doc.head.appendChild(meta);}// 计算根字体大小function refreshRem() {const width = docEl.clientWidth || doc.body.clientWidth;const rem = width / 10; // 将屏幕10等分docEl.style.fontSize = rem + 'px';}// 初始化执行refreshRem();// 事件监听win.addEventListener('resize', refreshRem);win.addEventListener('pageshow', function(e) {if (e.persisted) refreshRem();});// 重写alert避免缩放影响const originalAlert = win.alert;win.alert = function(message) {docEl.style.fontSize = originalRem + 'px';originalAlert(message);refreshRem();};
})(window, document);
2.2 关键代码解析
2.2.1 DPR(设备像素比)处理
dpr = dpr >= 3 ? 3 : (dpr >= 2 ? 2 : 1);
这里对dpr进行限制主要基于两个考虑:
- 避免过高dpr值导致过度缩放
- 市面上主流设备dpr值集中在1、2、3三个档次
2.2.2 Viewport动态调整
const scale = 1 / dpr;
通过设置initial-scale缩放视口,将CSS像素与设备像素对齐。例如在dpr=2的设备上,实际视口宽度会被放大到设备宽度的2倍,再通过CSS的缩放保证布局尺寸正确。
2.2.3 rem计算函数
function refreshRem() {const width = docEl.clientWidth || doc.body.clientWidth;const rem = width / 10;docEl.style.fontSize = rem + 'px';
}
将屏幕宽度10等分的方案平衡了计算便捷性和布局精度。当设计稿宽度为750px时,1rem=75px,开发时可以直接用设计稿尺寸除以75得到rem值。
2.3 特殊事件处理
win.addEventListener('pageshow', function(e) {if (e.persisted) refreshRem();
});
处理页面从缓存恢复时的状态重置问题,确保字体大小重新计算。
三、实际应用示例
3.1 CSS使用规范
/* 设计稿尺寸为750px时 */
.box {width: 2rem; /* 对应150px */height: 1.333rem; /* 对应100px */font-size: 0.32rem; /* 对应24px */
}
3.2 开发工具函数(Sass示例)
@function px2rem($px) {@return $px / 75 * 1rem;
}.box {width: px2rem(150);height: px2rem(100);font-size: px2rem(24);
}
四、避坑指南与常见问题
4.1 字体大小异常
现象:文本在不同dpr设备上显示大小不一致
解决方案:
[data-dpr="1"] .text {font-size: 12px;
}
[data-dpr="2"] .text {font-size: 24px;
}
[data-dpr="3"] .text {font-size: 36px;
}
或使用rem单位配合媒体查询:
.text {font-size: 0.32rem;@media (-webkit-min-device-pixel-ratio: 2) {font-size: 0.3rem;}
}
4.2 1px边框问题
解决方案:
.border {position: relative;&::after {content: "";position: absolute;left: 0;bottom: 0;width: 100%;height: 1px;background: #ccc;transform: scaleY(0.5);}
}
4.3 第三方组件样式问题
现象:引入的UI库组件尺寸异常
解决方案:
// 在组件容器上重置字体大小
.component-wrapper {font-size: 16px; /* 设置基准字号 */[class^="component-"] {font-size: inherit; /* 继承父级字号 */}
}
4.4 Android设备缩放异常
解决方案:
// 在计算rem前加入最大宽度限制
function refreshRem() {let width = docEl.clientWidth;if (width > 540) width = 540; // 限制最大适配宽度const rem = width / 10;docEl.style.fontSize = rem + 'px';
}
五、进阶优化技巧
5.1 动态设计稿基准
const designWidth = 750; // 可配置的设计稿宽度
const rem = (width / designWidth) * 100; // 保持1rem=100px的比例关系
5.2 横竖屏切换处理
let timer;
win.addEventListener('resize', () => {clearTimeout(timer);timer = setTimeout(refreshRem, 300);
});
5.3 图片高清适配
<img src="image@2x.png" srcset="image@1x.png 1x, image@2x.png 2x, image@3x.png 3x"alt="responsive image">
六、方案局限性及替代方案
6.1 当前方案的不足
- 依赖JavaScript执行时机
- 需要构建工具配合单位转换
- 视口缩放可能影响某些浏览器特性
6.2 新一代适配方案
/* 使用vw单位 */
html {font-size: calc(100vw / 7.5); /* 750px设计稿时1rem=100px */
}
结语
本文从原理到实践详细解析了rem+flexible适配方案,手写的简化版脚本已具备核心功能,开发者可根据项目需求进行扩展。在实际应用中,建议结合Webpack等构建工具实现自动化单位转换,同时关注CSS新特性(如容器查询、:has()选择器)的发展,适时优化适配方案。