Инструкция:
Шаг 1
Создаём Zero-блок и вешаем на него класс uc-infinite-1
Шаг 2
В этом же Zero-блоке создаём HTML-блок и добавляем код, его можно найти внизу страницы "Добавить в Zero-блок". Размеры блока на скрине ниже
Шаг 3
Создаём карточки в виде групп Object и вешаем классы со своими порядковыми номера: infinite-child-1 (-1, -2, -3 и тд). Выравниваем их по верхней левой границе Zero-блока.
В моём примере заложено 5 штук, если нужно больше, то подробнее в видео
Шаг 4
Код из видео (autoscale)

<!--Добавить в Zero-блок-->
<div class="infinite-wrapper">
<div class="gallery-inner">
<div class="infinite-box-1"></div>
<div class="infinite-box-2"></div>
<div class="infinite-box-3"></div>
<div class="infinite-box-4"></div>
<div class="infinite-box-5"></div>
</div>
</div>
<!---->
<style>
:root {
--MainHeight1200: 650px; /*высота контейнера на разрешении 1200+*/
--MainHeight960: 650px; /*высота контейнера на разрешении 960-1199*/
--MainHeight640: 650px; /*высота контейнера на разрешении 640-959*/
--MainHeight480: 650px; /*высота контейнера на разрешении 480-639*/
--MainHeight320: 450px; /*высота контейнера на разрешении 320-479*/
--ChildHeight1200: 360px; /*высота кейсов на разрешении 1200+*/
--ChildHeight960: 360px; /*высота кейсов на разрешении 960-1199*/
--ChildHeight640: 360px; /*высота кейсов на разрешении 640-959*/
--ChildHeight480: 360px; /*высота кейсов на разрешении 480-639*/
--ChildHeight320: 240px; /*высота кейсов на разрешении 320-479*/
}
/*.gallery-15, .gallery-15 .tn-atom__html {*/
/* overflow: visible;*/
/*}*/
/*.gallery-15 {*/
/* transform: rotate(-15deg);*/
/*}*/
@media screen and (min-width: 1200px) {
.infinite-wrapper {
height: var(--MainHeight1200);
}
[class*="infinite-box-"] {
height: var(--ChildHeight1200);
}
}
@media screen and (min-width: 960px) and (max-width: 1199px) {
.infinite-wrapper {
height: var(--MainHeight960);
}
[class*="infinite-box-"] {
height: var(--ChildHeight960);
}
}
@media screen and (min-width: 640px) and (max-width: 959px) {
.infinite-wrapper {
height: var(--MainHeight640);
}
[class*="infinite-box-"] {
height: var(--ChildHeight640);
}
}
@media screen and (min-width: 480px) and (max-width: 639px) {
.infinite-wrapper {
height: var(--MainHeight480);
}
[class*="infinite-box-"] {
height: var(--ChildHeight480);
}
}
@media screen and (min-width: 320px) and (max-width: 479px) {
.infinite-wrapper {
height: var(--MainHeight320);
}
[class*="infinite-box-"] {
height: var(--ChildHeight320);
}
}
.infinite-wrapper {
position: relative;
overflow: hidden;
/*overflow: visible;*/
}
.gallery-inner {
display: flex;
flex-direction: column;
}
[class*="infinite-box-"] {
display: flex;
position: relative;
flex-direction: column;
}
[class*="infinite-box-"] div {
zoom: 1 !important;
}
.tn-elem[class*="infinite-child-"] {
position: absolute;
}
.infinite-wrapper.dragging {
user-select: none;
cursor: grabbing;
}
.infinite-wrapper {
cursor: grab;
}
.infinite-wrapper img {
pointer-events: none;
-webkit-user-drag: none;
}
</style>
<script src="https://matilda-design.ru/library/GSAP.js"></script>
<script>
function initInfiniteGallery(container) {
const gallery = container.querySelector(".gallery-inner");
if (!gallery) return;
const childs = container.querySelectorAll('[class*="infinite-child-"]');
childs.forEach(child => {
const match = Array.from(child.classList).find(c => /^infinite-child-\d+$/.test(c));
if (!match) return;
const num = match.split('-')[2];
let targetBox = gallery.querySelector(".infinite-box-" + num);
if (!targetBox) {
targetBox = document.createElement("div");
targetBox.classList.add("infinite-box-" + num);
gallery.appendChild(targetBox);
}
if (!targetBox.contains(child)) {
$(child).appendTo(targetBox);
}
});
gallery.appendChild(gallery.cloneNode(true));
const totalHeight = gallery.scrollHeight / 2;
let autoTween = gsap.to(gallery, {
y: -totalHeight,
duration: 35,
ease: "none",
repeat: -1
});
let isHovered = false;
let manualOffset = 0;
let velocity = 0;
const setY = gsap.quickSetter(gallery, "y", "px");
const wrapper = container.querySelector(".infinite-wrapper");
wrapper.addEventListener("mouseenter", () => {
isHovered = true;
autoTween.pause();
manualOffset = gsap.getProperty(gallery, "y");
setY(manualOffset);
velocity = 0;
});
wrapper.addEventListener("mouseleave", () => {
if (isDragging) return;
isHovered = false;
autoTween.progress(manualOffset / -totalHeight);
autoTween.play();
gsap.to(gallery, {
scaleX: 1,
scaleY: 1,
filter: "blur(0px)",
duration: 0.6,
ease: "power3.out"
});
});
wrapper.addEventListener("wheel", (e) => {
if (!isHovered) return;
e.preventDefault();
velocity += -e.deltaY * 0.05;
});
if (window.innerWidth >= 480) {
let touchStartY = 0;
let isTouching = false;
wrapper.addEventListener("touchstart", (e) => {
isHovered = true;
autoTween.pause();
manualOffset = gsap.getProperty(gallery, "y");
setY(manualOffset);
velocity = 0;
touchStartY = e.touches[0].clientY;
isTouching = true;
wrapper.classList.add("dragging");
}, {passive: true});
wrapper.addEventListener("touchmove", (e) => {
if (!isTouching) return;
const touchY = e.touches[0].clientY;
const deltaY = touchY - touchStartY;
touchStartY = touchY;
velocity += deltaY * 0.15;
}, {passive: true});
wrapper.addEventListener("touchend", (e) => {
isTouching = false;
wrapper.classList.remove("dragging");
const rect = wrapper.getBoundingClientRect();
const touch = e.changedTouches[0];
const inside =
touch.clientX >= rect.left &&
touch.clientX <= rect.right &&
touch.clientY >= rect.top &&
touch.clientY <= rect.bottom;
if (inside) {
isHovered = true;
} else {
isHovered = false;
autoTween.progress(manualOffset / -totalHeight);
autoTween.play();
gsap.to(gallery, {
scaleX: 1,
scaleY: 1,
filter: "blur(0px)",
duration: 0.6,
ease: "power3.out"
});
}
});
}
let isDragging = false;
let startY = 0;
wrapper.addEventListener("mousedown", (e) => {
isHovered = true;
autoTween.pause();
manualOffset = gsap.getProperty(gallery, "y");
setY(manualOffset);
velocity = 0;
startY = e.clientY;
isDragging = true;
wrapper.classList.add("dragging");
});
wrapper.addEventListener("mousemove", (e) => {
if (!isDragging) return;
const deltaY = e.clientY - startY;
startY = e.clientY;
velocity += deltaY * 0.15;
});
wrapper.addEventListener("mouseup", (e) => {
if (!isDragging) return;
isDragging = false;
wrapper.classList.remove("dragging");
const rect = wrapper.getBoundingClientRect();
const inside =
e.clientX >= rect.left &&
e.clientX <= rect.right &&
e.clientY >= rect.top &&
e.clientY <= rect.bottom;
if (inside) {
isHovered = true;
} else {
isHovered = false;
autoTween.progress(manualOffset / -totalHeight);
autoTween.play();
gsap.to(gallery, {
scaleX: 1,
scaleY: 1,
filter: "blur(0px)",
duration: 0.6,
ease: "power3.out"
});
}
});
wrapper.addEventListener("mouseleave", () => {
if (!isDragging) {
isHovered = false;
autoTween.progress(manualOffset / -totalHeight);
autoTween.play();
gsap.to(gallery, {
scaleX: 1,
scaleY: 1,
filter: "blur(0px)",
duration: 0.6,
ease: "power3.out"
});
} else {
isDragging = false;
wrapper.classList.remove("dragging");
isHovered = false;
autoTween.progress(manualOffset / -totalHeight);
autoTween.play();
}
});
let resetTween = null;
gsap.ticker.add(() => {
if (!isHovered) return;
manualOffset += velocity;
if (manualOffset <= -totalHeight) manualOffset += totalHeight;
if (manualOffset >= 0) manualOffset -= totalHeight;
setY(manualOffset);
const absVelocity = Math.abs(velocity);
const scaleX = 1 + absVelocity * 0.002;
const scaleY = 1 - absVelocity * 0.001;
const blur = Math.min(absVelocity * 0.10, 10);
gsap.set(gallery, {
scaleX: scaleX,
scaleY: scaleY,
filter: `blur(${blur}px)`
});
if (absVelocity < 0.01 && !resetTween) {
resetTween = gsap.to(gallery, {
scaleX: 1,
scaleY: 1,
filter: "blur(0px)",
duration: 0.6,
ease: "power3.out",
onComplete: () => resetTween = null
});
}
velocity *= 0.88;
});
}
document.querySelectorAll('[class*="uc-infinite-"]').forEach(container => {
initInfiniteGallery(container);
});
</script>
Примечание
Если нужно создать новую анимацию в другом Zero-блоке, то у него уже меняем свой порядковый номер на uc-infinite-2
© 2022-2025 все права защищены
ИП Нестерчук Кристина Юрьевна ИНН: 251110424315
ЗАДАТЬ ВОПРОС