页面悬浮滚动特效

悬浮滚动特效

需求

最近项目经理提到一个这样的交互需求:长页面内容,中间有一块部分是分为左右布局,左侧是目录菜单,右侧是内容,右侧内容部分很长。

  • 需求1:左侧菜单锚点定位,点击,右侧滑动到对应菜单的内容;
  • 需求2:右侧内容滑动到指定位置,左侧菜单锚点定位到对应菜单;
  • 需求3:当向下滑动时,左右侧同时滑动,当左侧菜单滑动到视口顶部,右侧内容还没有结束,左侧菜单要固定再顶部;当向上滑动时,左右侧同时滑动,当左侧菜单滑动到视口底部,右侧内容还没有结束,左侧菜单要固定到底部;直到右侧滑动结束,左右一起划出

原理

  1. 初始化记录固定元素位置,目标盒子的高度
  2. 监听页面滚动
  3. 动态获取元素位置,开启计算,如果目标盒子最顶部滑出视口或者小于设定值(距离视口顶部的值),开启菜单位置固定;如果目标盒子底部出现在视口底部,继续向上滑动,则左侧菜单要根据视口高度+初始固定值。
  4. 滑动时激活对应锚点

实现

以vue3为例

  1. html部分
<div ref="stickyContainerRef" >
    <!-- 左侧菜单 -->  
    <div ref="sytckyMenuRef" class="menu">
        <ul>
            <li v-for="(item,index) in menuList" :class="[{active:activeTag===item.id}]" :key="index" @click="handleMenuClick(item)">{{item.name}}</li>
        </ul>
    </div>
    <!-- 右侧内容 -->
    <div class="content">   
        <div class="content-inner">   
            <div class="content-inner-inner">{{content}}</div>  
        </div>  
    </div>
</div> 
  1. js部分
const stickyContainerRef=ref() // 外层盒子实例化
const stickyContainerRect=ref({
    top:0,
    left:0,
    height:0,
    width:0
})
const stickyMenuRef=ref() // 左侧菜单实例化
const stickyMenuRect=ref({
    top:0,
    left:0
})
const stickyMenuLeft=ref(0) // 左侧菜单距离左侧距离
const containerTop=ref(0) // 外层盒子top
const containerHeight=ref(0) // 外层盒子高度
//
const webHeaderHeight=ref(0)   // 顶部导航高度(即距离顶部多少开始固定)

onMounted(()=>{
    // 初始化记录值
    containerHeight.value=getDomPosition(stickyContainerRef.value).height;
    containerTop.value=getDomPosition(stickyContainerRef.value).top;
    stickyMenuLeft.value=getDomPosition(stickyMenuRef.value).left;

    // 1. 监听滚动
    document.addEventListener('scroll',handleScroll)
})


// 滚动方法
const handleScroll=()=>{
    stickyContainerRect.value=getDomPosition(stickyContainerRef.value)
    scrollCompute()
    handleMenuActive()
}

// 滚动计算
const scrollCompute=()=>{
    // 1. 计算目标盒子外壳顶部距离视口最下方的高度差
    let diffHeight=stickyContainerRect.value.height-Math.abs(stickyContainerRect.value.top)-webHeaderHeight.value;
    if(stickyContainerRect.value.top<35){
        stickyMenuRef.value.style.position='fixed';
        stickyMenuRef.value.style.left=stickyMenuLeft.value+'px';
        // 高度差<0:说明盒子整体底部已经离开视口底部 ;
        // 高度差>=0:说明盒子整体底部在视口顶部下面,
        if(diffHeight<0){
            // 盒子整体底部已经离开视口底部 下次进来左侧菜单需要从底部开始展现,所以需要计算整理盒子距离底部位置来动态计算菜单盒子位置
            stickyMenuRef.value.style.top=webHeaderHeight.value+diffHeight+'px';
        }else{
            sticklyMenuRef.value.style.top=webHeaderHeight.value+'px';
        }
    }else{
        clearStyleSite(stickyMenuRef.value)
    }
}

// 清楚动态位置
const clearStyleSite=(dom:HTMLElement)=>{
    dom.style.position='static';
    dom.style.top='0px';
    dom.style.left='0px';
}

// 获取dom元素位置值
function getDomPosition(dom:HTMLElement){
    return dom.getBoundingClientRect()
}

// 处理锚点激活
function handleMenuActive(marginTop:number=0){
    const className='card-box'
    for(let dom of document.getElementsByClassName(className)){
        if(getDomPosition(dom).top>0&&getDomPosition(dom).top-marginTop<=0){
            activeMenu()
        }
    }
}

// 锚点高亮激活
const activeMenu=debounce((dom)=>{
    activeTag.value=dom.getAttribute('data-id')
,200})
  
// 点击锚点滚动
const handleMenuClick=(item)=>{
    document.querySelector(`[data-id="${item.id}"]`).scrollIntoView({behavior:'smooth',block:'start'})
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值