우수사례 - Chrome에서 드래그 앤 드롭 다운로드

소개

드래그 앤 드롭 (DnD)은 HTML 5의 여러 뛰어난 기능 중 하나이며 Firefox 3.5, Safari, Chrome 및 IE에서 지원됩니다. Google은 최근 Chrome 사용자가 브라우저에서 데스크톱으로 파일을 드래그 앤 드롭할 수 있는 새로운 기능을 출시했습니다. 이 기능은 매우 편리한 기능이지만 Ryan Seddon이 이 새로운 기능에 관한 리버스 엔지니어링 발견에 관한 기사를 게시하기 전에는 널리 알려지지 않았습니다.

Box.net은 이러한 새로운 기능이 어떻게 클라우드 콘텐츠 관리 솔루션을 개선하고 개발자 커뮤니티에 더 기여할 수 있게 되는지 매우 기대하고 있습니다. DnD 다운로드가 제품에 통합되었다는 소식을 전해드립니다. 이제 Box 사용자는 Chrome 브라우저에서 데스크톱으로 직접 파일을 드래그하여 파일을 다운로드하고 저장할 수 있습니다.

이 새로운 기능을 개발하는 동안 제가 여러 번 반복한 방법을 공유해 드리고자 합니다.

드래그 앤 드롭 API 지원 확인

먼저 브라우저에서 HTML5 드래그 앤 드롭을 완벽하게 지원하는지 확인해야 합니다. 이를 수행하는 쉬운 방법은 Modernizr라는 라이브러리를 사용하여 특정 기능을 확인하는 것입니다.

if (Modernizr.draganddrop) {
// Browser supports native HTML5 DnD.
} else {
// Fallback to a library solution.
}

반복 1

처음에는 Seddon이 Gmail에서 찾은 접근 방식을 시도했습니다. 파일의 앵커 링크에 'data-downloadurl'이라는 새 속성을 추가했습니다. 이 과정에서 HTML5의 맞춤 데이터 속성이 사용됩니다. data-downloadurl에는 파일의 MIME 유형, 대상 파일 이름(다운로드한 파일에서 원하는 파일 이름), 파일의 다운로드 URL을 포함해야 합니다. 따라서 다음은 HTML 템플릿에 추가됩니다.

<a href="#" class="dnd"
data-downloadurl="{$item.mime}:{$item.filename}:{$item.url}"></a>

다음과 같은 출력이 생성됩니다.

<a href="#" class="dnd" data-downloadurl=
"image/jpeg:Penguins.jpg:https://www.box.net/box_download_file?file_id=f66690"></a>

저는 Seddon의 글을 바탕으로 von Schorsch가 만든 jQuery plugin을 기반으로 약간의 브라우저 기능 감지를 수행하는 jQuery 플러그인을 추가했습니다. 제가 폰 슈르쉬의 버전에 추가한 줄이 강조표시되어 있습니다.

(function($) {

$.fn.extend({
dragout: function() {
var files = this;
if (files.length > 0) {
    $(files).each(function() {
    var url = (this.dataset && this.dataset.downloadurl) ||
                this.getAttribute("data-downloadurl");
    if (this.addEventListener) {
        this.addEventListener("dragstart", function(e) {
        if (e.dataTransfer && e.dataTransfer.constructor == Clipboard &&
            e.dataTransfer.setData('DownloadURL', 'http://www.box.net')) {
            e.dataTransfer.setData("DownloadURL", url);
        }
        },false);
    }
    });
}
}
});

})(jQuery);

사전 브라우저 감지 없이 IE의 HTML 요소에 addEventListener()를 실행하면 자바스크립트 오류가 발생하기 때문입니다. IE는 자체 connectEvent() 메서드를 사용하기 때문입니다. e.dataTransfer는 현재 IE에서 정의되지 않았으며, e.dataTransfer.structor는 Firefox(Mozilla)에서 DataTransfer를 반환하고 Webkit 브라우저(Chrome 및 Safari)는 클립보드 생성자를 구현합니다. Safari에서 e.dataTransfer.setData('DownloadURL','http://www.box.net')는 false를 반환하고 Chrome은 이 문에 true를 반환합니다. 위에서 언급한 모든 테스트를 수행하면 Chrome에서만 이 기능을 사용할 수 있습니다. 다음과 같이 간단히 대처할 수 있다고 주장하실 수도 있습니다.

/chrome/.test( navigator.userAgent.toLowerCase() )

하지만 기술적으로 DnD 다운로드가 작동할지 감지하지는 못하지만 브라우저 감지보다 기능 감지를 선호합니다.

반복 문제 1

