Lazyload图像与Knockout& jQuery的

时间:2013-12-10 00:20:13

标签: javascript jquery knockout.js lazy-loading

我有一个图像密集型网站,用淘汰赛和包括jQuery。

这些是在foreach循环中:

<!-- ko foreach: {data: categories, as: 'categories'} -->
<!-- Category code -->
<!-- ko foreach: {data: visibleStations, as: 'stations'} -->
<!-- specific code -->
<img class="thumb lazy" data-bind="attr: { src: imageTmp,
                                           'data-src': imageThumb,
                                           alt: name,
                                           'data-original-title': name },
                                   css: {'now-playing-art': isPlaying}">
<!-- /ko -->
<!-- /ko -->

所以基本上当我创建这些元素时,imageTmp是一个计算的observable,它返回一个临时url,而imageThumb被设置为来自CDN的真实url。

我也有这段代码,称之为Lazy Sweeper:

var lazyInterval = setInterval(function () {
    $('.lazy:in-viewport').each(function () {
        $(this).attr('src', $(this).data('src')).bind('load', function(){
            $(this).removeClass('lazy')
        });
    });
}, 1000);

该代码会查找视口中的这些图像(使用自定义选择器仅在屏幕上查找图像),然后将src设置为data-src

我们想要的行为是避免加载用户看不到的jillion(呃,实际上是几百)的开销。

我们看到的行为是,在第一次加载时,看起来在调用ko.applyBindings()以后,Lazy Sweeper会被破坏,我们会看到图像恢复为默认图像。然后扫地机重新运行,我们看到它们再次显示。

我们不清楚如何以更加淘汰的方式实现这一目标。

思考?见解?想法?


我在twitter上得到了一个答案,提到了一个不同的延迟加载库。这没有解决问题 - 问题是不了解DOM和ko表示如何交互以设置延迟加载。我相信我需要的是更好的方法来思考创建设置imageTmp的挖空模型的问题,并根据它是否在视口中响应延迟加载,然后更新模型imageThumb (真实图像)已加载。

3 个答案:

答案 0 :(得分:11)

更新:现在使用working example

我的方法是:

  • 让您的模型(电台)决定图像网址是什么 - 临时或真实网址,就像您已经
  • 一样
  • 有一个绑定,其工作是处理DOM - 设置该图像源并处理load事件
  • 将懒惰扫地机限制为只提供“你现在可见”的信号

视图模型

  1. 添加一个showPlaceholder标志,其中包含我们的状态:

    this.showPlaceholder = ko.observable(true);
    
  2. 添加一个计算的observable,它始终返回当前正确的图像URL,具体取决于该状态:

    this.imageUrl = ko.computed(function() {
      return this.showPlaceholder() ? this.imageTemp() : this.imageThumb();
    }, this);
    
  3. 现在我们所要做的就是每当图片加载时都将showPlaceholder设置为false。更多关于这一点。

    绑定

    我们的绑定工作是在计算出的<img src>发生变化时设置imageUrl。如果src是真实图像,则应在加载后删除lazy类。

      ko.bindingHandlers.lazyImage = {
        update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
          var $element     = $(element),
              // we unwrap our imageUrl to get a subscription to it,
              // so we're called when it changes.
              // Use ko.utils.unwrapObservable for older versions of Knockout
              imageSource  = ko.unwrap(valueAccessor());
    
          $element.attr('src', imageSource);
    
          // we don't want to remove the lazy class after the temp image
          // has loaded. Set temp-image-name.png to something that identifies
          // your real placeholder image
          if (imageSource.indexOf('temp-image-name.png') === -1) {
            $element.one('load', function() {
              $(this).removeClass('lazy');
            });
          }
        }
      };
    

    懒惰扫地者

    所有这一切需要做的是给我们的viewmodel提示它现在应该从占位符切换到真实图像。

    ko.dataFor(element)ko.contextFor(element) helper functions让我们可以访问从外部绑定DOM元素的任何内容:

    var lazyInterval = setInterval(function () {
        $('.lazy:in-viewport').each(function () {
          if (ko.dataFor(this)) {
            ko.dataFor(this).showPlaceholder(false);
          }
        });
    }, 1000);
    

答案 1 :(得分:3)

我不熟悉Knockout.js所以我不能指出你更倾向于'淘汰'的方向,但是: 所以不要认为这是一个完整的答案,只是提示每张图片检查成本更低。

首先:您可以优化代码

var lazyInterval = setInterval(function () {
    $('.lazy:in-viewport').each(function () {
        $(this)
            .attr('src', $(this).data('src'))
            .removeClass('lazy'); 
        // you want to remove it from the loadable images once you start loading it
        // so it wont be checked again.
        // if it won't be loaded the first time, 
        // it never will since the SRC won't change anymore.
    });
}, 1000);

另外:如果您在视口中检查图像但视口没有改变,那么您只需再次将它们重新检查过ander,这是没有充分理由的...... 您可以添加“脏标志”以检查视口是否实际更改。

var reLazyLoad = true;
var lazyInterval = setInterval(function () {
    if (! reLazyLoad) return;
    ...current code...
    reLazyLoad = false;
}, 1000);
$(document).bind('scroll',function(){ reLazyLoad = true; });

当然,您希望每次修改DOM时都要重新检查它。

这不能解决您的数据绑定问题,但它对性能部分有帮助: - )

(你也可以让lazySweeper成为一个受限制的函数,并在每次更改时调用它(视口或dom)。创建更漂亮的代码......)

最后:你不能使用数据绑定添加懒类吗?这样,只有在绑定完成后它才会被lazySweeper拾取...(在输入时想出了这个。我真的不知道如何敲击js与数据绑定一起工作所以这是一个很长的事实)

答案 2 :(得分:2)

使用自定义绑定处理程序

我没有创建一个小提琴来测试这个,但是如果你认为它是正确的方向,但我的伪代码有问题,请告诉我,我可以在小提琴中验证。

这就是我个人所做的事情 -

使用自定义绑定处理程序检查元素上是否存在类“lazy”。只要对同一元素的绑定具有'viewPortChanged'名称(allBindingsAccessor.get()应该找到该绑定并设置一个等于它的局部变量),就应该触发它 -

ko.bindingHandlers.showPicture = {
    init: function (element, valueAccessor, allBindingsAccessor) {
        // The actual link to your image or w/e
        var actualSource = valueAccessor();
        var viewPortChanged = allBindingsAccessor.get('viewPortChanged');
        viewPortChanged.subscribe(function () {
            if ($(element).hasClass('lazy')) {
                $(element).attr("src", actualSource());
            }
        });
    }
};

在viewmodel中,创建一个标志来触发自定义绑定处理程序 -

function viewModel() {
    var self = this;
    self.viewPortChanged = ko.observable(false);

    // Register this to fire on resize of window
    $(window).resize(function() {
        // Do your view change class logic
        $('.lazy:in-viewport').each(function () {
            $(this).attr('src', $(this).data('src')).bind('load', function(){
                $(this).removeClass('lazy')
            });
        });
        // Have the observable flag change to recalc
        // the custom binding handler
        self.viewPortChanged(!self.viewPortChanged());
    });
}
ko.applyBindings(new viewModel());

最后,在元素上注册自定义绑定处理程序 -

<img class="thumb lazy" data-bind="showPicture: thisImageSource, viewPortChanged: viewPortChanged">

基本上,每当viewPortChanged observable触发时,应该触发每张图片的延迟检查。一个问题是viewPortChanged显然只是反向设置自己,所以你可能想让它成为一个其他东西的计算器,但是可以对所有的observable进行双重标记。