position:sticky
简介 : 粘性定位 sticky 相当于相对定位 relative 和固定定位 fixed 的结合;在页面元素滚动过程中,某个元素距离其父元素的距离达到 sticky 粘性定位的要求时;元素的相对定位 relative 效果变成固定定位 fixed 的效果。
使用条件:
- 父元素不能 overflow:hidden 或者 overflow:auto 属性
- 必须指定 top、bottom、left、right 4 个值之一,否则只会处于相对定位
- 父元素的高度不能低于 sticky 元素的高度
- sticky 元素仅在其父元素内生效
<template>
<div id="app">
<div class="container">
<div class="box"></div>
<div class="header sticky">使用 `position:sticky` 实现</div>
<div class="box_two"></div>
</div>
</div>
</template>
<style>
.box {
width: 100%;
height: 2rem;
background: #333;
}
.box_two {
width: 100%;
height: 20rem;
background: -webkit-linear-gradient(
top,
#333333 20%,
#999999 40%,
#333333 80%
);
}
.header {
width: 100%;
height: 0.4rem;
text-align: center;
line-height: 0.4rem;
background: #999;
color: #fff;
font-size: 0.16rem;
}
.sticky {
position: -webkit-sticky;
position: sticky;
top: 0;
}
</style>
使用原生的 offsetTop
实现
<template>
<div class="color_box"></div>
<div class="title_box" ref="pride_tab_fixed">
<div class="title" :class="titleFixed == true ? 'isFixed' :''">
使用原生的 `offsetTop` 实现
</div>
</div>
<div class="color_box_two"></div>
</div>
</template>
<script>
data() {
return {
titleFixed: '',
startTime: new Date()
}
},
mounted() {
this.init()
},
methods: {
init() {
window.addEventListener('scroll', this.throttle(this.handleScroll, 100))
},
throttle(fn, time = 300) {
if (typeof fn !== 'function') {
throw new Error('必须传入一个函数作为参数')
}
const currentTime = new Date()
if (currentTime - this.startTime > time) {
this.startTime = currentTime
fn()
}
},
handleScroll () {
let self = this
let scrollTop =
window.pageYOffset ||
document.documentElement.scrollTop ||
document.body.scrollTop
let offsetTop = self.getOffset(self.$refs.pride_tab_fixed)
self.titleFixed = scrollTop > offsetTop
},
getOffset(obj, direction) {
let offsetL = 0
let offsetT = 0
while (obj !== window.document.body && obj !== null) {
offsetL += obj.offsetLeft
offsetT += obj.offsetTop
obj = obj.offsetParent
}
if (direction === 'left') {
return offsetL
} else {
return offsetT
}
}
}
</script>
<style>
.isFixed {
position: fixed;
top: 0;
left: 0;
z-index: 99;
}
</style>
getBoundingClientRect().top 来实现
handleScroll () {
let self = this
let offsetTop = self.$refs.pride_tab_fixed.getBoundingClientRect().top
self.titleFixed = offsetTop < 0
},
防抖和节流
防抖和节流的作用都是防止函数被多次调用。区别在于:假设一个用户一直触发这个函数,且每次触发函数的间隔小于设定时间,防抖的情况下只会调用一次,而节流的情况会每隔一定时间就调用一次该函数。
- 放抖:
- 如果在频繁的事件回调中做复杂计算,很有可能导致页面卡顿
- 触发高频事件后
n
秒内函数 只会执行一次 ,如果n
秒内事件又再次被触发,则重新计算时间只有触发间隔超过制定间隔的任务才会执行 - 每次触发事件时都取消之前所设定的延时调用方法,确保函数只被出发一次
- 接受一个参数,可以取消等待,立即调用传入的函数
- 页面滚动,监听输入框内容
function debounce(fn, wait = 50, immediate = true) {
let timer, context, args, result, firstRun
if (typeof fn !== 'function') {
throw new Error('必须传入一个函数作为参数')
}
const debounced = () => {
context = this
args = arguments
if (timer) clearTimeout(timer)
if (immediate) {
// 是否已经执行,如果已经执行过,不再执行
firstRun = !timer // 首次立即调用
timer = setTimeout(() => {
fn.apply(context, args)
}, wait)
if (firstRun) result = fn.apply(context, args)
} else {
timer = setTimeout(() => {
fn.apply(context, args)
}, wait)
}
return result
}
// 取消 debounce 函数
debounced.cancel = () => {
clearTimeout(timer)
timer = null
}
return debounced
}
- 节流:
- 高频事件每 n 秒内都会执行一次,所以节流会稀释函数的执行频率,将多次执行变成每隔一段时间执行
- 每次触发事件时都判断当前是否有等待执行的延时函数,两种实现方式,一种是使用时间戳,一种是设置定时器
- 首次是否执行以及结束后是否执行,效果有所不同
使用时间戳可以实现首次就执行,设置定时器可以实现结束后仍然执行一次
时间戳形式
throttle(fn, time = 300) {
let previous = 0
if (typeof fn !== 'function') {
throw new Error('必须传入一个函数作为参数')
}
return () => {
let now = +new Date(); //返回当前的时间戳
if (now - previous > wait) {
func.apply(this, arguments);
previous = now;
}
}
}
定时器模式
throttle(fn, time = 300) {
let timer, context, args
if (typeof fn !== 'function') {
throw new Error('必须传入一个函数作为参数')
}
return () => {
context = this;
args = arguments;
if (!timer) {
timer = setTimeout(function(){
timer = null;
func.apply(context, args)
}, wait)
}
}
}
requestAnimationFrame
告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行
若你想在浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用
window.requestAnimationFrame()
window.requestAnimationFrame(callback);