프로그래머스 고양이 사진 검색 사이트 - 개발 (이미지 상세보기 모달)

Z6su3·2022년 4월 20일
0

🐇 Modal close Event

이미지 클릭 시 생성된 모달에서 다음 종료(닫힘) 이벤트를 추가한다. 이미지가 닫힐 때, App에서 image와 visible의 값이 변경되어야 합니다.

  • 키보드의 esc를 누를 때
  • 모달 영역밖을 클릭할 때
  • 우측 상단의 닫기를 클릭할 때

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,
      });
    },
  });

	...
}

🐇 Modal Width

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%;
  }
}

🐇 Fade in/Fade out

Modal 열고 닫기 이벤트에 fade in/out을 적용해야 합니다. 해당 기능을 구현하기 위해선 css의 opacityanimation의 @keyframes에 대해 숙지하고 있어야 합니다. MDN링크를 통해 기본내용을 숙지할 수 있습니다.

  • opacity를 활용해 불투명도 조절
  • animation @keyframes와 opacity를 통해 fadein/fadeout을 구현

위를 참고하여 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();
}
profile
기억은 기록을 이길수 없다

0개의 댓글