theme: cyanosis
第一反应是什么?
window.addEventListener("resize", handler, useCapture)
简单粗暴,但缺点也明显:文档视图调整大小时会触发 resize 事件,针对的 仅是window,而普通的DOM元素没有 onresize 事件的。
所以严格来说,这个第一反应不算正确
其他想法:window.matchMedia
在 JavaScript 中,你可以通过 window.matchMedia()
方法检测 CSS 中定义的相同的 CSS 媒体查询字符串。例如,在 CSS 中,如果你有以下的 CSS 媒体查询。
/* #### 当查看区域的宽度为 765px 或更小时应用的 CSS #### */
@media screen and (max-width: 765px){
/* CSS定义在这里 */
}
要在 JavaScript 中检测相同的媒体查询,你要做以下工作。
var mql = window.matchMedia("screen and (max-width: 765px)")
window.matchMedia 的应用与封装 – 知乎 (zhihu.com)
可展开/收缩搜索条件且支持自适应功能 – 掘金 (juejin.cn)
主角:ResizeObserver
用法
var ro = new ResizeObserver( entries => {
for (let entry of entries) {
const cr = entry.contentRect;
console.log('Element:', entry.target);
console.log(`Element size: ${cr.width}px x ${cr.height}px`);
console.log(`Element padding: ${cr.top}px ; ${cr.left}px`);
}
});
// 观察一个或多个元素
ro.observe(eleZxx);
vue包
@juggle/resize-observer – npm (npmjs.com)
ResizeObserver实际可以用来做什么
实现类似效果的菜单(Overflow Menu)
原理
- 获取菜单容器宽度
- 获取每个菜单项宽度
- 当菜单容器宽度改变时,判断菜单项总宽度是否大于菜单容器宽度,如果是,则将菜单项从尾到头,依次隐藏,直至可显示的菜单项总宽度小于等于菜单容器宽度
- 当菜单容器宽度改变时,判断菜单项总宽度是否小于菜单容器宽度,如果是,则将菜单项从头到为,依次显示,直至可显示的菜单项达到菜单容器的极限
- 当菜单容器宽度改变时,判断菜单项总宽度是否等于菜单容器宽度,如果是,则将菜单项全部显示
vue简单实现
<!--
@author: pan
@createDate: 2022-11-19 15:21
-->
<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref, toRefs, watch } from 'vue'
import { ResizeObserver } from '@juggle/resize-observer'
export interface MenuItem {
key: string
name: string
}
interface MenuItemInner extends MenuItem {
//是否显示
responseShow: boolean
// 宽度
width: number
}
const props = withDefaults(
defineProps<{
menuArr?: MenuItem[]
}>(),
{ menuArr: () => [] as MenuItem[] }
)
const { menuArr: tmpArr } = toRefs(props)
const menuArr = ref<MenuItemInner[]>([])
watch(
tmpArr,
newVal => {
menuArr.value = newVal.map(item => ({
...item,
responseShow: true,
width: 0,
}))
},
{ immediate: true }
)
const menuContainerRef = ref<HTMLUListElement>()
onMounted(() => {
// 获取每个li的长度
const menuContainerEl: HTMLUListElement =
menuContainerRef.value as HTMLUListElement
menuContainerEl.querySelectorAll('li').forEach((item, idx) => {
const width = item.offsetWidth
menuArr.value[idx].width = width
})
})
interface MenuResponseShowData {
// 可见li总宽度
menuItemWidth: number
// 可见li集合
responseShowDataArr: MenuItemInner[]
}
/**
* 菜单容器大小改变的回调函数
*
* @param menuContainerEl 菜单容器
*/
function menuContainerSizeChange(menuContainerEl: HTMLUListElement): void {
// 获取菜单容器宽度
const menuContainerWidth = menuContainerEl.offsetWidth
// 计算可见li的总宽度,以及可见li集合
const { menuItemWidth, responseShowDataArr } =
menuArr.value.reduce<MenuResponseShowData>(
(prevVal, curItem) => {
if (curItem.responseShow) {
prevVal.menuItemWidth += curItem.width
prevVal.responseShowDataArr.push(curItem)
}
return prevVal
},
{ menuItemWidth: 0, responseShowDataArr: [] }
)
if (menuItemWidth > menuContainerWidth) {
// li的总宽度超过菜单容器宽度
let tmpMenuItemWidth = menuItemWidth
for (let i = responseShowDataArr.length; i >= 0; i--) {
tmpMenuItemWidth -= responseShowDataArr[i - 1].width
// 从后向前,将可见菜单项设置为不可见,直至可见菜单项的总长度小于等于菜单容器长度
responseShowDataArr[i - 1].responseShow = false
if (tmpMenuItemWidth <= menuContainerWidth) {
break
}
}
} else if (menuItemWidth < menuContainerWidth) {
// li的总宽度小于菜单容器宽度
let tmpMenuItemWidth = 0
const tmpMenuArr = menuArr.value
for (let i = 0; i < tmpMenuArr.length; i++) {
tmpMenuItemWidth += tmpMenuArr[i].width
// 从前向后,将菜单设置为可见,直至可见菜单项的总长度等于菜单容器长度
tmpMenuArr[i].responseShow = true
if (tmpMenuItemWidth === menuContainerWidth) {
break
} else if (tmpMenuItemWidth > menuContainerWidth) {
tmpMenuArr[i].responseShow = false
break
}
}
} else {
// li的总宽度等于菜单容器宽度
const tmpMenuArr = menuArr.value
for (let i = 0; i < tmpMenuArr.length; i++) {
tmpMenuArr[i].responseShow = true
}
}
}
const ro = new ResizeObserver(entries => {
for (let entry of entries) {
switch (entry.target) {
case menuContainerRef.value: {
menuContainerSizeChange(menuContainerRef.value as HTMLUListElement)
break
}
}
}
})
onMounted(() => {
const menuContainerEl = menuContainerRef.value as HTMLUListElement
ro.observe(menuContainerEl)
})
onBeforeUnmount(() => {
ro.disconnect()
})
</script>
<template>
<div>
<ul ref="menuContainerRef" class="menuContainer">
<template v-for="menuItem of menuArr" :key="menuItem.key">
<li v-if="menuItem.responseShow !== false">
{{ menuItem.name }}
</li>
</template>
</ul>
</div>
</template>
<style lang="scss" scoped>
ul,
li {
list-style-type: none;
margin: 0;
padding: 0;
}
.menuContainer {
display: flex;
li {
padding: 10px 20px;
border-right: 1px solid #333;
&:last-child {
border-right-width: 0;
}
}
}
</style>
参考文章
监测DOM元素尺寸大小变化|Vue – 掘金 (juejin.cn)
检测DOM尺寸变化JS API ResizeObserver简介 « 张鑫旭-鑫空间-鑫生活 (zhangxinxu.com)
Navbar with Overflow Menu (codepen.io)
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END
暂无评论内容