前端全屏显示解决方案分享

本文分享的内容是前端全屏显示的解决方案,来源于103项目中的需求。主要是将开发过程中遇到问题,解决问题的思路分享出来,对于后面遇到相同的业务场景时可以借鉴一下,提高我们的开发效率,少走一些弯路。

需求背景
  • 用户需要针对车辆的行驶路径与收费路径以及拆分路径中的费用进行比对分析,由于交互设计问题导致用户无法同时比对较多的数据,如下图:

    在这里插入图片描述

  • 用户在门架流水中需要操作门架数据进行二次计算,同样是交互设计问题无法给到用户较大的操作空间,如下图:

    在这里插入图片描述

基于以上的需求,需要前端在项目中加入全屏按钮,便于给到用户较大的空间进行比对和操作,那么下面就将给出在开发过程中我用到的解决方案,以及方案的可行性。

  1. 使用 requestFullscreenexitFullscreen API

    通过浏览器的全屏 API(Fullscreen API)来控制页面或某个元素进入或退出全屏模式。这也是最开始我才用的方案,具体的代码实现如下:

    <template>
    	......
    	<div
    		class="tab"
    		:class="{ 'fullscreen-tab': isFullscreen }"
    		id="analysis-tab"
    	>
    		......
    	</div>
    	...
    </template>
    
    <script lang='ts'>
    export default defineComponent({
        setup(props) {
            ......;
            
            const isFullscreen = ref(false);
    		const toggleFullscreen = () => {
    			const elem = document.getElementById("analysis-tab");
    			if (!document.fullscreenElement) {
    				isFullscreen.value = true;
    				if (elem.requestFullscreen) {
    					elem.requestFullscreen();
    				}
    			} else {
    				isFullscreen.value = false;
    				if (document.exitFullscreen) {
    					document.exitFullscreen();
    				}
    			}
    		};
    		const handleFullscreenChange = () => {
    			isFullscreen.value = !!document.fullscreenElement;
    		};
    
    		onMounted(() => {
    			// 监听键盘事件
    			document.addEventListener(
    				"fullscreenchange",
    				handleFullscreenChange
    			);
    			document.addEventListener(
    				"webkitfullscreenchange",
    				handleFullscreenChange
    			);
    			document.addEventListener(
    				"mozfullscreenchange",
    				handleFullscreenChange
    			);
    			document.addEventListener(
    				"MSFullscreenChange",
    				handleFullscreenChange
    			);
    			getFeeBasicInfo(props.transId, props.transDate);
    		});
    
    		onBeforeUnmount(() => {
    			// 移除事件监听
    			document.removeEventListener(
    				"fullscreenchange",
    				handleFullscreenChange
    			);
    			document.removeEventListener(
    				"webkitfullscreenchange",
    				handleFullscreenChange
    			);
    			document.removeEventListener(
    				"mozfullscreenchange",
    				handleFullscreenChange
    			);
    			document.removeEventListener(
    				"MSFullscreenChange",
    				handleFullscreenChange
    			);
    		});
            
            return {
                ...,
                isFullscreen,
                toggleFullscreen,
    		}
    	}
    });
    </script>
    

    这种解决方案是大部分前端首先能够想到的,那么这种方案并不是适用大多数场景,对于仅仅需要全屏展示的业务场景来说是可以满足的,但是遇到目标元素全屏后,内部有其他的操作:点击按钮展示弹窗、数据的增删改查等,这些需求是不能满足的。

    比如103中的需求,用户点击全屏按钮后,目标元素全屏展示;用户点击新增门架,此时会有一个弹窗展示,但是实际情况是弹窗元素已经在body元素中出现,但是在页面中无法显示。如下图:

在这里插入图片描述

原因是:浏览器在全屏模式下通常会屏蔽掉一些页面交互和 UI 元素,以减少干扰。当你进入全屏模式时,requestFullscreen 会把目标元素的 z-index 设置为较高,可能会导致其他弹窗无法显示在其之上。因为全屏模式本质上覆盖了整个视口,导致无法正常显示任何其他 UI 元素。

这里需要注意的是:修改层级z-index是没有用的,仍然不能在全屏之后展示弹窗,如下图所示:

在这里插入图片描述

