CSS Font-Size: em、px 、pt 、Percent之间的关系及换算

本文详细介绍了em、px、pt、%在网页文本字体设置中的区别,强调了em和%在移动端可访问性方面的优势,并提供了将px转换为em和%的方法及注意事项,最后解释了em与百分比的换算关系及在实际应用中的影响。

一、基础介绍

1、“Ems”: em,大小不固定 ,成为相对单位(body则相对浏览器的默认字体设置,子集相对父级), 浏览器默认设置字体大小为16px , 则1em = 16px , 且其可扩展,2em = 32px , 目前常用的字体大小px换算成em ,

16px = 1em; 14px = 0.875em; 12px = 0.75em; 10px = 0.625em

2、“Pixels”: px,大小是固定的,称为绝对单位,在移动端的可访问性差

3、“Points”:pt,大小固定,属于绝对单位,适用于印刷、打印媒体。

4、“Percent”: %,跟em相似,以percent来表示,则当前字体的大小为100% ,使用% 设置字体,你的页面字体在移动设备端的可访问性也很好。

二、关系

一般情况下,1em=12pt=16px=100% . 下面例子前提在body中设置基础字体大小。

图片一

由上图可看出,相对单位em 和 % 会随着基础字体大小的变化而变化,而pt 和 px 不会变化,这就是为什么选择em 和 % 设置web文档文本的字体(其在移动端的访问性也很好)。

三、em 与 % ,em与px 的换算

em的特点:

1. em的值并不是固定的;

2. em会继承父级元素的字体大小。

重写步骤:

1. body选择器中声明Font-size:62.5%;

2. 将你的原来的px数值除以10,然后换上em作为单位;

如果只需要以上两步就能解决问题的话,可能就没人用px了。经过以上两步,你会发现你的网站字体大得出乎想象。因为em的值不固定,又会继承父级 元素的大小,你可能会在content这个div里把字体大小设为1.2em, 也就是12px。然后你又把选择器p的字体大小也设为1.2em,但如果p属于content的子级的话,p的字体大小就不是12px,而是1.2em= 1.2 * 12px=14.4px。这是因为content的字体大小被设为1.2em,这个em值继承其父级元素body的大小,也就是16px * 62.5% * 1.2=12px, 而p作为其子级,em则继承content的字体高,也就是12px。所以p的1.2em就不再是12px,而是14.4px。

3. 重新计算那些被放大的字体的em数值。避免字体大小的重复声明,也就是避免以上提到的1.2 * 1.2= 1.44的现象。比如说你在#main中声明了字体大小为1.2em,那么在声明p的字体大小时就只能是1em,而不是1.2em, 因为此em非彼em,它因继承#content的字体高而变为了1em=12px。

诡异的12px汉字

在完成em转换时还会发现一个诡异的现象,就是由以上方法得到的12px(1.2em)大小的汉字在IE中并不等于直接用12px定义的字体大小,而 是稍大一点。这个问题我已经解决,你只需在body选择器中把62.5%换成63%就能正常显示了。


