<template>
<div class="l__marquee" v-show="isItMobile === false" ref="el">
<div ref="fullscreenBackground" class="fullscreen-background"></div>
<div class="marquee home__marquee noimagemarquee">
<div ref="marquee" class="marquee__inner">
<div
class="home__project marquee--item"
v-for="(project, index) in filteredProjects"
:key="project.project.uid"
>
<div
draggable="false"
class="title--container"
ref="titleItems"
:data-index="index"
:data-to="'/project/' + project.project.uid"
:data-thumbnail="project?.project?.data?.project_visual?.url || ''"
:data-projectid="project?.project?.uid"
:data-projectName="project?.project?.data?.project_name"
data-section-slider
>
<h1 ref="titleItem" class="title">
{{ project.project.data.client_name }}
</h1>
</div>
</div>
</div>
</div>
<div ref="mouseleave" class="mouseleave"></div>
</div>
</template>
<script setup>
import { nextTick, onMounted, ref } from "vue";
import { gsap } from "gsap";
import Marquee from "@/librairies/Marquee.js";
import { useFirstLoaded } from "../composables/state";
import { isItMobile } from "../composables/useMobile";
import { SplitText } from "gsap/SplitText";
const { $bus } = useNuxtApp();
const el = ref(null);
const marquee = ref(null);
const mouseleave = ref(null);
const titleItem = ref([]);
const titleItems = ref(null);
const fullscreenBackground = ref(null);
let elements,
mouse,
font,
scroll,
marqueeInstance,
baseFontHeight,
isRunning = false;
const { client } = usePrismic();
const { data: homepage } = await useAsyncData("Homepage", () =>
client.getSingle("homepage_projects", {
fetchLinks: [
"project.project_name",
"project.client_name",
"project.uid",
"project.project_visual",
],
})
);
const filteredProjects = computed(() => {
return homepage.value.data.featured_projects.filter(
(project) => project.project.uid !== currentRoute
);
});
const router = useRouter();
const currentRoute = router.currentRoute.value.params.id;
onMounted(() => {
watch(
isItMobile,
async (value) => {
if (!value && isRunning === false) {
await nextTick();
preloadImages();
initVariables();
mouse = window.mouse;
initPageAnimations();
if (useFirstLoaded().value === true) {
initPageEntrance();
}
if (marqueeInstance === undefined) {
marqueeInstance = new Marquee(marquee.value);
} else {
marqueeInstance.reset();
}
setTimeout(() => {
onResize();
}, 150);
initListeners();
isRunning = true;
document.body.classList.add("--homepage-modifier");
document
.querySelector(".l__layout")
.classList.add("--homepage-modifier");
} else if (value) {
removeListeners();
isRunning = false;
document.body.classList.remove("--homepage-modifier");
document
.querySelector(".l__layout")
.classList.remove("--homepage-modifier");
}
},
{ immediate: true }
);
});
$bus.$on("loaded", () => {
initPageEntrance();
});
const preloadImages = () => {
titleItems.value.forEach((element) => {
const type = checkFileType(element.dataset.thumbnail);
if (type === "image") {
const img = new Image();
img.src = element.dataset.thumbnail;
} else if (type === "video") {
let mediaElement = document.createElement("video");
mediaElement.preload = "auto";
mediaElement.src = element.dataset.thumbnail;
mediaElement.load();
}
});
};
const checkFileType = (url) => {
url = url.split("?")[0];
const imageRegex = /\.(jpeg|jpg|gif|png)$/;
const videoRegex = /\.(mp4)$/;
if (url.match(imageRegex)) {
return "image";
} else if (url.match(videoRegex)) {
return "video";
} else {
return "unknown";
}
};
const initVariables = () => {
elements = [];
font = {
height: 1,
};
scroll = {
ease: 0.05,
current: 0,
target: 0,
};
baseFontHeight = window.innerWidth < 1024 ? 35 : 1;
};
const initPageAnimations = () => {
splitText();
};
const splitText = () => {
titleItem.value.forEach((element) => {
new SplitText(element, {
type: "words, lines, chars",
wordsClass: "SplitTextJS-wrapper",
charsClass: "SplitTextJS-char",
});
});
elements = el.value.querySelectorAll(".SplitTextJS-char");
};
const initPageEntrance = () => {
titleItems.value.forEach((title, index) => {
gsap.to(title, {
y: 0,
opacity: 1,
ease: "expo.inOut",
duration: 2.5,
delay: -0.8 + index * 0.05,
overwrite: true,
});
});
};
const onTitleClick = async (e) => {
if (window.isDragging) return;
const link = e.target.dataset.to;
titleItem.value.forEach((element) => {
gsap.set(element, {
color: "white",
opacity: 0.25,
});
});
const transitionTL = gsap.timeline({
defaults: {
ease: "expo.inOut",
duration: 1.5,
},
});
transitionTL.to(
".SplitTextJS-wrapper",
{
y: 500,
opacity: 0,
duration: 1.2,
ease: "expo.inOut",
},
0
);
await navigateTo(link);
};
const initListeners = () => {
window.addEventListener("resize", () => {
marqueeInstance.onResize();
});
mouseleave.value.addEventListener("mouseenter", onContainerEnter);
marquee.value.addEventListener("mouseover", onMarqueeMouseOver);
marquee.value.addEventListener("mouseenter", onMarqueeMouseEnter);
marquee.value.addEventListener("mouseleave", onMarqueeMouseLeave);
document.addEventListener("mouseleave", onDocumentLeave);
titleItems.value.forEach((element, index) => {
element.addEventListener("mouseenter", () => {
onTitleMouseEnter(element);
});
element.addEventListener("mouseover", () => {
onTitleMouseOver(element, index);
});
element.addEventListener("mouseleave", () => {
onTitleMouseLeave();
});
element.addEventListener("click", onTitleClick);
});
};
const removeListeners = () => {
// window.removeEventListener("resize", marqueeInstance.onResize);
marquee.value.removeEventListener("mouseover", onMarqueeMouseOver);
marquee.value.removeEventListener("mouseleave", onMarqueeMouseLeave);
mouseleave.value.removeEventListener("mouseenter", onContainerEnter);
document.removeEventListener("mouseleave", onDocumentLeave);
window.removeEventListener("mousemove", onTitleMouseMove);
titleItems.value.forEach((element) => {
element.removeEventListener("mouseover", onTitleMouseOver);
element.removeEventListener("mouseleave", onTitleMouseLeave);
element.removeEventListener("mouseenter", onTitleMouseEnter);
element.removeEventListener("click", onTitleClick);
});
};
const onDocumentLeave = () => {
resetLetters();
};
const resetLetters = () => {
const titleCharacters = el.value.querySelectorAll(".SplitTextJS-char");
titleCharacters.forEach((letter) => {
const fontPerLetter = {
height: letter.style.getPropertyValue("--fontHeight") || baseFontHeight,
};
gsap.to(letter, {
fontSize: "19rem",
duration: 0.2,
});
gsap.to(fontPerLetter, {
height: 1,
duration: 0.2,
onUpdate: () => {
letter.style.setProperty(
"--fontHeight",
fontPerLetter.height || baseFontHeight
);
},
});
});
};
const onContainerEnter = () => {
resetLetters();
};
const onTitleMouseEnter = (element) => {
const media = element.dataset.thumbnail;
const fileType = checkFileType(media);
let mediaElement;
if (fileType === "video") {
mediaElement = document.createElement("video");
mediaElement.playsInline = true;
mediaElement.loop = true;
mediaElement.muted = true;
mediaElement.controls = false;
mediaElement.classList.add("fullscreen-background--media");
mediaElement.src = media;
mediaElement.load();
mediaElement.play();
} else if (fileType === "image") {
mediaElement = document.createElement("img");
mediaElement.classList.add("fullscreen-background--media");
mediaElement.src = media;
}
fullscreenBackground.value.append(mediaElement);
const existingVideo = document.querySelector(".fullscreen-background--media");
if (existingVideo) {
gsap.to(existingVideo, {
opacity: 0,
duration: 0.2,
onComplete: () => existingVideo.remove(),
});
}
gsap.to(mediaElement, {
opacity: 1,
scale: 1,
duration: 0.3,
overwrite: "auto",
});
};
const onTitleMouseOver = (element, index) => {
// CHARACTERS ANIMATION ON MOUSEMOVE
window.addEventListener("mousemove", onTitleMouseMove);
marqueeInstance.isHovering = 0; // STOP AUTOSCROLL
};
const onTitleMouseLeave = (event) => {
marqueeInstance.isHovering = 1; // STOP AUTOSCROLL
scroll.target = 0;
scroll.current = 0;
};
const onTitleMouseMove = (element) => {
const index = Number(element.target.dataset.index);
const elements = [
el.value.querySelector(
`[data-index='${index <= 0 ? titleItems.value.length - 1 : index - 1}']`
),
el.value.querySelector(`[data-index='${index}']`),
el.value.querySelector(
`[data-index='${
index + 1 > titleItems.value.length - 1 ? 0 : index + 1
}']`
),
];
if (elements[0] === null) return;
const titleCharacters = [];
elements.forEach((element) => {
const characters = element.querySelectorAll(".SplitTextJS-char");
titleCharacters.push(...characters);
});
titleCharacters.forEach((elem) => {
if (elem.innerHTML === " ") return;
adjustImage(elem, mouse?.position?.x, mouse?.position?.y);
});
};
const onMarqueeMouseOver = () => {
marqueeInstance.marqueeSpeed = 0;
};
const onMarqueeMouseEnter = () => {
gsap.to(".cursor__drag", { opacity: 1, duration: 0.3 });
};
const onMarqueeMouseLeave = () => {
marqueeInstance.marqueeSpeed = 0.5;
const allVideos = document.querySelectorAll(".fullscreen-background--media");
gsap.to(allVideos, {
opacity: 0,
duration: 0.3,
onComplete: () => {
allVideos.forEach((video) => video.remove());
},
});
gsap.to(".cursor__drag", { opacity: 0, duration: 0.3 });
};
const calculateCenter = (elem) => {
var rect1 = elem.getBoundingClientRect();
var x = rect1.left + rect1.width * 0.5;
var y = rect1.top + rect1.height * 0.99;
return { x: x, y: y };
};
const getDistance = (x1, y1, x2, y2) => {
let y = x2 - x1;
let x = y2 - y1;
return Math.sqrt(x * x + y * y);
};
const distanceFromCenter = (image, mouseX, mouseY) => {
var imageCenter = calculateCenter(image);
return getDistance(imageCenter.x, imageCenter.y, mouseX, mouseY);
};
const adjustImage = (image, mX, mY) => {
var distance = distanceFromCenter(image, mX, mY);
const baseScale = 1;
const maxScaling = 1.2;
const scalingFactor = 1;
const adjustedScaling = maxScaling - (distance / 1500) * scalingFactor;
let scaling = adjustedScaling >= baseScale ? adjustedScaling : baseScale;
scaling = Math.max(1, scaling);
gsap.set(image, {
fontSize: 19 + (scaling - 1) * scaling * 15 + "rem",
});
gsap.set(font, {
height: 1 + (scaling - 1) * scaling * 250,
duration: 0.5,
onUpdate: () => {
image.style.setProperty("--fontHeight", font.height);
},
});
};
const onResize = () => {
marqueeInstance.onResize();
};
onBeforeUnmount(() => {
if (isItMobile === true) return;
document.body.classList.remove("--homepage-modifier");
document.querySelector(".l__layout").classList.remove("--homepage-modifier");
gsap.to(".cursor__drag", { opacity: 0, duration: 0.3 });
removeListeners();
elements = [];
mouse = {
x: null,
y: null,
};
mouse = null;
font = {
height: null,
};
});
</script>
<style lang="scss">
@import "@/assets/css/pages/home.scss";
</style>