scaleBox大屏自适应容器组件
1.引言
在做数字大屏时,需要整体页面,图表能跟着浏览器的尺寸自动变化,本文采用 vue 前端框架,采用 JavaScript 语言,封装了一个大屏自适应组件,将需要显示的页面放入组件的插槽中,就能实现自适应屏幕大小的效果。
echart引入教程:echart引入和组件封装-CSDN博客
2.实际效果
您的浏览器不支持播放该视频!
3.组件代码
scaleBox.vue 组件
<!-- 大屏自适应容器组件 需固定宽高 否者按照 1920*1080 处理-->
<template>
<!-- <section class="screen-box" :style="boxStyle"> -->
<div class="screen-wrapper" ref="screenWrapper" :style="wrapperStyle">
<slot></slot>
</div>
<!-- </section> -->
</template>
<script>
/**
* 防抖函数
* @param {T} fn
* @param {number} delay
* @return
*/
function debounce(fn, delay) {
let timer = null;
return function (...args) {
timer = setTimeout(
() => {
typeof fn === "function" && fn.apply(null, args);
clearTimeout(timer);
},
delay > 0 ? delay : 100
);
};
}
export default {
name: "VScaleScreen",
props: {
// 大屏宽度
width: {
type: [String, Number],
default: 1920,
},
// 大屏高度
height: {
type: [String, Number],
default: 1080,
},
// 全屏自适应,启用此配置项时会存在拉伸效果,同时autoScale失效,非必要情况下不建议开启
fullScreen: {
type: Boolean,
default: false,
},
// 自适应配置,配置为boolean类型时,为启动或者关闭自适应,配置为对象时,若x为true,x轴产生边距,y为true时,y轴产生边距,启用fullScreen时此配置失效 | Boolean or {x:boolean,y:boolean}
autoScale: {
type: [Object, Boolean],
default: true,
},
//是否进行自适应
selfAdaption: {
type: Boolean,
default: true,
},
// 窗口变化防抖延迟时间
delay: {
type: Number,
default: 500,
},
// 修改容器样式,如居中展示时侧边背景色,符合Vue双向绑定style标准格式
boxStyle: {
type: Object,
default: () => ({}),
},
// 修改自适应区域样式,符合Vue双向绑定style标准格式
wrapperStyle: {
type: Object,
default: () => ({}),
},
},
data() {
return {
currentWidth: 0,
currentHeight: 0,
originalWidth: 0,
originalHeight: 0,
onResize: null,
observer: null,
};
},
watch: {
//是否进行自适应判断
selfAdaption(val) {
// 如果自适应,设置并初始化自适应,添加监听事件
if (val) {
this.resize();
this.addListener();
} else {
// 否者清除所有样式操作,并移除监听事件
this.clearListener();
this.clearStyle();
}
},
},
computed: {
screenWrapper() {
return this.$refs["screenWrapper"];
},
},
methods: {
initSize() {
return new Promise((resolve, reject) => {
// console.log("初始化样式");
//给父元素设置 overflow:hidden
this.screenWrapper.parentNode.style.overflow = "hidden";
this.screenWrapper.parentNode.scrollLeft = 0;
this.screenWrapper.parentNode.scrollTop = 0;
this.$nextTick(() => {
// region 获取大屏真实尺寸
if (this.width && this.height) {
this.currentWidth = this.width;
this.currentHeight = this.height;
} else {
this.currentWidth = this.screenWrapper.clientWidth;
this.currentHeight = this.screenWrapper.clientHeight;
}
// endregion
// region 获取画布尺寸
if (!this.originalHeight || !this.originalWidth) {
this.originalWidth = window.screen.width;
this.originalHeight = window.screen.height;
}
// endregion
resolve();
});
});
},
updateSize() {
if (this.currentWidth && this.currentHeight) {
this.screenWrapper.style.width = `${this.currentWidth}px`;
this.screenWrapper.style.height = `${this.currentHeight}px`;
} else {
this.screenWrapper.style.width = `${this.originalWidth}px`;
this.screenWrapper.style.height = `${this.originalHeight}px`;
}
},
handleAutoScale(scale) {
if (!this.autoScale) return;
const screenWrapper = this.screenWrapper;
const domWidth = screenWrapper.clientWidth;
const domHeight = screenWrapper.clientHeight;
const currentWidth = document.body.clientWidth;
const currentHeight = document.body.clientHeight;
screenWrapper.style.transform = `scale(${scale},${scale}) `;
let mx = Math.max((currentWidth - domWidth * scale) / 2, 0);
let my = Math.max((currentHeight - domHeight * scale) / 2, 0);
if (typeof this.autoScale === "object") {
// @ts-ignore
!this.autoScale.x && (mx = 0);
// @ts-ignore
!this.autoScale.y && (my = 0);
}
// console.log({
// mx,
// my,
// currentWidth,
// currentHeight,
// domWidth,
// domHeight,
// scale,
// });
this.screenWrapper.style.margin = `${my}px ${mx}px`;
},
updateScale() {
const screenWrapper = this.screenWrapper;
// 获取真实视口尺寸
const currentWidth = document.body.clientWidth;
const currentHeight = document.body.clientHeight;
// 获取大屏最终的宽高onResize
const realWidth = this.currentWidth || this.originalWidth;
const realHeight = this.currentHeight || this.originalHeight;
// 计算缩放比例
const widthScale = currentWidth / realWidth;
const heightScale = currentHeight / realHeight;
// console.log({currentWidth, currentHeight,realWidth,realHeight});
// 若要铺满全屏,则按照各自比例缩放
if (this.fullScreen) {
screenWrapper.style.transform = `scale(${widthScale},${heightScale})`;
return false;
}
// 按照宽高最小比例进行缩放
const scale = Math.min(widthScale, heightScale);
this.handleAutoScale(scale);
},
initMutationObserver() {
const screenWrapper = this.screenWrapper;
const observer = (this.observer = new MutationObserver(() => {
this.onResize();
}));
observer.observe(screenWrapper, {
attributes: true,
attributeFilter: ["style"],
attributeOldValue: true,
});
},
/**
* @description 添加监听事件
*/
addListener() {
window.addEventListener("resize", this.onResize);
},
/**
* @description 移除监听事件
*/
clearListener() {
window.removeEventListener("resize", this.onResize);
},
/**
* @description 清除样式
*/
clearStyle() {
// console.log("清除");
const screenWrapper = this.screenWrapper;
screenWrapper.parentNode.style.overflow = "auto";
screenWrapper.style = "";
},
/**
* @description 初始化并设置自适应
*/
async resize() {
if (!this.selfAdaption) {
return;
}
await this.initSize();
this.updateSize();
this.updateScale();
},
},
mounted() {
this.onResize = debounce(() => {
this.resize();
}, this.delay);
this.$nextTick(() => {
if (this.selfAdaption) {
this.resize();
this.addListener();
}
});
},
beforeDestroy() {
this.clearListener();
// this.observer.disconnect()
},
};
//
</script>
<style scoped lang="scss">
.screen-box {
overflow: hidden;
background-size: 100% 100%;
background: #000;
width: 100vw;
height: 100vh;
}
.screen-wrapper {
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 500ms;
position: relative;
overflow: hidden;
z-index: 100;
transform-origin: left top;
}
</style>
4.使用实列
4.1 flex 布局
<!-- scaleBox + flex基础布局 -->
<template>
<scaleBox
:width="1920"
:height="1080"
class="scale-wrap"
:selfAdaption="selfAdaption"
>
<div class="container p-10 flex fd-column gap-10">
<div class="header e-card"></div>
<div class="body flex flex-1 fd-column gap-10">
<div class="center flex-2 flex fd-row gap-10">
<div class="c-left flex-1 flex fd-column gap-10">
<div class="c-left1 e-card flex-1"></div>
<div class="c-left2 e-card flex-1"></div>
</div>
<div class="c-center flex-2 flex">
<div class="c-center1 e-card flex-1"></div>
</div>
<div class="c-right flex-1 flex fd-column gap-10">
<div class="c-right1 e-card flex-1"></div>
<div class="c-right2 e-card flex-1"></div>
</div>
</div>
<div class="bottom flex-1 flex fd-row gap-10">
<div class="bottom1 e-card flex-1"></div>
<div class="bottom2 e-card flex-2"></div>
<div class="bottom3 e-card flex-1"></div>
</div>
</div>
</div>
</scaleBox>
</template>
<script>
import scaleBox from "@/components/scaleBox";
export default {
components: { scaleBox },
data() {
return {
selfAdaption: true,
};
},
};
</script>
<style scoped lang="scss">
.e-card {
border: 2px solid;
border-image: linear-gradient(to right, #917667, #578aef) 1;
}
.container {
--header-hight: 100px;
height: 100%;
width: 100%;
background: #03213d;
//background-image: url("../../images/bg1.jpg");
background-repeat: no-repeat;
background-position: center center;
background-size: cover;
.header {
height: var(--header-hight);
}
}
</style>
4.2 grid 布局
<!-- scaleBox + grid布局 -->
<template>
<scaleBox
:width="1920"
:height="1080"
class="scale-wrap"
:selfAdaption="selfAdaption"
>
<div class="container p-10">
<div class="header e-card"></div>
<div class="body">
<div class="center">
<div class="c-left">
<div class="c-left1 e-card"></div>
<div class="c-left2 e-card"></div>
</div>
<div class="c-center">
<div class="c-center1 e-card"></div>
</div>
<div class="c-right">
<div class="c-right1 e-card"></div>
<div class="c-right2 e-card"></div>
</div>
</div>
<div class="bottom">
<div class="bottom1 e-card"></div>
<div class="bottom2 e-card"></div>
<div class="bottom3 e-card"></div>
</div>
</div>
</div>
</scaleBox>
</template>
<script>
import scaleBox from "@/components/scaleBox";
export default {
components: { scaleBox },
data() {
return {
selfAdaption: true,
};
},
};
</script>
<style scoped lang="scss">
.e-card {
border: 2px solid;
border-image: linear-gradient(to right, #917667, #578aef) 1;
}
.container {
--header-hight: 100px;
height: 100%;
width: 100%;
display: grid;
grid-template-rows: var(--header-hight) auto;
row-gap: 10px;
background: #03213d;
//background-image: url("../../images/bg1.jpg");
background-repeat: no-repeat;
background-position: center center;
background-size: cover;
.header {
}
.body {
height: 100%;
display: grid;
grid-template-rows: 2fr 1fr;
row-gap: 10px;
.center {
display: grid;
grid-template-columns: 1fr 2fr 1fr;
column-gap: 10px;
.c-left {
display: grid;
grid-template-rows: 1fr 1fr;
row-gap: 10px;
}
.c-center {
display: grid;
grid-template-rows: 1fr;
}
.c-right {
display: grid;
grid-template-rows: 1fr 1fr;
row-gap: 10px;
}
}
.bottom {
display: grid;
grid-template-columns: 1fr 2fr 1fr;
column-gap: 10px;
}
}
}
</style>