1) 현재 온페이지 DND를 사용하여 폴더 간에 파일을 이동하거나 복사할 수 있으므로 DnD 다운로드와 온페이지 DNS를 구분할 방법이 필요합니다. 기술적으로 이 두 작업을 결합할 수는 없습니다. 사용자가 Box.net 계정 내의 다른 폴더로 파일을 이동하려고 하는지 아니면 데스크톱으로 드래그하려고 하는지 예측할 수 없습니다. 이 두 가지 작업은 완전히 다릅니다. 게다가 커서가 브라우저 창 밖에 있는지 쉽게 감지할 수도 없습니다. window.onmouseout (IE) 및 document.onmouseout (다른 브라우저)을 사용하여 마우스아웃 이벤트를 문서에 연결하고 e.relatedTarget.nodeName == "HTML" (e가 마우스아웃 이벤트 또는 window.event 중 사용 가능한 것임)를 확인할 수 있습니다. 하지만 이벤트 버블링으로 인해 이는 꽤 어렵습니다. 특히 Box.net과 같은 복잡한 웹 앱에서 이미지나 레이어 위에 있을 때 이벤트가 무작위로 트리거될 수 있습니다.

2) 사용자가 실수로 데스크톱으로 항목을 드래그하지 않도록 명시적으로 무언가를 해야 합니다. Box 폴더의 편집자가 이 파일을 다운로드하는 사람의 컴퓨터에서 원하지 않는 작업을 수행하는 실행 파일을 업로드할 수 있습니다. 파일이 데스크톱에 다운로드되는 시점을 사용자가 정확히 알 수 있도록 하려고 합니다.

반복 2

우리는 Ctrl + 드래그 (Windows Ctrl 키를 누를 때 파일 드래그)를 실험하기로 결정했습니다. 이는 사용자가 Windows 데스크톱에서 파일을 복제하기 위해 할 수 있는 작업과 일치합니다. 또한 파일이 실수로 다운로드되지 않도록 하려면 사용자의 추가 작업 (추가 작업 아님)이 필요합니다.

반복 1의 jQuery 플러그인은 이제 DnD 다운로드를 온페이지 DND와 긴밀하게 통합해야 하므로 폐기됩니다. 관심이 있으신 분들을 위해 jQuery UI의 Draggable 플러그인의 수정된 버전을 사용합니다. 타겟 요소의 마우스다운 이벤트 내에 다음 코드를 삽입합니다.

// DnD to desktop when the Ctrl key is pressed while dragging
if (e.ctrlKey) {
var that = $(e.target);
// make sure it is not IE (attachEvent).
if (that[0].addEventListener) {
    that[0].addEventListener("dragstart",function(e) {
        // e.dataTransfer in Firefox uses the DataTransfer constructor
        // instead of Clipboard
        // make sure it's Chrome and not Safari (both webkit-based).
        // setData on DownloadURL returns true on Chrome, and false on Safari
        if (e.dataTransfer && e.dataTransfer.constructor == Clipboard &&
            e.dataTransfer.setData('DownloadURL','http://www.box.net')) {
        var url = (this.dataset && this.dataset.downloadurl) ||
                    this.getAttribute("data-downloadurl");
        e.dataTransfer.setData("DownloadURL", url);
        }
    }, false);
    return;
}
}

Ctrl 키를 사용 설정하는 것 외에 사용자가 일반적인 페이지 내 드래그를 실행할 때 표시되는 작은 토스터 도움말도 추가했습니다. Ctrl 키를 누른 상태에서 파일 아이콘을 데스크톱으로 드래그하면 파일을 다운로드할 수 있다고 사용자에게 알립니다.

반복 문제 2

보안 문제로 인해 Box.net은 정적 파일에 직접 액세스할 수 있는 영구 URL을 노출하지 않습니다. 이는 Box.net의 고유한 특징이 아닙니다. 모든 온라인 저장소 서비스에서는 파일이 공개되어 있고 적절한 권한이 있는 사용자가 의도한 다운로드를 요청했는지 여부를 확인하기 위한 추가 보안 계층 없이 영구 URL을 노출해서는 안 됩니다.

