数据大屏项目首先要解决的问题就是如何让数据大屏适应各种不同的屏幕尺寸,针对此问题一般采用css3的scale缩放来解决,本文把个人在开发中遇到的问题做了一个总结。
容器组件结构
使用容器组件需要传入宽高属性,这个宽高属性就是设计师提供的设计稿的宽高。
// 容器组件
<template>
<div id="datav-container" ref="datavContainer">
<template v-if="ready">
<slot></slot>
</template>
</div>
</template>
// 容器组件样式
#datav-container {
display: flex;
// 从上到下排列
flex-direction: column;
transform-origin: left top;
}
// 容器组件的使用
<container :options="{ width: 3840, height: 2160 }"></container>
缩放逻辑
- 首先确定初始缩放比例
const datavContainer = ref(null)
// 用户自定义宽高
const width = ref(0)
const height = ref(0)
// 屏幕宽高
const originalWidth = ref(0)
const originalHeight = ref(0)
const ready = ref(false)
const initSize = () => {
// 如果当改变屏幕尺寸的时候,就会触发initSize方法,但是如果这个时候还在执行渲染更新操作,那么需要等渲染完成以后来执行initSize方法
return new Promise(resolve => {
nextTick(() => {
// 获取大屏的真实尺寸,即设计稿的尺寸
if (props.options && props.options.width && props.options.height) {
width.value = props.options.width
height.value = props.options.height
} else {
width.value = datavContainer.value.clientWidth
height.value = datavContainer.value.clientHeight
}
// 整个屏幕的宽高,而不是可视区域的宽高
if (!originalWidth.value && !originalHeight.value) {
originalWidth.value = window.screen.width
originalHeight.value = window.screen.height
}
resolve()
})
})
}
// 给容器组件设置宽高,一般是设计稿的宽高
const updateSize = () => {
if (width.value && height.value) {
datavContainer.value.style.width = `${width.value}px`
datavContainer.value.style.height = `${height.value}px`
} else {
datavContainer.value.style.width = `${originalWidth.value}px`
datavContainer.value.style.height = `${originalHeight.value}px`
}
}
// 根据可视区域的宽高以及设计稿的宽高来进行缩放
const updateScale = () => {
// 真实的视口的宽高,也就是显示区域的宽高
const currentWidth = document.body.clientWidth
const currentHeight = document.body.clientHeight
// 获取大屏最终的宽高,因为width可以能有值
const realWidth = width.value || originalWidth.value
const realHeight = height.value || originalHeight.value
// 计算宽高比
const widthScale = currentWidth / realWidth
const heightScale = currentHeight / realHeight
datavContainer.value.style.transform = `scale(${widthScale}, ${heightScale})`
}
onMounted(async () => {
ready.value = false
await initSize()
updateSize()
updateScale()
ready.value = true
}
这里需要注意,我们缩放的时候是对x轴和y轴同时进行缩放,在很多技术文章中我看到它们的缩放比例都是选择最小的那个缩放比例:
const scale = widthScale < heightScale ? widthScale : heightScale
datavContainer.value.style.transform = `scale(${scale}`
这样是不对的,因为我们的大屏要全部铺满整个屏幕,如果你像上面这样缩放会导致不会铺满整个屏幕,会有留白。
- 根据不同尺寸动态设置缩放比例
const onResize = async () => {
// 这个是为了保证渲染完成,确保可以拿到可视区域的宽高
await initSize()
// 最主要的是当缩放的时候,执行这个函数
updateScale()
}
// 防抖
onMounted(async () => {
ready.value = false
await initSize()
updateSize()
updateScale()
window.addEventListener('resize', debounce(100, onResize))
}
onUnmounted(() => {
window.removeEventListener('resize', onResize)
})
当我们不是通过改变浏览器窗口的尺寸,而是通过按ctrl+/ctrl-来改变窗口的分辨率(物理尺寸没有变化),当物理尺寸不变的情况下改变屏幕的分辨率大小时1px所对应的物理尺寸会变化,比如当分辨率变小时,但是整个电脑尺寸没变化,所以就会导致1像素的物理尺寸变大,这样字体等都会变大,所以把这些放大了的要缩小。
- dom元素某个样式发生变化,resize事件是无法监听到的情况 如果dom元素某个样式发生变化,这个时候resize事件是无法监听到的,那怎么才能监听到dom元素样式变化呢,可以使用mutation observe。
const initMutationObserver = () => {
const MutationObserver = window.MutationObserver
observer = new MutationObserver(onResize)
observer.observe(datavContainer.value, {
attributes: true,
attributeFilter: ['style'],
attributeOldValue: true
})
}
const removeMutationObserver = () => {
if (observer) {
observer.disconnect()
observer.takeRecords()
observer = null
}
}
onMounted(async () => {
window.addEventListener('resize', debounce(100, onResize))
initMutationObserver()
}
onUnmounted(() => {
window.removeEventListener('resize', onResize)
removeMutationObserver()
})
其实这里我们可以使用watch来监听style,因为style是父组件传递过来的。不过这里可以复习下mutation observe的使用。
但是这里有个缺点,我们在开发中各个小模块的高度是写死的,如下:
.left1 {
height: 300px;
}
.left2 {
height: 320px;
}
.left3 {
height: 280px;
}
.left4 {
height: 230px;
}
.left5 {
height: 360px;
}
.left6 {
height: 360px;
}
这些小模块高度的总和就是设计稿的高度。
如果我们设计稿临时做了修改,比如由原来的2160px改变为2640px,那么整个高度就会受影响,所以其实可以把小模块的高度改为%,或者vh这样的单位,这样就能很好的兼容。不过设计稿定稿之后很少改动的。