前置知识
px 像素单位
px 全称为 pixel(像素),它是相对于 屏幕显示器分辨率(桌面设定的分辨率,不是显示器的物理分辨率) 而言的,在 相同/不同 的设备上 1px 表示多个 设备像素。
当 一个像素点越大 时, 呈现的图像就会 越模糊;当一个像素点越小时, 像素点就会 越密集, 呈现的图像就会 越清晰。
em 相对单位
好多人都认为 em 就是相对于 父元素 font-size,实际上在不同的 CSS 属性当中使用 em 其相对的目标也是不同,如下:
- 用于 font-size 中则是相对于 父元素 font-size 大小
- 用于 其他属性(如 width,height) 中使用是相对于 自身 font-size 大小
值得注意的是,若 当前元素/父元素 的 font-size 未设置,由于 font-size 属性值可被继承的原因,可逐级向上查找,最终找不到则相对于浏览器默认字体大小,即 font-size = 16px。
rem (root em) 相对单位
rem 是 CSS3 新增的一个相对单位,它只相对于 根元素 html 的 font-size 字体大小,rem 与 em 的区别在于:
rem 相对于 html 根元素的,因此在 body 标签里面设置 font-size 是不起作用的
因此 rem 就可做到 目标元素 与 根元素 间保持 成比例 的大小关系,又可以避免字体大小逐层复合的连锁反应等,例如公共的字体大小可以在 body 中设置即可
vw 和 vh
vw 全称是 viewport width,代表的是 视口的宽度,相对于 视口 viewport 的 宽度
vh 全称是 viewport height,代表的是 视口的高度,相对于 视口 viewport 的 高度
vw 和 vh 是将 视口 宽/高 都分成 100 份,因此 100vw = 视口宽、100vh = 视口高
适配方案
rem适配
核心原理
将 设备视口 划分成 n 份, n 可以是 任何正确的值(如 flexible.js 中的 n = 10)
设置 设备视口 根元素 html 的 font-size = 设备视口宽 ÷ 份数 n,即得到 设备视口 1 rem 到底表示 多少设备视口 px
将 设计稿 也同样划分成 n 份,此时 设计稿中的 a px 对应 设备视口 b rem 的计算方式为
设备视口 b rem = 设计稿 a px ÷ (设计稿 宽 ÷ n 份)
举个例子
设备视口宽为 375px
将设备视口分成 10 份,设置 根元素 html 的 font-size = 375 ÷ 10 = 37.5 px,即 1 rem = 37.5 px
(function (n = 10){
const dEl = document.documentElement;
function setRem(){
const rem = dEl.clientWidth / n;
dEl.style.fontSize = rem + 'px';
}
// 初始化执行
setRem()
// 视口大小变动时执行
window.onresize = setRem
})()
设计稿宽为 750px
将 设计稿 也分成 10 份,每份大小 = 750 ÷ 10 = 75 px
此时 设计稿 上的 "点我拍照"(font-size: 34px) 的文案转换成符合 设备视口 对应的 rem 就为:
设备视口 中 文字 font-size = 34 ÷ 75 = 0.4533333333333333 rem ≈ 0.45 rem
【注意】 一般计算结果(人为计算、插件自动化自动化计算)都不会使用这么长的小数位,比如 0.4533333333333333 rem ≈ 0.45 rem,此时:
原始值 0.4533333333333333 rem = 0.4533333333333333 * 37.5 = 17 px
保留两位小数后的值 0.45 rem = 0.45 * 37.5 = 16.875 px
而在肉眼观察下 16.875 px 和 17 px 是没有差别的,因此可以忽略不计
这种方式其实就是淘宝的 amfe-flexible 方案。
实际使用
在vue3+vite+ts项目中
utiles/change.ts 基本使用淘宝flexible源码,加了最大width限制
(function flexible(window, document) {
var docEl = document.documentElement
var dpr = window.devicePixelRatio || 1
// adjust body font size
function setBodyFontSize() {
if (document.body) {
document.body.style.fontSize = (12 * dpr) + 'px'
}
else {
document.addEventListener('DOMContentLoaded', setBodyFontSize)
}
}
setBodyFontSize();
// set 1rem = viewWidth / 10
function setRemUnit() {
// 这里对源码进行了修改,加了宽度限制
var rem = docEl.clientWidth > 540 ? 540 / 10 : docEl.clientWidth / 10
docEl.style.fontSize = rem + 'px'
}
setRemUnit()
// reset rem unit on page resize
window.addEventListener('resize', setRemUnit)
window.addEventListener('pageshow', function (e) {
if (e.persisted) {
setRemUnit()
}
})
// detect 0.5px supports
if (dpr >= 2) {
var fakeBody = document.createElement('body')
var testElement = document.createElement('div')
testElement.style.border = '.5px solid transparent'
fakeBody.appendChild(testElement)
docEl.appendChild(fakeBody)
if (testElement.offsetHeight === 1) {
docEl.classList.add('hairlines')
}
docEl.removeChild(fakeBody)
}
}(window, document))
style/index.scss
$baseFontSize: 75;
@function p2r($px) {
@return calc($px / $baseFontSize) * 1rem;
}
在vite中导入scss文件
css: {
preprocessorOptions: {
scss: {
// 引入scss文件
additionalData: `@use "./src/style/index.scss" as *; `,
}
}
},
main.ts中导入change.ts
import '@/utils/change'
使用
<style lang="scss">
html,body {
margin: 0;
}
#app {
height: 100vh;
width: p2r(750);
margin: 0 auto;
}
#app-contain {
margin: 0 auto;
height: 100vh;
width: 100%;
/* max-width: 700px; */
}
</style>
可以看到已经生效