Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed public/assets/rending/new-swipe/1.png
Binary file not shown.
174 changes: 174 additions & 0 deletions public/assets/rending/new-swipe/1.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed public/assets/rending/new-swipe/2.png
Binary file not shown.
143 changes: 143 additions & 0 deletions public/assets/rending/new-swipe/2.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed public/assets/rending/new-swipe/3.png
Binary file not shown.
387 changes: 387 additions & 0 deletions public/assets/rending/new-swipe/3.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed public/assets/rending/new-swipe/4.png
Binary file not shown.
387 changes: 387 additions & 0 deletions public/assets/rending/new-swipe/4.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
149 changes: 99 additions & 50 deletions src/feature/root/components/RendingBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,39 +16,41 @@ const KAKAO_AUTH_URL = `https://dev.say-cheese.me/oauth2/authorization/kakao`;
export const RendingBody = () => {
const [api, setApi] = useState<CarouselApi>();
const [current, setCurrent] = useState(0);
const [touchStart, setTouchStart] = useState(0);
const [touchEnd, setTouchEnd] = useState(0);
Comment on lines +19 to +20
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

touchStarttouchEnd 값을 useState로 관리하면 handleTouchMove가 호출될 때마다 불필요한 리렌더링이 발생하여 성능이 저하될 수 있습니다. 터치 이동 중 좌표는 렌더링에 직접적인 영향을 주지 않으므로, useRef를 사용하여 값을 관리하는 것이 더 효율적입니다. useRef로 변경하고 관련 핸들러 함수들도 수정해주세요.

// ...
import { useEffect, useState, useRef } from 'react';
// ...
const touchStartRef = useRef(0);
const touchEndRef = useRef(0);

// ...
const handleTouchStart = (e: React.TouchEvent) => {
  touchStartRef.current = e.targetTouches[0].clientX;
};

const handleTouchMove = (e: React.TouchEvent) => {
  touchEndRef.current = e.targetTouches[0].clientX;
};

const handleTouchEnd = () => {
  if (!api) return;

  const swipeDistance = touchStartRef.current - touchEndRef.current;
  // ...
};
// ...

const searchParams = useSearchParams();
const redirect = searchParams.get('redirect');

const slides = [
{
src: '/assets/rending/new-swipe/1.png',
src: '/assets/rending/new-swipe/1.svg',
alt: '앨범 목록 화면',
width: 260,
height: 530,
height: 530.31,
title: '이벤트마다 만드는 공유앨범',
description: '감튀 모임부터 찐친 여행까지',
},
{
src: '/assets/rending/new-swipe/2.png',
src: '/assets/rending/new-swipe/2.svg',
alt: 'QR 코드 공유',
width: 260,
height: 530,
height: 530.31,
title: '이벤트마다 모인 자리에서 바로 공유',
description: '감튀 모임부터 앨범 만들고 초대까지 딱 10초',
},
{
src: '/assets/rending/new-swipe/3.png',
src: '/assets/rending/new-swipe/3.svg',
alt: '베스트컷',
width: 312.5,
height: 530.3,
width: 312.56,
height: 530.31,
title: '이벤트마다 한눈에 보는 베스트컷',
description: '사진 고르는 고민 이제 끝',
},
{
src: '/assets/rending/new-swipe/4.png',
src: '/assets/rending/new-swipe/4.svg',
alt: '네컷추억',
width: 289,
height: 530,
width: 288.8,
height: 530.31,
title: '딱 네컷으로 남는 추억',
description: '앨범이 닫히면 사라지는 원본 사진들',
},
Expand All @@ -63,6 +65,29 @@ export const RendingBody = () => {
});
}, [api]);

const handleTouchStart = (e: React.TouchEvent) => {
setTouchStart(e.targetTouches[0].clientX);
};

const handleTouchMove = (e: React.TouchEvent) => {
setTouchEnd(e.targetTouches[0].clientX);
};

const handleTouchEnd = () => {
if (!api) return;

const swipeDistance = touchStart - touchEnd;
const minSwipeDistance = 50;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

minSwipeDistance 값으로 50이 하드코딩되어 있습니다. 이 값을 컴포넌트 상단에 const MIN_SWIPE_DISTANCE = 50;과 같이 상수로 정의하고 사용하면 코드의 가독성과 유지보수성이 향상됩니다.


if (swipeDistance > minSwipeDistance) {
// 왼쪽으로 스와이프 (다음)
api.scrollNext();
} else if (swipeDistance < -minSwipeDistance) {
// 오른쪽으로 스와이프 (이전)
api.scrollPrev();
}
};

