| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196 |
- <template>
- <section class="prallax--banner--wrappers">
- <section class="prallax--banner--wrapper" ref="parallaxWrapper">
- <div class="prallax--banner" ref="parallaxBanner">
- <picture>
- <source
- media="(min-width:1440px)"
- :srcset="`${images.xl} 1920w, ${images.xl2x || images.xl} 3840w`"
- sizes="(min-width:1920px) 1920px, 100vw"
- width="1920"
- height="2000"
- />
- <source
- media="(min-width:768px)"
- :srcset="`${images.m} 1439w, ${images.m2x || images.m} 2878w`"
- sizes="100vw"
- width="1439"
- height="1750"
- />
- <source
- media="(min-width:375px)"
- :srcset="`${images.s} 787w, ${images.s2x || images.s} 1574w`"
- sizes="100vw"
- width="787"
- height="1250"
- />
- <img
- :alt="imageAlt"
- loading="lazy"
- :src="images.xs"
- :srcset="`${images.xs} 374w, ${images.xs2x || images.xs} 748w`"
- />
- </picture>
- </div>
- <div class="text--box--wrapper" ref="textWrapper">
- <div class="text--container">
- <h2>{{ title }}</h2>
- <p>{{ description }}</p>
- <a v-if="linkUrl && linkText" :href="linkUrl">{{ linkText }}</a>
- </div>
- </div>
- </section>
- <div class="caution--text" v-if="cautionText" v-html="cautionText"></div>
- </section>
- </template>
- <script setup>
- // Props 정의
- const props = defineProps({
- // 이미지 경로들
- images: {
- type: Object,
- default: () => ({
- xl:
- "https://media.audi.com/is/image/audi/nemo/misc/audi-exclusive-one-audi-ready/parallax-teaser/02_r8_2021_3104_58056-XL.jpg?auto=webp&width=1920",
- xl2x:
- "https://media.audi.com/is/image/audi/nemo/misc/audi-exclusive-one-audi-ready/parallax-teaser/02_r8_2021_3104_58056-XL.jpg?auto=webp&width=3840",
- m:
- "https://media.audi.com/is/image/audi/nemo/misc/audi-exclusive-one-audi-ready/parallax-teaser/02_r8_2021_3104_58056-M.jpg?auto=webp&width=1439",
- m2x:
- "https://media.audi.com/is/image/audi/nemo/misc/audi-exclusive-one-audi-ready/parallax-teaser/02_r8_2021_3104_58056-M.jpg?auto=webp&width=2878",
- s:
- "https://media.audi.com/is/image/audi/nemo/misc/audi-exclusive-one-audi-ready/parallax-teaser/02_r8_2021_3104_58056-S.jpg?auto=webp&width=787",
- s2x:
- "https://media.audi.com/is/image/audi/nemo/misc/audi-exclusive-one-audi-ready/parallax-teaser/02_r8_2021_3104_58056-S.jpg?auto=webp&width=1574",
- xs:
- "https://media.audi.com/is/image/audi/nemo/misc/audi-exclusive-one-audi-ready/parallax-teaser/02_r8_2021_3104_58056-XS.jpg?auto=webp&width=374",
- xs2x:
- "https://media.audi.com/is/image/audi/nemo/misc/audi-exclusive-one-audi-ready/parallax-teaser/02_r8_2021_3104_58056-XS.jpg?auto=webp&width=748",
- }),
- },
- // 이미지 alt 텍스트
- imageAlt: {
- type: String,
- default: "",
- },
- // 제목
- title: {
- type: String,
- default: "",
- },
- cautionText: {
- type: String,
- default: "",
- },
- // 설명
- description: {
- type: String,
- default: "",
- },
- // 링크 URL
- linkUrl: {
- type: String,
- default: "",
- },
- // 링크 텍스트
- linkText: {
- type: String,
- default: "",
- },
- });
- // Refs
- const parallaxWrapper = ref(null);
- const parallaxBanner = ref(null);
- const textWrapper = ref(null);
- // Easing 함수 - 부드러운 가속/감속 효과
- const easeOutQuart = (t) => {
- return 1 - Math.pow(1 - t, 4);
- };
- // 패럴렉스 효과 함수
- const handleScroll = () => {
- if (!parallaxWrapper.value || !parallaxBanner.value || !textWrapper.value) return;
- const rect = parallaxWrapper.value.getBoundingClientRect();
- const windowHeight = window.innerHeight;
- // 요소가 화면에 보이는지 확인
- if (rect.bottom >= 0 && rect.top <= windowHeight) {
- // 스크롤 진행률 계산 (0 ~ 1.5) - 50% 추가 범위
- const scrollProgress = Math.max(
- 0,
- Math.min(1.5, (windowHeight - rect.top) / (windowHeight + rect.height))
- );
- // Easing 적용
- const easedProgress = easeOutQuart(scrollProgress);
- // 배경 이미지 패럴렉스 효과 (아우디 실제 로직: 스크롤 거리의 25%만큼 움직임) - translate3d로 GPU 가속
- const scrollDistance = windowHeight - rect.top;
- const bgTranslateY = scrollDistance * 0.25;
- parallaxBanner.value.style.transform = `translate3d(-50%, ${bgTranslateY}px, 0)`;
- parallaxBanner.value.style.webkitTransform = `translate3d(-50%, ${bgTranslateY}px, 0)`;
- // 텍스트 박스 패럴렉스 효과 (아우디 실제 로직: 스크롤 거리의 -12.5%만큼 움직임) - translate3d로 GPU 가속
- const textTranslateY = scrollDistance * -0.125;
- const textOpacity = Math.max(
- 0.5,
- Math.min(1, 1.3 - Math.abs(scrollProgress - 0.5) * 1.2)
- );
- textWrapper.value.style.transform = `translate3d(0, ${textTranslateY}px, 0)`;
- textWrapper.value.style.webkitTransform = `translate3d(0, ${textTranslateY}px, 0)`;
- textWrapper.value.style.opacity = textOpacity;
- }
- };
- // Intersection Observer를 사용한 성능 최적화
- let observer = null;
- onMounted(() => {
- // 스크롤 이벤트를 throttle하여 성능 최적화
- let ticking = false;
- const throttledScroll = () => {
- if (!ticking) {
- requestAnimationFrame(() => {
- handleScroll();
- ticking = false;
- });
- ticking = true;
- }
- };
- // Intersection Observer로 뷰포트 진입 감지
- observer = new IntersectionObserver(
- (entries) => {
- entries.forEach((entry) => {
- if (entry.isIntersecting) {
- window.addEventListener("scroll", throttledScroll, { passive: true });
- } else {
- window.removeEventListener("scroll", throttledScroll);
- }
- });
- },
- {
- rootMargin: "100px 0px",
- }
- );
- if (parallaxWrapper.value) {
- observer.observe(parallaxWrapper.value);
- }
- // 초기 실행
- handleScroll();
- });
- onUnmounted(() => {
- if (observer) {
- observer.disconnect();
- }
- window.removeEventListener("scroll", handleScroll);
- });
- </script>
|