前言
在日常开发过程中,有时分咱们会希望全屏一个元素。可是,仅仅是希望它是处于网页全屏状况,而不是浏览器全屏状况。这时分,咱们能够统一封装一个 useElementFullscreen
办法,来完成这个功用。
办法剖析
在完成这个功用之前,咱们先剖析一下咱们需求这个办法做什么?
功用需求剖析
希望传递一个元素,然后这个元素就能够全屏显现。而且仅仅是运用 js + css
完成。
办法设计
- 参数一:元素
- 参数二:办法配置项
完成前需求考虑的问题
在完成这个功用之前,咱们需求考虑一下,咱们需求处理哪些问题?
怎么全屏一个元素
咱们剖析一下,怎么运用 css + js
全屏一个元素?
肯定是先获取到元素,然后将其宽高铺满整个屏幕,然后将其 position
设置为 fixed
,然后将其 top
和 left
设置为 0
,这样就能够完成全屏了。
怎么退出全屏
将所有的款式还原即可。
款式注入与特点注入或许产生的问题
由所以手动经过 css + js
的办法,使一个元素完成铺满效果,所以,咱们需求考虑一下,如果该元素本身包括一些基础款式,那么咱们的款式注入或许会掩盖掉这些款式,导致元素款式错乱。
怎么处理这个问题是一个需求考虑的问题。咱们后续会在完成的时分,考虑这个问题。
完成
辅佐函数
import type { ComponentPublicInstance } from 'vue'
export type TargetValue<T> = T | undefined | null
export type TargetType =
| HTMLElement
| Element
| SVGElement
| Window
| Document
| ComponentPublicInstance
export type BasicTarget<T extends TargetType = Element> =
| (() => TargetValue<T>)
| TargetValue<T>
| Ref<TargetValue<T>>
/**
*
* @param target 获取 ref dom, vue instance 的 dom
* @param defaultTarget 默认值
*
* @example
* <template>
* <div ref="refDom"></div>
* </template>
*
* const refDom = ref<HTMLElement | null>(null)
* const computedDom = computed(() => refDom.value)
*
* unrefElement(refDom) => div
* unrefElement(computedDom) => div
*/
function unrefElement<T extends TargetType>(
target: BasicTarget<T>,
defaultElement?: T,
) {
if (!target) {
return defaultElement
}
let targetElement: TargetValue<T>
if (typeof target === 'function') {
targetElement = target()
} else if (isRef(target)) {
targetElement =
(target.value as ComponentPublicInstance)?.$el ?? target.value
} else {
targetElement = target
}
return targetElement
}
/**
*
* @param fc effect 效果域卸载时需执行函数
*
* @remark 返回 true 表明获取到 effect 效果域而且卸载;false 表明未存在 effect 效果域
*/
export function effectDispose<T extends (...args: any[]) => any>(fc: T) {
if (getCurrentScope()) {
onScopeDispose(fc)
return true
}
return false
}
正式完成
import { isUndefined, isNull } from 'lodash-es'
import { useWindowSize } from '@vueuse/core'
export interface UseElementFullscreenOptions {
beforeEnter?: () => void
beforeExit?: () => void
zIndex?: number
backgroundColor?: string
}
let currentZIndex = 999
let isAppend = false
const ID_TAG = 'ELEMENT-FULLSCREEN-RAY'
const { height } = useWindowSize() // 获取实践高度防止 100vh 会导致手机端浏览器获取不精确问题
const styleElement = document.createElement('style')
export const useElementFullscreen = (
target: BasicTarget,
options?: UseElementFullscreenOptions,
) => {
const { beforeEnter, beforeExit, backgroundColor, zIndex } = options ?? {}
const cacheStyle: Partial<CSSStyleDeclaration> = {} // 缓存一些需求被掩盖的款式,例如: transition
let isSetup = false
const updateStyle = () => {
const element = unrefElement(target) as HTMLElement | null
if (!element) {
return
}
const { left, top } = element.getBoundingClientRect()
const cssContent = `
#${ID_TAG} {
position: fixed;
width: 100% !important;
height: ${height.value}px !important;
transform: translate(-${left}px, -${top}px) !important;
transition: all 0.3s var(--r-bezier);
z-index: ${
isValueType<null>(zIndex, 'Null') ||
isValueType<undefined>(zIndex, 'Undefined')
? currentZIndex
: zIndex
} !important;
background-color: ${backgroundColor ?? ''};
}
`
styleElement.innerHTML = cssContent
// 防止重复添加 style 标签
if (!isAppend) {
document.head.appendChild(styleElement)
}
}
const enter = () => {
const element = unrefElement(target) as HTMLElement | null
beforeEnter?.()
if (element) {
if (!element.getAttribute(ID_TAG)) {
element.setAttribute(ID_TAG, ID_TAG)
}
if (!isSetup) {
isSetup = true
currentZIndex += 1
}
if (!isAppend) {
updateStyle()
isAppend = true
}
cacheStyle.transition = element.style.transition
element.style.transition = 'all 0.3s var(--r-bezier)'
element.setAttribute('id', ID_TAG)
}
}
const exit = () => {
beforeExit?.()
const element = unrefElement(target)
if (element) {
element.removeAttribute('id')
element.removeAttribute(ID_TAG)
}
}
const toggleFullscreen = () => {
const element = unrefElement(target)
if (element) {
if (element.getAttribute(ID_TAG)) {
exit()
} else {
enter()
}
}
}
const stopWatch = watch(() => height.value, updateStyle)
effectDispose(() => {
const element = unrefElement(target) as HTMLElement | null
if (element) {
element.style.transition = cacheStyle.transition ?? ''
element.removeAttribute(ID_TAG)
element.removeAttribute('id')
}
stopWatch()
})
return {
enter,
exit,
toggleFullscreen,
}
}
代码解说
怎么处理款式注入与特点注入或许产生的问题
在完成的时分注入了一个 style
标签,这个标签的效果是用来掩盖元素的款式,使其全屏。
而且关于传递元素注入了 id
, ID_TAG
特点。
currentZIndex
这个变量的效果是用来记录当前全屏元素的 z-index
值,每次全屏一个元素,都会自增 1
。
isAppend
这个变量的效果是用来记录 style
标签是否已经被添加到 head
中。
isSetup
标记是否是初始化状况,如果是初始化状况,那么 currentZIndex
就会自增 1
。
运用演示
<template>
<div ref="demoRef">
<div @click="enter">全屏</div>
<div @click="exit">退出全屏</div>
<div @click="toggleFullscreen">切换全屏</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const { enter, exit, toggleFullscreen } = useElementFullscreen(demoRef)
</script>
最终
其实该办法还是有一些问题没有处理:
- 会注入
id
,导致掩盖原有的id
,或许会导致一些问题 - 全局注入了一个
style
标签,或许会导致一些问题 - 办法侵略性很强,而且需求开发者自己考虑
dom
元素结构。否则在调用的时分会出现因为款式掩盖导致款式错乱的问题
而且,这个办法通用性没有那么强。适合在项目中提前约定好某些功用运用,而且在运用的时分,需求开发者自己已经考虑了一些问题。
如果大家有更好的思路,请多多指教~