Горизонтальная лента из карточек с перетаскиванием.
uc-techers; карточки лежат в группе flow-blog.<style>
/* Контейнер ленты: обрезаем всё, что уехало за край блока */
.uc-techers .flow-blog {
overflow: hidden !important;
width: calc(100% - 240px) !important; /* 120px отступ слева = столько же справа */
}
.uc-techers .flow-blog > .tn-molecule {
--post-gap: 24px;
--post-width: calc((100% - 72px) / 4); /* 4 карточки + 3 промежутка */
display: flex !important;
flex-wrap: nowrap !important;
gap: var(--post-gap) !important;
width: 100% !important;
overflow-x: auto !important;
overflow-y: hidden !important;
scroll-behavior: smooth;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
cursor: grab;
}
.uc-techers .flow-blog > .tn-molecule::-webkit-scrollbar {
display: none;
}
.uc-techers .flow-blog > .tn-molecule.is-dragging {
cursor: grabbing;
user-select: none;
-webkit-user-select: none;
}
.uc-techers .flow-blog > .tn-molecule > .post {
position: relative !important;
top: auto !important;
left: auto !important;
width: var(--post-width) !important;
min-width: var(--post-width) !important;
max-width: none !important;
flex: 0 0 var(--post-width) !important;
}
.uc-techers .flow-blog > .tn-molecule > .post > .tn-molecule {
width: 100% !important;
}
.uc-techers .btn-left,
.uc-techers .btn-right {
cursor: pointer;
user-select: none;
-webkit-user-select: none;
-webkit-tap-highlight-color: transparent;
}
.uc-techers .btn-left.disabled,
.uc-techers .btn-right.disabled {
pointer-events: none;
opacity: 0.35;
}
.uc-techers .btn-left.hidden,
.uc-techers .btn-right.hidden {
display: none !important;
}
@media screen and (max-width: 1199px) {
.uc-techers .flow-blog > .tn-molecule {
--post-gap: 20px;
--post-width: calc((100% - 40px) / 3);
}
}
@media screen and (max-width: 639px) {
.uc-techers .flow-blog > .tn-molecule {
--post-gap: 16px;
--post-width: calc((100% - 16px) / 2);
}
}
@media screen and (max-width: 419px) {
.uc-techers .flow-blog > .tn-molecule {
--post-gap: 12px;
--post-width: 100%;
}
}
</style>
<script>
$(function () {
const sliderSelector = ".uc-techers";
function initSlider(record) {
const $record = $(record);
const $scroller = $record.find(".flow-blog > .tn-molecule").first();
if (!$scroller.length || $record.data("simpleFeedSliderReady")) return;
const el = $scroller[0];
const ns = ".simpleFeedSlider" + ($record.attr("id") || "");
const $leftArrow = $record.find(".btn-left").first();
const $rightArrow = $record.find(".btn-right").first();
if (!$leftArrow.length || !$rightArrow.length) return;
if (!$scroller.children(".post").length) return;
$record.data("simpleFeedSliderReady", true);
// Zero Block масштабирует группу через CSS zoom. scrollLeft и offsetLeft —
// в «дизайнерских» px, pageX и getBoundingClientRect() — в экранных.
// Всё, что смешивает две системы координат, делим на этот коэффициент.
function getScale() {
const rectWidth = el.getBoundingClientRect().width;
return rectWidth && el.clientWidth ? rectWidth / el.clientWidth : 1;
}
// offsetLeft карточек — в той же системе координат, что и scrollLeft
function getCardOffsets() {
return $scroller
.children(".post")
.toArray()
.map(function (card) {
return card.offsetLeft;
})
.sort(function (a, b) {
return a - b;
});
}
function updateControls() {
const maxScroll = Math.max(0, el.scrollWidth - el.clientWidth);
const noScroll = maxScroll <= 5;
$leftArrow.add($rightArrow).toggleClass("hidden", noScroll);
$leftArrow.toggleClass("disabled", noScroll || el.scrollLeft <= 5);
$rightArrow.toggleClass(
"disabled",
noScroll || el.scrollLeft >= maxScroll - 5
);
}
function scrollToCard(direction) {
const current = el.scrollLeft;
let target = null;
getCardOffsets().forEach(function (x) {
if (direction > 0) {
if (x <= current + 5) return;
if (target === null || x < target) target = x;
} else {
if (x >= current - 5) return;
if (target === null || x > target) target = x;
}
});
el.scrollTo({
left: target !== null ? target : direction > 0 ? el.scrollWidth : 0,
behavior: "smooth"
});
setTimeout(updateControls, 400);
}
$leftArrow.off(ns).on("click" + ns, function (event) {
event.preventDefault();
scrollToCard(-1);
});
$rightArrow.off(ns).on("click" + ns, function (event) {
event.preventDefault();
scrollToCard(1);
});
let isDragging = false;
let startX = 0;
let startScrollLeft = 0;
let dragScale = 1;
let wasDragged = false;
$scroller.off(ns);
$(document).off(ns);
$(window).off(ns);
$scroller.on("mousedown" + ns, function (event) {
if (event.button !== 0) return;
isDragging = true;
wasDragged = false;
startX = event.pageX;
startScrollLeft = el.scrollLeft;
dragScale = getScale();
$scroller.addClass("is-dragging");
});
$(document).on("mousemove" + ns, function (event) {
if (!isDragging) return;
const dx = event.pageX - startX;
if (Math.abs(dx) > 4) {
wasDragged = true;
event.preventDefault();
}
el.scrollLeft = startScrollLeft - dx / dragScale;
});
$(document).on("mouseup" + ns, function () {
if (!isDragging) return;
isDragging = false;
$scroller.removeClass("is-dragging");
setTimeout(function () {
wasDragged = false;
}, 50);
});
// после перетаскивания не даём сработать ссылке «Подробнее»
$scroller.on("click" + ns, "a", function (event) {
if (!wasDragged) return;
event.preventDefault();
event.stopImmediatePropagation();
});
$scroller.on("scroll" + ns, updateControls);
$(window).on("resize" + ns, updateControls);
updateControls();
setTimeout(updateControls, 500);
setTimeout(updateControls, 1500);
}
function initAllSliders() {
$(sliderSelector).each(function () {
initSlider(this);
});
}
// Zero Block дорисовывает карточки не сразу — ждём их появления, но не вечно
let attempts = 0;
const timer = setInterval(function () {
attempts += 1;
initAllSliders();
if (
$(sliderSelector + " .flow-blog > .tn-molecule > .post").length ||
attempts >= 40
) {
clearInterval(timer);
}
}, 150);
initAllSliders();
});
</script>