이미지 클릭 시 생성된 모달에서 다음 종료(닫힘) 이벤트를 추가한다. 이미지가 닫힐 때, App에서 image와 visible의 값이 변경되어야 합니다.
ImageInfo.js
export default function ImageInfo({ $app, initialState, onBackClick }) {
...
this.onBackClick = onBackClick;
// esc를 누를 때
this.$target.addEventListener("keyup", (e) => {
if (e.keyCode === 27) {
this.onBackClick();
}
});
//모달 영역 밖이나, 우측 상단의 닫기를 누를 때
this.$target.addEventListener("click", (e) => {
const $className = e.target.classList;
if ($className.contains("ImageInfo") || $className.contains("close")) {
this.onBackClick();
}
});
this.render();
}
App.js
...
export default function App($app) {
...
const imageInfo = new ImageInfo({
$app,
initialData: {
visible: false,
image: null,
},
onBackClick: () => {
this.setState({
...this.state,
visible: false,
image: null,
});
},
});
...
}
/cats/:id를 통해 고양이의 성격, 태생정보를 렌더링합니다. 해당정보를 렌더링 하는 방법은 ImageInfo.js에 이미 작성되어 있으므로 정보를 불러오는데 포커싱을 잡습니다.
정보를 불러오기 위해서는 SearchResult.js에서 이미지가 클릭됐을 때, request(/cats/:id)요청을 보내 데이터를 받아 렌더링 해야합니다. 관련한 App.js까지 먼저 처리해줍니다.
기존 컴포넌트 재구성 시 작성된 코드를 수정해 줍니다.
SearchResult.js
...
export default function SearchResult({ $app, initialState }) {
...
this.onClick = onClick;
//이벤트 위임을 통한 최적화
this.$target.addEventListener("click", (e) => {
//고양이 아이템이 클릭되는 경우
const $item = e.target.closest(".item");
if ($item) {
//아이템의 인덱스를 통해, 데이터를 찾고, 데이터의 id값을 추출
const { index } = $item.dataset;
const itemId = this.state.data[parseInt(index, 10)].id;
this.onClick(index ? itemId : null);
}
});
this.render();
LazyLoad();
}
App.js
...
export default function App($app) {
...
const searchResult = new SearchResult({
$app,
initialState: [],
onClick: async (catId) => {
//받은 catId로 고양이 상세정보 요청
const imageData = await request("", catId);
//response에 따라 image에 data할당
this.setState({
...this.state,
visible: true,
image: imageData.data,
});
},
});
...
}
media의 max-size가 768px 이하인경우 모달의 가로길이를 디바이스 가로길이만큼 늘려야 합니다.
해당 처리와 추후 media 쿼리를 위해 style-media.css 파일로 관리해줍니다.
아래와 같이 ImageInfo 의 content-wrapper를 100%로 지정해주면 됩니다. 단, 정상작동하는 코드로 확인되진 않았으므로, 추후 정확한 방법이나 정상작동 테스팅이 되신 경우 연락 부탁드리겠습니다.
index.html에 빼먹지 않고 link를 걸어주도록 합시다.
style-media.css
@media (max-width: 768px) {
.ImageInfo .content-wrapper {
width: 100%;
}
}
Modal 열고 닫기 이벤트에 fade in/out을 적용해야 합니다. 해당 기능을 구현하기 위해선 css의 opacity와 animation의 @keyframes에 대해 숙지하고 있어야 합니다. MDN링크를 통해 기본내용을 숙지할 수 있습니다.
위를 참고하여 css를 통해 구현하는 방법은 어렵지 않습니다. 다음과 같습니다.
style.css
.ImageInfo.fadein {
animation: fadein 1s;
}
.ImageInfo.fadeout {
animation: fadeout 1s;
}
@keyframes fadein {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadeout {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
이제 이미지 모달을 렌더링할 때, ImageInfo class에 fadein class를 추가하고 추후 뒤로가기 행위가 실행될 때 fadein을 제거하고 fadeout을 추가하여 효과를 적용합니다.
단, fadein/out을 설정할 때, 1초 간격을 주었기 때문에 class add, remove가 바로 이루어지면 중구난방한 fadein/out을 경험하게 됩니다.
해당 부정적 경험을 해소하기 위해 setInterval()을 활용하여 opacity가 0이 될 때까지 호출하는 함수를 추가합니다.
ImageInfo.js
export default function ImageInfo({ $app, initialState, onBackClick }) {
...
this.render = () => {
if (this.state.image) {
...
this.$target.classList.add("fadein");
}
this.$target.style.display = this.state.visible ? "block" : "none";
};
this.onBackClick = onBackClick;
const fadeEffect = () => {
//뒤로가기가 클릭됐을 때, fadeout 추가, fadein이 있다면 삭제
this.$target.classList.add("fadeout");
const effect = setInterval(() => {
if (this.$target.classList.contains("fadein")) {
this.$target.classList.remove("fadein");
}
//ImageInfo의 opacity(불투명도)가 0이 될 때까지 반복호출
if (this.$target.style.opacity == 0) {
this.$target.classList.remove("fadeout");
clearInterval(effect);
this.onBackClick();
}
}, 500);
};
this.$target.addEventListener("keyup", (e) => {
if (e.keyCode === 27) {
fadeEffect();
}
});
this.$target.addEventListener("click", (e) => {
const $className = e.target.classList;
if ($className.contains("ImageInfo") || $className.contains("close")) {
fadeEffect();
}
});
this.render();
}