背景
大部分app
的适配只需要针对一个维度进行适配就行了, 以为另外一个维度是可以上下拖动, 即理论上是无限大小的. 但是之前公司做的一个移动端的项目, 需要两个维度都固定, 无法拖动. 这就导致了必须针对不同屏幕比设计不同的设计稿, 同时使用相应长宽比的css规则
方案
采用@media
中的min-aspect-ratio
与max-aspect-ratio
假设设计师给出了 4/3, 667/375, 2/1两款长宽比的设计稿, 只需要让长宽比更接近4/3选择 4/3的css规则, 让接近2/1的采用2/1的规则, 于是我们需要算出两列之间的中间值
@media (max-aspect-ratio: 3501/2250) {
编写响应 4:3 设计稿上的规则
}
@media (min-aspect-ratio: 3501/2250) and (max-aspect-ratio: 1417/750){
编写响应 667:375 设计稿上的规则
}
@media (min-aspect-ratio: 1417/750) {
编写响应 2:1 设计稿上的规则
}
这个方案的麻烦点在于每次新添加一个新的长宽比设置的话, 就需要重新计算这些值, 还需要对所有css文件添加@media
规则, 因此如果使用webpack
打包项目的话, 可以编写一个简单的loader
来做这些事情
编写思路
// step1 添加当前有的设计稿
let arr = [
{
w: 4,
h: 3
},
{
w: 667,
h: 375
},
{
w: 2,
h: 1
}
].map(v => {
return {
...v,
type: `v-${v.w}-${v.h}`
}
})
// step2 根据长宽比大小排序
arr = arr.sort((v1, v2) => v1.w/v1.h - v2.w/v2.h)
// step3 计算与相邻配置的平均值
arr.forEach((val, idx) => {
if (idx === 0) {
val.media = `@media (max-aspect-ratio: ${cal(val, arr[idx + 1])})`
} else if (idx === arr.length - 1) {
val.media = `@media (min-aspect-ratio: ${cal(val, arr[idx - 1])})`
} else {
val.media = `@media (max-aspect-ratio: ${cal(val, arr[idx + 1])}) and (min-aspect-ratio: ${cal(val, arr[idx - 1])})`
}
})
/*
[ { w: 4,
h: 3,
type: 'v-4-3',
media: '@media (max-aspect-ratio: 3501/2250)' },
{ w: 667,
h: 375,
type: 'v-667-375',
media: '@media (max-aspect-ratio: 1417/750) and (min-aspect-ratio: 3501/2250)' },
{ w: 2,
h: 1,
type: 'v-2-1',
media: '@media (min-aspect-ratio: 1417/750)' } ]
*/
step4: 在响应的css文件标示标志, 方便loader
处理使用哪个@media
查询
// index.css
/*v-4-3 标示*/
step5: loader
读取每一个css文件, 尝试读取标识符
const regex = /(v-[\d]+-[\d]+)/
const type = regex.exec(source)
if (type === null) return source // 没有标识符, 不做处理
const item = arr.find(v => v.type === type)
if (item === null) return source// 没有对应的适配规则
return `${item.media} { ${source} }` // 包上媒体查询语句返回
以上就可以简单实现我们的方案了
采用根目录的类名来做适配
也可以直接在根目录上设置当前设备长宽比最近接近的类名, 然后去匹配对应规则
<app class="v-4-3">
<child-component></child-component>
</app>
.v-4-3 p {
...
}
.v-2-1 p {
...
}
前面的v-x-y 也可以使用之前类似的方案用一个loader去动态添加
核心代码
// 配置 ratio.js
const ratios = [
{
value: 4/3,
clazz: 'v-4-3'
},
{
value: 1334/750,
clazz: ''
}
]
// 计算当前设备应该采用哪一个, 最接近的长宽比
export const calRatio = (docE) => {
let w = docE.clientWidth
let h = docE.clientHeight
// h 会有可能为0吗 ?
const r = w / h
// 找到最接近的比例, 返回对应的class
// 3个数的排序, 无需太多要求
return ratios.sort((a, b) => {
return Math.abs(a.value - r) - Math.abs(b.value - r)
})[0].clazz
}
// App.vue
<div id="app" :class="ratio">
...
</div>
created () {
this.initRatio()
window.addEventListener('resize', () => {
this.initRatio()
})
},
destroyed() {
window.removeEventListener('resize', () => {
this.initRatio()
})
},
methods: {
initRatio () {
const doc = document.documentElement
this.ratio = calRatio(doc)
}
}
// css
<style scoped>
/* v-4-3 */
p {
color: red;
}
</style>
<style scoped>
p {
color: skyblue;
}
</style>
网友评论