Калькулятор корзины и доставки для интернет-магазина
Считает заказ целиком: позиции со счётчиками, скидка за объём, стоимость доставки по весу и подсказка, на какую сумму добрать до бесплатной отправки.
<section class="vbkkd">
<div class="vbkkd-wrap">
<div class="vbkkd-head">
<h2 class="vbkkd-h2">Соберите корзину и посмотрите итог</h2>
<p class="vbkkd-lead">Отметьте позиции и количество, выберите способ доставки. Скидка за объём и стоимость отправки пересчитываются сразу, порог бесплатной доставки виден в правой колонке.</p>
</div>
<div class="vbkkd-grid">
<div class="vbkkd-goods" id="vbkkd-goods"></div>
<aside class="vbkkd-side">
<div class="vbkkd-ship" id="vbkkd-ship" role="group" aria-label="Способ доставки"></div>
<ul class="vbkkd-sum">
<li><span>Позиций в корзине</span><b id="vbkkd-cnt">0</b></li>
<li><span>Стоимость товаров</span><b id="vbkkd-goodsum">0 ₽</b></li>
<li id="vbkkd-discrow" hidden><span id="vbkkd-disclbl">Скидка за объём</span><b id="vbkkd-disc">0 ₽</b></li>
<li><span id="vbkkd-shiplbl">Доставка</span><b id="vbkkd-shipval">0 ₽</b></li>
</ul>
<div class="vbkkd-total">
<span>К оплате</span>
<b id="vbkkd-total">0 ₽</b>
</div>
<div class="vbkkd-free">
<div class="vbkkd-free__bar" aria-hidden="true"><i id="vbkkd-freefill"></i></div>
<p class="vbkkd-free__txt" id="vbkkd-freetxt"></p>
</div>
<a class="vbkkd-btn" href="#katalog">Открыть каталог целиком</a>
<p class="vbkkd-note">Расчёт показывает порядок суммы. Точная стоимость отправки зависит от веса коробки и адреса, её подтверждает менеджер после сборки заказа.</p>
</aside>
</div>
</div>
</section>
<style>
.vbkkd, .vbkkd *, .vbkkd *::before, .vbkkd *::after { box-sizing: border-box; }
.vbkkd {
--acc: #9c2b45; --acc-dark: #7d2036; --ink: #1e1c1b; --muted: #6b6561; --line: #e4e0dc; --soft: #f7f5f2; --ok: #3f7a52;
--f: var(--t-text-font, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif);
--fh: var(--t-headline-font, var(--f));
background: #fff; color: var(--ink); font-family: var(--f); padding: clamp(36px, 5vw, 84px) 20px;
}
.vbkkd-wrap { max-width: 1200px; margin: 0 auto; padding: 0 clamp(0px, 2vw, 38px); }
.vbkkd-head { max-width: 44em; margin-bottom: clamp(22px, 3vw, 34px); }
.vbkkd-h2 { font-family: var(--fh); font-size: clamp(25px, 3.3vw, 39px); line-height: 1.1; font-weight: 700; letter-spacing: -.02em; margin: 0 0 12px; }
.vbkkd-lead { font-size: 16px; line-height: 1.62; color: var(--muted); margin: 0; }
.vbkkd-grid { display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(0, .65fr); gap: clamp(16px, 2.4vw, 28px); align-items: start; }
.vbkkd-goods { display: grid; gap: 10px; }
.vbkkd-item { display: grid; grid-template-columns: 44px minmax(0, 1fr) auto; align-items: center; gap: 16px;
border: 1px solid var(--line); border-radius: 16px; padding: 14px 18px; transition: border-color .2s ease; }
.vbkkd-item--on { border-color: var(--acc); background: var(--soft); }
.vbkkd-ico { width: 44px; height: 44px; display: flex; align-items: center; justify-content: center; border-radius: 12px;
background: var(--soft); color: var(--acc); }
.vbkkd-item--on .vbkkd-ico { background: #fff; }
.vbkkd-ico svg { width: 24px; height: 24px; }
.vbkkd-nm { display: block; font-size: 15.5px; font-weight: 600; }
.vbkkd-ds { display: block; font-size: 12.5px; line-height: 1.45; color: var(--muted); margin-top: 3px; }
.vbkkd-cnt { display: flex; align-items: center; gap: 4px; }
.vbkkd-pm { width: 36px; height: 36px; border: 1px solid var(--line); margin: 0; padding: 0; appearance: none; cursor: pointer;
border-radius: 10px; background: #fff !important; color: var(--ink) !important; font: inherit; font-size: 19px; line-height: 1; }
.vbkkd-pm:hover { border-color: var(--acc); color: var(--acc) !important; }
.vbkkd-qty { min-width: 30px; text-align: center; font-size: 15px; font-weight: 600; }
.vbkkd-pr { display: block; text-align: right; font-size: 13px; color: var(--muted); margin-top: 5px; white-space: nowrap; }
.vbkkd-side { background: var(--soft); border-radius: 22px; padding: clamp(20px, 2.4vw, 26px); }
.vbkkd-ship { display: grid; gap: 7px; margin-bottom: 20px; }
.vbkkd-sw { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; width: 100%; border: 1.5px solid transparent;
margin: 0; padding: 12px 15px; appearance: none; font: inherit; cursor: pointer; text-align: left; border-radius: 12px;
background: #fff !important; color: var(--ink) !important; transition: border-color .18s ease; }
.vbkkd-sw span { font-size: 14.5px; }
.vbkkd-sw i { font-style: normal; font-size: 12px; color: var(--muted); white-space: nowrap; }
.vbkkd-sw--on { border-color: var(--acc); }
.vbkkd-sum { list-style: none; margin: 0 0 16px; padding: 0; display: grid; gap: 10px; }
.vbkkd-sum li { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; font-size: 14px; color: var(--muted); }
.vbkkd-sum b { font-size: 15px; font-weight: 600; color: var(--ink); white-space: nowrap; }
.vbkkd-sum li b.vbkkd-minus { color: var(--ok); }
.vbkkd-total { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; border-top: 1px solid var(--line);
padding-top: 16px; margin-bottom: 16px; }
.vbkkd-total span { font-size: 14px; color: var(--muted); }
.vbkkd-total b { font-family: var(--fh); font-size: clamp(24px, 2.6vw, 30px); font-weight: 700; color: var(--acc); white-space: nowrap; }
.vbkkd-free__bar { height: 5px; border-radius: 5px; background: var(--line); overflow: hidden; margin-bottom: 8px; }
.vbkkd-free__bar i { display: block; height: 100%; width: 0; border-radius: 5px; background: var(--ok); transition: width .4s cubic-bezier(.22,1,.36,1); }
.vbkkd-free__txt { margin: 0 0 18px; font-size: 12.5px; line-height: 1.5; color: var(--muted); }
.vbkkd-btn, .vbkkd-btn:link, .vbkkd-btn:visited, .vbkkd-btn:focus { display: flex; align-items: center; justify-content: center;
min-height: 54px; border-radius: 12px; font-size: 16px; font-weight: 600; background: var(--acc); color: #fff !important;
text-decoration: none !important; }
.vbkkd-btn:hover { background: var(--acc-dark); color: #fff !important; }
.vbkkd-note { margin: 14px 0 0; font-size: 11.5px; line-height: 1.5; color: #9a938e; }
@media (max-width: 900px) { .vbkkd-grid { grid-template-columns: minmax(0, 1fr); } }
@media (max-width: 520px) {
.vbkkd-item { grid-template-columns: minmax(0, 1fr) auto; }
.vbkkd-ico { display: none; }
}
@media (prefers-reduced-motion: reduce) { .vbkkd * { transition: none !important; } }
</style>
<script>
(function () {
/* ── КОЭФФИЦИЕНТЫ: цены, скидки, доставка ── */
var CONF = {
free: 6000,
steps: [{ from: 8000, p: 5 }, { from: 15000, p: 10 }, { from: 30000, p: 15 }],
ship: [
{ k: 'pvz', n: 'Пункт выдачи', d: '3-5 дней', base: 290, perKg: 0 },
{ k: 'kur', n: 'Курьер до двери', d: '2-4 дня', base: 490, perKg: 20 },
{ k: 'tk', n: 'Транспортная компания', d: 'от 4 дней', base: 850, perKg: 35 }
],
goods: [
{ n: 'Кружка «Слой», 350 мл', d: 'керамика, ручная формовка', price: 690, kg: 0.5, ico: 'm' },
{ n: 'Тарелка обеденная, 27 см', d: 'глазурь одной партии', price: 940, kg: 0.8, ico: 'p' },
{ n: 'Столовый комплект на 4 персоны', d: '16 предметов в коробке', price: 8900, kg: 6.4, ico: 'p' },
{ n: 'Кастрюля стальная, 3 литра', d: 'дно с тройным слоем', price: 5400, kg: 2.2, ico: 'k' },
{ n: 'Банка для хранения, 0,7 литра', d: 'крышка из бука, кольцо', price: 820, kg: 0.6, ico: 'b' },
{ n: 'Доска разделочная, дуб', d: '38 на 24 сантиметра', price: 2100, kg: 1.4, ico: 'd' }
]
};
var ICONS = {
m: '<path d="M4 6h11v11a4 4 0 0 1-4 4H8a4 4 0 0 1-4-4z"/><path d="M15 9h3a3 3 0 0 1 0 6h-3"/>',
p: '<ellipse cx="12" cy="12" rx="9" ry="9"/><ellipse cx="12" cy="12" rx="4" ry="4"/>',
k: '<path d="M4 8h16v8a5 5 0 0 1-5 5H9a5 5 0 0 1-5-5z"/><path d="M9 8V5h6v3"/>',
b: '<rect x="6" y="8" width="12" height="13" rx="3"/><rect x="5" y="3" width="14" height="4" rx="2"/>',
d: '<rect x="3" y="4" width="14" height="17" rx="3"/><path d="M17 8h3a2 2 0 0 1 0 4h-3"/>'
};
var roots = document.querySelectorAll('.vbkkd');
for (var i = 0; i < roots.length; i++) init(roots[i]);
function init(root) {
if (root.__vb) return; root.__vb = 1;
var qty = [], ship = 'pvz';
for (var z = 0; z < CONF.goods.length; z++) qty.push(z === 0 ? 2 : 0);
var box = root.querySelector('#vbkkd-goods'), shipBox = root.querySelector('#vbkkd-ship');
function fmt(n) { return String(Math.round(n)).replace(/\B(?=(\d{3})+(?!\d))/g, ' ') + ' ₽'; }
var html = '';
for (var a = 0; a < CONF.goods.length; a++) {
var g = CONF.goods[a];
html += '<div class="vbkkd-item" data-i="' + a + '">' +
'<span class="vbkkd-ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">' + ICONS[g.ico] + '<\/svg><\/span>' +
'<span><b class="vbkkd-nm">' + g.n + '<\/b><span class="vbkkd-ds">' + g.d + ', ' + fmt(g.price) + '<\/span><\/span>' +
'<span><span class="vbkkd-cnt"><button type="button" class="vbkkd-pm" data-d="-1" aria-label="Убрать одну штуку">−<\/button>' +
'<span class="vbkkd-qty">0<\/span>' +
'<button type="button" class="vbkkd-pm" data-d="1" aria-label="Добавить одну штуку">+<\/button><\/span>' +
'<span class="vbkkd-pr">0 ₽<\/span><\/span><\/div>';
}
box.innerHTML = html;
var shtml = '';
for (var b = 0; b < CONF.ship.length; b++) {
shtml += '<button type="button" class="vbkkd-sw" data-k="' + CONF.ship[b].k + '"><span>' + CONF.ship[b].n + '<\/span><i>' + CONF.ship[b].d + '<\/i><\/button>';
}
shipBox.innerHTML = shtml;
var items = box.querySelectorAll('.vbkkd-item');
var sws = shipBox.querySelectorAll('.vbkkd-sw');
for (var c = 0; c < items.length; c++) {
var pms = items[c].querySelectorAll('.vbkkd-pm');
for (var e = 0; e < pms.length; e++) {
pms[e].addEventListener('click', bind(items[c], pms[e]));
}
}
function bind(item, btn) {
return function () {
var i = parseInt(item.getAttribute('data-i'), 10);
qty[i] = Math.max(0, Math.min(30, qty[i] + parseInt(btn.getAttribute('data-d'), 10)));
render();
};
}
for (var f = 0; f < sws.length; f++) {
sws[f].addEventListener('click', function () { ship = this.getAttribute('data-k'); render(); });
}
function render() {
var sum = 0, cnt = 0, kg = 0;
for (var i = 0; i < CONF.goods.length; i++) {
sum += CONF.goods[i].price * qty[i];
kg += CONF.goods[i].kg * qty[i];
cnt += qty[i];
items[i].className = qty[i] > 0 ? 'vbkkd-item vbkkd-item--on' : 'vbkkd-item';
items[i].querySelector('.vbkkd-qty').textContent = qty[i];
items[i].querySelector('.vbkkd-pr').textContent = fmt(CONF.goods[i].price * qty[i]);
}
var pct = 0;
for (var s = 0; s < CONF.steps.length; s++) { if (sum >= CONF.steps[s].from) pct = CONF.steps[s].p; }
var disc = Math.round(sum * pct / 100), goodsNet = sum - disc;
var sh = CONF.ship[0];
for (var t = 0; t < CONF.ship.length; t++) { if (CONF.ship[t].k === ship) sh = CONF.ship[t]; }
var freeNow = goodsNet >= CONF.free;
var shipCost = cnt === 0 ? 0 : (freeNow ? 0 : Math.round(sh.base + sh.perKg * kg));
for (var w = 0; w < sws.length; w++) {
sws[w].className = sws[w].getAttribute('data-k') === ship ? 'vbkkd-sw vbkkd-sw--on' : 'vbkkd-sw';
}
root.querySelector('#vbkkd-cnt').textContent = cnt + ' шт.';
root.querySelector('#vbkkd-goodsum').textContent = fmt(sum);
root.querySelector('#vbkkd-discrow').hidden = disc === 0;
root.querySelector('#vbkkd-disclbl').textContent = 'Скидка за объём, ' + pct + '%';
root.querySelector('#vbkkd-disc').textContent = '-' + fmt(disc);
root.querySelector('#vbkkd-disc').className = 'vbkkd-minus';
root.querySelector('#vbkkd-shiplbl').textContent = 'Доставка, ' + sh.d;
root.querySelector('#vbkkd-shipval').textContent = freeNow && cnt > 0 ? 'бесплатно' : fmt(shipCost);
root.querySelector('#vbkkd-total').textContent = fmt(goodsNet + shipCost);
var left = Math.max(0, CONF.free - goodsNet);
root.querySelector('#vbkkd-freefill').style.width = Math.min(100, goodsNet / CONF.free * 100) + '%';
root.querySelector('#vbkkd-freetxt').textContent = cnt === 0
? 'Добавьте позиции, чтобы увидеть расчёт. Доставка становится бесплатной от ' + fmt(CONF.free) + ' в корзине.'
: (left === 0
? 'Доставка уже бесплатная: сумма товаров перешла порог ' + fmt(CONF.free) + '.'
: 'До бесплатной доставки не хватает ' + fmt(left) + '. Порог считается по сумме товаров после скидки.');
}
render();
}
})();
</script> Вставляется целиком в блок T123 «HTML-код». Внутри уже есть стили и скрипт, подключать ничего не нужно.
Как вставить блок в Tilda
- Скопируйте кодКнопка «Скопировать код» выше кладёт всё содержимое в буфер обмена.
- Добавьте блок T123В редакторе страницы: «Библиотека блоков» → «Другое» → T123 «HTML-код».
- Вставьте и сохранитеНажмите «Контент», вставьте код в поле, сохраните и обновите страницу.
- ОпубликуйтеВ превью скрипты Tilda работают не всегда — проверяйте на опубликованной странице.
Подробная инструкция с граблями — как вставить вайб-блок в Tilda.
Что менять под себя
| Где в коде | За что отвечает |
|---|---|
CONF.goods в начале скрипта | список позиций: название, описание, цена, вес, иконка |
CONF.steps | ступени скидки за объём: сумма заказа и процент |
CONF.free | порог бесплатной доставки, от него считается полоса и подсказка |
CONF.ship | способы доставки: название, срок, база и надбавка за килограмм |
Калькулятор корзины и доставки: зачем он нужен
Витрина показывает цену за штуку, а вопрос у покупателя всегда про итог с доставкой. Блок собирает корзину прямо на странице: плюс и минус у каждой позиции, сумма товаров, ступенчатая скидка за объём, стоимость отправки по выбранному способу. Строка со скидкой появляется только когда сумма перешла ступень, поэтому расчёт остаётся читаемым.
Под итогом идёт полоса до бесплатной доставки и подпись с недостающей суммой: это тот самый довод, который увеличивает средний чек без уговоров. Все числа собраны в объект CONF в первых строках скрипта: порог бесплатной отправки, ступени скидки, три способа доставки со ставкой за килограмм и список товаров с ценой и весом.
Вопросы по этому блоку
-
Из ставки способа: база плюс надбавка за килограмм, вес складывается из поля kg у выбранных позиций. Для пункта выдачи надбавка нулевая, поэтому цена держится фиксированной.
-
Оставьте в массиве CONF.steps один объект с нужной суммой и процентом. Пустой массив отключает скидку совсем, строка в правой колонке тогда просто не показывается.
-
Да, список ограничен только длиной страницы. Добавляйте объекты в CONF.goods, иконку берите из набора ICONS ниже или впишите туда свой вектор с новым ключом.
-
Да. Код на этой странице копируется целиком, без регистрации и подписки. Указывать авторство не нужно, использовать можно в коммерческих проектах.
-
Нет. Все классы внутри имеют собственный префикс vbkkd, поэтому не пересекаются ни со стилями Tilda, ни с другими блоками. Скрипт привязан к своему элементу и запускается один раз, даже если блок вставлен дважды.
-
Раскладка перестраивается под узкие экраны, размеры заданы через clamp, поля ввода имеют шрифт 16 пикселей — iOS не будет зумить страницу при фокусе. Отдельной мобильной версии подключать не нужно.
Другие калькуляторы
Ещё блоки для товарных брендов
Собрать сайт целиком, а не по блокам
Дровосеки делают сайты на Tilda под ключ: структура, тексты, дизайн, вёрстка и вайб-блоки под задачи бизнеса.