비동기 이미지 업로드 - 리사이징 및 이미지 회전을 포함

비동기 이미지 업로드

비동기 업로드를 이용하면 멀티 이미지 업로드나 모바일 이미지 업로드를 쉽게 처리할 수 있다.

모바일 이미지 업로드시에는 exif 속성에 따라 (카메라로 찍은 방향에 따라) 이미지가 회전되어서 올라가게 되고, 비율로 이미지를 표시해야 되기 때문에 리사이징이 필요하다.

이 부분은 load-image 모듈을 사용하면 다 해결된다.
아니라면… 사진 값을 바이너리로 읽어 처리해야한다.

설치

1
2
# bower install https://github.com/blueimp/JavaScript-Load-Image.git --save
$ yarn add blueimp-load-image

소스

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
<script type="text/javascript">
var imageModule = (function () {
'use strict';
var possible = window.File && window.FileReader && window.FormData; // html5 업로드를 지원하는지의 여부
var apiPath = '/api/test.php'; // 업로드 서버 처리 경로
var folder = '/upload/review/'; // 기본 이미지 업로드 폴더
var $input;
var maxWidth = 600;
var maxHeight = 480;
var imageArray = []; // callback에 사용할 이미지 파일명 배열
var callback;
/**
* [sendImageFile 이미지 비동기 업로드]
* @scope {[private]}
* @param {[string]} imageData [이미지 binary]
* @return {[function]} callback [업로드 후처리]
*/
function sendImageFile(imageData) {
if (imageData) {
var formData = new FormData();
formData.append('type', 'upload');
formData.append('folder', folder);
formData.append('imageData', imageData);
$.ajax({
type: 'post',
url: apiPath,
data: formData,
dataType: 'json',
contentType: false, // contentType header 제거
processData: false, // Dom 객체를 전송하려면 false 처리해야함
})
.then(function (data) {
if (data.data) {
imageArray.push(data.data.file); // 이미지 배열에 저장
$input.val('');
if (callback) {
callback(imageArray);
} else {
console.log('callback function not initialized');
}
}
})
.catch(function (error) {
console.log('upload fail: ', error);
});
} else {
console.log('image data is null');
}
}
return {
/**
* [init 이미지 모듈 초기화]
* @param {[string]} fileId [변경할 input id]
* @param {[function]} callbackFunc [후처리]
* @param {[object]} settings [width, height, folder 설정가능]
*/
init: function (fileId, callbackFunc, settings) {
if (possible) {
if (typeof settings === 'object') {
if (settings.width) {
maxWidth = settings.width;
}
if (settings.height) {
maxHeight = settings.height;
}
if (settings.folder) {
folder = settings.folder;
}
}
if ($.isFunction(callbackFunc)) {
callback = callbackFunc;
} else {
console.log('callback is not defined');
}
var options = {
maxWidth: maxWidth, // resize width 값
maxHeight: maxHeight, // resize height 값
canvas: true, // 사진을 돌리려면 canvas 객체로 받아야함
downsamplingRatio: 0.5, // 비율에 맞추어 크기 감소
};
$input = $('#' + fileId);
$input.on('change', function (e) {
e.preventDefault();
e = e.originalEvent;
var target = e.dataTransfer || e.target;
var file = target && target.files && target.files[0];
loadImage.parseMetaData(file, function (data) {
// exif js를 사용해 이미지의 tag를 가져옴
if (data.exif) {
options.orientation = data.exif.get('Orientation'); // 화면이 돌아간 비율을 지정
} else {
if (options.orientation) {
options.orientation = null;
}
}
// 캔버스 이미지 리사이징 처리 후 서버호출
loadImage(
file,
function (img) {
sendImageFile(img.toDataURL());
},
options
);
});
});
} else {
console.log('file upload is not supported');
}
},
/**
* [getFolder 업로드 경로 호출]
* @return {[string]} [경로]
*/
getFolder: function () {
return folder;
},
/**
* [delete 이미지 삭제]
* @param {[number]} idx [이미지 배열의 인덱스]
* @param {[function]} callbackFunc [delete 후처리]
*/
delete: function (idx, callbackFunc) {
if (imageArray[idx]) {
$.ajax({
url: apiPath,
type: 'post',
data: {
type: 'delete',
folder: folder,
filename: imageArray[idx],
},
dataType: 'json',
})
.then(function (data) {
console.log('file delete success');
console.log(data);
if ($.isFunction(callbackFunc)) {
callbackFunc(data);
}
})
.catch(function (error) {
console.log('server file delete failed');
console.log(error);
})
.done(function () {
imageArray.splice(idx, 1);
$input.val('');
if (callback) {
callback(imageArray);
} else {
console.log('callback function not initialized');
}
});
} else {
console.log('delete index not defined');
}
},
clear: function () {
console.log('clear array');
imageArray = [];
},
};
})();
$(function () {
imageModule.init(
'fileInput',
function (data) {
var folder = imageModule.getFolder();
var str = '';
for (var i = 0, len = data.length; i < len; i++) {
str +=
'<img src="' +
folder +
data[i] +
'" style="width:100%;height:100%;">';
}
$('#area').html(str);
$('#fileData').val(JSON.stringify(data));
},
{}
);
// options, width height folder..
// {width:500, height:300, folder:'/upload/board/'}
});
// after insert into database
$('#area').empty();
$('#fileData').val('');
imageModule.clear();
</script>

<input type="file" name="imageFile" id="fileInput" />

설명

코드양이 되게 긴데, 5-6 줄에서 업로드 서버 처리 경로 및 기본 업로드 폴더를 지정해준다.
9-10 줄에서는 최대 가로 세로(px)을 지정해준다. 비율로 줄어들기 때문에 걱정하지말고 지정하자

사용법은 169 줄 이후를 보면 된다.
callback 함수를 통해 올라간 이미지를 바로 보여주게 처리할 수 있다.
184 줄부터는 db 와의 통신이 끝난 후 화면을 초기화해주는 부분이다.

127 줄의 imageModule.delete 함수를 사용해 업로드 된 사진을 삭제할 수 있다.

현재는 api/test.php로 서버의 업로드 처리 로직을 구현해놨는데,
type 을 받아 delete 일 경우 해당 이미지를 삭제,
아닐 경우 binary 데이터를 이미지로 변환해주게 로직을 짜면 된다.