Chinese (Simplified) (中文(简体)) translation by Songfeng Li(李松峰) (you can also view the original English article)
在本教程中,我们将从零开始学习如何构建响应式垂直时间线。 首先,创建基本的标记结构,然后应用CSS伪元素魔法。 接着,使用JavaScript添加向下滚动页面时的过渡效果。
先来看看最终完成后的时间线效果(通过CodePen来看更大的版本)
1. HTML标记
标记很简单,就是一个无序列表,然后每个列表项包含一个div
元素。 因为时间线上要显示事件,所以为每个列表项添加time
元素以显示时间。
此外,把整个列表都放到一个section
元素里,给它一个类叫timeline
:
<section class="timeline"> <ul> <li> <div> <time>1934</time> Some content here </div> </li> <!-- more list items here --> </ul> </section>
此时的页面布局如下所示:
2. 添加CSS样式
先添加一些颜色(下面CSS代码的上半部分),再为列表项应用结构化的CSS规则。 同时,也为这些项目的::after
伪元素添加样式:
.timeline ul li { list-style-type: none; position: relative; width: 6px; margin: 0 auto; padding-top: 50px; background: #fff; } .timeline ul li::after { content: ''; position: absolute; left: 50%; bottom: 0; transform: translateX(-50%); width: 30px; height: 30px; border-radius: 50%; background: inherit; }
为了清楚起见,这里删除了列表项里的内容,结果如下:
3. 时间线元素样式
现在为列表项中的div
元素(我们叫它“时间线元素”)添加样式。 同样,也给这些元素的::before
伪元素添加样式。
一会儿我们会看到,并不是所有“时间线元素”的样式都完全一样。 这就需要用到:nth-child(odd)
和:nth-child(even)
这两个伪类了,通过它们可以实现样式差异化。
以下是相应的CSS规则:
.timeline ul li div { position: relative; bottom: 0; width: 400px; padding: 15px; background: #F45B69; } .timeline ul li div::before { content: ''; position: absolute; bottom: 7px; width: 0; height: 0; border-style: solid; }
然后是奇数个元素的样式:
.timeline ul li:nth-child(odd) div { left: 45px; } .timeline ul li:nth-child(odd) div::before { left: -15px; border-width: 8px 16px 8px 0; border-color: transparent #F45B69 transparent transparent; }
最后是偶数个元素的样式:
.timeline ul li:nth-child(even) div { left: -439px; } .timeline ul li:nth-child(even) div::before { right: -15px; border-width: 8px 0 8px 16px; border-color: transparent transparent transparent #F45B69; }
有了这些样式(以及补充好内容的HTML),时间线的效果就有了雏形:
奇数(odd)和偶数(even)“时间线元素”在样式上的差别主要是定位。 前者为left:45px;
,后者为left:-439px;
。 不理解这些值是怎么来的?好,下面是计算过程:
每个“时间线元素”的宽度+留白-每个列表项的宽度=400px+45px-6px=439px
其次,是各自伪元素上生成的箭头。 “奇数”伪元素生成左箭头,“偶数”伪元素生成右箭头。
4. 交互
时间线的基本结构完成了,下面看看新的需求:
- 默认情况下,时间线元素应该隐藏;
- 它们应该在父元素进入视口时出现。
第一个需求简单。 第二个还挺复杂的。 因为需要检测目标元素是否完全进入了当前视口,如果是则显示其子元素。 要实现这个功能,不需要使用任何JavaScript库(比如WOW.js或ScrollReveal.js)。 甚至都不需要我们自己写太复杂的代码。 哈哈,StackOvewrflow上对这个功能有一个非常热门的讨论。 我们先用上面建议的方式测试某个元素是否完全进入了当前视口。
这是我们使用的一个简单的函数:
function isElementInViewport(el) { var rect = el.getBoundingClientRect(); return ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth) ); }
进入视口时添加类
接下来为当前视口中可见的列表项添加in-view
类。
注意:测试它们在以下情况下是否可见很重要。
- 页面加载时
- 向下滚动时
有必要的话,还可以再测试更多的情况(比如浏览器窗口缩放时)。
我们这里使用的代码如下:
var items = document.querySelectorAll(".timeline li"); // code for the isElementInViewport function function callbackFunc() { for (var i = 0; i < items.length; i++) { if (isElementInViewport(items[i])) { items[i].classList.add("in-view"); } } } window.addEventListener("load", callbackFunc); window.addEventListener("scroll", callbackFunc);
添加完JavaScript,再刷新页面,应该看到如下结果:

隐藏或显示
先回顾一下我们的需求。 记住,默认情况下,所有DIV都应该隐藏。 为此我们visibility
和opacity
这两个CSS属性。 另外,我们使用translated3d()
把它们从原始位置移开200px。 只要它们的父元素可见,就显示它们并删除预先设置的位移。 这样,就可以实现漂亮的滑入特效。
最后还要做一件事,就是当li
元素在视口中时,要修改其::before
伪元素的背景颜色。
以下就是实现以上需要的所有样式:
.timeline ul li::after { background: #fff; transition: background .5s ease-in-out; } .timeline ul li.in-view::after { background: #F45B69; } .timeline ul li div { visibility: hidden; opacity: 0; transition: all .5s ease-in-out; } .timeline ul li:nth-child(odd) div { transform: translate3d(200px,0,0); } .timeline ul li:nth-child(even) div { transform: translate3d(-200px,0,0); } .timeline ul li.in-view div { transform: none; visibility: visible; opacity: 1; }
下面的示意图展示了时间线的初始状态。 能看到时间线元素,是为了显示它们的初始位置而为它们应用了一点不透明度。

而以下是时间线的最终状态:

5. 响应式
终于快完成了。 最后一件事就是实现响应式的时间线。
首先,在所谓的“中等屏幕”(>600px且≤900px)上,我们只做少许改动。 特别是要修改DIV的宽度。
以下是要修改的规则:
@media screen and (max-width: 900px) { .timeline ul li div { width: 250px; } .timeline ul li:nth-child(even) div { left: -289px; /*250+45-6*/ } }
此时的时间线效果如下:

而在小屏幕(≤600px)上,所有时间线元素看起来都一样,无论是奇数个还是偶数个。 同样,需要覆盖一些CSS规则:
@media screen and (max-width: 600px) { .timeline ul li { margin-left: 20px; } .timeline ul li div { width: calc(100vw - 91px); } .timeline ul li:nth-child(even) div { left: 45px; } .timeline ul li:nth-child(even) div::before { left: -15px; border-width: 8px 16px 8px 0; border-color: transparent #F45B69 transparent transparent; } }
在更小一些的屏幕上,时间线应该是这样的:

注意:在小屏幕上,我们使用了vw
单位指定时间线的宽度。 使用这个单位并没有什么特别的原因。 使用百分比或像素也是一样的。
浏览器支持
以上示例可以在大多数较新的浏览器和设备中运行。 但是在iOS设备上,时间线元素始终会保持可见,而不是在它们的父元素进入视口时才出现。
根据我的测试,我发现在那些设备上window.innerHeight和document.documentElement.clientHeight返回的并非实际的视口高度。 它们返回的值要大很多。 由于存在这种不一致的问题,所有列表项都会在页面加载后获得in-view
类。
虽然这不算是个大问题(通常我们都希望在大屏幕上看到动画),但假如你知道这个问题,或者以前也碰到过,请在评论中留言吧。
小结
本教程教大家写了一个响应式的垂直时间线。 涉及的知识点不少,我们来回顾一下:
- 使用简单的无序列表和CSS伪元素,我们实现了时间线的主框架。 但也有一个不足,就是CSS伪元素并非百分之百地可用,要注意。
- 我们利用了StackOverflow上的一段代码来检测列表项是否进入了视口。 然后,我们又写了CSS实现它们子元素的进入动画。 当然,这里也可以利用JavaScript库。
希望大家能从这篇教程中学到新东西,并利用这个时间线实现一些有意思的效果。 如果大家有什么问题,欢迎留言。
Subscribe below and we’ll send you a weekly email summary of all new Web Design tutorials. Never miss out on learning about the next big thing.
Update me weeklyEnvato Tuts+ tutorials are translated into other languages by our community members—you can be involved too!
Translate this post