vue3 实现div滚动到一定位置就固定,布局层使用了el-scrollbar,滚动对象不是windows的情况,而且需要固定的元素本身距离顶部有一定间距的情况。评论区域固定 电脑版发表于:2025/10/29 15:36 [TOC] ### 监听滚动对象的问题,正确找到滚动事件的监听对象 该项目vue3的布局页有使用el-scrollbar,代码如下: ``` <template> <el-container class="layout-container"> <LayoutAside /> <el-container class="layout-container-view h100"> <el-scrollbar ref="layoutScrollbarRef" class="layout-backtop"> <LayoutHeader /> <LayoutMain ref="layoutMainRef" /> </el-scrollbar> </el-container> </el-container> </template> ``` 所以监听滚动对象不能使用,下面的方式监听了: ``` window.addEventListener('scroll', handleStickyScroll, { passive: true }) ``` 当然这种方式也是不行的: ``` $(window).scroll(function () {}) ``` #### 所以需要正确的找到监听对象 查找滚动容器的方法如下: ``` // 查找滚动容器 const findScrollContainer = () => { console.log("查找滚动容器...") // 首先尝试找到el-scrollbar的wrapRef const layoutScrollbarRef = document.querySelector('.layout-backtop') if (layoutScrollbarRef) { console.log("找到layoutScrollbarRef:", layoutScrollbarRef) // 尝试获取el-scrollbar的wrapRef const wrapRef = layoutScrollbarRef.querySelector('.el-scrollbar__wrap') if (wrapRef) { console.log("找到el-scrollbar wrapRef:", wrapRef) console.log("wrapRef scrollHeight:", wrapRef.scrollHeight, "clientHeight:", wrapRef.clientHeight) if (wrapRef.scrollHeight > wrapRef.clientHeight) { console.log("使用el-scrollbar wrapRef作为滚动容器") return wrapRef } } } console.log("使用默认的window作为滚动容器") return window } ``` #### 然后监听滚动事件的方式如下 ``` onMounted(async () => { await loadRoomInfo() await loadMaterialDetail() await loadComments() // 设置一下右侧的高度,与左侧的最小宽度 calRightMaxHeight() // 延迟添加滚动监听,确保DOM完全渲染 setTimeout(() => { // 查找正确的滚动容器 const scrollContainer = findScrollContainer() console.log("使用滚动容器:", scrollContainer) // 监听el-scrollbar的滚动事件 scrollContainer.addEventListener('scroll', handleStickyScroll, { passive: true }) }, 300) }) ``` ### 实现滚动固定的方法 这个方法其实和以前写的方法差不多的,核心就是要注意一点本身距离顶部有一定高度这块要根据情况计算进去 #### html的结构大概如下 ``` <!-- 左侧详情内容 --> <div class="detail-section" ref="detailSection"> <el-card class="detail-card"> <div class="detail-header"></div> <div class="detail-content"></div> </el-card> </div> <!-- 右侧讨论区域 --> <div class="discussion-section" ref="discussionSection"> <el-card class="discussion-card"> <div class="discussion-header">....</div> <div class="discussion-content">....</div> </el-card> </div> ``` #### 具体的滚动固定方法 ``` // 左侧的详情区域 const detailSection = ref<HTMLElement>() // 右侧讨论区域引用 const discussionSection = ref<HTMLElement>() const handleStickyScroll = () => { if (!discussionSection.value) { console.log("discussionSection.value 为空") return } if (!detailSection.value) { console.log("detailSection.value 为空") return } const discussionRect = discussionSection.value.getBoundingClientRect() const detailRect = detailSection.value.getBoundingClientRect() const layoutScrollbarRef = document.querySelector('.layout-backtop') if(layoutScrollbarRef==null){ console.log("滚动容器layoutScrollbarRef为空了") return } const wrapRef = layoutScrollbarRef.querySelector('.el-scrollbar__wrap') if(wrapRef==null){ console.log("滚动容器layoutScrollbarRef为空了") return } // console.log("wrapRef scrollTop:"+wrapRef.scrollTop,"wrapRef scrollHeight:", wrapRef.scrollHeight, "clientHeight:", wrapRef.clientHeight) // 获取滚动条的高度 let scrollTop = wrapRef.scrollTop // 获取一个屏幕的高度 let windowHeight = findHeight() // 右边讨论区域的高度 let rightHeight = discussionRect.height // 左边详情的高度 let detailHeight = detailRect.height console.log("右边的高度(讨论区域的高度):", discussionRect.height) console.log("左边详情的高度:", detailHeight) console.log("一个屏幕的高度:", windowHeight) // 动态计算右边区域的最大高度 // calRightMaxHeight(windowHeight,discussionSection.value) // 如果右边的高度本身就比左边的高度高 那么就不用固定右边了(加100是为了增加一点容错率,100肯定还是在可视范围内所以不用固定) if (rightHeight+100 > detailHeight) { console.log("右边的高度本身就比左边的高度高 那么就不用固定右边了") return } // let righttop = discussionRect.top // console.log("右边距离顶部的高度:", righttop) // 300是因为右边的距离顶部默认有一个距离,所以可以直接设置高一点 if (scrollTop < 300) { console.log("滚动到顶部了") changeBefore() // 恢复div可以跟随滚动条滚动 } /* 当滚动条滚动到一边的最底部,把需要的div滚动到底部就固定div不让移动了。 这里加300是因为右边距离顶部有一个距离所以右边的高度应该要加上这个数量, 其实右边距离顶部的高度可以通过discussionRect.top动态获取,但是设置position: 'fixed'后, discussionRect.top值会动态的变化,所以这里还是自己固定一个值比较好,这样好测试一点 */ else if (scrollTop > (rightHeight+300) - windowHeight) { console.log("右边固定到底部了需要固定了") fixedRight() } else { changeBefore() // 恢复div可以跟随滚动条滚动 } } ``` ### 让右边需要滚动的区域内容的高度固定一个屏幕剩下的高度,如果内容超过就出现滚动条 不然右边是评论区,高度不固定可能很短也可能很长这样就很不好控制了,所以为了一屏能浏览完就干脆固定一下高度,这样如果评论比如多的时候自动出现滚动条一屏能浏览完其实用户体验还是很好的。 然后左边的最小高度也控制一下为一个屏幕的高度,这样即使左边区域的内容很少也至少是一个屏幕的内容,这样看着就比较舒服了。 ``` /* 动态计算右边评论区域的最大高度,如果超过最大高度就让他出现滚动条,这样用户体验好一些, 而且也好控制右边的滚动 */ const calRightMaxHeight = () => { // 获取一个屏幕的高度 let windowHeight = findHeight() if (!discussionSection.value) { console.log("discussionSection.value 为空") return } // 计算discussion-content的最大高度 // 顶部间距(包括页面padding、discussion-header等) const topSpacing = 190 // 可以根据实际情况调整 const maxContentHeight = windowHeight - topSpacing // 获取discussion-content元素 const discussionContent = discussionSection.value.querySelector('.discussion-content') as HTMLElement if (discussionContent) { const contentHeight = discussionContent.scrollHeight console.log("计算的最大内容高度:", maxContentHeight, "当前内容高度:", contentHeight) // 直接让右侧的讨论区高度固定一个屏幕剩下的高度吧,好统一控制一些 discussionContent.style.height = `${maxContentHeight}px` discussionContent.style.overflowY = 'auto' // 让左边的最小高度也等于一个屏幕剩下的高度吧 if (detailSection.value) { // 要设置里边detail-content的高度,这样才会把外层自动撑开,因为外边用了el-card的,而且这样可以左右的高度保持一致(因为右边也是设置的内容的高度) let leftArea = detailSection.value.querySelector('.detail-content') as HTMLElement leftArea.style.minHeight = `${maxContentHeight+30}px` } // detailSection.value.style.minHeight = `${maxContentHeight}px` // 设置discussion-content的最大高度 // if (contentHeight > maxContentHeight) { // console.log("内容高度超过屏幕,设置最大高度:", maxContentHeight) // discussionContent.style.maxHeight = `${maxContentHeight}px` // discussionContent.style.overflowY = 'auto' // } else { // console.log("内容高度正常,移除高度限制") // discussionContent.style.maxHeight = 'none' // discussionContent.style.overflowY = 'visible' // } } } ``` ### 有时候要判断滚动条滚动到底部后做一点操作,比如设置距离底部的样式 因为有些情况左边距离底部有一个间距,所以为了让右边距离底部的间距和左边保持一致,滚动到底部需要设置一下右边距离底部一个间距,注意只设置底部bottom可能不行哦,不行的话要一起调整一下top,因为如果固定的时候设置的是top,是优先满足top的间距的 ``` // 如果滚动条滚动到最底部,把右侧的讨论区域设置一个距离底部的间距,这样可以保持和右边对其 if(scrollTop+windowHeight>scrollHeight-10){ // alert("滚动条滚动到最底部了") discussionSection.value.style.top = `${-2}px` discussionSection.value.style.bottom = `${49}px` } ``` **完整的获取各种高度的代码在贴一下** ``` const wrapRef = layoutScrollbarRef.querySelector('.el-scrollbar__wrap') if(wrapRef==null){ console.log("滚动容器layoutScrollbarRef为空了") return } // 获取滚动条的距离 let scrollTop = wrapRef.scrollTop // 获取滚动条的高度 let scrollHeight = wrapRef.scrollHeight // 获取一个屏幕的高度 let windowHeight = findHeight() // 右边讨论区域的高度 let rightHeight = discussionRect.height // 左边详情的高度 let detailHeight = detailRect.height // 如果滚动条滚动到最底部,把右侧的讨论区域设置一个距离底部的间距,这样可以保持和右边对其 if(scrollTop+windowHeight>scrollHeight-10){ // alert("滚动条滚动到最底部了") discussionSection.value.style.top = `${-2}px` discussionSection.value.style.bottom = `${49}px` } ```