Custom Lazy Load - IE9 Memory Leak - javascript

I'm currently developing a basic image gallery that dynamically loads new images in the following order (on document.ready):
Uses an ajax call to get JSON which contains all the information needed to dynamically render images.
Iterates over the JSON object to create proper divs/img elements which are then appended to the page.
$.ajax({
url: '/wp-content/themes/base/library/ajax/posts-json.php',
type: 'get',
//dataType: 'json',
success: function(data) {
// turn string response to JSON array
window.responseArray = JSON.parse(data);
window.lastPhotoIndex = 0;
// make sure there is a response
if (responseArray.length > 0) {
// get container
var container = document.getElementById("photos-container");
var ulElement = document.createElement('ul');
ulElement.className = "rig columns-3";
ulElement.setAttribute("id", "photo-list");
// iterate over each response
window.photoCount = 0;
for (var i = 0; i < responseArray.length; i += 1) {
// Only load first 10 images
if (responseArray[i]["post-type"] == "photo" && photoCount < 20) {
// Set the last photo index to this photo
lastPhotoIndex = i;
// create the li
var liElement = document.createElement("li");
liElement.className = liElement.className + responseArray[i]["day"];
//create class name string from WP tags
if (responseArray[i].tags.length > 0) {
for (var ii = 0; ii < responseArray[i].tags.length; ii += 1) {
nospaceTagName = responseArray[i].tags[ii].split(' ').join('');
liElement.className += " " + nospaceTagName;
}
}
//create image element and append to div
var imgTag = document.createElement("img");
imgTag.src = responseArray[i]["thumb-url"];
liElement.appendChild(imgTag);
//Add modal class info to outer div
liElement.className += " md-trigger";
//Add data-modal attribute to outer div
liElement.setAttribute("data-modal", "photo-modal");
ulElement.appendChild(liElement);
//next slide
photoCount++;
}
}
//append ul to container
container.appendChild(ulElement);
}
},
error: function(xhr, desc, err) {
console.log(xhr);
console.log("Details: " + desc + "\nError:" + err);
}
});// end ajax call
After the ajax call, I add a window scroll event that will be called while there are still more photos in the JSON object.
// Window scroll event
$(window).scroll(function () {
var trigger = $(document).height() - 300;
if (trigger <= $(window).scrollTop() + $(window).height()) {
//Call function to load next 10
loadNextPhotos();
}
});
The function called by the scroll even simply starts off at the previously left off index (lastPhotoIndex variable set at the beginning of ajax call - 'window.lastPhotoIndex'). The function looks like this:
function loadNextPhotos() {
if (photoCount < getPhotoCount()) {
var photosOutput = 0;
var startingIndex = lastPhotoIndex + 1;
var photoList = $('#photo-list');
for (var i = startingIndex; i < responseArray.length; i += 1) {
if (responseArray[i]["post-type"] == "photo" && photosOutput < 10) {
lastPhotoIndex = i;
photosOutput++;
// create the li needed
var element = document.createElement("li");
element.className = responseArray[i]["day"];
//create class name string from tags
if (responseArray[i].tags.length > 0) {
for (var ii = 0; ii < responseArray[i].tags.length; ii += 1) {
nospaceTagName = responseArray[i].tags[ii].split(' ').join('');
element.className = element.className + " " + nospaceTagName;
}
}
//create image element and append to li
var imgTag = document.createElement("img");
imgTag.src = responseArray[i]["thumb-url"];
element.appendChild(imgTag);
//Add modal class info to li
element.className = element.className + " md-trigger";
//Add data-modal attribute to outer div
element.setAttribute("data-modal", "photo-modal");
photoList.append(element);
// Keep track of photo numbers so modal works for appropriate slide number
photoCount++;
}
}
}
}
Bear in mind, this code is stripped down a lot from the full application. It works fine in Chrome, Safari, Firefox, IE10+.
When loaded in IE9, I'm experiencing crazy memory leaks as I hit the scroll event and append more items to the UL.
My guess is that I'm not following best practices when creating new items to be appended and they're staying in memory longer than they should. The only issue is I'm not sure how to solve it/debug it because the page crashes so quickly in IE9.
Any help would be awesome. Thanks!
EDIT:
I've tried implementing Darmesh's solution with no real luck. As I said in his comment it only delays the rate at which memory is leaked. I've also added jquery.visible.js on top of a scroll event so it looks like this:
$(window).scroll(function () {
if($('#lazy-load-trigger').visible() && window.isLoadingPhotos != true) {
console.log("VISIBLE!");
loadNextPhotos();
}
});
But it also only delays the memory leak. I still believe there are issues with Garbage Collection in IE9, but am not sure how to troubleshoot.

