Блок отзывов «до и после» со слайдером сравнения
Перетаскиваемая шторка сравнения «до и после» рядом с историей клиента: срок, результат в цифрах и короткий отзыв.
<section class="vbodp">
<div class="vbodp-wrap">
<div class="vbodp-head">
<h2 class="vbodp-h2">Результаты клиентов</h2>
<p class="vbodp-lead">Фотографии сделаны в одном свете и в одной позе. Потяните шторку — увидите, как было.</p>
</div>
<div class="vbodp-case" id="vbodp-case"></div>
<div class="vbodp-nav" id="vbodp-nav" role="tablist" aria-label="Истории клиентов"></div>
</div>
</section>
<style>
.vbodp, .vbodp *, .vbodp *::before, .vbodp *::after { box-sizing: border-box; }
.vbodp {
--acc: #d6f24b; --bg: #131518; --panel: #1c1f24; --ink: #f2f4f6; --muted: #939ba5; --line: #2a2e35;
--f: var(--t-text-font, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif);
--fh: var(--t-headline-font, var(--f));
background: var(--bg); color: var(--ink); font-family: var(--f); padding: clamp(36px, 5vw, 80px) 20px;
}
.vbodp-wrap { max-width: 1140px; margin: 0 auto; padding: 0 clamp(0px, 2vw, 40px); }
.vbodp-head { max-width: 42em; margin-bottom: clamp(24px, 3vw, 38px); }
.vbodp-h2 { font-family: var(--fh); font-size: clamp(26px, 3.4vw, 40px); line-height: 1.1; font-weight: 700; letter-spacing: -.025em;
text-transform: uppercase; margin: 0 0 12px; }
.vbodp-lead { font-size: 16px; line-height: 1.6; color: var(--muted); margin: 0; }
.vbodp-case { display: grid; grid-template-columns: minmax(0, .9fr) minmax(0, 1.1fr); gap: clamp(22px, 3vw, 44px);
background: var(--panel); border: 1px solid var(--line); border-radius: 24px; padding: clamp(20px, 2.6vw, 32px); align-items: center; }
.vbodp-slider { position: relative; border-radius: 16px; overflow: hidden; touch-action: none; cursor: ew-resize; user-select: none; }
.vbodp-slider img, .vbodp-slider svg { display: block; width: 100%; height: auto; }
.vbodp-after { position: absolute; inset: 0; }
.vbodp-after > * { width: 100%; height: 100%; object-fit: cover; }
.vbodp-handle { position: absolute; top: 0; bottom: 0; width: 3px; background: var(--acc); transform: translateX(-50%); }
.vbodp-handle::after { content: ''; position: absolute; top: 50%; left: 50%; width: 42px; height: 42px; transform: translate(-50%, -50%);
border-radius: 50%; background: var(--acc); box-shadow: 0 2px 10px rgba(0,0,0,.4); }
.vbodp-handle span { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 1; font-size: 15px; color: #131518; font-weight: 700; }
.vbodp-tag { position: absolute; top: 12px; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em;
background: rgba(19,21,24,.72); color: #fff; border-radius: 999px; padding: 5px 12px; }
.vbodp-tag--l { left: 12px; }
.vbodp-tag--r { right: 12px; }
.vbodp-info__name { font-family: var(--fh); font-size: clamp(20px, 2.2vw, 27px); font-weight: 700; margin: 0 0 4px; }
.vbodp-info__meta { font-size: 13.5px; color: var(--muted); margin: 0 0 18px; }
.vbodp-nums { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; margin-bottom: 18px; }
.vbodp-nums div { background: rgba(255,255,255,.04); border-radius: 12px; padding: 14px 16px; }
.vbodp-nums b { display: block; font-family: var(--fh); font-size: 21px; font-weight: 700; color: var(--acc); }
.vbodp-nums span { display: block; font-size: 11.5px; color: var(--muted); margin-top: 4px; }
.vbodp-info__txt { font-size: 15.5px; line-height: 1.65; color: var(--muted); margin: 0; }
.vbodp-nav { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 18px; }
.vbodp-tab { border: 1.5px solid var(--line); margin: 0; -webkit-appearance: none; appearance: none; font: inherit; cursor: pointer;
background: transparent !important; color: var(--muted) !important; border-radius: 999px; padding: 9px 18px; font-size: 14px; transition: .18s ease; }
.vbodp-tab:hover { border-color: var(--acc); color: var(--ink) !important; }
.vbodp-tab[aria-selected="true"] { background: var(--acc) !important; border-color: var(--acc); color: #131518 !important; font-weight: 600; }
@media (max-width: 820px) { .vbodp-case { grid-template-columns: minmax(0, 1fr); } }
@media (prefers-reduced-motion: reduce) { .vbodp * { transition: none !important; } }
</style>
<script>
(function () {
/* ── КЕЙСЫ: before/after — адреса фото; пустые — рисуются SVG-заглушки ── */
var CASES = [
{ name: 'Сергей, 34 года', period: '5 месяцев, 3 тренировки в неделю',
nums: [['−14 кг', 'вес'], ['−11 см', 'талия'], ['+6 кг', 'мышцы']],
txt: 'Пришёл после того, как не смог застегнуть костюм на свадьбу друга. Первый месяц было тяжело: болело всё. Тренер не давал жалеть себя, но и не гнал — программу пересобирали трижды. Питание считали в приложении, без голодовок.',
before: '', after: '', c1: '#3a3f34', c2: '#586152' },
{ name: 'Анна, 29 лет', period: '7 месяцев, 4 тренировки в неделю',
nums: [['−9 кг', 'вес'], ['−13 см', 'бёдра'], ['22%', 'жир в теле']],
txt: 'Цель была не похудеть, а перестать уставать от лестницы на четвёртый этаж. Через два месяца ушла одышка, через четыре — начала бегать по утрам. Вес пошёл вниз сам, когда я перестала на него смотреть.',
before: '', after: '', c1: '#3d3a44', c2: '#5b5566' },
{ name: 'Марат, 41 год', period: '9 месяцев, 3 тренировки в неделю',
nums: [['−19 кг', 'вес'], ['−17 см', 'талия'], ['130/80', 'давление']],
txt: 'Начал по настоянию кардиолога. Первые полгода — только ходьба на дорожке и лёгкие тренажёры, силовые добавили позже. Давление стабилизировалось на пятом месяце, таблетки убрали с разрешения врача.',
before: '', after: '', c1: '#343d3f', c2: '#4f5c5f' },
{ name: 'Лиза, 24 года', period: '4 месяца, 4 тренировки в неделю',
nums: [['+5 кг', 'вес'], ['+8 см', 'бёдра'], ['×2', 'сила в приседе']],
txt: 'Задача была набрать, а не сбросить — с этим сложнее найти тренера. Здесь составили программу на массу и объяснили, сколько реально нужно есть. Первый месяц заставляла себя доедать, потом привыкла.',
before: '', after: '', c1: '#413a36', c2: '#615751' }
];
var roots = document.querySelectorAll('.vbodp');
for (var i = 0; i < roots.length; i++) init(roots[i]);
function init(root) {
if (root.__vb) return; root.__vb = 1;
var box = root.querySelector('#vbodp-case'), nav = root.querySelector('#vbodp-nav');
var cur = 0;
/* SVG-заглушка фигуры: замените поля before/after на адреса реальных фото */
function figure(c1, c2, wide) {
var w = wide ? 1 : 0.72;
return '<svg viewBox="0 0 300 380" role="img" aria-label="Фотография фигуры" focusable="false">' +
'<rect width="300" height="380" fill="' + c1 + '"/>' +
'<g fill="' + c2 + '">' +
'<circle cx="150" cy="72" r="34"/>' +
'<path d="M150 112c' + (44 * w) + ' 0 ' + (62 * w) + ' 26 ' + (62 * w) + ' 62l-' + (10 * w) + ' 74h-' + (104 * w) + 'l-' + (10 * w) + '-74c0-36 ' + (18 * w) + '-62 ' + (62 * w) + '-62z"/>' +
'<path d="M' + (150 - 44 * w) + ' 258h' + (88 * w) + 'l' + (10 * w) + ' 122h-' + (34 * w) + 'l-' + (20 * w) + '-84-' + (20 * w) + ' 84h-' + (34 * w) + 'z"/>' +
'<\/g><\/svg>';
}
function draw() {
var c = CASES[cur];
box.innerHTML =
'<div class="vbodp-slider" id="vbodp-sl">' +
'<div class="vbodp-before">' + (c.before ? '<img src="' + c.before + '" alt="До">' : figure(c.c1, c.c2, true)) + '<\/div>' +
'<div class="vbodp-after" id="vbodp-af" style="clip-path:inset(0 0 0 50%)">' +
(c.after ? '<img src="' + c.after + '" alt="После">' : figure(c.c1, c.c2, false)) + '<\/div>' +
'<span class="vbodp-tag vbodp-tag--l">До<\/span><span class="vbodp-tag vbodp-tag--r">После<\/span>' +
'<div class="vbodp-handle" id="vbodp-h" style="left:50%"><span>↔<\/span><\/div><\/div>' +
'<div class="vbodp-info"><h3 class="vbodp-info__name">' + c.name + '<\/h3>' +
'<p class="vbodp-info__meta">' + c.period + '<\/p>' +
'<div class="vbodp-nums">' + c.nums.map(function (n) {
return '<div><b>' + n[0] + '<\/b><span>' + n[1] + '<\/span><\/div>';
}).join('') + '<\/div>' +
'<p class="vbodp-info__txt">' + c.txt + '<\/p><\/div>';
bindSlider();
}
function bindSlider() {
var sl = root.querySelector('#vbodp-sl');
var af = root.querySelector('#vbodp-af');
var h = root.querySelector('#vbodp-h');
if (!sl) return;
var dragging = false;
function set(clientX) {
var r = sl.getBoundingClientRect();
var p = Math.max(0, Math.min(1, (clientX - r.left) / r.width));
af.style.clipPath = 'inset(0 0 0 ' + (p * 100) + '%)';
h.style.left = (p * 100) + '%';
}
/* события указателя: мышь, палец и стилус работают одинаково */
sl.addEventListener('pointerdown', function (e) { dragging = true; sl.setPointerCapture(e.pointerId); set(e.clientX); });
sl.addEventListener('pointermove', function (e) { if (dragging) set(e.clientX); });
sl.addEventListener('pointerup', function () { dragging = false; });
sl.addEventListener('pointercancel', function () { dragging = false; });
}
nav.innerHTML = CASES.map(function (c, i) {
return '<button type="button" class="vbodp-tab" role="tab" aria-selected="' + (i === 0) + '" data-i="' + i + '">' + c.name.split(',')[0] + '<\/button>';
}).join('');
nav.querySelectorAll('.vbodp-tab').forEach(function (b) {
b.addEventListener('click', function () {
cur = +b.dataset.i;
nav.querySelectorAll('.vbodp-tab').forEach(function (x) { x.setAttribute('aria-selected', 'false'); });
b.setAttribute('aria-selected', 'true');
draw();
});
});
draw();
}
})();
</script> Вставляется целиком в блок T123 «HTML-код». Внутри уже есть стили и скрипт, подключать ничего не нужно.
Как вставить блок в Tilda
- Скопируйте кодКнопка «Скопировать код» выше кладёт всё содержимое в буфер обмена.
- Добавьте блок T123В редакторе страницы: «Библиотека блоков» → «Другое» → T123 «HTML-код».
- Вставьте и сохранитеНажмите «Контент», вставьте код в поле, сохраните и обновите страницу.
- ОпубликуйтеВ превью скрипты Tilda работают не всегда — проверяйте на опубликованной странице.
Подробная инструкция с граблями — как вставить вайб-блок в Tilda.
Что менять под себя
| Где в коде | За что отвечает |
|---|---|
массив CASES | история, срок, результат, ссылки на фото |
SVG-заглушки | две картинки до и после |
--acc | цвет ручки шторки и цифр |
Отзывы «до и после»: зачем он нужен
Сравнение «до и после» — самое сильное доказательство в фитнесе, но обычно его показывают двумя картинками рядом. Шторка вовлекает: человек сам двигает границу, и результат воспринимается как настоящий, а не как коллаж.
Рядом со шторкой стоят срок и цифры: минус восемь килограммов за четыре месяца. Без этих данных фотографии выглядят рекламой, с ними — кейсом.
Вопросы по этому блоку
-
Да, перетаскиванием пальцем. Обработка идёт через события указателя, поэтому мышь, палец и стилус работают одинаково.
-
В объекте кейса есть два поля для адресов картинок. Если они пустые, показываются SVG-заглушки, если заполнены — реальные фото. Разметку менять не нужно.
-
Да. Код на этой странице копируется целиком, без регистрации и подписки. Указывать авторство не нужно, использовать можно в коммерческих проектах.
-
Нет. Все классы внутри имеют собственный префикс vbodp, поэтому не пересекаются ни со стилями Tilda, ни с другими блоками. Скрипт привязан к своему элементу и запускается один раз, даже если блок вставлен дважды.
-
Раскладка перестраивается под узкие экраны, размеры заданы через clamp, поля ввода имеют шрифт 16 пикселей — iOS не будет зумить страницу при фокусе. Отдельной мобильной версии подключать не нужно.
Другие блоки отзывов
Ещё блоки для фитнеса и спорта
Собрать сайт целиком
Дровосеки делают сайты на Tilda под ключ: структура, тексты, дизайн, вёрстка и вайб-блоки под задачи бизнеса.