이번에 내가 맡게 된 업무는 dropzone.js 를 이용하여 동영상 업로드와 미리보기 및 썸네일 기능을 만드는 것이였다.
요즘들어 부쩍 스크립트 만질 일이 많아졌는데
BE 코드를 다루지 못하고 있어서 아쉬움이 많다... 😭
업무 진행 당시 dropzone.js version : 5.5.0
진행상황
동영상 업로드 , 동영상 특정 구간 썸네일 추출 , 미리보기 기능 구현 완료! 😎
이슈 & 고민 및 해결
배너배리에이션 기능을 만들었던 이미지 등록하는 화면에서
당시에는 발견하지 못했던 버그들도 발견하면서 수정하고 dropzone.js 오픈 소스 라이브러리 코어 스크립트를 조금
수정하였다. 😂
이 업무를 진행하면서 이슈나 고민 거리는 없었던 것 같다 🤗
[지난 배너 배레이이션 작업 이후 체크 못한 결함 사항 수정 리스트 🛠]
1. 250*250 소재 업로드 후 250*250 소재 다시 업로드 하면 중복 로직에 막힘
이후 250*250 소재 삭제 하고 다시 업로드 하기 전까지 이미지 자동생성 안되는 문제
-> 자동생성 유효성 로직 수정 ( 사용 할 수 있는 객체 값을 다시 찾아 할당하는 방식으로 수정)
2. 미리보기 표출 좌표 계산식 수정 ( 표출하는 영역 높이에 따라 미리보기 이미지들이 잘려서 안보일 수 있으므로 계산식을 통해 표출하는 위치를 조정)
-> 페이지 레이아웃이 변경 되어 잘못 계산 되는 수식 수정
as-is preveiw element y 좌표 + preveiw 높이 > 상위 element 높이
to-be preview 높이 + preview 바닥 좌표 + 이미지 높이 + 이미지 바닥 좌표 > 상위 element 높이
3. 기존 홀로그램 배너 수정 시 데이터 값 표출 누락 수정
[dropzone core script 동영상 업로드 가능하도록 커스터마이징]
*** el로 유효성 모두 판단 ***
1. addedfile 이벤트 중 영상 파일 이름으로 업로드 할 동영상 규격 맞는지 여부 판단
1-1. el 생성 및 이벤트
2. dropzone.js videoUploadSucess, videoUploadFail 이벤트 리스너 추가 ( 동영상 업로드 이후 무해화 로직 , 실패 시 화면에 처리할 로직 추가 )
3. 썸네일 추출 ( 영상 특정 구간 캔버스로 변환 후 사진 추출 ) - 지난 업무 중 배너 배리에이션 기능을 만들면서
캔버스에 조금 익숙해져서 바로 생각나서 기능을 만들었던 것 같다, 😁 (캔버스 API 도전기)
참고 레퍼런스
https://github.com/dropzone/dropzone
https://developer.mozilla.org/ko/docs/Web/JavaScript
TO DO List
1. 단위테스트
2. 코드 리팩토링 (불필요 코드 제거 개발 중 디버깅 콘솔 로그 제거,변수명 정리)(FE,BE)
Example Code
dropzone.js core 파일 추가 이벤트에 파일 타입이 영상 일 때 처리
내부 이벤트 리스너에 "videoUploadSucess","videoUploadFail" 이벤트 추가
this.prototype.events = ["drop", "dragstart", "dragend", "dragenter", "dragover", "dragleave", "addedfile", "addedfiles", "removedfile", "thumbnail", "error", "errormultiple", "processing", "processingmultiple", "uploadprogress", "totaluploadprogress", "sending", "sendingmultiple", "success", "successmultiple", "canceled", "canceledmultiple", "complete", "completemultiple", "reset", "maxfilesexceeded", "maxfilesreached", "queuecomplete","videoUploadSucess","videoUploadFail"];
....
....
....
addedfile: function addedfile(file) {
console.log("process core");
var _this2 = this;
if (this.element === this.previewsContainer) {
this.element.classList.add("dz-started");
}
if (this.previewsContainer) {
file.previewElement = Dropzone.createElement(this.options.previewTemplate.trim());
file.previewTemplate = file.previewElement; // Backwards compatibility
if (this.options.previewArea === null) {
if (file.type === "video/mp4"){
var _obj = {"title" : "" , "code" : "" ,"titleEng" : "" , "imageSize" : ""};
var vidUrl = window.URL.createObjectURL(file);
if(file.name === "width.mp4"){
_obj["title"] = "가로형";
_obj["code"] = "96";
_obj["titleEng"] = "width";
_obj["imageSize"] = "VIDEO_WIDTH";
}else if(file.name === "length.mp4"){
_obj["title"] = "세로형";
_obj["code"] = "97";
_obj["titleEng"] = "length";
_obj["imageSize"] = "VIDEO_HEIGHT";
}else if(file.name === "square.mp4"){
_obj["title"] = "사각형";
_obj["code"] = "98";
_obj["titleEng"] = "square";
_obj["imageSize"] = "VIDEO_SQUARE";
}
if($(`#div_${_obj["titleEng"]}`).length > 0){
return this.emit("videoUploadFail", file , "동일파일 이슈");
return false;
}else if(file.size > 5242880){
return this.emit("videoUploadFail", file , "용량 이슈");
return false;
}
var _div = document.createElement('div');
_div.className = "imgItem";
_div.id = `div_${_obj["titleEng"]}`;
_div.dataset.title = _obj["title"];
_div.dataset.code = _obj["code"];
_div.dataset.titleEng = _obj["titleEng"];
_div.dataset.imageSize = _obj["imageSize"];
_div.innerHTML = `<button type="button" class="ico-close-gray" onclick="deleteVideoItem(this)" ></button>
<div class="videoBox">
<video autoplay="" controls="" loop="" muted="" width="181px" height="120px">
<source src=${vidUrl}>
</video>
</div>
<div class="textBox">
<em>[PC/M]</em>
<p>${_obj["title"]}</p>
<button type="button" class="btn-ss btn-gray-line" onclick="previewVideoItem(this)">미리보기</button>
</div>`;
if(file.name !== "width.mp4" && file.name !== "length.mp4" && file.name !== "square.mp4"){
return this.emit("videoUploadFail", file , "파일명 이슈");
return false;
}
document.getElementById("mobonVideoUpload").appendChild(_div);
file.previewElement = _div;
return this.emit("videoUploadSucess", file);
}else{
document.getElementById("mobonImageUpload").appendChild(file.previewElement);
}
} else {
this.options.previewArea.appendChild(file.previewElement);
}
for (var _iterator3 = file.previewElement.querySelectorAll("[data-dz-name]"), _isArray3 = true, _i3 = 0, _iterator3 = _isArray3 ? _iterator3 : _iterator3[Symbol.iterator]();;) {
var _ref3;
if (_isArray3) {
if (_i3 >= _iterator3.length) break;
_ref3 = _iterator3[_i3++];
} else {
_i3 = _iterator3.next();
if (_i3.done) break;
_ref3 = _i3.value;
}
var node = _ref3;
node.textContent = file.name;
}
for (var _iterator4 = file.previewElement.querySelectorAll("[data-dz-size]"), _isArray4 = true, _i4 = 0, _iterator4 = _isArray4 ? _iterator4 : _iterator4[Symbol.iterator]();;) {
if (_isArray4) {
if (_i4 >= _iterator4.length) break;
node = _iterator4[_i4++];
} else {
_i4 = _iterator4.next();
if (_i4.done) break;
node = _i4.value;
}
node.innerHTML = this.filesize(file.size);
}
if (this.options.addRemoveLinks) {
//file._removeLink = Dropzone.createElement("<a class=\"dz-remove\" href=\"javascript:undefined;\" data-dz-remove>" + this.options.dictRemoveFile + "</a>");
file._removeLink = Dropzone.createElement("<a class=\"dz-remove btn-del\" href=\"javascript:undefined;\" class=\"btn-del\" data-dz-remove>삭제</a>");
file.previewElement.appendChild(file._removeLink);
}
var removeFileEvent = function removeFileEvent(e) {
e.preventDefault();
e.stopPropagation();
if (file.status === Dropzone.UPLOADING) {
return Dropzone.confirm(_this2.options.dictCancelUploadConfirmation, function () {
return _this2.removeFile(file);
});
} else {
if (_this2.options.dictRemoveFileConfirmation) {
return Dropzone.confirm(_this2.options.dictRemoveFileConfirmation, function () {
return _this2.removeFile(file);
});
} else {
return _this2.removeFile(file);
}
}
};
for (var _iterator5 = file.previewElement.querySelectorAll("[data-dz-remove]"), _isArray5 = true, _i5 = 0, _iterator5 = _isArray5 ? _iterator5 : _iterator5[Symbol.iterator]();;) {
var _ref4;
if (_isArray5) {
if (_i5 >= _iterator5.length) break;
_ref4 = _iterator5[_i5++];
} else {
_i5 = _iterator5.next();
if (_i5.done) break;
_ref4 = _i5.value;
}
var removeLink = _ref4;
removeLink.addEventListener("click", removeFileEvent);
}
}
}
...
...
...
videoUploadSucess: function videoUploadSucess(file){},
videoUploadFail: function videoUploadFail(file,errMsg){},
...
...
...
function deleteVideoItem(element) {
element.parentElement.remove();
}
function previewVideoItem(element){
var videoSrc = $(element).parent().siblings('.videoBox').find("video > source").attr('src');
$(element).parent().parent().append("<div class='videoWrap'><video class='model-vid' autoplay controls loop muted width='100%' height='100%'><source src='" + videoSrc + "' type='video/mp4'></source></video></div>");
}
동영상 썸네일
클라이언트 단에서 동영상 썸네일 추출해야 했기에
캔버스 API를 이용해 동영상 특정 구간을 캔버스에 그린 후 base64 로 인코딩하여 작업하였다. 😛
this.on("videoUploadFail", function (file,errMsg) {
console.debug("videoUploadFail", file);
alert("업로드 파일을 다시 한 번 확인해 주세요. \n가로형 : width.mp4\n세로형 : length.mp4\n사각형 : square.mp4");
var video = document.createElement("video");
video.style.width = "80px";
video.style.height = "80px";
var source = document.createElement("source");
source.src = window.URL.createObjectURL(file);
video.appendChild(source);
var thumbnailCanvas = document.createElement("canvas");
thumbnailCanvas.width = 180;
thumbnailCanvas.height = 180;
var ctx = thumbnailCanvas.getContext("2d");
// 'loadedmetadata' 이벤트를 등록하고, 이벤트가 한 번만 실행되도록 함
video.addEventListener("loadedmetadata", function () {
video.currentTime = 1; // 원하는 시간(초)으로 이동
}, { once: true });
video.addEventListener("seeked", function () {
ctx.drawImage(video, 0, 0, thumbnailCanvas.width, thumbnailCanvas.height);
var thumbnailURL = thumbnailCanvas.toDataURL();
var failEl = document.createElement("div");
failEl.className = "imgItem noimg";
//<button type="button" class="ico-none ico-close-gray"></button>
failEl.innerHTML = `<img src="${thumbnailURL}" width="180px" height="180px">
<div class="textBox">
<p style="overflow-x: hidden;">${file.name}</p>
</div>
<div class="errorLabel error_type01">
<i></i><span>${errMsg}</span>
</div>
`;
document.getElementById("videoFailContainer").innerHTML="";
document.getElementById("videoFailContainer").appendChild(failEl);
myDropzone.removeFile(file);
}, { once: true });
video.load();
});
'Develop > Front-End' 카테고리의 다른 글
[axios] Axios의 사용자 제공 입력의 잘못된 유효성 검사로 인한 CSRF 취약점 CVE-2023-45857 (10) | 2024.09.10 |
---|---|
난독화 된 자바 스크립트 분석하기 (0) | 2023.07.28 |
배너배리에이션 기능 - 자바스크립트 캔버스 API 도전기 (1) | 2023.07.25 |
React 로 롤 전적 통계 사이트를 만들어보자! - 3 (2) | 2023.02.07 |
React 로 롤 전적 통계 사이트를 만들어보자! - 2 (21) | 2022.12.24 |