I think this is due to the browser calling loadNextPhotos function multiple times at the same time every time you scroll. This might work, give it a try,
function loadNextPhotos() {
// Add flag to indicate new photos adding started
window.isLoadingPhotos = true;
....
....
....
....
// Indicate new photos adding completed
window.isLoadingPhotos = false;
}
And,
$(window).scroll(function () {
var trigger = $(document).height() - 300;
if (trigger <= $(window).scrollTop() + $(window).height()) {
if(!window.isLoadingPhotos) {
//Call function to load next 10
loadNextPhotos();
}
}
});

Related

getJSON issue with loading images across Safari and iOS

I'm trying to load a series of images that disappear at an interval of 300ms on page load.
The images are chosen at random from a JSON file, based on the users screen dimensions.
This works in Chrome but seems to fail randomly, and does not work at all in Safari (pauses on a random image) or on iOS (fails to load any images at all).
Here is my code:
// get the screen orientation based on css media query
var orientation = window.getComputedStyle(document.querySelector('body'), ':after').getPropertyValue('content').replace(/['"]+/g, '');
window.slides = [];
// function to randomise the order of an array
function shuffle(a) {
var j, x, i;
for (i = a.length - 1; i > 0; i--) {
j = Math.floor(Math.random() * (i + 1));
x = a[i];
a[i] = a[j];
a[j] = x;
}
return a;
}
$(document).ready(function() {
$( 'html' ).addClass( 'js' ).removeClass( 'no-js' );
// define the category and number of slides
var imageDirs = { "lovers": 16 };
// get slide urls from json file
$.getJSON('slides/slides.json', function (data) {
for (var thisDir in imageDirs) {
var theseSlides = [];
theseSlides = data[thisDir][orientation];
shuffle(theseSlides)
slides = theseSlides.slice(0, imageDirs[thisDir]);
}
})
.done(function() {
// randomise order of slides
shuffle(slides);
// have one blank slide at the beginning of the animation
slides.push("initial-slide.png");
if ( slides.length ) {
for (var i = 0; i < slides.length; i++) {
$('#wrapper').append('<img class="slide" src="slides/' + slides[i] + '">');
}
}
});
});
$(window).on('load', function(){
// wait for images to load before displaying animation
$('#wrapper').waitForImages(true).done(function() {
$('#preloader').remove();
var currentSlides = [];
$('.slide').each(function(){
currentSlides.push($(this));
});
// remove slides at an interval of 300ms
var timer = setInterval(function(){
if (currentSlides.length){
currentSlides.pop().remove();
} else {
clearInterval(timer);
}
}, 300);
});
});
JSFiddle: https://jsfiddle.net/robertirish/6g41vwnL/59/
Live site: http://robertirish.com
Thanks in advance!
Thanks for the fiddle. Basically your problem is that in your load event the images haven't been appended to be the DOM yet. You can see this by logging currentSlides: https://jsfiddle.net/1fb3gczq/
So, you need to ensure the DOM is ready before you manipulate it. I've moved your function to the ajax success which works in this case, but on slower connections you'll want to only run the effect once all the images have loaded in the DOM. Here's a working fiddle: https://jsfiddle.net/rcx3w48b/

passing array of hyperlinks in jquery

I am creating a flowchart using jquery and html which has nodes(circles) and arrows which connect these circles.. two actions need to be done, one is a tooltip action, which will show a particular text when u hover your cursor on particular circle. And the other function is that whenever we click those circles another html page pops up AKA hyperlinks. I have 18 circles and i hav created desired 18 HTML pages. BUt m stuck at hyperlinking. I dont know how to pass these hyperlinks to my Jquery plugin. Below is an attached code for tooltip function
function oncanvasmousemove(evt) {
clearTimeout(timer);
lastTimeMouseMoved = new Date().getTime();
timer = setTimeout(function () {
var currentTime = new Date().getTime();
if (currentTime - lastTimeMouseMoved > 300) {
var mousePos = getMousePos(canvas, evt);
var tC, isMatched = false;
for (c = 0; c < circles.length; c++) {
tC = circles[c];
if (mousePos.DistanceTo(tC.centerX, tC.centerY) < tC.Radius + 5) {
isMatched = true;
break;
}
}
if (isMatched === true) {
$("#tooltip").html(tC.Text).css({
'top': mousePos.Y + canvasoffset.top - 40,
'left': mousePos.X + canvasoffset.left - $("#tooltip").width() / 2
}).show();
} else {
$("#tooltip").hide();
}
}
}, 300);
}
i am attaching a image of the page
You need to give each circle a CSS ID.
For my example, I will just use "#circle-1", "#circle-2" ... "#circle-18".
Also add a CSS class to every circle. For my example I will use ".circle-link".
//On clicking anything with the circle-link class...
$('.circle-link').click(function() {
var link_id = $(this).attr("id"); //Get ID of circle that was clicked
//Get ID number
link_id = link_id.split("-"); //Split the string on the dash/hyphen (returns array)
link_id = link_id[2]; //Get second array element (should be the number)
//Use the above number to determine which link to call
});

Nonconcurrent async recursion

My end goal is to mitigate as much lag (window freezing/stuttering) as possible, giving the client a responsive window from page load.
My program is a Chrome extension, and part of it needs to search through a reddit submission, including all comments for certain words and then do some stuff with them. After this answer, I converted my code to use setInterval for the recursive search. Unforutnately, this runs concurrently, so even though each branch in the comment tree is delayed from its parent, the overall search overlaps each other, negating any benefit in the delay.
I have a solution, but I don't know how to implement it.
The solution would be to have a callback when a branch runs out that goes to the nearest parent fork. This in effect would traverse the comment tree linearly and would allow the setInterval (or probably setTimeout would be more appropriate) to have a noticeable affect.
The code that would need to be changed is:
function highlightComments(){
var elems = $(".content .usertext-body > .md");
var index = 0;
var total = elems.length;
console.log("comments started");
var intId = setInterval(function(){
highlightField(elems.get(index));
index++;
if(index == total){
clearInterval(intId);
addOnClick();
console.log("comments finished");
}
}, 25);
}
and highlightField is:
function highlightField(node) {
var found = $(node).attr("data-ggdc-found") === "1";
var contents = $.makeArray($(node).contents());
var index = 0;
var total = contents.length;
if (total == 0){
return;
}
var intId = setInterval(function() {
if (contents[index].nodeType === 3) { // Text
if (!found){
//Mods
var content = contents[index].nodeValue.replace(new RegExp(data.mods.regex, "gi"), data.mods.replacement);
//Creators
content = content.replace(new RegExp(data.creators.regex, "gi"), data.creators.replacement);
//Blacklist
for (var key in data.blacklist.regex){
if(data.blacklist.regex.hasOwnProperty(key)){
content = content.replace(new RegExp(data.blacklist.regex[key], "gi"), data.blacklist.replacement[key]);
}
}
if (content !== contents[index].nodeValue) {
$(contents[index]).replaceWith(content);
}
}
} else if (contents[index].nodeType === 1) { // Element
highlightField(contents[index]);
}
index++;
if(index == total){
clearInterval(intId);
}
}, 25);
}

Getting Javascript/jQuery scrolling function to work on successive divs

I'm currently trying to implement functionality similar to infinite/continuous/bottomless scrolling, but am coming up with my own approach (as an intern, my boss wants to see what I can come up with on my own). So far, I have divs of a fixed size that populate the page, and as the user scrolls, each div will be populated with an image. As of now, the function I've written works on the first div, but no longer works on successive divs.
$(window).scroll(function () {
var windowOffset = $(this).scrollTop();
var windowHeight = $(this).height();
var totalHeight = $(document).height();
var bottomOffset = $(window).scrollTop() + $(window).height();
var contentLoadTriggered = new Boolean();
var nextImageCount = parseInt($("#nextImageCount").attr("value"));
var totalCount = #ViewBag.totalCount;
var loadedPageIndex = parseInt($("#loadedPageIndex").attr("value"));
var calcScroll = totalHeight - windowOffset - windowHeight;
$("#message").html(calcScroll);
contentLoadTriggered = false;
if (bottomOffset >= ($(".patentPageNew[id='" + loadedPageIndex + "']").offset().top - 1000)
&& bottomOffset <= $(".patentPageNew[id='" + loadedPageIndex + "']").offset().top && contentLoadTriggered == false
&& loadedPageIndex == $(".patentPageNew").attr("id"))
{
contentLoadTriggered = true;
$("#message").html("Loading new images");
loadImages(loadedPageIndex, nextImageCount);
}
});
This is the image-loading function:
function loadImages(loadedPageIndex, nextImageCount) {
var index = loadedPageIndex;
for(var i = 0; i < nextImageCount; i++)
{
window.setTimeout(function () {
$(".patentPageNew[id='" + index + "']").html("<img src='/Patent/GetPatentImage/#Model.Id?pageIndex=" + index + "' />");
index++;
var setValue = index;
$("#loadedPageIndex").attr("value", setValue);
}, 2000);
}
}
I was wondering what may be causing the function to stop working after the first div, or if there might be a better approach to what I'm attempting?
EDIT: It seems that loadedPageIndex == $(".patentPageNew").attr("id") within the if statement was the culprit.
#ViewBag.totalCount; is not a JavaScript, it's .NET, so your script probably stops after encountering an error.
Also: ".patentPageNew[id='" + loadedPageIndex + "']" is inefficient. Since IDs must be unique, just query by ID instead of by class name then by ID.

replacing images inplace

I have a array of static images that I am using for an animation.
I have one frame image that I want to update the image of and I have seen a lot of tutorials on animating images with javascript just simply update the source of an image.
frame.src = animation[2].src; etc
When I look at the resource tracking in chrome, it doesnt look like they are getting cached even thought the web browser does download the image more than once but not once for each time it is displayed, so there is still some browser caching going on.
What is the best way to replace the frame image object with another image?
Well, you can either position all images absolute and give them a z-index, then use jQuery/JS to shuffle their z-indexes, bringing a new one to the top in a cross fader style,
or you can take all the id's and fadeone in slightly faster than the last one fades out.
Like so:
function fader(func) {
var currID = $('#mainimg ul').data('currLI');
var currLiStr = '#mainimg ul li#' + currID;
img = $(currLiStr).find('img').attr('src');
nextID = (currID == 'six') ? 'one' : $(currLiStr).next().attr('id');
nextLiStr = $('#mainimg ul li#' + nextID);
$(currLiStr).fadeOut(3000);
$(nextLiStr).fadeIn(2000).find('div.inner').delay(3000).fadeIn('slow').delay(6000).fadeOut('slow');
$('#mainimg ul').data('currLI',nextID);
}
Note 'six' is the id of the last li, reseting it back to one, but if you do $('#mainimg ul li:last').attr('id'); and $('#mainimg ul li:first').attr('id') to get the last and first id's, you can allow it to cope with any amount of images (obviously this is with li's given id's one, two and so on, but if you are finding out the last and first id you could use any structure.
Or you can set a ul width a width of all the li's multiplied, and give the li's the width of the images, and set overflow to hidden, then use JS to pull the li's left by the width of 1 li on each iteration in a slider like I have done here: http://www.reclaimedfloorboards.com/
There are loads of options
I ended up using jquery's replaceWith command and gave all the frames a class "frame" that i could select with $('.frame') which happened to only select visible frames.
<script type="text/javascript">
var animation = [];
var firstFrame = 1;
var lastFrame = 96;
var animationFrames = 16;
var loadedImageCount = 0;
$(function() {
$("button, input:submit",'#forecastForm').button();
$("#progressbar").progressbar({
value: 0
});
$('#id_Species').attr('onChange', 'loadAnimation($(\'#id_Species\').val(),$(\'#id_Layer\').val(),$(\'#id_StartTime\').val(),$(\'#id_EndTime\').val())' )
$('#id_Layer').attr('onChange', 'loadAnimation($(\'#id_Species\').val(),$(\'#id_Layer\').val(),$(\'#id_StartTime\').val(),$(\'#id_EndTime\').val())' )
$('#id_StartTime').attr('onChange', 'loadAnimation($(\'#id_Species\').val(),$(\'#id_Layer\').val(),$(\'#id_StartTime\').val(),$(\'#id_EndTime\').val())' )
$('#id_EndTime').attr('onChange', 'loadAnimation($(\'#id_Species\').val(),$(\'#id_Layer\').val(),$(\'#id_StartTime\').val(),$(\'#id_EndTime\').val())' )
});
if (document.images) { // Preloaded images
loadAnimation('Dry_GEM',1,1,96);
}
function rotate(animation, frame)
{
if (frame >= animation.length)
frame = 0;
$('.frame').replaceWith(animation[frame]);
window.setTimeout('rotate(animation,'+eval(frame+1)+')',150);
}
function loadAnimation(species, layer, startTime, endTime)
{
layer = Number(layer);
startTime = Number(startTime);
endTime = Number(endTime);
if (startTime > endTime)
{
swap = startTime;
startTime = endTime;
endTime = swap;
delete swap;
}
for (i=0;i<animation.length;i++)
delete animation[i];
delete animation;
animation = []
$('#progressbar').progressbar({value: 0});
loadedImgCount = 0;
animationFrames = endTime - startTime + 1;
for(i=0;i < animationFrames;i++)
{
animation[i] = new Image();
animation[i].height = 585;
animation[i].width = 780;
$(animation[i]).attr('class','frame');
animation[i].onload = function()
{
loadedImgCount += 1;
$('#progressbar').progressbar({value: eval(loadedImgCount / animationFrames * 100)});
};
animation[i].src = 'http://[[url]]/hemi_2d/' + species + '_' + layer + '_' + eval(i+startTime) + '.png';
}
}
</script>
The easiest way to do it is create a separate hidden image for each frame. Something like this:
var nextImage = (function(){
var imagePaths='basn0g01.png,basn0g02.png,basn0g04.png,basn0g08.png'.split(','),
imageHolder=document.getElementById('custom-header'),
i=imagePaths.length, imageIndex=i-1, img;
for (;i--;) {
img=document.createElement('img');
img.src='http://www.schaik.com/pngsuite/' + imagePaths[i];
if (i) img.style.display='none';
imageHolder.appendChild(img);
}
return function(){
imageHolder.childNodes[imageIndex].style.display='none';
if (++imageIndex >= imageHolder.childNodes.length) imageIndex=0;
imageHolder.childNodes[imageIndex].style.display='inline-block';
}
}());
Try this example on this page; paste it in the console and then call nextImage() a few times. Watch the top of the page.
edit
If you already have all the images in your HTML document, you can skip most of the above and just do something like this:
var nextImage = (function(){
var imageHolder=document.getElementById('custom-header'),
images=imageHolder.getElementsByTagName('img'),
i=images.length, imageIndex=0, img;
for (;i--;) if (i) images[0].style.display='none';
return function(){
imageHolder.childNodes[imageIndex].style.display='none';
if (++imageIndex >= imageHolder.childNodes.length) imageIndex=0;
imageHolder.childNodes[imageIndex].style.display='inline-block';
}
}());

Categories

Resources