最近头给安排了一个任务,做一个数据可视化页放在公司展厅的数据大屏上展示。UI同学确定好大屏尺寸后分分钟丢我一张 3000 * 1672 的设计稿。
屏幕适配问题
为满足展厅里大屏的正常显示,页面编写时肯定严格按照设计稿的尺寸来。但这种将尺寸固定的写法会存在很明显的屏幕适配问题,在小尺寸的电脑屏幕或更大尺寸的大屏上会出现滚动条或者占不满屏的情况。
屏幕适配原理
要适配不同的屏幕,让UI给我们出不同尺寸的设计稿明显不现实。唯一的办法就是让我们的页面像图片一样能自由缩放适配不同的容器。
锁定屏幕宽高比
我们需要将内容在一屏里全部展示出来。这就需要页面的内容宽高能像图片一样自由缩放填满整个屏幕。这种填满整个屏幕的方式可以理解为锁定屏幕宽高比。即不管设计稿尺寸多少,在不同屏幕上显现的内容宽高要与屏幕尺寸保持一致。这种方案在确定的大屏里展示完美,在与设计稿宽高比不一致的屏幕里则存在一些变形。
让内容在任何屏幕下能实现一屏展示,锁定屏幕宽高比的方案应该是最理想的了。如果boss还纠结在电脑屏幕上的细微变形,那我也只能连夜跑路了。 o(╥﹏╥)o
实现原理
让内容进行缩放的实现非常简单,我们将写好的内容放在顶层的容器组件里,然后对容器组件进行缩放即可。
<!-- 数据大屏容器组件 -->
<bi-container class="bi-container" ref="biContainer">
<div>
数据大屏内容
</div>
</bi-container>
锁定屏幕宽高比实现
// 实现锁定屏幕宽高比
let width = xxx // 设计稿(大屏) 宽度
let height = xxx // 设计稿(大屏) 高度
let screenWidth = xxxx // 屏幕视口 宽度
let screenHeight = xxxx // 屏幕视口 高度
// 计算 缩放比
let widthScaleRadio = screenWidth / originalWidth
let heightScaleRadio = screenHeight / originalHeight
// 对内容进行缩放
this.$refs.biContainer.style.transform = `scale(${widthScaleRadio }, ${heightScaleRadio })`
这里有个知识点,当我们对元素进行缩放时,它默认的缩放基点在中心位置,我们需要将基点设置在元素左上角,并让它fixed定位到屏幕左上角,这样就能完美显示,不会出现上图出现的问题。
.bi-container{
position: fixed;
top: 0;
left: 0;
z-index: 999;
transform-origin: left top;
}
容器组件开发
数据大屏 容器组件主要包含两个功能
- 初始化时对宽高进行缩放,锁定屏幕宽高比,实现一屏显示全部内容
- 监听 resize 事件,当用户对浏览器窗口进行缩放改变视口大小时对内容进行重新缩放,锁定屏幕宽高比,保持一屏展示全部内容。
容器组件完整实现请戳这里: 数据大屏容器组件仓库
下面贴上部分核心代码:
<!-- 传递数据大屏宽高 -->
<bi-container :options="{ width: 3000, height: 1672 }">
<!-- 使用图片来模拟内容 -->
<img class="content" src="../assets/1.jpeg" alt="" />
</bi-container>
data() {
return {
width: 0, // 大屏真实宽度
height: 0, // 大屏真实高度
originalWidth: 0, // 窗口原始宽度
originalHeight: 0 // 窗口原始高度
};
},
async mounted() {
// 获取相关尺寸数据
await this.initSize();
// 设置容器尺寸,让容器尺寸与内容尺寸一致
this.updateSize();
// 设置容器缩放比例,实现内容一屏完整显示
this.updateScale();
// 监听 resize事件,实现页面动态适配
window.addEventListener('resize', this.onResize);
},
beforeDestroy() {
// 页面销毁前 移除 resize 事件监听
window.removeEventListener('resize', this.onResize);
},
// 获取相关尺寸数据
initSize() {
return new Promise(resolve => {
// 使用 nextTick 确保容器中的内容渲染完成
this.$nextTick(() => {
// 获取大屏真实尺寸
if (this.options.width && this.options.height) {
this.width = this.options.width;
this.height = this.options.height;
} else {
// 若未传递大屏真实尺寸,则获取容器被内容撑满后的尺寸 作为大屏真实尺寸
this.width = this.$refs.biContainer.clientWidth;
this.height = this.$refs.biContainer.clientHeight;
}
// 获取窗口原始尺寸
if (!this.originalWidth || !this.originalHeight) {
this.originalWidth = window.screen.width;
this.originalHeight = window.screen.height;
}
});
resolve();
});
},
// 设置容器尺寸,让容器尺寸与内容尺寸一致
updateSize() {
if (this.width && this.height) {
this.$refs.biContainer.style.width = `${this.width}px`;
this.$refs.biContainer.style.height = `${this.height}px`;
}
},
// 设置容器缩放比例,实现内容一屏完整显示
updateScale() {
// 屏幕视口存在认为缩放,拖动,导致真实视口发生变化,这里获取真实的视口尺寸
const currentWidth = document.body.clientWidth;
const currentHeight = document.body.clientHeight;
// 获取大屏最终宽高, 若未获得大屏幕尺寸,则将屏幕视口原始尺寸作为大屏最终宽高
const realWidth = this.width || this.originalWidth;
const realHeight = this.height || this.originalHeight;
// 计算宽高比
const widthScale = currentWidth / realWidth;
const heightScale = currentHeight / realHeight;
this.$refs.biContainer.style.transform = `scale(${widthScale}, ${heightScale})`;
},
// 监听 resize 事件, 动态更新容器缩放比
async onResize() {
await this.initSize();
this.updateScale();
}