В ходе тестирования своего сервиса собралось достаточное количество фидбека, что олдскульный способ загрузки файлов в приложение не уносит. Люди хотели drag and drop и пытались перетащить картинки прямо с рабочего стола. Приложение у нас занимает весь экран браузера и написано на флеше, так что прямого способа решить задачу не нашлось.
Поразмыслив и погуглив решили реализовать D&D хотя бы для хрома вот так:
Когда пользователь переключается с вкладки с приложением, т.е. она теряет фокус, поверх флешки накладывается div на который навешано событие отлова дропнутых файлов.
Затем через ExternalInterface изображение в виде ByteArray передается во flash, где оно декодируется и отображается.
Отображение div поверх flash
#over_holder_full
{
background-color: #eee;
opacity: 0.6;
position: absolute;
width: 100%;
height: 100%;
z-index: 7;
display: none;
}
#flash_conainer
{
width: 100%;
height: 100%;
position: absolute;
z-index: 0;
}
<div id="over_holder_full"></div>
<div id="flash_conainer">
<div id="myContent">
<h1>Alternative content</h1>
</div>
</div>
Показываем #over_holder_full при потере фокуса, на который навешан drag and drop
window.addEventListener('blur', function() {
fullDragArea.style.display = "block";
console.log("not focused");
});
window.addEventListener('focus', function() {
fullDragArea.style.display = "none";
console.log("focus");
});
Отлов файла дропнутого на #over_holder_full
fullDragArea = $id("over_holder_full");
// is XHR2 available?
var xhr = new XMLHttpRequest();
if (xhr.upload) {
fullDragArea.addEventListener("dragover", FileDragHover, false);
fullDragArea.addEventListener("drop", FileSelectHandler, false); // <-- то что нас интересует
//....
}
Передача изображения во flash
// file selection
function FileSelectHandler(e) {
fullDragArea.style.display = "none";
// fetch FileList object
var files = e.target.files || e.dataTransfer.files;
//set grey preview in flash
setPreview(pageX, pageY);
// process all File objects
setTimeout(function(){//<-- задерка нужна чтобы команда setPreview успела отправиться во флешку
for (var i = 0, f; f = files[i]; i++) {
ParseFile(f);
}}, 100);
}
// output file information
function ParseFile(file) {
var arrBuffer;
if (file.type.indexOf("image") == 0) {
var reader = new FileReader();
reader.onload = function(e) {
//console.log("arrBuffer + " + e.target.result.byteLength);
var dataview = new DataView(e.target.result);
var ints = new Int32Array(e.target.result.byteLength / 4); //<-- вручную преобразуем изображение в массив байтов
for (var i = 0; i < ints.length; i++) {
ints[i] = dataview.getInt32(i * 4);//<-- теперь записываем по 4 байта (необходимо чтобы потом во флеше нормально преобразовать в ByteArray)
}
console.log("ints" + ints.length);
// display an image
setImage(pageX, pageY, ints);
}
reader.readAsArrayBuffer(file);
}
}
Функции связывающие JavaScript и Flash
function setImage(pageX, pageY, int32Arr) {
if (swfReady){
var res = [];
for (var i = 0; i < int32Arr.length; i++) {
res[i] = int32Arr[i]; //<-- необходимое преобразование т.к. через externalinterface можно передавать только примитивные типы данных, коим Int32Array не является
}
getSWF("DefaultLauncher").setImage(pageX, pageY, res);// первые два параметры - координаты курсора
}
}
function setPreview(pageX, pageY) {
if (swfReady) {
console.log("setPreview");
getSWF("DefaultLauncher").setPreview(pageX, pageY);
}
}
Принимаем изображение на стороне флеша
var exManager:EXManager = new EXManager();
exManager.setImageCallback = function(pageX:Number, pageY:Number, listBytes:Array):void{
var ba:ByteArray = new ByteArray();
for(var i:int = 0; i<listBytes.length; i++){
ba.writeInt(listBytes[i]); //преобразуем в ByteArray
}
var myDecoder:JPEGDecoder = new JPEGDecoder(); //пришлось воспользоваться сторонней библиотекой для декодирования картинки из ByteArray
myDecoder.parse(ba);
var colorComponents:uint = myDecoder.colorComponents;
var numComponents:uint = myDecoder.numComponents;
var pixels:Vector.<uint> = myDecoder.pixels;
var width:uint = myDecoder.width;
var height:uint = myDecoder.height;
var bitmap:BitmapData = new BitmapData ( width, height, false );
bitmap.setVector ( bitmap.rect, pixels );
var bmp:Bitmap = new Bitmap(bitmap);
var oldW:Number = bmp.width;
bmp.width = bmp.width > 500 ? 500 : bmp.width;
bmp.height = bmp.height*(bmp.width / oldW);
bmp.x = pageX;
bmp.y = pageY;
stage.addChild(bmp);
}
exManager.setPreviewCallback = function(pageX:Number, pageY:Number):void{
var sp:Sprite = new Sprite();
sp.graphics.beginFill(0x666666, 0.8);
sp.graphics.drawRect(0,0,100,80);
sp.x = pageX;
sp.y = pageY;
stage.addChild(sp);
}
Исходники можно скачать тут
Замечание:
Данный пример работает только с изображениями с расширением JPG, но его можно легко расширить на любые другие форматы.
Из-за свойства wmode:opaque, которое необходимо чтобы поверх нее отображать div, не работает скролл во флешке. Если есть решения буду рад их услышать.
Чтобы запустить пример необходимо разместить его хотя бы на локальном сервере.
Автор: pltnkv