Untitled

mail@pastecode.io avatar
unknown
plain_text
a year ago
12 kB
5
Indexable
Never
<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 === "&nbsp;") 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>