I have a web application which dynamically loads in data over a long period of time. Within the data there are links to images which are then rendered in the browser.
e.g.
var object = {
Name: ko.observable("Foo"),
Ref: ko.observable("Bar"),
ImageUrl: ko.observable("http://.....")
}
I am using Knockoutjs 's template binding to render the data on the screen.
<img data-bind="attr: { src: imageUrl }" />
So every time the object changes via an Ajax call, the Knockoutjs template is re-rendered with the data, and the images change.
After a long period of time, these images build up and will eat up more memory. Modern browsers seem to cope better, but the problem is mainly with IE8 (we do not support < IE8). Even in modern browsers the memory will eventually get so high that the browser freezes.
See this screen shot for an example of the image resources building up.
I decided to see what would happen if instead of using an <img />
tag, use a <iframe />
.
So my code now looks like
<iframe data-bind="attr: { src: imageUrl }"></iframe>
What happens now is that the frame is created, but as soon as the imageUrl changes, the frame simply updates and does not create additional resources.
So if I want to keep the browser memory usage down, I can use this <iframe />
technique, but I don't like it. It forces me to make many other changes to the application, plus I need to use iframes!
I have run various tests now to see how much memory is used up using both techniques, and over the same period of time the memory will increase from 81,000k to 200,000k (with <img />
) compared to 81,000k to 98,000k ( with <iframe />
)
Question
Is there a better way to manage image resources within a browser? Is there a way to properly dispose of this image? I have searched the web for an answer, but so far I have not found anything.
Edit
At the very basic level. I have tried to remove an image via the jQuery method remove()
, but the image resource is never removed. See this fiddle for a very basic example. http://jsfiddle.net/ezb9e/
code:
html
<img src="http://www.fillmurray.com/200/300" />
JS
$(function(){
setTimeout(function(){
$('img').remove();
$('body').append($('<img />', { attr: { src: 'http://www.fillmurray.com/300/200' }}));
}, 3000);
});
I would try using a custom binding, and create and destroy the images in that. I had a similar problem last year with an SVG, and that's what I had to do.
ko.bindingHandlers.createImage = {
update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
// Use something unique to identify this image object
var imageName = viewModel.Name();
var parent = bindingContext.$parent;
var imageObject = parent.Images[imageName];
if (imageObject) {
$(element).empty()
imageObject = null;
}
imageObject = $(element).append('<img src="' + viewModel.imgSrc() + '" />')[0];
parent.Images[imageName] = imageObject;
}
};
Here's recreating your original problem:
http://jsfiddle.net/manzanotti/ezb9e/5/
And here's my version:
http://jsfiddle.net/manzanotti/ezb9e/13/
Memory goes up initially, but it gets garbage collected every now and again, so it never gets out of control. This is tested in IE9 and Chrome.
Update Hmmm, I'm not now convinced that this completely fixes the problem in IE8. I've run the fiddle in sIEve, and the memory is going up still in that, though as sIEve adds hooks into DOM nodes, it could be a result of running it in sIEve. Chrome is definitely fine though, and IE9 seems, at the very least, to be a lot better, if not completely fixed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With