← Назад к шаблонам На главную
Формы

Интерактивный калькулятор стоимости

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

Сложность: Сложная HTML CSS JavaScript calculator sliders checkboxes live price responsive layout
Калькулятор стоимости

Цена считается прямо во время выбора

Посетитель выбирает формат, услуги и срочность, а итоговая стоимость пересчитывается сразу. Такой блок помогает быстрее довести человека до заявки.

Формат работы

Дополнительные опции

Количество встреч

3 шт.

Срочность

<section class="price-lab">
    <div class="price-lab__head">
        <span class="price-lab__badge">Калькулятор стоимости</span>

        <h2>Цена считается прямо во время выбора</h2>

        <p>
            Посетитель выбирает формат, услуги и срочность, а итоговая стоимость
            пересчитывается сразу. Такой блок помогает быстрее довести человека до заявки.
        </p>
    </div>

    <div class="price-lab__grid">
        <div class="price-lab__controls">
            <div class="price-lab__group">
                <h3>Формат работы</h3>

                <div class="price-lab__cards">
                    <button class="price-lab__format active" type="button" data-price="2500" data-label="Базовый">
                        <strong>Базовый</strong>
                        <span>от 2 500 ₽</span>
                    </button>

                    <button class="price-lab__format" type="button" data-price="4500" data-label="Расширенный">
                        <strong>Расширенный</strong>
                        <span>от 4 500 ₽</span>
                    </button>

                    <button class="price-lab__format" type="button" data-price="7000" data-label="Премиум">
                        <strong>Премиум</strong>
                        <span>от 7 000 ₽</span>
                    </button>
                </div>
            </div>

            <div class="price-lab__group">
                <h3>Дополнительные опции</h3>

                <label class="price-lab__option">
                    <input type="checkbox" data-price="1200" data-title="Подробная диагностика">
                    <span>Подробная диагностика</span>
                    <strong>+1 200 ₽</strong>
                </label>

                <label class="price-lab__option">
                    <input type="checkbox" data-price="900" data-title="Индивидуальные рекомендации">
                    <span>Индивидуальные рекомендации</span>
                    <strong>+900 ₽</strong>
                </label>

                <label class="price-lab__option">
                    <input type="checkbox" data-price="1500" data-title="Сопровождение после услуги">
                    <span>Сопровождение после услуги</span>
                    <strong>+1 500 ₽</strong>
                </label>
            </div>

            <div class="price-lab__group">
                <div class="price-lab__range-head">
                    <h3>Количество встреч</h3>
                    <strong><span class="price-lab__visits-value">3</span> шт.</strong>
                </div>

                <input class="price-lab__range" type="range" min="1" max="10" value="3">
            </div>

            <div class="price-lab__group">
                <h3>Срочность</h3>

                <select class="price-lab__urgency">
                    <option value="1">Обычный срок</option>
                    <option value="1.25">Быстрее обычного ×1.25</option>
                    <option value="1.5">Срочно ×1.5</option>
                </select>
            </div>
        </div>

        <aside class="price-lab__result">
            <span class="price-lab__result-label">Предварительная стоимость</span>

            <div class="price-lab__price">
                <span class="price-lab__price-value">0</span> ₽
            </div>

            <div class="price-lab__summary">
                <p>
                    Формат:
                    <strong class="price-lab__summary-format">Базовый</strong>
                </p>

                <p>
                    Встреч:
                    <strong class="price-lab__summary-visits">3</strong>
                </p>

                <p>
                    Опции:
                    <strong class="price-lab__summary-options">не выбраны</strong>
                </p>
            </div>

            <div class="price-lab__bar">
                <span></span>
            </div>

            <button class="price-lab__button" type="button">
                Оставить заявку
            </button>

            <p class="price-lab__note">
                Это пример расчёта. В реальном проекте можно отправлять выбранные параметры в заявку.
            </p>
        </aside>
    </div>
