본문 바로가기
작은 모듈(IT구슬)

JavaScript을 사용한 multiple 속성 이미지 미리보기

by IT여행자 2021. 1. 10.
728x90

안녕하세요. IT여행자입니다.

 

이번 여행지는 JavaScript를 사용하여 file 태그에 의해 선택된 이미지를 미리 보는 작업을 해볼까 합니다. HTML5가 이제 보편적으로 사용되면서 하나의 file 태그에 의해 하나 이상의 파일들을 한 번에 선택할 수 있게 되었습니다. 아직 몇몇 파일 다운로드 API 중에는 file 태그의 multiple 속성을 사용할 수 없는 것도 있지만 버전업이 되지 않는다면 시장에서 도태되겠지요~

 

여담이지만, 우리나라도 이제 것 IE에 종속적이었기 때문에 web 개발 능력이 한없이 IT 선진국에 비해 뒤쳐져있는 건 사실일 것입니다. 아니, 정확히 이야기하면 신기술을 사용하지 못하고 그저 IE의 특성만을 답습하다 보니 우리도 모르게 세계 시장에서 도태된 것일 겁니다. 사설 끊고~ 자, 이제 시작해 봅시다.

 

[시연 동영상]

 

 

[요구조건]

  • 하나의 file 태그를 사용하여 이미지를 다중 선택할 수 있어야 한다.
  • 선택되어 있는 이미지는 삭제될 수 있어야 한다.
  • 기존 file 태그를 사용하여 이미지를 추가할 수 있어야 한다.
  • 파일 탐색기 등을 통해 파일을 드래그 앤 드롭해도 이미지가 추가되어야 한다.

 

위와 같은 요구조건에 맞게 JavaScript와 HTML5, CSS를 사용하여 작업해 보도록 하겠습니다.

 

1. HTML과 CSS를 사용한 레이아웃

 

간단하게 레이아웃을 작성하였고, CSS를 사용하여 placeholder 기능만 추가하였습니다.

 

[HTML]

 

<div id='image_preview'>
	<h3>이미지 미리보기 [ IT여행자 ]</h3>
	<input type='file' id='btnAtt' multiple='multiple'/>
	<div id='att_zone' 
	      data-placeholder='파일을 첨부 하려면 파일 선택 버튼을 클릭하거나 파일을 드래그앤드롭 하세요'></div>
</div>

 

[CSS]

 

#att_zone{
	width: 660px;
	min-height:150px;
	padding:10px;
	border:1px dotted #00f;
}
#att_zone:empty:before{
	content : attr(data-placeholder);
	color : #999;
	font-size:.9em;
}

 

[구현 화면]

 

HTML + CSS 구현 결과

 

2. JavaScript 부분

 

2.1 자동 실행 함수를 사용하여 만들겠습니다.

 

첫 번째 매개변수인 att_zone은 첨부된 이미지들이 표시될 영역을 나타내는 id값이고, 두 번째 매개변수는 file 태그의 id값을 의미합니다.

( /* att_zone : 이미지들이 들어갈 위치 id, btn : file tag id */
  imageView = function imageView(att_zone, btn){
  
  }
)('att_zone', 'btnAtt')

 

2.2 함수내에서 사용될 변수들 선언

 

함수의 매개변수로 전달된 id값을 사용하여 각각 attZone, btnAtt 객체를 생성한 뒤, 첨부된 파일의 목록을 저장할 sel_files 변수를 배열로 지정해 둡니다.

( /* att_zone : 이미지들이 들어갈 위치 id, btn : file tag id */
	imageView = function imageView(att_zone, btn){
		var attZone = document.getElementById(att_zone);
		var btnAtt = document.getElementById(btn)
		var sel_files = [];
        ...
	}
)('att_zone', 'btnAtt')        

 

2.2 첨부 이미지 영역의 스타일

 

첨부된 이미지들은 아래의 그림과 같이 relative 속성을 갖는 div안에 버튼과 함께 배치될 것입니다. 이를 위해 CSS부분을 자바 스크립트 안에 임베드시키기 위해 스타일 코드를 작성해 둡니다.

 

첨부 이미지 레이아웃

 

