跳到主要内容

认识

2025年01月14日
柏拉文
越努力,越幸运

一、认识


remRoot Em:相对于根元素 (<html>) 的字体大小。适配思路为: 将 px 转化为 rem 后, 通过 动态调整根元素的字体大小 来动态改变页面布局。

Rem 计算规则: 我们为了方面计算, 采用换算单位时固定除数, 动态计算 HTML.fontSize 来实现终端适配。以下是我们的计算逻辑: htmlFontSize = clientWidth / uiWidth * postcssRootValue

  • htmlFontSize: 最终的根字体大小, 它根据屏幕宽度与设计稿宽度的比例乘以我们固定的除数来计算。

  • clientWidth: 屏幕宽度

  • uiWidth: UI 稿宽度

  • postcssRootValue: 换算单位时的除数, 比如我们为了方便计算,规定 1rem = 100px,当 UI 稿某个元素为 400px 时, 那就在代码中换算成 4rem 即可。

举例: 假如我们的 UI 稿为 750px, 我们在 iPhone 14 Pro Max 430*932 的终端开发, 为了很方便的适配各种终端以及不同终端尺寸与 UI 稿无缝衔接, 我们需要将 750pxUI 稿撑满 430px 宽的屏幕。因此需要知道 屏幕宽度与设计稿宽度 的比例。然后我们换算 px -> rem 时, 固定使用 100, 所以他们的比例乘 100 就是最终的 font-size

二、实现


注意: 以 UI 稿为 750px 为例进行的 Demo。如果 UI 稿有变更, 要改变 uiWidth 值。

2.1 App.tsx

import "uno.css";
import "./App.css";
import "./App.scss";
import flexible from "./flexible";
import { useEffect } from "react";
import city1 from "./images/city1.png";
import AppStyle from "./App.module.scss";


function App() {

useEffect(()=>{
flexible(window, document);
}, []);

return (
<div className="app">
App 页面
<div className={AppStyle.div1}></div>
<img className="w-750" src={city1} />
<div className="m-1 color-SubColor font-size-32">嘻嘻哈哈</div>
<div className="p-2 color-Text2 bg-BrandDark font-size-32">哈哈嘻嘻</div>
<div className="box"></div>
<div className="text-ellipsis-2 w-100 font-size-32">
敷设电缆;范德萨范德萨;林凤娇了;附件都说了;就发了;三等奖发了发;啦束带结发;拉数据;
发;苏妲己;啊;发的酸;拉法基;了发的酸;浪费啊;减肥的;是佛i额文峰街道舒服了扩大升级
</div>
<span className="text-ellipsis-2-inline w-100 font-size-32">
敷设电缆;范德萨范德萨;林凤娇了;附件都说了;就发了;三等奖发了发;啦束带结发;拉数据;
发;苏妲己;啊;发的酸;拉法基;了发的酸;浪费啊;减肥的;是佛i额文峰街道舒服了扩大升级
</span>
</div>
);
}

export default App;

2.2 flexible.ts

function flexible(window: Window, document: Document) {
const docEl = document.documentElement;
const uiWidth = 750;
const postcssRootValue = 100;

function setRemUnit() {
const clientWidth = docEl.clientWidth;
const rem = (clientWidth / uiWidth) * postcssRootValue;
docEl.style.fontSize = rem + "px";
}

setRemUnit();

window.addEventListener("resize", setRemUnit);
window.addEventListener("pageshow", function (e) {
if (e.persisted) {
setRemUnit();
}
});
}

export default flexible;

2.3 uno.config.js

UnoCSS 中,需要我们自己换算单位。我们的方案是, 换算单位统一为 1rem = 100px。所以, 换算逻辑如下:

import { Preset } from "unocss";

const uiWidth = 750;

const getSizeValue = (value: string): string => {
if (!value) {
return "";
}
if (value.includes("-")) {
const values = value.split("-");
return values.map(getSizeValue).join(" ");
}
if (isNaN(Number(value))) {
return value;
}

const remSize = parseFloat(value) / 100;
return remSize ? `${remSize}rem` : "0";
};

const boxSizeValue = [
"border-box",
"content-box",
"clip",
"visible",
"scroll",
].join("|");

