基础知识
offsetWidth/offsetHeight
HTMLElement.offsetWidth
是一个只读属性,返回一个元素的布局宽度。offsetWidth 是测量包含元素的边框 (c)、水平线上的内边距 (padding)、竖直方向滚动条 (scrollbar)(如果存在的话)、以及 CSS 设置的宽度 (width) 的值。即:
offsetWidth = offsetWidth * 2 + padding * 2 + width + scrollbar
ResizeObserver
ResizeObserver 是一个 JavaScript API,用于监视元素的大小变化。它可以观察一个或多个 DOM 元素,以便在元素的大小或形状发生变化时触发回调函数。ResizeObserver 是为了更有效地处理元素尺寸变化而引入的,特别适用于响应式设计和自适应布局。
Vue3
const myObserver = new ResizeObserver((entries) => {
entries.forEach((entry) => {
console.log('被监听元素content的宽高及位置', entry.contentRect)
// bottom: 700 指top + height的值
// height: 600 指元素本身的高度,不包含padding,border值
// left: 100 指padding-left的值
// right: 1143 指left + width的值
// top: 100 指padidng-top的值
// width: 1043 指元素本身的宽度,不包含padding,border值
// x: 100
// y: 100
console.log('被监听元素的宽高', entry.borderBoxSize)
// blockSize: 1000
// inlineSize: 1443
console.log('被监听元素content部分的宽高', entry.contentBoxSize)
// blockSize: 600
// inlineSize: 1043
console.log('被监听元素', entry.target)
})
})
myObserver.observe(contentRef.value)
在使用ResizeObserver API的时候,在每次触发元素的大小变化时,会在1s内触发回调多次。如果想进一步优化性能,可以加防抖函数处理。
Vue3
const observer = new ResizeObserver(useDebounceFn(handleSetPosition, 300));
observer.observe(contentRef.value);
节流与防抖的区别
函数防抖:一段时间内连续触发事件,只执行最后一次
函数节流:一段时间内只执行一次
鼠标事件mouseover和mouseenter的区别
mouseover和mouseenter都是鼠标移到元素身上就触发,区别是:
1.mouseover经过自身盒子触发,经过子盒子也触发,用于冒泡特性
2.mouseenter只经过自身盒子触发,没有冒泡特性
1.事件的触发时机
mouseover:当鼠标移入元素或子元素都会 触发事件。
mouseenter:当鼠标移入元素才会触发事件。
2.是否支持冒泡
mouseover:支持冒泡
mouseenter:不支持冒泡
WangScroll实现原理
主要通过transform translateY以及requestAnimationFrame来实现竖向滚动效果。
- transform translateY不会触发回流,有助于优化性能。原因是:渲染流水线是这样的顺序:重排 -> 重绘 -> 合成,transform: translate是直接合成,跳过了前面的重排重绘。
requestAnimationFrame
会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率。能保证回调函数在屏幕每一次的绘制间隔中只被执行一次,这样就不会引起丢帧现象,也不会导致动画出现卡顿的问题。
WangScroll代码说明
- WangScroll接收两个props, loading以及dataSource。
Vue3
const props = defineProps({
loading: {
type: Boolean,
required: true
},
dataSource: {
type: Array as PropType<any[]>,
required: true
}
});
dataSource为传入的数据源,当传入的dataSource发生变化时,会触发addNewList方法。addNewList将新拿到的dataSource赋值给propList。
Vue3
/**
* @description 将新拿到的数据赋值给propList
*/
const addNewList = () => {
propList.value = props.dataSource.slice(0);
};
// 监听数据源传入
watch(
() => props.dataSource.length,
() => {
addNewList();
}
);
当数据(dataSource)正在获取过程中时,loading为true,关闭滚动。 当数据(dataSource)获取完成后,loading为false,开启滚动。
Vue3
/**
* @description 播放滚动
*/
const move = () => {
if (state.isHover) return;
// 数据加载中就停止
if (props.loading) {
_cancel();
return;
}
tanslateY.value += 1;
if (tanslateY.value === propList.value.length * state.itemHeight) {
tanslateY.value = 0;
emit("scrollEnd");
}
state.rafTimer = requestAnimationFrame(move);
};
/**
* @description 关闭滚动
*/
const _cancel = () => {
state.rafTimer !== null && window.cancelAnimationFrame(state.rafTimer);
};
watch(
() => props.loading,
value => {
// 数据加载完之后开启滚动
if (!value) {
move();
}
}
);
- 滚动区域的滚动通过transform translateY来实现,且会进行鼠标移入移出监听,当鼠标悬停在滚动区域时,停止滚动,并于用户查看滚动信息。
Vue3
<div
class="fs-estimated-virtuallist-content"
:style="scrollStyle" @mouseleave="leave"
@mouseenter="enter"
@mouseover="over">
...
</div>
const tanslateY = ref<number>(0); // 通过tanslateY实现滚动效果
const scrollStyle = computed(
() =>
({
transform: `translate3d(0, -${tanslateY.value}px, 0)`
}) as CSSProperties
);
/**
* @description 关闭滚动
*/
const _cancel = () => {
state.rafTimer !== null && window.cancelAnimationFrame(state.rafTimer);
};
// 鼠标移入关闭滚动
const enter = () => {
state.isHover = true; //关闭_move
_cancel();
};
// 鼠标移出开启滚动
const leave = () => {
state.isHover = false; //开启_move
move();
};
const over = () => {
_cancel();
};
- 滚动区域的中包含两块数据列表,用于实现滚动的
无缝衔接
。随着滚动区域的滚动,当上面列表的底部到达可视区的顶部,下面列表的顶部也正好在可视区的顶部。此时,将滚动区域的transform translateY设置为0,上面列表的顶部又回到可视区的顶部,这个变换过程是无感知的,因为transform translateY不会引起回流,并且两个列表内容一样,人眼感知不到变化。相关代码在move方法中:
js
// 当上面列表的底部到达可视区的顶部 将滚动区域的transform translateY设置为0 上面列表的顶部又回到可视区的顶部
if (tanslateY.value === propList.value.length * state.itemHeight) {
tanslateY.value = 0;
emit("scrollEnd");
}
当上面列表的底部到达可视区的顶部,即数据列表滚动完成后,会抛出scrollEnd
方法,在使用WangScroll的Vue文件中可监听scrollEnd
方法,进行相关处理,比如调用接口刷新数据源。
- WangScroll使用的前提是滚动的每一行都是定高的,且会在onMounted生命周期中计算出相应高度。这个高度主要用于计算出滚动列表的总高,进而用于判断上面列表的底部是否到达可视区的顶部,即
if (tanslateY.value === propList.value.length * state.itemHeight)
。
Vue3
onMounted(() => {
setTimeout(() => {
if (itemRef.value) state.itemHeight = itemRef.value[0].offsetHeight;
});
});