웹에서 파일을 첨부하려면 <input type="file" />을 사용한다. 자동으로 찾아보기 버튼이 생기고 파일브라우징이 가능하다.
input type=file은 약간의 특징이 있다. 파일을 업로드하는 기능을 가진 특성때문에 다른 input타입과는 다르게 약간의 제약이 있다. 그건 스크립트를 이용해서 제어하는 것을 막고 있는 것인데 그것 때문에 다루기가 좀 쉽지 않다. 하지만 그것보다 문제되는게 저 "찾아보기..."버튼이다. 점점 디자인이 강조되고 있는 가운데 기본적으로 뜨는 찾아보기 버튼은 웹디자이너에게는 걸리적거리는 것 같다.
항상 디자인을 받을 때마다 찾아보기 버튼대신에 이미지버튼으로 된 디자인이 왔었는데 이번에 여러가지 테스트를 해보았다. 결론적으로 말하자면 찾아보기버튼은 바꿀수 없다.(적어도 내 지식 수준에서는...) 물론 이에대한 태클도 있을꺼라고 생각한다.
가장 쉽게 생각할 수 있는 것이 input type=file을 input type=text와 이미지버튼의 조합으로 대치하는 것이다.
<input type="text" size="30" id="txt" />
<img src="" onclick="document.getElementById('file').click();" />
<input type="file" size="30" id="file" style="display:none;" onchange="document.getElementById('txt').value=this.value;" />
위와같은 모양이다. 인풋파일은 보기에는 인풋텍스트와 버튼의 조합형태이기 때문에 앞에 인풋텍스트를 넣어놓고 그옆에 디자인된 이미지 버튼을 넣은 뒤에 인풋파일은 스타일을 주어서 보이지 않게 하는 것이다. 이미지 버튼을 onclick했을 때 인풋타입의 객체에 click()이벤트를 주는 구조이다. 파일선택하는 창도 아주 잘 뜬다. 인풋파일에 onchange이벤트를 주어서 파일선택해서 값이 들어갈때 인풋텍스트로 값을 복사해서 넣어서 찾아보기버튼을 이미지로 교체한 듯한 효과를 주는 것이다.
실제적으로 인터넷을 찾아보면 찾아보기 버튼 교체에 대한 위의 소스가 엄청 많다. 잘못된 정보의 대표적인 예라고 할 수가 있다. 서버쪽은 제대로 돌려보지도 않고 클라이언트쪽만 테스트 해보고 잘된다고 올려진 소스를 서로 계속 퍼나르고 있는 상황이다. ㅡ..ㅡ
인풋파일은 보안문제상 Read Only이기 때문에 스트립트로 값을 입력하는 것을 허용하지 않는다. 소스상으로는 아주 잘 돌아가는 거서럼 보이지만 막상 submit버튼을 누르면 전송이 되는 것이 아니라 인풋파일의 값이 clear되어 버린다. 스크립트로 실행한 것은 초기화 시켜버리고 다시한번 submit버튼을 눌러야 POST전송이 일어난다.
쉽게 말하면 이구조로는 절대 파일 업로드를 받을 수가 없다.
그래서 다른 방법을 찾기 시작했다.
<input type="text" size="30" id="txt" />
<span style="overflow:hidden; width:61; height:20; background-image:url(/images/button.gif);">
<input type='file' id="file" style="width:0; height:20; filter:alpha(opacity=0);" onchange='document.getElementById('txt').value=this.value;'>
</span>
그래서 찾은 방법이 위의 방법이다. 방법은 앞에서 얘기했던것과 거의 비슷하다. 파일명을 보여줄 인풋텍스트를 하나 보여주고 그 뒤에 span을 둔다. span은 찾아보기 버튼을 대체해 주는 역할을 한다. 크기를 정해주고 배경색으로 버튼 이미지를 지정해 준다. 그리고 그 span안에 인풋파일을 넣어주는데 여기서 스타일로 width를 0px를 주고 alpha값으로 투명도를 0을 준 것이다.
이렇게 하면 인풋파일의 파일명이 나타나는 부분의 width가 0px가 되기 때문에 나타나지 않고 찾아보기 버튼의 크기는 조절할 수가 없기 때문에 앞에 넣은 인풋파일뒤에 바로 찾아보기 버튼이 위치한다. 하지만 투명도가 0이기 때문에 실제 위치하고 있기는 하지만 보이지가 않고 span의 배경인 버튼 이미지가 보이는 구조이다.
하지만 여기에 약간의 문제가 있다. 일단 여기서 중요한 역할인 투명도를 나타내는 alpha값은 IE에서만 유효한 스타일값이다. Firefox에서는 투명도를 -moz-opacity:0; 를 사용해야 하는데 이걸 사용해도 Firefox에서는 IE같은 효과가 나지 않는다. 배경으로 지정된 이미지버튼이 나오지 않는다. 머 이것만으로도 요즘같은 분위기에서는 의미가 없다고 본다. 그리고 눈에 보이는 버튼이 실제버튼이 아니고 실제버튼은 투명하게 있는 버튼이기 때문에 클릭을 하면 실제 버튼보다 작게 점선이 생겨서 보기에 별로이다. 또한 저 소스에서는 width가 61로 설정이 되어있는데 브라우저마다 다른지는 모르겠지만 IE7에서는 찾아보기 버튼이 103px이기 때문에 이미지버튼도 103에 맞추어 져야 크기에 맞출수 있을것 같다.
앞의 방법보다는 좀 낫긴 하지만 이 방법에도 확실히 문제가 있다.
하지만 이런 방식으로 실제 구현해서 사용하려고 하면 더 큰 문제에 부딪히게 된다. 인풋파일의 경우 파일을 선택했다가 올리기가 싫어지면 그냥 지워주면 되는데 여기서는 편법을 써서 인풋텍스트를 눈앞에 보여주었기 때문에 지워도 인풋텍스트만 지워질뿐 실제 인풋파일의 내용이 지워진것이 아니다. 결국은 아예 한번 선택하면 지우지 못하도록 막아주던가(이렇게 할수는 있지만 기존의 사용자경험을 깨버린다고 생각하기에 별로 하고싶지 않았다.) 인풋텍스트의 값을 지우면 인풋파일의 값도 지워줘야 한다.
그럴려면 천상 스크립트로 값을 지워져야 하는데 보안문제 때문에 인풋파일은 스크립트가 값을 바꿔버리는 것을 허용하지 않는다. 다른거에 하던 식으로
document.getElementById("file").value = "";
는 아예 먹지도 않는다. 대신에...
document.getElementById("file").select();
//document.execCommand('Delete');
document.selection.clear();
위와 같은 자바스크립트 코드를 이용하면 해당 인풋파일의 값을 지워줄 수 있다. 2번줄에 주석처리한 부분은 3번줄 대신 2번줄을 사용해도 된다. 둘줄 하나로 하면 된다. 물론 이 소스
(어찌된 일인지 글을 여기까지만 작성된채 공개됐다. 그럴리가 없는데 약간의 문제가 있었던듯.. 기억을 더듬어 글을 마무리 한다. 2008.7.17)
도 돌아가지 않는다. 위에 말한대로 보안문제 때문에 submit()할 때 문제가 생긴다.
인터넷을 찾아보다 보면 CSS를 이용해서 수십줄로 바꾸는 것도 있긴 한데 해보진 않았다. 버튼 하나 바꿀려고 여러문제는 가지게 되거나 아니면 수십줄의 코드를 넣는게 과연 의미가 있을까 하는 생각이 든다.
차라리 그 노력이면 요즘 게시판 등에서 많이 하는 추세대로 플래시 컴포넌트를 이용해서 바로바로 파일을 올릴수 있게 만드는 것이 훨씬 좋은 선택이라고 생각된다. 찾아보기 버튼은 왠만하면 그냥 쓰자.
위에서 clear()를 사용하는 것에도 문제가 있습니다.
물론 이것은 해결할 수 있는 문제이지요..
하지만 엑세스 거부문제가 남아 있지요..
물론 이것도 해결할 수 있는 문제이지요...
그러나 결적적으로 click() 으로 파일객체를 컨트롤 하는 것도 IE에서만 가능하니..
적어도 스크립 사용을 자제할 수밖없으니...
제가 쓴글이 이상하게 뒷부분이 잘려 있었네요. 분명이 내용을 작성한 기억이 나는데... 실수가 있었던건지 오류가 있었던건지 모르겠지만 기억을 더듬어서 대충 글을 마무리 했습니다.
말씀하신대로 엑세스문제 때문에 스크립트로 다루는 것은 안되겠다라는 결론이 나더군요. click()으로 인풋파일을 다루는 것이 IE에서만 되는 것은 처음 알았네요. ^^
이 문제 어떻게 해결하셨나요??
제가 해결 못한 10대 문제 중에 하나로 남는 것 같네요..;;;
억지로 가능하게는 하였다지만... 문제에 문제가 콤보로 기다리고 있을 줄이야....
결국 IE에서만 돌아간다는 그 찝찝함...-_-;;;
며칠사이 좀 아이디어를 구상하기는 했는데...
이론과 실제는 근간이 있네요...
가능한 경우를 다 해보고 싶기도 하지만...
그럴 시간적 여력이 안되니....
마지막 부분을 보시면 아시겠지만 저로써는 불가능하다 혹은 할 필요가 없다가 결론이었습니다. 위에 써진대로 글작성후 이유를 알수 없게 뒷부분이 없어져서 재작성을 하기는 했는데요. 인터넷을 수업이 뒤져봐도 완벽하게 이부분을 해결한 것은 아직 본적이 없습니다.
이게 되면 저게 안되거나 되긴 되는데 뭔가 찝질한... 그리고 이런 것을 변환하는 것도 허용여부가 브라우져마다 제각각이라서 요즘같은 개발 추세에서는 바꾸지 않는게 오히려 옳다는 것이 제 결론이었습니다.
절대 불가능하다는 아닌것 같지만 구글링을 해서 찾을 수 있는 수십줄의 소스들을 보면 브라우져 호환성도 가능하면서 실시간으로 파일 올리고 파일이 올라가는 프로그래스바까지 볼 수 있는 플래시 등의 형식으로 파일업로드를 구현하는 것이 모든 면에서 낫다고 판단했기 때문에 이 부분에 대해서 더이상 고민을 하지 않기로 했습니다.
대신 디자이너는 설득해야할듯 합니다. 정 안되면 그렇게 바꾼 곳을 보여달라고 하시면 될듯합니다. ^^
http://phpschool.com/gnuboard4/bbs/board.php?bo_table=tipntech&wr_id=65738&page=1
오~ 좋은 정보 감사합니다.
이것저것 테스트해봤는데 거의 완벽한 해결책같군요. 스크립트로 input을 이미지 크기에 충분하게할당하고 div로 감싸고 실제로는 input file이 동작하게 한 거군요.
위에 적었던거랑 개념은 비슷하기는 한데 찾아보기 버튼만 변환하지 않고 input file전체를 이용한게 최고네요...
<script language="javascript">
<!--
function getForm()
{
return document.documentFrm;
}
function select_file()
{
getForm().upload_file.click();
if(getForm().upload_file.value != "")
{
getForm().upload_path.value=getForm().upload_file.value;
}
//아래 부분이 없으면 엑세스 거부 에러 발생.
document.getElementById("file_span").innerHTML = '<input type="file" name="upload_file" id="upload_file" style="display:none;">'
}
//-->
</script>
<body>
<form name="documentFrm" method="POST">
<span id="file_span">
<input type="file" name="upload_file" id="upload_file" style="display:none;">
</span>
<input type="text" class="text" size="35" name="upload_path" value="">
<img src="이미지 경로" width="100" height="20" onClick="javascript:select_file();" style="cursor:hand;" align="absmiddle">
</body>
이런 방식으로 하면 엑세스 어쩌구 에러 없이 자알 됩니다.
IE7하고 FF3에서 테스트 해보았는데 되지 않습니다. 서버쪽에서도 테스트 해보신건가요?
소스보면 엑세스 거부에러를 없애기 위해서 만드신 코드를 보면 file_span밑으로 다시 input file을 만드셔서 넣으셨는데 그렇게 하면 당연히 보안엑세스를 어긴 엘리먼트는 날라가 버렸기 때문에 엑세스 거부는 뜨지 않지만 파일업로드파일이 지정된 엘리먼트도 같이 날라갔기 때문에 결국 값을 서버쪽에 날라오지 않는군요. 그냥 input file에 파일지정을 안하고 submit한것과 동일하게 보이는군요.
그리고 지금 확인하면서 안건데 getForm().upload_file.click();로는 FF에서는 파일선택창이 나오지 않습니다.
댓글 주신건 감사한데 안되서 안타깝네요. ㅠ..ㅠ 혹 누락된 소스가 있으시거나 하시면 다시 해결책을 말씀해 주시면 감사하겠습니다.
투명이미지로 어떻게든 크로스브라우징 되면서 하려면 하겠지만...
그렇게 까지 해야 하는 회의감이 드네요...
구글의 오픈오피스는 어떻게 했나 찾아가 봤더니 고민을 확 날려버리더군요.
파일열기 누르면 화면 바뀌면서 아주 평범한 파일 Input 박스가 떡하니 나타나더라는.
지금이야 어떻게든 해결하겠지만 플래시 방식도 마찬가지고요. 앞으로 보안은 점점 강화되는 추세니 어쩌면 구글의 방식이 현명할지도 모르겠네요.
구글을 비롯한 해외의 경우 일반적인 인풋박스가 통하지만 국내에서는 그런부분까지 디자인이 되길 원하죠. 디자이너들도 그렇게 하고 싶어하고요.
억지로 짜맞추기보다는 플래시같은 대안을 쓰는게 더 나은듯 합니다. 점점 더 좋은 방법이 생기겠죠.
글 잘 읽고 갑니다. 웹개발자에게 큰 도움이 되는 글이네요 ...
감사합니다. 즐거운 코딩되세요. ^^
요새는 jQuery로 가능하지 않나요?
jQuery 돔 조작으로 하신다는 말슴이신지 아니면 jQuery의 플러그인들을 말쓴하시는건가요?
크로스 브라우징 input type file 디자인하기
Firefox 3, IE6, IE7, IE8, Opera 10, Chrome 3 에서 테스트해봤다네요
저도 실무에서 써봤는데 괜찮음!
http://tiagoe.blogspot.com/2010/01/css-style-typefile-tags.html
위에서 설명한 코드중 opacity를 이용한 예시임.
첫번째 예제에서 빠진게 있는데 .click()은 IE밖에 안될꺼에요(아마도)
와~~ 공유 감사합니다.
kyungw00k님이 추천해 주시니까 믿음이 가네요 ㅎㅎㅎㅎㅎㅎ
저글 쓸때에 비해서도 프론트앤드가 참 많이 발전한것 같네요... 사실 저때는 잘 모르는것도 많아서(지금도!!!) 왠지 자꾸 이슈될때마다 좀 부끄러운 글이긴 해요 ㅎㅎㅎㅎ
좋은 글과 댓글들 정말 감사합니다!
보안이슈때문에 엄청 고민이 많고 시간을 끌었는데.. 해결했어요!
감사합니다!
해결하셨다니 다행이네요...
브라우저에서 파일핸들링은 항상 피곤하죠.. 꼭 해달라고 하는 경우가 많아서.
어떻게 해결하셨는지도 궁금하군요.
이론적 내용보단 프론트엔드 개발자들이 서버측 검증도 안하고
'파일첨부 이미지 버튼쓰기' 따위 포스팅을 남발한다는것에 심히 공감합니다.
그리고 현재로선 플래시와 opacity밖에 답이없는듯합니다.
필요하다면 방법이야 있겠지만 말씀하신대로 제대로 검증되진 않은 글들이 많이 있다는 부분이 더 큰 문제겠죠.. 이런 글들이 계속 걸러져야 되는데...(최근엔 검색해본적은 없지만요.)
지나가는 한 고등학생입니다. 폼 태그 네임을 "documnet"라 하고, 찾아보기 버튼을 "plus.png"라는 이름의 이미지로 바꾸고 싶으면
<input type="file" style="display:none;" name='document">
<a href="" onclick="upload.document.click();" >
<img src="images/plus.png" style="width:30px; height:30px;">
</a>
이렇게도 가능한것같습니다. 한번 해보셨는지요? ㅎㅎ
제가 테스트 해보지는 않았지만 그부분은 input에서 찾아보기 부분만 바꾸는게 아니라 image 버튼으로 아예 대체한 것으로 보입니다. 이미지 버튼으로 하는 경우는 최신 브라우저에서는 File API가 있기도 하고 말슴하신대로 가능할 겁니다.
안녕하세요. 이 문제로 골머리를 앓다가 위에 kyungw00k님이 링크 걸어주신 페이지의 내용으로 일단 해결은 했는데요. 저희가 지금 해결해야할 한가지 추가적인 문제가 파일 업로드 전에 파일 사이즈 체킹을 해야 합니다. 예를 들어 용량제한이 5MB라고 하면 이보다 큰 파일을 등록했을 때 알럿 메시지를 띄워야 하는데요. 스크립트로 처리하는 몇가지 방법을 적용해보았는데 위의 방법으로는 type=file의 내용을 스크립트로 type=text에 옮기고 있어서 안되는거 같더라고요. 뭔가 해결 방법이 없을까요? 검색중에 이곳이 그래도 최근까지 질문답이 이뤄지고 있는것 같아 여쭤봅니다.
최근에 파일업로드 구현을 하지 않아서 구체적인 내용은 확인안해봤지만 모던 브라우저에서는 File API를 사용하셔야 합니다. 그렇지 않은 경우라면 플래시나 ActiveX 등이 필요합니다. 최근에 적용해 보신 다른 분이 더 좋은 대답을 주실지 모르겠네요.