如何使用 React 创建时间轴组件

2025-06-07

如何使用 React 创建时间轴组件

最近我一直在为我的网站制作一个新页面。我想用一个时间轴来展示我多年来的一些职业成就。

我这么做有几个原因:

  1. 未来的某一天,我会回首往事,感叹:“哇……我还记得我实现那个目标的那一天!实现那个目标我有多开心啊!” 成功是一段旅程,而非终点,我想写下一路走来我实现的每一个目标。
  2. 它可能会吸引更多的客户(我们拭目以待吧😄)
  3. 在我看来,这是一个与众不同的作品集。或许是一个独一无二的作品集?😜

尽管如此...我们现在就建造一些东西吧!

在上图中,你可以看到我们今天要用 React 构建的内容!在开始之前,让我们先分解一下需要采取的步骤:

  1. 创建data我们需要的
  2. 创建TimelineItem组件 - 每个单独的时间线条目
  3. 创建一个Timeline容器 - 它将接收data并将其传递TimelineItem
  4. 为一切设计风格

创建数据

在实际创建 React 组件之前,我们需要确切地知道数据的外观,以便我们可以规划出 DOM 结构。

对于这个时间轴应用,我们需要一个对象数组timelineData。我们将这个数组命名为: 。

让我们看看它看起来怎么样:

[
    {
        text: 'Wrote my first blog post ever on Medium',
        date: 'March 03 2017',
        category: {
            tag: 'medium',
            color: '#018f69'
        },
        link: {
            url:
                'https://medium.com/@popflorin1705/javascript-coding-challenge-1-6d9c712963d2',
            text: 'Read more'
        }
    },
    {
        // Another object with data
    }
];
Enter fullscreen mode Exit fullscreen mode

这些属性很简单,对吧?我使用的数据和我的时间线页面类似,所以可以说它已经可以投入生产了!😆

接下来,我们将构建TimelineItem组件。这将使用上面对象中的数据:

TimelineItem 组件

const TimelineItem = ({ data }) => (
    <div className="timeline-item">
        <div className="timeline-item-content">
            <span className="tag" style={{ background: data.category.color }}>
                {data.category.tag}
            </span>
            <time>{data.date}</time>
            <p>{data.text}</p>
            {data.link && (
                <a
                    href={data.link.url}
                    target="_blank"
                    rel="noopener noreferrer"
                >
                    {data.link.text}
                </a>
            )}
            <span className="circle" />
        </div>
    </div>
);
Enter fullscreen mode Exit fullscreen mode

我们有以下标签:

  1. .timeline-itemdiv - 用作包装器。此 div 的宽度将是其父级宽度的一半(50%),并且每个其他div 将使用选择器.timeline-item放置在右侧:nth-child(odd)
  2. .timeline-item-contentdiv - 另一个包装器(更多关于我们为什么需要它的信息请参见样式部分)
  3. .tagspan - 此标签将根据类别具有自定义背景颜色
  4. /timedatetext
  5. link- 我们需要检查是否link提供了,因为我们可能并不总是想要一个
  6. .circlespan - 此标签将用于在中间线/条上放置一个圆圈

注意:当我们进入CSS /样式部分时,一切都会变得更有意义,但在此之前,让我们先创建Timeline组件:

时间轴容器

这个组件基本上会map覆盖整个数组,并为每个对象创建一个TimelineItem组件。我们还添加了一个小检查,以确保数组中至少有一个元素:

import timelineData from '_path_to_file_';

const Timeline = () =>
    timelineData.length > 0 && (
        <div className="timeline-container">
            {timelineData.map((data, idx) => (
                <TimelineItem data={data} key={idx} />
            ))}
        </div>
    );
Enter fullscreen mode Exit fullscreen mode

如上所述,这timelineData是一个包含所有必需信息的对象数组。在我的例子中,我将这个数组存储在一个文件中,然后将其导入到这里。不过,您可以从自己的数据库或 API 端点获取它,这取决于您。

CSS

请注意,大多数包装器都是flexbox容器,因为我们可以更轻松地调整它们的定位。让我们从.timeline-containerCSS 开始:

.timeline-container {
    display: flex;
    flex-direction: column;
    position: relative;
    margin: 40px 0;
}

.timeline-container::after {
    background-color: #e17b77;
    content: '';
    position: absolute;
    left: calc(50% - 2px);
    width: 4px;
    height: 100%;
}
Enter fullscreen mode Exit fullscreen mode

我们使用::after选择器在 的中间创建那条红线/条.timeline-container。使用calc()2px函数,我们可以从 中减去其大小的一半( ),从而将线条精确定位在中间50%。我们需要这样做是因为默认情况下,该left属性会根据元素的左边缘而不是中间位置来定位线条。


现在,让我们转到.timeline-item包装器。

下面你可以看到它们在其父级()中如何定位的示例.timeline-container。为了演示,我添加了一个边框来突出显示这些包装器:

定位示例

如你所见,其他所有包装器都位于右侧而内部包装器(.timeline-item-content)占用的空间较少——p大部分空间由其内部的标签提供。让我们看看它的 CSS:

.timeline-item {
    display: flex;
    justify-content: flex-end;
    padding-right: 30px;
    position: relative;
    margin: 10px 0;
    width: 50%;
}

.timeline-item:nth-child(odd) {
    align-self: flex-end;
    justify-content: flex-start;
    padding-left: 30px;
    padding-right: 0;
}
Enter fullscreen mode Exit fullscreen mode

关键在于我们使用:nth-child(odd)选择器并将align-self属性设置为flex-end“尽可能向右走”

