我有一个图像密集型网站,用淘汰赛和包括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
(真实图像)已加载。
答案 0 :(得分:11)
更新:现在使用working example。
我的方法是:
load
事件视图模型
添加一个showPlaceholder
标志,其中包含我们的状态:
this.showPlaceholder = ko.observable(true);
添加一个计算的observable,它始终返回当前正确的图像URL,具体取决于该状态:
this.imageUrl = ko.computed(function() {
return this.showPlaceholder() ? this.imageTemp() : this.imageThumb();
}, this);
现在我们所要做的就是每当图片加载时都将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进行双重标记。