</section>
.price-lab {
    padding: 46px;
    border-radius: 28px;
    overflow: hidden;
    background:
        radial-gradient(circle at 12% 18%, rgba(59, 130, 246, 0.30), transparent 32%),
        radial-gradient(circle at 88% 82%, rgba(34, 197, 94, 0.18), transparent 34%),
        linear-gradient(135deg, #020617, #0f172a);
}

.price-lab__head {
    max-width: 860px;
    margin-bottom: 34px;
}

.price-lab__badge {
    display: inline-flex;
    margin-bottom: 16px;
    padding: 8px 13px;
    border-radius: 999px;
    color: #bbf7d0;
    background: rgba(34, 197, 94, 0.12);
    border: 1px solid rgba(34, 197, 94, 0.35);
    font-size: 13px;
}

.price-lab__head h2 {
    margin: 0 0 16px;
    color: #ffffff;
    font-size: clamp(32px, 5vw, 56px);
    line-height: 1.02;
    letter-spacing: -0.05em;
}

.price-lab__head p {
    color: #cbd5e1;
    font-size: 17px;
    line-height: 1.7;
}

.price-lab__grid {
    display: grid;
    grid-template-columns: 1.1fr 0.9fr;
    gap: 26px;
    align-items: start;
}

.price-lab__controls,
.price-lab__result {
    border: 1px solid rgba(148, 163, 184, 0.28);
    border-radius: 26px;
    background: rgba(15, 23, 42, 0.78);
    box-shadow: 0 28px 80px rgba(2, 6, 23, 0.5);
}

.price-lab__controls {
    display: grid;
    gap: 22px;
    padding: 26px;
}

.price-lab__group h3 {
    margin: 0 0 14px;
    color: #ffffff;
    font-size: 18px;
}

.price-lab__cards {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 12px;
}

.price-lab__format {
    border: 1px solid #334155;
    border-radius: 18px;
    padding: 16px;
    color: #cbd5e1;
    text-align: left;
    background: #020617;
    cursor: pointer;
    transition: 0.25s;
}

.price-lab__format strong,
.price-lab__format span {
    display: block;
}

.price-lab__format strong {
    color: #ffffff;
    margin-bottom: 8px;
}

.price-lab__format span {
    color: #94a3b8;
}

.price-lab__format.active,
.price-lab__format:hover {
    border-color: #22c55e;
    background: rgba(34, 197, 94, 0.12);
    transform: translateY(-2px);
}

.price-lab__option {
    display: grid;
    grid-template-columns: auto 1fr auto;
    gap: 12px;
    align-items: center;
    padding: 14px;
    margin-bottom: 10px;
    border: 1px solid #334155;
    border-radius: 16px;
    color: #dbeafe;
    background: #020617;
    cursor: pointer;
}

.price-lab__option input {
    accent-color: #22c55e;
}

.price-lab__option strong {
    color: #86efac;
}

.price-lab__range-head {
    display: flex;
    justify-content: space-between;
    gap: 14px;
    align-items: center;
}

.price-lab__range-head strong {
    color: #86efac;
}

.price-lab__range {
    width: 100%;
    accent-color: #22c55e;
}

.price-lab__urgency {
    width: 100%;
    border: 1px solid #334155;
    border-radius: 16px;
    padding: 13px 14px;
    color: #ffffff;
    background: #020617;
    outline: none;
}

.price-lab__result {
    position: sticky;
    top: 110px;
    padding: 30px;
    overflow: hidden;
}

.price-lab__result::before {
    content: "";
    position: absolute;
    width: 260px;
    height: 260px;
    right: -110px;
    top: -110px;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(34, 197, 94, 0.25), transparent 70%);
}

.price-lab__result > * {
    position: relative;
    z-index: 2;
}

.price-lab__result-label {
    color: #94a3b8;
}

.price-lab__price {
    margin: 14px 0 22px;
    color: #ffffff;
    font-size: clamp(42px, 7vw, 72px);
    font-weight: 800;
    letter-spacing: -0.06em;
}

.price-lab__summary {
    display: grid;
    gap: 10px;
    margin-bottom: 24px;
}

.price-lab__summary p {
    display: flex;
    justify-content: space-between;
    gap: 16px;
    margin: 0;
    color: #94a3b8;
}

