vue版本大屏适配组件
2022-04-15 本文已影响0人
小俊的世界
起因
最近开发一个信息管理大屏,屏幕尽寸为2560*1080。
公司内部的有一些项目上对于大屏是直接定死宽度与高度的,这样在开发时,特别是针对这种特大屏,开发人员很难有整体感受。还有一些是进行了适配的处理,但是是针对于当前的项目进行处理的,并且还没有沉淀出工具可以直接复用。
适配方案
利用rem进行布局,最著名的就是淘宝的flexable布局。其核心的原理,就是更改更路径下的 font-size,然后,使用的大小全部从px转换为rem,这需要开发人员进行计算,还可以利用css预处理语言中(项目中使用是scss)的高级功能来实现px2rem。但是整体上来说,还是比较复杂的。
利用百分比,同样这里开发人员也就是需要进行计算的。
第三种,利用scale进行缩放处理,也就是本组件所使用的方式。
注意点
但是对于中间的区域 ,我们通过是使用加载模型,或者地图相关,此处是不能直接使用scale进行区域缩放的,只处理对宽高进行比例计算,它会自动适配。
核心技术
scale
scale是css3中的属性。一般情况下默认缩放中心点,是图形的中心点,但是在使用translate(-50%,-50%)时,需要将默认缩放中心点变为左上角。
transform:scale(0.5);
transform-origin:0 0;
css变量
css变量是可以由开发者进行自定义,必须要以 -- 开头的,然后利用var()函数在其它css属性中使用,它是对大写小敏感的。
element{
--color:red;
}
div{
color:var(--color)
}
如何利用js来操作css变量?
方式一:直接内联到元素中
document.body.style.setProperty(--color
, 'red')
方式二:创建style 再来插入
const styleEl = documente.createElement('style')
styleEl.innerHTML = `
:root{
--color:red;
}
`
document.body.append(styleEl)
水平垂直居中
因为涉及到多层 所以可以采用定位的方式
<div class="parent">
<div class="son"></div>
</div>
<style>
.parent{
position:relative;
width:100%;
height:100vh;
}
.son{
position:absolute;
left:50%;
top:50%;
transform:translate(-50%,-50%);
}
</style>
组件使用
class="ssfc-screen"
prop-name="ssfc-screen-scale"
adpter-class="adpter-area"
:width="width"
:height="height"
:resize-listenter="resizeListenter"
>
<cesium-main slot="main"></cesium-main>
<div>
<div v-show="ifShow">
<top-bar></top-bar>
<!-- 左侧面板-->
<left></left>
<!-- 右侧侧面板-->
<right></right>
<!-- 底部 -->
<bottom></bottom>
<!-- ./staticData/imgs/allScreen.png -->
<!-- 模型上其余部分 -->
<remainingAreas class="reset-events"></remainingAreas>
</div>
<img
:src="picUrl"
alt=""
class="reset-events"
:class="ifShow ? 'imgFull' : 'imgPart'"
@click="
ifShow = !ifShow
getFullScreen()
"
/>
</div>
</AdpterScreen>
slot="main" 主区域不进行缩放 其它区域进行缩放
propName css变量名 注意不需要加 --
adpterClass 适配类 可供弹框等使用
width height 设计稿宽高
resizeListenter risize事件监听里面回调参数为当前缩放值
源码
<!--
* @Description
* @Autor 朱俊
* @Date 2022-04-13 17:19:05
* @LastEditors: wangs
* @LastEditTime: 2022-04-14 18:03:24
-->
<template>
<div class="big-screen-wrapper" ref="containerRef">
<div class="main-wrapper" ref="mainRef">
<slot name="main"></slot>
</div>
<div class="layer-wrapper" ref="layerRef">
<slot />
</div>
</div>
</template>
<script>
import ScaleLayout from './scaleLayout'
export default {
props: {
propName: {
type: String,
default: 'scale' + new Date().getTime()
},
width: {
type: Number,
default: 1920
},
height: {
type: Number,
default: 1080
},
adpterClass: {
type: String,
default: 'apter-area'
},
resizeListenter: {
type: Function,
default: () => {
return () => {}
}
}
},
data() {
return {}
},
mounted() {
this.$nextTick(() => {
const container = document.body
const mainEl = this.$refs['mainRef']
const layerEl = this.$refs['layerRef']
new ScaleLayout({
context: this,
propName: this.propName,
width: this.width,
height: this.height,
container,
mainEl,
layerEl,
adpterClass: this.adpterClass,
resizeListenter: this.resizeListenter
})
})
}
}
</script>
<style lang="scss" scoped>
.big-screen-wrapper {
width: 100%;
height: 100vh;
position: relative;
background: #000;
overflow: hidden;
.main-wrapper {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
.layer-wrapper {
position: absolute;
left: 50%;
top: 50%;
overflow: hidden;
pointer-events: none; // 图层事件穿透
* {
// 其它事件恢复
pointer-events: auto;
}
}
}
</style>
/*
* @Description
* @Autor 朱俊
* @Date 2022-04-13 17:19:27
* @LastEditors 朱俊
* @LastEditTime 2022-04-14 23:05:19
*/
// 防抖
function debounce(fn, t) {
const delay = t || 500
let timer
return (...args) => {
if (timer) {
clearTimeout(timer)
}
const context = this
timer = setTimeout(() => {
timer = null
fn.apply(context, args)
}, delay)
}
}
class ScaleLayout {
constructor({
context,
width,
height,
container,
propName,
mainEl,
layerEl,
adpeterClass,
resizeListenter
}) {
this.styleEl = null
this.scale = 1 // 默认初始缩放1
this.context = context
this.width = width // 设计稿宽度
this.height = height // 设计稿高度
this.container = container || document.body
this.propName = propName || 'scale'
this.mainEl = mainEl
this.layerEl = layerEl
this.adpeterClass = adpeterClass || 'adpter-area'
this.resizeListenter = resizeListenter || (() => {})
this.dealLayout()
this.setScale()
this.resizeListenter(this.scale)
this.listen()
}
// 处理布局
dealLayout() {
// 处理main区域
this.mainEl.style = `
width: calc(${this.width}px * var(--${this.propName}));
height: calc(${this.height}px * var(--${this.propName}));
`
// 处理图层
this.layerEl.style = `
width: ${this.width}px;
height: ${this.height}px;
transform: scale(var(--${this.propName})) translate(-50%,-50%);
transform-origin:0 0;
`
// 动态生成适配类
this.styleEl = document.createElement('style')
this.styleEl.innerHTML = `
.${this.adpeterClass} {
transform: scale(var(--${this.propName}));
transform-origin:0 0;
}
`
this.container.append(this.styleEl)
}
// 页面生命周期及浏览器大小监听
listen() {
this.onresize = debounce(() => {
this.setScale()
this.resizeListenter(this.scale)
}, 500)
window.addEventListener('resize', this.onresize)
this.context.$on('hook:beforeDestroy', () => {
window.removeEventListener('resize', this.onresize)
this.styleEl && this.container.removeChild(this.styleEl)
})
}
// 设置缩放
dealScale() {
const ws = window.innerWidth / this.width
const hs = window.innerHeight / this.height
return ws < hs ? ws : hs
}
// 获取scale值
getScale() {
return this.scale
}
// 设置scale
setScale() {
this.scale = this.dealScale()
this.container.style.setProperty(`--${this.propName}`, this.scale)
}
}
export default ScaleLayout