Intro
๊ฐ์๊ฐ ๋ฐ๋ฆฌ๋๊น ๋ถ๋ด์ด ํฌ๋ค...
๊ณผ์ ๋ฅผ ๋จผ์ ํ์ด๋ณผ๊น ํ๋๋ฐ ์๊ฐ๋ณด๋ค ๋ณต์กํด์ ํด์ค ๊ฐ์๋ฅผ ๋ฃ๊ณ ๋ณต์ต์ฐจ ๊ณผ์ ๋ฅผ ํ์ด๋ณด๋ ค ํ๋ค.
์ค๋์ ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ก ๋ชจ๋ฌ ์ ์ดํ๋ ๊ฒ๊ณผ ๋ก์ปฌ ์คํ ๋ฆฌ์ง ์ ์ฅํ๊ณ ๋ถ๋ฌ์ค๊ธฐ, ๋ค์ ํ์ด์ง api ์์ฒญํ๊ธฐ๋ฅผ ์ค์ตํ๋ค.
์ค๋ ํ์ตํ ๋ด์ฉ
: ๋ชจ๋ฌ์ ์ด, ๋ก์ปฌ์คํ ๋ฆฌ์ง, ์คํฌ๋กค๋ค์ํ์ด์ง
๐ฉ๐ป ๋ชจ๋ฌ ์ฐฝ ์ ์ด
์ด๋ฏธ์ง๋ฅผ ๊ฒ์ํ ํ ๊ฒฐ๊ณผ๋ก ์ฃผ์ด์ง ์ด๋ฏธ์ง๋ฅผ ํด๋ฆญํ๋ฉด ๋ชจ๋ฌ์ด ๋จ๋๋ฐ, ๋ชจ๋ฌ ์์ญ ๋ฐ์ ๋๋ฅด๊ฑฐ๋ / ํค๋ณด๋์ ESC ํค๋ฅผ ๋๋ฅด๊ฑฐ๋ / ๋ชจ๋ฌ ์ฐ์ธก์ ๋ซ๊ธฐ(x) ๋ฒํผ์ ๋๋ฅด๋ฉด ๋ซํ๋๋ก ์์ ํด์ผ ํฉ๋๋ค.
๋ซ๊ธฐ ๋ฒํผ ํด๋ฆญ || ๋ชจ๋ฌ ์์ญ ๋ฐ ํด๋ฆญ ์ ๋ชจ๋ฌ ์ฐฝ ๋ซ๊ธฐ
this.$imageInfo.addEventListener('click', (e) => {
if (e.target.className === 'ImageInfo' || e.target.className === 'close') this.closeImageInfo();
});
event.target์ผ๋ก ์ด๋ค ์์๋ฅผ ํด๋ฆญํ๋์ง ํ์ธํ๋ค.
๋ชจ๋ฌ ์ฐฝ ๋ฐ๊นฅ ์์ญ์ className์ด ImageInfo์ธ div ์์์ด๋ฏ๋ก className์ด ImageInfo์ธ ์์๋ฅผ ํด๋ฆญํ์ ๊ฒฝ์ฐ์ ๋๋ close๋ผ๋ ํด๋์ค ๋ค์์ ๊ฐ์ง๊ณ ์๋ ๋ฒํผ ์์๋ฅผ ํด๋ฆญํ์ ๊ฒฝ์ฐ์ ์ฐฝ์ ๋ซ๋ ์ด๋ฒคํธ๋ฅผ ์์ฑํ๋ฉด ๋๋ค.
ํค๋ณด๋์ ESC ํค๋ฅผ ๋๋ฌ ๋ชจ๋ฌ์ฐฝ ๋ซ๊ธฐ
: Escape ํค๊ฐ ๋๋ ธ๋ค๋ ์ด๋ฒคํธ๊ฐ ๊ฐ์ง๋์์ ๋, ๋์์ ์คํํ๋ ์ฝ๋๋ฅผ ์์ฑํด์ผ ํ๋ค.
KeyboardEvent๋ก ํด๊ฒฐํ ์ ์๋ค. KeyboardEvent์ ์ ํ์ผ๋ก๋ keypress, keyup, keydown์ด ์๋ค.
keypress, keyup, keydown ์ฐจ์ด
โ ๏ธ keypress๋ ๋ ์ด์ ๊ถ์ฅ๋์ง ์๋ ๊ธฐ๋ฅ์ด๋ค. ์ฌ์ฉํ์ง ๋ง ๊ฒ
keypress๋ ๋ฌธ์ ๊ฐ์ ์์ฑํ๋ ํค๋ฅผ ๋๋ฅด๋ฉด ์ด๋ฒคํธ๊ฐ ์์๋๋ค. ๊ทธ๋ฌ๋, ํ๊ธ ์ ๋ ฅ ์์๋ ์ ์ ๋์ํ์ง ์๋ ๋ฌธ์ ๊ฐ ์๋ค. ๊ทธ๋ฆฌ๊ณ Escape ์ ๋ ฅ๋ ๋ฐ์ ์ ์๋ ๋จ์ ์ด ์๋ค.
keydown์ ํค๋ฅผ ๋๋ฅผ ๋ ์ด๋ฒคํธ๊ฐ ์์๋๋ค. keypress์ ๋์ํ๋ ๊ฒ ๊ฐ์ง๋ง ๋ฌธ์ ๊ฐ ์์ฑ ์ฌ๋ถ์ ๊ด๊ณ ์์ด ๋ชจ๋ ํค์ ๋ํด ์ด๋ฒคํธ๊ฐ ์์๋๋ ์ฐจ์ด์ ์ด ์๋ค.
๋ฐ๋๋ก, keyup์ ํค๋ฅผ ๋์ ๋ ๋ฐ์ํ๋ ์ด๋ฒคํธ์ด๋ค.
๋ค์์ esc ํค๋ฅผ ๋๋ ์ ๋, ์ด๋ฒคํธ๋ฅผ ๋ฐ์์ํค๊ณ ์ ํ ๋ ์์ฑํ๋ ์ฝ๋ ์์์ด๋ค.
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') this.closeImageInfo();
});
e.key๋ฅผ ํตํด ์ด๋ค ํค๊ฐ ๋๋ ธ๋ ์ง ํ์ ํ๊ณ ์กฐ๊ฑด๋ฌธ์ ์ฌ์ฉํด ์ปจํธ๋กคํ ์ ์๋ค.
keyboardEvent ์ธ์คํด์ค ์์ฑ
- KeyboardEvent.code
์ด๋ฒคํธ๊ฐ ๋ํ๋ด๋ ๋ฌผ๋ฆฌ์ ํค์ ์ฝ๋ ๊ฐ์ ๋ฌธ์์ด๋ก ๋ฐํ
ex) KeyA, KeyB, Digit1, Digit2, Backquote, Backspace, Enter, ControlLeft...
- KeyboardEvent.key
์ด๋ฒคํธ๊ฐ ๋ํ๋ด๋ ํค์ ํค ๊ฐ์ ๋ํ๋ด๋ ๋ฌธ์์ด์ ๋ฐํ
ex) a, b, 1, 2, `, Backspace, Enter, Control...
๐ฉ๐ป ๋ก์ปฌ์คํ ๋ฆฌ์ง ๋ค๋ฃจ๊ธฐ
์ต๊ทผ ๊ฒ์ํ ํค์๋๋ฅผ SearchInput ์๋์ ํ์๋๋๋ก ๋ง๋ค๊ณ , ํด๋น ์์ญ์ ํ์๋ ํน์ ํค์๋๋ฅผ ๋๋ฅด๋ฉด ๊ทธ ํค์๋๋ก ๊ฒ์์ด ์ผ์ด๋๋๋ก ๋ง๋ญ๋๋ค. ๋จ, ๊ฐ์ฅ ์ต๊ทผ์ ๊ฒ์ํ 5๊ฐ์ ํค์๋๋ง ๋ ธ์ถ๋๋๋ก ํฉ๋๋ค.
ํ์ด์ง๋ฅผ ์๋ก๊ณ ์นจํด๋ ๋ง์ง๋ง ๊ฒ์ ๊ฒฐ๊ณผ ํ๋ฉด์ด ์ ์ง๋๋๋ก ์ฒ๋ฆฌํฉ๋๋ค.
localStorage
๋ก์ปฌ ์คํ ๋ฆฌ์ง(localStorage)๋ฅผ ํตํด ๋ก์ปฌ ์ ์ฅ์์ ์ ๊ทผํ ์ ์๋ค. ๋ก์ปฌ ์คํ ๋ฆฌ์ง๋ ํ์ด์ง๋ ๋ธ๋ผ์ฐ์ ๋ฅผ ๋ซ์๋ ๋ด์ฉ์ ๊ธฐ์ตํ๋ ์ ์ฅ์๋ฅผ ์๋ฏธํ๋ค.
sessionStorage์ ๋น์ทํ๋ ์ธ์ ์คํ ๋ฆฌ์ง๋ ํ์ด์ง ์ธ์ ์ด ๋๋ ๋, ์ฆ ํ์ด์ง๋ฅผ ๋ซ์ ๋ ์ฌ๋ผ์ง๋ ์ ์ด ๋ค๋ฅด๋ค.
๋ก์ปฌ ์คํ ๋ฆฌ์ง๋ key / value ํํ๋ก ์ ์ฅ๋๋ฉฐ, value๋ ํญ์ ๋ฌธ์์ด์ด๋ค.
๋ฐ๋ผ์ ๋ฐฐ์ด์ด๋ ๊ฐ์ฒด๋ฅผ ์ ์ฅํ ๊ฒฝ์ฐ์ JSON ํ์ฑํ๊ฑฐ๋ split์ ํตํด ๋ฌธ์์ด์ ๋ฐฐ์ด๋ก ๋ฐ๊พธ๋ ๊ณผ์ ์ด ํ์ํ๋ค.
- ๋ก์ปฌ ์คํ ๋ฆฌ์ง ๊ฐ ์ถ๊ฐํ๊ธฐ
localStorage.setItem('lastResult', JSON.stringify(result))
- ๋ก์ปฌ ์คํ ๋ฆฌ์ง ๊ฐ ์ฝ์ด์ค๊ธฐ
JSON.parse(localStorage.getItem('lastResult'))
- ๋ก์ปฌ ์คํ ๋ฆฌ์ง ๊ฐ ์ญ์ ํ๊ธฐ
localStorage.removeItem('lastResult')
๐ฉ๐ป ์คํฌ๋กค & ๋ค์ ํ์ด์ง ๋ก๋ฉ
๊ฒ์ ๊ฒฐ๊ณผ ํ๋ฉด์์ ์ ์ ๊ฐ ๋ธ๋ผ์ฐ์ ์คํฌ๋กค ๋ฐ๋ฅผ ๋๊น์ง ์ด๋์์ผฐ์ ๊ฒฝ์ฐ, ๊ทธ ๋ค์ ํ์ด์ง๋ฅผ ๋ก๋ฉํ๋๋ก ๋ง๋ค์ด์ผ ํฉ๋๋ค.
๊ณผ์ API ๋ช ์ธ์ page์ ๋ํ ๋ด์ฉ์ด ์์ต๋๋ค. ์๋์ฒ๋ผ ์์ ์ ํด์ฃผ์ธ์- /cats/search?page=1, /cats/search?page=2์ฒ๋ผ ์๋ํฌ์ธํธ์ page๋ผ๋ ํ๋ผ๋ฏธํฐ๋ฅผ ์ถ๊ฐํด ์ฃผ์ธ์- 1ํ์ด์ง๋ ํ๋ผ๋ฏธํฐ๋ฅผ ๋ถ์ผ ํ์ ์์ต๋๋ค.- page ํ๋ผ๋ฏธํฐ๋ฅผ ์ ์ดํ๋ ์ํ๋ฅผ ์ถ๊ฐํ์ฌ ์์ฑํด ๋ณด์ธ์.
getBoundingClientRect()
: ์๋ฆฌ๋จผํธ์ ํฌ๊ธฐ์ ๋ทฐํฌํธ์ ์๋์ ์ธ ์์น ์ ๋ณด๋ฅผ ์ ๊ณตํ๋ DOMRect ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ค.
top, bottom, left, right ๊ฐ์ ๋ฝ์๋ผ ์ ์๋ค.
๋ฐ์ดํฐ ์์ด ๋ง์์ง๋ฉด ๊ณ์ ๊ฐ์ ๋ฐํํ๊ธฐ ๋๋ฌธ์ ๋ฉ๋ชจ๋ฆฌ ๋์ ์์ธ์ด ๋ ์ ์๋ค.
IntersectionObserver
์ ํํ ์์์ ๋ทฐํฌํธ๋ฅผ ๊ด์ฐฐํด ํด๋น ์์๊ฐ ํ๋ฉด์ ๋ณด์ด๋ ์ง ์๋ณด์ด๋ ์ง ํ๋จํ๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค.
์ํ๋ ์ด๋ฏธ์ง๊ฐ ๋ณด์ผ ๊ฒฝ์ฐ (๋ง์ง๋ง ์์ ์ด๋ฏธ์ง) ๋ค์ ํ์ด์ง๋ฅผ ๋ก๋ฉํ๋ ๋์์ ๊ตฌํํ๋ฉด ์คํฌ๋กค ๋ฐ๋ฅผ ๋๊น์ง ์ด๋์์ผฐ์ ๊ฒฝ์ฐ, ๊ทธ ๋ค์ ํ์ด์ง๋ฅผ ๋ก๋ฉํ๋๋ก ๋ง๋ค ์ ์๋ค.
if (item.isIntersecting) {
let dataIndex = Number(item.target.dataset.index);
// ๋ง์ง๋ง ์์์ธ์ง ์ฒดํฌ
if (dataIndex === this.data.length - 1) {
this.onNextPage();
}
}
lazy loading
์ด๋ฏธ์ง๊ฐ ๋ก๋ฉ๋๊ธฐ ์ ์ ๋๋ฏธ ์ด๋ฏธ์ง๋ฅผ ํตํด ํ๋ฉด ์์๋ค์ ๋ฐฐ์นํ ํ, ๋ก๋ฉ์ด ์๋ฃ๋ ํ์ ๋๋ฏธ ์ด๋ฏธ์ง๋ฅผ ๋ก๋ฉ๋ ์ด๋ฏธ์ง๋ก ๋ฐ๊พธ๋ฉด UI๊ฐ ์์ฐ์ค๋ฌ์์ง๋ค.
`<li class="item" data-index=${idx}>
<img src="https://via.placeholder.com/200x300" data-src=${cat.url} alt=${cat.name} />
</li>`
์์์ dataset ๊ธฐ๋ฅ์ ํ์ฉํ๋ค.
img์ src ์์ฑ์ ์ฒ์์ ๋๋ฏธ ๋ฐ์ดํฐ๋ก ๋ฃ๊ณ ๋ก๋ฉ์ด ์๋ฃ๋ ํ dataset์ src๋ฅผ ๋ถ๋ฌ์ img์ src ์์ฑ ๊ฐ์ผ๋ก ๋ณ๊ฒฝํด์ฃผ๋ฉด ๋๋ค.
const $img = item.target.querySelector('img');
$img.src = $img.dataset.src;
๋ง๋ฌด๋ฆฌ
์ค๋ ๋ฐฐ์ด ๋ด์ฉ๋ค์ ์ค์ ๊ฐ๋ฐ์์ ์์ฃผ ๋ณด์ด๊ณ ์ฐ์ด๋ ์ฝ๋๋ค์ ํ์ตํ ๊ฒ ๊ฐ์์ ์ ์ตํ๋ค.
๊ณผ์ฐ ๋ด๊ฐ ํผ์ ์ง ๋ค๊ณ ํ๋ฉด ์ ํ ์ ์์๊น? ์์ง ๊ณผ์ ๋ฅผ ์ํ์ด๋ด์ ๋ชจ๋ฅด๊ฒ ๋ค. ใ ใ ์ ์ ์ฝ๋๊ฐ ๋ง์์ง๋ ํท๊ฐ๋ฆฌ๋ ๋ถ๋ถ๋ ๋ง์ด ์๊ธด๋ค.
๋ณต์ต์ด ๊ผญ ํ์ํ ๋ถ๋ถ์ธ ๊ฒ ๊ฐ๋ค.
๊ฐ์๊ฐ ์ฝ๊ฐ ๋ฐ๋ ค์๋๋ฐ ๋ณต์ต๋ ํ๋ ค๋ฉด... ์ฃผ๋ง์๋ ์ด์ฌํ ๋ค์ด์ ์ด๋ฒ ์ฃผ ๊ฐ์๋ ๋ค ๋๋ผ ์ ์๋๋ก ํด์ผ๊ฒ ๋ค.
