项目配置 Vue3 + vite
需求:页面滚动有翻页的效果,遇到不能一屏展示完的,需要可以慢慢滚动查看
参考文章:自己动手实现页面滚动动画效果 - 掘金 (juejin.cn)
感谢博主的视差效果代码,本篇稍做修改,在视差的基础上增加了不能以一屏展示时的默认滚动
HTML结构:
<div class="introduction_item_cls introduction_cls0" id="introduction00">
// 内容自行添加
</div>
<div class="introduction_item_cls introduction_cls0" id="introduction01">
</div>
<div class="introduction_item_cls introduction_cls0" id="introduction02">
</div>
<div class="introduction_item_cls introduction_cls0" id="introduction03">
</div>
<div class="introduction_item_cls introduction_cls0" id="introduction04">
</div>
<div class="introduction_item_cls introduction_cls0" id="introduction05">
</div>
state数据
const state = reactive({
// 存放每一个需要滚动效果的元素id
divIdList: ['introduction00', 'introduction01', 'introduction02', 'introduction03', 'introduction04', 'introduction05',],
// 动态计算每一个元素的高度
positionList: [0, 530, 1490, 2150, 3000, 4250, 4700],
debounce: false, // 是否正在滚动
pageNowIndex: -1, // 当前页面元素下标
})
methods 方法
onMounted(() => {
// 页面加载完成计算各个元素高度
getDivHeight()
// 滚动监听
window.addEventListener('mousewheel', wheelFn, { passive: false })
})
const getDivHeight = () => { // 计算各div高度
state.positionList = [0]// 动态获取
setTimeout(() => {// 转异步,等待页面元素加载完成,使用nextTick没效果,不知道为啥
state.divIdList.forEach((el, idx) => {
let dv = document.getElementById(el)
let nowH = dv.offsetHeight
let perH = state.positionList[idx]
let newH = nowH + perH + 10 // 当前div的高度加上一个div的高度 +10去边
state.positionList.push(newH)
});
console.log(state.positionList, 'state.state.positionListuserInfoP');
}, 100);
}
/** 计算现在在哪个位置,是正好对准边界还是夹在中间 */
const calcNowPosition = (nowY) => {
for (let i = 0; i < state.positionList.length; i++) {
if (state.positionList[i] > nowY) {
return { index: i - 1, between: state.positionList[i - 1] !== nowY }
}
}
return { index: state.positionList.length - 1, between: false }
}
/** 缓动函数 https://easings.net#easeInOutCubic */
const easeInOutCubic = (x) => {
// return 4 * x * x * x
return x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2
}
const wheelFn = (e) => {
const nowY = window.scrollY
if (e.deltaY > 0) { // 只有下拉的时候才会触发默认的 慢速滚动
const { index, between } = calcNowPosition(nowY + 2) //
if (index !== 0) {
let nowEl = document.getElementById(state.divIdList[index])
let isinv = isInViewPortOfOne(nowEl, 2)
if (!isinv) { // 超出的元素id下标,除了第一个
console.log('慢点');
return
}
}
}
// e.preventDefault()
// 正在滚动中
if (state.debounce) {
return
}
// 滚轮是向下还是向上
const down = e.deltaY > 0
state.debounce = true
let start = 0
let scrollHeight = 0
let nowx
if (down) {
const { index, between } = calcNowPosition(nowY + 2) //
// 向下滚,滚动高度等于下一个位置与现在的差值
scrollHeight = state.positionList[index + 1] - nowY
nowx = index + 1
} else {
const { index, between } = calcNowPosition(nowY - 2)
// 向上滚,夹在中间需滚动上一个边界与现在的差值,在边界就滚动一个完整距离
scrollHeight = between ? (nowY - state.positionList[index]) : (state.positionList[index] - state.positionList[index - 1])
nowx = index
}
let nowEl = document.getElementById(state.divIdList[nowx])
let isinv = isInViewPortOfOne(nowEl, 1)
if (!isinv && nowx !== 0) { // 超出的元素id下标,除了第一个。这个判断可根据实际业务判断,不要判断直接写 state.pageNowIndex = nowx即可
state.pageNowIndex = nowx
} else {
state.pageNowIndex = -1
}
// 动画函数,需要闭包访问 start 就没有分离出来
const step = (unix) => {
if (!start) {
start = unix
}
const duration = unix - start
const y = easeInOutCubic(duration / 1000) * scrollHeight
// 当调用getDivHeight()动态获取div高度时,会出现window.scrollY获取到的也买你滚动高度与 window.scrollTo设置的高度不一样的问题
let toNum = down ? nowY + y : nowY - y
window.scrollTo(0, toNum)
if (duration <= 1001) {
requestAnimationFrame(step)
} else {
state.debounce = false
}
}
requestAnimationFrame(step)
}
const isInViewPortOfOne = (el, type) => { // 判断元素是否在视图之内
// viewPortHeight 兼容所有浏览器写法
if (!el) { // 没有元素,在视图中
return true
}
const rect = el.getBoundingClientRect();
const windowHeight = window.innerHeight || document.documentElement.clientHeight;
if (type == 1) { // 计算元素能否在窗口中全部展示
return (rect.height <= windowHeight)
} else if (type == 2) { // 计算是否能看到该元素的底部
return (
rect.bottom <= windowHeight
);
}
}