<template> <div class="risk-container container"> <div class="risk-container-header"> <div class="title">{{ $t('风险站点') }}</div> <el-icon class="big-icon" @click="() => $emit('detail-show', true)"> <FullScreen /> </el-icon> </div> <div class="risk-overview"> <div class="risk-overview-item"> <div class="icon-page icon-page1"></div> <div class="item-right"> <div class="overview-title">{{ $t('资产分布站点数') }}</div> <div class="overview-number">{{ handelNumber(props.data.assetsitetotalcount) }}</div> </div> </div> <div class="risk-overview-item risk-overview-item2"> <div class="icon-page icon-page2"></div> <div class="item-right"> <div class="overview-title">{{ $t('风险站点数') }}</div> <div class="overview-number alarm-number"> {{ handelNumber(props.data.risksitetotalcount) }} </div> </div> </div> </div> <div class="hor-line"></div> <div class="box"> <div class="legend"> <div v-for="(item, index) in options" :key="index" class="lenged-item"> <div class="value-change-body"> <div class="value-title">{{ item.value }}</div> <div v-if="item.changeValue" :class="{ 'top-change-icon': item.changeValue > 0, 'bottom-change-icon': item.changeValue < 0, 'change-icon': true, }" ></div> <div class="change-title" v-if="item.changeValue"> {{ Math.abs(item.changeValue) }} </div> </div> <div class="name-tooltip-body"> <div class="name-icon" :style="{ backgroundColor: colorList[index] }"></div> <div class="name-text">{{ item.name }}</div> <el-tooltip popper-class="asset-layer-bar-scale-tooltip" effect="dark" :content="item.content" placement="right" > <div class="asset-layers-question-icon" v-if="item.content">?</div> </el-tooltip> </div> </div> </div> <el-tooltip effect="dark" placement="bottom"> <template #content> <div v-for="(item, index) in options" :key="index" class="tooltip-legend"> <div class="left"> <div class="legend-block" :style="{ 'background-color': colorList[index] }"></div> <div class="legend-label">{{ item.name }}</div> </div> <div class="legend-value">{{ item.percent }}</div> </div> </template> <div class="series"> <div v-for="(item, index) in options" :key="index" class="series-item" :style="{ width: item.percent, backgroundColor: colorList[index], }" > {{ item.percent }} </div> </div> </el-tooltip> </div> </div> </template> <script lang="ts" setup> import { defineEmits, defineProps, ref, watch } from 'vue'; import { useI18n } from 'vue-i18n'; import { FullScreen } from '@element-plus/icons-vue'; const { t } = useI18n(); const props = defineProps({ data: { type: Object, default: () => {}, }, }); const colorList = ['#ff7c7c', '#ffae1d', '#6dc476']; let options = ref<Array<any>>([]); const emits = defineEmits(['detail-show']); const handelNumber = (num, fixed?: number) => { if (num || num === 0) { return fixed ? Number(num).toFixed(fixed) + '%' : num; } else { return '--'; } }; watch( () => props.data, val => { options.value = [ { name: t('高风险'), percent: handelNumber(props.data.highrisksiteratio, 2), value: handelNumber(props.data.highrisksitecount), content: t('高风险解释'), }, { name: t('低风险'), percent: handelNumber(props.data.lowrisksiteratio, 2), value: handelNumber(props.data.lowrisksitecount), content: t('低风险解释'), }, { name: t('无风险'), percent: handelNumber(props.data.norisksiteratio, 2), value: handelNumber(props.data.norisksitecount), }, ]; }, { immediate: true, deep: true } ); </script> <style lang="less" scoped> .risk-container { margin-top: 12px; width: 100%; height: 250px; overflow: hidden; background-color: #fff; border-radius: 10px; border: 1px solid #f1f1f3; .risk-container-header { display: flex; align-items: center; justify-content: space-between; .title { margin: 20px 0; font-weight: 600; font-size: 14px; } .big-icon { font-size: 18px; } } .risk-overview { display: flex; margin-bottom: 16px; .risk-overview-item { display: flex; .icon-page { width: 70px; height: 60px; background-image: url('../assets/page1.png'); background-repeat: no-repeat; background-size: 100%; } .icon-page2 { margin-left: 10px; background-image: url('../assets/page2.png'); } .item-right { margin-left: 10px; .overview-title { margin-top: 6px; font-size: 14px; font-weight: 600; } .overview-number { margin-top: 16px; font-size: 20px; font-weight: 600; } .overview-number.alarm-number { color: #f76f6a; } } } .risk-overview-item2 { margin-left: 20px; } } .box { width: 100%; font-size: 12px; margin-top: 16px; .legend { display: flex; margin-bottom: 5px; .lenged-item { flex: 1; .value-change-body { display: flex; align-items: center; .value-title { font-size: 16px; font-weight: 600; } .change-title { color: #606266; } .change-icon { width: 12px; height: 12px; background-size: 70%; background-repeat: no-repeat; margin-left: 8px; } .top-change-icon { background-image: url('../assets/top.png'); animation-name: xing1; animation-timing-function: ease-in-out; animation-iteration-count: infinite; animation-duration: 1s; } .bottom-change-icon { background-image: url('../assets/bottom.png'); animation-name: xing; animation-timing-function: ease-in-out; animation-iteration-count: infinite; animation-duration: 1s; } @keyframes xing { 0% { background-position: top; } 100% { background-position: bottom; } } @keyframes xing1 { 0% { background-position: bottom; } 100% { background-position: top; } } } .name-tooltip-body { display: flex; align-items: center; margin: 6px 0; .name-icon { height: 10px; width: 10px; display: inline-block; border-radius: 3px; } .name-text { display: inline-block; color: #606266; } .asset-layers-question-icon { width: 12px; height: 12px; margin-left: 4px; font-size: 12px; display: flex; align-items: center; justify-content: center; border: 1px solid #606266; border-radius: 50%; color: #606266; } } } } .series { .series-item { display: inline-block; color: #fff; text-align: center; border-radius: 3px; line-height: 16px; height: 16px; } } } .hor-line { width: 100%; border: 2px solid #edeff3; } } .dark { .risk-container { background: #282b33; border-color: rgba(223, 225, 230, 0.1); color: #dfe1e6; .box { .legend { .lenged-item { .name-tooltip-body { .name-text { color: #dfe1e6; } } .asset-layers-question-icon { color: #adb0bb; border-color: #adb0bb; } } } } .risk-overview { .risk-overview-item { .icon-page { background-size: contain; } } } } } .tooltip-legend { display: flex; justify-content: space-between; align-items: center; .legend-block { display: inline-block; width: 10px; height: 10px; border-radius: 2px; margin-right: 6px; } .legend-label { margin-right: 12px; display: inline-block; } } </style> 这里第打开了又关闭,又点击,打印的val一直都是true
最新发布
11-15
你提到的问题非常关键: > **“打开了又关闭,再次点击时 `val` 一直是 `true`”** 并且你在父组件中打印了: ```js const sendToParent = val => { console.log('val', val); // 打印出来的 val 始终是 true }; ``` 这说明:**子组件(RiskComp)每次点击都只 emit `true`,但从不 emit `false`。** --- ## 🚨 根本问题定位 ### ❌ 当前逻辑缺陷 | 组件 | 行为 | |------|------| | `RiskComp` | 点击图标 → `$emit('detail-show', true)` ✅ | | `AssetSafetyDetail` | 关闭时 → `emits('update:visible', false)` ✅ | | `父组件` | 接收到 `true` → 显示;但没有监听 `update:visible` 来接收 `false` ❌ | ➡️ 所以虽然子页面自己发出了 `update:visible=false`,但**父组件不知道它被关闭了!** 于是: - 第一次点击:`sendToParent(true)` → `showDetail = true` → 打开 ✅ - 关闭:子组件 emit `update:visible=false`,但父组件没监听 → `showDetail` 仍是 `true` - 再次点击:`sendToParent(true)` → `showDetail = true`(已经是 true)→ Vue 认为状态未变 → 不更新视图 ❌ 👉 **结果就是:看起来“打不开”** --- ## ✅ 正确的通信闭环应该是: ``` [打开] RiskComp -- detail-show=true --> 父组件 -- v-model:visible=true --> AssetSafetyDetail [关闭] AssetSafetyDetail -- update:visible=false --> 父组件 -- showDetail=false ``` 但现在缺少的是:**父组件必须监听 `update:visible` 才能同步关闭状态** --- ## ✅ 解决方案:修复父子通信链路 ### ✅ Step 1: 修改父组件 —— 监听 `update:visible` 你现在用了: ```vue <AssetSafetyDetail v-model:visible="showDetail" /> ``` 这是语法糖,等价于: ```vue <AssetSafetyDetail :visible="showDetail" @update:visible="val => showDetail = val" /> ``` ✅ 这个已经自动处理了双向绑定,不需要手动写。 但是你还用了一个 `div` 包裹并加了 `v-if="showDetail"`: ```vue <div v-if="showDetail" class="asset-security-detail-body"> <AssetSafetyDetail v-model:visible="showDetail" /> </div> ``` 这就导致了一个严重问题! --- ## ⚠️ 大坑:`v-if` + `v-model` 同时使用会导致状态丢失! 当你这样写: ```vue <div v-if="showDetail"> <AssetSafetyDetail v-model:visible="showDetail" /> </div> ``` 会发生什么? 1. 点击打开 → `showDetail = true` → 渲染 `<AssetSafetyDetail />` 2. 关闭弹窗 → 子组件 emit `update:visible=false` → `showDetail = false` 3. `showDetail = false` → 触发 `v-if="false"` → 整个组件被销毁! 4. 下次再设为 `true` 时,组件重新创建 → 初始状态加载 → 看似正常 ❌ 但这不是问题所在。 更大的问题是: > 如果你在 `AssetSafetyDetail` 内部有表单、分页、缓存数据,它们都会因为 `v-if` 销毁而丢失! 而且更重要的是: > ❗️如果你在 `sendToParent(true)` 之前 `showDetail` 已经是 `true`,Vue 就不会重新渲染! --- ## ✅ 正确做法:去掉外层 `v-if`,交给 `AssetSafetyDetail` 自己控制显隐 ### ✅ 修改父组件模板 ```vue <teleport to="body"> <!-- 去掉 v-if,让 AssetSafetyDetail 自己决定是否显示 --> <div v-show="showDetail && productRole.role[1] === 'amira.Infrastructure.assetSecurity'" class="asset-security-detail-body" > <AssetSafetyDetail v-model:visible="showDetail" /> </div> </teleport> ``` 或者更干净: ```vue <teleport to="body"> <AssetSafetyDetail v-if="productRole.role[1] === 'amira.Infrastructure.assetSecurity'" v-model:visible="showDetail" /> </teleport> ``` 然后在 `AssetSafetyDetail` 内部使用 `<el-dialog v-model="visible">` 来控制显示。 --- ### ✅ 确保 `AssetSafetyDetail` 支持 `visible` prop 和 `update:visible` ```js // AssetSafetyDetail.vue const props = defineProps({ visible: Boolean, }); const emits = defineEmits(['update:visible']); const handleClose = () => { emits('update:visible', false); }; ``` ```vue <el-dialog v-model="props.visible" @close="handleClose"> <!-- 内容 --> </el-dialog> ``` 或者直接: ```vue <el-dialog v-model="props.visible"> <!-- 内容 --> </el-dialog> ``` `v-model` 会自动同步。 --- ### ✅ 父组件无需额外监听,`v-model` 自动同步 你现在的 `sendToParent` 函数其实可以简化: ```js const sendToParent = (val) => { console.log('open detail:', val); showDetail.value = val; }; ``` 但实际上你可以直接: ```js // RiskComp 发送 true 即可 @click="$emit('detail-show', true)" ``` 父组件接收: ```js const sendToParent = () => { showDetail.value = true; // 只需要设置 true }; ``` 而 `false` 是由 `v-model` 自动从子组件同步回来的,**你不需要手动 emit false** --- ## ✅ 最终建议结构 ### ✅ 父组件(简化版) ```vue <template> <div class="container"> <RiskComp @detail-show="() => showDetail = true" /> <teleport to="body"> <AssetSafetyDetail v-if="hasPermission" v-model:visible="showDetail" /> </teleport> </div> </template> <script setup> import { ref } from 'vue'; import { useProductRole } from '@/composables/useProductRole'; const productRole = useProductRole(); const hasPermission = computed(() => productRole.role[1] === 'amira.Infrastructure.assetSecurity'); const showDetail = ref(false); </script> ``` ### ✅ 子组件 `AssetSafetyDetail.vue` ```vue <template> <el-dialog v-model="innerVisible" title="资产安全明细" width="90%" @closed="onClosed"> <内部表格等内容 /> </el-dialog> </template> <script setup> const props = defineProps({ visible: Boolean }); const emit = defineEmits(['update:visible']); const innerVisible = computed({ get() { return props.visible; }, set(val) { emit('update:visible', val); } }); const onClosed = () => { // 可选:重置内部状态 }; </script> ``` --- ## ✅ 验证流程 | 步骤 | 行为 | 是否触发更新 | |------|------|---------------| | 1 | 点击 RiskComp | `showDetail = true` → 显示 ✅ | | 2 | 点 × 或遮罩关闭 | `update:visible=false` → `showDetail = false` ✅ | | 3 | 再次点击按钮 | `showDetail = true` → 组件重新显示 ✅ | 此时无论之前是否已销毁,都能正确打开。 --- ## ✅ 为什么你看到 `val` 总是 `true`? 因为: - `RiskComp` 每次点击都 emit `true` - 你只在 `@detail-show` 里监听,所以只会收到 `true` - 你没监听 `update:visible`,所以收不到 `false` - 但 `showDetail` 的 `false` 是通过 `v-model` 自动更新的,不是来自 `sendToParent` 所以 `sendToParent` 只负责“打开”,不负责“关闭” --- ## ✅ 总结 | 问题 | 原因 | 解决方案 | |------|------|-----------| | ❌ 第二次打不开 | `showDetail` 没被正确设回 `false` | 使用 `v-model` 自动同步关闭状态 | | ❌ `val` 总是 `true` | `sendToParent` 只接收 `true` | 正常现象,`false` 来自 `update:visible` | | ❌ 组件不重新渲染 | `v-if` + `v-model` 冲突 | 去掉外层 `v-if`,或改用 `v-show` | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值