使用Intersection Observer js实现超出视口固定底部按钮

1.前言

关于【交叉观察器】 详解​,可参考个人此篇文章:

https://mp.csdn.net/mp_blog/creation/success/155816771https://mp.csdn.net/mp_blog/creation/success/155816771

2.目标效果

Intersection Observer 【交叉观察器】

3.实现代码

javascript 复制代码
<!-- vue3框架即可,直接引入并路由添加后即可查看效果 -->
<template>
    <div class="demo-container">
        <!-- 页面标题 -->
        <h1 class="demo-title">{{ title }}</h1>

        <!-- 内容区域 -->
        <div class="content-area">
            <div v-for="(item, index) in curList" :key="index" class="content-item">
                <h3>{{ item.title }}</h3>
                <p>{{ item.content }}</p>
            </div>

            <!-- 观察元素:用于检测内容是否超出一屏 -->
            <!-- 这个元素会被IntersectionObserver观察,它的可见性决定了按钮是否需要固定 -->
            <div ref="observerRef" class="observer-element">
                <p>我是观察元素,当我不可见时,底部按钮会固定显示</p>
            </div>
        </div>

        <!-- 操作按钮 -->
        <!-- 当isButtonVisible为true时,按钮会固定在页面底部 -->
        <div class="action-button-container" :class="{ fixed: isButtonVisible }">
            <button class="action-button">提交操作</button>
        </div>
    </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted, computed } from 'vue'

// 响应式变量:控制按钮是否显示为固定定位
const isButtonVisible = ref(false)

// 观察元素的引用
const observerRef = ref(null)
const title = ref('Intersection Observer 演示 未固定')

// IntersectionObserver实例引用
let observer = null

// 初始化IntersectionObserver
const initIntersectionObserver = () => {
    if (observerRef.value) {
        observer = new IntersectionObserver(
            (entries) => {
                entries.forEach((entry) => {
                    // entry.isIntersecting为true表示观察元素可见
                    // entry.isIntersecting为false表示观察元素不可见

                    // 逻辑解释:
                    // 1. 当观察元素可见时,说明内容不足一屏,按钮区域在可视范围内,无需固定
                    // 2. 当观察元素不可见时,说明内容超出一屏,按钮区域已滚动出视野,需要固定按钮
                    if (!isButtonVisible.value) {
                        isButtonVisible.value = !entry.isIntersecting
                    }
                })
            },
            {
                root: null, // 观察相对于视口
                rootMargin: '0px', // 根元素的边距
                threshold: 0.1, // 只要10%的观察元素可见,就认为可见
            }
        )

        // 开始观察指定元素
        observer.observe(observerRef.value)
    }
}

// 演示数据:生成足够的内容以便测试滚动效果
const demoData = [
    {
        title: '演示内容 1',
        content: '这是一段演示文本,用于填充页面内容。IntersectionObserver API 可以帮助我们检测元素是否在可视区域内。',
    },
    {
        title: '演示内容 2',
        content: '当页面内容较少时,操作按钮会正常显示在页面底部。当内容超出一屏时,按钮会固定在视口底部。',
    },
    {
        title: '演示内容 3',
        content: 'IntersectionObserver 提供了一种异步检测目标元素与祖先元素或顶级文档视口交叉状态的方法。',
    },
    {
        title: '演示内容 4',
        content: '使用 IntersectionObserver 可以避免频繁触发 scroll 事件,提高性能。',
    },
    {
        title: '演示内容 5',
        content: '滚动页面,观察底部按钮的变化。当观察元素不可见时,按钮会固定显示。',
    },
    {
        title: '演示内容 6',
        content: 'IntersectionObserver 是现代浏览器提供的 API,用于检测元素是否进入视口。',
    },
    {
        title: '演示内容 7',
        content: '这种技术常用于无限滚动、懒加载图片、广告曝光统计等场景。',
    },
    {
        title: '演示内容 8',
        content: '在移动应用中,固定操作按钮可以提升用户体验,确保用户随时可以执行关键操作。',
    },
    {
        title: '演示内容 9',
        content: '滚动到页面底部,观察按钮的变化效果。',
    },
    {
        title: '演示内容 10',
        content: '这是最后一段演示文本,用于确保页面内容足够长,可以触发滚动效果。',
    },
]

const curList = ref()

const clipData = (num) => demoData.slice(0, num || -1)

curList.value = clipData(2)

setTimeout(() => {
    title.value = 'Intersection Observer 演示 固定'
    curList.value = clipData()
}, 5000)

// 组件挂载时初始化IntersectionObserver
onMounted(() => {
    // 延迟初始化,确保DOM已经渲染完成
    setTimeout(() => {
        initIntersectionObserver()
    }, 100)
})

