Frontend 에서 안드로이드로 이미지 첨부하고 미리보기를 만들었을때 회전되어서 나오는 이슈가 있다. 해당 부분을 수정하는 방법에 대해서 적어본다 작업 환경은 Chrome / Jquery 사용했다
<input type="file" id="imageFileList" name="imageFileList" accept="image/*" multiple />
<Input /> 태그를 HTML 입력하면 간단하게 파일을 첨부할수 있다. 하지만 미리보기는 볼수 없다 이미지 미리보기 코드를 잡아줘야 한다. 먼저 <Input /> 태그를 감지하자
document.getElementById('imageFileList').addEventListener('change',function(){
let files = this.files;
});
files변수를 consle.log로 보면 삽입된 파일 리스트를 확인할수 있다. input type="file" ReadOnly다 임의로 수정할 수 없다. files를 Array형태로 변화시켜주고 해당 이미지의 orientation의 값을 뽑아내야한다 안드로이드에서 사진의 회전된 값은 6이 나온다.
작업전에 체크사항부터 정리해보자
- 이미지 orientation 값 체크 > onload 가 사용 된다
- 이미지의 가로, 세로 값 체크 > onload 가 사용 된다
- img만 회전은 쉽지만 div 높이값이 수정이 안되기 때문에 겹침현상이 있다
- 회전 되었을때의 부모 div의 높이값 수정
- 다중이미지 선택일 경우 대비하여 동기로 작업이 들어가야 한다
- async / await / promise 코드 확인
- 클라이언트 크기에 비례하여 수정
- 이미지 미리보기를 위하여 blob 확인
- 이렇게 체크사항을 나열할수 있다.
- 따라서 위의 체크사항으 고려하여 만든 코드이다
/*
* 클라이언트 사이즈체크를 위한 공통 변수
* */
const documentWidth = document.body.clientWidth;
/*
* 이미지를 blob으로 변화하기 위한 공통 변수
* */
const _URL = window.URL || window.webkitURL;
/*
* 이미지 회전 값을 체크하는 함수
* */
function getOrientationPromise(file, callback) {
// 동기를 위한 Promise 코두 추가
return new Promise((resolve, reject) => {
var reader = new FileReader();
reader.onload = function (e) {
var view = new DataView(e.target.result);
if ( view.getUint16(0, false) != 0xFFD8 ) {
return resolve(callback(-2));
}
var length = view.byteLength, offset = 2;
while (offset < length) {
if ( view.getUint16(offset + 2, false) <= 8 ) return resolve(callback(-1));
var marker = view.getUint16(offset, false);
offset += 2;
if ( marker == 0xFFE1 ) {
if ( view.getUint32(offset += 2, false) != 0x45786966 ) {
return resolve(callback(-1));
}
var little = view.getUint16(offset += 6, false) == 0x4949;
offset += view.getUint32(offset + 4, little);
var tags = view.getUint16(offset, little);
offset += 2;
for (var i = 0; i < tags; i++) {
if ( view.getUint16(offset + (i * 12), little) == 0x0112 ) {
return resolve(callback(view.getUint16(offset + (i * 12) + 8, little)));
}
}
} else if ( (marker & 0xFF00) != 0xFF00 ) {
break;
} else {
offset += view.getUint16(offset, false);
}
}
// 동기 처리를 위해 resolve 추가
return resolve(callback(-1));
};
reader.readAsArrayBuffer(file);
})
}
/*
* 이미지 크기를 체크하기 위한 함수
* */
function getImagePromise(src) {
return new Promise((resolve, reject) => {
var img = new Image();
img.src = src
img.onload = function (e) {
// 동기 처리를 위해 resolve 추가
resolve(img)
};
})
}
/*
* async 함수를 통해 동기 처리를 한다
* 위의 getOrientationPromise onload가 사용되기 떄문에 동기 처리를 해야한다
* */
async function promisesMap(files, imgHTML, callback) {
// 다중이미지 일 경우를 대비 loop 처리
const filesData = files.map(
async (file) => {
await getOrientationPromise(file, (get) => {
// 이미지 회전값이 6일경우 flag 삽입
if ( get === 6 ) file.orientation = true;
file.blobData = _URL.createObjectURL(file);
}
);
return await insertMap(file, imgHTML);
})
const numFruits = await Promise.all(filesData);
callback(numFruits);
}
/*
* 이미지 html 코드로 가공
* DOM 미리보기를 위해 가공한다
* */
function insertMap(file, imgHTML) {
let divHeight = '';
let orientation = false;
let rotateStatus = 'max-width:100%; display:block;';
// then
return getImagePromise(file.blobData).then((v) => {
if ( file.orientation ) {
// 클라이언트 width값에 맞춰서 높이값 체크
let ratio = v.height / v.width;
orientation = true;
// 회전된 이미지에 맞춰 div 높이값 체크
rotateStatus = 'width:auto; display:block; height: ' + documentWidth + 'px; ';
divHeight = 'height: ' + (Math.ceil(documentWidth / ratio)) + 'px';
}
// data-orientation 상태를 삽입하여 CSS 선택자를 통해 이미지 회전
let imgTag = `
<div style="${divHeight}">
<img src="${file.blobData}" data-orientation="${orientation}" style="${rotateStatus}">
</div>
`;
return imgHTML += imgTag;
})
}
document.getElementById('imageFileList').addEventListener('change', function () {
let files = Array.from(this.files);
let imgHTML = '';
promisesMap(files, imgHTML, (result) => {
// 결과값은 array형태로 받는다
document.getElementById('testEl').insertAdjacentHTML('beforeend', result);
});
});
div {
position: relative;
}
div img[data-orientation="true"] {
transform-origin: top left;
transform: rotate(-90deg) translate(-100%);
transform: rotate(90deg) translate(0, -100%);
}
완성된 값을 DOM에 삽입한다. 위의 코드 장점은 이미지를 회전하여 유저에게 미리 보여줄수가 있다. 스마트폰 안드로이드 폰에서 사진을 찍거나 첨부하면 미리보기를 만들때 회전되서 나오는 이슈가 있다. 이미지서버를 통하여 수정할수 있지만 위의 코드는 프론트에서만 처리하기 때문에 속도가 빠르다 굳이 서버를 타지 않아도 된다.
하지만 DB에 저장시에는 백엔드에서도 이미지 체크하여 돌려줘야 한다 이 코드는 순전히 미리보기만을 위한 코드이다.
간단히 정리해서 리팩토링한 코드이다 해당 코드를 사용시에는 역시 커스텀이 필요하다
'Javascript' 카테고리의 다른 글
[Javasciprt] 말머리 붙히기 (0) | 2019.10.12 |
---|---|
[JavaScript] event 대하여 (0) | 2019.09.19 |