注意
本文最后更新于 2024-07-28,文中内容可能已过时。
本文将通过一个实际应用场景,展示如何使用现代 CSS 实现时间轴、背景图 loading 效果、渐变边框等效果。
最近在调整 FixIt 主题的官方文档,调整过程中总觉得首页空荡荡的少了些内容,然后就在脑海里构思了如本文封面图所示的效果,希望引导用户阅读文档。
需求分析:
第一点很简单,一个 flex
布局就能搞定了,这里不再展开。
接下来我们重点看看如何实现时间轴、背景图 loading 效果和渐变边框。
先睹为快,效果如下:
时间轴动画
我不希望时间轴的实现和其他 UI 框架一样拥有复杂的 DOM 结构,所以使用最简单的 ul
和 li
即可,关键代码如下:
1 2 3 4 5 6 | <ul class="docs-outline"><br> <li><a href="{{ $page1.Permalink }}">{{ $page1.LinkTitle }}</a></li><br> <li><a href="{{ $page2.Permalink }}">{{ $page2.LinkTitle }}</a></li><br> <li><a href="{{ $page3.Permalink }}">{{ $page3.LinkTitle }}</a></li><br> <li><a href="{{ $page4.Permalink }}">{{ $page4.LinkTitle }}</a></li><br></ul> |
---|
DOM 结构就这么简单,重点在于 CSS 的实现,实现思路如下:
ul
设置 list-style: none
,去掉默认的 li
样式::before
和 ::after
伪元素实现时间轴的小圆点和连接线animation-delay
属性实现动画效果关键帧代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | li::before { border: var(--timeline-width) solid var(--timeline-color); animation-name: border-color-fade; animation-duration: 2s; animation-iteration-count: infinite; animation-delay: calc(var(--timeline-index) * 0.3s); @keyframes border-color-fade { 0% { border-color: var(--timeline-color); } 50% { border-color: var(--timeline-circle-color); } 100% { border-color: var(--timeline-color); } } } |
---|
通过 nth-child
选择器来设置 --timeline-index
来线性增加每个小圆点的动画延迟时间,从而在视觉上出现依次点亮的效果。
如果使用 SCSS 可以简化代码,如下:
1 2 3 4 5 6 7 8 9 | @for $i from 1 through 4 { li:nth-child(#{$i}) { --timeline-index: #{$i}; &::before { content: '#{$i}'; } } } |
---|
如果需要在小圆点的内部显示序号,可以像设置 --timeline-index
一样依次设置 content
。
然后在小圆点点亮动画过程中同时转变 color: transparent
到具体的颜色即可。
背景图 loading 效果
如果只是想实现图片的 loading 效果其实很简单,之前在「浏览器 IMG 图片原生懒加载 Loading=“lazy”」中有介绍过,但是这次我是把图片当作背景图片使用的,那问题来了,不通过 JS 背景图片的 loading 效果怎么实现呢?
还是可以利用 ::before
和 ::after
伪元素,一个伪元素用来显示 loading 图,另一个伪元素用来显示背景图片。
原理:利用两张图片加载的时间差,由于 loading 图片很小,所以加载很快,而背景图片加载较慢,然后默认不设置 z-index
的情况下,后面的元素会在上层,所以在背景图片加载完成前,loading 图片会一直显示。
关键代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | .docs-preview { &::before, &::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-position: center; background-repeat: no-repeat; } &::before { background-color: rgba(204, 204, 204, 0.1); background-image: url(/images/loading.min.svg); background-size: 60px; data-theme='dark' & { background-color: rgba(0, 0, 0, 0.1); } } &::after { background-image: url(/images/apple-devices-preview.webp); background-size: 130%; } } |
---|
最后实现一个全尺寸带圆角渐变边框效果,一句话概括为利用线性渐变 linear-gradient
分别设置 padding-box
和 border-box
的背景,然后 border
颜色设置为透明即可实现。
1 2 3 4 5 | .docs-navigation { border-radius: 2.5px; background: linear-gradient(#fff, #fff) padding-box, linear-gradient(45deg, #42d392, #FF7359) border-box; border: 0.25rem solid transparent; } |
---|
值得一提的是,这里面利用到的一个核心概念是 background-clip
属性,详见 background-clip - CSS: Cascading Style Sheets | MDN:
padding-box
表示背景延伸到内边距边界border-box
表示背景延伸到边框边界。另外,如果想实现渐变边框的动画效果,用 SCSS 可以这样做(虽然我不认为这是一个好的做法):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | .docs-navigation { border-radius: 2.5px; background: linear-gradient(#fff, #fff) padding-box, linear-gradient(var(--gradient-angle), #42d392, #FF7359) border-box; border: 0.25rem solid transparent; transition: background-color 0.5s, border-color 0.5s; --gradient-angle: 45deg; animation: gradient-angle-change 10s infinite linear; // 分的越细,动画效果越平滑 @keyframes gradient-angle-change { @for $i from 0 through 100 { #{$i * 1%} { --gradient-angle: #{45 + $i * 4}deg; } } } } |
---|
为了方便使用 FixIt 主题的用户在自己的笔记内插入 FixIt 官方文档的书签,我把这个效果封装成了一个独立的组件,你可以在 hugo-fixit/shortcode-docs-bookmark 中找到完整代码及食用方法。
很多时候都感觉写 CSS 就像在写诗,相同的效果 CSS 实现往往会比 JS 更加优雅。