Калькулятор грузоперевозки для сайта на Tilda
Считает стоимость перевозки по весу, объёму и расстоянию, сравнивает фактический вес с объёмным и показывает разбивку суммы по строкам.
<section class="vbkgp">
<div class="vbkgp-wrap">
<div class="vbkgp-head">
<h2 class="vbkgp-h2">Стоимость перевозки по вашим данным</h2>
<p class="vbkgp-lead">Укажите вес, объём и расстояние. Калькулятор сравнит фактический вес с объёмным, возьмёт больший и разложит сумму по строкам.</p>
</div>
<div class="vbkgp-grid">
<div class="vbkgp-panel">
<div class="vbkgp-two">
<div class="vbkgp-field">
<label class="vbkgp-lbl" for="vbkgp-w">Вес груза, кг</label>
<input type="number" id="vbkgp-w" class="vbkgp-inp" value="450" min="20" max="20000" step="10" inputmode="numeric">
</div>
<div class="vbkgp-field">
<label class="vbkgp-lbl" for="vbkgp-v">Объём, м³</label>
<input type="number" id="vbkgp-v" class="vbkgp-inp" value="2.4" min="0.1" max="90" step="0.1" inputmode="decimal">
</div>
</div>
<div class="vbkgp-field">
<div class="vbkgp-lbl vbkgp-lbl--row"><span>Расстояние</span><b id="vbkgp-dist">1 800 км</b></div>
<input type="range" id="vbkgp-d" min="100" max="6000" step="50" value="1800" aria-label="Расстояние перевозки в километрах">
<div class="vbkgp-ticks"><i>100 км</i><i>3 000 км</i><i>6 000 км</i></div>
</div>
<div class="vbkgp-field">
<label class="vbkgp-lbl" for="vbkgp-t">Тип груза</label>
<select id="vbkgp-t" class="vbkgp-sel"></select>
</div>
<div class="vbkgp-opts">
<label class="vbkgp-sw"><input type="checkbox" id="vbkgp-pick" class="vbkgp-cb"><span class="vbkgp-sw__b"></span><span class="vbkgp-sw__t">Забрать груз с адреса отправителя</span></label>
<label class="vbkgp-sw"><input type="checkbox" id="vbkgp-door" class="vbkgp-cb" checked><span class="vbkgp-sw__b"></span><span class="vbkgp-sw__t">Доставка до двери получателя</span></label>
<label class="vbkgp-sw"><input type="checkbox" id="vbkgp-ins" class="vbkgp-cb" checked><span class="vbkgp-sw__b"></span><span class="vbkgp-sw__t">Страхование по объявленной стоимости</span></label>
</div>
<div class="vbkgp-field vbkgp-hide" id="vbkgp-valbox">
<label class="vbkgp-lbl" for="vbkgp-val">Объявленная стоимость груза, ₽</label>
<input type="number" id="vbkgp-val" class="vbkgp-inp" value="380000" min="0" step="10000" inputmode="numeric">
</div>
</div>
<div class="vbkgp-res">
<div class="vbkgp-sum">
<span>Стоимость перевозки</span>
<b id="vbkgp-total">0 ₽</b>
<i id="vbkgp-perkg">0 ₽ за килограмм оплачиваемого веса</i>
</div>
<div class="vbkgp-weights" id="vbkgp-weights"></div>
<ul class="vbkgp-rows" id="vbkgp-rows"></ul>
<div class="vbkgp-term">
<span>Срок в пути</span><b id="vbkgp-days">0</b>
</div>
<p class="vbkgp-note">Расчёт предварительный. Итог закрепляем в заявке после замера мест на терминале: если объём окажется меньше, цена уменьшится.</p>
</div>
</div>
</div>
</section>
<style>
.vbkgp, .vbkgp *, .vbkgp *::before, .vbkgp *::after { box-sizing: border-box; }
.vbkgp {
--acc: #c2701a; --ink: #171c22; --muted: #5f6a76; --line: #e2e5e9; --soft: #f4f5f7; --deep: #1b2430;
--f: var(--t-text-font, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif);
--fh: var(--t-headline-font, var(--f));
background: var(--soft); color: var(--ink); font-family: var(--f); padding: clamp(36px, 5vw, 80px) 20px;
}
.vbkgp-wrap { max-width: 1160px; margin: 0 auto; padding: 0 clamp(0px, 2vw, 40px); }
.vbkgp-head { max-width: 44em; margin-bottom: clamp(22px, 3vw, 36px); }
.vbkgp-h2 { font-family: var(--fh); font-size: clamp(26px, 3.4vw, 40px); line-height: 1.1; font-weight: 700; letter-spacing: -.022em; margin: 0 0 12px; }
.vbkgp-lead { font-size: 16px; line-height: 1.6; color: var(--muted); margin: 0; }
.vbkgp-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 16px; align-items: start; }
.vbkgp-panel { background: #fff; border: 1px solid var(--line); border-radius: 20px; padding: clamp(20px, 2.4vw, 28px); }
.vbkgp-two { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 16px; }
.vbkgp-field { margin-bottom: 18px; }
.vbkgp-lbl { display: block; font-size: 14px; color: var(--muted); margin-bottom: 8px; }
.vbkgp-lbl--row { display: flex; justify-content: space-between; align-items: baseline; }
.vbkgp-lbl--row b { font-family: var(--fh); font-size: 18px; color: var(--ink); }
.vbkgp-inp, .vbkgp-sel { width: 100%; height: 52px; font: inherit; font-size: 16px; -webkit-appearance: none; appearance: none;
border: 1.5px solid var(--line); border-radius: 11px; background: #fff; color: var(--ink); }
.vbkgp-inp { padding: 0 15px; }
.vbkgp-sel { padding: 0 38px 0 15px;
background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%), linear-gradient(135deg, var(--muted) 50%, transparent 50%);
background-position: calc(100% - 19px) 23px, calc(100% - 13px) 23px; background-size: 6px 6px, 6px 6px; background-repeat: no-repeat; }
.vbkgp-inp:focus, .vbkgp-sel:focus { outline: 2px solid var(--acc); outline-offset: -2px; }
.vbkgp input[type=range] { -webkit-appearance: none; appearance: none; width: 100%; height: 4px; border-radius: 4px; background: #dfe2e6; outline: none; }
.vbkgp input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 24px; height: 24px; border-radius: 50%;
background: var(--acc); border: 4px solid #fff; box-shadow: 0 1px 6px rgba(0,0,0,.2); cursor: pointer; }
.vbkgp input[type=range]::-moz-range-thumb { width: 18px; height: 18px; border-radius: 50%; background: var(--acc); border: 3px solid #fff; cursor: pointer; }
.vbkgp-ticks { display: flex; justify-content: space-between; margin-top: 6px; font-size: 11px; color: #98a0a9; }
.vbkgp-ticks i { font-style: normal; }
.vbkgp-opts { display: grid; gap: 10px; margin-bottom: 18px; }
.vbkgp-sw { display: flex; align-items: center; gap: 11px; cursor: pointer; }
.vbkgp-cb { position: absolute; opacity: 0; width: 0; height: 0; }
.vbkgp-sw__b { flex: 0 0 42px; width: 42px; height: 24px; border-radius: 999px; background: #d7dbe0; position: relative; transition: background .2s ease; }
.vbkgp-sw__b::after { content: ''; position: absolute; top: 3px; left: 3px; width: 18px; height: 18px; border-radius: 50%; background: #fff; transition: transform .2s ease; }
.vbkgp-cb:checked + .vbkgp-sw__b { background: var(--acc); }
.vbkgp-cb:checked + .vbkgp-sw__b::after { transform: translateX(18px); }
.vbkgp-cb:focus + .vbkgp-sw__b { outline: 2px solid var(--acc); outline-offset: 2px; }
.vbkgp-sw__t { font-size: 14.5px; line-height: 1.45; }
.vbkgp-hide { display: none; }
.vbkgp-res { background: #fff; border: 1px solid var(--line); border-radius: 20px; padding: clamp(20px, 2.4vw, 28px); }
.vbkgp-sum { background: var(--deep); border-radius: 16px; padding: 20px 22px; margin-bottom: 16px; }
.vbkgp-sum span { display: block; font-size: 13px; color: #9aa7b6; margin-bottom: 6px; }
.vbkgp-sum b { display: block; font-family: var(--fh); font-size: clamp(30px, 3.6vw, 40px); line-height: 1; font-weight: 700; color: #fff; letter-spacing: -.02em; }
.vbkgp-sum i { display: block; font-style: normal; font-size: 12.5px; color: var(--acc); margin-top: 8px; }
.vbkgp-weights { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px; margin-bottom: 18px; }
.vbkgp-wt { background: var(--soft); border-radius: 12px; padding: 12px 14px; border: 1.5px solid transparent; }
.vbkgp-wt--on { border-color: var(--acc); background: #fdf6ee; }
.vbkgp-wt span { display: block; font-size: 11.5px; color: var(--muted); margin-bottom: 4px; }
.vbkgp-wt b { font-family: var(--fh); font-size: 17px; font-weight: 700; }
.vbkgp-rows { list-style: none; margin: 0 0 18px; padding: 0; }
.vbkgp-rows li { display: flex; align-items: baseline; gap: 12px; padding: 12px 0; border-bottom: 1px solid var(--line); }
.vbkgp-rows li:last-child { border-bottom: 0; }
.vbkgp-rows i { flex: 1 1 auto; font-style: normal; font-size: 14.5px; }
.vbkgp-rows u { display: block; text-decoration: none; font-size: 12px; color: var(--muted); margin-top: 3px; }
.vbkgp-rows b { flex: 0 0 auto; font-size: 15px; font-weight: 600; white-space: nowrap; }
.vbkgp-term { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; background: var(--soft); border-radius: 12px; padding: 14px 16px; }
.vbkgp-term span { font-size: 13px; color: var(--muted); }
.vbkgp-term b { font-family: var(--fh); font-size: 18px; font-weight: 700; }
.vbkgp-note { margin: 14px 0 0; font-size: 12.5px; line-height: 1.55; color: var(--muted); }
@media (prefers-reduced-motion: reduce) { .vbkgp * { transition: none !important; } }
</style>
<script>
(function () {
/* КОЭФФИЦИЕНТЫ РАСЧЁТА */
var RATES = {
volumeToKg: 250,
perKgKm: 0.0041,
minPrice: 1600,
terminal: 640,
doorBase: 780,
doorPerKg: 0.42,
insuranceRate: 0.0035,
insuranceMin: 350,
cargo: [
{ n: 'Обычный груз в таре', k: 1 },
{ n: 'Хрупкий груз с обрешёткой', k: 1.22 },
{ n: 'Негабарит или длинномер', k: 1.35 },
{ n: 'Температурный режим', k: 1.4 },
{ n: 'Опасный груз по классам', k: 1.6 }
],
days: [[400, '1-2 дня'], [1200, '2-3 дня'], [2500, '3-5 дней'], [4000, '5-7 дней'], [99999, '7-9 дней']]
};
var roots = document.querySelectorAll('.vbkgp');
for (var i = 0; i < roots.length; i++) init(roots[i]);
function init(root) {
if (root.__vb) return; root.__vb = 1;
var q = function (s) { return root.querySelector(s); };
var w = q('#vbkgp-w'), v = q('#vbkgp-v'), d = q('#vbkgp-d'), t = q('#vbkgp-t');
var pick = q('#vbkgp-pick'), door = q('#vbkgp-door'), ins = q('#vbkgp-ins'), val = q('#vbkgp-val');
function fmt(n) { return String(Math.round(n / 10) * 10).replace(/\B(?=(\d{3})+(?!\d))/g, ' '); }
function num(el, def) { var x = parseFloat(String(el.value).replace(',', '.')); return isNaN(x) || x <= 0 ? def : x; }
t.innerHTML = RATES.cargo.map(function (c, i) {
return '<option value="' + i + '">' + c.n + '<\/option>';
}).join('');
function render() {
var kg = Math.min(20000, num(w, 450));
var m3 = Math.min(90, num(v, 2.4));
var km = +d.value;
var cargo = RATES.cargo[+t.value] || RATES.cargo[0];
var volKg = m3 * RATES.volumeToKg;
var payKg = Math.max(kg, volKg);
var line = Math.max(RATES.minPrice, payKg * km * RATES.perKgKm);
var extra = line * (cargo.k - 1);
var doorFee = RATES.doorBase + payKg * RATES.doorPerKg;
var pickFee = pick.checked ? doorFee : 0;
var dropFee = door.checked ? doorFee : 0;
var insFee = ins.checked ? Math.max(RATES.insuranceMin, num(val, 0) * RATES.insuranceRate) : 0;
var total = line + extra + RATES.terminal + pickFee + dropFee + insFee;
q('#vbkgp-valbox').className = 'vbkgp-field' + (ins.checked ? '' : ' vbkgp-hide');
q('#vbkgp-dist').textContent = fmt(km) + ' км';
q('#vbkgp-weights').innerHTML =
'<div class="vbkgp-wt' + (payKg === kg ? ' vbkgp-wt--on' : '') + '"><span>Фактический вес<\/span><b>' + fmt(kg) + ' кг<\/b><\/div>' +
'<div class="vbkgp-wt' + (payKg > kg ? ' vbkgp-wt--on' : '') + '"><span>Объёмный вес<\/span><b>' + fmt(volKg) + ' кг<\/b><\/div>' +
'<div class="vbkgp-wt"><span>Считаем по<\/span><b>' + fmt(payKg) + ' кг<\/b><\/div>';
var rows = [
['Магистральная перевозка', fmt(payKg) + ' кг на ' + fmt(km) + ' км', line],
['Надбавка за тип груза', cargo.n + ', коэффициент ' + cargo.k, extra],
['Обработка на терминале', 'приём, маркировка, выдача', RATES.terminal]
];
if (pickFee) rows.push(['Забор с адреса отправителя', 'подача машины и погрузка', pickFee]);
if (dropFee) rows.push(['Доставка до двери получателя', 'подвоз с терминала назначения', dropFee]);
if (insFee) rows.push(['Страхование груза', 'от объявленной стоимости', insFee]);
q('#vbkgp-rows').innerHTML = rows.map(function (r) {
return '<li><i>' + r[0] + '<u>' + r[1] + '<\/u><\/i><b>' + fmt(r[2]) + ' ₽<\/b><\/li>';
}).join('');
q('#vbkgp-total').textContent = fmt(total) + ' ₽';
var perKg = Math.round(total / payKg * 10) / 10;
q('#vbkgp-perkg').textContent = String(perKg).replace('.', ',') + ' ₽ за килограмм оплачиваемого веса';
var term = RATES.days[RATES.days.length - 1][1];
for (var j = 0; j < RATES.days.length; j++) {
if (km <= RATES.days[j][0]) { term = RATES.days[j][1]; break; }
}
q('#vbkgp-days').textContent = term;
}
var fields = [w, v, d, t, pick, door, ins, val];
for (var j = 0; j < fields.length; j++) {
fields[j].addEventListener('input', render);
fields[j].addEventListener('change', render);
}
render();
}
})();
</script> Вставляется целиком в блок T123 «HTML-код». Внутри уже есть стили и скрипт, подключать ничего не нужно.
Как вставить блок в Tilda
- Скопируйте кодКнопка «Скопировать код» выше кладёт всё содержимое в буфер обмена.
- Добавьте блок T123В редакторе страницы: «Библиотека блоков» → «Другое» → T123 «HTML-код».
- Вставьте и сохранитеНажмите «Контент», вставьте код в поле, сохраните и обновите страницу.
- ОпубликуйтеВ превью скрипты Tilda работают не всегда — проверяйте на опубликованной странице.
Подробная инструкция с граблями — как вставить вайб-блок в Tilda.
Что менять под себя
| Где в коде | За что отвечает |
|---|---|
объект RATES в начале скрипта | плотность для объёмного веса, ставка за килограмм, минимальный заказ |
массив cargo внутри RATES | типы груза и коэффициенты надбавки к магистральной перевозке |
массив days внутри RATES | границы расстояния и подписи срока в пути |
--acc и --deep | цвет ползунка с переключателями и фон плашки с итоговой суммой |
Калькулятор грузоперевозки: зачем он нужен
Главная причина спора о цене перевозки это объёмный вес: лёгкий габаритный груз занимает место как тяжёлый. Калькулятор считает объёмный вес по коэффициенту из настроек, сравнивает его с фактическим и подсвечивает тот, по которому идёт расчёт. Дальше сумма раскладывается на магистральную перевозку, надбавку за тип груза, обработку на терминале, забор, доставку и страхование.
Переключатели забора с адреса, доставки до двери и страхования добавляют строки в разбивку и меняют итог, поле объявленной стоимости появляется только при включённом страховании. Рядом с суммой выводится цена за килограмм оплачиваемого веса и срок в пути по расстоянию. Все коэффициенты собраны в объекте RATES в начале скрипта, править разметку для смены тарифа не нужно.
Вопросы по этому блоку
-
Поле volumeToKg в объекте RATES задаёт, сколько килограммов считается в одном кубометре. По умолчанию стоит 250, у сборных перевозчиков встречаются значения от 200 до 330. Расчёт пересчитается сразу после замены числа.
-
Удалите переключатель с идентификатором vbkgp-ins и блок с объявленной стоимостью из разметки, а в скрипте уберите переменную insFee из суммы и из массива строк разбивки. Остальные строки останутся на месте.
-
Блок даёт предварительную оценку по линейной модели и подходит для первого понимания бюджета. Под суммой выведена подпись о том, что итог закрепляется после замера мест на терминале, её текст можно поменять в разметке.
-
Да. Код на этой странице копируется целиком, без регистрации и подписки. Указывать авторство не нужно, использовать можно в коммерческих проектах.
-
Нет. Все классы внутри имеют собственный префикс vbkgp, поэтому не пересекаются ни со стилями Tilda, ни с другими блоками. Скрипт привязан к своему элементу и запускается один раз, даже если блок вставлен дважды.
-
Раскладка перестраивается под узкие экраны, размеры заданы через clamp, поля ввода имеют шрифт 16 пикселей — iOS не будет зумить страницу при фокусе. Отдельной мобильной версии подключать не нужно.
Другие калькуляторы
Ещё блоки для логистики и грузоперевозок
Собрать сайт целиком, а не по блокам
Дровосеки делают сайты на Tilda под ключ: структура, тексты, дизайн, вёрстка и вайб-блоки под задачи бизнеса.