export const preset1: Preset = {
name: "preset1",
rules: [
[
/^w-([.\d]+)$/,
([_, num]) => ({ width: getSizeValue(num) }),
{ autocomplete: "w-<num>" },
],
[
/^max-w-([.\d]+)$/,
([, value]) => ({ "max-width": getSizeValue(value) }),
{ autocomplete: "max-w-<num>" },
],
[
/^min-w-([.\d]+)$/,
([, value]) => ({ "min-width": getSizeValue(value) }),
{ autocomplete: "min-w-<num>" },
],
[
/^h-([.\d]+)$/,
([, value]) => ({ height: getSizeValue(value) }),
{ autocomplete: "h-<num>" },
],
[
/^max-h-([.\d]+)$/,
([, value]) => ({ "max-height": getSizeValue(value) }),
{ autocomplete: "max-h-<num>" },
],
[
/^min-h-([.\d]+)$/,
([, value]) => ({ "min-height": getSizeValue(value) }),
{ autocomplete: "min-h-<num>" },
],
[
new RegExp(`^(?:box)-(${boxSizeValue})$`),
([, v]) => (boxSizeValue.includes(v) ? { "box-sizing": v } : undefined),
{
autocomplete: [`(box)-(${boxSizeValue})`],
},
],
[
/^m-([\d]+)$/,
([, value]) => ({ margin: getSizeValue(value) }),
{ autocomplete: "m-<num>" },
],
[
/^m-t-([\d]+)$/,
([, value]) => ({ "margin-top": getSizeValue(value) }),
{ autocomplete: "m-t-<num>" },
],
[
/^m-r-([\d]+)$/,
([, value]) => ({ "margin-right": getSizeValue(value) }),
{ autocomplete: "m-r-<num>" },
],
[
/^m-b-([\d]+)$/,
([, value]) => ({ "margin-bottom": getSizeValue(value) }),
{ autocomplete: "m-b-<num>" },
],
[
/^m-l-([\d]+)$/,
([, value]) => ({ "margin-left": getSizeValue(value) }),
{ autocomplete: "m-l-<num>" },
],
[
/^p-([\d]+)$/,
([, value]) => ({ padding: getSizeValue(value) }),
{ autocomplete: "p-<num>" },
],
[
/^p-t-([\d]+)$/,
([, value]) => ({ "padding-top": getSizeValue(value) }),
{ autocomplete: "p-t-<num>" },
],
[
/^p-r-([\d]+)$/,
([, value]) => ({ "padding-right": getSizeValue(value) }),
{ autocomplete: "p-r-<num>" },
],
[
/^p-b-([\d]+)$/,
([, value]) => ({ "padding-bottom": getSizeValue(value) }),
{ autocomplete: "p-b-<num>" },
],
[
/^p-l-([\d]+)$/,
([, value]) => ({ "padding-left": getSizeValue(value) }),
{ autocomplete: "p-l-<num>" },
],
[
/^font-size-([\d]+)$/,
([, value]) => ({ "font-size": getSizeValue(value) }),
],
[/^font-bold$/, () => ({ "font-weight": "550" })],
[/^bold$/, () => ({ "font-weight": "550" })],
[/^weight-([\d]+)$/, ([, value]) => ({ "font-weight": value })],
[
/^line-height-([0-9].*)$/,
([, value]) => ({ "line-height": getSizeValue(value) }),
],
]
};

2.4 postcss.config.js

module.exports = {
plugins: [
"autoprefixer",
"postcss-preset-env",
[
"postcss-pxtorem",
{
rootValue: 100,
unitPrecision: 5,
propList: ["*"],
selectorBlackList: [],
minPixelValue: 1,
mediaQuery: false,
},
],
],
};

2.5 webpack.config.js

const combineCssLoader = (params = {}) => {
const { modules = false, postcss = true, devMode = true } = params;

const baseLoaders = [
devMode ? "style-loader" : MiniCssExtractPlugin.loader,
{
loader: "css-loader",
options: {
modules: modules ? { namedExport: false } : false,
},
},
];

if (postcss) {
baseLoaders.push({
loader: "postcss-loader",
options: {
postcssOptions: {
config: Path.resolve(process.cwd(), "./postcss.config.js"),
},
},
});
}

return baseLoaders;
};