所以在第一版开发的时候,通过变量判断是否进入全屏模式,全屏模式下禁用按钮的点击操作,后续用户要求既要能够全屏展示,同时能够进行其他操作。

  1. 封装全屏组件

    应用户需求,调整方案,我想到的是封装全屏组件,就是类似于UI库的弹窗组件,当用户点击全屏按钮时,显示全屏弹窗组件。这样做看起来也是满足需求的,同时也可以解决前面描述的问题,具体实现步骤如下:

    • 将需要全屏的内容封装成组件,进行复用

    • 引入全屏组件,代码如下:

      <div class="tab__div_height-600">
      	<tab-analysis
      		ref="tatbAnalysisRef"
      		:activeName="activeName"
      		:feeBasicInfo="feeBasicInfo"
      		:isFullscreen="tabAnalysisStatus"
      		@updateActiveName="updateActiveName"
      		@updateTabAnalysisStatus="updateTabAnalysisStatus"
      	/>
      </div>
      ......  
      <c-full-screen v-model="tabAnalysisStatus" :close-btn-status="false">
      	<tab-analysis
      		ref="tatbAnalysisRef"
      		:activeName="activeName"
      		:feeBasicInfo="feeBasicInfo"
      		:isFullscreen="tabAnalysisStatus"
      		@updateActiveName="updateActiveName"
      		@updateTabAnalysisStatus="updateTabAnalysisStatus"
      	/>
      </c-full-screen>
      
      <!--
       @author: duanfc
       @time: 2024-11-15 15:40:00
       @description: 全屏弹窗组件
       @path: /
       @lastChange: duanfc
      -->
      
      <template>
      	<transition name="fade">
      		<div
      			v-if="visible"
      			class="fullscreen-dialog-overlay"
      			@click.self="close"
      		>
      			<div class="fullscreen-dialog-content">
      				<button
      					class="fullscreen-dialog-close"
      					@click="close"
      					v-if="closeBtnStatus"
      				>
      					×
      				</button>
      				<div class="fullscreen-dialog-body">
      					<slot></slot>
      				</div>
      			</div>
      		</div>
      	</transition>
      </template>
      
      <script>
      export default {
      	name: "fullScreenDialog",
      	components: {},
      	props: {
      		value: {
      			type: Boolean,
      			default: false,
      		},
      		closeBtnStatus: {
      			type: Boolean,
      			default: true,
      		},
      	},
      	data() {
      		return {
      			visible: this.value,
      		};
      	},
      	watch: {
      		value(newVal) {
      			this.visible = newVal;
      			if (newVal) {
      				document.body.appendChild(this.$el);
      			}
      		},
      	},
      	computed: {},
      	methods: {
      		close() {
      			this.visible = false;
      			this.$emit("input", false); // 双向绑定
      		},
      	},
      	created() {},
      	mounted() {
      		if (this.visible) {
      			document.body.appendChild(this.$el);
      		}
      	},
          destroyed() {
              if (this.$el && this.$el.parentNode) {
                  this.$el.parentNode.removeChild(this.$el);
              }
          }
      };
      </script>
      
      <style scoped>
      .fullscreen-dialog-overlay {
      	position: fixed;
      	top: 0;
      	left: 0;
      	width: 100vw;
      	height: 100vh;
      	background: rgba(0, 0, 0, 0.3);
      	display: flex;
      	justify-content: center;
      	align-items: center;
      	z-index: 9999;
      }
      .fullscreen-dialog-content {
      	position: relative;
      	width: 100%;
      	height: 100%;
      	background: white;
      	overflow: auto;
      	display: flex;
      	flex-direction: column;
      }
      
      .fullscreen-dialog-close {
      	position: absolute;
      	top: 20px;
      	right: 20px;
      	background: none;
      	border: none;
      	font-size: 30px;
      	color: #333;
      	cursor: pointer;
      }
      
      .fullscreen-dialog-body {
      	flex-grow: 1;
      	padding: 16px 24px;
      	overflow-y: auto;
      }
      .fade-enter-active,
      .fade-leave-active {
      	transition: opacity 0.5s ease-in-out;
      }
      
      .fade-enter,
      .fade-leave-to {
      	opacity: 0;
      }
      </style>
      

      tab-analysis组件是需要比对的模块,封装为组件,在c-full-screen全屏组件中二次复用。看起来写法没有问题,但是会有一个问题,就是全屏操作无法是没有记忆功能的:当用户现在页面中进行操作,然后中途点击全屏按钮,此时的弹窗中是不能记录到先前的用户操作,是一个初始的内容。如下:

      在这里插入图片描述

      当然了,如果较真非要通过这种方式实现用户需求,也许可以实现,但是对于前端来说处理的逻辑太多,复杂性也比较高;对于门架的全屏需求使用这种方式是比较符合的,因为内部操作较少,便于对数据进行控制,综合评估还是不推荐采用此方式。

    1. CSS的position属性

      在 CSS 中,position: fixed 是一种常见的布局方式,用于将元素定位到浏览器窗口的固定位置,而不随着页面的滚动而移动。固定定位使元素始终保持在视口内,不管页面滚动与否。这个方案是从上面的方法中衍生出来的,通过position: fixed将需要全屏的内容脱离文档流,然后通过z-index控制层级,就可以完美解决我们的问题。

      具体步骤如下:

      • 将需要全屏展示的组件及其父元素设置id属性
      • 通过原生js操作DOM的方式,动态修改目标元素的css属性,移动DOM

      代码如下:

      <div
      	class="rate-visualization"
      	v-loading="loading"
      	element-loading-text="加载中..."
      	:element-loading-spinner="$loadingStyle"
      	id="rate-visualization"
      >
      	<!-- 收费基础信息 -->
      	.....
      	
      	<div
      		class="tab"
      		:class="{ 'fullscreen-tab': isFullscreen }"
      		id="analysis-tab"
      	>
      		<!-- 全屏模块 -->
      	</div>
      </div>
      
          
      const isFullscreen = ref(false);
      const toggleFullscreen = () => {
      	const elem = document.getElementById("analysis-tab");
      	if (!isFullscreen.value) {
      		isFullscreen.value = true;
      		document.body.appendChild(elem);
      	} else {
      		isFullscreen.value = false;
      		const parent = document.getElementById("rate-visualization");
      		const children = parent.children;
      		parent.insertBefore(elem, children[1]);
      	}
      };
      
      <style lang="less">
      .fullscreen-tab {
          height: 100%;
          width: 100%;
          position: fixed;
          top: 0;
          left: 0;
          z-index: 999;
      	background-color: #fff;
          .tab {
      		height: 100%;
      		width: 100%;
      		padding-top: 6px;
      		position: relative;
      		.el-tabs__header {
      			// margin: 0;
                  padding-top: 6px;
      			.el-tabs__nav-scroll {
      				padding-left: 10px;
      				.el-tabs__nav {
      					border: none !important;
      					.el-tabs__item {
      						border: none !important;
      						color: #409eff;
      						&.is-active {
      							background: linear-gradient(
      								180deg,
      								#83a0fd 0%,
      								#2552dd 100%
      							);
      							border-radius: 10px 10px 0px 0px;
      							color: #fff;
      						}
      					}
      				}
      			}
      		}
      		.el-tabs__content {
      			height: calc(100% - 56px);
      			padding: 0 20px 20px;
      			.el-tab-pane {
      				height: 100%;
      			}
      		}
      	}
          .change-compare-mode {
              position: absolute;
              right: 50px;
              top: 12px;
              width: 106px;
              height: 32px;
              line-height: 32px;
          }
          .el-icon-full-screen {
              font-size: 20px;
              margin-top: 11px;
              margin-left: 8px;
              cursor: pointer;
              position: absolute;
              top: 8px;
              right: 20px;
          }
          .exit-fullscreen {
              height: 24px;
              width: 24px;
              margin-top: 9px;
              margin-left: 8px;
              cursor: pointer;
              position: absolute;
              top: 8px;
              right: 20px;
          }
      }
      </style>
      
      1. 当用户点击全屏时,动态设置position: fixed脱离文档流,设置全屏展示
      2. 点击取消展示时,移除position: fixed属性,同时需要将该元素移动到原来的位置

      [!CAUTION]

      需要注意的是样式设置,由于全屏后脱离文档流,因此属性需要全局设置,要保证样式的唯一性,避免被影响

      完整效果如下:
      在这里插入图片描述

      总结

      三种方案适用的场景是不一样的,前两种方案的适用性需要根据业务需求具体情况具体分析,不能兼容所有场景;第三种方案是相对来说适用性较强的,也有不足之处,就是动态控制DOM会造成重绘,对于性能会有一点影响。从复杂性和实用性考虑,首选方案3

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值