项目中有一个需求,搜索框的宽度需要根据页面宽度动态变化,例如宽度小于等于1280时搜索框的宽度为280px,大于1920时宽度为360px,在1280和1920之间则根据比例缩放。

由于页面是flex布局,并且靠两端对齐的,所以给搜索框设置 min-widthmax-width 基本上是排不上用场了。当时首先想到的是 媒体查询 ,但是 媒体查询 不能实现按比例缩放的效果。后来想到通过 JS 获取页面的 innerWidth ,然后根据这个值来判断搜索框应该取多少宽度。

那么这个逻辑应该放在什么位置呢?先在计算属性里面试了下,代码如下:

<template>
	<el-input v-model="search" :style="{'width': `${searchFormWidth}px`}"
</template>

<script>
export default {
	computed: {
		searchFormWidth() {
	      let w = window.innerWidth;
	      if(w <= 1280) {
	        return 280
	      } else if (w < 1920) {
	        return ((w - 1280)*80)/640 + 280
	      } else {
	        return 360
	      }
	    }
	}
}
</script>

计算属性具有缓存,并且依赖改变后,会重新计算,所以经常用于动态样式绑定。但是经过试验后发现, searchFormWidth 只会在组件初始化的时候调用一次,之后页面缩放无法改变搜索框宽度。

看来计算属性只能监听响应式依赖的变化,对于 window 对象属性的变化无法检测。那就另辟蹊径,通过监听浏览器的窗口缩放事件 window.onresize ,在组件的 mounted 钩子里面绑定监听回调,然后在 methods 中定义 searchFormWidth 方法动态计算宽度。

<template>
	<el-input v-model="search" :style="{'width': `${searchWidth}px`}"
</template>

<script>
export default {
	data() {
		return {
			searchWidth: 280
		}
	}
	mounted() {
		this.searchFormWidth(); // 组件初始化的时候不会触发onresize事件,这里强制执行一次
	    window.onresize = () => {
	      if(!this.timer){ // 使用节流机制,降低函数被触发的频率
	        this.timer = true;
	        let that = this; // 匿名函数的执行环境具有全局性,为防止this丢失这里用that变量保存一下
	        setTimeout(function(){
	          that.searchFormWidth();
	          that.timer = false;
	        },400)
	      }
	    }
	}
	destroyed() {
		// 组件销毁后解绑事件
		window.onresize = null;
	}
	methods: {
		searchFormWidth() {
	      let w = window.innerWidth;
	      if(w <= 1280) {
	        this.searchWidth = 280
	      } else if (w < 1920) {
	        this.searchWidth = ((w - 1280)*80)/640 + 280
	      } else {
	        this.searchWidth = 360
	      }
	    }
	}
}
</script>

这边有个注意点,也就是在组件销毁后需要手动解绑事件

在 Vue 中通过 v-on 绑定的事件,在组件销毁后会自动解绑。

window 对象上的事件,例如 window.onresizewindow.addEventListener ,组件销毁后是不会解绑的,必须手动在 destroyed 钩子中解绑。还有一些定时器也是一样操作。

至于为什么建议在 destroyed 钩子而不是 beforeDestroy ,因为通常组件中可能还嵌套了很多子组件,我们知道父子组件生命周期顺序:

1. 加载渲染过程
父组件 beforeCreate -> 父组件 created -> 父组件 beforeMount -> 子组件 beforeCreate -> 子组件 created -> 子组件 beforeMount -> 子组件 mounted -> 父组件 mounted
2. 子组件更新过程
父组件 beforeUpdate -> 子组件 beforeUpdate -> 子组件 updated -> 父组件 updated
3. 父组件更新过程
父组件 beforeUpdate -> 父组件 updated
4. 销毁过程
父组件 beforeDestroy -> 子组件 beforeDestroy -> 子组件 destroyed -> 父组件 destroyed

从上面就可以看出,父组件进入 beforeDestroy ,子组件此时并没有销毁,在这个时候如果解绑事件,可能会导致子组件出问题,因此建议在 destroyed 钩子中解绑。

参考:
vue项目如何监听窗口变化,达到页面自适应?

Logo

前往低代码交流专区

更多推荐