Лучший способ предварительной загрузки изображений с помощью Angular.js

Angular ng-src сохраняет предыдущую модель до тех пор, пока не загрузит изображение внутри. Я использую разные изображения для баннера на каждой странице, когда я переключаю маршруты, я меняю основной вид, оставляя вид заголовка как есть, просто меняя модель bannerUrl, когда она у меня есть.

Это приводит к тому, что предыдущее изображение баннера отображается во время загрузки нового.

Я был удивлен, что для него еще нет директивы, но я хотел провести обсуждение, прежде чем пытаться его создать.

То, что я хочу сделать, я думаю, это иметь модель баннера для пользовательского атрибута. как:

<img preload-src="{{bannerUrl}}" ng-src="{{preloadedUrl}}">

Затем измените $scope.watch для bannerUrl, и как только он изменится, сначала замените ng-src на загрузчик spinner, а затем создайте временный элемент img dom, предварительно загрузите изображение из preload-src, а затем присвойте его preloadUrl.

Нужно подумать, как обрабатывать несколько изображений, например, для галерей.

Есть ли у кого-нибудь какие-либо сведения об этом? или, может быть, кто-то может указать мне на существующий код?

Я видел существующий код на github, который использует фоновое изображение, но это не работает для меня, так как мне нужна динамическая высота/ширина, поскольку мое приложение отзывчиво, и я не могу сделать это с фоновым изображением.

Спасибо


person Yuri    schedule 07.01.2014    source источник
comment
Обычный обходной путь заключается в использовании CSS для установки изображения на visibility: hidden до тех пор, пока окончательное изображение не будет загружено, когда вы измените его на visibility: visible, и, таким образом, вы увидите только окончательное изображение.   -  person jfriend00    schedule 08.01.2014
comment
@ jfriend00, поэтому установите для всех изображений значение visibility:hidden, затем используйте javascript, чтобы увидеть, когда все изображения в массиве загружены, и затем установите для всех изображений значение visibility:visible?   -  person Yuri    schedule 08.01.2014


Ответы (9)


Наличие двух URL-адресов в директиве кажется слишком сложным. Я думаю, что лучше написать директиву, которая работает так:

<img ng-src="{{bannerUrl}}" spinner-on-load />

И директива может смотреть ng-src и (например) устанавливать visibility:false с помощью счетчика, пока изображение не загрузится. Итак, что-то вроде:

scope: {
  ngSrc: '='
},
link: function(scope, element) {
  element.on('load', function() {
    // Set visibility: true + remove spinner overlay
  });
  scope.$watch('ngSrc', function() {
    // Set visibility: false + inject temporary spinner overlay
  });
}

Таким образом, элемент ведет себя очень похоже на стандартный img с атрибутом ng-src, только с дополнительным поведением.

http://jsfiddle.net/2CsfZ/47/

person Michal Charemza    schedule 08.01.2014
comment
Имеет смысл, спасибо. Попробую сегодня днем. - person Yuri; 08.01.2014
comment
Спасибо, сэр. Я немного расширил вашу идею, и она отлично сработала. - person Yuri; 09.01.2014
comment
изображение в jsfiddle не работает. Даже если я использую изображение, которое работает, оно никогда не удаляет счетчик. - person goerwin; 12.08.2015
comment
Если вам интересно, скрипт работает с Angular 1.2 и выше, в котором были внесены некоторые критические изменения jsfiddle.net/janklimo. /de8ntcdv - person Jan Klimo; 24.08.2015
comment
Ага ! Спасибо @JanKlimo! - person Despertaweb; 19.05.2016

Если кому-то интересно, это мое окончательное решение: я использую twitter bootstrap. Поэтому добавлен класс «fade» для всех изображений и просто переключение класса «in» с директивой для постепенного появления и исчезновения при загрузке изображения.

angular.module('myApp').directive('imgPreload', ['$rootScope', function($rootScope) {
    return {
      restrict: 'A',
      scope: {
        ngSrc: '@'
      },
      link: function(scope, element, attrs) {
        element.on('load', function() {
          element.addClass('in');
        }).on('error', function() {
          //
        });

        scope.$watch('ngSrc', function(newVal) {
          element.removeClass('in');
        });
      }
    };
}]);

<img img-preload class="fade" ng-src="{{imgSrc}}">

Рабочий пример: http://ishq.org.

person Yuri    schedule 10.01.2014
comment
Спасибо за ссылку на рабочий пример из реальной жизни, но можете ли вы выделить плюсы в Plunker или какую-либо другую тестовую онлайн-среду-песочницу? Спасибо - person ScottN; 11.02.2014
comment
Именно то, что я искал. Спасибо - person Vinnyq12; 11.04.2016

Если вы хотите, вы можете передать сбой изображения и загрузчик изображения в качестве атрибутов для директивы....

