包阅导读总结
1.
关键词:CSS、电梯导航、滚动驱动动画、滚动锚定、兼容性
2.
总结:本文介绍了纯 CSS 实现电梯导航的方法,包括滚动锚定效果、滚动驱动动画、滚动视区范围的调整,核心代码不到 10 行,性能好但兼容性不足,目前仅支持 Chrome 116+。
3.
主要内容:
– 纯 CSS 实现电梯导航
– 滚动锚定
– 通过 a 标签的 href 属性和对应位置的相同 id 实现跳转,可添加滚动动画使跳转更平滑
– 滚动驱动动画
– 利用 view-timeline 和 timeline-scope 实现内容区域和导航区域的滚动联动
– 滚动视区范围
– 通过 view-timeline-inset 手动改变视区范围,避免导航同时选中多个
– 兼容性和总结
– 核心代码简洁,性能优,但兼容性差,仅 Chrome 116+支持,总结了实现过程中的要点
思维导图:
文章地址:https://juejin.cn/post/7396342567867301927
文章来源:juejin.cn
作者:XboxYan
发布时间:2024/7/29 2:07
语言:中文
总字数:3310字
预计阅读时间:14分钟
评分:80分
标签:CSS,滚动驱动动画,电梯导航,前端开发,交互设计
以下为原文内容
本内容来源于用户推荐转载,旨在分享知识与观点,如有侵权请联系删除 联系邮箱 media@ilingban.com
本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!
欢迎关注我的公众号:前端侦探
我们经常会在博客、文档中看到类似这样的侧边导航目录,例如
这种导航也被称为“电梯导航”(当然可能还有其他叫法,知道是这个交互就行)。它会随着内容的滚动而自动切换当前选中态,点击任意目录也会自动滚动到对应标题,就像这样
通常要实现这样一个交互肯定少不了JS
,常规的做法是监听滚动事件,也可以用IntersectionObserver
监听元素的滚动位置状态,下面有一篇关于用IntersectionObserver
的实现
尝试使用JS IntersectionObserver让标题和导航联动
大家可能也发现了,这个交互最大的特点就是滚动,是不是也可以联想到 CSS
滚动驱动动画呢?经过一番尝试,发现纯 CSS
也能完美实现,而且实现更加简单(不到10行),下面是我复刻的效果
是不是非常神奇?CSS
还能实现这样的效果?一起看看吧
一、CSS 滚动锚定
这个导航主要有两个交互:
- 点击导航会自动滚动到页面对应位置
- 页面滚动会自动切换导航选中态
第一条比较容易,我们可以直接用a
标签的能力实现锚定跳转。假设HTML
结构如下
<nav> <a>一、标题一</a> <a>二、标题二</a> <a>三、标题三</a> <a>四、标题四</a> <a>五、标题五</a> <a>六、标题六</a></nav><h1>CSS 电梯导航</h1><div class="content"> <h2>一、标题一</h2> <section> <span></span> <span></span> <span></span> <span></span> <span></span> </section></div><div class="content"> <h2>二、标题二</h2> <section> <span></span> <span></span> <span></span> <span></span> <span></span> <span></span> <span></span> <span></span> <span></span> <span></span> </section></div><div class="content"> <h2>三、标题三</h2> <section> <span></span> <span></span> <span></span> <span></span> <span></span> <span></span> <span></span> <span></span> <span></span> <span></span> </section></div><div class="content"> <h2>四、标题四</h2> <section> <span></span> <span></span> <span></span> <span></span> <span></span> <span></span> <span></span> <span></span> <span></span> <span></span> </section></div><div class="content"> <h2>五、标题五</h2> <section> <span></span> <span></span> <span></span> <span></span> <span></span> <span></span> <span></span> <span></span> <span></span> <span></span> </section></div><div class="content"> <h2>六、标题六</h2> <section> <span></span> <span></span> <span></span> <span></span> <span></span> <span></span> <span></span> <span></span> <span></span> <span></span> </section></div>
然后简单修饰一下
body{ padding: 0 15px;}h2{ margin: 0; padding: .8em 0; scroll-margin: 20px;}nav{ position: fixed; top: 15px; right: 15px; background: #fff; padding: 10px 0; border-radius: 4px; overflow: hidden;}nav>a{ position: relative; display: block; line-height: 2; padding: 0 15px; font-size: 14px; color: #191919; text-decoration: none;}nav>a:hover{ background-color: #d5d5d54a;}section{ display: flex; flex-wrap: wrap; gap: 10px;}section span{ width: 30%; height: 100px; border-radius: 4px; background-color: #E4CCFF;}
效果如下
然后我们只需要给a
标签添加href
属性,页面相对应的地方指定相同的id
,就像这样
<nav> <a href="#t1">一、标题一</a> <a href="#t2">二、标题二</a> ...</nav><div class="content"> <h2 id="t1">一、标题一</h2> <section> ... </section></div><div class="content"> <h2 id="t2">二、标题二</h2> <section> ... </section></div>
这样点击a
标签会自动锚点到对应位置,效果如下
这样就能跳转了,如果你觉得有点生硬,可以加入滚动动画
html,body{ scroll-behavior: smooth;}
这样就平滑多了
这样就实现了滚动锚定效果,还算比较容易。
下面来看如何实现滚动联动效果。
二、CSS 滚动驱动动画
我们可以想一下,如果是IntersectionObserver
该如何做呢?没错,就是监听每一块区域的出现时机,然后改变导航的状态
刚好CSS
滚动驱动动画中的view-timeline
可以实现类似的效果。它可以监测到元素在可视区的情况,更多详细可以回顾之前这篇文章
CSS 滚动驱动动画终于正式支持了~
不过,单独依靠view-timeline
还不行,因为默认情况下,CSS
滚动驱动作用范围只能影响到子元素,而我们的dom
结构明显是分离的
<nav> <a href="#t1">一、标题一</a> <a href="#t2">二、标题二</a> ...</nav><div class="content"> <h2 id="t1">一、标题一</h2> <section> ... </section></div><div class="content"> <h2 id="t2">二、标题二</h2> <section> ... </section></div>
为了解决这个问题,我们需要用到 CSS
时间线范围,也就是 timeline-scope
developer.mozilla.org/en-US/docs/…
这里简单介绍一下,假设有这样一个结构
<div class="content"> <div class="box animation"></div></div><div class="scroller"> <div class="long-element"></div></div>
这是两个元素,右边的是滚动容器,左边的是一个可以旋转的矩形
我们想实现滚动右边区域时,左边矩形跟着旋转,如何实现呢?
可以给他们共同的父级,比如body
定义一个timeline-scope
body{ timeline-scope: --myScroller;}
然后,滚动容器的滚动和矩形的动画就可以通过这个变量关联起来了
.scroller { overflow: scroll; scroll-timeline-name: --myScroller; background: deeppink;}.animation { animation: rotate-appear; animation-timeline: --myScroller;}
效果如下
这样就实现任意元素间的滚动联动。
回到这里,我们要做的事情其实很简单,给父级(body)定义多个timeline-scope
,然后给内容区域和导航区域都绑定一个相同CSS
变量,具体做法如下
<body style="timeline-scope: --t1,--t2,--t3,--t4,--t5,--t6;"> <nav> <a href="#t1" style="--s: --t1">一、标题一</a> <a href="#t2" style="--s: --t2;">二、标题二</a> <a href="#t3" style="--s: --t3">三、标题三</a> <a href="#t4" style="--s: --t4">四、标题四</a> <a href="#t5" style="--s: --t5">五、标题五</a> <a href="#t6" style="--s: --t6">六、标题六</a> </nav> <h1>CSS 电梯导航</h1> <div class="content" style="--s: --t1"> <h2 id="t1">一、标题一</h2> <section> ... </section> </div> <div class="content" style="--s: --t2"> <h2 id="t2">二、标题二</h2> <section> ... </section> </div> <div class="content" style="--s: --t3"> <h2 id="t3">三、标题三</h2> <section> ... </section> </div> <div class="content" style="--s: --t4"> <h2 id="t4">四、标题四</h2> <section> ... </section> </div> <div class="content" style="--s: --t5"> <h2 id="t5">五、标题五</h2> <section> ... </section> </div> <div class="content" style="--s: --t6"> <h2 id="t6">六、标题六</h2> <section> ... </section> </div>
然后给内容区域添加view-timeline-name
,导航标签添加 animation-timeline
,让这两者关联起来,也就是内容滚动时,导航的动画跟着执行,这里的动画很简单,就是改变导航链接的文字颜色和边框颜色,关键实现如下
.content{ view-timeline-name: var(--s);}nav>a{ animation: active; animation-timeline: var(--s);}@keyframes active { 0%,100% { color: #6f00ff; border-color: #6f00ff; }}
效果如下
这样滚动联动效果基本就出来了,不过还是有些小问题,接着优化
三、CSS 滚动视区范围
前面的实现其实还个小问题,右边的导航会同时选中多个
很明显是因为左侧的内容同时出现了这两部分区域。
如果每一块内容高度更少,那同时选中的就更多了,就像这样
而我们需要的肯定是同一时刻只选中一个导航,你可以自己定义规则,比如后面的优先于前面的。
那CSS
该如何实现这样的效果呢?
其实,这里需要换一种思维,上面的实现之所以会同时出现多个选中,是因为视区范围太大,是整个屏幕,所以可以同时匹配到多个内容区域。
因此,我们可以手动的减少视区范围,一直减少成一条线,这样无论怎样滚动,都只会匹配一个区域
在这里,我们可以通过view-timeline-inset
来手动改变视区范围,默认是0
比如我们希望以滚动区域中间为分割线,只要滚动到达这个点,就高亮当前导航,可以这样实现
.content{ view-timeline-name: var(--s); view-timeline-inset: 50%; }
为了方便演示,我在滚动区域中间加了一条红色的线,便于观察
可以很清楚的发现,只要越过这条线,导航马上触发高亮选中。
当然你也可以自己调整这个临界线,比如下面的表示在距离滚动区域底部30%
的地方做判断
.content{ view-timeline-name: var(--s); view-timeline-inset: 70% 30%; }
这样就实现了我们想要的效果了,你也可以访问以下在线链接查看实际效果(chrome 116+)
四、兼容性和总结
看似这么多,其实核心代码就这几行
body{ timeline-scope: --t1,--t2,--t3,--t4,--t5,--t6;}.content{ view-timeline-name: var(--s); view-timeline-inset: 50%;}nav>a{ animation: active; animation-timeline: var(--s);}@keyframes active { 0%,100% { color: #6f00ff; border-color: #6f00ff; }}
包括在HTML
中的几行自定义变量,是不是还不到 10 行?相比 JS
实现,代码更简单,性能也更好,无需初始化,也不用等待 dom
加载,扩展性也强。
唯一的缺点可能是兼容性不足,由于依赖timeline-scope
,所以必须Chrome 116+
,完整兼容性如下
下面总结一下
- 滚动锚定可以借助
a
标签和#id
实现自动滚动跳转 scroll-behavior: smooth
可以实现平滑滚动- 默认情况下,CSS 滚动驱动作用范围只能影响到子元素,但是通过
timeline-scope
,可以让任意元素都可以受到滚动驱动的影响。 - 利用
timeline-scope
,我们可以将每个内容的位置状态和每个导航的选中状态联动起来 - 右边的导航会同时选中多个是因为左边的滚动视区太大了,可以同时包含多个内容区域
- 可以用
view-timeline-inset
来手动改变视区范围,缩小成一条线,这样无论怎样滚动,都只会匹配一个区域 - 兼容性还不足,目前是
Chrome 116+
总的来说,CSS
滚动驱动动画不愧是2023
年度最强特性,可以做的事情太多了,很多 JS
才能实现的交互都可以取代了,而且做的更好,至于兼容性,还是留给时间吧。关注我,学习更多有趣的前端新特性。最后,如果觉得还不错,对你有帮助的话,欢迎点赞、收藏、转发 ❤❤❤