Как предварительно загрузить изображения в 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.