Эффект следа из изображений

Эффект следа из изображений

Модификация позволяет создать эффект следа изображений во время движения курсора мыши. На выбор 5 разных версий.

Описание
Видеоинструкция по подключению
Инструкция с копированием шаблона к себе
ID шаблона:
47384353
Создаём новую страницу на сайте
Шаг 1
Скроллим в самый низ и нажимаем кнопку «Указать ID шаблона»
Шаг 2
В графе указываем номер шаблона (он указан в начале инструкции), нажимаем кнопку «Выбрать»
Шаг 3
Пошаговая инструкция с подключением кода
В Zero блоке создаём HTML блок и помещаем в него код
Шаг 1
<!--Эффект следа из изображений
https://mt-webdesign.ru/trail-image-hover-->
<main><div class="content">
<img class="content__img" src="https://static.tildacdn.com/tild6637-3935-4561-a265-353061633134/Frame_1.jpg"/>
<img class="content__img" src="https://static.tildacdn.com/tild6462-3336-4665-b730-343934316138/Frame_2.jpg"/>
<img class="content__img" src="https://static.tildacdn.com/tild3763-3838-4531-a562-393238396362/Frame_3.jpg"/>
<img class="content__img" src="https://static.tildacdn.com/tild6464-6565-4563-a265-613231326233/Frame_4.jpg"/>
<img class="content__img" src="https://static.tildacdn.com/tild3838-3437-4635-a138-313935313838/Frame_5.jpg"/>
</div></main>
Задаём HTML блоку 100% высоту и ширину экрана
Шаг 2
В коде заменяем пути до изображений
Шаг 3
Zero блоку задаём класс uc-trail-img
Шаг 4
Ниже создаем блок T123 и помещаем в него код на выбор
Шаг 5
Анимация с падением изображений
Добавить код в блок T123
<!--Эффект следа из изображений
https://mt-webdesign.ru/trail-image-hover-->
<script src='https://www.matilda-design.ru/library/trail-img.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/gsap/3.7.0/gsap.min.js'></script>
<style>
:root {
--ImgWidthTrail: 250px; /*размер изображения*/
--ImgBorderTrail: 20px; /*размер изображения*/
}
@media screen and (max-width: 768px) /*минимальное разрешение для запуска*/ {
main {
display: none;
}
}
.content {
height: 100vh;
width: 100%;
position: relative;
display: flex;
justify-content: center;
align-items: center;
isolation: isolate;
pointer-events: none;
}
.content__img {
pointer-events: none;
width: var(--ImgWidthTrail);
border-radius: var(--ImgBorderTrail);
position: absolute;
top: 0;
left: 0;
opacity: 0;
transform: translateY(0%);
}
.content__img--full {
width: 100%;
height: 100%;
background-size: cover;
}
</style>
<script>
window.addEventListener('DOMContentLoaded', () => {
const screenWidth = window.innerWidth;
if (screenWidth > 768) {
const body = document.body;
const docEl = document.documentElement;
const MathUtils = {
lerp: (a, b, n) => (1 - n) * a + n * b,
distance: (x1, y1, x2, y2) => Math.hypot(x2 - x1, y2 - y1)
};
let winsize;
const calcWinsize = () => {
winsize = {
width: window.innerWidth,
height: window.innerHeight
};
};
calcWinsize();
window.addEventListener('resize', calcWinsize);
const getMousePos = (ev) => {
let posx = 0;
let posy = 0;
if (ev.pageX || ev.pageY) {
posx = ev.pageX;
posy = ev.pageY - window.pageYOffset;
} else if (ev.clientX || ev.clientY) {
posx = ev.clientX + body.scrollLeft + docEl.scrollLeft;
posy = ev.clientY + body.scrollTop + docEl.scrollTop;
}
return { x: posx, y: posy };
};
let mousePos = lastMousePos = cacheMousePos = { x: 0, y: 0 };
window.addEventListener('load', () => {
document.querySelector('.uc-trail-img').addEventListener('mousemove', ev => mousePos = getMousePos(ev));
});
const getMouseDistance = () => MathUtils.distance(mousePos.x, mousePos.y, lastMousePos.x, lastMousePos.y);
class Image {
constructor(el) {
this.DOM = { el: el };
this.defaultStyle = {
x: 0,
y: 0,
opacity: 0
};
this.getRect();
this.initEvents();
}
initEvents() {
window.addEventListener('resize', () => this.resize());
}
resize() {
gsap.set(this.DOM.el, this.defaultStyle);
this.getRect();
}
getRect() {
this.rect = this.DOM.el.getBoundingClientRect();
}
isActive() {
return gsap.isTweening(this.DOM.el) || this.DOM.el.style.opacity != 0;
}
}
class ImageTrail {
constructor() {
this.DOM = { content: document.querySelector('.content') };
this.images = [];
[...this.DOM.content.querySelectorAll('img')].forEach(img => this.images.push(new Image(img)));
this.imagesTotal = this.images.length;
this.imgPosition = 0;
this.zIndexVal = 1;
this.threshold = 150;
requestAnimationFrame(() => this.render());
}
render() {
let distance = getMouseDistance();
cacheMousePos.x = MathUtils.lerp(cacheMousePos.x || mousePos.x, mousePos.x, 0.1);
cacheMousePos.y = MathUtils.lerp(cacheMousePos.y || mousePos.y, mousePos.y, 0.1);
if (distance > this.threshold) {
this.showNextImage();
++this.zIndexVal;
this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
lastMousePos = mousePos;
}
let isIdle = true;
for (let img of this.images) {
if (img.isActive()) {
isIdle = false;
break;
}
}
if (isIdle && this.zIndexVal !== 1) {
this.zIndexVal = 1;
}
requestAnimationFrame(() => this.render());
}
showNextImage() {
const img = this.images[this.imgPosition];
gsap.killTweensOf(img.DOM.el);
gsap.timeline()
.set(img.DOM.el, {
startAt: { opacity: 0 },
opacity: 1,
zIndex: this.zIndexVal,
x: cacheMousePos.x - img.rect.width / 2,
y: cacheMousePos.y - img.rect.height / 2
}, 0)
.to(img.DOM.el, {
duration: 3.6,
ease: 'expo.out',
x: mousePos.x - img.rect.width / 2,
y: mousePos.y - img.rect.height / 2
}, 0)
.to(img.DOM.el, {
duration: 1,
ease: 'power1.out',
opacity: 0
}, 0.9)
.to(img.DOM.el, {
duration: 1,
ease: 'quint.inOut',
y: `+=${winsize.height + img.rect.height / 2}`
}, 0.9);
}
}
const preloadImages = () => {
return new Promise((resolve, reject) => {
imagesLoaded(document.querySelectorAll('.content__img'), resolve);
});
};
preloadImages().then(() => {
document.body.classList.remove('loading');
new ImageTrail();
});
}
});
</script>
Анимация с отдалением изображений
Добавить код в блок T123
<!--Эффект следа из изображений
https://mt-webdesign.ru/trail-image-hover-->
<script src='https://www.matilda-design.ru/library/trail-img.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/gsap/3.7.0/gsap.min.js'></script>
<style>
:root {
--ImgWidthTrail: 250px; /*размер изображения*/
--ImgBorderTrail: 20px; /*размер изображения*/
}
@media screen and (max-width: 768px) /*минимальное разрешение для запуска*/ {
main {
display: none;
}
}
.content {
height: 100vh;
width: 100%;
position: relative;
display: flex;
justify-content: center;
align-items: center;
isolation: isolate;
pointer-events: none;
}
.content__img {
pointer-events: none;
width: var(--ImgWidthTrail);
border-radius: var(--ImgBorderTrail);
position: absolute;
top: 0;
left: 0;
opacity: 0;
transform: translateY(0%);
}
.content__img--full {
width: 100%;
height: 100%;
background-size: cover;
}
</style>
<script>
window.addEventListener('DOMContentLoaded', () => {
const screenWidth = window.innerWidth;
if (screenWidth > 768) {
const body = document.body;
const docEl = document.documentElement;
const MathUtils = {
lerp: (a, b, n) => (1 - n) * a + n * b,
distance: (x1, y1, x2, y2) => Math.hypot(x2 - x1, y2 - y1)
};
let winsize;
const calcWinsize = () => {
winsize = {
width: window.innerWidth,
height: window.innerHeight
};
};
calcWinsize();
window.addEventListener('resize', calcWinsize);
const getMousePos = (ev) => {
let posx = 0;
let posy = 0;
if (ev.pageX || ev.pageY) {
posx = ev.pageX;
posy = ev.pageY - window.pageYOffset;
} else if (ev.clientX || ev.clientY) {
posx = ev.clientX + body.scrollLeft + docEl.scrollLeft;
posy = ev.clientY + body.scrollTop + docEl.scrollTop;
}
return { x: posx, y: posy };
};
let mousePos = lastMousePos = cacheMousePos = { x: 0, y: 0 };
window.addEventListener('load', () => {
document.querySelector('.uc-trail-img').addEventListener('mousemove', ev => mousePos = getMousePos(ev));
});
const getMouseDistance = () => MathUtils.distance(mousePos.x, mousePos.y, lastMousePos.x, lastMousePos.y);
class Image {
constructor(el) {
this.DOM = { el: el };
this.defaultStyle = {
x: 0,
y: 0,
opacity: 0
};
this.getRect();
this.initEvents();
}
initEvents() {
window.addEventListener('resize', () => this.resize());
}
resize() {
gsap.set(this.DOM.el, this.defaultStyle);
this.getRect();
}
getRect() {
this.rect = this.DOM.el.getBoundingClientRect();
}
isActive() {
return gsap.isTweening(this.DOM.el) || this.DOM.el.style.opacity != 0;
}
}
class ImageTrail {
constructor() {
this.DOM = { content: document.querySelector('.content') };
this.images = [];
[...this.DOM.content.querySelectorAll('img')].forEach(img => this.images.push(new Image(img)));
this.imagesTotal = this.images.length;
this.imgPosition = 0;
this.zIndexVal = 1;
this.threshold = 150;
requestAnimationFrame(() => this.render());
}
render() {
let distance = getMouseDistance();
cacheMousePos.x = MathUtils.lerp(cacheMousePos.x || mousePos.x, mousePos.x, 0.1);
cacheMousePos.y = MathUtils.lerp(cacheMousePos.y || mousePos.y, mousePos.y, 0.1);
if (distance > this.threshold) {
this.showNextImage();
++this.zIndexVal;
this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
lastMousePos = mousePos;
}
let isIdle = true;
for (let img of this.images) {
if (img.isActive()) {
isIdle = false;
break;
}
}
if (isIdle && this.zIndexVal !== 1) {
this.zIndexVal = 1;
}
requestAnimationFrame(() => this.render());
}
showNextImage() {
const img = this.images[this.imgPosition];
gsap.killTweensOf(img.DOM.el);
gsap.timeline()
.set(img.DOM.el, {
startAt: { opacity: 0 },
opacity: 1,
scale: 1,
zIndex: this.zIndexVal,
x: cacheMousePos.x - img.rect.width / 2,
y: cacheMousePos.y - img.rect.height / 2
}, 0)
.to(img.DOM.el, {
duration: 1.6,
ease: 'expo.out',
x: mousePos.x - img.rect.width / 2,
y: mousePos.y - img.rect.height / 2
}, 0)
.to(img.DOM.el, {
duration: 0.5,
ease: 'power1.out',
opacity: 0,
scale: 0,
}, 0.6)
.to(img.DOM.el, {
duration: 1,
ease: 'quint.inOut',
}, 0.9);
}
}
const preloadImages = () => {
return new Promise((resolve, reject) => {
imagesLoaded(document.querySelectorAll('.content__img'), resolve);
});
};
preloadImages().then(() => {
document.body.classList.remove('loading');
new ImageTrail();
});
}
});
</script>
Анимация с появлением и закручиванием
Добавить код в блок T123
<!--Эффект следа из изображений
https://mt-webdesign.ru/trail-image-hover-->
<script src='https://www.matilda-design.ru/library/trail-img.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/gsap/3.7.0/gsap.min.js'></script>
<style>
:root {
--ImgWidthTrail: 250px; /*размер изображения*/
--ImgBorderTrail: 20px; /*размер изображения*/
}
@media screen and (max-width: 768px) /*минимальное разрешение для запуска*/ {
main {
display: none;
}
}
.content {
height: 100vh;
width: 100%;
position: relative;
display: flex;
justify-content: center;
align-items: center;
isolation: isolate;
pointer-events: none;
}
.content__img {
pointer-events: none;
width: var(--ImgWidthTrail);
border-radius: var(--ImgBorderTrail);
position: absolute;
top: 0;
left: 0;
opacity: 0;
transform: translateY(0%);
}
.content__img--full {
width: 100%;
height: 100%;
background-size: cover;
}
</style>
<script>
window.addEventListener('DOMContentLoaded', () => {
const screenWidth = window.innerWidth;
if (screenWidth > 768) {
const body = document.body;
const docEl = document.documentElement;
const MathUtils = {
lerp: (a, b, n) => (1 - n) * a + n * b,
distance: (x1, y1, x2, y2) => Math.hypot(x2 - x1, y2 - y1)
};
let winsize;
const calcWinsize = () => {
winsize = {
width: window.innerWidth,
height: window.innerHeight
};
};
calcWinsize();
window.addEventListener('resize', calcWinsize);
const getMousePos = (ev) => {
let posx = 0;
let posy = 0;
if (ev.pageX || ev.pageY) {
posx = ev.pageX;
posy = ev.pageY - window.pageYOffset;
} else if (ev.clientX || ev.clientY) {
posx = ev.clientX + body.scrollLeft + docEl.scrollLeft;
posy = ev.clientY + body.scrollTop + docEl.scrollTop;
}
return { x: posx, y: posy };
};
let mousePos = lastMousePos = cacheMousePos = { x: 0, y: 0 };
let prevMousePos = { x: 0, y: 0 };
window.addEventListener('load', () => {
document.querySelector('.uc-trail-img').addEventListener('mousemove', ev => mousePos = getMousePos(ev));
});
const getMouseDistance = () => MathUtils.distance(mousePos.x, mousePos.y, lastMousePos.x, lastMousePos.y);
class Image {
constructor(el) {
this.DOM = { el: el };
this.defaultStyle = {
x: 0,
y: 0,
opacity: 0
};
this.getRect();
this.initEvents();
}
initEvents() {
window.addEventListener('resize', () => this.resize());
}
resize() {
gsap.set(this.DOM.el, this.defaultStyle);
this.getRect();
}
getRect() {
this.rect = this.DOM.el.getBoundingClientRect();
}
isActive() {
return gsap.isTweening(this.DOM.el) || this.DOM.el.style.opacity != 0;
}
}
class ImageTrail {
constructor() {
this.DOM = { content: document.querySelector('.content') };
this.images = [];
[...this.DOM.content.querySelectorAll('img')].forEach(img => this.images.push(new Image(img)));
this.imagesTotal = this.images.length;
this.imgPosition = 0;
this.zIndexVal = 1;
this.threshold = 150;
requestAnimationFrame(() => this.render());
}
render() {
let distance = getMouseDistance();
cacheMousePos.x = MathUtils.lerp(cacheMousePos.x || mousePos.x, mousePos.x, 0.1);
cacheMousePos.y = MathUtils.lerp(cacheMousePos.y || mousePos.y, mousePos.y, 0.1);
let mouseDirection = mousePos.x > prevMousePos.x ? 'right' : 'left';
if (distance > this.threshold) {
this.showNextImage(mouseDirection);
++this.zIndexVal;
this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
lastMousePos = mousePos;
}
prevMousePos = { x: mousePos.x, y: mousePos.y };
let isIdle = true;
for (let img of this.images) {
if (img.isActive()) {
isIdle = false;
break;
}
}
if (isIdle && this.zIndexVal !== 1) {
this.zIndexVal = 1;
}
requestAnimationFrame(() => this.render());
}
showNextImage(direction) {
const img = this.images[this.imgPosition];
gsap.killTweensOf(img.DOM.el);
let rotation = direction === 'right' ? 15 : -15; 
gsap.timeline()
.set(img.DOM.el, {
startAt: { opacity: 0 },
opacity: 1,
scale: 0,
rotation: 0,
zIndex: this.zIndexVal,
x: cacheMousePos.x - img.rect.width / 2,
y: cacheMousePos.y - img.rect.height / 2
}, 0)
.to(img.DOM.el, {
duration: 1.6,
ease: 'expo.out',
scale: 1,
x: mousePos.x - img.rect.width / 2,
y: mousePos.y - img.rect.height / 2
}, 0)
.to(img.DOM.el, {
duration: 0.5,
ease: 'power1.out',
opacity: 0,
scale: 0,
rotation: rotation 
}, 0.6)
.to(img.DOM.el, {
duration: 1,
ease: 'quint.inOut',
}, 0.9);
}
}
const preloadImages = () => {
return new Promise((resolve, reject) => {
imagesLoaded(document.querySelectorAll('.content__img'), resolve);
});
};
preloadImages().then(() => {
document.body.classList.remove('loading');
new ImageTrail();
});
}
});
</script>
Анимация с разбросом изображений
Добавить код в блок T123
<!--Эффект следа из изображений
https://mt-webdesign.ru/trail-image-hover-->
<script src='https://www.matilda-design.ru/library/trail-img.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/gsap/3.7.0/gsap.min.js'></script>
<style>
:root {
--ImgWidthTrail: 250px; /*размер изображения*/
--ImgBorderTrail: 20px; /*размер изображения*/
}
@media screen and (max-width: 768px) /*минимальное разрешение для запуска*/ {
main {
display: none;
}
}
.content {
height: 100vh;
width: 100%;
position: relative;
display: flex;
justify-content: center;
align-items: center;
isolation: isolate;
pointer-events: none;
}
.content__img {
pointer-events: none;
width: var(--ImgWidthTrail);
border-radius: var(--ImgBorderTrail);
position: absolute;
top: 0;
left: 0;
opacity: 0;
transform: translateY(0%);
}
.content__img--full {
width: 100%;
height: 100%;
background-size: cover;
}
</style>
<script>
window.addEventListener('DOMContentLoaded', () => {
const screenWidth = window.innerWidth;
if (screenWidth > 768) {
const body = document.body;
const docEl = document.documentElement;
const MathUtils = {
lerp: (a, b, n) => (1 - n) * a + n * b,
distance: (x1, y1, x2, y2) => Math.hypot(x2 - x1, y2 - y1)
};
let winsize;
const calcWinsize = () => {
winsize = {
width: window.innerWidth,
height: window.innerHeight
};
};
calcWinsize();
window.addEventListener('resize', calcWinsize);
const getMousePos = (ev) => {
let posx = 0;
let posy = 0;
if (ev.pageX || ev.pageY) {
posx = ev.pageX;
posy = ev.pageY - window.pageYOffset;
} else if (ev.clientX || ev.clientY) {
posx = ev.clientX + body.scrollLeft + docEl.scrollLeft;
posy = ev.clientY + body.scrollTop + docEl.scrollTop;
}
return { x: posx, y: posy };
};
let mousePos = lastMousePos = cacheMousePos = { x: 0, y: 0 };
let prevMousePos = { x: 0, y: 0 };
window.addEventListener('load', () => {
document.querySelector('.uc-trail-img').addEventListener('mousemove', ev => mousePos = getMousePos(ev));
});
const getMouseDistance = () => MathUtils.distance(mousePos.x, mousePos.y, lastMousePos.x, lastMousePos.y);
class Image {
constructor(el) {
this.DOM = { el: el };
this.defaultStyle = {
x: 0,
y: 0,
opacity: 0
};
this.getRect();
this.initEvents();
}
initEvents() {
window.addEventListener('resize', () => this.resize());
}
resize() {
gsap.set(this.DOM.el, this.defaultStyle);
this.getRect();
}
getRect() {
this.rect = this.DOM.el.getBoundingClientRect();
}
isActive() {
return gsap.isTweening(this.DOM.el) || this.DOM.el.style.opacity != 0;
}
}
class ImageTrail {
constructor() {
this.DOM = { content: document.querySelector('.content') };
this.images = [];
[...this.DOM.content.querySelectorAll('img')].forEach(img => this.images.push(new Image(img)));
this.imagesTotal = this.images.length;
this.imgPosition = 0;
this.zIndexVal = 1;
this.threshold = 150;
requestAnimationFrame(() => this.render());
}
render() {
let distance = getMouseDistance();
cacheMousePos.x = MathUtils.lerp(cacheMousePos.x || mousePos.x, mousePos.x, 0.1);
cacheMousePos.y = MathUtils.lerp(cacheMousePos.y || mousePos.y, mousePos.y, 0.1);
let mouseDirection = mousePos.x > prevMousePos.x ? 'right' : 'left';
if (distance > this.threshold) {
this.showNextImage(mouseDirection);
++this.zIndexVal;
this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
lastMousePos = mousePos;
}
prevMousePos = { x: mousePos.x, y: mousePos.y };
let isIdle = true;
for (let img of this.images) {
if (img.isActive()) {
isIdle = false;
break;
}
}
if (isIdle && this.zIndexVal !== 1) {
this.zIndexVal = 1;
}
requestAnimationFrame(() => this.render());
}
showNextImage(direction) {
const img = this.images[this.imgPosition];
gsap.killTweensOf(img.DOM.el);
let rotation = direction === 'right' ? 15 : -15; 
gsap.timeline()
.set(img.DOM.el, {
startAt: { opacity: 0 },
opacity: 1,
scale: 0,
zIndex: this.zIndexVal,
x: cacheMousePos.x - img.rect.width / 2,
y: cacheMousePos.y - img.rect.height / 2
}, 0)
.to(img.DOM.el, {
duration: 1.6,
ease: 'expo.out',
scale: 1,
x: mousePos.x - img.rect.width / 2,
y: mousePos.y - img.rect.height / 2
}, 0)
.to(img.DOM.el, {
duration: 0.5,
ease: 'power1.out',
opacity: 0,
scale: 0,
rotation: rotation,
x: `+=${Math.random() * 800 - 400}`, 
y: `+=${Math.random() * 800 - 400}`, 
}, 0.6)
.to(img.DOM.el, {
duration: 1,
ease: 'quint.inOut',
y: `-=${winsize.height + img.rect.height / 2}`, 
}, 0.9);
}
}
const preloadImages = () => {
return new Promise((resolve, reject) => {
imagesLoaded(document.querySelectorAll('.content__img'), resolve);
});
};
preloadImages().then(() => {
document.body.classList.remove('loading');
new ImageTrail();
});
}
});
</script>
Анимация с растворением изображений
Добавить код в блок T123
<!--Эффект следа из изображений
https://mt-webdesign.ru/trail-image-hover-->
<script src='https://www.matilda-design.ru/library/trail-img.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/gsap/3.7.0/gsap.min.js'></script>
<style>
:root {
--ImgWidthTrail: 250px; /*размер изображения*/
--ImgBorderTrail: 20px; /*размер изображения*/
}
@media screen and (max-width: 768px) /*минимальное разрешение для запуска*/ {
main {
display: none;
}
}
.content {
height: 100vh;
width: 100%;
position: relative;
display: flex;
justify-content: center;
align-items: center;
isolation: isolate;
pointer-events: none;
}
.content__img {
pointer-events: none;
width: var(--ImgWidthTrail);
border-radius: var(--ImgBorderTrail);
position: absolute;
top: 0;
left: 0;
opacity: 0;
transform: translateY(0%);
}
.content__img--full {
width: 100%;
height: 100%;
background-size: cover;
}
</style>
<script>
window.addEventListener('DOMContentLoaded', () => {
const screenWidth = window.innerWidth;
if (screenWidth > 768) {
const body = document.body;
const docEl = document.documentElement;
const MathUtils = {
lerp: (a, b, n) => (1 - n) * a + n * b,
distance: (x1, y1, x2, y2) => Math.hypot(x2 - x1, y2 - y1)
};
let winsize;
const calcWinsize = () => {
winsize = {
width: window.innerWidth,
height: window.innerHeight
};
};
calcWinsize();
window.addEventListener('resize', calcWinsize);
const getMousePos = (ev) => {
let posx = 0;
let posy = 0;
if (ev.pageX || ev.pageY) {
posx = ev.pageX;
posy = ev.pageY - window.pageYOffset;
} else if (ev.clientX || ev.clientY) {
posx = ev.clientX + body.scrollLeft + docEl.scrollLeft;
posy = ev.clientY + body.scrollTop + docEl.scrollTop;
}
return { x: posx, y: posy };
};
let mousePos = lastMousePos = cacheMousePos = { x: 0, y: 0 };
let prevMousePos = { x: 0, y: 0 };
window.addEventListener('load', () => {
document.querySelector('.uc-trail-img').addEventListener('mousemove', ev => mousePos = getMousePos(ev));
});
const getMouseDistance = () => MathUtils.distance(mousePos.x, mousePos.y, lastMousePos.x, lastMousePos.y);
class Image {
constructor(el) {
this.DOM = { el: el };
this.defaultStyle = {
x: 0,
y: 0,
opacity: 0
};
this.getRect();
this.initEvents();
}
initEvents() {
window.addEventListener('resize', () => this.resize());
}
resize() {
gsap.set(this.DOM.el, this.defaultStyle);
this.getRect();
}
getRect() {
this.rect = this.DOM.el.getBoundingClientRect();
}
isActive() {
return gsap.isTweening(this.DOM.el) || this.DOM.el.style.opacity != 0;
}
}
class ImageTrail {
constructor() {
this.DOM = { content: document.querySelector('.content') };
this.images = [];
[...this.DOM.content.querySelectorAll('img')].forEach(img => this.images.push(new Image(img)));
this.imagesTotal = this.images.length;
this.imgPosition = 0;
this.zIndexVal = 1;
this.threshold = 150;
requestAnimationFrame(() => this.render());
}
render() {
let distance = getMouseDistance();
cacheMousePos.x = MathUtils.lerp(cacheMousePos.x || mousePos.x, mousePos.x, 0.1);
cacheMousePos.y = MathUtils.lerp(cacheMousePos.y || mousePos.y, mousePos.y, 0.1);
let mouseDirection = mousePos.x > prevMousePos.x ? 'right' : 'left';
if (distance > this.threshold) {
this.showNextImage(mouseDirection);
++this.zIndexVal;
this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
lastMousePos = mousePos;
}
prevMousePos = { x: mousePos.x, y: mousePos.y };
let isIdle = true;
for (let img of this.images) {
if (img.isActive()) {
isIdle = false;
break;
}
}
if (isIdle && this.zIndexVal !== 1) {
this.zIndexVal = 1;
}
requestAnimationFrame(() => this.render());
}
showNextImage(direction) {
const img = this.images[this.imgPosition];
gsap.killTweensOf(img.DOM.el);
let rotation = direction === 'right' ? 15 : -15; 
gsap.timeline()
.set(img.DOM.el, {
startAt: { opacity: 0 },
opacity: 1,
scale: 0,
rotation: 0,
filter: 'grayscale(100%) blur(0px)',
zIndex: this.zIndexVal,
x: cacheMousePos.x - img.rect.width / 2,
y: cacheMousePos.y - img.rect.height / 2
}, 0)
.to(img.DOM.el, {
duration: 1.6,
ease: 'expo.out',
scale: 1,
filter: 'grayscale(0%) blur(0px)',
x: mousePos.x - img.rect.width / 2,
y: mousePos.y - img.rect.height / 2
}, 0)
.to(img.DOM.el, {
duration: 0.5,
ease: 'power1.out',
opacity: 0,
scale: 3,
filter: 'grayscale(0%) blur(100px)',
rotation: rotation 
}, 0.6)
.to(img.DOM.el, {
duration: 1,
ease: 'quint.inOut',
}, 0.9);
}
}
const preloadImages = () => {
return new Promise((resolve, reject) => {
imagesLoaded(document.querySelectorAll('.content__img'), resolve);
});
};
preloadImages().then(() => {
document.body.classList.remove('loading');
new ImageTrail();
});
}
});
</script>
Более подробные настройки, возможности взаимодействия и подключение в видео
Другие модификации
3D галерея
Tilda
Подсказки при наведении курсора
Tilda