写博客的时候总想塞点额外的小贴士或者吐槽,放正文里怕打断阅读节奏,搁底下又不够显眼。要是能让脚注像聊天里的气泡弹窗一样,自动飘到文章两侧的空地儿上,并且随着页面滚动一个个蹦出来,那得多带劲。这事儿搁以前得写一堆JavaScript监听滚动和计算位置,现在靠CSS的锚点定位加滚动驱动动画就能整得明明白白。
先看个大概效果:正文里的每个脚注标签(比如一段话末尾的小注释)会脱离原本的位置,飞到屏幕左右两侧固定区域,并且当这个脚注块滚动到可视区域时,它会有一个从小到大、从透明到可见的弹出动画。屏幕太窄的时候(比如手机),脚注自动变回普通内联样式,不占两侧空间。
核心概念
锚点定位(CSS Anchor Positioning)是一套让绝对定位元素相对于另一个元素(叫“锚点”)来摆位置的机制。传统绝对定位只能相对于最近的具有定位属性的祖先容器,而锚点定位可以绑定页面里任意一个元素。滚动驱动动画(Scroll-Driven Animations)则把动画的进度条从“时间”换成了“滚动位置”或“元素可见比例”,想实现“元素出现一半就弹出来”这种效果简直不要太爽。
实战流程
准备标记
HTML结构走起。假设有一篇博客文章,里面某些段落末尾塞了一个脚注容器。
<main class="post">
<h1>标题</h1>
<p>正文第一段,讲了个超有趣的冷知识<span class="footnote">冷知识出处:某本古老秘籍第42页</span>。</p>
<p class="note">另一个段落,这里写重要观点<span class="footnote">观点补充:实际上还有第三种可能性</span>。</p>
<p class="note">再一个段落,继续唠<span class="footnote">唠嗑脚注:这玩意儿真能跑起来</span>。</p>
</main>注意.footnote就是那个要飞出去的目标元素,它原本乖乖待在段落末尾。.post是整个文章容器,后面会把它注册成锚点。
锚点设置
给.post起一个锚点名,用anchor-name属性,值必须是--开头的自定义标识符。
.post {
anchor-name: --post;
}这一步就像在地上钉了个钉子,之后所有脚注都能拿这个钉子当参照物。要是页面里有多篇文章,每篇各自的.post可以起不同的锚点名,互不干扰。
目标定位
让.footnote变成绝对定位元素,然后绑定到锚点--post上。用position-anchor声明默认锚点,再用anchor()函数读取锚点的某条边位置。
.footnote {
position: absolute;
position-anchor: --post;
background: #fff;
border-radius: 20px;
padding: 20px;
margin: 0 20px;
max-width: 200px;
}现在脚注已经脱离文档流,但它们全挤在文章右侧边缘,因为还没指定具体放在左边还是右边。用nth-of-type选择器让奇数脚注贴右侧,偶数脚注贴左侧。由于.footnote不是.note的直接子元素?实际结构里每个.footnote都在各自.note内部,而.note是.post下的同级段落。要按段落顺序交替放置,应该基于.note来计数。
.note:nth-of-type(odd) .footnote {
left: anchor(right);
}
.note:nth-of-type(even) .footnote {
right: anchor(left);
}奇数段落里的脚注,让它的left值等于锚点--post的右边缘位置,脚注就贴在文章框的右侧外面。偶数段落里的脚注,right值等于锚点的左边缘位置,脚注就贴在左侧外面。margin会在外侧撑开一段空白,避免紧贴边缘。
写代码时容易犯一个错:把nth-of-type写成nth-child。如果段落之间混了图片、引用块之类的元素,nth-child计数会乱套,而nth-of-type只数.note元素,稳得很。
弹出动画
弹出动画用@keyframes定义,让脚注从半透明缩小状态变成完全不透明。
@keyframes pop-up {
from {
opacity: 0;
transform: scale(0.5);
}
to {
opacity: 1;
transform: scale(1);
}
}把动画绑定到.footnote上,但动画时长不设固定秒数,而是交给animation-timeline: view()来控制。这个view()关键字让动画进度随着元素进入视口的程度走。
.footnote {
/* 继承上面的定位样式 */
animation: pop-up linear;
animation-timeline: view();
animation-range: cover 0% cover 40%;
}animation-range设置动画的起止点:当脚注刚刚露出一点点(0%覆盖)时开始播放,当它露出四成(40%覆盖)时就播完。这样脚注还没完全滚到屏幕中间就已经完成弹出效果,看着更跟手。如果去掉animation-range,默认是元素完全进入视口才开始,离开视口才结束,体验差一截。
移动适配
窄屏幕下左右两侧根本没空间放飞出去的脚注。解决方法是用媒体查询,宽度小于1000px时把脚注恢复成普通块级元素,不再绝对定位。
@media (width <= 1000px) {
.footnote {
position: static;
margin: 10px 0 0 0;
display: flex;
gap: 10px;
background: #fce6c2;
border-radius: 12px;
}
.footnote::before {
content: "📌 注:";
font-weight: bold;
}
/* 重置那些left/right和anchor相关属性,避免冲突 */
.note:nth-of-type(odd) .footnote,
.note:nth-of-type(even) .footnote {
left: auto;
right: auto;
}
}position: static让脚注回到正常文档流里,老老实实跟在段落屁股后面。加个伪元素::before显示“注:”字样,在手机上看着更明白。媒体查询的阈值设1000px,因为一般博客正文宽度约700-800px,两侧留出200px以上才放得下气泡。
还有一个细节:如果同时用left: anchor(right)和right: anchor(left),两个声明写在不同选择器里不会互相覆盖,但注意在窄屏重置时要把它们都干掉。上面的代码用了.note:nth-of-type(odd) .footnote, .note:nth-of-type(even) .footnote一次性重置。
整活儿完成。跑一下效果:桌面浏览器里,滚动页面时脚注会从两侧pop出来,动画顺滑;手机上看,脚注变成卡片样式跟在正文后面,不占两侧空间。整个实现没有一行JavaScript,全是CSS的新特性在撑场面。锚点定位和滚动驱动动画这两个玩意儿单独用已经很香,搅和在一起能搞出更多花活——比如侧边栏目录随滚动高亮、阅读进度条、视差弹出层等等。
