[ํด๋ผ์ฐ๋ฉ ์ดํ๋ฆฌ์ผ์ด์ ์์ง๋์ด๋ง TIL] 240123 - ๊ณ ์์ด ์ฌ์ง ๊ฒ์ ์ค์ต 6
Intro
์ถ๊ฐ ์๊ตฌ ์ฌํญ ํด๊ฒฐ ํ๊ธฐ์ ๋ํ ๊ฐ์๋ฅผ ๋ค์๋ค.
์ ๋ ๋ชจ์ํ ์คํธ์์ ํ์๋ ๋ง์ฐ์ค ์ค๋ฒ ๋ฌธ์ ๋ฅผ ๋๋ ์ด๋ฒคํธ ํจ๋ค๋ฌ๋ก ๊ตฌํํ๋๋ฐ ๊ฐ์ฌ๋์ CSS๋ก ํจ์ฌ ๊ฐ๋จํ๊ฒ ๊ตฌํํ๋ค.
์ค๋ ํ์ตํ ๋ด์ฉ
๐ฉ๐ป ๊ฒ์์ด ํ์คํ ๋ฆฌ๋ฅผ ์จ๊ฒผ๋ค๊ฐ ์ธํ์ ๋ง์ฐ์ค๊ฐ ์ค๋ฒ๋ ๋ ๋ณด์ฌ์ฃผ๋๋ก UI๋ฅผ ๋ฐ๊ฟ์ฃผ์ธ์.
- style.css
.SearchResult .item .content {
position: absolute;
top: 50%;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.3);
color: #fff;
text-align: center;
font-size: 30px;
opacity: 0;
transition: all 0.5s ease;
}
.SearchResult .item .content:hover {
opacity: 1;
}
์์๋ฅผ ํฌ๋ช ํ๊ฒ ๋ง๋๋ opacity ์์ฑ์ ์ด์ฉํด ์ฒ์์ ํฌ๋ช ์ด์๋ค๊ฐ ๋ง์ฐ์ค๊ฐ ์ค๋ฒ๋์์ ๋ opacity๋ฅผ 1๋ก ๋ฐ๊ฟ ํ๋ฉด์ ๋ ธ์ถ๋๊ฒ ํ๋ ๋ฐฉ๋ฒ์ด๋ค.
์์ฐ์ค๋ฝ๊ฒ ๋ ธ์ถ๋๊ธฐ ์ํด transition ์์ฑ๋ ์ ์ฉํ์๋ค.
์ด ๋ฐฉ๋ฒ์ ์ฌ์ฉํ๋ ค๋ฉด position: absolute; ๋ฅผ ํ์ฉํด์ ์์น๋ฅผ ์ ์ ํ ๋ฐฐ์นํด์ฃผ์ด์ผ ํ๋ค.
JS์์ mouseover/out ์ด๋ฒคํธ๋ฅผ ์ ์ฉํ๋ ๊ฒ๋ณด๋ค ํจ์ฌ ๊ตฌํ์ด ๊ฐ๋จํ๊ณ ๋ช ํํ๋ค.
์ ๋ TIL์์ ๋ดค๋ฏ์ด mouseover/out ์ด๋ฒคํธ๋ฅผ ์ ์ฉํ๋ฉด ์ฝ๋๋ ํจ์ฌ ๊ธธ์ด์ง๊ณ ์ง๊ด์ ์ด์ง ์์ ์ฝ๋ ๋ณด๊ธฐ ๋ถํธํ๋ค.
CSS๋ก ์ค์ ํ ์ ์๋ ๊ฑด ๋จผ์ CSS๋ฅผ ํ์ฉํ๋ ์ต๊ด์ ๋ค์ฌ์ผ๊ฒ ๋ค.
๐ฉ๐ป ๋ชจ๋ฌ ์ด๊ณ ๋ซ๊ธฐ์ fade in/out์ ์ ์ฉํด ์ฃผ์ธ์.
- style.css
.ImageInfo {
position: fixed;
left: 0;
top: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.5);
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
}
.ImageInfo.show {
visibility: visible;
opacity: 1;
}
- ImageInfo.js
setState(nextData) {
this.data = nextData;
this.render();
this.setFade(nextData.visible);
}
setFade(visible) {
if (visible) this.$imageInfo.classList.add('show');
else this.$imageInfo.classList.remove('show');
}
๐ฉ๐ป ๊ฒ์ ๊ฒฐ๊ณผ ๊ฐ ์์ดํ ์ ๋ง์ฐ์ค ์ค๋ฒ์ ๊ณ ์์ด ์ด๋ฆ์ ๋ ธ์ถํฉ๋๋ค.
์ด์ ํ์๋ ๋ฌธ์ !!
CSS์ :hover ์์ฑ์ผ๋ก ์์ฃผ ๊ฐ๋จํ๊ฒ ๋ค์ ๊ตฌํํ๋ค.
์์ ๊ฒ์์ด ํ์คํ ๋ฆฌ๋ฅผ ์จ๊ฒผ๋ค๊ฐ ์ธํ์ ๋ง์ฐ์ค๊ฐ ์ค๋ฒ๋ ๋ ๋ณด์ฌ์ฃผ๋๋ก UI๋ฅผ ๊ตฌํํ๋ ๋ฌธ์ ์์ ํด๊ฒฐํ ๋ฐฉ๋ฒ๋๋ก ์งํํ๋ค.
.SearchResult .item {
position: relative;
background-color: #eee;
display: inline-block;
margin: 0 0 1em;
width: 100%;
cursor: pointer;
}
.SearchResult .item .content {
position: absolute;
top: 50%;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.3);
color: #fff;
text-align: center;
font-size: 30px;
opacity: 0;
transition: all 0.5s ease;
}
.SearchResult .item .content:hover {
opacity: 1;
}
content ํด๋์ค๋ฅผ ๊ฐ์ง div ์์์ ๊ณ ์์ด ์ด๋ฆ์ ํํํ์๋๋ฐ width, height๋ฅผ 100%๋ก ์ค์ ํ ๋ค top: 50%์ผ๋ก ์ง์ ํ๋ฉด ์ด๋ฏธ์ง ๊ฐ์ด๋ฐ์ ๊ธ์๊ฐ ์ฌ ์ ์๋ค.
๐ฉ๐ป ์ต๋ ๊ฒ์ ๊ฒฐ๊ณผ ๊ฐฏ์๋ฅผ ์ค์ ํ ์ ์๋ select UI๋ฅผ ์ ๊ณตํด ์ฃผ์ธ์.
option์ 10๊ฐ, 25๊ฐ, 50๊ฐ ์ ๋๋ค. (api๋ 50๋ฅผ ๊ธฐ๋ณธ์ผ๋ก ์ง์ํ๋ฏ๋ก BE ์์ ํ์)
select ์์๋ฅผ ์ถ๊ฐํ์ฌ option ๊ฐ์ ๋ฐ๋ผ data๋ฅผ ๊ฐ์ ธ์ค๋ ๊ธฐ๋ฅ์ ๊ตฌํํ๋ค.
- api.js
const api = {
...
fetchCatsWithLimit: (keyword, limit) => {
return request(`${API_ENDPOINT}/api/cats/search?q=${keyword}&limit=${limit}`);
},
...
}
- SearchInput.js
...
// select UI
const $limitCount = document.createElement('select');
this.$limitCount = $limitCount;
this.$limitCount.classList = 'LimitCount';
$wrapper.appendChild(this.$limitCount);
const LimitCountOptions = [10, 25, 50];
LimitCountOptions.map((option) => {
let $option = document.createElement('option');
$option.value = option;
$option.textContent = `${option}๊ฐ`;
$limitCount.appendChild($option);
});
...
onSearch์ ๋งค๊ฐ๋ณ์๋ฅผ ๊ธฐ์กด์ keyword ํ๋๋ง ๋ฐ์๋๋ฐ limit์ ์ถ๊ฐ๋ก ๋ฐ์ api๋ฅผ ํธ์ถํ๋๋ก ๊ตฌํํ๋ค.
๐ฉ๐ป ๋๋ค ๊ณ ์์ด ๋ฐฐ๋ ์น์ ์ถ๊ฐํ์ฌ ๊ฒ์ ๊ฒฐ๊ณผ ๋ชฉ๋ก ์์ ๋ฐฐ๋ ํํ์ ๋๋ค ๊ณ ์์ด ์น์ ์ ์ถ๊ฐํฉ๋๋ค.
์ฑ์ด ๊ตฌ๋๋ ๋ /api/cats/random50 api๋ฅผ ์์ฒญํ์ฌ ๋ฐ๋ ๊ฒฐ๊ณผ๋ฅผ ๋ณ๋์ ์น์ ์ ๋ ธ์ถํฉ๋๋ค.
๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ๋ง๋๋ผ๋ ํ๋ฉด์ 5๊ฐ๋ง ๋ ธ์ถํ๋ฉฐ ๊ฐ ์ด๋ฏธ์ง๋ ์ข, ์ฐ ์ฌ๋ผ์ด๋ ์ด๋ ๋ฒํผ์ ๊ฐ์ต๋๋ค.
์ข, ์ฐ ๋ฒํผ์ ํด๋ฆญํ๋ฉด, ํ์ฌ ๋ ธ์ถ๋ ์ด๋ฏธ์ง๋ ์ฌ๋ผ์ง๊ณ ์ด์ ๋๋ ๋ค์ ์ด๋ฏธ์ง๋ฅผ ๋ณด์ฌ์ค๋๋ค. (ํธ๋ ์ง์ ์ ์ ํ)
๋ฐฐ๋๋ฅผ ๋ง๋ค๊ธฐ ์ํด์๋ CSS ์คํฌ(?)์ด ํ์ํ๋ค.
Banner.js ์ฝ๋์์ ๊ตฌํํด์ผ ํ ๊ฒ์..
1. banner ์์์ width ํฌ๊ธฐ๋ฅผ (๋ฐฐ๋ ํฌ๊ธฐ * ๋ฐฐ๋์ ๋ณด์ฌ ์ค ์ด๋ฏธ์ง ๊ฐฏ์)๋ก ์ง์ ํด์ค์ผ ํ๋ค.
this.$banner.style.width = Number(this.$wrapper.clientWidth) * this.data.length + 'px';
this.$banner.querySelectorAll('li').forEach((item) => {
item.style.width = this.$wrapper.clientWidth + 'px';
});
2. css์์ overflow: hidden ์ฒ๋ฆฌ๋ฅผ ํด์ค์ผ ํจ + float: left๋ก ๋ฐฐ๋ ์ด๋ฏธ์ง์ธ li ์์๋ค์ ์ผ์ชฝ์์ ์ค๋ฅธ์ชฝ์ผ๋ก ๋ถ์ฌ์ค๋ค.
.Banner {
width: 100%;
height: 300px;
overflow: hidden;
padding: 0;
margin: 0;
position: relative;
}
.Banner ul {
margin: 0;
padding: 0;
position: absolute;
transition: all 0.3s ease;
}
.Banner ul li {
float: left;
margin: 0;
padding: 0;
list-style: none;
height: 300px;
background-position: 50% 50%;
background-size: cover;
}
.Banner button {
position: absolute;
top: 50%;
z-index: 10;
cursor: pointer;
}
.Banner .prevBtn {
left: 10px;
}
.Banner .nextBtn {
right: 10px;
}
3. prev/next button ์์๋ฅผ ํด๋ฆญํ์ ๋ ๋ฐฐ๋ ์ด๋ฏธ์ง๊ฐ ๋์ด๊ฐ๋ ๊ธฐ๋ฅ ๊ตฌํํ๊ธฐ
changeCurrent(index) {
this.current = index;
this.moveTo(index);
}
moveTo(index) {
let leftPosition = -Number(this.$wrapper.clientWidth) * index;
this.$banner.style.left = leftPosition + 'px';
}
this.$prevButton.addEventListener('click', (e) => {
let prev = this.current - 1;
if (prev === -1) return;
this.changeCurrent(prev);
});
this.$nextButton.addEventListener('click', (e) => {
let next = this.current + 1;
if (next === this.data.length) return;
this.changeCurrent(next);
});
๋ง๋ฌด๋ฆฌ
ํฌ๊ฒ ์ด๋ ค์ด ๋ด์ฉ์ ์์์ง๋ง ํ๋ค๋ณด๋ฉด CSS๊ฐ ์คํ๋ ค ๋ณต์กํด์ง ๊ฒ ๊ฐ์ ๋๋์ด๋ค...
๋ฐฐ๋ UI๋ ์นํ์ด์ง ๊ฐ๋ฐ์ ๊ฑฐ์ ํ์์ธ๋ฐ ๋ค์ํ ๋ฐฉ๋ฒ์ผ๋ก ๊ตฌํํ๋ ๊ฒ์ ๋ ๊ณต๋ถํด๋ด์ผ๊ฒ ๋ค.
๊ณต๋ถํ ๊ฒ ๋๋ฌด ๋๋ฌด ๋๋ฌด ๋ง๋ค.
