При работе над новой страницей своего веб-сайта я решил добавить Timeline, чтобы показать свои профессиональные достижения за последние годы. С помощью временной шкалы можно не только отследить собственные достижения, но и привлечь новых клиентов.
На изображении выше показана временная шкала, которую мы будем создавать с помощью React! Для этого выполним следующие действия:
data
TimelineItem
Timeline
для размещения data
, которые затем будут переданы в TimelineItem
Прежде чем создавать компоненты React, нужно разобраться в том, как будут выглядеть денные, чтобы распланировать структуру DOM.
Для этого приложения Timeline понадобится массив объектов. Назовем этот массив: 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
}
];
Свойства достаточно ясны.
Затем создадим компонент 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>
);
У нас есть следующие теги:
.timeline-item
div — используется в качестве wrapper. Этот div обладает половиной родительской ширины (50%
), а все остальные .timeline-item
div размещаются с правой стороны с помощью селектора :nth-child(odd)
.timeline-item-content
div — еще один wrapper (больше информации об этом в разделе стилизации).tag
span — этот тег обладает пользовательским фоновым цветом с зависимости от категорииtime
/date
и text
link
— ее необходимо проверять, чтобы узнать о наличии ссылки (link
) , поскольку она не всегда необходима.circle
span — этот тег используется для размещения круга в середине строки/панелиПримечание: Все будет понятно в разделах 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>
);
Как было сказано выше, timelineData
— это массив объектов, содержащий всю необходимую информацию. В этом случае массив сохранен в файле и импортирован сюда, однако его вы можете взять его из своей базы данных или endpoint API.
Примечание: большинство wrappers будут контейнерами flexbox
, поскольку так легче экспериментировать с их расположением.
Начнем с CSS .timeline-container
:
.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%;
}
Используем селектор ::after
для создания красной линии/полосы в середине .timeline-container
. С помощью функции calc()
линию можно разместить посередине путем вычисления половины ее размера (2px
) из 50%
. Это нужно сделать, поскольку по умолчанию свойство left
размещает ее в соответствии с левым краем, а не посередине.
Теперь перейдем к wrapper .timeline-item
.
Ниже показан пример их размещения относительно родителя (.timeline-container
):
Каждый следующий wrapper переходит вправо, а внутренний wrapper (.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;
}
Смысл заключается в том, что мы используем селектор :nth-child(odd)
и устанавливаем свойство align-self
для flex-end
, которое означает: “Перейти вправо настолько, насколько это возможно”!
Поскольку эти wrappers 50%
шириной, то два из них занимают всю ширину. Таким образом, для изменения стилизации с правой стороны нужно использовать этот подход.
Переходим к wrapper .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);
}
Здесь происходит следующее:
width
и max-width
, необходимые для наличия рамок. Это означает, что при наличии лишь нескольких слов, коробка должна быть минимум 400px
шириной. Однако если текста много, она не должна занимать все пространство (50%
от wrapper .timeline-item
), а текст должен переходить на следующую строку -> по этой причине мы использовали второй wrapper: .timeline-item-content
text-align
и align-items
используются для перемещения внутренних элементов влево или вправо в зависимости от родителя::after
. По сути, это коробка с box-shadow
, нанесенной на нее и повернутой на 45deg
: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;
}
Примечания:
.tag
имеет размещение absolute
, поскольку он должен находится в левом (или правом) верхнем углу независимо от размера коробкиa
для обозначения ссылки.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;
}
}
У нас есть два медиа-запроса:
На маленьких экранах ноутбуков — max-width: 1023px
— .timeline-item-content
должен проходить по всей ширине родителя, поскольку экран меньше, иначе изображение будет выглядеть сжатым
max-width: 767px
.tag
на полную ширину (width
) (не забудьте вычесть 10px
от 100%
— поскольку его расположение left: 5px
, поэтому удаляем вдвое больше от этой суммы)Вот и все!
Этот компонент находится на моей странице Timeline. Там можно увидеть его в действии!
Счастливого программирования! ????
Перевод статьи Florin Pop: How to create a Timeline Component with React
Комментарии