因为这些包装器是50%按宽度排列的,所以你可以看到其中两个占据了整个宽度。从现在开始,每次我们想在右侧设置不同的样式时都必须使用这种方法。


接下来是.timeline-item-content包装器:

.timeline-item-content {
    box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
    border-radius: 5px;
    background-color: #fff;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    padding: 15px;
    position: relative;
    width: 400px;
    max-width: 70%;
    text-align: right;
}

.timeline-item-content::after {
    content: ' ';
    background-color: #fff;
    box-shadow: 1px -1px 1px rgba(0, 0, 0, 0.2);
    position: absolute;
    right: -7.5px;
    top: calc(50% - 7.5px);
    transform: rotate(45deg);
    width: 15px;
    height: 15px;
}

.timeline-item:nth-child(odd) .timeline-item-content {
    text-align: left;
    align-items: flex-start;
}

.timeline-item:nth-child(odd) .timeline-item-content::after {
    right: auto;
    left: -7.5px;
    box-shadow: -1px 1px 1px rgba(0, 0, 0, 0.2);
}
Enter fullscreen mode Exit fullscreen mode

我们正在做几件事:

  1. 这个包装器有一个固定的width和一个max-width。这是因为我们希望它有一些边界,这意味着如果只有几个单词,我们希望盒子至少足够400px宽,但如果有很多文本,它不应该占据整个空间(50%来自.timeline-item包装器),但文本应该移动到下一行 -> 这就是我们使用第二个包装器的原因:.timeline-item-content
  2. 属性用于将内部元素推到左侧或右侧,具体取决于父text-alignalign-items
  3. 指向中间线的箭头::after是由选择器上应用的样式决定的。本质上,它是一个box-shadow应用了旋转的框45deg
  4. 如上所述,我们通过使用选择器选择父级来设置右侧的样式:nth-child(odd)

接下来是所有内部元素:

.timeline-item-content .tag {
    color: #fff;
    font-size: 12px;
    font-weight: bold;
    top: 5px;
    left: 5px;
    letter-spacing: 1px;
    padding: 5px;
    position: absolute;
    text-transform: uppercase;
}

.timeline-item:nth-child(odd) .timeline-item-content .tag {
    left: auto;
    right: 5px;
}

.timeline-item-content time {
    color: #777;
    font-size: 12px;
    font-weight: bold;
}

.timeline-item-content p {
    font-size: 16px;
    line-height: 24px;
    margin: 15px 0;
    max-width: 250px;
}

.timeline-item-content a {
    font-size: 14px;
    font-weight: bold;
}

.timeline-item-content a::after {
    content: ' ►';
    font-size: 12px;
}

.timeline-item-content .circle {
    background-color: #fff;
    border: 3px solid #e17b77;
    border-radius: 50%;
    position: absolute;
    top: calc(50% - 10px);
    right: -40px;
    width: 20px;
    height: 20px;
    z-index: 100;
}

.timeline-item:nth-child(odd) .timeline-item-content .circle {
    right: auto;
    left: -40px;
}
Enter fullscreen mode Exit fullscreen mode

这里有几点需要注意:

  1. 你可能已经猜到了,之所以.tag定位,absolute是因为无论盒子大小如何,我们都希望它保持在左上角(或右上角)。
  2. 我们想在标签添加一个小插入符号a来突出显示它是一个链接
  3. 我们创建一个并将其放置在箭头正前方.circle的中间线/条的顶部

快完成了!😄 剩下唯一要做的就是添加 CSS,让所有内容都能在所有屏幕尺寸上响应:

@media only screen and (max-width: 1023px) {
    .timeline-item-content {
        max-width: 100%;
    }
}

@media only screen and (max-width: 767px) {
    .timeline-item-content,
    .timeline-item:nth-child(odd) .timeline-item-content {
        padding: 15px 10px;
        text-align: center;
        align-items: center;
    }

    .timeline-item-content .tag {
        width: calc(100% - 10px);
        text-align: center;
    }

    .timeline-item-content time {
        margin-top: 20px;
    }

    .timeline-item-content a {
        text-decoration: underline;
    }

    .timeline-item-content a::after {
        display: none;
    }
}
Enter fullscreen mode Exit fullscreen mode

我们有两个媒体查询:

  1. 在小型笔记本电脑屏幕上,max-width: 1023px我们希望能够.timeline-item-content跨越其父级的整个宽度,因为屏幕较小,否则看起来会很挤
  2. 在手机上 -max-width: 767px
    • 将设置.tag为满width(为此我们不需要忘记10px从总数中减去100%- 这是因为我们将其定位在left: 5px,所以我们删除该数量的两倍)
    • 将所有文本居中,并从顶部向下推一点
    • 删除链接上的插入符号并添加下划线 - 在移动设备上看起来更好😉

然后...我们完成了!

海绵宝宝完成

结论

正如我提到的,这个组件在我的时间线页面上。快来体验一下它的实际效果吧!😄

如果您对本文有任何不明白的地方,请务必联系我,我很乐意回答您的问题!

祝你编程愉快!😇


最初发布于www.florin-pop.com

文章来源:https://dev.to/florinpop17/how-to-create-a-timeline-component-with-react-2dme
PREV
JavaScript 的坚实原则
NEXT
📚 前 1% 的 React 开发者使用的 8 个 repos 🏆 如何找到前 1% 的开发者使用的 repos?🔦 🪮 jsxstyle/jsxstyle 💨 alangpierce/sucrase 🎨 wooorm/refractor 🐦 transitive-bullshit/react-static-tweets 🖨️ preactjs/preact-render-to-string 🏆 bikeshaving/crank 🎯 evoluhq/evolu 📸 jest-community/snapshot-diff