관련 지식
javascript, jquery, file-download, xmlhttprequest, xhr, progress, canvas

이번 내용은 파일 다운로드시에 프로그레시브 바, 즉 파일 다운로드 진행률을 어떻게 보일수 있는지 설명합니다. 하지만 파일 업로드와는 다르게 다운로드는 브라우저에서 진행률을 보여줄수가 있죠. 따라서 진행률을 보이는 것보다는 파일 다운로드를 이렇게도 할 수 있다는것과 파일을 다운로드 완료 시점을 알아낼수 있다는 것에 주목하면 좋을것 같습니다.

진행률 보이기

파일 다운로드 진행률은 canvas를 이용하겠습니다. 이 코드는 아래와 같은 이미지를 보여줄 것입니다.

  1. <canvas id="canvas" style="display:none"></canvas>
  1. function showPer(per) {
  2. ctx.clearRect(0, 0, 400, 400);
  3. //바깥쪽 써클 그리기
  4. ctx.strokeStyle = "#f66";
  5. ctx.lineWidth = 10;
  6. ctx.beginPath();
  7. ctx.arc(60, 60, 50, 0, Math.PI * 2 * per / 100);
  8. ctx.stroke();
  9. //숫자 올리기
  10. ctx.font = '32px serif';
  11. ctx.fillStyle = "#000";
  12. ctx.textAlign = 'center';
  13. ctx.textBaseline = 'middle';
  14. ctx.fillText(per + '%', 60, 60);
  15. }

써클을 그리기 위해선 arc() 함수를 사용해야 합니다. 원을 그리기 위한 각도가 흔히 아는 각을 쓰지 않고 라디안 값을 사용하기 때문에 MDN의 설명을 한번 읽어보시는게 좋습니다

https://developer.mozilla.org/ko/docs/Web/HTML/Canvas/Tutorial/Drawing_shapes#%ED%98%B8(arc)

파일 다운로드

다운로드와 관련된 여러 외부 플러그인이나 라이브러리가 있지만 iframe 을 이용한 파일 다운로드 방식이 많습니다. 그러나 iframe 을 이용한 방식은 다운로드 완료 시점을 알 수가 없기 때문에 ajax를 이용하여 직접 만들어보겠습니다.

ajax 호출은 평소에 사용하던 형태와 크게 다르진 않지만 xhrFields 속성을 반드시 재정의 해야 합니다. ajax 는 response 데이터를 기본적으로 문자로 처리하기 때문에 cssjsonhtml 같이 텍스트 파일이 아닌것을 받으려고 할 경우 잘못된 데이터를 받게 됩니다. 따라서 xhrFields 속성을 지정하여 문자가 아니라 바이너리로 처리하게 해야 합니다.

  1. var url = '/img/bigsize.jpg';
  2. $("#btnSubmit").on("click", function(e) {
  3. $.ajax({
  4. url: url,
  5. type : 'get',
  6. xhrFields: { //response 데이터를 바이너리로 처리한다.
  7. responseType: 'blob'
  8. },
  9. success : function(data) {
  10. console.log("완료");
  11. var blob = new Blob([data]);
  12. //파일저장
  13. if (navigator.msSaveBlob) {
  14. return navigator.msSaveBlob(blob, url);
  15. }
  16. else {
  17. var link = document.createElement('a');
  18. link.href = window.URL.createObjectURL(blob);
  19. link.download = url;
  20. link.click();
  21. }
  22. }
  23. });
  24. });

파일을 저장하는 부분은 이전에 올린 ‘html2canvas 를 이용한 웹 화면 캡쳐’ 에서 사용했던 방법을 이용하였습니다.

다운로드 진행률 알아내기

하지만 위 내용으로는 다운로드는 가능하지만 다운로드 진행률은 알아낼 수 없습니다. AJAX에서 사용할 XMLHttpRequest 에 progress 에 대한 이벤트 리스너를 추가하면 다운로드 진행 과정을 전달 받을 수 있습니다.

  1. xhr: function() { //XMLHttpRequest 재정의 가능
  2. var xhr = $.ajaxSettings.xhr();
  3. xhr.onprogress = function(e) {
  4. //퍼센트가 100이 되었을때 다운로드 완료
  5. showPer(Math.floor(e.loaded / e.total * 100));
  6. };
  7. return xhr;
  8. },

