Как предварительно загрузить изображения в Ember.js
Я люблю Ember.js и ежедневно использую его для различных проектов. Каждый день для меня - новое познавательное приключение. Медленно, но верно я углубляюсь в его сокровенные тайны. Для решения любой конкретной технической задачи эта амбициозная структура предлагает множество путей к успеху и прекрасные джунгли возможностей.
Например, одной из тех интересных задач для меня было выяснить, как предварительно загрузить изображения в Ember.js. Прежде чем прибыть в конечный пункт назначения, я испробовал несколько вариантов.
Поэтому я подумал, что было бы неплохо поделиться своим опытом в надежде, что другие смогут узнать больше от меня и моего избранного пути.
Пришло время взять мачете и вместе со мной пробиться сквозь чудесную растительность в поисках спрятанных сокровищ.
Честно говоря, я не совсем уверен, что мой метод - лучший способ предварительной загрузки изображений. Но пока все работает нормально. Насколько я знаю, может быть какая-то другая скрытая возможность Ember.js, которую я, возможно, упустил. Однако, если из-за несовершенного решения я генерирую много диалогов и других предложений, значит, я также достиг полезной цели.
Хорошо, поехали. Начнем с анализа проблемы.
Допустим, у вас есть набор изображений с высоким разрешением, которые будут отображаться на нескольких отдельных страницах веб-сайта. Каждое изображение представляет собой определенный баннер, соответствующий содержанию этой страницы. Вы хотите, чтобы каждое изображение было готово к рендерингу в тот момент, когда пользователь впервые переходит на эту страницу.
Поскольку эти изображения большие, их показ в первый раз может привести к заметной задержке. Запрошенное изображение загружается с сервера, а затем отображается браузером. Ваш клиент разочарован и говорит вам, что эта задержка вызывает раздражение и выглядит не очень профессионально. Вам также говорят, что с мобильными устройствами дела обстоят еще хуже, поскольку из-за задержки сети и менее мощного оборудования ситуация становится в сто раз хуже. Все больше и больше людей пользуются мобильными телефонами и планшетами.
Я предоставлю более подробную техническую информацию о том, как можно предварительно загрузить фоновые изображения баннеров на этапе инициализации приложения. Причина, по которой я выбрал это, заключалась в том, что я заметил, что при первом открытии каждой страницы происходила очень заметная икота во время загрузки изображения.
Давай избавимся от этого! Пришло время решить эту проблему, предварительно загрузив соответствующие изображения и кэшировав их в браузере.
Процесс предварительной загрузки изображений с помощью JavaScript всегда был довольно простым, и вот уже много лет он делается следующим образом:
(new Image()).src = value;
Где `value` представляет URL-адрес изображения. Прежде всего, нам нужно каким-то образом выяснить, что такое `value`. Именно здесь и возникает наше первое препятствие: снятие отпечатков пальцев.
Отпечатки пальцев используются для взлома тайников. После каждого нового развертывания образу присваивается немного другое имя, чтобы он обновлялся и обновлялся в браузере, а не с использованием предыдущей кэшированной версии.
В производственной среде как часть процесса развертывания мы обычно запускаем:
$ ember build --environment=production
который будет генерировать `отпечатанные` имена всех ресурсов, включая изображения. Все изображения баннеров хранятся в каталоге `public / assets / images / Banner`, каждое из которых имеет размеры 700 x 400.
Это означает, что изображение баннера `about`, которое зародилось как
about.png
будет переименован примерно так
about-c4ee817e7744249afd5aa27ea38c2fe5.png
Какое уродливое имя, тебе не кажется? Эта длинная буквенно-цифровая строка, добавленная к базовому имени, представляет собой `контрольную сумму`, что не очень удобно для пользователя и невозможно запомнить. Но в этом вся идея.
Чтобы иметь возможность предварительно загрузить изображение, вам необходимо заранее знать, что это за отпечатанное имя, а не исходное имя. Так как же нам это сделать? Вот тут-то и вступает в игру `asset maps`.
Давайте посмотрим на Broccili-asset-rev, плагин Broccoli, который по умолчанию используется Ember.js для добавления контрольных сумм отпечатков пальцев в ваши файлы. Источник также обновляется, чтобы отразить новые имена файлов.
По умолчанию `карта ресурсов` не создается на этапе сборки. Вам необходимо включить его, включив следующий параметр `fingerprint` в файл ` ember-cli-build.js`:
var app = new EmberApp(defaults, { ... fingerprint: { generateAssetMap: true } });
Теперь, когда приложение Ember.js создано для производства, в каталоге `dist / assets` создается ` assets map`.
` dist / assets / assetMap.json ` содержит сопоставление исходного имени файла с новым именем файла отпечатков пальцев. В основном это список пар ключ-значение, который выглядит примерно так:
{ "assets": { "assets/gishtech.css": "assets/gishtech-XXX.css", "assets/gishtech.js": "assets/gishtech-XXX.js", ... "assets/images/banners/about.png": \ "assets/images/banners/about-XXX.png", ... "assets/vendor.css": "assets/vendor-XXX.css", "assets/vendor.js": "assets/vendor-XXX.js" }, "prepend": "" }
Для краткости `XXX` означает отпечатанную часть базового имени файла ресурсов, как описано выше.
Теперь все, что нам нужно сделать во время инициализации приложения, - это получить доступ к этому `assetMap.json` и сопоставить исходное имя изображения баннера с отпечатанным. Таким образом, мы можем предварительно загрузить все изображения баннеров на лету.
Поскольку меня интересуют только изображения баннеров, я могу отфильтровать их, используя следующее регулярное выражение:
RE = /^assets\/images\/banners\//
Я решил сделать это, определив Ember Initializer под названием `asset-map`:
$ ember generate initializer asset-map
а затем измените asset-map.js, чтобы он выглядел примерно так:
import Ember from 'ember'; export function initialize(container, application) { var AssetMap = Ember.Object.extend(); var promise = new Ember.RSVP.Promise(function(resolve, reject) { Ember.$.getJSON(‘assets/assetMap.json’, resolve).fail(reject); }); promise.then(function(assetMap) { AssetMap.reopen({ assetMap: assetMap, resolve: function(name) { return assetMap.assets[name]; } }); for (var key in assetMap.assets) { var value = assetMap.assets[key]; // Filter out only banner images and preload. if (/^assets\/images\/banners\//.test(value)) { (new Image()).src = value; // <-- PRELOAD! } } }, function() { ... }).then(function() { ... }); } ...
В более поздних версиях Ember.js метод `initialize` принимает только один аргумент, и вы, скорее всего, увидите предупреждение об устаревании. Я еще не понял, как провести рефакторинг моей версии, заменив `container` каким-либо другим механизмом, поэтому я открыт для любых предложений о том, как этого добиться. Заранее большое спасибо.
Я понимаю, что Sass также предлагает продвинутую технику предварительной загрузки изображений. Однако мне так и не удалось заставить его работать в Ember.js с включенным отпечатком пальца. Для тех, кто заинтересован, ознакомьтесь со следующей статьей: Элегантно предварительно загружайте фоновые изображения с помощью Sass.
Кроме того, я хочу отдать должное ember-cli-inject-asset-map от jcaffey
, который направил меня на правильный путь и заставил глубже задуматься о правильной реализации.
Дальнейшее обсуждение всего этого можно найти на всемирно известном форуме Ember: Решение проблем с отпечатками пальцев с помощью generateAssetMap.
Теперь, когда мы разобрались со всем этим, я просто хотел закончить эту статью, объяснив, как я беру эти предварительно загруженные изображения баннеров и отображаю их на нужных страницах. Можно сказать что-нибудь забавное на десерт.
Вверху каждой страницы отображается начальная страница jumbotron ` с другим фоновым изображением, покрывающим ее. В этом разделе я объясню, как это достигается с помощью старого доброго `Sass` вместе с предварительно загруженными изображениями, как подробно описано выше.
Прежде всего, я встроил компонент `header-banner` в шаблон ` application.hbs `, который гарантирует, что он будет отображаться вверху
каждой страницы:
<div class=”container”> ... {{header-banner title='Gishtech' subtitle='Advanced Software Development<br/>for the Web' routename=currentRouteName }} ... </div>
Файл шаблона компонента header-banner.hbs выглядит следующим образом:
<div class="jumbotron background-image-{{routename}}"> <h1>{{{title}}}</h1> <p>{{{subtitle}}}</p> </div>
Например, предположим, что мы переходим на страницу `about`, тогда следующие действия вступят в силу:
<div class="jumbotron background-image-about"> ... </div>
Здесь у класса `jumbotron` будет фоновое изображение, покрывающее весь` div`, как определено стилем:
/* app/styles/main.scss */ .jumbotron { ... background: no-repeat 0 25%; background-size: cover; ... }
Итак, как это приводит к появлению правильного изображения баннера? Очень просто, на помощь приходит Sass!
Начнем с определения директивы @mixin в файле `globals.scss` следующим образом:
/* app/styles/globals.scss */ @mixin background-image($name) { .background-image-#{$name} { background-image: url(/assets/images/banners/#{$name}.png); } }
В файл `main.scss` мы импортируем файл ` globals.scss` и используем директиву @each для перебора всех страниц.
/* app/styles/main.scss */ @import “globals”; ... @each $name in about, contact, credits, index/*, ... */ { @include background-image(#{$name}); }
Предыдущий пример, замена `$ name` на страницу ` about` приведет к следующему:
.background-image-about { background-image: url(/assets/images/banners/about.png); }
Это именно то, что мы хотели, мы сделали это!
Большое спасибо за то, что дочитали эту статью до конца, я надеюсь, что это имело смысл и помогло вам.
Весь код, использованный в этой статье, находится в свободном доступе в моей учетной записи github, а репозиторий можно найти по адресу: h ttp: //github.com/kgish/gishtech.com.