티스토리 뷰
이번에 업무를 보면서 퍼블리셔분을 도와줄 일이 있었습니다.
문제는 input으로 연 파일 창(file dialog)을 열면 배경이 dimmed 처리 되고
아래 사진과 같이 파일을 선택할 때 말고 취소 버튼을 눌렀을때를 감지해
dimmed처리를 해제해야하는 상황이였습니다.
리액트로 프로젝트를 진행하고 있어서 취소 이벤트를 감지해 상태변화로 dimmed처리를
관리하면 될것 같아 쉬워 보였고, 열기를 통해 파일을 넣었을 때엔 파일 상태를 관리하는
state의 변화를 감지하거나 input의 onChange를 통해서 처리하면 되겠다고 판단했습니다.
파일 열기는 쉬웠지만 취소상태를 감지하기에는 어려움이 있었습니다.
인터넷에 검색을 통해 알게된 것은 window의 포커스를 감지해 취소인지를
판별하는 방법이 보편적이라는 것을 알게되었습니다.
< Vanilla js >
여러 방법들을 확인했을 때, 취소 상태에 따라
dimmed 처리를 다음과 같이 해결했습니다.
1. input을 클릭 한 것을 label을 클릭했을때 openDialog에 true를 저장합니다.
2. window에 이벤트를 줘서 파일창이 켜지고 취소를 눌렀을때
window의 focus 이벤트가 발생하고 openDialog가 true 이면
close_action 함수를 실행해 dimmed처리 된 배경의 클래스를 조정합니다.
<style>
* { margin:0; padding:0; outline:0; box-sizing:border-box; }
html, body { width:100%; height:100%; }
.wrap { width:100%; height:100%; }
.wrap .bg {
display:none; width:100%; height:100%;
position:fixed; top:0; left:0; background:rgba(0,0,0,0.45);
}
.wrap .bg.active { display:block; }
label {
font-weight:bold; cursor:pointer; padding:10px 20px; border:2px solid #000000;
position:absolute; left:50%; top:50%;
transform:translate(-50%,-50%); border-radius:25px;
}
label input { display:none; }
</style>
<div class="wrap">
<div class="bg"></div>
<label for="fileTest">
파일
<input id="fileTest" type="file"/>
</label>
</div>
<script type="text/javascript">
const bgEl = document.querySelector(".wrap .bg");
const labelEl = document.querySelector("label");
const inputEl = document.querySelector("input");
let file = {}; // 파일 객체 담는 변수
let timeSet = null; // setTimeout 담는 변수
let openDialog = false; // 창이 켜졌는지 판별하는 변수
function open_action(){
/* file dialog 켜졌을때 동작 함수 */
openDialog = true;
bgEl.classList.add("active");
}
function close_action(){
/* file dialog 껴졌을때 동작 함수 */
openDialog = false;
bgEl.classList.remove("active");
}
inputEl.addEventListener("change", function(e){
/* 파일 변경시 동작 함수 */
files = e.target.files[0];
e.target.value = "";
clearTimeout(timeSet);
close_action();
})
labelEl.addEventListener("click", function(){
/* 파일 클릭시 동작 함수 */
clearTimeout(timeSet);
open_action();
})
window.addEventListener("focus", function(){
/* 윈도우에 포커스가 갈때 이벤트 발생 함수 */
if(openDialog){
clearTimeout(timeSet);
timeSet = setTimeout(close_action, 100);
}
})
</script>
미리보기
< React >
리엑트에서는 개인적으로 제가 느끼기에는 더 간편했습니다.
스타일링 및 dimmed처리 관리를 편리하게 하기위해
styled-components라는 라이브러리를 사용했습니다.
플로우와 코드는 다음과 같습니다.
1. 상태관리 변수 선언
- dimmed처리할 background관련 state 선언
- file을 관리할 state 선언
- 창이 켜졌는지 관리할 변수를 ref로 선언해줍니다.
* ref로 관리를 한 이유는 state는 한 스택이 끝나야 발생해서 이벤트발생보다
늦게 state가 변화될 우려가 있어 바뀜을 바로 감지할 수 있는 ref로 선언했습니다.
2. label, input, window에 이벤트 부여
- label에 click 이벤트를 주어서 파일 창이 켜졌을 때 action_open 함수를 실행합니다.
- input에는 change 이벤트로 파일을 업로드 할 때를 감지해 action_close 함수를 실행합니다.
- window에 focus 이벤트를 주어 이벤트가 발생했을 때 action_close 함수를 실행합니다.
import React, {useState, useRef, useEffect} from 'react';
import styled from 'styled-components';
export const InputCancelTest = ()=> {
const [onBg, setOnBg] = useState(false); // dimmed처리 background 관리 state
const [file, setFile] = useState(false); // file 관리 state
const onClickInput = useRef(false); // 창이 켜졌는지 관리 ref
function action_open(){
onClickInput.current = true;
/* file dialog 켜졌을때 동작 함수 */
setOnBg(true);
}
function action_close(){
onClickInput.current = false;
/* file dialog 껴졌을때 동작 함수 */
setOnBg(false);
}
function onFocusWin(){
/* 윈도우에 포커스가 갈때 이벤트 발생 함수 */
if(onClickInput.current){
action_close();
}
}
useEffect(()=>{
/* 컴포넌트가 마운트 되면 window에 focus 이벤트 부여 */
window.addEventListener("focus", onFocusWin);
return ()=> {
/* 컴포넌트가 마운트 해제되면 window에 이벤트 제거 */
window.removeEventListener("focus", onFocusWin);
}
},[]);
return (
<Wrap>
<Bg active={onBg}/>
<Label onClick={()=>{
/* 파일 변경시 action_open 함수 실행 */
action_open();
}} htmlFor='fileS'>
파일
<Input id="fileS"
onChange={(e)=>{
/* 파일 클릭시 file state에 저장, action_close 함수 실행 */
const file = e.target.files[0];
setFile(file);
e.target.value = "";
action_close();
}}
type="file"/>
</Label>
</Wrap>
)
}
const Wrap = styled.div``;
const Bg = styled.div`
position:fixed; top:0; left:0;
width:100vw; height:100vh;
background:rgba(0,0,0,0.3);
${({active})=> active ? "display:block;" : "display:none;"}
`;
const Label = styled.label`
padding:10px 20px; border-radius:25px;
position:absolute; left:50%; top:50%; transform:translate(-50%, -50%);
border:2px solid #000000; font-weight:bold; cursor:pointer;
`;
const Input = styled.input`
display:none;
`;
지금까지 input file Dialog(파일 창)에서 취소 눌렀을 때를 감지하는 방법에 대해서 알아보았습니다.
방법은 많겠지만 window에 focus를 감지해 창이 꺼졌는지 감지하는 방법으로 해결을 했습니다.
다른 방법을 알게되거나 찾게되면 더 정리를 할 계획입니다.
* 출처 및 링크
stack overflow에서 해당 문제를 다룬 링크