首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

IntersectionObserver + scrollIntoView 实现电梯导航

编程知识
2024年08月06日 22:54

电梯导航也被称为锚点导航,当点击锚点元素时,页面内相应标记的元素滚动到视口。而且页面内元素滚动时相应锚点也会高亮。电梯导航一般把锚点放在左右两侧,类似电梯一样。常见的电梯导航效果如下,比如一些官方文档中:

image

image

之前可能会用 getBoundingClientRect() 判断元素是否在视口中来实现类似效果,但现在有更方便的方法了,那就是 IntersectionObserver + scrollIntoView,轻松实现电梯导航。

scrollIntoView() 介绍

scrollIntoView() 方法会滚动元素的父容器,使元素出现在可视区域。默认是立即滚动,没有动画效果。

如果要添加动画效果,可以这么做:

scrollIntoView({
  behavior: 'smooth'  // instant 为立即滚动
})

它还有两个可选参数 blockinline

block 表示元素出现在视口时垂直方向与父容器的对齐方式,inline 表示元素出现在视口时水平方向与父容器的对齐方式。

他们同样都有四个值可选 startcenterend 、nearest。默认为 start;

scrollIntoView({
  behavior: 'smooth',
  block:'center',
  inline:'center',
})

对于 block

  • start  将元素的顶部和滚动容器的顶部对齐。

  • center  将元素的中心和滚动容器的中心垂直对齐。

  • end  将元素的底部和滚动容器的底部对齐。

对于 inline

  • start 将元素的左侧和滚动容器的左侧对齐。

  • center  将元素的中心和滚动容器的中心水平对齐。

  • end  将元素的右侧和容器的右侧对齐。

nearest 不论是垂直方向还是水平方向,只要出现在视口任务就完成了。可以理解为以最小移动量让元素出现在视口,(慵懒移动)。如果元素已经完全出现在视口中,则不会发生变化。

通过下面动图来感受这个变化,下面滚动容器中有四行五列,包含了从字母 AT。点击 出现在视口 的按钮会取三个下拉框的值作为参数来调用 scrollIntoView() 方法。

image

再来看看设置为 nearest 后的滚动情况

image

当字母 G 在视口内时,调用方法滚动容器不会发生变化。当 G 不完全在视口内,则会滚动到完全出现在视口内为止。

在这里可以查看这个完整例子 scrollIntoView 可选项参数实践(codepen)

而且 scrollIntoView 兼容性也很好

image

IntersectionObserver 介绍

Intersection Observer API(交叉观察器 API) 提供了一种异步检测目标元素与祖先元素或顶级文档的视口相交情况变化的方法。也就是能判断元素是否在视口中,并且能监听元素在视口中出现的可见部分的比例,从而可以执行我们自定义的逻辑。

由于是异步,也就不会阻塞主线程,性能自然比之前的频繁执行 getBoundingClientRect() 判断元素是否在视口中要好。

创建一个 IntersectionObserver

let options = {
  root: document.querySelector(selector),
  rootMargin: "0",
  threshold: 1.0,
};

let observer = new IntersectionObserver(callback, options);

let target = document.querySelector(selector);
observer.observe(target); //监听目标元素

通过调用 IntersectionObserver 构造函数可以创建一个交叉观察器,构造函数接收两个参数,一个回调函数和一个可选项。上面例子中,当元素完全出现(100%)在视口中时会调用回调函数。

可选项

  • root 用作视口的元素,必须是目标的祖先。默认为浏览器视口。

  • rootMargin 根周围的边距,也就是可以限制根元素检测视口的大小。值的方向大小和平常用的 margin 一样,例如 "10px 20px 30px 40px"(上、右、下、左)。只不过正数是增大根元素检测范围,负数是减小检测范围。

比如设置一个可以滚动的 div 容器为根元素,宽高都为1000px。 此时设置 rootMargin:0 表示根元素检测视口大小就是当前根元素可视区域大小,也就是 1000px * 1000px。设置 rootMargin:25% 0 25% 0 表示上下边距为 25%,那么检测视口大小就是 1000px * 500px。

  • threshold 一个数字或一个数字数组,表示目标出现在视口中达到多少百分比时,观察器的回调就应该执行。如果只想在能见度超过 50% 时检测,可以使用 0.5 的值。如果希望每次能见度超过 25% 时都执行回调,则需要指定数组 [0, 0.25, 0.5, 0.75, 1]。默认值为 0(这意味着只要有一个像素可见,回调就会运行)。值为 1.0 意味着在每个像素都可见之前,阈值不会被认为已通过。

回调函数

当目标元素匹配了可选项中的配置后,会触发我们定义的回调函数

let options = {
  root: document.querySelector(selector),
  rootMargin: "0",
  threshold: 1.0,
};