...
// 이미지와 체크 박스를 감싸고 있는 div 속성
var div_style = 'display:inline-block;position:relative;'
              + 'width:150px;height:120px;margin:5px;border:1px solid #00f;z-index:1';
// 미리보기 이미지 속성
var img_style = 'width:100%;height:100%;z-index:none';
// 이미지안에 표시되는 체크박스의 속성
var chk_style = 'width:30px;height:30px;position:absolute;font-size:24px;'
              + 'right:0px;bottom:0px;z-index:999;background-color:rgba(255,255,255,0.1);color:#f00';
...

 

 

2.3 file 태그 상태가 바뀌었을 때(파일을 선택했을때)

 

multiple 속성의 file 태그로부터 n개의 이미지를 전달받아 각각의 이미지를 읽어 화면에 표시해 주는 imageLoader() 함수를 호출합니다.

 

<script>
...
  btnAtt.onchange = function(e){
    var files = e.target.files;
    var fileArr = Array.prototype.slice.call(files)
    for(f of fileArr){
      imageLoader(f);
    }
  }	
...
</script>

 

 

2.4. 파일이 드롭되었을 때

 

탐색기 등에서 파일이 드롭되었을 때 처리되는 부분입니다. 이 부분이 없으면 브라우저에 이미지가 드롭되면 브라우저가 이미지 정보를 인식할 수 있기 때문에 그냥 브라우저에 표시되고 맙니다.

 

<script>
...
    // 탐색기에서 드래그앤 드롭 사용
    attZone.addEventListener('dragenter', function(e){
      e.preventDefault();
      e.stopPropagation();
    }, false)
    
    attZone.addEventListener('dragover', function(e){
      e.preventDefault();
      e.stopPropagation();
      
    }, false)
  
    attZone.addEventListener('drop', function(e){
      var files = {};
      e.preventDefault();
      e.stopPropagation();
      var dt = e.dataTransfer;
      files = dt.files;
      for(f of files){
        imageLoader(f);
      }
      
    }, false)	
...
</script>

 

2.5 이미지 미리 보는 imageLoader() 함수

 

file 태그에 의해 전달된 파일 하나하나를 sel_files에 추가한 후 이미지와 버튼을 추가하여 하나의 div를 만들어 반환하는 함수 makeDiv() 함수를 호출하여 그 결과를 att_zone에 추가한다.

 

<script>
...
    /*첨부된 이미리즐을 배열에 넣고 미리보기 */
    imageLoader = function(file){
      sel_files.push(file);
      var reader = new FileReader();
      reader.onload = function(ee){
        let img = document.createElement('img')
        img.setAttribute('style', img_style)
        img.src = ee.target.result;
        attZone.appendChild(makeDiv(img, file));
      }
      
      reader.readAsDataURL(file);
    }
...
</script>

 

2.6. 선택된 파일을 삭제할 때

 

추가할  div 태그를 생성하고, 전달받은 이미지를 div에 추가합니다. 또한 전달받은 파일 정보를 사용하여  삭제 기능을 하는 button 태그를 만들어 div에 붙입니다. 또한 만들어진 삭제 버튼이 클릭되면 div와 sel_files에서 관련 정보를 삭제하는 코드를 입력합니다.

 

<script>
...
    /*첨부된 파일이 있는 경우 checkbox와 함께 attZone에 추가할 div를 만들어 반환 */
    makeDiv = function(img, file){
      var div = document.createElement('div')
      div.setAttribute('style', div_style)
      
      var btn = document.createElement('input')
      btn.setAttribute('type', 'button')
      btn.setAttribute('value', 'x')
      btn.setAttribute('delFile', file.name);
      btn.setAttribute('style', chk_style);
      btn.onclick = function(ev){
        var ele = ev.srcElement;
        var delFile = ele.getAttribute('delFile');
        for(var i=0 ;i<sel_files.length; i++){
          if(delFile== sel_files[i].name){
            sel_files.splice(i, 1);      
          }
        }
        
        dt = new DataTransfer();
        for(f in sel_files) {
          var file = sel_files[f];
          dt.items.add(file);
        }
        btnAtt.files = dt.files;
        var p = ele.parentNode;
        attZone.removeChild(p)
      }
      div.appendChild(img)
      div.appendChild(btn)
      return div
    }

