| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167 |
- /**
- * rem 适配工具 - 全终端适配方案(防御式设计)
- * 设计稿基准: 393px(与 postcss / px2vw 一致)
- * 1rem = 100px (便于计算,设计稿 100px = 1rem)
- *
- * 适配范围: 280px - 768px (支持手机到平板)
- * - 280px 屏幕: 根字体 75px (iPhone SE 竖屏)
- * - 320px 屏幕: 根字体 85px (小屏手机)
- * - 393px 屏幕: 根字体约 100px (标准手机)
- * - 430px 屏幕: 根字体 114px (大屏手机)
- * - 768px 屏幕: 根字体 100px (平板,固定标准大小)
- * - 1024px+ 屏幕: 根字体 100px (桌面,居中显示)
- *
- * 兼容性: iOS 8+, Android 4.4+, 所有现代浏览器
- */
- const BASE_WIDTH = 393; // 设计稿宽度
- const BASE_FONT_SIZE = 100; // 1rem = 100px
- const MIN_WIDTH = 280; // 最小宽度(支持极小屏幕)
- const MOBILE_MAX_WIDTH = 430; // 移动端最大宽度(超过则按平板处理)
- const TABLET_WIDTH = 768; // 平板宽度(超过则固定大小)
- const MIN_FONT_SIZE = 75; // 最小根字体(防止元素过小不可用)
- const TABLET_FONT_SIZE = 100; // 平板/桌面固定字体(保持设计稿标准大小)
- /**
- * 检测设备类型
- */
- const getDeviceType = (width: number): 'mobile' | 'tablet' | 'desktop' => {
- if (width < MOBILE_MAX_WIDTH) return 'mobile';
- if (width < TABLET_WIDTH) return 'tablet';
- return 'desktop';
- };
- /**
- * 设置根元素字体大小(三层防护策略)
- */
- const setRootFontSize = () => {
- const screenWidth = window.innerWidth;
- const screenHeight = window.innerHeight;
- const deviceType = getDeviceType(screenWidth);
-
- let fontSize: number;
-
- // 第一层:移动端(280px - 430px)- 动态缩放
- if (deviceType === 'mobile') {
- const clampedWidth = Math.min(Math.max(screenWidth, MIN_WIDTH), MOBILE_MAX_WIDTH);
- fontSize = (clampedWidth / BASE_WIDTH) * BASE_FONT_SIZE;
- fontSize = Math.max(fontSize, MIN_FONT_SIZE); // 防止过小
- }
- // 第二层:平板(430px - 768px)- 渐进式缩放
- else if (deviceType === 'tablet') {
- // 平板采用渐进式缩放,从 114px 到 100px
- const progress = (screenWidth - MOBILE_MAX_WIDTH) / (TABLET_WIDTH - MOBILE_MAX_WIDTH);
- const startSize = (MOBILE_MAX_WIDTH / BASE_WIDTH) * BASE_FONT_SIZE; // 114px
- fontSize = startSize - (progress * (startSize - TABLET_FONT_SIZE));
- }
- // 第三层:桌面(768px+)- 固定大小 + 居中
- else {
- fontSize = TABLET_FONT_SIZE; // 固定 100px,内容居中显示
- }
-
- // 设置 html 根字体大小
- document.documentElement.style.fontSize = `${fontSize}px`;
-
- // 设置设备类型标识(用于 CSS 媒体查询增强)
- document.documentElement.setAttribute('data-device-type', deviceType);
-
- // 设置屏幕方向标识
- const orientation = screenWidth > screenHeight ? 'landscape' : 'portrait';
- document.documentElement.setAttribute('data-orientation', orientation);
-
- // 极小屏幕检测:设置滚动条降级标记
- const isTooSmall = screenWidth < MIN_WIDTH || screenHeight < 500;
- document.documentElement.setAttribute('data-too-small', String(isTooSmall));
- document.body.setAttribute('data-too-small', String(isTooSmall));
-
- // rem 适配完成
- };
- /**
- * 防抖函数(优化 resize 性能)
- */
- const debounce = (fn: () => void, delay: number) => {
- let timer: number | null = null;
- return () => {
- if (timer) clearTimeout(timer);
- timer = window.setTimeout(fn, delay);
- };
- };
- /**
- * 初始化 rem 适配(全终端支持)
- */
- export const initRem = () => {
- // 立即设置一次
- setRootFontSize();
-
- // 监听窗口变化(防抖优化性能)
- const debouncedResize = debounce(setRootFontSize, 100);
- window.addEventListener('resize', debouncedResize);
-
- // 监听横竖屏切换(立即响应)
- window.addEventListener('orientationchange', () => {
- // orientationchange 后需要延迟执行,等待实际尺寸变化
- setTimeout(setRootFontSize, 100);
- });
-
- // 监听 DOMContentLoaded(确保 DOM 加载完成)
- if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', setRootFontSize);
- }
-
- // 监听页面可见性变化(切换标签页后重新计算)
- document.addEventListener('visibilitychange', () => {
- if (!document.hidden) {
- setRootFontSize();
- }
- });
-
- // iOS Safari 特殊处理:监听滚动条显示/隐藏导致的宽度变化
- if (/iPhone|iPad|iPod/.test(navigator.userAgent)) {
- let lastWidth = window.innerWidth;
- window.addEventListener('scroll', debounce(() => {
- const currentWidth = window.innerWidth;
- if (Math.abs(currentWidth - lastWidth) > 5) { // 宽度变化超过 5px 才重新计算
- lastWidth = currentWidth;
- setRootFontSize();
- }
- }, 200));
- }
-
- // 微信浏览器特殊处理:监听软键盘弹出/收起
- if (/MicroMessenger/.test(navigator.userAgent)) {
- let lastHeight = window.innerHeight;
- window.addEventListener('resize', debounce(() => {
- const currentHeight = window.innerHeight;
- // 高度变化超过 100px 认为是键盘弹出/收起
- if (Math.abs(currentHeight - lastHeight) > 100) {
- lastHeight = currentHeight;
- setRootFontSize();
- }
- }, 100));
- }
-
- // rem 适配已启用
- };
- /**
- * px 转 rem 辅助函数(手动计算时使用)
- * @param px 设计稿上的 px 值
- * @returns rem 字符串
- * @example px2rem(100) => '1rem'
- */
- export const px2rem = (px: number): string => {
- return `${px / BASE_FONT_SIZE}rem`;
- };
- /**
- * rem 转 px 辅助函数(用于 JS 计算)
- * @param rem rem 值
- * @returns 实际像素值
- */
- export const rem2px = (rem: number): number => {
- const fontSize = parseFloat(getComputedStyle(document.documentElement).fontSize);
- return rem * fontSize;
- };
|