Сделать передвижение изображений в одну точку при скроллинге
Всех приветствую. Как можно реализовать такую штуку, наверное это делается на js.
Есть изображения разбросанные по одному блоку (div). При скроллинге страницы вниз, эти изображение потихоньку скапливаются в кучу и собираются в один пакетик и остаются там уже при скроллинге на следующий блок. Пример такого блока вы сможете увидеть на organic-zone .ru(не реклама) Прошу помочь, товарищи! Желательно показать на примере
Ответы (2 шт):
Один из способов реализации который мне сразу приходит на ум это отслеживать событие скролла. То есть в чём идея у тебя есть n-блоков (картинок), который при скроле до определенного момента буду съезжаться в одну точку. Для каждого блока я задаю функцию изменения траектории по X и начальную позицию по Y, в идеале конечно задать функции нужно так чтобы они пересекались в одной точке, я просто подобрал нужные коэффициенты для функции чтобы показать простой пример, функция вообще необязательно должна быть линейной, можно сделать квадратную или любую другую и получить более интересные траектории по итогу. И так у нас есть функции которые задают траекторию для каждого блока и которые пересекаются в одной точке, теперь просто при событии скролла изменяем позицию каждого блока в соответствии с координатой Y и вычисляемой по функции координатой X (для absolute позиционирования это будет top и left свойства). И да, чтобы блоки не преклеивались к верху страницы задаём ускорение для координаты Y, тут тоже можно поиграться с коэффициентом для нужной скорости.
Собственно пример ниже
const blocks = [
{
className: 'bl1',
yOffset: 10,
xFunc: (y) => 0.4 * y + 30,
},
{
className: 'bl2',
yOffset: 10,
xFunc: (y) => 0.2 * y + 170,
},
{
className: 'bl3',
yOffset: 20,
xFunc: (y) => 0.3 * y + 90,
},
];
blocks.forEach((block) => {
const y = block.yOffset;
const el = document.getElementsByClassName(block.className)[0];
el.style.top = `${y * 2}px`;
el.style.left = `${block.xFunc(y * 2)}px`;
});
scrollEl.onscroll = (e) => {
if (e.srcElement.scrollTop < 320) {
blocks.forEach((block) => {
const y = e.srcElement.scrollTop + block.yOffset;
const el = document.getElementsByClassName(block.className)[0];
el.style.top = `${y * 2}px`;
el.style.left = `${block.xFunc(y * 2)}px`;
});
}
};
.item {
position: absolute;
border: 1px solid black;
width: 50px;
height: 50px;
}
.bl1 {
background: red;
}
.bl2 {
background: blue;
}
.bl3 {
background: green;
}
#root {
height: 1200px;
display: block;
position: relative;
}
#scrollEl {
display: block;
height: 500px;
overflow: auto;
}
<div id="scrollEl">
<div id="root">
<div class="item bl1"></div>
<div class="item bl2"></div>
<div class="item bl3"></div>
</div>
</div>
Данный код лишь для демонстрации и не готов к употреблению.
1. Создаем корзинку, кладем в неё все элементы задав им абсолютное позиционирование.
2. Расставляем элементы с помощью transform
Вот так, например, выглядит сдвиг одного элемента по оси X:
calc(var(--tx) * (1 - var(--v, 0)))
где --tx задается вручную в css файле, а --v обновляется скриптом.
Изначально --v равен нулю, но по мере приближении элемента к корзине, эта переменная будет увеличиваться до 1.
Если убрать эти страшные спецсимволы, то уже находясь в корзинке будет что-нибудь такое: 20vw * (1 - 1), то есть ноль(изначальное положение).
3. Осталось только найти способ увеличивать переменную --v:
// верхушка элемента относительно всего документа(наша конечная точка)
let end = cumulativeTop(this.node)
// верхушка элемента относительно окна браузера(наша начальная точка)
let { top } = this.node.getBoundingClientRect()
// чуть-чуть сдвигаем
end = end - innerHeight * 0.5
// чуть-чуть сдвигаем
top += innerHeight * -0.4
// вычисляем расхождение(хуй знает как это ещё назвать)
let v = 1 / (end - top) * (scrollY - top)
// обрезаем ненужное
v = Math.max(0, Math.min(1, v))
let rid = 0
const els = [...document.querySelectorAll('.el')].map((el, i) => {
return {
node: el,
v: [0, 0],
ease: 0.05 + Math.random() * 0.05,
scroll() {
let end = cumulativeTop(this.node)
if (scrollY > end) {
cancelAnimationFrame(rid)
rid = null
} else if (!rid) {
rid = requestAnimationFrame(loop)
}
let { top } = this.node.getBoundingClientRect()
end = end - innerHeight * 0.5
top += innerHeight * -0.4
const v = 1 / (end - top) * (scrollY - top)
this.v[0] = Math.max(0, Math.min(1, v))
}
}
})
addEventListener('scroll', () => els.forEach(el => el.scroll()))
function cumulativeTop(element) {
let top = 0
do {
top += element.offsetTop || 0
element = element.offsetParent
} while (element)
return top
}
function loop() {
els.forEach(el => {
el.v[1] += (el.v[0] - el.v[1]) * el.ease
el.node.style.setProperty('--v', el.v[1])
})
rid = requestAnimationFrame(loop)
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
height: 600vh;
}
.bag-section {
position: relative;
height: 200vh;
width: 100%;
overflow: hidden;
}
.bag {
position: absolute;
left: 50%;
bottom: 0;
width: calc((30vw + 30vh) / 2);
height: calc((20vw + 20vh) / 2);
transform: translateX(-50%);
display: flex;
align-items: center;
justify-content: center;
}
.bag::after {
content: "";
position: absolute;
left: 0;
top: 0;
background-color: lightblue;
width: 100%;
height: 100%;
}
.el {
--tx: 0vw;
--ty: 0vh;
position: absolute;
width: calc((5vw + 5vh) / 2);
height: calc((5vw + 5vh) / 2);
background-color: lightgreen;
transform: translate(calc(var(--tx) * (1 - var(--v, 0))), calc(var(--ty) * (1 - var(--v, 0)))) rotate(calc(360deg * var(--v, 0))) scale(calc(1 + (1 - var(--v, 0))));
}
.el-1 {
--tx: -17vw;
--ty: -100vh;
}
.el-2 {
--tx: 17vw;
--ty: -100vh;
}
.el-3 {
--tx: 30vw;
--ty: -150vh;
}
.el-4 {
--tx: -30vw;
--ty: -150vh;
}
.el-5 {
--tx: 30vw;
--ty: -125vh;
}
.el-6 {
--tx: -30vw;
--ty: -125vh;
}
.el-7 {
--tx: -10vw;
--ty: -80vh;
}
.el-8 {
--tx: 10vw;
--ty: -80vh;
}
<div class="bag-section">
<div class="bag">
<div class="el el-1"></div>
<div class="el el-2"></div>
<div class="el el-3"></div>
<div class="el el-4"></div>
<div class="el el-5"></div>
<div class="el el-6"></div>
<div class="el el-7"></div>
<div class="el el-8"></div>
</div>
</div>