.price-lab__summary strong {
    color: #dbeafe;
    text-align: right;
}

.price-lab__bar {
    height: 10px;
    overflow: hidden;
    border-radius: 999px;
    background: #020617;
    border: 1px solid #334155;
    margin-bottom: 22px;
}

.price-lab__bar span {
    display: block;
    width: 0;
    height: 100%;
    border-radius: 999px;
    background: linear-gradient(135deg, #22c55e, #3b82f6);
    transition: 0.35s;
}

.price-lab__button {
    width: 100%;
    border: 0;
    border-radius: 16px;
    padding: 15px 18px;
    color: #ffffff;
    background: linear-gradient(135deg, #16a34a, #2563eb);
    cursor: pointer;
    transition: 0.25s;
}

.price-lab__button:hover {
    transform: translateY(-2px);
}

.price-lab__note {
    margin: 18px 0 0;
    color: #94a3b8;
    font-size: 14px;
}

.price-lab__price.is-updated {
    animation: pricePulse 0.28s ease;
}

@keyframes pricePulse {
    from {
        transform: scale(0.96);
        color: #86efac;
    }

    to {
        transform: scale(1);
        color: #ffffff;
    }
}

@media (max-width: 900px) {
    .price-lab {
        padding: 26px 18px;
    }

    .price-lab__grid {
        grid-template-columns: 1fr;
    }

    .price-lab__result {
        position: static;
    }
}

@media (max-width: 560px) {
    .price-lab__cards {
        grid-template-columns: 1fr;
    }

    .price-lab__summary p {
        flex-direction: column;
        gap: 4px;
    }

    .price-lab__summary strong {
        text-align: left;
    }
}
const priceFormats = document.querySelectorAll(".price-lab__format");
const priceOptions = document.querySelectorAll(".price-lab__option input");
const priceRange = document.querySelector(".price-lab__range");
const priceUrgency = document.querySelector(".price-lab__urgency");

const visitsValue = document.querySelector(".price-lab__visits-value");
const priceValue = document.querySelector(".price-lab__price-value");
const priceBox = document.querySelector(".price-lab__price");
const summaryFormat = document.querySelector(".price-lab__summary-format");
const summaryVisits = document.querySelector(".price-lab__summary-visits");
const summaryOptions = document.querySelector(".price-lab__summary-options");
const progressBar = document.querySelector(".price-lab__bar span");

let basePrice = 2500;
let formatLabel = "Базовый";

function formatMoney(value) {
    return new Intl.NumberFormat("ru-RU").format(value);
}

function animatePrice() {
    priceBox.classList.remove("is-updated");
    void priceBox.offsetWidth;
    priceBox.classList.add("is-updated");
}

function calculatePrice() {
    const visits = Number(priceRange.value);
    const urgency = Number(priceUrgency.value);

    let optionsPrice = 0;
    const selectedOptions = [];

    priceOptions.forEach(function (option) {
        if (option.checked) {
            optionsPrice += Number(option.dataset.price);
            selectedOptions.push(option.dataset.title);
        }
    });

    const subtotal = (basePrice + optionsPrice) * visits;
    const total = Math.round(subtotal * urgency);

    visitsValue.textContent = visits;
    summaryVisits.textContent = visits;
    summaryFormat.textContent = formatLabel;
    summaryOptions.textContent = selectedOptions.length ? selectedOptions.join(", ") : "не выбраны";
    priceValue.textContent = formatMoney(total);

    const progress = Math.min(100, (total / 120000) * 100);
    progressBar.style.width = progress + "%";

    animatePrice();
}

priceFormats.forEach(function (button) {
    button.addEventListener("click", function () {
        priceFormats.forEach(item => item.classList.remove("active"));

        button.classList.add("active");
        basePrice = Number(button.dataset.price);
        formatLabel = button.dataset.label;

        calculatePrice();
    });
});

priceOptions.forEach(function (option) {
    option.addEventListener("change", calculatePrice);
});

priceRange.addEventListener("input", calculatePrice);
priceUrgency.addEventListener("change", calculatePrice);

calculatePrice();
🚀 Рассчитать проект