항목의 '다운로드 URL' (예: https://www.box.net/box_download_file?file_id=f_60466690)을 따르면 '302 Found' 상태 코드가 반환되고 파일의 임시 '실제 URL'인 임의의 URL(예: https://www.box.net/dl/6045?a=1f1207a084&m=168299,11211&t=2&b=aca15820d924e3b)로 리디렉션됩니다. 문제는 몇 분마다 만료되므로 HTML 출력에 배치하는 것은 비실용적입니다. 사용자가 몇 분 전에 생성된 HTML 출력의 링크로 파일을 다운로드하려고 하면 '404'가 반환될 수 있습니다.

DND 다운로드는 리소스로 직접 연결되는 실제 URL에서만 작동합니다. 리디렉션이 포함된 경우 현재 체인을 따를 만큼 스마트하지 않습니다(보안으로 인해 체인을 따라가지 않아야 함). 따라서 위의 링크 https://www.box.net/box_download_file?file_id=f_60466690을 사용하면 브라우저 주소 표시줄에 입력할 때 파일을 다운로드할 수 있지만 DnD에서는 작동하지 않습니다.

'실제 URL'과 '리디렉션 URL'의 차이를 더 잘 설명하려면 스크린샷을 참조하세요.

302 리디렉션 URL
302 리디렉션 URL
실제 URL
실제 URL

반복 3

Ajax를 사용해 보겠습니다.

이전 반복에서 코드를 약간 수정하여 다음을 만들었습니다.

// DnD to desktop when the Ctrl key is pressed while dragging
if (e.ctrlKey) {
var that = $(e.target);
// make sure it is not IE (attachEvent).
if (that[0].addEventListener) {
that[0].addEventListener("dragstart", function(e) {
    // e.dataTransfer in Firefox uses the DataTransfer constructor
    // instead of Clipboard
    // make sure it's Chrome and not Safari (both webkit-based).
    // setData on DownloadURL returns true on Chrome, and false on Safari
    if (e.dataTransfer && e.dataTransfer.constructor == Clipboard &&
        e.dataTransfer.setData('DownloadURL', 'http://www.box.net')) {
    var url = (this.dataset && this.dataset.downloadurl) ||
                this.getAttribute("data-downloadurl");
    $.ajax({
        complete: function(data) {
        e.dataTransfer.setData("DownloadURL", data.responseText);
        },
        type:'GET',
        url: url
    });
    }
}, false);
return;
}
}

합리적입니다. 드래그 스타트 시 즉시 서버에 Ajax를 호출하여 파일의 최신 다운로드 URL을 검색합니다. 하지만 작동하지 않습니다.

그것은 동기 호출 (또는 내가 부르고 싶은 대로 Sjax)여야 하는 것으로 밝혀졌습니다. 이벤트 리스너가 연결될 때 setData가 실행되어야 하는 것 같습니다. jQuery API에 따르면 강조 표시된 줄은 다음과 같이 됩니다.

$.ajax({
async: false,
complete: function(data) {
e.dataTransfer.setData("DownloadURL", data.responseText);
},
type: 'GET',
url: url
});

네트워크 연결을 끊을 때까지 잘 작동합니다. 동기 호출을 실행하므로 호출이 성공할 때까지 브라우저가 정지됩니다. Ajax 호출이 실패해도 (404 또는 전혀 응답하지 않는 경우) 브라우저는 충돌한 것처럼 전혀 지연시키지 않습니다.

다음과 같이 하는 것이 훨씬 안전합니다.

$.ajax({
async: false,
complete: function(data) {
e.dataTransfer.setData("DownloadURL", data.responseText);
},
error: function(xhr) {
if (xhr.status == 404) {
    xhr.abort();
}
},
type: 'GET',
timeout: 3000,
url: url
});

이 기능의 데모를 사용하려면 Box.net 계정에 정적 파일을 업로드하세요. Ctrl 키를 누른 상태에서 파일 아이콘을 바탕화면으로 드래그합니다. 계정이 없는 경우, 계정을 만드는 데는 30초도 걸리지 않습니다.

이 기능을 사용하면 창의력을 발휘하여 많은 것을 이룰 수 있습니다. Windows 프린터 대화상자로 이미지를 드래그하면 이미지가 즉시 인쇄됩니다. Box에서 휴대전화 드라이브로 노래를 복사하고 Box에서 메신저 클라이언트로 파일을 드래그하여 친구에게 바로 전송할 수 있습니다. 이렇게 하면 생산성을 높일 수 있는 무궁무진한 가능성이 열립니다.

프린터에 파일 비우기
파일을 프린터로 드래그
메신저 클라이언트로 파일 드래그
파일을 메신저 클라이언트로 드래그합니다.

의견 및 향후 개선사항

이는 여전히 이상적인 방법은 아닙니다. 동기 호출을 실행하면 잠시 브라우저를 잠글 수 있기 때문입니다. 웹 작업자는 비동기식이어야 하므로 HTML 5 웹 작업자도 도움이 되지 않습니다. 이벤트 리스너가 연결될 때 setData가 실행되어야 하는 것으로 보입니다.

실제로 성능이 꽤 괜찮은 수준입니다. 동기 Ajax (Sjax) 호출은 URL 문자열만 가져오며, 매우 빠릅니다. 하지만 HTTP 헤더에 큰 오버헤드가 발생하며 WebSockets로 해결할 수 있습니다. 그러나 이러한 종류의 기술이 더 많이 사용될 때까지는 WebSocket을 사용하여 모든 작은 업데이트를 클라이언트에 전송할 가치가 없습니다.

또한 향후 API에 다중 파일 다운로드 기능이 추가되기를 바랍니다. 사용자 인터페이스에서 여러 파일을 선택하는 맞춤 체크박스와 결합하면 놀라울 것입니다. 또한 제출된 양식의 결과에서 생성된 텍스트 파일과 같이 클라이언트가 생성한 파일을 이러한 방식으로 다운로드할 수 있다면 더욱 좋습니다.

  • 열 dnd
  • 목록 재정렬
  • 이미지 갤러리 만들기
  • 캔버스 이미지 내보내기

참조