- 2020 Dev-matching (프론트엔드) 과정을 복습하며 관련 내용을 학습하는 프로젝트
- 웹페이지는 html을 통해 구성되는 문서
- 의미있는
semantic
구조markup
을 구성하자 - 내가 채울 데이터를 가장 잘 설명하고 나타내는 tag를 선택
- h1이 아닌 span으로 css를 주어 나타내면 non-semantic
시맨틱 태그 목록
태그 | 내용 |
---|---|
header | 헤더 (상단) |
nav | 네비게이션 |
aside | 사이드에 위치한 공간 |
section | 본문의 여러 내용(article)을 포함한 공간 |
article | 본문의 주내용 |
footer | 푸터 (하단) |
h1 | 해당 페이지의 최상위 제목 |
mark | 현재 맥락에서 중요하여 하이라이트할 부분 |
details | 열고 닫을 수 있는 상세 정보 위젯, 자식으로 |
time | 시간의 특정 지점 또는 구간 |
- 디바이스의 화면 가로 크기에 따라, row당 column 개수 조절하기
- 그리드 html은 이중 div 구조
- grid-wrapper에 css를 적용한다
- grid-template-colums : 명시적 컬럼의 크기를 정의. repeat()을 통해 반복할 수 있으며 fr(fraction, 공간비율 단위)를 사용가능
- repeat(n, 1fr)은 n개의 그리드 아이템에 대해 각각 1fr을 적용
- fr은 남은 자유 공간을 총 fr로 분할한 뒤, 각 fr만큼 배정
- 그런데 이 경우 자유 공간 = 전체 공간이므로,
1개 row에 3개 column을 넣겠다
는 단순한 표현에 불과함
- grid-auto-rows: grid-template의 영향을 받지 않는 암시적 행의 크기를 정의.
.grid-wrapper {
display: grid;
grid-auto-rows: 200px;
grid-template-columns: repeat(3, 1fr);
grid-gap: 10px;
}
반응형 그리드 구현
- 디바이스의 width에 따라 컬럼 개수가 달라지는 반응형 그리드
- 오버라이딩은 안되는 듯. 중복 코드를 여러 번 넣어야 한다.
@media all and (max-width: 450px) {
.grid-wrapper {
display: grid;
grid-auto-rows: 200px;
grid-template-columns: repeat(1, 1fr);
grid-gap: 10px;
}
}
@media all and (min-width: 451px) and (max-width: 900px) {
.grid-wrapper {
display: grid;
grid-auto-rows: 200px;
grid-template-columns: repeat(3, 1fr);
grid-gap: 10px;
}
}
@media all and (min-width: 901px) {
.grid-wrapper {
display: grid;
grid-auto-rows: 200px;
grid-template-columns: repeat(4, 1fr);
grid-gap: 10px;
}
}
- 버튼을 누르면 화이트모드/다크모드가 토글되는 버튼 구현
토글 버튼 구현
- 버튼 click event로 클릭 시 로컬 스토리지에서 현재 theme변수값을 가져옴
- 변수값에 따라 적절한 반대값을 body className에 적용
- theme에 따라 폰트컬러와 백그라운드컬러 css 변수를 다르게 정의
- 하위 요소에서 해당 변수를 사용하면 된다.
- localStorage.getItem() / localStorage.setItem()
다크 모드 탐지
- window.matchMedia(미디어쿼리스트링)
- 해당 OS의 선호 모드는 @media (prefers-color-scheme: dark){ ... } 으로 선언
- matches 참/거짓 값을 통해 분기를 태울 수 있다.
const mqList = window.matchMedia("(prefers-color-scheme: dark)");
if (mqList.matches) {
body.className = "dark";
} else {
//3. 선호 모드가 없다면 기본 light 모드
body.className = "light";
}
- 스크롤을 내리면서 이미지가 일정 비율 viewport에 들어왔을 때 loading
- Intersection Observer API를 이용해 구현
- 링크에 매우 자세하게 나와있음
Intersection API
- 어떤 컨테이너릁 탐색할 것인지, 마진은 어떻게 할 것인지, 어떤 비율만큼 교차되었을 때 해당 콜백을 실행할 것인지 등의 options 변수 선언
const options = {
root: null, //null이면 브라우저의 viewport
rootMargin: '0px 0px 30px 0px',
threshold: 0
}
- Intersection Observer 인스턴스 생성
- 콜백함수(탐지되었을 때 실행할 함수)와 options를 인자로 생성
- 이미지를 로딩해야 하므로, data-src attribute에 URL을 넣어놓았다가, src attribute에 할당하는 방법 사용
- 한 번 불러왔다면 더 이상 observe할 필요 없으므로 unobserve
- 해당 콜백은 IntersectionObserverEntry 객체의 배열을 반환하므로 루프를 돌며 해당 객체의 프로퍼티를 활용해야 함
const io = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.src = entry.target.dataset.src;
observer.unobserve(entry.target);
}
})
}, options)
- 관찰할 대상을 등록
- 여러 개의 그리드 이미지를 querySelectorAll로 탐색한 뒤, 루프 돌려서 등록 가능
const images = document.querySelectorAll('.image');
images.forEach((el) => {
io.observe(el);
})
- 이미지를 클릭하면 이미지 / 이름 / url을 담은 모달 띄움
- 모달이 띄워져 있을 때 배경 투명화
- x버튼이나 모달 바깥 부분을 클릭하면 모달이 사라짐
모달 구현
- viewport 전체를 감싸는 모달 컨테이너 (display: none 상태)
- 모달 컨테이너 내의 모달 컨텐츠 (부모가 none이므로 따로 설정할 필요는 없음)
- 이미지를 클릭 시, 해당 이미지의 상태를 모달 컴포넌트에 전달
Q. 모달 컴포넌트에 이미지 상태 전달 시, 모달 컨텐츠는 캐싱된 이미지를 보여주는가? A. 캐싱해서 바로 로딩 가능하다.
모달 오픈 및 클로즈 구현
- 오픈 -> 클릭 이벤트 + img태그의 src 및 data-name 바꿔끼기
- 클로즈 -> 윈도우 객체 keydown 이벤트 + x버튼 click 이벤트 + 모달컨테이너(투명한 부분) click 시 e.target.className 감지하여 modal close 함수 호출
검색
버튼 클릭 시 해당 id의 사진 그리드에 담기랜덤
버튼 클릭 시 20개의 랜덤 사진 그리드에 담기- 검색 중임을 표시하는 UI 집어넣기
- 검색 결과가 없는 경우, 이를 표시하는 UI 집어넣기