...
</script>

 

[전체 코드] HTML+CSS+JavaScript

 

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>image preview [ it여행자 ]</title>
<style>
#att_zone {
  width: 660px;
  min-height: 150px;
  padding: 10px;
  border: 1px dotted #00f;
}

#att_zone:empty:before {
  content: attr(data-placeholder);
  color: #999;
  font-size: .9em;
}
</style>
</head>
<body>
  <div id='image_preview'>
    <h3>이미지 미리보기 [ IT여행자 ]</h3>
    <input type='file' id='btnAtt' multiple='multiple' />
    <div id='att_zone'
      data-placeholder='파일을 첨부 하려면 파일 선택 버튼을 클릭하거나 파일을 드래그앤드롭 하세요'></div>
  </div>

  <script>
( /* att_zone : 이미지들이 들어갈 위치 id, btn : file tag id */
  imageView = function imageView(att_zone, btn){

    var attZone = document.getElementById(att_zone);
    var btnAtt = document.getElementById(btn)
    var sel_files = [];
    
    // 이미지와 체크 박스를 감싸고 있는 div 속성
    var div_style = 'display:inline-block;position:relative;'
                  + 'width:150px;height:120px;margin:5px;border:1px solid #00f;z-index:1';
    // 미리보기 이미지 속성
    var img_style = 'width:100%;height:100%;z-index:none';
    // 이미지안에 표시되는 체크박스의 속성
    var chk_style = 'width:30px;height:30px;position:absolute;font-size:24px;'
                  + 'right:0px;bottom:0px;z-index:999;background-color:rgba(255,255,255,0.1);color:#f00';
  
    btnAtt.onchange = function(e){
      var files = e.target.files;
      var fileArr = Array.prototype.slice.call(files)
      for(f of fileArr){
        imageLoader(f);
      }
    }  
    
  
    // 탐색기에서 드래그앤 드롭 사용
    attZone.addEventListener('dragenter', function(e){
      e.preventDefault();
      e.stopPropagation();
    }, false)
    
    attZone.addEventListener('dragover', function(e){
      e.preventDefault();
      e.stopPropagation();
      
    }, false)
  
    attZone.addEventListener('drop', function(e){
      var files = {};
      e.preventDefault();
      e.stopPropagation();
      var dt = e.dataTransfer;
      files = dt.files;
      for(f of files){
        imageLoader(f);
      }
      
    }, false)
    

    
    /*첨부된 이미리즐을 배열에 넣고 미리보기 */
    imageLoader = function(file){
      sel_files.push(file);
      var reader = new FileReader();
      reader.onload = function(ee){
        let img = document.createElement('img')
        img.setAttribute('style', img_style)
        img.src = ee.target.result;
        attZone.appendChild(makeDiv(img, file));
      }
      
      reader.readAsDataURL(file);
    }
    
    /*첨부된 파일이 있는 경우 checkbox와 함께 attZone에 추가할 div를 만들어 반환 */
    makeDiv = function(img, file){
      var div = document.createElement('div')
      div.setAttribute('style', div_style)
      
      var btn = document.createElement('input')
      btn.setAttribute('type', 'button')
      btn.setAttribute('value', 'x')
      btn.setAttribute('delFile', file.name);
      btn.setAttribute('style', chk_style);
      btn.onclick = function(ev){
        var ele = ev.srcElement;
        var delFile = ele.getAttribute('delFile');
        for(var i=0 ;i<sel_files.length; i++){
          if(delFile== sel_files[i].name){
            sel_files.splice(i, 1);      
          }
        }
        
        dt = new DataTransfer();
        for(f in sel_files) {
          var file = sel_files[f];
          dt.items.add(file);
        }
        btnAtt.files = dt.files;
        var p = ele.parentNode;
        attZone.removeChild(p)
      }
      div.appendChild(img)
      div.appendChild(btn)
      return div
    }
  }
)('att_zone', 'btnAtt')

</script>
</body>
</html>

 

이상으로 IT 여행자였습니다.