Skip to content

Latest commit

 

History

History
315 lines (261 loc) · 7.94 KB

README.md

File metadata and controls

315 lines (261 loc) · 7.94 KB

'CGV 리뉴얼'

사용자 경험을 극대화하기 위해 인터랙티브한 요소와 재사용가능한 컴포넌트를 활용했습니다.
- Swiper를 사용하여 다양한 영화 정보를 제공했습니다.
- 각 영화의 재생 버튼을 클릭하여 모달 창을 통해 예고편을 바로 볼 수 있도록 구성했습니다.
- 극장 목록은 scroll-x animation을 적용하여 사용자가 다양한 극장을 한눈에 볼 수 있도록 구성했습니다.
- Flex와 transition을 활용하여 이벤트에 인터랙티브한 레이아웃을 구성했습니다.


• 배포 주소: https://cgv-re.netlify.app/


- 작업 기간: 2024.07

- 리팩토링: 2024.09


기술 스택

Development

Config

Environment



웹 성능 최적화 (Lighthouse 사용)

1️⃣ 이미지 포맷을 jpg에서 webp로 변환

  • 성능 : 64 → 71

    최대 텍스트 또는 이미지가 표시되는 시간 : 2.1초 → 0.8초 before after

2️⃣ useMemo를 활용하여 SwiperSlide를 동적으로 생성(코드의 반복성 줄임) & loading="lazy"로 초기 로딩 성능 개선

  • 성능 : 71 -> 73

    페이지 콘텐츠가 표시되는 속도 : 2.2초 → 1.9초 before after



전체 페이지



💻 주요 기능


  • swiper
/* Intro */
import { Swiper, SwiperSlide } from "swiper/react";
import { EffectFade, Autoplay, Navigation } from "swiper/modules";
import "swiper/css";
import "swiper/css/navigation";
import "swiper/css/effect-fade";

<Swiper
  loop={true}
  navigation={true}
  modules={[EffectFade, Navigation, Autoplay]}
  autoplay={{ delay: 6000 }}
  effect={"fade"}
>



  • modal
// Trailer Modal
const [showModal, setShowModal] = useState(false);
const [videoSource, setVideoSource] = useState("");

const openModal = useCallback((source) => {
  setVideoSource(source);
  setShowModal(true);
}, []);

const handleCloseModal = useCallback(() => {
  setVideoSource("");
  setShowModal(false);
}, []);
{
  /* 모달 */
}
{
  showModal && (
    <div className="modal">
      <div className="inner">
        <div>
          <button onClick={handleCloseModal}>
            <FontAwesomeIcon icon={faXmark} />
          </button>
        </div>
        <video controls autoPlay loop muted>
          <source src={videoSource} type="video/mp4" />
        </video>
      </div>
    </div>
  );
}
<div
  className="btn"
  onClick={() =>
    openModal(
      "https://h.vod.cgv.co.kr/vodCGVa/88267/88267_226464_1200_128_960_540.mp4"
    )
  }
>
  <img className="play" src={play} alt="play" loading="lazy" />
</div>



  • Flex & animation
{/* section : theater */}
<section className="theater">
  <div className="inner">
    <div className="list">
      <div className="list-group">
        <img src="{theater01}" alt="theater01" loading="lazy" />
        <img src="{theater02}" alt="theater02" loading="lazy" />
        <img src="{theater03}" alt="theater03" loading="lazy" />
        <img src="{theater04}" alt="theater04" loading="lazy" />
        <img src="{theater05}" alt="theater05" loading="lazy" />
      </div>
      <div aria-hidden="true" className="list-group">
        <img src="{theater01}" alt="theater01" loading="lazy" />
        <img src="{theater02}" alt="theater02" loading="lazy" />
        <img src="{theater03}" alt="theater03" loading="lazy" />
        <img src="{theater04}" alt="theater04" loading="lazy" />
        <img src="{theater05}" alt="theater05" loading="lazy" />
      </div>
    </div>
    <div className="list list-reverse">
      <div className="list-group">
        <img src="{theater06}" alt="theater06" loading="lazy" />
        <img src="{theater07}" alt="theater07" loading="lazy" />
        <img src="{theater08}" alt="theater08" loading="lazy" />
        <img src="{theater09}" alt="theater09" loading="lazy" />
        <img src="{theater10}" alt="theater10" loading="lazy" />
        <img src="{theater11}" alt="theater11" loading="lazy" />
      </div>
      <div aria-hidden="true" className="list-group">
        <img src="{theater06}" alt="theater06" loading="lazy" />
        <img src="{theater07}" alt="theater07" loading="lazy" />
        <img src="{theater08}" alt="theater08" loading="lazy" />
        <img src="{theater09}" alt="theater09" loading="lazy" />
        <img src="{theater10}" alt="theater10" loading="lazy" />
        <img src="{theater11}" alt="theater11" loading="lazy" />
      </div>
    </div>
  </div>
</section>
.theater {
  margin-top: 126px;
  overflow: hidden;
  .inner {
    display: flex;
    flex-direction: column;
    gap: 16px;
    margin: auto;
    .list {
      display: flex;
      user-select: none;
      gap: 16px;
      .list-group {
        display: flex;
        justify-content: space-around;
        gap: 16px;
        animation: scroll-x var(--duration) linear infinite;
      }
    }
    .list-reverse {
      .list-group {
        animation-direction: reverse;
        animation-delay: -3s;
      }
    }
  }
  img {
    width: 382px;
    height: 212px;
    border-radius: 12px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .list {
    animation-play-state: paused;
  }
}
@keyframes scroll-x {
  from {
    transform: translateX(var(--scroll-start));
  }
  to {
    transform: translateX(var(--scroll-end));
  }
}



  • tabBackgrounds 배열에서 현재 활성화된 탭('activeTab')의 배경 목록 표시
<div className="lists" data-aos="fade-up" data-aos-duration="1500">
  <ul>
    {tabBackgrounds[activeTab].map((bg, index) => (
      <li key={index} className={`background-${bg}`}>
        <div>
          <button onClick={() => handleNavigation("/making")}>상세보기</button>
        </div>
      </li>
    ))}
  </ul>
</div>

  • Flex를 활용하여 동적 리스트 항목 크기 조절 및 애니메이션 효과 구현
.event {
  .inner {
    .contents {
      .lists {
        ul {
          list-style: none;
          padding: 0;
          margin: 0;
          display: flex;
          gap: 6px;
          overflow: hidden;
          li {
            border-radius: 12px;
            position: relative;
            width: calc(1200px / 7);
            height: 530px;
            flex: 1;
            filter: grayscale(1);
            transition: 0.3s;
            cursor: pointer;
            overflow: hidden;
            &:hover {
              flex: 5;
              filter: grayscale(0);
              div {
                opacity: 1;
                transition: 0.3s;
              }
            }
          }
        }
      }
    }
  }
}