← Назад к шаблонам На главную
Анимации

Интерактивная дорожная карта

Интерактивная дорожная карта с анимацией появления карточек и заполнением линии при прокрутке. Подходит для этапов лечения, разработки, ремонта, обучения и любых процессов, которые нужно показать красиво и понятно.

Сложность: Средняя HTML CSS JavaScript scroll animations intersection observer timeline glassmorphism
Процесс работы

От идеи до результата

Каждый этап появляется при прокрутке, а центральная линия постепенно заполняется цветом.

01

Идея

Обсуждение задачи, пожеланий и формирование концепции.

02

Анализ

Изучение конкурентов, целевой аудитории и технических требований.

03

Дизайн

Создание структуры, анимаций и визуального оформления.

04

Разработка

Реализация интерфейса, логики и адаптивности.

05

Запуск

Финальные проверки и публикация проекта.

<section class="storyline">
    <div class="storyline__header">
        <span class="storyline__badge">Процесс работы</span>
        <h2>От идеи до результата</h2>
        <p>
            Каждый этап появляется при прокрутке, а центральная линия постепенно заполняется цветом.
        </p>
    </div>

    <div class="storyline__wrapper">
        <div class="storyline__progress"></div>

        <article class="storyline__item left">
            <div class="storyline__dot"></div>
            <div class="storyline__card">
                <span>01</span>
                <h3>Идея</h3>
                <p>Обсуждение задачи, пожеланий и формирование концепции.</p>
            </div>
        </article>

        <article class="storyline__item right">
            <div class="storyline__dot"></div>
            <div class="storyline__card">
                <span>02</span>
                <h3>Анализ</h3>
                <p>Изучение конкурентов, целевой аудитории и технических требований.</p>
            </div>
        </article>

        <article class="storyline__item left">
            <div class="storyline__dot"></div>
            <div class="storyline__card">
                <span>03</span>
                <h3>Дизайн</h3>
                <p>Создание структуры, анимаций и визуального оформления.</p>
            </div>
        </article>

        <article class="storyline__item right">
            <div class="storyline__dot"></div>
            <div class="storyline__card">
                <span>04</span>
                <h3>Разработка</h3>
                <p>Реализация интерфейса, логики и адаптивности.</p>
            </div>
        </article>

        <article class="storyline__item left">
            <div class="storyline__dot"></div>
            <div class="storyline__card">
                <span>05</span>
                <h3>Запуск</h3>
                <p>Финальные проверки и публикация проекта.</p>
            </div>
        </article>
    </div>
</section>
.storyline {
    padding: 50px;
    border-radius: 30px;
    overflow: hidden;
    background:
        radial-gradient(circle at 10% 15%, rgba(59, 130, 246, 0.25), transparent 30%),
        radial-gradient(circle at 90% 90%, rgba(236, 72, 153, 0.18), transparent 30%),
        linear-gradient(135deg, #020617, #0f172a);
}

.storyline__header {
    max-width: 700px;
    margin: 0 auto 70px;
    text-align: center;
}

.storyline__badge {
    display: inline-flex;
    margin-bottom: 18px;
    padding: 8px 14px;
    border: 1px solid rgba(96, 165, 250, 0.35);
    border-radius: 999px;
    color: #bfdbfe;
    background: rgba(59, 130, 246, 0.15);
}

.storyline h2 {
    margin: 0 0 18px;
    color: #ffffff;
    font-size: clamp(34px, 5vw, 58px);
}

.storyline p {
    color: #cbd5e1;
    line-height: 1.8;
}

.storyline__wrapper {
    position: relative;
    max-width: 950px;
    margin: auto;
}

.storyline__wrapper::before {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: 50%;
    width: 4px;
    transform: translateX(-50%);
    border-radius: 999px;
    background: #1e293b;
}

.storyline__progress {
    position: absolute;
    top: 0;
    left: 50%;
    width: 4px;
    height: 0;
    transform: translateX(-50%);
    border-radius: 999px;
    background: linear-gradient(to bottom, #3b82f6, #9333ea);
}

.storyline__item {
    position: relative;
    width: 50%;
    padding: 30px 50px;
    opacity: 0;
    transform: translateY(60px);
    transition: 0.7s ease;
}

.storyline__item.visible {
    opacity: 1;
    transform: none;
}

.storyline__item.left {
    margin-right: auto;
}

.storyline__item.right {
    margin-left: auto;
}

.storyline__card {
    padding: 30px;
    border: 1px solid rgba(148, 163, 184, 0.2);
    border-radius: 24px;
    background: rgba(15, 23, 42, 0.7);
    backdrop-filter: blur(18px);
    transition: 0.35s ease;
}

.storyline__item.visible .storyline__card {
    transform: scale(1.03);
}

.storyline__card span {
    color: #60a5fa;
    font-size: 14px;
}

.storyline__card h3 {
    margin: 14px 0;
    color: #ffffff;
}

.storyline__dot {
    position: absolute;
    top: 50%;
    width: 22px;
    height: 22px;
    border: 4px solid #0f172a;
    border-radius: 50%;
    background: #1e293b;
    transition: 0.4s ease;
}

.storyline__item.left .storyline__dot {
    right: -11px;
}

.storyline__item.right .storyline__dot {
    left: -11px;
}

.storyline__item.visible .storyline__dot {
    background: #3b82f6;
    box-shadow: 0 0 20px #3b82f6, 0 0 45px #3b82f6;
}

@media (max-width: 850px) {
    .storyline {
        padding: 28px 18px;
    }

    .storyline__wrapper::before,
    .storyline__progress {
        left: 24px;
    }

    .storyline__item {
        width: 100%;
        padding: 0 0 40px 60px;
    }

    .storyline__item.right,
    .storyline__item.left {
        margin: 0;
    }

    .storyline__dot {
        right: auto !important;
        left: 13px !important;
    }
}
const timelineItems = document.querySelectorAll(".storyline__item");
const timeline = document.querySelector(".storyline");
const progress = document.querySelector(".storyline__progress");

const observer = new IntersectionObserver(
    entries => {
        entries.forEach(entry => {
            if (entry.isIntersecting) {
                entry.target.classList.add("visible");
            }
        });
    },
    {
        threshold: 0.25
    }
);

timelineItems.forEach(item => {
    observer.observe(item);
});

window.addEventListener("scroll", () => {
    const rect = timeline.getBoundingClientRect();
    const total = timeline.offsetHeight;
    const visible = Math.min(Math.max(window.innerHeight - rect.top, 0), total);
    const percent = visible / total;

    progress.style.height = percent * 100 + "%";
});
🚀 Рассчитать проект