let observer = new IntersectionObserver(function (entries) {
      entries.forEach(entry => {
        
      })
    }, options);

entries 表示被监听目标元素组成的数组,数组里面每个 entry 都有下列一些值

  • entry.boundingClientRect 返回目标元素的边界信息,值和 getBoundingClientRect() 形式一样。

  • entry.intersectionRatio 目标元素和根元素交叉的比例,也就是出现在检测区域的比例。

  • entry.intersectionRect 返回根和目标元素的相交区域的边界信息,值和 getBoundingClientRect() 形式一样。

  • entry.isIntersecting 返回true或者fasle,表示是否出现在根元素检测区域内

  • entry.rootBounds 返回根元素的边界信息,值和 getBoundingClientRect() 形式一样。

  • entry.target 返回出现在根元素检测区域内的目标元素

  • entry.time 返回从交叉观察器被创建到目标元素出现在检测区域内的时间戳

比如,要检测目标元素有75%出现在检测区域中就可以这样做:

entries.forEach(entry => {
    if(entry.isIntersecting && entry.intersectionRatio>0.75){
         
    }
})

监听目标元素

创建一个观察器后,对一个或多个目标元素进行观察。

let target = document.querySelector(selector);
observer.observe(target);

document.querySelectorAll('div').forEach(el => {
    observer.observe(el)
})

IntersectionObserver 的兼容性也很好:

image

掌握了 IntersectionObserver + scrollIntoView 的用法,实现电梯导航就简单了。

简单写一个电梯导航的 htmlcss

<div style="background:aqua;">第一章</div>
<div style="background: blueviolet;">第二章</div>
<div style="background: chartreuse;">第三章</div>
<div style="background: darkgoldenrod;">第四章</div>
<div style="background: firebrick;">第五章</div>
<div style="background: gold;">第六章</div>
<div style="background: hotpink;">第七章</div>
<ul>
    <li>第一章</li>
    <li>第二章</li>
    <li>第三章</li>
    <li>第四章</li>
    <li>第五章</li>
    <li>第六章</li>
    <li>第七章</li>
</ul>
html,
body {
  width: 100%;
  height: 100%;
  background-color: #fff;
}

ul,li{list-style: none;}

body {
  padding: 20px 0;
}

div{
  width: 60%;
  height: 70%;
  border-radius: 10px;
  margin-left: auto;
  margin-right: auto;
  opacity: 0.4;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 30px;
  font-weight: bold;
  color: #000;
}

div+div {
  margin-top: 20px;
}

.rightBox {
  position: fixed;
  right: 20px;
  top: 50%;
  color: teal;
  transform: translatey(-50%);
}

li {
  cursor: pointer;
  box-sizing: border-box;
  border: 1px solid #fff;
  border-radius: 4px;
  padding: 8px 12px;
}

li:hover {
  background: #f5d2c4;
}

.active {
  background: #f5d2c4;
}

预览如下:

image


第一步:点击右边的导航菜单,利用 scrollIntoView 方法使内容区域对应的元素出现在可视区域中。

    let rightBox = document.querySelector('.rightBox')
    rightBox.addEventListener('click', function (e) {
      let target = e.target || e.srcElement;
      if (target && !target.classList.contains('rightBox')) {
        document.querySelector('.' + target.className.replace('Li', '')).scrollIntoView({
          behavior: 'smooth',
          block: 'center'
        })
      }
    }, false)

image


第二步:页面容器滚动时,当目标元素出现在检测区域内则联动改变对应导航的样式。

这里 threshold 被设置为 1,也就是当目标元素完全显示在可视区域时执行回调,改变导航菜单的样式。

let observer = new IntersectionObserver(function (entries) {
  entries.forEach(entry => {
    let target = document.querySelector('.' + entry.target.className + 'Li')
    if (entry.isIntersecting) { // 出现在检测区域内
      document.querySelectorAll('li').forEach(el => {
        if(el.classList.contains('active')){
          el.classList.remove('active')
        }
      })
      if (!target.classList.contains('active')) {
        target.classList.add('active')
      }
    }
  })
}, {
  threshold: 1
})

document.querySelectorAll('div').forEach(el => {
  observer.observe(el)
})

效果如下:

image


基本要求达到了,不过在滚动过程中,还有些问题。比如连续两个元素来回切换时,第二个元素比第一个元素在检测区域显示的比例更高,虽然没达到 100%,这时候导航菜单显示还是第一个元素的。见下图:

image


所以这里可以控制的更细,两个元素之间谁显示的比例更高时就高亮谁的导航菜单。

let observer = new IntersectionObserver(function (entries) {
    entries.forEach(entry => {
        if (entry.isIntersecting && entry.intersectionRatio > 0.65) {
            
        }
    })
}, {
    threshold: [0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]
})

