问题
I am making an image slideshow using native JS.
I decided to make it myself because it needs to run at ~100ms intervals, and without any special transition effects (see it here), so I figured it was unnecessary to include a big library like JQuery just for just a simple application.
This is the code I am currently using [edit: original code - now modified]:
// JavaScript Document
function preloadimages(arr){ // the preloadimages() function is adapted from http://www.javascriptkit.com/javatutors/preloadimagesplus.shtml
var newimages = [], loadedimages = 0;
var postaction = function() {};
var arr = (typeof arr != \"object\") ? [arr] : arr;
function imageloadpost() {
loadedimages++;
if (loadedimages == arr.length) {
postaction(newimages); //call postaction and pass in newimages array as parameter
}
}
for (var i = 0; i < arr.length; i++) {
newimages[i] = new Image();
newimages[i].src = arr[i];
newimages[i].onload = function() {
imageloadpost();
}
newimages[i].onerror = function() {
imageloadpost();
}
}
return { //return blank object with done() method
done: function(f) {
postaction = f || postaction; //remember user defined callback functions to be called when images load
}
}
}
/* USAGE:
preloadimages([\'ed.jpg\', \'fei.jpg\', \'budapest.gif\', \'duck.jpg\']).done(function(images) {
images.sort(function(a, b) {
return a.width - b.width; //sort images by each image\'s width property, ascending
});
alert(images[0].src); //alerts the src of the smallest image width wise
});
*/
function animateSlideshow() {
var num = window.imgNum + 1 ;
if (num >= d[\'imgs\'].length) {
num = 0;
}
window.imgNum = num;
imgTag.src = d[\'imgs\'][num];
var t = window.setTimeout(function(){animateSlideshow(imgNum, imgTag, d)}, 100);
}
var d;
var imgTag;
var imgNum = 0;
$.onDomReady (function () { // This is not JQuery, it\'s a simple cross-browser library which you can read here: http://assets.momo40k.ch/common/js/$.js
// data is an array that should be already defined on the calling page,
// containing all the necessary information to generate all the rotation slideshows on the page
for (i = 0; i < data.length; i++) {
d = data[i];
var div = document.getElementById(d[\'id\']);
imgTag = $.Elements.getElementsByClassName(\'theImage\', div)[0];
// preload the images...
preloadimages(d[\'imgs\']).done(function(images) {
imgTag.src = d[\'imgs\'][0];
animateSlideshow();
});
}
});
<!-- HTML calling JS Scripts -->
... HTML document ...
<script src=\"http://assets.momo40k.ch/common/js/$-min.js\" language=\"javascript\" type=\"text/javascript\"></script>
<script language=\"javascript\" type=\"text/javascript\">
var data = [];
// I would have an index for each slideshow on the page
data[0] = [];
data[0][\'id\'] = \'rotation2\';// the ID of the tag the initial image is in
data[0][\'imgs\'] = [\'http://www.momo40k.ch/images/pages/stefan_lehmann/bat/pic1.png\',
\'http://www.momo40k.ch/images/pages/stefan_lehmann/bat/pic2.png\',
\'http://www.momo40k.ch/images/pages/stefan_lehmann/bat/pic3.png\',
\'... all the images ... \'];
</script>
<script src=\"js/rotation.js\" language=\"javascript\" type=\"text/javascript\"></script>
</body>
</html>
This is what the tag the initial image is in looks like:
<div id=\"rotation2\" class=\"rotation blackbg\">
<img src=\"http://www.momo40k.ch/images/pages/stefan_lehmann/bat/pic1.png\" width=\"300\" title=\"\" class=\"theImage\" />
</div>
Now for the question:
this script only allows me to have one single \'slideshow\' on the page - because in each iteration of the loop it overrides the imgNum variable. Is there an other, better way of doing this slideshow (if possible without JQuery, otherwise OK), even in a completely different way?
Thank you
EDIT: I have remade the script following Jared Farrish\'s answer and it\'s now working fine!
回答1:
There were some issues I saw with your code or approach, so I decided to redo it with the approach I would take. For instance:
- I would use
document.imagesto get all images and, for ones that have the rotator-specificclassName, to identify (domElement.parentNode) and obtain the containingdiv, which will give me it'sid. - I would use the
parentNode.idof theclass="rotation"images to create an object with sets (by containerids) I can use to store references to theimgnodes. - Use closure scope to stay out of the global scope, as well as be able to share variables between the closure-scoped functions.
- Use variable functions to setup the handler and callback functions.
Let me know if you have any questions or find something that doesn't work.
<div id="rotator1" class="rotation blackbg">
<img class="slides" src="http://upload.wikimedia.org/wikipedia/commons/6/6e/Brandenburger_Tor_2004.jpg" />
<img class="slides" src="http://upload.wikimedia.org/wikipedia/commons/a/ad/Cegonha_alsaciana.jpg" />
<img class="slides" src="http://upload.wikimedia.org/wikipedia/commons/d/da/CrayonLogs.jpg" />
</div>
<div id="rotator2" class="rotation blackbg">
<img class="slides" src="http://upload.wikimedia.org/wikipedia/commons/1/17/Bobbahn_ep.jpg" />
<img class="slides" src="http://upload.wikimedia.org/wikipedia/commons/9/90/DS_Citro%C3%ABn.jpg" />
<img class="slides" src="http://upload.wikimedia.org/wikipedia/commons/f/f0/DeutzFahr_Ladewagen_K_7.39.jpg" />
<img class="slides" src="http://upload.wikimedia.org/wikipedia/commons/c/c7/DenglerSW-Peach-faced-Lovebird-20051026-1280x960.jpg" />
<img class="slides" src="http://upload.wikimedia.org/wikipedia/commons/4/4d/FA-18F_Breaking_SoundBarrier.jpg" />
</div>
var slideshows = function(){
var timeouts = {},
imgs;
function preloadImages(list){
var loading = list,
img,
loaded = {},
newimages = [];
var imageloaded = function(){
// this here is one of the new Image()s we created
// earlier; it's not the "real" image on the screen.
// So I put the reference to the actual image it represents
// on the screen in the rel attribute so I can use it's
// reference; I just have to use this.rel to get it.
var parent = this.rel.parentNode;
// Keeping track of the individual sets loaded.
loaded[parent.id]++;
// Here is where this/self gets it's context from, when
// we .call(parent, 0). It's the parentNode to the image
// we've referenced above. This should only run once,
// when the last image has loaded for the set.
if (loaded[parent.id] == loading[parent.id].length) {
animateSlideshow.call(parent, 0);
}
};
var imagenotloaded = function(){
// this.rel is the reference to the actual image we
// have in the DOM, so we'll set the error flag on it.
this.rel['imageerror'] = true;
imageloaded.call(this);
};
for (var load in loading) {
// loaded is equivalent to imgs.sets, so load is the
// id for the container.
loaded[load] = [];
// Now we're going to loop over every image in the
// current set, creating a Javascript image object
// to initiate the download of the file and tell us
// when it's finished. Not the newimages[i].rel = img
// part.
for (var i = 0, l = loading[load].length; i < l; i++) {
img = loading[load][i];
newimages[i] = new Image();
newimages[i].onload = imageloaded;
newimages[i].onerror = imagenotloaded;
newimages[i].rel = img;
newimages[i].src = img.src;
}
}
}
var animateSlideshow = function(current) {
// this could be used instead of self. I was doing
// something else at first, but making this copy
// of the context (this) is not necessary with this
// code. It doesn't hurt either.
var self = this;
// Our current context is the containing div, so
// this.id/self.id will give us the key to the correct
// group in imgs.sets, and the current argument will
// tell us with image in that list we're currently
// working with. First, we hide the last displayed
// image.
imgs.sets[self.id][current].style.display = 'none';
// Increment to get the next image.
current++;
// If we're at the end, let's move back to the
// beginning of the list.
if (current >= imgs.sets[self.id].length) {
current = 0;
}
// This skips images which had an error on load. The
// test for this in the markup above is the third image
// in rotator1, which is not an image url.
if (imgs.sets[self.id][current].imageerror == true) {
// See how I'm passing self using .call()? This sets
// the context for the next animateSlideshow() call,
// which allows this/self to work on the correct div
// container.
animateSlideshow.call(self, current);
return;
}
imgs.sets[self.id][current].style.display = 'inline';
// Everything is organized by the self.id key, event
// saving the references to the timeouts.
timeouts[self.id] = setTimeout(function(){
animateSlideshow.call(self, current);
}, 100);
};
function getImages(){
var list = document.images,
img,
data = {sets: {}, allimages: []},
parent;
// document.images gives me an array of references to all
// img elements on the page. Let's loop through and create
// an array of the relevant img elements, keying/grouping on the
// parent element's id attribute.
for (var i = 0, l = list.length; i < l; i++){
img = list[i];
parent = img.parentNode;
// parent should now be a reference to the containing div
// for the current img element. parent.id should give us
// rotator1 or rotator2 in the demo markup.
if (parent.className.indexOf('rotation') !== -1) {
if (!data.sets[parent.id]) {
data.sets[parent.id] = [];
}
// Let's put the img reference into the appropriate
// imgs.sets. I also put the img.src into an index
// container in data.allimages; this is also a remnant
// of a previous approach I took. It could probably be
// removed unless you need it.
data.sets[parent.id].push(img);
data.allimages.push(img.src);
}
}
return data;
}
function initializeSlideshows(){
imgs = getImages();
preloadImages(imgs.sets);
}
initializeSlideshows();
};
$.onDomReady(slideshows);
http://jsfiddle.net/DLz92/1
来源:https://stackoverflow.com/questions/9240479/javascript-to-make-a-fast-running-image-slideshow