myApp.directive("mySrc", function() {
    return {
      link: function(scope, element, attrs) {
        var img, loadImage;
        var IMAGE_LOAD="123.jpg";
        var IMAGE_FAIL="123.jpg";
        img = null;

        loadImage = function() {

          element[0].src = IMAGE_LOAD;

          img  = new Image();
          img.src = attrs.mySrc;

          img.onload = function() {
            element[0].src = attrs.mySrc;
          };
          img.onerror=function ()
          {
              element[0].src = IMAGE_FAIL;
          }
        };

        loadImage();


      }
    };
  });
person yonia    schedule 10.12.2014

Я думаю, что это, пожалуй, самое элегантное решение, потому что директива фактически создает счетчик и автоматически удаляет его:

app.directive('spinnerLoad', [function spinnerLoad() {
    return {
        restrict: 'A',
        link: function spinnerLoadLink(scope, elem, attrs) {
            scope.$watch('ngSrc', function watchNgSrc() {
                elem.hide();
                elem.after('<i class="fa fa-spinner fa-lg fa-spin"></i>');  // add spinner
            });
            elem.on('load', function onLoad() {
                elem.show();
                elem.next('i.fa-spinner').remove(); // remove spinner
            });
        }
    };
}]);

Вот html:

<img ng-src='{{imgUrl}}' spinner-load />

Примечание: вам нужно использовать шрифт font-awesome, чтобы это работало, как описано здесь.

person Jeremy Moritz    schedule 11.04.2016
comment
Глупый вопрос, но как я могу использовать директиву в HTML-шаблоне, у которого есть собственный контроллер? Я добавил файл directive.js в index.html, но он показывает typerror (elem.hide и elem.show не являются функциями). Также является ли область видимости типом ($scope)? Спасибо :) - person Jamie; 29.03.2017
comment
Это не работает для меня. Функция наблюдения вызывается один раз, и когда src обновляется, она не запускает функцию наблюдения. - person Pietro Coelho; 14.06.2017

Просто поделиться ^^

 //css
.media-box{
                position: relative;
                width:220px;
                height: 220px;
                overflow: hidden;
            }
            .media-box div{
                position: absolute;
                left: 0;
                top: 0;
            }
            .spinner{
                position: absolute;
                left: 0;
                top: 0;
                background: #CCC url(./spinner.gif) no-repeat center center;
                display: block;
                width:220px;
                height: 220px;
            }
            .feed img.spinner-show{
                visibility: visible;
            }
            .feed img.spinner-hide{
                visibility: hidden;
            }

//html
<div class="media-box">
  <div>
    <img data-ng-src="{{item.media}}" alt="" title="" data-spinner-on-load>
  </div>
</div>

//js
.directive('spinnerOnLoad', function() {
            return {
                restrict: 'A',
                link: function(scope,element){
                    element.on('load', function() {
                        element.removeClass('spinner-hide');
                        element.addClass('spinner-show');
                        element.parent().find('span').remove();
                    });
                    scope.$watch('ngSrc', function() {
                        element.addClass('spinner-hide');
                        element.parent().append('<span class="spinner"></span>');
                    });      
                }
            }
        });
person Whisher    schedule 13.02.2014

Вместо того, чтобы использовать

element.on('load', function() {});

используйте плагин imagesLoaded. Это значительно ускорит ваши изображения.

Таким образом, окончательный код будет таким:

link: function(scope, element) {
  imagesLoaded(element, function() {

  });
  scope.$watch('ngSrc', function() {

  });
}
person borN_free    schedule 09.04.2014

У меня есть эта директива, которая показывает счетчик при изменении img-src:

<img-with-loading
      img-src="{{src}}"
      spinner-class="{{spinnerClass}}"
/>

Код здесь: http://jsfiddle.net/ffabreti/yw74upyr/

person Fernando Fabreti    schedule 08.09.2015

изображения могут быть предварительно загружены при изменении маршрута с помощью image-preloader фабрика и решить:

// call REST
                    return getContent.get().$promise.then(function(response) {

                                //return response;

                                // preload images from response
                                var imageLocations = [
                                  // put image(s) from response to array
                                  response.PostImage.big[0],
                                ];

                                // check do we have (all) image(s) in array
                                console.log(imageLocations);

                                // return when all images are preloaded
                                return preloader.preloadImages( imageLocations )
                                .then(function() {

                                    //if it was success 
                                    return response;
                                },
                                function() {

                                        //if it failed 
                                    return response;
                                });

                            });

полное руководство здесь: https://www.coditty.com/code/angular-preload-images-on-route-change-by-using-resolve

person Igor Simic    schedule 15.09.2017

Простое решение, которое я нашел, состоит в том, чтобы изменить URL-адрес на «//: 0», прежде чем назначать ему новое значение.

$scope.bannerUrl = 'initial value'; 

// When we want to change it
$scope.bannerUrl = '//:0';  // remove the previous img so it's not visible while the new one loads
$scope.bannerUrl = scope.preloadedUrl
person Guinness    schedule 08.05.2015