Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] 차량 간단정보 구현 #56

Merged
merged 5 commits into from
Aug 1, 2024
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 added public/carimg1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/carimg2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/carimg3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/carimg4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/carimg5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 2 additions & 5 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useEffect } from "react";
import IntroSection from "./introSection";
import Header from "./header";
import SimpleInformation from "./simpleInformation";
import QnA from "./qna";
import Footer from "./footer";

Expand All @@ -14,11 +15,7 @@ function App() {
<>
<IntroSection />
<Header />
<img
src="https://image.utoimage.com/preview/cp872655/2018/03/201803016775_500.jpg"
className="h-[2000px]"
alt="test image"
/>
<SimpleInformation />
<QnA />
<Footer />
</>
Expand Down
34 changes: 34 additions & 0 deletions src/simpleInformation/contentList.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"content": [
{
"src": "carimg1.png",
"title": "독창적인 디자인",
"desc": ["아이오닉 브랜드를 상징하는\n", "파라메트릭 픽셀 램프 디자인", "으로\n유니크한 이미지를 구현합니다."],
"sub": "프로젝션 타입과 MFR 타입 중 선택 가능"
},
{
"src": "carimg2.png",
"title": "전용 전기차 플랫폼(E-GMP)",
"desc": ["", "새로워진 전기차 플랫폼 E-GMP", "는\n알루미늄 압출재를 이용해\n구조적 안정성을 높였습니다."],
"sub": "연출된 이미지로 실제 작동 사양과 다를 수 있음"
},
{
"src": "carimg3.png",
"title": "인터랙티브 픽셀 라이트",
"desc": ["", "신규 디자인의 스티어링 휠", "로\n아이오닉5만의 차별화된\n주행 경험을 제공합니다."],
"sub": "시동 / 충전 중 / 후진 중 표시 가능"
},
{
"src": "carimg4.png",
"title": "증강현실 내비게이션",
"desc": ["주행에 필요한 각종 정보들을\n", "증강현실 기술", "을 통해\n직관적으로 제공합니다."],
"sub": "인포테인먼트 시스템 화면 이미지는 업데이트에 따라 변동 가능"
},
{
"src": "carimg5.png",
"title": "디지털 사이드 미러",
"desc": ["보다 슬림해진 ", "디지털 사이드 미러", "는\n카메라와 ", "OLED 모니터", "를 통해\n선명한 후방 시야를 제공합니다."],
"sub": "모니터는 내부 운전석에 위치"
}
]
}
72 changes: 72 additions & 0 deletions src/simpleInformation/contentSection.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { useEffect, useRef, useState } from "react";
import style from "./contentSection.module.css";

export default function ContentSection({ content }) {
const contentRef = useRef(null);
const [isVisible, setIsVisible] = useState(false);
const [isHighlighted, setIsHighlighted] = useState(false);

useEffect(() => {
const contentDOM = contentRef.current;
Copy link
Collaborator

Choose a reason for hiding this comment

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

반복되는 부분을 별도의 변수로 떼신 것 아주 좋습니다.

Copy link
Collaborator Author

@darkdulgi darkdulgi Aug 1, 2024

Choose a reason for hiding this comment

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

이렇게 안해주면 warning을 발생시킵니다. useRef로 선언한 개체는 current 속성을 통해 접근해야 하는데 여기서 current는 가변성이고 cleanup 함수가 실행되는 순간에는 화면이 업데이트 되고 난 뒤기 때문에 current의 값은 null로 세팅된다는 잠재적 문제가 있습니다.
https://velog.io/@cjhlsb/kencland

const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setIsVisible(true);
observer.unobserve(entry.target); // 애니메이션 실행 후 옵저버 중지
}
});
},
{
threshold: 0.8, // 80%가 보일 때 실행
},
);

if (contentDOM) {
observer.observe(contentDOM);
}

// 클린업 함수
return () => {
if (contentDOM) {
observer.unobserve(contentDOM);
}
};
}, []);

const highlightDynamicStyle = {
"--progress": isHighlighted ? "100%" : "0%",
};

return (
<div
ref={contentRef}
onAnimationEnd={() => setIsHighlighted(true)}
className={`${isVisible ? style.fadeIn : "opacity-0"} z-0 flex flex-col font-bold`}
>
<img src={content.src} className="w-full" />

<span className="pt-10 text-body-l text-neutral-800">
{content.title}
</span>

<div className="pt-3 flex justify-between items-end">
<div>
{content.desc.map((str, index) => (
<span
key={index}
Copy link
Collaborator

Choose a reason for hiding this comment

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

사실 key에 index를 쓰는 건 썩 좋은 습관은 아닙니다. 왜냐하면 index를 key로 쓴 상태에서 배열이 바뀌거나 하면 리액트에서 뭐가 제대로 삭제 안 되거나 하는 버그가 생길 수 있어요.
(리액트 내부에서 key를 지정 안 하면 index를 key로 쓰긴 합니다. 그러지 말라고 오류 뱉는 거지만요.)

자세한 건 다음 링크를 참조하세요.
https://ko.react.dev/learn/rendering-lists#why-does-react-need-keys

style={highlightDynamicStyle}
className={`${index % 2 ? style.highlightAnim : "text-neutral-800"} text-title-m whitespace-pre-wrap`}
>
{str}
</span>
))}
</div>

<span className="absoulte top-0 right-0 text-body-s text-neutral-300">
{content.sub}
</span>
</div>
</div>
);
}
30 changes: 30 additions & 0 deletions src/simpleInformation/contentSection.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
.fadeIn {
animation: fade-in 0.4s ease-out forwards;
}

@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}

.highlightAnim {
display: inline-block;
position: relative;
}

.highlightAnim::before {
content: "";
position: absolute;
display: block;
width: 100%;
height: 1.4em;
background: linear-gradient(90deg, #3ED7BE, #069AF8);
opacity: 0.3;
z-index: -1;
clip-path: polygon(0 0, 0 100%, var(--progress, 0) 100%, var(--progress, 0) 0);
transition: clip-path 0.4s;
}
22 changes: 22 additions & 0 deletions src/simpleInformation/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import JSONData from "./contentList.json";
import ContentSection from "./contentSection";

export default function SimpleInformation() {
const contentList = JSONData.content;
Copy link
Collaborator

Choose a reason for hiding this comment

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

good


return (
<div className="h-[4700px] flex justify-center ">
<div className="w-[1200px] flex flex-col gap-[160px]">
<div className="flex flex-col text-black font-bold pt-[240px]">
<span className="text-title-m">내가 선택한 단 하나의 전기차</span>

<span className="text-head-l">The new IONIQ 5</span>
</div>

{contentList.map((content, index) => (
<ContentSection key={index} content={content} />
))}
</div>
</div>
);
}