Набор JavaScript инструментов для работы с файлами.
<div>
<!-- "js-fileapi-wrapper" -- обязательный class -->
<div class="js-fileapi-wrapper upload-btn" id="choose">
<div class="upload-btn__txt">Choose files</div>
<input name="files" type="file" multiple />
</div>
<div id="images"><!-- предпросмотр --></div>
</div>
<script>window.FileAPI = { staticPath: '/js/FileAPI/dist/' };</script>
<script src="/js/FileAPI/dist/FileAPI.min.js"></script>
<script>
FileAPI.event.on(choose, 'change', function (evt){
var files = FileAPI.getFiles(evt); // Retrieve file list
FileAPI.filterFiles(files, function (file, info/**Object*/){
if( /^image/.test(file.type) ){
return info.width >= 320 && info.height >= 240;
}
return false;
}, function (files/**Array*/, rejected/**Array*/){
if( files.length ){
// Создаем предпросмотр 100x100
FileAPI.each(files, function (file){
FileAPI.Image(file).preview(100).get(function (err, img){
images.appendChild(img);
});
});
// Загружаем файлы
FileAPI.upload({
url: './ctrl.php',
files: { images: files },
progress: function (evt){ /* ... */ },
complete: function (err, xhr){ /* ... */ }
});
}
});
});
</script>
Отредактируйте файл crossdomain.xml
и разместите его в корне домена, на который будут загружаться файлы.
<script>
window.FileAPI = {
debug: false // дебаг режим, смотрите Console
, cors: false // если используете CORS -- `true`
, media: false // если используете веб-камеру -- `true`
, staticPath: '/js/FileAPI/dist/' // путь к '*.swf'
, postNameConcat: function (name, idx){
// Default: object[foo]=1&object[bar][baz]=2
// .NET: https://github.com/mailru/FileAPI/issues/121#issuecomment-24590395
return name + (idx != null ? '['+ idx +']' : '');
}
};
</script>
<script src="/js/FileAPI/dist/FileAPI.min.js"></script>
<!-- ИЛИ -->
<script>
window.FileAPI = { /* etc. */ };
require(['FileAPI'], function (FileAPI){
// ...
});
</script>
Получить список файлов из input
элемента, или event
, также поддерживается jQuery
.
- input —
HTMLInputElement
,change
иdrop
события,jQuery
коллекция илиjQuery.Event
var el = document.getElement('my-input');
FileAPI.event.on(el, function (evt/**Event*/){
// Получить список файлов из `input`
var files = FileAPI.getFiles(el);
// или события
var files = FileAPI.getFiles(evt);
});
Получить информацию о файле (см. FileAPI.addInfoReader).
- file — объект файла (https://developer.mozilla.org/en-US/docs/DOM/File)
- callback — функция, вызывается по завершению сбора информации
// Получить информацию о изображении (FileAPI.exif.js подключен)
FileAPI.getInfo(file, function (err/**String*/, info/**Object*/){
if( !err ){
console.log(info); // { width: 800, height: 600, exif: {..} }
}
});
// Получить информацию о mp3 файле (FileAPI.id3.js included)
FileAPI.getInfo(file, function (err/**String*/, info/**Object*/){
if( !err ){
console.log(info); // { title: "...", album: "...", artists: "...", ... }
}
});
Отфильтровать список файлов, используя дополнительную информацию о них. см. FileAPI.getInfo или FileAPI.addInfoReader.
- files — оригинальный список файлов
- filter — функция, принимает два аргумента:
file
— сам файл,info
— дополнительная информация - callback — функция:
list
— список файлов, подошедшие под условия,other
— все остальные.
// Получаем список файлов
var files = FileAPI.getFiles(input);
// Фильтруем список
FileAPI.filterFiles(files, function (file/**Object*/, info/**Object*/){
if( /^image/.test(file.type) && info ){
return info.width > 320 && info.height > 240;
} else {
return file.size < 20 * FileAPI.MB;
}
}, function (list/**Array*/, other/**Array*/){
if( list.length ){
// ..
}
});
Получить весь список файлов, включая директории.
- evt —
drop
event - callback — фнукция, принимает один аргумент — список файлов
FileAPI.event.on(document, 'drop', function (evt/**Event*/){
evt.preventDefault();
// Получаем все файлы
FileAPI.getDropFiles(evt, function (files/**Array*/){
// ...
});
});
Загрузка файлов на сервер (последовательно). Возвращает XHR-подобный объект. Помните, для корректной работы flash-транспорта, тело ответа сервера не должно быть пустым, например можно ответить простым текстом "ok".
- opts — объект настрое, см. раздел Upload options
var el = document.getElementById('my-input');
FileAPI.event.on(el, 'change', function (evt/**Event*/){
var files = FileAPI.getFiles(evt);
var xhr = FileAPI.upload({
url: 'http://rubaxa.org/FileAPI/server/ctrl.php',
files: { file: files[0] },
complete: function (err, xhr){
if( !err ){
var result = xhr.responseText;
// ...
}
}
});
});
Добавить обработчик, для сбора информации о файле. см. также: FileAPI.getInfo и FileAPI.filterFiles.
- mime — маска mime-type
- handler — функция, принимает два аргумента:
file
объект иcomplete
функция обратного вызова
FileAPI.addInfoReader(/^image/, function (file/**File*/, callback/**Function*/){
// http://www.nihilogic.dk/labs/exif/exif.js
// http://www.nihilogic.dk/labs/binaryajax/binaryajax.js
FileAPI.readAsBinaryString(file, function (evt/**Object*/){
if( evt.type == 'load' ){
var binaryString = evt.result;
var oFile = new BinaryFile(binaryString, 0, file.size);
var exif = EXIF.readFromBinaryFile(oFile);
callback(false, { 'exif': exif || {} });
}
else if( evt.type == 'error' ){
callback('read_as_binary_string');
}
else if( evt.type == 'progress' ){
// ...
}
});
});
Чтение содержимого указанного файла как dataURL.
- file — файл для чтения
- callback — функция обработчик
FileAPI.readAsDataURL(file, function (evt/**Object*/){
if( evt.type == 'load' ){
// Всё хорошо
var dataURL = evt.result;
} else if( evt.type =='progress' ){
var pr = evt.loaded/evt.total * 100;
} else {
// Ошибка
}
})
Чтение содержимого указанного файла как BinaryString
.
- file — файл для чтения
- callback — функция обработчик
FileAPI.readAsBinaryString(file, function (evt/**Object*/){
if( evt.type == 'load' ){
// Всё хорошо
var binaryString = evt.result;
} else if( evt.type =='progress' ){
var pr = evt.loaded/evt.total * 100;
} else {
// Ошибка
}
})
Чтение содержимого указанного файла как ArrayBuffer
.
- file — файл для чтения
- callback — функция обработчик
FileAPI.readAsArrayBuffer(file, function (evt/**Object*/){
if( evt.type == 'load' ){
// Всё хорошо
var arrayBuffer = evt.result;
} else if( evt.type =='progress' ){
var pr = evt.loaded/evt.total * 100;
} else {
// Ошибка
}
})
Чтение содержимого указанного файла как text
.
- file — файл для чтения
- callback — функция обработчик
FileAPI.readAsText(file, function (evt/**Object*/){
if( evt.type == 'load' ){
// Всё хорошо
var text = evt.result;
} else if( evt.type =='progress' ){
var pr = evt.loaded/evt.total * 100;
} else {
// Ошибка
}
})
Чтение содержимого указанного файла как text
в нужной кодировке.
- encoding — строкой с указанием кодировки. По умолчанию UTF-8.
FileAPI.readAsText(file, "utf-8", function (evt/**Object*/){
if( evt.type == 'load' ){
// Всё хорошо
var text = evt.result;
} else if( evt.type =='progress' ){
var pr = evt.loaded/evt.total * 100;
} else {
// Ошибка
}
})
Строка, содержащая адрес, на который отправляется запрос.
Дополнительные данные, которые должны быть отправлены вместе с файлом.
var xhr = FileAPI.upload({
url: '...',
data: { 'session-id': 123 },
files: { ... },
});
Дополнительные заголовки запроса, только HTML5.
var xhr = FileAPI.upload({
url: '...',
headers: { 'x-upload': 'fileapi' },
files: { .. },
});
Размер части файла в байта, только HTML5.
var xhr = FileAPI.upload({
url: '...',
files: { images: fileList },
chunkSize: 0.5 * FileAPI.MB
});
Количество попыток загрузки одной части, только HTML5.
var xhr = FileAPI.upload({
url: '...',
files: { images: fileList },
chunkSize: 0.5 * FileAPI.MB,
chunkUploadRetry: 3
});
--
Правила модификации оригинально изображения.
var xhr = FileAPI.upload({
url: '...',
files: { image: imageFiles },
// Changes the original image
imageTransform: {
// Ресайз по боьшой строне
maxWidth: 800,
maxHeight: 600,
// Добавляем водяной знак
overlay: [{ x: 10, y: 10, src: '/i/watemark.png', rel: FileAPI.Image.RIGHT_BOTTOM }]
}
});
--
Правила для нарезки дополнительных изображения на клиенте.
var xhr = FileAPI.upload({
url: '...',
files: { image: imageFiles },
imageTransform: {
// Ресайз по большой строне
'huge': { maxWidth: 800, maxHeight: 600 },
// Ресайз и кроп
'medium': { width: 320, height: 240, preview: true },
// ресайз и кроп + водяной знак
'small': {
width: 100, height: 100,
// Добавляем водяной знак
overlay: [{ x: 5, y: 5, src: '/i/watemark.png', rel: FileAPI.Image.RIGHT_BOTTOM }]
}
}
});
--
Конвертация всех изображений в jpeg или png.
var xhr = FileAPI.upload({
url: '...',
files: { image: imageFiles },
imageTransform: {
type: 'image/jpeg',
quality: 0.86 // качество jpeg
}
});
Отправлять исходное изображение на сервер или нет, если определен imageTransform
вариант.
--
Автоматический поворот изображения на основе EXIF.
--
Подготовка опций загрузки для конкретного файла.
var xhr = FileAPI.upload({
url: '...',
files: { .. }
prepare: function (file/**Object*/, options/**Object*/){
options.data.secret = utils.getSecretKey(file.name);
}
});
--
Начало загрузки
var xhr = FileAPI.upload({
url: '...',
files: { .. }
upload: function (xhr/**Object*/, options/**Object*/){
// ...
}
});
--
Начало загрузки файла
var xhr = FileAPI.upload({
url: '...',
files: { .. }
fileupload: function (file/**Object*/, xhr/**Object*/, options/**Object*/){
// ...
}
});
--
Общий прогресс загрузки файлов.
var xhr = FileAPI.upload({
url: '...',
files: { .. }
progress: function (evt/**Object*/, file/**Object*/, xhr/**Object*/, options/**Object*/){
var pr = evt.loaded/evt.total * 100;
}
});
--
Прогресс загрузки файла.
var xhr = FileAPI.upload({
url: '...',
files: { .. }
fileprogress: function (evt/**Object*/, file/**Object*/, xhr/**Object*/, options/**Object*/){
var pr = evt.loaded/evt.total * 100;
}
});
--
Завершение загрузки всех файлов.
var xhr = FileAPI.upload({
url: '...',
files: { .. }
complete: function (err/**String*/, xhr/**Object*/, file/**Object/, options/**Object*/){
if( !err ){
// Все файлы загружены успешно
}
}
});
--
Конец загрузки файла.
var xhr = FileAPI.upload({
url: '...',
files: { .. }
filecomplete: function (err/**String*/, xhr/**Object*/, file/**Object/, options/**Object*/){
if( !err ){
// Файл загружен успешно
var result = xhr.responseText;
}
}
});
Имя файла.
MIME type
Размер файла в байтах.
Добавить функцию обработки события.
- el — DOM элемент.
- events — одно или нескольких разделенных пробелами типов событий.
- handler — функция обработчик события.
Удалить обработчик события.
- el — DOM элемент
- events — одно или нескольких разделенных пробелами типов событий.
- handler — функции обработчика ранее назначения на
event
.
Добавить функцию обработки события. Обработчик выполняется не более одного раза.
- el — DOM элемент.
- events — одно или нескольких разделенных пробелами типов событий.
- handler — функция обработчик события.
Добавить функцию обработки событий drag
и drop
.
- el — DOM элемент
- hover —
dragenter
иdragleave
слушатель - handler — обработчик события
drop
var el = document.getElementById('dropzone');
FileAPI.event.dnd(el, function (over){
el.style.backgroundColor = over ? '#f60': '';
}, function (files){
if( files.length ){
// Загружаем их.
}
});
// или jQuery
$('#dropzone').dnd(hoverFn, dropFn);
Удалить функцию обработки событий drag
и drop
.
- el — DOM элемент
- hover —
dragenter
иdragleave
слушатель - handler — обработчик события
drop
// Native
FileAPI.event.dnd.off(el, hoverFn, dropFn);
// jQuery
$('#dropzone').dndoff(hoverFn, dropFn);
--
Класс для работы с изображениями
Конструктор получает только один параметр, файл.
- file — файл изображения
FileAPI.Image(imageFile).get(function (err/**String*/, img/**HTMLElement*/){
if( !err ){
document.body.appendChild( img );
}
});
Кроп изображения по ширине и высоте.
- width — новая ширина изображения
- height — новая высота изображения
FileAPI.Image(imageFile)
.crop(640, 480)
.get(function (err/**String*/, img/**HTMLElement*/){
})
;
Кроп изображения по ширине и высоте, а также смещению по x и y.
- x — смещение относительно по x левого угла
- y — смещение относительно по y левого угла
FileAPI.Image(imageFile)
.crop(100, 50, 320, 240)
.get(function (err/**String*/, img/**HTMLElement*/){
})
;
Ресайз.
- width — новая ширина
- height — новая высота
- strategy — enum:
min
,max
,preview
. По умолчаниюundefined
.
FileAPI.Image(imageFile)
.resize(320, 240)
.get(function (err/**String*/, img/**HTMLElement*/){
})
;
// По большей стороне
FileAPI.Image(imageFile)
.resize(320, 240, 'max')
.get(function (err/**String*/, img/**HTMLElement*/){
})
;
Кроп и ресайз изображения.
- width — новая ширина
- height — новая высота
FileAPI.Image(imageFile)
.preview(100, 100)
.get(function (err/**String*/, img/**HTMLElement*/){
})
;
Поворот.
- deg — угол поворота в градусах
FileAPI.Image(imageFile)
.rotate(90)
.get(function (err/**String*/, img/**HTMLElement*/){
})
;
Применить фильтр функцию. Только HTML5
.
- callback — принимает два рагумента,
canvas
элемент и методdone
.
FileAPI.Image(imageFile)
.filter(function (canvas/**HTMLCanvasElement*/, doneFn/**Function*/){
// бла-бла-бла
doneFn(); // вызываем по завершению
})
.get(function (err/**String*/, img/**HTMLElement*/){
})
;
Используется CamanJS, подключите его перед библиотекой FileAPI.
- name — название CamanJS фильтра (произвольный, либо предустановленный)
Caman.Filter.register("my-funky-filter", function () {
// http://camanjs.com/guides/#Extending
});
FileAPI.Image(imageFile)
.filter("my-funky-filter") // или .filter("vintage")
.get(function (err/**String*/, img/**HTMLElement*/){
})
;
Добавить наложение, например: водяной знак.
- images — массив наложений
FileAPI.Image(imageFile)
.overlay([
// Левый угл.
{ x: 10, y: 10, w: 100, h: 10, src: '/i/watermark.png' },
// Правый нижний угл.
{ x: 10, y: 10, src: '/i/watermark.png', rel: FileAPI.Image.RIGHT_BOTTOM }
])
.get(function (err/**String*/, img/**HTMLElement*/){
})
;
Получить итоговое изображение.
- fn — функция обратного вызова
Для работы с веб-камерой, обязательно установить параметр FileAPI.media: true
.
Публикация камеры.
- el — куда публикуем
- options — {
width: 100%
,height: 100%
,start: true
} - callback — первый параметр возможная ошибка, второй экземпляр FileAPI.Camera
var el = document.getElementById('cam');
FileAPI.Camera.publish(el, { width: 320, height: 240 }, function (err, cam/**FileAPI.Camera*/){
if( !err ){
// Камера готова, можно использовать
}
});
Включить камеру
- callback — будет вызван в момент готовности камеры
var el = document.getElementById('cam');
FileAPI.Camera.publish(el, { start: false }, function (err, cam/**FileAPI.Camera*/){
if( !err ){
// Включаем камеру
cam.start(function (err){
if( !err ){
// камера готова к использованию
}
});
}
});
Выключить камеру
Сделать снимок с камеры
var el = document.getElementById('cam');
FileAPI.Camera.publish(el, function (err, cam/**FileAPI.Camera*/){
if( !err ){
var shot = cam.shot(); // делаем снимок
// создаем предпросмотр 100x100
shot.preview(100).get(function (err, img){
previews.appendChild(img);
});
// и/или загружаем
FileAPI.upload({
url: '...',
files: { cam: shot
});
}
});
1024 байт
1048576 байт
1073741824 байт
1.0995116e+12 байт
Перебор объект или массив, выполняя функцию для каждого элемента.
- obj — массив или объект
- callback — функция, выполняется для каждого элемента.
- thisObject — объект для использования в качестве
this
при выполненииcallback
.
--
Объединить содержимое двух объектов вместе.
- dst — объект, который получит новые свойства
- src — объект, содержащий дополнительные свойства для объединения
--
Создает новый массив со всеми элементами, которые соответствуют условиям.
- array — оригинальный массив
- callback — функция для проверки каждого элемента массива.
- thisObject — объект для использования в качестве
this
при выполненииcallback
.
- Multiupload: все браузеры поддерживающие HTML5 или Flash
- Drag'n'Drop загрузка: файлы (HTML5) и директории (Chrome 21+)
- Загрузка файлов по частям, только HTML5
- Загрузка одно файла: все браузеры, даже очень старые
-
Работа с изображениями: IE6+, FF 3.6+, Chrome 10+, Opera 11.1+, Safari 5.4+
- crop, resize, preview & rotate (HTML5 или Flash)
- авто ориентация на основе EXIF (HTML5, если подключен FileAPI.exif.js или Flash)
Поддержка HTML5.
Поддержка кроссдоменных запросов.
Поддержка Drag'n'drop событий.
Наличие Flash плагина.
Поддержка canvas.
Поддержка dataURI в качестве src для изображений.
Возможность загрузки по частям.
Флеш очень "глючная" штука :]
Поэтому в случае успешной загрузки http status должен быть только 200 OK
.
Настройки для flash части. Желательно, разместить flash на том же сервере, куда будут загружаться файлы.
<script>
var FileAPI = {
// @default: "./dist/"
staticPath: '/js/',
// @default: FileAPI.staticPath + "FileAPI.flash.swf"
flashUrl: '/statics/FileAPI.flash.swf',
// @default: FileAPI.staticPath + "FileAPI.flash.image.swf"
flashImageUrl: '/statics/FileAPI.flash.image.swf'
};
</script>
<script src="/js/FileAPI.min.js"></script>
Обязательно создайте этот файл на сервере, куда будут загружаться файлы.
Не забудьте заменить youdomain.com
на имя вашего домена.
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
<site-control permitted-cross-domain-policies="all"/>
<allow-access-from domain="youdomain.com" secure="false"/>
<allow-access-from domain="*.youdomain.com" secure="false"/>
<allow-http-request-headers-from domain="*" headers="*" secure="false"/>
</cross-domain-policy>
Пример запроса, который отправляет flash player.
POST /server/ctrl.php HTTP/1.1
Accept: text/*
Content-Type: multipart/form-data;
boundary=----------Ij5ae0ae0KM7GI3KM7
User-Agent: Shockwave Flash
Host: www.youdomain.com
Content-Length: 421
Connection: Keep-Alive
Cache-Control: no-cache
------------Ij5GI3GI3ei4GI3ei4KM7GI3KM7KM7
Content-Disposition: form-data; name="Filename"
MyFile.jpg
------------Ij5GI3GI3ei4GI3ei4KM7GI3KM7KM7
Content-Disposition: form-data; name="Filedata"; filename="MyFile.jpg"
Content-Type: application/octet-stream
[[..FILE_DATA_HERE..]]
------------Ij5GI3GI3ei4GI3ei4KM7GI3KM7KM7
Content-Disposition: form-data; name="Upload"
Submit Query
------------Ij5GI3GI3ei4GI3ei4KM7GI3KM7KM7--
<script>
(function (ctx, jsonp){
'use strict';
var status = {{httpStatus}}, statusText = "{{httpStatusText}}", response = "{{responseBody}}";
try {
ctx[jsonp](status, statusText, response);
} catch (e){
var data = "{\"id\":\""+jsonp+"\",\"status\":"+status+",\"statusText\":\""+statusText+"\",\"response\":\""+response.replace(/\"/g, '\\\\\"')+"\"}";
try {
ctx.postMessage(data, document.referrer);
} catch (e){}
}
})(window.parent, '{{request_param_callback}}');
</script>
<!-- or -->
<?php
include './FileAPI.class.php';
if( strtoupper($_SERVER['REQUEST_METHOD']) == 'POST' ){
// Получим список файлов
$files = FileAPI::getFiles();
// ... ваша логика
// JSONP callback name
$jsonp = isset($_REQUEST['callback']) ? trim($_REQUEST['callback']) : null;
// Ответ сервера: "HTTP/1.1 200 OK"
FileAPI::makeResponse(array(
'status' => FileAPI::OK
, 'statusText' => 'OK'
, 'body' => array('count' => sizeof($files)
), $jsonp);
exit;
}
?>
Включение CORS.
<?php
// Permitted types of request
header('Access-Control-Allow-Methods: POST, OPTIONS');
// Describe custom headers
header('Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Range, Content-Disposition, Content-Type');
// A comma-separated list of domains
header('Access-Control-Allow-Origin: ' . $_SERVER['HTTP_ORIGIN']);
if( $_SERVER['REQUEST_METHOD'] == 'OPTIONS' ){
exit;
}
if( $_SERVER['REQUEST_METHOD'] == 'POST' ){
// ...
}
Всё общение между клиентом и сервером ведётся на уровне HTTP заголовков.
Для передачи отдельного chunk'а клиент устанавливает заголовки:
- Content-Range: bytes <start-offset>-<end-offset>/<total>
- Content-Disposition: attachment; filename=<file-name>
В ответ на передаваемый chunk сервер может отвечать следующими кодами:
- 200, 201 — chunk сохранён успешно
- 416, 500 — восстановимая ошибка
Простой input[type="file"]
<span class="js-fileapi-wrapper" style="position: relative; display: inline-block;">
<input name="files" type="file" multiple/>
</span>
Стилизованная кнопка.
<style>
.upload-btn {
width: 130px;
height: 25px;
overflow: hidden;
position: relative;
border: 3px solid #06c;
border-radius: 5px;
background: #0cf;
}
.upload-btn:hover {
background: #09f;
}
.upload-btn__txt {
z-index: 1;
position: relative;
color: #fff;
font-size: 18px;
font-family: "Helvetica Neue";
line-height: 24px;
text-align: center;
text-shadow: 0 1px 1px #000;
}
.upload-btn input {
top: -10px;
right: -40px;
z-index: 2;
position: absolute;
cursor: pointer;
opacity: 0;
filter: alpha(opacity=0);
font-size: 50px;
}
</style>
<div class="upload-btn js-fileapi-wrapper">
<div class="upload-btn__txt">Upload files</div>
<input name="files" type="file" multiple />
</div>
Кнопка в виде ссылки
<style>
.upload-link {
color: #36c;
display: inline-block;
*zoom: 1;
*display: inline;
overflow: hidden;
position: relative;
padding-bottom: 2px;
text-decoration: none;
}
.upload-link__txt {
z-index: 1;
position: relative;
border-bottom: 1px dotted #36c;
}
.upload-link:hover .upload-link__txt {
color: #f00;
border-bottom-color: #f00;
}
.upload-link input {
top: -10px;
right: -40px;
z-index: 2;
position: absolute;
cursor: pointer;
opacity: 0;
filter: alpha(opacity=0);
font-size: 50px;
}
</style>
<a class="upload-link js-fileapi-wrapper">
<span class="upload-link__txt">Upload photo</span>
<input name="photo" type="file" accept="image/*" />
</a>