При работе над новой страницей своего веб-сайта я решил добавить Timeline, чтобы показать свои профессиональные достижения за последние годы. С помощью временной шкалы можно не только отследить собственные достижения, но и привлечь новых клиентов. 

На изображении выше показана временная шкала, которую мы будем создавать с помощью React! Для этого выполним следующие действия:

  1. Создадим data
  2. Создадим компонент TimelineItem
  3. Создадим контейнер Timeline для размещения data, которые затем будут переданы в TimelineItem
  4. Выполним стилизацию

Создание data

Прежде чем создавать компоненты 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. Он будет использовать данные из объекта выше:

Компонент 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>
);

У нас есть следующие теги:

  1. .timeline-item div — используется в качестве wrapper. Этот div обладает половиной родительской ширины (50%), а все остальные .timeline-item div размещаются с правой стороны с помощью селектора :nth-child(odd)
  2. .timeline-item-content div — еще один wrapper (больше информации об этом в разделе стилизации)
  3. .tag span — этот тег обладает пользовательским фоновым цветом с зависимости от категории
  4. time/date и text
  5. link — ее необходимо проверять, чтобы узнать о наличии ссылки (link) , поскольку она не всегда необходима
  6. .circle span — этот тег используется для размещения круга в середине строки/панели

Примечание: Все будет понятно в разделах CSS иСтилизация, но для начала создадим компонент Timeline:

Контейнер 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.

CSS

Примечание: большинство 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);
}

Здесь происходит следующее:

  1. Этот wrapper установил width и max-width, необходимые для наличия рамок. Это означает, что при наличии лишь нескольких слов, коробка должна быть минимум 400px шириной. Однако если текста много, она не должна занимать все пространство (50% от wrapper .timeline-item), а текст должен переходить на следующую строку -> по этой причине мы использовали второй wrapper: .timeline-item-content
  2. Свойства text-align и align-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;
}

Примечания:

  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;
    }
}

У нас есть два медиа-запроса:

На маленьких экранах ноутбуков  — max-width: 1023px — .timeline-item-content должен проходить по всей ширине родителя, поскольку экран меньше, иначе изображение будет выглядеть сжатым

  1. На телефонах  — max-width: 767px
  • установите .tag на полную ширину (width) (не забудьте вычесть 10px от 100% — поскольку его расположение left: 5px, поэтому удаляем вдвое больше от этой суммы)
  • разместите текст в центре и опустите его немного вниз от верхнего края
  • удалите значок на ссылке и добавьте подчеркивание  — оно выглядит лучше на мобильном устройстве ?

Вот и все!

Заключение

Этот компонент находится на моей странице Timeline. Там можно увидеть его в действии!

Счастливого программирования! ?

Читайте также:


Перевод статьи Florin Pop: How to create a Timeline Component with React

Предыдущая статьяСоздание анимации Gapminder двумя строчками кода с помощью Plotly Express
Следующая статьяСекрет быстрого обучения