- Using Binary Data with Front-End JavaScript and the Web
- Loading Data via Base64 String Data URLs
- Disclaimers
- More Resources
- More About Me:
- Как подгрузить HTML-код из файла силами JavaScript на web-страницу через объект XMLHttpRequest?
- Задача
- Предпосылки. Зачем это нужно?
- Решение задачи
- Логика работы объекта XMLHttpRequest
- Результат работы
Using Binary Data with Front-End JavaScript and the Web
If you already have your data stored in a binary format, in memory, and need to load it into an element (for example an img element), there is an alternative to Base64 DataURLs that is much preferable: Object URLs.
These are essentially virtual URLs that point to raw data; this could be a blob of binary data stored in memory, or even a reference to a user’s local file from their OS. Because they are references to existing data location, they use less memory than Base64 strings (and you can even release the pointer after loading)
// Example: Loading an image blob const blob = await (await fetch('https://www.wikipedia.org/portal/wikipedia.org/assets/img/Wikipedia-logo-v2.png')).blob(); const imageElem = document.querySelector('img'); // Good practice is to release once loaded imageElem.onload = () => < URL.revokeObjectURL(imageElem.src); >imageElem.src = URL.createObjectURL(blob);
Just like Data URLs, Loading data through Object URLs works for a variety of media element, including elements!
Loading Data via Base64 String Data URLs
Data URLs (formerly called Data URIs) are essentially just strings that are comprised of the data itself, as opposed to a pointer to where the data resides (how normal URLs or Object URLs work).
They follow a standardized syntax:
data:;base64, # Example data:image/bmp;base64,Qk1aAAAAAAAAAEIAAAAoAAAABgAAAAYAAAABAAQAAAAAAAAAAADEDgAAxA4AAAMAAAADAAAAAAAA/wAA//8A2P//IAEgAAASAAABIAEAEgASACABIAAAEgAA # If the data is *not* base64 encoded, you can leave off the `;base64` section # Example: data:text/plain,hello
They can be used in (AFAIK) pretty much any element with a src attribute, even including and tags!
This is getting into the advanced part of data loading in JavaScript, but I’d like to point out that certain elements (HTMLMediaElement based, i.e. video and audio) now support loading data more directly, via streams, buffers, and the MediaSource interface. Collectively, most of these technologies fall under the “Media Source Extensions API” (aka MSE), which is an evolving specification for more advanced and dynamic media sources, such as append-able buffers.
The advantage to this approach is that it allows you to load in data in chunks, as opposed to through a single resource URL or blob. This makes it ideal for streaming applications, where video or audio is progressively loaded (and/or rendered).
Certain elements can even emit streams, which can be then captured and fed back into other media elements. A great example of this: you can stream data directly from element to a element on the same page (demo). Or stream from one video to another.
I’m not going to get too in-depth into this, as it is an advanced topic (and I have an entire separate blog post on it), but the general minimal steps required to load data into a (video) element with a dynamic MediaSource are:
- Create a new mediaSource instance ( new MediaSource() )
- Create an object URL that points to that source. Point the video to it
- Example: videoElem.src = URL.createObjectURL(mediaSource)
- On the MediaSource instance, listen for the sourceopen even. Once it fires, you can begin preparing to load data into it
- Before you start loading data, you need to create a buffer to hold it. You do so by creating a SourceBuffer attached to the source, with mediaSource.addSourceBuffer(mimeType)
- Example: const sourceBuffer = mediaSource.addSourceBuffer(‘video/webm; codecs=»vp9″‘);
- Once you have the buffer, you can finally start loading in raw chunks of binary data
- One way to load in a chunk is with ArrayBuffer(s), via the sourceBuffer.appendBuffer(ArrayBuffer) method
- If you have a blob instead of an ArrayBuffer, you can use myBlob.arrayBuffer() to get Promise
- Eventually, you also need to deal with signaling that the end of data has been reached, and controlling the playback
You can find a full example of these steps put together on MDN’s page for SourceBuffer, and an even more advanced example in the MediaSource / MSE spec. As I mentioned, I’m also planning to write and release a blog post on the topic soon. I also just finished a blog post that goes into depth on this topic!
Disclaimers
I wrestled with the decision to include this section, as I don’t want to make any developers feel like I am trying to “shame” them, but I also feel that it is important to include it due to the level of abuse that Data URLs and Base64 often receive. This is an attempt to point out situations in which these alternative loading techniques are used, when maybe they shouldn’t be. Or ways in which they can used incorrectly. So, without further delay:
- Base64 is not a form of encryption. It cannot be used to hide passwords, secrets, or prevent assets from being downloaded
- Base64 is generally a less efficient way of storing binary data, and it is a non-zero amount of processing power required to convert Base64 strings into other formats
- This is (part of) why taking a 30 second video and storing it directly as a Base64 encoded string in the page and loading via Data URL, instead of just using a normal URL as the src , is very much a bad idea
- A huge drawback is this one: Data URLs are treated as an opaque origin as opposed to matching the origin of the page they are loaded into. (ref)
- This means they do things like taint HTML and affect CORS usage
More Resources
More About Me:
Как подгрузить HTML-код из файла силами JavaScript на web-страницу через объект XMLHttpRequest?
Файл index.html обрабатывается силами web-сервера и автоматически загружается в браузер пользователя при обращении к сайту (главной странице).
Файл index.html имеет классическую разметку документа:
DOCTYPE html> html lang="ru"> head> meta charset="UTF-8"> meta name="viewport" content="width=device-width, initial-scale=1.0"> title>Документtitle> head> body> body> html>
Это «пустая» HTML-страница со своим уникальным адресом. На странице визуально нет ничего. Просто белый лист.
Задача
Необходимо на страницу index.html подключить HTML-разметку из файла text.html , но так чтобы файл text.html содержал только HTML-элементы и . То есть мы хотим подгрузить только уникальную информацию на страницу без «лишних» мета-данных.
Также мы хотим сделать эту загрузку в фоне, без перезагрузки страницы index.html . То есть пользователь не увидит в адресной строке браузера другого адреса. Перезагрузки страницы не будет.
Файл text.html имеет разметку:
ВНИМАНИЕ! Запросы к серверу мы будем делать ТОЛЬКО через работающий локально веб-сервер. Ознакомьтесь с протоколом CORS и стандартом Fetch. Локальный запуск файла index.html в браузере не приведёт к работающему результату. Используйте бесплатный продукт «OpenServer» для своих тестов.
Предпосылки. Зачем это нужно?
Главная идея задачи — это создание шаблонов генерации документов силами JavaScript, при помощи которых можно лучше управлять визуальным содержимым сайта.
Каждая отдельная страница сайта перестаёт быть статичной (уже собранной). В результате, мы разделяем потенциальную страницу на части. Например:
- Один документ отвечает за шапку сайта
- Другой за подвал
- Третий за основное содержимое
- Четвёртый за боковые панели
- Пятый за рекламные баннеры
- Шестой за галерею фотографий
- Седьмой за контактную информацию
- и т. п..
Решение задачи
Для начала подключим на страницу index.html файл со скриптом, который будет называться gettext.js .
В файле index.html внутри элемента поместим элемент с атрибутом src и его значением gettext.js
Для решения задачи мы будем использовать объект XMLHttpRequest. Стандарт XMLHttpRequest определяет API-интерфейс, который предоставляет клиенту функциональные возможности по сценарию для передачи данных между клиентом и сервером .
- Посылаем запрос на сервер.
- Дожидаемся ответа. Ловим содержимое файла.
- Вносим нужные изменения в документ.
Отредактируем файл gettext.js
var inBody = function()< // Создаём анонимную функцию. Помещаем её в переменную "inBody" var xhr = new XMLHttpRequest() // Создаём локальную переменную XHR, которая будет объектом XMLHttpRequest xhr.open('GET', 'text.html') // Задаём метод запроса и URL запроса xhr.onload = function()< // Используем обработчик событий onload, чтобы поймать ответ сервера XMLHttpRequest console.log(xhr.response) // Выводим в консоль содержимое ответа сервера. Это строка! document.body.innerHTML = xhr.response // Содержимое ответа, помещаем внутрь элемент "body" > xhr.send() // Инициирует запрос. Посылаем запрос на сервер. > inBody() // Запускаем выполнение функции получения содержимого файла
Логика работы объекта XMLHttpRequest
В первой строке мы создаём анонимную функцию и помещаем её в переменную «inBody«. Название переменной описывает решаемую задачу — дословно «вТело«. То есть результатом выполнения этой функции будет интеграция содержимого файла text.html внутрь элемента загруженной странице index.html на клиенте (в браузере)
Со второй строки начинается тело функции. С помощью конструктора объектов мы создаём новый объект XMLHttpRequest и помещаем его в локальную переменную «xhr«. Название переменной означает сокращённую запись от первых трёх букв — X ML H ttp R equest ( XHR ). Т.к. область видимости ограничена родительской функцией, то можно использовать подобное название без опасений. В рабочих проектах не рекомендую использовать глобальные переменные с именем XHR , т. к. на практике такое имя применяется в основном к объектам XMLHttpRequest.
Третья строка запускает метод open() объекта XMLHttpRequest. В этом методе задаётся HTTP-метод запроса и URL-адрес запроса. В нашем случае мы хотим получить содержимое файла по адресу «text.html», который находится в той же директории, что и загруженный в браузер index.html. Получать содержимое мы будем методом «GET» протокола HTTP.
Четвёртая строка описывает логику работы обработчика события onload. Пользовательский агент ДОЛЖЕН отправить событие load, когда реализация DOM завершит загрузку ресурса (такого как документ) и любых зависимых ресурсов (таких как изображения, таблицы стилей или сценарии). То есть обработчиком события onload мы ловим срабатывание типа события load и полученные ресурсы мы достаём при помощи атрибута ответа response объекта XMLHttpRequest.
Пятой строкой мы выводим в консоль результат ответа сервера. Она необходима для разработки. Она не обязательна. ВНИМАНИЕ! Содержимое ответа по-умолчанию имеет тип данных — string (строка). Это стандарт клиент-серверного взаимодействия. Все данные передаются по сети в виде «строковых данных». Так всегда происходит — это норма. Если вы точно знаете каким образом строка будет оформлена, тогда вы можете воспользоваться атрибутом ответа responseType и в этом случае содержимое ответа будет одним из:
- пустая строка (по умолчанию),
- arraybuffer
- blob
- document
- json
- text
В шестой строке мы присваиваем элементу внутренне содержимое пришедшее из файла на сервере. Это содержимое будет заключено между открывающим и закрывающим . XMLHttpRequest имеет связанный ответ response.
Восьмая строка инициирует запрос на сервер методом send() и отправляет его.
На десятой строке мы вызываем функцию «inBody»
Результат работы
Мы видим итоговую страницу с нужным нам содержимым. На favicon не обращаем внимания т. к. браузер Chrome вдруг решил его поискать.
Вкладка имеет название «Документ», которое пришло из элемента .
Главная страница нашего «воображаемого» сайта http://getinnerofpage/ содержит информацию, пришедшую из другого файла.
Разметка итогового документа после выполнения запроса к серверу. Браузер «переварил» строковые данные и преобразовал их в HTML-разметку.
В инструментах разработчика на вкладке Network мы видим последовательность загрузки данных для главной страницы сайта
Сперва браузер запросил HTML-документ главной страницы сайта. Статус 200 — ОК. Потом после разбора разметки браузер загрузил файл со скриптом. Статус 200 — ОК. После этого браузер начал синхронно обрабатывать выполнение инструкций файла скрипта. На восьмой строчке выполнения файла gettext.js мы видим обращение к файлу text.html
Статус 200 — ОК означает успешную подачу запроса — запрашиваемые ресурсы имеются на сервере.
В ответе сервера браузер уже понимает пришедшие данные и подсвечивает HTML-синтаксис, указывая на элементы и . То есть браузер уже сам разобрал пришедший тип данных string и подсветил разработчику открывающиеся и закрывающиеся элементы.
Важно обратить внимание, что на подгрузку данных «в фоне» потребовалось некоторое время. В реальных проектах нужно учитывать эту особенность и правильно распределять зависимости последовательностей отдачи содержимого пользователю.
Может оказаться так, что при формировании финансового графика часть данных не успеет прийти вовремя — это исказит трактование данных из отчёта и навредит бизнесу из-за ошибки вычислений. Будьте внимательны! В таких случаях уместно использовать объект Promise .