// 组件卸载时停止观察
onUnmounted(() => {
    if (observer && observerRef.value) {
        observer.unobserve(observerRef.value)
        observer.disconnect()
    }
})
</script>

<style scoped>
.demo-container {
    max-width: 800px;
    margin: 0 auto;
    padding: 20px;
}

.demo-title {
    text-align: center;
    margin-bottom: 30px;
    color: #333;
}

.content-area {
    margin-bottom: 60px; /* 为固定按钮留出空间 */
}

.content-item {
    margin-bottom: 25px;
    padding: 20px;
    background-color: #f5f5f5;
    border-radius: 8px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.content-item h3 {
    margin-top: 0;
    color: #2c3e50;
}

.content-item p {
    color: #666;
    line-height: 1.6;
}

.observer-element {
    padding: 20px;
    margin: 20px 0;
    background-color: #e8f4f8;
    border: 2px dashed #3498db;
    border-radius: 8px;
    text-align: center;
    color: #2980b9;
    font-weight: bold;
}

.action-button-container {
    width: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
    border: 2px solid #fa0000;
}

.action-button-container.fixed {
    position: fixed;
    bottom: 0;
    left: 0;
    z-index: 1000;
    /* box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1); */
    border-top: 1px solid #fa0000;
    padding: 20px;
    box-sizing: border-box;
    background-color: #fff;
}

.action-button {
    display: block;
    width: 80%;
    padding: 15px;
    background-color: #3498db;
    color: white;
    border: none;
    border-radius: 8px;
    font-size: 16px;
    font-weight: bold;
    cursor: pointer;
    transition: background-color 0.3s;
}

.action-button:hover {
    background-color: #2980b9;
}
</style>

4.总结

Intersection Observer 概述

Intersection Observer API 提供了一种异步观察目标元素与祖先元素或顶级文档视窗交叉状态的方法。它能够高效地监听元素是否进入视口,适用于懒加载、无限滚动、广告曝光统计等场景。

底部按钮固定的实现方法

使用 Intersection Observer 监听目标元素(如页脚或特定区块)是否进入视口。当目标元素与视口交叉时,动态调整底部按钮的定位方式(如从 fixed 改为 absolute 或反之)。

注意事项与建议

阈值(Threshold)设置

合理设置 threshold 参数(如 0.1[0, 0.1, 0.9, 1]),避免过于敏感或迟钝的触发。对于底部按钮,通常需要较小的阈值以提前响应。

性能优化

避免在回调中执行高耗能操作(如频繁 DOM 操作)。优先使用 requestAnimationFrame 或防抖/节流控制触发频率。

javascript 复制代码
const element = document.getElementById('box');
let position = 0;

function move(timestamp) {
  position += 1;
  element.style.transform = `translateX(${position}px)`;
  
  if (position < 200) {
    requestAnimationFrame(move);
  }
}

requestAnimationFrame(move);

// 举例 requestAnimationFrame 使用场景
// 避免在单个动画帧中执行过多计算
// 复杂的动画可以考虑使用 Web Animations API
// 移动端注意电池消耗问题
// 后台标签页中的动画会自动暂停
 

边缘情况处理

考虑移动端键盘弹出、浏览器缩放等场景,可能导致视口高度突变。可通过监听 resize 事件或增加备用检测逻辑。

备用方案

部分旧浏览器不支持 Intersection Observer,需提供 Polyfill 或回退到传统滚动事件监听:

javascript 复制代码
if (!('IntersectionObserver' in window)) {
  window.addEventListener('scroll', throttle(handleScroll, 100));
}

无障碍访问

动态调整按钮位置时,确保焦点管理和屏幕阅读器兼容性。避免按钮遮挡关键内容或导致布局跳动。

相关推荐
南山安2 小时前
Vue学习:ref响应式数据、v-指令、computed
javascript·vue.js·面试
Ayu阿予2 小时前
C++从源文件到可执行文件的过程
开发语言·c++
思茂信息2 小时前
CST电动车EMC仿真——电机控制器MCU滤波仿真
javascript·单片机·嵌入式硬件·cst·电磁仿真
C++业余爱好者2 小时前
JVM优化入门指南:JVM垃圾收集器(GC)介绍
java·开发语言·jvm
福尔摩斯张2 小时前
基于C++的UDP网络通信系统设计与实现
linux·c语言·开发语言·网络·c++·tcp/ip·udp
Trouvaille ~2 小时前
【Java篇】基石与蓝图::Object 类与抽象类的双重奏
java·开发语言·javase·抽象类·类与对象·基础入门·object类
鲸落落丶2 小时前
Vue Router路由
前端·javascript·vue.js
卜锦元2 小时前
Golang中make()和new()的区别与作用?
开发语言·后端·golang
阿呜的边城2 小时前
终于还是吃上了react-i18next的细糠
前端·前端框架