끝났습니다. 아주 쉬워요

정리

이 다운로드 방식은 매우 독특합니다. 일반적으로 리소스의 링크를 클릭하여 다운로드를 하는 경우 아래의 흐름대로 동작합니다.

  1. 다운로드 링크 클릭
  2. 브라우저에서 서버로 파일 다운로드 요청
  3. PC의 저장될 경로 선택
  4. 파일 다운로드(시간 오래 걸림)
  5. 저장 완료

그러나 이번에 만든 방식은 아래처럼 동작합니다.

  1. 다운로드 링크 클릭
  2. 브라우저에서 서버로 파일 다운로드 요청
  3. 브라우저에 파일 다운로드(시간 오래 걸림)
  4. PC의 저장될 경로 선택
  5. 저장 완료

AJAX로 파일을 다운로드 할 경우엔 저장될 경로 선택창이 뜨기 전까지가 느립니다. 따라서 진행률 표시 같은것이 없다면 다운로드 링크 클릭후 아무 반응이 없다고 느낄수 있습니다.

제가 100MB 파일까지 다운로드 테스트 했을땐 이상 없었지만 그 이상의 대용량 파일에서 문제가 있을지는 모르겠습니다. 용량이 작을땐 문제가 없으므로 파일 다운로드 완료 시점을 알아야 하는 특수한 상황에선 안심하고 사용하셔도 될것 같습니다.

최종 소스

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <title>CSS Template</title>
  5. <meta charset="utf-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1">
  7. <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
  8. </head>
  9. <body>
  10. <canvas id="canvas" style="display:none"></canvas>
  11. <input type="button" id="btnSubmit" value="다운로드"/>
  12. <script>
  13. var canvas = document.getElementById("canvas");
  14. var ctx = canvas.getContext("2d");
  15. function showPer(per) {
  16. ctx.clearRect(0, 0, 400, 400);
  17. //바깥쪽 써클 그리기
  18. ctx.strokeStyle = "#f66";
  19. ctx.lineWidth=10;
  20. ctx.beginPath();
  21. ctx.arc(60, 60, 50, 0, Math.PI * 2 * per / 100);
  22. ctx.stroke();
  23. //숫자 올리기
  24. ctx.font = '32px serif';
  25. ctx.fillStyle = "#000";
  26. ctx.textAlign = 'center';
  27. ctx.textBaseline = 'middle';
  28. ctx.fillText(per + '%', 60, 60);
  29. }
  30. var url = '/img/bigsize.dat';
  31. $("#btnSubmit").on("click", function(e) {
  32. $.ajax({
  33. url: url,
  34. type : 'get',
  35. xhrFields: { //response 데이터를 바이너리로 처리한다.
  36. responseType: 'blob'
  37. },
  38. beforeSend : function() { //ajax 호출전 progress 초기화
  39. showPer(0);
  40. canvas.style.display = 'block';
  41. },
  42. xhr: function() { //XMLHttpRequest 재정의 가능
  43. var xhr = $.ajaxSettings.xhr();
  44. xhr.onprogress = function(e) {
  45. showPer(Math.floor(e.loaded / e.total * 100));
  46. };
  47. return xhr;
  48. },
  49. success : function(data) {
  50. console.log("완료");
  51. var blob = new Blob([data]);
  52. //파일저장
  53. if (navigator.msSaveBlob) {
  54. return navigator.msSaveBlob(blob, url);
  55. }
  56. else {
  57. var link = document.createElement('a');
  58. link.href = window.URL.createObjectURL(blob);
  59. link.download = url;
  60. link.click();
  61. }
  62. },
  63. complete : function() {
  64. canvas.style.display = 'none';
  65. }
  66. });
  67. });
  68. </script>
  69. </body>
  70. </html>