登录
在开发移动端时,需要使用rem,就需要设置根字体大小(font-size),同时监听屏幕尺寸变化,实时调整根字体大小。
众所周知,根字体大小需要和屏幕宽度挂钩,这里就有两种方式。
一种是很简单,直接一行css就行。
第二种比较麻烦,就是使用js获取屏幕宽度,计算后再赋值,并且监听屏幕宽度变化,实时计算并修改覆盖。
我之前一直使用的都是第二种,后来再微信公众号中,看到了第一种纯css的写法,才豁然开朗。
这里推荐优先是第一种纯css的方式。
直接使用css配置,方便快捷。
html {
font-size: calc(因数 * (100vw / UI图总宽度));
}
/* or */
/* 页面可视区域宽度小于等于 1000px 时,里面css生效 */
@media (max-width: 1000px) {
html {
font-size: calc(因数 * (100vw / UI图总宽度));
}
}
其中,UI图总宽度不用多说,就是移动端页面的设计图的总宽度。
因数只是一个值,建议填写100,这样后面开发每个标签的样式时,直接把从UI图上测得的值,除以100,单位再改为rem即可,比如测得一个按钮的宽度是50px
,那写这个按钮的宽度时,值就写成0.5rem
。
其中100这个因数必须要有,可以不是100。 因为,比如这里把因数改为1,且手机屏幕宽度恰好和UI图总宽度一样宽,这样计算完,font-size的值为1px,这里就应该设置根字体大小为1px。 但由于浏览器可能为了用户体验,可能会设置一个字体大小的最小限制,比如chrome的pc端限制字体最小为12px,小于此值时,仍然展示为12px。 由于移动端也可能会有类似的设置,所以这里乘以一个比例,让根字体的大小大一些,不过这样在写css时,也需要除以这个值,这是唯一的弊端,不过这个问题,在后面会解决。
e.g:因数设置为100,UI图总宽度为750,则写成下面即可,这样在使用时,7.5rem 就是正好宽度100%,因为 7.5rem * 因数100 = 750px。
html {
font-size: calc(100 * (100vw / 750));
}
下面代码直接用<script>
标签,插入到html的head中即可,因数也是设置的100,UI图总宽度uiWidth设置的是750,可以修改。
(function (doc, win) {
// 设置当前设置UI图的总宽度,直接看UI给的设计的宽度就行,固定的
var uiWidth = 750
var docEl = doc.documentElement
var recalc = function () {
// 乘以一个比例,作为rem的值,实现rem的值随着设别宽度的不同而不同
var clientWidth = docEl.clientWidth || 0
docEl.style.fontSize = 100 * (clientWidth / uiWidth) + 'px'
}
recalc() // 先自动触发一下
win.addEventListener('resize', recalc, false) // 添加页面监听事件
})(document, window)
然后再使用的时候,量取了设计图的尺寸,写css时直接将这个值除以因数100,单位设置为rem。
比如在宽度为750的设计图上,量取一个按钮的宽度为125px
,则写css时,宽度写为1.25rem
即可,字体大小、圆角尺寸、阴影尺寸均可以以此类推。
css用法
button {
width: 1.25rem; /* (125px / 100 = 1.25rem) */
}
但对于开发着来说,每次写css还需要计算一下,哪怕这个计算是很简单的,也是很招人烦,所以如果使用了scss,就可以使用下面scss的语法,封装一个scss方法。
// phoneSet.scss 文件
$divisor: 100; // 需要除以的除数因子(js中的因数)
// 去掉单位 把传入的带单位的值的单位去掉
@function clearUnit($number) {
@if type-of($number) == 'number' and not unitless($number) {
@return $number / ($number * 0 + 1);
}
@return $number;
}
// 传过来一个带单位的值,比如1px,返回固定rem单位的值,比如3rem
@function r($numAndUnit) {
@if not unitless($numAndUnit) {
$numAndUnit: clearUnit($numAndUnit);
}
@return ($numAndUnit / $divisor) * 1rem;
}
注:新版的scss,貌似有些数学计算方法不再支持,所以如果报错的话,就需要换种写法
// phoneSet.scss 文件
@use "sass:math";
$divisor: 100; // 需要除以的除数因子(js中的因数)
// 去掉单位 把传入的带单位的值的单位去掉
@function clearUnit($number) {
@if type-of($number) == 'number' and not unitless($number) {
@return math.div($number, ($number * 0 + 1));
}
@return $number;
}
// 传过来一个带单位的值,比如1px,返回固定rem单位的值,比如3rem
@function r($numAndUnit) {
@if not unitless($numAndUnit) {
$numAndUnit: clearUnit($numAndUnit);
}
@return calc((#{$numAndUnit} / #{$divisor}) * 1rem);
}
用法
// 其他scss文件
@import(./phoneSet.scss) // 引入scss方法文件
button {
width: r(125); /* 直接传入测量的值即可 */
}
tailwindcss提供sm、md、lg、xl、2xl这5个默认断点,但在开发pc端+移动端页面时,会发现 tailwindcss 有一个问题。
首先列举一下这5个断点的官方示意。
断点前缀 | 最小宽度 | CSS |
---|---|---|
sm |
640像素 | @media (min-width: 640px) { ... } |
md |
768px | @media (min-width: 768px) { ... } |
lg |
1024px | @media (min-width: 1024px) { ... } |
xl |
1280像素 | @media (min-width: 1280px) { ... } |
2xl |
1536px | @media (min-width: 1536px) { ... } |
需要注意的是:当使用 md:bg-white 时,指的并不是 屏幕宽度小于 768px 时,而是 屏幕宽度大于等于768px时,背景色为白色!
因为 tailwindcss 是 移动端优先,当不添加前缀时的样式,是屏幕宽度大于等于0时的样式,当使用了md前缀,指的是屏幕宽度大于等于 768px 时,其他断点同理。
即使在 tailwind.config.ts 文件中添加断点:
theme: {
extend: {
screens: {
mo: '500px',
pc: '1200px'
},
},
},
当是用 mo 前缀时,指的也是屏幕尺寸大于等于 500px 时会应用的样式,比较蛋疼。
tailwindcss相关文档 也有说明:不要使用sm:
定位移动端设备,而是使用无前缀的类名来定位移动端样式,并在较大的断点处覆盖它们!
官方文档在此:自定义屏幕断点。
下面摘抄整理文档中的示例,在 tailwind.config.js 文件中配置:
/** @type {import('tailwindcss').Config} */
module.exports = {
theme: {
screens: {
'sm': '200px', // 可以自定义key,也可以覆盖默认值
// => @media (min-width: 200px) { ... }
// 意思是生效的范围的「最小值」是「200px」,也就是大于等于200px时生效
'pad': {'max': '900px'},
// => @media (max-width: 900px) { ... }
// 意思是生效的范围的「最大值」是「900px」,也就是小于等于900px时生效
'md': {'min': '768px', 'max': '1023px'},
// => @media (min-width: 768px and max-width: 1023px) { ... }
// 意思是生效的范围是 >= 768 且 <= 1023
'tall': { 'raw': '(min-height: 800px)' },
// 完全自定义媒体查询的值
}
}
}
下面给出最适用的配置。
例子中将要完成的效果:
html {
font-size: 16px;
}
/* 小于等于900px时应用的样式 */
@media (max-width: 900px) {
html {
/* 设置小于等于900px时的根字体大小 */
/* 按下面这样设置,当测量设计图的按钮宽度是100px时,开发时这个按钮的样式可以直接设置为 100rem */
font-size: calc(100vw / 375);
}
body {
font-size: 16rem;
}
}
/**
* 获取设备宽度信息
* @returns [宽度像素值, 是否是移动端]
*/
function getDeviceInfo(): [number, boolean] {
const deviceWidth = document.documentElement.clientWidth
let isMobile = false
if (deviceWidth < screenPc) isMobile = true
return [deviceWidth, isMobile]
}
// 监听设备宽度和处理
export function watchDevice() {
// 处理方法
const handle = () => {
let result = getDeviceWidth()
if (!result) return
const [deviceWidth, isMobile] = result
// 拿到了「设备宽度」和「是否是移动端」
// 可以存储到全局状态中
let classList = document.querySelector('html')?.classList
if (!classList) return
// 添加、删除类名
if (isMobile) {
if (classList.contains('pc')) classList.remove('pc')
classList.add('mo')
} else {
if (classList.contains('mo')) classList.remove('mo')
classList.add('pc')
}
}
// 先执行一次
handle()
// 添加监听
window.addEventListener('resize', handle)
}
添加 pc 和 mo 的断点, tailwind.config.js 文件中配置:
/** @type {import('tailwindcss').Config} */
module.exports = {
theme: {
screens: {
// 设置屏幕尺寸大于 900px 时的前缀
// 下面的媒体查询,是:「小于等于900px的取反」,也就是大于900px
'pc': {'raw': 'not (max-width: 900px)'},
// 设置屏幕尺寸小于等于 900px 时的前缀
'mo': {'max': '900px'},
}
}
}
下面这个页面的功能,是先设定 1px 等于多少 rem,然后粘贴px为单位css,把里面的px自动计算转换为rem的页面。
还是有点用处的,偶尔使用。
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title>px计算转为rem</title>
<style>
.box {
width: max-content;
margin: 100px auto 0;
}
.tip {
margin-bottom: 20px;
display: flex;
flex-direction: column;
align-items: center;
}
.tip .text {
text-align: center;
color: #999;
}
.tip .setRow {
margin: 10px auto 0;
}
.tip .setRow > input {
width: 200px;
line-height: 2em;
padding: 0 5px;
}
.tip .setRow > span {
line-height: 2em;
color: #999;
}
.inputBox {
display: flex;
}
textarea {
resize: none;
padding: 20px 10px;
}
textarea + textarea {
margin-left: 10px;
}
</style>
</head>
<body>
<div class="box">
<div class="tip">
<h1>css 中的 px 自动计算转换为 rem</h1>
<div class="text">左侧框贴入px为单位的代码,右侧自动计算转换为rem之后的代码,同时会自动复制。</div>
<div class="setRow">
<input type="text" placeholder="1px === ?rem">
<span>输入1px等于几rem</span>
</div>
</div>
<div class="inputBox">
<textarea placeholder="请粘贴css代码" class="input" name="" id="" cols="50" rows="20"></textarea>
<textarea placeholder="转换后代码" class="output" name="" id="" cols="50" rows="20"></textarea>
</div>
</div>
</body>
<script>
let setDom = document.querySelector('.setRow input')
let inputDom = document.querySelector('.input')
let outputDom = document.querySelector('.output')
let setRem = 0.01
setDom.value = setRem
// 监听修改了转换比例
setDom.addEventListener('input', (e) => {
let newSetRem = setDom.value.trim()
if (!/[0-9.]/.test(newSetRem)) return
setRem = Number(newSetRem)
handler()
})
// focus 自动全选
setDom.addEventListener('focus', () => {
setDom.select()
})
inputDom.addEventListener('focus', () => {
inputDom.select()
})
outputDom.addEventListener('focus', () => {
outputDom.select()
})
// 监听修改了输入的css
inputDom.addEventListener('input', () => {
handler()
})
// 处理
function handler() {
let input = inputDom.value
let result = input.replace(/[0-9.]+px/ig, (text, b) => {
let a = parseFloat(text)
let result = `${a * setRem}rem`
return result
})
outputDom.value = result
navigator.clipboard.writeText(result)
}
</script>
</html>