这里设置了 threshold: [0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8],当目标元素出现在检测区域的比例达到 20%,30%,40%,50%,60%,70%,80% 的时候会执行回调函数,在回调函数里,目标元素可见并且在检测区域显示的比例达到 65% 时高亮导航菜单。这样效果就好些了:

image

在这里可以查看这个完整例子 IntersectionObserver + scrollIntoView 实现电梯导航

当然,具体还是看实际元素块大小和业务需求来定。

如有帮助,帮忙点点赞,感谢~

From:https://www.cnblogs.com/zsxblog/p/18346207
本文地址: http://www.shuzixingkong.net/article/861
0评论
提交 加载更多评论
其他文章 c#12 实验特性Interceptor如何使用的一个简单但完整的示例
一直有很多转载dotnet对Interceptor说明文档的,但鲜有说明Interceptor如何使用的,这里写一篇简单示例来展示一下 c# 12 实验特性Interceptor 是什么? 官方解释如下(其实简单说就是语言特性中内置的静态编织方式的aop功能,不同于其他il修改代码的方式,使用上得结
ffmpeg和ffplay常用指令
FFmpeg 常见用法 1. 基本命令结构 ffmpeg [global_options] -i input_file [input_options] output_file [output_options] 2. 将其它格式图片转换为 YUV420p ffmpeg -i input.jpg -pi
数据结构学习之树结构
前段时间刚好在学习机器学习中的决策树,想起多年前学习树这个数据结构的场景,刚好借此机会回归一下知识点。 树是一种非常常见的数据结构,它由节点(Node)和边(Edge)构成。它有如下的一些特征: 1. 根结点(Root Node):树有且只有一个根结点,它是树的顶端结点。 2. 结点(Node):每
爬虫简易说明
想必大家都了解爬虫,也就是爬取网页你所需要的信息 相比于网页繁多的爬虫教程,本篇主要将爬虫分为四个部分,以便你清楚,代码的功能以及使用,这四部分分别为 1.获取到源代码 2.根据网页中的标签特征,获取源代码你所需要的部分 3.想一下如何根据页面的逻辑将一系列的网页自动化抓取 4.保存数据在xlsx等
增强用户体验:2个功能强大的.NET控制台应用帮助库
前言 对于.NET开发者而言,构建控制台应用程序时,如何提升用户交互的流畅性和满意度,是一个持续探索与优化的话题。今天大姚给大家分享2个功能强大的.NET控制台应用帮助库,希望可以帮助大家能够快速的构建漂亮、强交互性、丰富功能的控制台应用程序。 Terminal.Gui Terminal.Gui是一
增强用户体验:2个功能强大的.NET控制台应用帮助库 增强用户体验:2个功能强大的.NET控制台应用帮助库 增强用户体验:2个功能强大的.NET控制台应用帮助库
给我5分钟,保证教会你在vue3中动态加载远程组件
前言 在一些特殊的场景中(比如低代码、减少小程序包体积、类似于APP的热更新),我们需要从服务端动态加载.vue文件,然后将动态加载的远程vue组件渲染到我们的项目中。今天这篇文章我将带你学会,在vue3中如何去动态加载远程组件。 欧阳写了一本开源电子书vue3编译原理揭秘,这本书初中级前端能看懂。
给我5分钟,保证教会你在vue3中动态加载远程组件 给我5分钟,保证教会你在vue3中动态加载远程组件 给我5分钟,保证教会你在vue3中动态加载远程组件
Redis系列:使用Stream实现消息队列 (图文总结+Go案例)
★ Redis24篇集合 1 先导 我们在《Redis系列14:使用List实现消息队列》这一篇中详细讨论了如何使用List实现消息队列,但同时也看到很多局限性,比如: 不支持消息确认机制,没有很好的ACK应答 不支持消息回溯,无法排查问题和做消息分析 List按照FIFO机制执行,所以存在消息堆积
Redis系列:使用Stream实现消息队列 (图文总结+Go案例)
结合实例看 maven 传递依赖与优先级,难顶也得上丫
开心一刻 想买摩托车了,但是钱不够,想找老爸借点 我:老爸,我想买一辆摩托车,上下班也方便 老爸:你表哥上个月骑摩托车摔走了,你不知道?还要买摩托车? 我:对不起,我不买了 老板:就是啊,骑你表哥那辆得了呗,买啥新的 先抛问题 关于 maven 的依赖(dependency),我相信大家多少都知道点
结合实例看 maven 传递依赖与优先级,难顶也得上丫 结合实例看 maven 传递依赖与优先级,难顶也得上丫 结合实例看 maven 传递依赖与优先级,难顶也得上丫