I' m writing a web app that needs to load dynamically a lot of images.
I wrote an utility function, loadMultipleImages, that takes care of loading them and calling (possibly optional) callbacks whenever:
a single image is loaded
an error is encountered (a single image can't load)
all the images are loaded without errors
I invoke it like this (you can find a complete example here):
var imageResources = [];
var mulArgs = {multipleImages: [],
onComplete: this.afterLoading.bind(MYAPP),
onError: this.logError.bind(MYAPP)
}
imageResources = ["imageA_1.png", "imageA_2.png"];
mulArgs.multipleImages.push ({ID: "imageA_loader", imageNames : imageResources});
imageResources = ["imageB_1.png", "imageB_2.png"];
mulArgs.multipleImages.push ({ID: "imageB_loader", imageNames : imageResources});
//Lots of more images
var mImageLoader = new loadMultipleImages (mulArgs);
As soon as I create my loadMultipleImages object, it loads the images and calls the afterLoading() function after they are all loaded (or logError() if there's some problem). Then I can access to the images with:
MYAPP.afterLoading = function (loaders) {
// Just an example
var imageA_array = loaders["imageA_loader"].images;
var imageB_first = loaders["imageB_loader"].images[0];
}
By the way, I'm thinking that I'm reinventing the (possibly square) wheel. Is there a lightweight, simple library that does that better than I'm doing? Or simply a better method that spares me the burden of maintaining the loadMultipleImages code?
http://jsfromhell.com/classes/preloader
Related
I am using the JavaScript (shown below) to preload/prefetch images. The use case is that I have client-side searchable lists of up to 30,000 records, and when a user uses the search I want the avatar/image associated with the search result to render immediately.
I'm running into apparent browser performance issues (browser and other JS code starts to become less responsive) when using the code below with large arrays of images (ie. 5,000 images).
Does anyone have suggestions for how to optimize this code to perform better? Or, is there a way to do this in the background using a Service Worker?
function prefetchImages(arr) {
var cache = document.createElement("CACHE");
cache.style = "position:absolute;z-index:-1000;opacity:0;";
document.body.appendChild(cache);
for (var i=0; i<arr.length; i++) {
var img = new Image();
img.src = arr[i];
img.style = "position:absolute";
cache.appendChild(img);
}
}
prefetchImages(['https://avatars.githubusercontent.com/u/9919?s=200&v=4']
I need to add an overlay to an existing OpenSeadragon viewer object which isn't created by my code, but elsewhere in the application.
I have got to a point where I know that the viewer has been created as I can access the various html elements that are created via jQuery. However I can't work out if there's any way to create a viewer from an existing reference.
I've tried using the id of the viewer div in:
var viewer = OpenSeadragon(id: "open-seadragon-viewer-id");
but this doesn't seem to work.
Is there any way to do this or can you only get the viewer within the code that initialised it?
Here's one crazy thought... you could monkey-patch some portion of OSD in order to grab the viewer...
var viewer;
var originalIsOpen = OpenSeadragon.Viewer.prototype.isOpen;
OpenSeadragon.Viewer.prototype.isOpen = function() {
// Now we know the viewer!
viewer = this;
// Reinstate the original, since we only need to run our version once
OpenSeadragon.Viewer.prototype.isOpen = originalIsOpen;
// Call the original
return originalIsOpen.call(this);
}
It's kind of tacky, but should work. Note this assumes there is only one viewer on the page... if there are more than one, the same principle could work but you would need to keep track of an array of viewers.
BTW, I'm using isOpen, because it's simple and it gets called every frame. Other functions could work as well.
EDIT: fixed code so we are using the prototype. I still haven't actually tested this code so there may still be bugs!
This solution does not directly answer the question, as it relies on your own code creating the OpenSeaDragon object. It is an implementation of #iangilman's mention of storing the viewer in a global variable. However others may find it useful. (Note that passing a global variable to a function requires a workaround - see Passing a global variable to a function)
The code demonstrates how to use the same OpenSeaDragon object to display different pictures.
var viewer3=null; //global variable
var newURL1='image/imageToDisplay1.png';
var newURL2='image/imageToDisplay2.png';
var elementID='myID';
//the loadScan function will display the picture using openSeaDragon and can be called as many times as you want.
loadScan("viewer3",newURL1,elementID);
loadScan("viewer3",newURL2,elementID);
//the actual function
function loadScan(theViewer,newURL,theID) {
//if object has already been created, then just change the image
if (window[theViewer]!=null) {
window[theViewer].open({
type: 'image',
url: newURL
});
} else {
//create a new OpenSeadragon object
window[theViewer] = OpenSeadragon({
prefixUrl: "/myapp/vendor/openseadragon/images/",
id: theID,
defaultZoomLevel: 1,
tileSources: {
url: newURL,
type: 'image'
}
});
}
}
I'm playing around with benchmarking in order to see how much of the pie my custom javascript takes up vs the things out of my control as far as optimization is concerned: dom/network/painting, etc.
I would use chrome's dev tools for this, but i don't see an accurate pie chart since my functions do ajax calls and therefore network is added to the javascript portion of the pie (as well as dom and other 'out of my control' stuff).
I'm using benchmarkjs (http://benchmarkjs.com/) to test this line:
document.querySelector("#mydiv").innerHTML = template(data);
where template is a precompiled handlebars template.
To the question...
I've broken the process down into 3 parts and took the mean of each run:
document.querySelector("#mydiv") - 0.00474178430265463
myDiv.innerHTML = already_called_template - 0.005627522903454419
template(data) - 0.004687963725254854
But all three together (the one liner above) turns out to be: 0.005539341673858488
Which is less than the lone call to set innerHTML.
So why don't the parts equal the sum? Am I doing it wrong?
Sample benchmark below (i'm using deferred as a constant because i plan to add ajax next):
var template = Handlebars.compile(html);
var showStats = function(e) { console.log(e.target.stats.mean); };
var cachedDiv = document.querySelector('#myDiv');
var cachedTemplate = template(data);
new Benchmark('just innerHTML', function(deferred) {
cachedDiv.innerHTML = cachedTemplate;
deferred.resolve();
}, {defer: true, onComplete: showStats}).run();
new Benchmark('full line', function(deferred) {
document.querySelector('#myDiv').innerHTML = template(users);
deferred.resolve();
}, {defer: true, onComplete: showStats}).run();
http://mrale.ph/blog/2012/12/15/microbenchmarks-fairy-tale.html
Turns out the jit is probably doing some crazy sh.... stuff.
The answer would be to try to outsmart it, but I think I should adjust my strategy instead.
Building a single page / fat client application and I'm wondering what the best practice is for including and tracking using http://piwik.org/
I'd like to use Piwik in a way that is architecturally sound and replacable with a different library in the future.
It seems that there are two basic options for tracking with Piwik:
Fill up a global _paq array with commands, then load the script (it's unclear to me how to record future "page" views or change variables though)
Get and use var myTracker = Piwik.getTracker()
_paq approach:
myApp.loadAnalytics = function() { /* dynamically insert piwik.php script */ }
myApp.track = function(pageName) {
window._paq = window._paq || [];
_paq.push(['setDocumentTitle', pageName]);
_paq.push(["trackPageView"]);
}
myApp.loadAnalytics()
// Then, anywhere in the application, and as many times as I want (I hope :)
myApp.track('reports/eastWing') // Track a "page" change, lightbox event, or anything else
.getTracker() approach:
myApp.loadAnalytics = function() { /* dynamically insert piwik.php script */ }
myApp.track = function(pageName) {
myApp.tracker = myApp.tracker || Piwik.getTracker('https://mysite.com', 1);
myApp.tracker.trackPageView(pageName);
}
myApp.loadAnalytics()
// Then, anywhere in the application, and as many times as I want (I hope :)
myApp.track('reports/eastWing') // Track a "page" change, lightbox event, or anything else
Are these approaches functionally identical? Is one preferred over another for a single page app?
To have the tracking library used (eg. piwik) completely independent from your application, you would need to write a small class that will proxy the functions to the Piwik tracker. Later if you change from Piwik to XYZ you can simply update this proxy class rather than updating multiple files that do some tracking.
The Async code is a must for your app (for example a call to any 'track*' method will send the request)
The full solution using .getTracker looks like this:
https://gist.github.com/SimplGy/5349360
Still not sure if it would be better to use the _paq array instead.
I'm working on a site where I'd like to cycle images, similar to a slideshow, while the user is on the page. I've searched around and haven't been able to find a lead.
Has anyone done this with Rails and the Javascript frameworks it supports?
you could possible use the jquery cycle plugin, here's the link: http://malsup.com/jquery/cycle/ . It looks like it would do what you want.
FYI, Rails isn't particularly tied to any JS framework, even though it comes with prototype out of the box.
I assume that when you say AJAX, you don't mean AJAX. If you were to rotate the image using AJAX, you would have the rotated image being generated server side and sent to the client, as AJAX = performing requests with javascript. </pedantic>
With that said, you can use pretty much any JS image rotator you can find on google.
EDIT: Oh, you meant cycling through images, not rotating it e.g. 90ยบ clockwise etc. (?)
I've been using this very simple function. It doesn't use any framework and it doesn't fade between slides, but it should get you started.
function SlideShow( elem_id, hold_time )
{
this.elem = document.getElementById( elem_id );
this.slides = [];
this.num_slides = 0;
this.cur_slide = 1;
this.add_slide = function( image )
{
this.slides[ this.num_slides++ ] = image;
}
var self = this;
this.next_slide = function()
{
if ( self.num_slides > 1 )
{
self.elem.src = self.slides[ self.cur_slide++ ].src;
if ( self.cur_slide == self.num_slides )
self.cur_slide = 0;
}
}
setInterval( self.next_slide, hold_time )
}
the parameters are the element_id of an img tag and the number of mS to display each slide.
the add_slide function takes a JavaScript Image object.
The reason cur_slide is initialised to 1 is because I pre load the img tag with the first image.
In my application I create the slideshow in the window.onload method and arrange for each Image to add itself to the slide show as it is loaded.
Example (untested):
window.onload = function() {
var slide_show = new SlideShow( "slide_image", 4000 )
{ var img = new Image();
img.onload = function(){ slide_show.add_slide(img); };
img.src="/images/slide1.jpg"; }
...
/* Repeated for each image */
}
This approach is only valid if you don't care about the order of the slides.
I assume you want to make persistent changes on images. I'm working on a Rails app where we implemented resize, crop and rotate similar to features in snipshot.com
On technical side we used YUI for resize and crop in user interface and RMagick on server to process the images and send the results back to UI. YUI provides imagecrop widget out-of-the box.
We also considered doing a series of actions in UI and then submit the last result for server-side processing but that would have been lead to inadequate results.