const handleKakaoLogin = async () => {
try {
const kakaoUrl = redirect
Expand All @@ -77,62 +102,86 @@ export const RendingBody = () => {

return (
<section className='bg-background-white-muted relative flex h-screen w-full flex-col'>
{/* 캐러셀 영역 */}
{/* 이미지 영역 - 고정 (애니메이션 없음) */}
<div className='flex h-screen w-full flex-col items-center justify-start px-4 pt-12'>
<div
className='relative w-full max-w-md'
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
>
{slides.map((slide, index) => (
<div
key={index}
className={`flex items-center justify-center overflow-hidden rounded-3xl transition-opacity duration-500 ${
current === index
? 'relative opacity-100'
: 'pointer-events-none absolute inset-0 opacity-0'
}`}
>
<Image
src={slide.src}
alt={slide.alt}
width={slide.width}
height={slide.height}
className='object-contain'
priority={index === 0}
/>
</div>
))}
</div>
</div>

{/* 하단 흰색 배경 영역 */}
<div
className='absolute bottom-0 z-10 flex w-full flex-col items-center bg-white px-4 pt-8'
style={{ paddingBottom: 'calc(env(safe-area-inset-bottom) + 20px)' }}
>
{/* 텍스트 및 인디케이터 영역 - 스와이프 가능 */}
<Carousel
setApi={setApi}
className='w-full max-w-md'
className='w-full'
opts={{
loop: true,
loop: false,
align: 'center',
}}
>
<CarouselContent>
{slides.map((slide, index) => (
<CarouselItem key={index}>
<div className='flex items-center justify-center overflow-hidden rounded-3xl'>
<Image
src={slide.src}
alt={slide.alt}
width={slide.width}
height={slide.height}
className='object-contain'
priority={index === 0}
/>
<div className='flex flex-col items-center'>
{/* 텍스트 영역 */}
<div className='mb-6 flex flex-col items-center text-center'>
<h2 className='text-text-basic text-[24px] font-[600]'>
{slide.title}
</h2>
<p className='mt-1 text-[16px] font-[500] text-[#746181]'>
{slide.description}
</p>
</div>

{/* 인디케이터 점들 */}
<div className='flex gap-2'>
{slides.map((_, idx) => (
<button
key={idx}
onClick={() => api?.scrollTo(idx)}
className={`h-1.5 rounded-full transition-all ${
current === idx
? 'w-6 bg-gray-700'
: 'w-1.5 bg-gray-200'
}`}
aria-label={`슬라이드 ${idx + 1}로 이동`}
/>
))}
</div>
</div>
</CarouselItem>
))}
</CarouselContent>
</Carousel>
Comment on lines 141 to 182
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

현재 캐러셀의 각 아이템(CarouselItem) 내에서 인디케이터 점들을 렌더링하고 있습니다. 이로 인해 slides 배열의 길이만큼 인디케이터 그룹이 중복 생성되어 비효율적이며, 인디케이터가 텍스트와 함께 슬라이드되는 문제가 발생합니다. 인디케이터는 슬라이드되지 않고 고정되어야 하므로, CarouselContent 외부에서 한 번만 렌더링하도록 수정하는 것이 좋습니다.

        <Carousel
          setApi={setApi}
          className='w-full'
          opts={{
            loop: false,
            align: 'center',
          }}
        >
          <CarouselContent>
            {slides.map((slide, index) => (
              <CarouselItem key={index}>
                <div className='flex flex-col items-center'>
                  {/* 텍스트 영역 */}
                  <div className='mb-6 flex flex-col items-center text-center'>
                    <h2 className='text-text-basic text-[24px] font-[600]'>
                      {slide.title}
                    </h2>
                    <p className='mt-1 text-[16px] font-[500] text-[#746181]'>
                      {slide.description}
                    </p>
                  </div>
                </div>
              </CarouselItem>
            ))}
          </CarouselContent>
          <div className='mt-6 flex justify-center gap-2'>
            {slides.map((_, idx) => (
              <button
                key={idx}
                onClick={() => api?.scrollTo(idx)}
                className={`h-1.5 rounded-full transition-all ${
                  current === idx
                    ? 'w-6 bg-gray-700'
                    : 'w-1.5 bg-gray-200'
                }`}
                aria-label={`슬라이드 ${idx + 1}로 이동`}
              />
            ))}
          </div>
        </Carousel>

</div>

{/* 하단 흰색 배경 영역 - 이미지 위에 오버레이 */}
<div className='absolute bottom-0 z-10 flex h-[242px] w-full flex-col items-center bg-white px-4 pt-8 pb-5'>
{/* 텍스트 영역 */}
<div className='mb-6 flex flex-col items-center text-center'>
<h2 className='text-text-basic text-[24px] font-[600]'>
{slides[current].title}
</h2>
<p className='mt-1 text-[16px] font-[500] text-[#746181]'>
{slides[current].description}
</p>
</div>

{/* 인디케이터 점들 */}
<div className='flex gap-2'>
{slides.map((_, index) => (
<button
key={index}
onClick={() => api?.scrollTo(index)}
className={`h-1.5 rounded-full transition-all ${
current === index ? 'w-6 bg-gray-700' : 'w-1.5 bg-gray-200'
}`}
aria-label={`슬라이드 ${index + 1}로 이동`}
/>
))}
</div>

{/* 카카오 로그인 버튼 */}
{/* 카카오 로그인 버튼 - 고정 */}
<div
className='mt-auto flex h-[56px] w-full cursor-pointer items-center justify-center gap-2 rounded-[6px] bg-[#FEE500]'
onClick={handleKakaoLogin}
Expand Down
2 changes: 1 addition & 1 deletion src/feature/root/components/ScreenNewRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const ScreenNewRoot = () => {

// 로그인 상태이거나 스플래시 시간이 지난 비로그인 사용자
return (
<div className='bg-background-brand flex min-h-screen flex-col items-center justify-center'>
<div className='bg-background-brand flex h-screen flex-col items-center justify-center'>
<RendingBody />
</div>
);
Expand Down
Loading