Скриншоты без десктопа на HTML5

в 6:40, , рубрики: image, javascript, screenshot, upload, Блог компании Cackle, Веб-разработка, метки: , ,

Практически каждый день я пользуюсь почтой Gmail, но вот недавно заметил, что если сделать скриншот экрана (www.take-a-screenshot.org/), то простым нажатием Ctrl + V этот скриншот можно скопировать прямо в текст письма Gmail. Это работает везде, но естественно кроме IE. Заинтересовавшись вопросом как это происходит нагуглил следующий пост на Stackoverflow. Под сильным впечатлением от возможностей HTML5 clipboardData решил сделать простенький портал, где без всяких Desktop приложений любой юзер может загрузить скриншот просто скопировав его.

Поддержка браузерами

Во первых надо отметить, что для того, чтобы что-то скопировать из буфера (например скриншот) в браузере должна быть поддержка Clipboard API. Как можно видеть на caniuse только самые последние браузеры работают с этим API. IE, к сожалению, курит в сторонке не смотря на partial support.

Загрузка скриншотов на javascript

Для загрузки изображения из буфера (скриншота) сначала нужно определить обработчик paste события:

document.body.addEventListener("paste", function(e) {
   ...
});

Обработчик будет вызываться всегда, когда в рабочей области окна браузера происходит событие «Вставить», например по нажатию Ctrl + V. Далее нужно определить код, который собственно и примет файл изображения из буфера. Этот код идентичен для браузеров Chrome и Opera, но разный для FireFox, так как у последнего по каким-то соображениям безопасности закрыты методы объекта clipboardData.

Chrome, Opera

// e.clipboardData.items - это и есть файлы находящиеся в буфер обмене
for (var i = 0; i < e.clipboardData.items.length; i++) {
    
    // выбираем только картинки
    if (e.clipboardData.items[i].kind == "file" && e.clipboardData.items[i].type == "image/png") {

        // Получаем файл как Blob (бинарные данные)
        var imageFile = e.clipboardData.items[i].getAsFile();
        var fileReader = new FileReader();
        fileReader.onloadend = function(e) {
            // Файл прочитан в this.result его Base64 представление
            loadImg(this.result);
        };

        // Читаем Blob как DataURL (Base64 представление бинарных данных)
        fileReader.readAsDataURL(imageFile);
        e.preventDefault();
        break;
    }
}
FireFox

В FireFox у нас нет возможности прочитать файл через clipboardData. При вставке, браузер самостоятельно создает <img> тег с src в виде DataURL. Поэтому придется сделать «костыль»:

// При инициализации js, если браузера Mozilla, то добавляем скрытый, редактируемый (contenteditable) div и
// ставим на него фокус.
if ($.browser.mozilla) {
    $(document.body).prepend('<div id="temp" contenteditable="true" style="height:1px;width:1px;color:#FFFFFF;"></div>');
    $('#temp').focus();
}

// Определяем событие "Вставить"
document.body.addEventListener("paste", function(e) {

    if ($.browser.mozilla) {

        // Если Mozilla, то фокусируем на созданный ранее div
        // (это нужно для того, чтобы FireFox вставил img в нужном месте)
    	$('#temp').focus();

        // Удаляем предыдущий img (вдруг мы копируем 2, 3, ..., N раз)
    	$('#temp img').remove();

        // У FireFox при создании img нет callback-а и единственный способ получить img - использовать небольшую паузу
        setTimeout(function() {
            // Вот тут FireFox точно вставил img в temp div, мы можем взять его DataURL и загрузить картинку
            loadImg($('#temp img').attr('src'));
        }, 1);
        return true;
    }
});

Куда загрузить картинку?

Загрузить картинку можно сразу на сервер, передавая через POST DataURL или например в canvas как сделано на Your screen:

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
...
function loadImg(dataURL) {
    var imageObj = new Image();
    imageObj.onload = function() {
        var width = this.width, height = this.height;
        canvas.width  = width;
        canvas.height = height;
        ctx.drawImage(this, 0, 0, width, height);
    };
    imageObj.src = dataURL;
}

Canvas удобен тем, что перед загрузкой скриншота на сервер можно его отредактировать: подчеркнуть, нарисовать, обвести, вырезать и т.д.

Что происходит на сервере?

На сервере должен быть POST контроллер, который бы принимал body в формате Base64 (DataURL) и декодировал его в бинарный файл изображения, например png. Контроллер может быть написан на любом языке, например Java с использованием SpringMVC:

@RequestMapping(value = "/upload", method = RequestMethod.POST)
public @ResponseBody String save(@RequestBody String b64) {
    File file = null;
    FileOutputStream out = null;
    try {
        // Создаем Output поток
        out = new FileOutputStream("/opt/files/somename.png");

        // Декодируем Base64 в байты и сохраняем в поток
        out.write(Base64.decodeBase64(StringUtils.replace(b64, "data:image/png;base64,", "")));

        // Закрываем поток
        IOUtils.closeQuietly(out);

    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return file.getName();
}

Для внимательных отмечу, что вот эта конструкция StringUtils.replace(b64, «data:image/png;base64,», "") нужна для нормализации передаваемого из Javascript DataURL. Дело в том, что при создании DataURL вначале ставится его тип и формат и после идет Base64 представление, например data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABVYAAAMAC…

Дополнительно можно ещё проверить размер файла, и если он превышает определенный максимум сжать в jpg.

Варианты использования

Сервис Your screen мы в компании Cackle используем каждый день в качестве быстрого и простого средства для снимка экранов наших клиентов. К сожалению, подобных решений в сети очень мало и те, что есть неудобны или работают только через Desktop:

  • prntscr.com — копировать скриншот можно только в Chrome и без редактирования
  • snag.gy — уже лучше, но скрншоты загружаются очень медленно и только один раз (первый)
  • Больше не нашел...

В скором времени мы планируем перенести весь этот функционал в наш Онлайн консультант Cackle. Таким образом у клиентов будет возможность сделать скриншот и передать изображение прямо через консультанта оператору. Это в некотором смысле лучшая замена Co-Browser, так как позволяет показывать весь экран (а не только браузер), снизить издержки на разработку и минимизировать ошибки.

В заключении скажу, что проект Your screen совсем новый и будет развиваться дальше, поэтому жду вашей критики и пожеланий по функционалу.

Автор: cackle

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js