247 lines
6.3 KiB
Vue
247 lines
6.3 KiB
Vue
<template>
|
|
<section
|
|
:style="{ ...styles.box, ...boxStyle }"
|
|
class="v-screen-box"
|
|
ref="box"
|
|
>
|
|
<div
|
|
:style="{ ...styles.wrapper, ...wrapperStyle }"
|
|
class="screen-wrapper"
|
|
ref="screenWrapper"
|
|
>
|
|
<slot></slot>
|
|
</div>
|
|
</section>
|
|
</template>
|
|
<script lang="ts" setup>
|
|
import { nextTick, onMounted, onUnmounted, reactive, ref, watch } from "vue";
|
|
import type { CSSProperties, PropType } from "vue";
|
|
/**
|
|
* 防抖函数
|
|
* @param {Function} fn
|
|
* @param {number} delay
|
|
* @returns {() => void}
|
|
*/
|
|
function debounce(fn: Function, delay: number): () => void {
|
|
// let timer: NodeJS.Timer;
|
|
let timer: any;
|
|
return function (...args: any[]): void {
|
|
if (timer) clearTimeout(timer);
|
|
timer = setTimeout(
|
|
() => {
|
|
typeof fn === "function" && fn.apply(null, args);
|
|
clearTimeout(timer);
|
|
},
|
|
delay > 0 ? delay : 100
|
|
);
|
|
};
|
|
}
|
|
|
|
interface IState {
|
|
originalWidth: string | number;
|
|
originalHeight: string | number;
|
|
width?: string | number;
|
|
height?: string | number;
|
|
observer: null | MutationObserver;
|
|
}
|
|
type IAutoScale =
|
|
| boolean
|
|
| {
|
|
x?: boolean;
|
|
y?: boolean;
|
|
};
|
|
const props = defineProps({
|
|
width: {
|
|
type: [String, Number] as PropType<string | number>,
|
|
default: 1920,
|
|
},
|
|
height: {
|
|
type: [String, Number] as PropType<string | number>,
|
|
default: 1080,
|
|
},
|
|
fullScreen: {
|
|
type: Boolean as PropType<boolean>,
|
|
default: false,
|
|
},
|
|
autoScale: {
|
|
type: [Object, Boolean] as PropType<IAutoScale>,
|
|
default: true,
|
|
},
|
|
delay: {
|
|
type: Number as PropType<number>,
|
|
default: 500,
|
|
},
|
|
boxStyle: {
|
|
type: Object as PropType<CSSProperties>,
|
|
default: () => ({}),
|
|
},
|
|
wrapperStyle: {
|
|
type: Object as PropType<CSSProperties>,
|
|
default: () => ({}),
|
|
},
|
|
});
|
|
|
|
const state = reactive<IState>({
|
|
width: 0,
|
|
height: 0,
|
|
originalWidth: 0,
|
|
originalHeight: 0,
|
|
observer: null,
|
|
});
|
|
|
|
const styles: Record<string, CSSProperties> = {
|
|
box: {
|
|
overflow: "hidden",
|
|
backgroundSize: `100% 100%`,
|
|
background: `#000`,
|
|
width: `100vw`,
|
|
height: `100vh`,
|
|
},
|
|
wrapper: {
|
|
transitionProperty: `all`,
|
|
transitionTimingFunction: `cubic-bezier(0.4, 0, 0.2, 1)`,
|
|
transitionDuration: `500ms`,
|
|
position: `relative`,
|
|
overflow: `hidden`,
|
|
zIndex: 100,
|
|
transformOrigin: `left top`,
|
|
},
|
|
};
|
|
|
|
const screenWrapper = ref<HTMLElement>();
|
|
const box = ref<HTMLElement>();
|
|
|
|
watch(
|
|
() => props.autoScale,
|
|
async (newVal: any) => {
|
|
if (newVal) {
|
|
onResize();
|
|
addListener();
|
|
} else {
|
|
clearListener();
|
|
clearScreenWrapperStyle();
|
|
}
|
|
}
|
|
);
|
|
/**
|
|
* 初始化大屏容器宽高
|
|
*/
|
|
const initSize = () => {
|
|
return new Promise<void>((resolve) => {
|
|
box.value!.scrollLeft = 0;
|
|
box.value!.scrollTop = 0;
|
|
nextTick(() => {
|
|
// region 获取大屏真实尺寸
|
|
if (props.width && props.height) {
|
|
state.width = props.width;
|
|
state.height = props.height;
|
|
} else {
|
|
state.width = screenWrapper.value?.clientWidth;
|
|
state.height = screenWrapper.value?.clientHeight;
|
|
}
|
|
// endregion
|
|
|
|
// region 获取画布尺寸
|
|
if (!state.originalHeight || !state.originalWidth) {
|
|
state.originalWidth = window.screen.width;
|
|
state.originalHeight = window.screen.height;
|
|
}
|
|
// endregion
|
|
resolve();
|
|
});
|
|
});
|
|
};
|
|
|
|
/**
|
|
* 更新大屏容器宽高
|
|
*/
|
|
const updateSize = () => {
|
|
if (state.width && state.height) {
|
|
screenWrapper.value!.style.width = `${state.width}px`;
|
|
screenWrapper.value!.style.height = `${state.height}px`;
|
|
} else {
|
|
screenWrapper.value!.style.width = `${state.originalWidth}px`;
|
|
screenWrapper.value!.style.height = `${state.originalHeight}px`;
|
|
}
|
|
};
|
|
const clearScreenWrapperStyle = () => {
|
|
screenWrapper.value!.style.transform = "";
|
|
screenWrapper.value!.style.margin = "";
|
|
};
|
|
const autoScale = (scale: number) => {
|
|
if (!props.autoScale) {
|
|
return;
|
|
}
|
|
const domWidth = screenWrapper.value!.clientWidth;
|
|
const domHeight = screenWrapper.value!.clientHeight;
|
|
const currentWidth = document.body.clientWidth;
|
|
const currentHeight = document.body.clientHeight;
|
|
screenWrapper.value!.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 props.autoScale === "object") {
|
|
!props.autoScale.x && (mx = 0);
|
|
!props.autoScale.y && (my = 0);
|
|
}
|
|
screenWrapper.value!.style.margin = `${my}px ${mx}px`;
|
|
};
|
|
const updateScale = () => {
|
|
// 获取真实视口尺寸
|
|
const currentWidth = document.body.clientWidth;
|
|
const currentHeight = document.body.clientHeight;
|
|
// 获取大屏最终的宽高
|
|
const realWidth = state.width || state.originalWidth;
|
|
const realHeight = state.height || state.originalHeight;
|
|
// 计算缩放比例
|
|
const widthScale = currentWidth / +realWidth;
|
|
const heightScale = currentHeight / +realHeight;
|
|
// 若要铺满全屏,则按照各自比例缩放
|
|
if (props.fullScreen) {
|
|
screenWrapper.value!.style.transform = `scale(${widthScale},${heightScale})`;
|
|
return false;
|
|
}
|
|
// 按照宽高最小比例进行缩放
|
|
const scale = Math.min(widthScale, heightScale);
|
|
autoScale(scale);
|
|
};
|
|
|
|
const onResize = debounce(async () => {
|
|
await initSize();
|
|
updateSize();
|
|
updateScale();
|
|
}, props.delay);
|
|
const initMutationObserver = () => {
|
|
const observer = (state.observer = new MutationObserver(() => {
|
|
onResize();
|
|
}));
|
|
observer.observe(screenWrapper.value!, {
|
|
attributes: true,
|
|
attributeFilter: ["style"],
|
|
attributeOldValue: true,
|
|
});
|
|
};
|
|
|
|
const clearListener = () => {
|
|
window.removeEventListener("resize", onResize);
|
|
// state.observer?.disconnect();
|
|
};
|
|
|
|
const addListener = () => {
|
|
window.addEventListener("resize", onResize);
|
|
// initMutationObserver();
|
|
};
|
|
onMounted(() => {
|
|
nextTick(async () => {
|
|
await initSize();
|
|
updateSize();
|
|
updateScale();
|
|
addListener();
|
|
// initMutationObserver();
|
|
});
|
|
});
|
|
onUnmounted(() => {
|
|
clearListener();
|
|
// state.observer?.disconnect();
|
|
});
|
|
</script>
|