일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
- 정렬
- nestjs
- styled-components
- 라이프사이클
- JavaScript
- cookie
- js알고리즘
- aws lightsail
- 슬라이딩윈도우
- 블로그만들기
- never타입
- react
- 투포인터
- Algorithm
- 해쉬
- 알고리즘
- 버블정렬
- 큐
- textarea autosize
- 빅오
- isNaN
- react-query
- 그리디
- TypeScript
- next.js
- tailwindcss
- NextAuth
- typscript
- nextjs
- 스택
- Today
- Total
far
[Javascript] 외부 클릭시 태그 닫기 + 커스텀 셀렉트 본문
번역기를 만들기 위해 커스텀 셀렉트로 언어 선택창을 만들었는데, 두개의 선택창이 동시에 열려있으면 모양새가 조금 이상했다. 그래서 한쪽이 열리면 다른쪽 선택창은 자동으로 닫히도록 만들기 위해 태그 외부를 클릭하면 자동으로 선택창이 닫히도록 만들어봤다.
index.html
<div class="translateSelect__select__container container__lt">
<button class="translateSelect__select__btn select__lt" name="orig_lang">
English
<i class="gg-chevron-down"></i>
</button>
<div class="translateSelect__select__lang lang_lt">
<ul>
<li class="translateSelect__select__lang--option">Korean</li>
<li class="translateSelect__select__lang--option">English</li>
<li class="translateSelect__select__lang--option">Japanese</li>
<li class="translateSelect__select__lang--option">Chinese</li>
<li class="translateSelect__select__lang--option">French</li>
</ul>
<ul>
<li class="translateSelect__select__lang--option">German</li>
<li class="translateSelect__select__lang--option">Italian</li>
<li class="translateSelect__select__lang--option">Portuguese</li>
<li class="translateSelect__select__lang--option">Russian</li>
<li class="translateSelect__select__lang--option">Polish</li>
</ul>
</div>
</div>
...
<div class="translateSelect__select__container container__rt">
<button class="translateSelect__select__btn select__rt" name="target_lang">
Korean
<i class="gg-chevron-down"></i>
</button>
<div class="translateSelect__select__lang lang_rt">
<ul>
<li class="translateSelect__select__lang--option">Korean</li>
<li class="translateSelect__select__lang--option">English</li>
<li class="translateSelect__select__lang--option">Japanese</li>
<li class="translateSelect__select__lang--option">Chinese</li>
<li class="translateSelect__select__lang--option">French</li>
</ul>
<ul>
<li class="translateSelect__select__lang--option">German</li>
<li class="translateSelect__select__lang--option">Italian</li>
<li class="translateSelect__select__lang--option">Portuguese</li>
<li class="translateSelect__select__lang--option">Russian</li>
<li class="translateSelect__select__lang--option">Polish</li>
</ul>
</div>
</div>
간단히 말하면 언어를 선택할 수 있는 부분이 Button이고 li가 select태그의 option값이 된다.
굳이 select태그를 사용하지 않고 커스텀 셀렉트를 사용한 이유는 select태그를 사용하면 option의 디자인이 제한되기 때문이다.
style.css
/* ... */
.translateSelect__select__container {
position: relative;
width: 100%;
height: 40px;
border-radius: 4px;
background-size: 20px;
}
.translateSelect__select__container:after {
content: '';
display: block;
height: 100%;
position: absolute;
top: 0;
right: 35px;
}
.translateSelect__select__container.active {
z-index: 30;
}
.translateSelect__select__btn {
font-size: var(--font-semi);
display: flex;
justify-content: space-between;
align-items: center;
width: inherit;
height: inherit;
border: 1px solid rgb(214, 214, 214);
border-radius: 6px;
outline: none;
padding: 0 0 0 14px;
background: transparent;
cursor: pointer;
}
.translateSelect__select__btn:focus {
outline: none;
}
.translateSelect__select__container.active .translateSelect__select__btn {
border-radius: 6px 6px 0 0;
border: 1px solid rgb(162, 162, 162);
border-bottom: none;
}
.translateSelect__select__btn > span {
padding-left: 5px;
}
.translateSelect__select__lang {
display: flex;
width: 100%;
list-style-type: none;
border-radius: 0 0 6px 6px;
overflow: hidden;
max-height: 0;
}
.translateSelect__select__lang > ul {
width: 110px;
}
.translateSelect__select__container.active .translateSelect__select__lang {
padding: 11px 15px;
max-height: 200px;
height: 180px;
border: 1px solid rgb(162, 162, 162);
border-top: none;
background-color: rgb(255, 255, 255);
z-index: 30;
}
.translateSelect__select__lang--option {
padding: 5px 10px;
cursor: pointer;
}
.translateSelect__select__lang--option:hover {
background-color: #dfe9f3;
border-radius: 5px;
color:rgb(72, 72, 72);
}
커스텀 셀렉트 선택창 디자인
translate.js
const selectLeft = document.querySelector(".select__lt");
const selectRight = document.querySelector(".select__rt");
const label = document.querySelectorAll('.translateSelect__select__btn');
const item = document.querySelector('.translateSelect__select__lang');
// 외부 클릭시 close
window.addEventListener('click', (event) => {
const selectLt = document.querySelector('.container__lt').classList
const selectRt = document.querySelector('.container__rt').classList
if (event.target !== item && event.target !== selectLeft) {
selectLt.remove('active');
}
if (event.target !== item && event.target !== selectRight) {
selectRt.remove('active');
}
})
// custom select
label.forEach((label) => {
label.addEventListener('click', () => {
let optionList = label.nextElementSibling;
let options = optionList.querySelectorAll('.translateSelect__select__lang--option');
clickLabel(label, options);
})
});
const clickLabel = (label, options) => {
if (label.parentNode.classList.contains('active')) {
options.forEach((opt) => {
const func = () => {
handleSelect(label, opt)
}
opt.removeEventListener('click', func)
})
label.parentNode.classList.remove('active');
} else {
label.parentNode.classList.add('active');
options.forEach((opt) => {
const func = () => {
handleSelect(label, opt)
}
opt.addEventListener('click', func)
})
}
};
const handleSelect = (label, opt) => {
let tmp = label.innerText
let newIcon = document.createElement('i');
label.innerText = opt.innerText;
newIcon.setAttribute('class', 'gg-chevron-down');
label.appendChild(newIcon);
label.parentNode.classList.remove('active');
if (selectLeft.innerText === selectRight.innerText) {
if (label.classList.contains('select__lt')) {
selectRight.innerHTML = tmp + `<i class="gg-chevron-down"></i>`
} else {
selectLeft.innerHTML = tmp + `<i class="gg-chevron-down"></i>`
}
}
tmp = ''
};
멀티 커스텀 셀렉트 참고: https://wazacs.tistory.com/34
커스텀 셀렉트
원래는 싱글 커스텀 셀렉트를 두개 만들어서 따로 작성 해줬었는데 기능을 추가할 때 마다 코드를 양쪽에 작성 해줘야 해서 비효율적이라고 생각했다. 그래서 위의 블로그를 참고해 멀티 커스텀 셀렉트의 기본 틀을 짰다.
또, 커스텀 셀렉트는 이벤트가 변경될 때 Button의 하위태그가 전부 제거되기 때문에 아이콘 태그를 다시 등록할 필요가 있었다. 그래서 createElement로 i 태그를 추가하고 setAttribute로 클래스를 추가해줬다.
그리고 양쪽에 같은 언어가 선택 되었을 때 내가 클릭하지 않은 쪽 셀렉트의 옵션을 변경하게 만들었다. 하지만 이 경우 내가 클릭하지 않은 쪽(자동으로 변경되는 쪽) 셀렉트의 아이콘은 element가 추가되지 않지 때문에 사라진다. 그래서 텍스트를 변경할 때 innerHTML을 사용해 태그를 같이 삽입했다.
외부 클릭시 태그 닫기
커스텀 셀렉트를 구현할 때 active를 추가/제거 하는 방식으로 구현을 했기에 active라는 클래스만 제거하면 되는 일이라 쉽게 해결할 수 있었다. 모든 곳을 클릭했을 때 이벤트가 실행이 되어야 하기 때문에 전역에 이벤트를 걸고 select컨테이너가 아닌곳을 클릭하면 active를 제거하는 방법을 사용했다.
'Javascript' 카테고리의 다른 글
[Javascript] textarea Autosize 만들기 (스크롤이 자동으로 올라가는 현상 해결하기) (0) | 2023.04.24 |
---|---|
[Javascript] 연속된 클릭 막기 (0) | 2023.04.06 |
[Javascript] 클로저 (0) | 2023.03.30 |
[Javascript] innerHTML, innerText, textContent 차이 (0) | 2023.03.30 |
[Javascript] 실행 컨텍스트 (0) | 2023.03.26 |