如何知道是否所有 onload 事件都已触发?
How to know if all onload Events already fired?
这个问题更具体,是这个问题的超集:How to know if window "load" event was fired already .
用例
所以我有这个用例,我需要制作延迟加载的 CSS-仅图片幻灯片,为此我通过 onload 事件实现了它,该事件随幻灯片编号一起触发,在延迟后触发仅当图片完成加载相关图片时。问题是建议的答案在第一个 onload 事件触发后显示 'complete'
,这不适合我。所以我的问题是 - 如何检测是否所有 onload 事件都已触发?
问题
我所有的幻灯片都放完了,然后放映就出现问题了。这就是为什么我需要检测这种情况,以便我可以在事后控制我的幻灯片行为,从而解决这个问题。
StackBlitz Link
https://stackblitz.com/edit/js-fsymew
GitHub Link
https://github.com/munchkindev/js-fsymew
我的HTML:
<div style="margin-bottom: 30px;">
<img class="slider" src="graphics/slider1.webp" onload="delayedCarousel(1);">
<div class="slider-text" id="slidert1">Text here, you can just ignore this.</div>
<img loading="lazy" class="slider" src="graphics/slider2.webp" onload="delayedCarousel(2);" style="display:none">
<img loading="lazy" class="slider" src="graphics/slider3.webp" onload="delayedCarousel(3);" style="display:none">
<img loading="lazy" class="slider" src="graphics/slider4.webp" onload="delayedCarousel(4);" style="display:none">
<img loading="lazy" class="slider" src="graphics/slider5.webp" onload="delayedCarousel(5);" style="display:none">
<img loading="lazy" class="slider" src="graphics/slider6.webp" onload="delayedCarousel(1);" style="display:none">
<button class="slider-button slider-black slider-display-left" onclick="changeSlide(-1);">❮</button>
<button class="slider-button slider-black slider-display-right" onclick="changeSlide(1);">❯</button>
</div>
JS:
function carousel(n) {
var i;
var x = document.getElementsByClassName("slider");
if (n === 6) { delayedCarousel(1); }
//for (i = 0; i < x.length; i++) {
x[n].style.display = "block";
if (n === 1) x[5].style.display = "none";
//}
slideIndex++;
//if (n > x.length) {/*slideIndex = 1*/
x[n-1].style.display = "none";
//setTimeout(carousel, 5000); // Change image every 5 seconds
}
function delayedCarousel(n) {
setTimeout(function(){
carousel(n);
}, 3000);
}
function changeSlide(n) {
showDivs(slideIndex += n);
}
function showDivs(n) {
var i;
var x = document.getElementsByClassName("slider");
if (n > x.length) { slideIndex = 1 }
if (n < 1) { slideIndex = x.length }
for (i = 0; i < x.length; i++) {
x[i].style.display = "none";
x[i].style.width = "100%";
}
x[slideIndex - 1].style.display = "block";
}
尝试 2
HTML:
<div style="margin-bottom: 30px;">
<img class="slider" src="graphics/slider1.webp" onload="delayedCarousel(1);">
<div class="slider-text" id="slidert1">Ignore this text, it's irrelevant.</div>
<img loading="lazy" class="slider" src="graphics/slider2.webp" onload="delayedCarousel(2);" style="display:none">
<img loading="lazy" class="slider" src="graphics/slider3.webp" onload="delayedCarousel(3);" style="display:none">
<img loading="lazy" class="slider" src="graphics/slider4.webp" onload="delayedCarousel(4);" style="display:none">
<img loading="lazy" class="slider" src="graphics/slider5.webp" onload="delayedCarousel(5);" style="display:none">
<img loading="lazy" class="slider" src="graphics/slider6.webp" onload="loopedCarousel(1);" style="display:none">
<button class="slider-button slider-black slider-display-left" onclick="changeSlide(-1);">❮</button>
<button class="slider-button slider-black slider-display-right" onclick="changeSlide(1);">❯</button>
</div>
JS:
// function startSliderLoop() {
// var i = 1;
// setInterval(function() {
// location.href = "/#slide-"+i;
// //document.addEventListener('click', function (event) {
// // If the clicked element does not have and is not contained by an element with the .click-me class, ignore it
// //if (!event.target.closest('#slideButton'+ i )) return;
// // Otherwise, do something...
// if (i <= 5)
// i++;
// else
// i = 1;
//}, 3000)
// }
// document.addEventListener("DOMContentLoaded", (event) => {
// startSliderLoop();
//});
var slideIndex = 0;
var sliders = document.querySelector('.slider');
//document.querySelector('.slider').forEach(function(img){
// img.addEventListener('load', carousel());
// });
function carousel(n) {
var i;
var x = document.getElementsByClassName("slider");
if (n === 6) { delayedCarousel(1); }
//for (i = 0; i < x.length; i++) {
x[n].style.display = "block";
if (n === 1) x[5].style.display = "none";
//}
slideIndex++;
//if (n > x.length) {/*slideIndex = 1*/
x[n-1].style.display = "none";
//setTimeout(carousel, 5000); // Change image every 5 seconds
}
function loopedCarousel(n) {
var i;
var x = document.getElementsByClassName("slider");
if (n === 5) { n=1; loopedCarousel(1); }
//for (i = 0; i < x.length; i++) {
x[n].style.display = "block";
if (n === 1) x[5].style.display = "none";
//}
slideIndex++;
//if (n > x.length) {/*slideIndex = 1*/
x[n-1].style.display = "none";
setTimeout(loopedCarousel(n+1), 5000); // Change image every 5 seconds
}
function delayedCarousel(n) {
setTimeout(function(){
carousel(n);
}, 3000);
}
<script>
window.onload = function() {
// code for when entire window loads
}
</script>
当 window 加载时,window.onload 是 运行。这应该是在所有元素、脚本和其他内容加载之后。
我会使用 promises 来监听图片加载事件,这样你就可以在启动轮播之前等待所有的 promises 完成。
例如,如果您有以下 html:
<img src="https://picsum.photos/100/300" />
<img src="https://picsum.photos/200/300" />
<img src="https://picsum.photos/300/300" />
<img src="https://picsum.photos/400/300" />
<img src="https://picsum.photos/500/300" />
您可以等待所有这些加载如下:
const promises = [];
document.querySelectorAll('img').forEach((img) => {
const promise = new Promise((resolve, reject) => {
img.addEventListener('load', resolve);
img.addEventListener('error', reject);
});
promises.push(promise);
});
Promise.all(promises).then(() => {
console.log('All images successfully loaded.');
});
我在下面为您创建了工作示例。您会注意到,在加载所有图像之前,轮播将显示红色边框。加载后边框颜色变为绿色。
class CarouselController {
/**
* @param {object} settings
*/
constructor(settings) {
this.carousel = settings.element;
this.current = 0;
if (!this.carousel) {
throw 'A carousel element is required. For example: new CarouselController({ element: document.getElementById(\'carousel\') })';
}
this.settings = {
loop: 'loop' in settings ? settings.loop : true,
delay: 'delay' in settings ? parseInt(settings.delay) : 5000
};
}
/**
* Get the carousel container element.
* @returns {Element}
*/
getCarousel() {
return this.carousel;
}
/**
* Get a setting value.
* @param {string} name
* @param defaultValue
* @returns {*}
*/
getSetting(name, defaultValue) {
return name in this.settings ? this.settings[name] : defaultValue;
}
/**
* Get all the children (slides) elements.
* @returns {Element[]}
*/
getSlides() {
return Array.from(this.getCarousel().children);
}
/**
* Get a specific slide by index.
* @param {int} index
* @returns {Element|null}
*/
getSlide(index) {
return this.getSlides()[index];
}
/**
* Show a specific slide by index.
* @param {int} index
* @returns {int}
*/
goTo(index) {
const slides = this.getSlides();
const slide = this.getSlide(index);
if (slide) {
slides.forEach((el) => {
el.classList.remove('active');
});
slide.classList.add('active');
this.current = slides.indexOf(slide);
}
return this.current;
}
/**
* Show the next slide (if has one).
*/
next() {
let replay = false;
// Check if carousel is looping through slides automatically.
if (this.playing) {
replay = true;
}
const slides = this.getSlides();
let nextIndex = this.current + 1;
// If the next slide is greater than the total, reset to 0 if looping else use -1 to stop `goTo` method.
if (nextIndex > (slides.length - 1)) {
if (this.getSetting('loop')) {
nextIndex = 0;
} else {
nextIndex = -1;
}
}
// Only go to slide if next index is valid.
if (nextIndex >= 0) {
this.goTo(nextIndex);
// Continue with auto play.
if (replay) {
this.play();
}
}
}
/**
* Show the previous slide (if has one).
*/
previous() {
let replay = false;
// Check if carousel is looping through slides automatically.
if (this.playing) {
replay = true;
}
const slides = this.getSlides();
let prevIndex = this.current - 1;
// If the prev slide is less than 0, reset to the last slide if looping else use -1 to stop `goTo` method.
if (prevIndex < 0) {
if (this.getSetting('loop')) {
prevIndex = slides.length - 1;
} else {
prevIndex = -1;
}
}
// Only go to slide if next index is valid.
if (prevIndex >= 0) {
this.goTo(prevIndex);
// Continue with auto play.
if (replay) {
this.play();
}
}
}
/**
* Automatically go to the next slide (or start if loop is true).
* @returns {number}
*/
play() {
this.stop();
this.goTo(this.current);
this.playing = setInterval(() => {
this.next();
}, this.getSetting('delay'));
return this.playing;
}
/**
* Stop the automatic carousel if running.
*/
stop() {
if (this.playing) {
clearInterval(this.playing);
}
}
}
/**
* Get the carousel container element.
* @type {Element}
*/
const carouselContainer = document.querySelector('.carousel-container');
/**
* Create a new controller instance for our carousel.
* @type {CarouselController}
*/
const carousel = new CarouselController({
element: carouselContainer.querySelector('.carousel'),
loop: true,
delay: 3000
});
/**
* Build an array of image load promises.
* @type {Promise[]}
*/
const imagePromises = [];
carousel.getCarousel().querySelectorAll('img').forEach((el) => {
const promise = new Promise((resolve, reject) => {
const image = new Image();
image.addEventListener('load', resolve);
image.addEventListener('error', reject);
image.src = el.src;
});
imagePromises.push(promise);
});
/**
* Wait for all image promises to complete (even if failed) and initiate the carousel.
* @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled
*/
Promise.allSettled(imagePromises).then(() => {
carouselContainer.classList.add('loaded');
/**
* Start auto playing with settings defined in the constructor.
*/
carousel.play();
/**
* Show previous slide (if has one) when clicking previous button.
*/
document.querySelector('.carousel-prev').addEventListener('click', function(event) {
event.preventDefault();
carousel.previous();
});
/**
* Show next slide (if has one) when clicking next button.
*/
document.querySelector('.carousel-next').addEventListener('click', function(event) {
event.preventDefault();
carousel.next();
});
});
/* Container styles BEFORE carousel has loaded */
.carousel-container {
opacity: 0.3;
pointer-events: none;
border: 2px solid red;
}
/* Container styles AFTER carousel has loaded */
.carousel-container.loaded {
opacity: 1;
pointer-events: auto;
border-color: green;
}
/* Hide all carousel slides by default */
.carousel-container .carousel > * {
display: none;
}
/* Only show the active carousel slide */
.carousel-container .carousel > *.active {
display: block;
}
<div class="carousel-container">
<div class="carousel">
<img src="https://picsum.photos/100/300" />
<img src="https://picsum.photos/200/300" />
<img src="https://picsum.photos/300/300" />
<img src="https://picsum.photos/400/300" />
<img src="https://picsum.photos/500/300" />
</div>
<button class="carousel-prev">
❮
</button>
<button class="carousel-next">
❯
</button>
</div>
或者您可以在 JSFiddle 上查看它:https://jsfiddle.net/thelevicole/b13gpyfd/2/
编辑
下面是更新的示例,说明如何仅在幻灯片处于视图中时才延迟加载幻灯片图像,而不是等待所有图像在初始化时加载。
https://jsfiddle.net/thelevicole/b13gpyfd/3/
class CarouselController {
defaultSettings = {
loop: true,
delay: 5000,
autoplay: true
}
/**
* @param {object} settings
*/
constructor(settings) {
this.carousel = settings.element;
delete settings.element;
this.current = 0;
this.hooks = {};
this.settings = settings;
if (!this.carousel) {
throw 'A carousel element is required. For example: new CarouselController({ element: document.getElementById(\'carousel\') })';
}
/**
* Sanitize `loop` setting
*/
this.addFilter('setting.loop', value => {
return String(value).toLowerCase() === 'true';
});
/**
* Sanitize `delay` setting
*/
this.addFilter('setting.delay', value => parseInt(value));
/**
* Sanitize `autoplay` setting
*/
this.addFilter('setting.autoplay', value => {
return String(value).toLowerCase() === 'true';
});
// Autoplay on init.
if (this.getSetting('autoplay')) {
this.play();
}
}
/**
* Get the carousel container element.
* @returns {Element}
*/
getCarousel() {
return this.carousel;
}
/**
* Get a setting value.
* @param {string} name
* @param defaultValue
* @returns {*}
*/
getSetting(name, defaultValue) {
if (!defaultValue && name in this.defaultSettings) {
defaultValue = this.defaultSettings[name]
}
/**
* Apply value filters.
* @example carousel.addFilter('setting.delay', function(value) { return value + 500; });
*/
return this.applyFilters(`setting.${name}`, name in this.settings ? this.settings[name] : defaultValue);
}
/**
* Get hooks by type and name. Ordered by priority.
* @param {string} type
* @param {string} name
* @returns {array}
*/
getHooks(type, name) {
let hooks = [];
if (type in this.hooks) {
let localHooks = this.hooks[type];
localHooks = localHooks.filter(el => el.name === name);
localHooks = localHooks.sort((a, b) => a.priority - b.priority);
hooks = hooks.concat(localHooks);
}
return hooks;
}
/**
* Add a hook.
* @param {string} type
* @param {object} hookMeta
*/
addHook(type, hookMeta) {
// Create new local hook type array.
if (!(type in this.hooks)) {
this.hooks[type] = [];
}
this.hooks[type].push(hookMeta);
}
/**
* Add action listener.
* @param {string} action Name of action to trigger callback on.
* @param {function} callback
* @param {number} priority
*/
addAction(action, callback, priority = 10) {
this.addHook('actions', {
name: action,
callback: callback,
priority: priority
});
}
/**
* Trigger an action.
* @param {string} name Name of action to run.
* @param {*} args Arguments passed to the callback function.
*/
doAction(name, ...args) {
this.getHooks('actions', name).forEach(hook => {
hook.callback(...args);
});
}
/**
* Register filter.
* @param {string} filter Name of filter to trigger callback on.
* @param {function} callback
* @param {number} priority
*/
addFilter(filter, callback, priority = 10) {
this.addHook('filters', {
name: filter,
callback: callback,
priority: priority
});
}
/**
* Apply all named filters to a value.
* @param {string} name Name of action to run.
* @param {*} value The value to be mutated.
* @param {*} args Arguments passed to the callback function.
* @returns {*}
*/
applyFilters(name, value, ...args) {
this.getHooks('filters', name).forEach(hook => {
value = hook.callback(value, ...args);
});
return value;
}
/**
* Get all the children (slides) elements.
* @returns {Element[]}
*/
getSlides() {
return Array.from(this.getCarousel().children);
}
/**
* Get a specific slide by index.
* @param {int} index
* @returns {Element|null}
*/
getSlide(index) {
return this.getSlides()[index];
}
/**
* Show a specific slide by index.
* @param {int} index
* @returns {int}
*/
goTo(index) {
const slides = this.getSlides();
const slide = this.getSlide(index);
if (slide) {
slides.forEach((el) => {
el.classList.remove('active');
});
slide.classList.add('active');
this.current = slides.indexOf(slide);
/**
* Trigger goto event.
* @example carousel.addAction('goto', function(slide, index) { ... });
*/
this.doAction('goto', slide, this.current);
}
return this.current;
}
/**
* Show the next slide (if has one).
*/
next() {
let replay = false;
// Check if carousel is looping through slides automatically.
if (this.playing) {
replay = true;
}
const slides = this.getSlides();
let nextIndex = this.current + 1;
// If the next slide is greater than the total, reset to 0 if looping else use -1 to stop `goTo` method.
if (nextIndex > (slides.length - 1)) {
if (this.getSetting('loop')) {
nextIndex = 0;
} else {
nextIndex = -1;
}
}
// Only go to slide if next index is valid.
if (nextIndex >= 0) {
this.goTo(nextIndex);
// Continue with auto play.
if (replay) {
this.play();
}
}
}
/**
* Show the previous slide (if has one).
*/
previous() {
let replay = false;
// Check if carousel is looping through slides automatically.
if (this.playing) {
replay = true;
}
const slides = this.getSlides();
let prevIndex = this.current - 1;
// If the prev slide is less than 0, reset to the last slide if looping else use -1 to stop `goTo` method.
if (prevIndex < 0) {
if (this.getSetting('loop')) {
prevIndex = slides.length - 1;
} else {
prevIndex = -1;
}
}
// Only go to slide if next index is valid.
if (prevIndex >= 0) {
this.goTo(prevIndex);
// Continue with auto play.
if (replay) {
this.play();
}
}
}
/**
* Automatically go to the next slide (or start if loop is true).
* @returns {number}
*/
play() {
this.stop();
this.goTo(this.current);
this.playing = setInterval(() => {
this.next();
}, this.getSetting('delay'));
return this.playing;
}
/**
* Stop the automatic carousel if running.
*/
stop() {
if (this.playing) {
clearInterval(this.playing);
}
}
}
/**
* Get the carousel container element.
* @type {Element}
*/
const carouselContainer = document.querySelector('.carousel-container');
/**
* Create a new controller instance for our carousel.
* @type {CarouselController}
*/
const carousel = new CarouselController({
element: carouselContainer.querySelector('.carousel'),
loop: true,
delay: 3000,
autoplay: true
});
/**
* Lazy load each image only when the slide is in view.
*/
carousel.addAction('goto', function(slide, index) {
let images = [];
if (slide.tagName.toLowerCase() === 'img') {
images.push(slide);
} else {
images.concat(slide.querySelectorAll('img'));
}
images.forEach((img) => {
if (!img.src && img.dataset.src) {
img.src = img.dataset.src;
}
});
});
/**
* Show previous slide (if has one) when clicking previous button.
*/
document.querySelector('.carousel-prev').addEventListener('click', function(event) {
event.preventDefault();
carousel.previous();
});
/**
* Show next slide (if has one) when clicking next button.
*/
document.querySelector('.carousel-next').addEventListener('click', function(event) {
event.preventDefault();
carousel.next();
});
/* Container styles */
.carousel-container {}
/* Hide all carousel slides by default */
.carousel-container .carousel > * {
display: none;
}
/* Only show the active carousel slide */
.carousel-container .carousel > *.active {
display: block;
}
<div class="carousel-container">
<div class="carousel">
<img data-src="https://picsum.photos/100/300" />
<img data-src="https://picsum.photos/200/300" />
<img data-src="https://picsum.photos/300/300" />
<img data-src="https://picsum.photos/400/300" />
<img data-src="https://picsum.photos/500/300" />
</div>
<button class="carousel-prev">
❮
</button>
<button class="carousel-next">
❯
</button>
</div>
我添加了一个挂钩机制,允许在每次幻灯片更改时回调 运行 carousel.addAction('goto', (slide, index) => { ... });
这个问题更具体,是这个问题的超集:How to know if window "load" event was fired already .
用例
所以我有这个用例,我需要制作延迟加载的 CSS-仅图片幻灯片,为此我通过 onload 事件实现了它,该事件随幻灯片编号一起触发,在延迟后触发仅当图片完成加载相关图片时。问题是建议的答案在第一个 onload 事件触发后显示 'complete'
,这不适合我。所以我的问题是 - 如何检测是否所有 onload 事件都已触发?
问题
我所有的幻灯片都放完了,然后放映就出现问题了。这就是为什么我需要检测这种情况,以便我可以在事后控制我的幻灯片行为,从而解决这个问题。
StackBlitz Link
https://stackblitz.com/edit/js-fsymew
GitHub Link
https://github.com/munchkindev/js-fsymew
我的HTML:
<div style="margin-bottom: 30px;">
<img class="slider" src="graphics/slider1.webp" onload="delayedCarousel(1);">
<div class="slider-text" id="slidert1">Text here, you can just ignore this.</div>
<img loading="lazy" class="slider" src="graphics/slider2.webp" onload="delayedCarousel(2);" style="display:none">
<img loading="lazy" class="slider" src="graphics/slider3.webp" onload="delayedCarousel(3);" style="display:none">
<img loading="lazy" class="slider" src="graphics/slider4.webp" onload="delayedCarousel(4);" style="display:none">
<img loading="lazy" class="slider" src="graphics/slider5.webp" onload="delayedCarousel(5);" style="display:none">
<img loading="lazy" class="slider" src="graphics/slider6.webp" onload="delayedCarousel(1);" style="display:none">
<button class="slider-button slider-black slider-display-left" onclick="changeSlide(-1);">❮</button>
<button class="slider-button slider-black slider-display-right" onclick="changeSlide(1);">❯</button>
</div>
JS:
function carousel(n) {
var i;
var x = document.getElementsByClassName("slider");
if (n === 6) { delayedCarousel(1); }
//for (i = 0; i < x.length; i++) {
x[n].style.display = "block";
if (n === 1) x[5].style.display = "none";
//}
slideIndex++;
//if (n > x.length) {/*slideIndex = 1*/
x[n-1].style.display = "none";
//setTimeout(carousel, 5000); // Change image every 5 seconds
}
function delayedCarousel(n) {
setTimeout(function(){
carousel(n);
}, 3000);
}
function changeSlide(n) {
showDivs(slideIndex += n);
}
function showDivs(n) {
var i;
var x = document.getElementsByClassName("slider");
if (n > x.length) { slideIndex = 1 }
if (n < 1) { slideIndex = x.length }
for (i = 0; i < x.length; i++) {
x[i].style.display = "none";
x[i].style.width = "100%";
}
x[slideIndex - 1].style.display = "block";
}
尝试 2
HTML:
<div style="margin-bottom: 30px;">
<img class="slider" src="graphics/slider1.webp" onload="delayedCarousel(1);">
<div class="slider-text" id="slidert1">Ignore this text, it's irrelevant.</div>
<img loading="lazy" class="slider" src="graphics/slider2.webp" onload="delayedCarousel(2);" style="display:none">
<img loading="lazy" class="slider" src="graphics/slider3.webp" onload="delayedCarousel(3);" style="display:none">
<img loading="lazy" class="slider" src="graphics/slider4.webp" onload="delayedCarousel(4);" style="display:none">
<img loading="lazy" class="slider" src="graphics/slider5.webp" onload="delayedCarousel(5);" style="display:none">
<img loading="lazy" class="slider" src="graphics/slider6.webp" onload="loopedCarousel(1);" style="display:none">
<button class="slider-button slider-black slider-display-left" onclick="changeSlide(-1);">❮</button>
<button class="slider-button slider-black slider-display-right" onclick="changeSlide(1);">❯</button>
</div>
JS:
// function startSliderLoop() {
// var i = 1;
// setInterval(function() {
// location.href = "/#slide-"+i;
// //document.addEventListener('click', function (event) {
// // If the clicked element does not have and is not contained by an element with the .click-me class, ignore it
// //if (!event.target.closest('#slideButton'+ i )) return;
// // Otherwise, do something...
// if (i <= 5)
// i++;
// else
// i = 1;
//}, 3000)
// }
// document.addEventListener("DOMContentLoaded", (event) => {
// startSliderLoop();
//});
var slideIndex = 0;
var sliders = document.querySelector('.slider');
//document.querySelector('.slider').forEach(function(img){
// img.addEventListener('load', carousel());
// });
function carousel(n) {
var i;
var x = document.getElementsByClassName("slider");
if (n === 6) { delayedCarousel(1); }
//for (i = 0; i < x.length; i++) {
x[n].style.display = "block";
if (n === 1) x[5].style.display = "none";
//}
slideIndex++;
//if (n > x.length) {/*slideIndex = 1*/
x[n-1].style.display = "none";
//setTimeout(carousel, 5000); // Change image every 5 seconds
}
function loopedCarousel(n) {
var i;
var x = document.getElementsByClassName("slider");
if (n === 5) { n=1; loopedCarousel(1); }
//for (i = 0; i < x.length; i++) {
x[n].style.display = "block";
if (n === 1) x[5].style.display = "none";
//}
slideIndex++;
//if (n > x.length) {/*slideIndex = 1*/
x[n-1].style.display = "none";
setTimeout(loopedCarousel(n+1), 5000); // Change image every 5 seconds
}
function delayedCarousel(n) {
setTimeout(function(){
carousel(n);
}, 3000);
}
<script>
window.onload = function() {
// code for when entire window loads
}
</script>
当 window 加载时,window.onload 是 运行。这应该是在所有元素、脚本和其他内容加载之后。
我会使用 promises 来监听图片加载事件,这样你就可以在启动轮播之前等待所有的 promises 完成。
例如,如果您有以下 html:
<img src="https://picsum.photos/100/300" />
<img src="https://picsum.photos/200/300" />
<img src="https://picsum.photos/300/300" />
<img src="https://picsum.photos/400/300" />
<img src="https://picsum.photos/500/300" />
您可以等待所有这些加载如下:
const promises = [];
document.querySelectorAll('img').forEach((img) => {
const promise = new Promise((resolve, reject) => {
img.addEventListener('load', resolve);
img.addEventListener('error', reject);
});
promises.push(promise);
});
Promise.all(promises).then(() => {
console.log('All images successfully loaded.');
});
我在下面为您创建了工作示例。您会注意到,在加载所有图像之前,轮播将显示红色边框。加载后边框颜色变为绿色。
class CarouselController {
/**
* @param {object} settings
*/
constructor(settings) {
this.carousel = settings.element;
this.current = 0;
if (!this.carousel) {
throw 'A carousel element is required. For example: new CarouselController({ element: document.getElementById(\'carousel\') })';
}
this.settings = {
loop: 'loop' in settings ? settings.loop : true,
delay: 'delay' in settings ? parseInt(settings.delay) : 5000
};
}
/**
* Get the carousel container element.
* @returns {Element}
*/
getCarousel() {
return this.carousel;
}
/**
* Get a setting value.
* @param {string} name
* @param defaultValue
* @returns {*}
*/
getSetting(name, defaultValue) {
return name in this.settings ? this.settings[name] : defaultValue;
}
/**
* Get all the children (slides) elements.
* @returns {Element[]}
*/
getSlides() {
return Array.from(this.getCarousel().children);
}
/**
* Get a specific slide by index.
* @param {int} index
* @returns {Element|null}
*/
getSlide(index) {
return this.getSlides()[index];
}
/**
* Show a specific slide by index.
* @param {int} index
* @returns {int}
*/
goTo(index) {
const slides = this.getSlides();
const slide = this.getSlide(index);
if (slide) {
slides.forEach((el) => {
el.classList.remove('active');
});
slide.classList.add('active');
this.current = slides.indexOf(slide);
}
return this.current;
}
/**
* Show the next slide (if has one).
*/
next() {
let replay = false;
// Check if carousel is looping through slides automatically.
if (this.playing) {
replay = true;
}
const slides = this.getSlides();
let nextIndex = this.current + 1;
// If the next slide is greater than the total, reset to 0 if looping else use -1 to stop `goTo` method.
if (nextIndex > (slides.length - 1)) {
if (this.getSetting('loop')) {
nextIndex = 0;
} else {
nextIndex = -1;
}
}
// Only go to slide if next index is valid.
if (nextIndex >= 0) {
this.goTo(nextIndex);
// Continue with auto play.
if (replay) {
this.play();
}
}
}
/**
* Show the previous slide (if has one).
*/
previous() {
let replay = false;
// Check if carousel is looping through slides automatically.
if (this.playing) {
replay = true;
}
const slides = this.getSlides();
let prevIndex = this.current - 1;
// If the prev slide is less than 0, reset to the last slide if looping else use -1 to stop `goTo` method.
if (prevIndex < 0) {
if (this.getSetting('loop')) {
prevIndex = slides.length - 1;
} else {
prevIndex = -1;
}
}
// Only go to slide if next index is valid.
if (prevIndex >= 0) {
this.goTo(prevIndex);
// Continue with auto play.
if (replay) {
this.play();
}
}
}
/**
* Automatically go to the next slide (or start if loop is true).
* @returns {number}
*/
play() {
this.stop();
this.goTo(this.current);
this.playing = setInterval(() => {
this.next();
}, this.getSetting('delay'));
return this.playing;
}
/**
* Stop the automatic carousel if running.
*/
stop() {
if (this.playing) {
clearInterval(this.playing);
}
}
}
/**
* Get the carousel container element.
* @type {Element}
*/
const carouselContainer = document.querySelector('.carousel-container');
/**
* Create a new controller instance for our carousel.
* @type {CarouselController}
*/
const carousel = new CarouselController({
element: carouselContainer.querySelector('.carousel'),
loop: true,
delay: 3000
});
/**
* Build an array of image load promises.
* @type {Promise[]}
*/
const imagePromises = [];
carousel.getCarousel().querySelectorAll('img').forEach((el) => {
const promise = new Promise((resolve, reject) => {
const image = new Image();
image.addEventListener('load', resolve);
image.addEventListener('error', reject);
image.src = el.src;
});
imagePromises.push(promise);
});
/**
* Wait for all image promises to complete (even if failed) and initiate the carousel.
* @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled
*/
Promise.allSettled(imagePromises).then(() => {
carouselContainer.classList.add('loaded');
/**
* Start auto playing with settings defined in the constructor.
*/
carousel.play();
/**
* Show previous slide (if has one) when clicking previous button.
*/
document.querySelector('.carousel-prev').addEventListener('click', function(event) {
event.preventDefault();
carousel.previous();
});
/**
* Show next slide (if has one) when clicking next button.
*/
document.querySelector('.carousel-next').addEventListener('click', function(event) {
event.preventDefault();
carousel.next();
});
});
/* Container styles BEFORE carousel has loaded */
.carousel-container {
opacity: 0.3;
pointer-events: none;
border: 2px solid red;
}
/* Container styles AFTER carousel has loaded */
.carousel-container.loaded {
opacity: 1;
pointer-events: auto;
border-color: green;
}
/* Hide all carousel slides by default */
.carousel-container .carousel > * {
display: none;
}
/* Only show the active carousel slide */
.carousel-container .carousel > *.active {
display: block;
}
<div class="carousel-container">
<div class="carousel">
<img src="https://picsum.photos/100/300" />
<img src="https://picsum.photos/200/300" />
<img src="https://picsum.photos/300/300" />
<img src="https://picsum.photos/400/300" />
<img src="https://picsum.photos/500/300" />
</div>
<button class="carousel-prev">
❮
</button>
<button class="carousel-next">
❯
</button>
</div>
或者您可以在 JSFiddle 上查看它:https://jsfiddle.net/thelevicole/b13gpyfd/2/
编辑
下面是更新的示例,说明如何仅在幻灯片处于视图中时才延迟加载幻灯片图像,而不是等待所有图像在初始化时加载。
https://jsfiddle.net/thelevicole/b13gpyfd/3/
class CarouselController {
defaultSettings = {
loop: true,
delay: 5000,
autoplay: true
}
/**
* @param {object} settings
*/
constructor(settings) {
this.carousel = settings.element;
delete settings.element;
this.current = 0;
this.hooks = {};
this.settings = settings;
if (!this.carousel) {
throw 'A carousel element is required. For example: new CarouselController({ element: document.getElementById(\'carousel\') })';
}
/**
* Sanitize `loop` setting
*/
this.addFilter('setting.loop', value => {
return String(value).toLowerCase() === 'true';
});
/**
* Sanitize `delay` setting
*/
this.addFilter('setting.delay', value => parseInt(value));
/**
* Sanitize `autoplay` setting
*/
this.addFilter('setting.autoplay', value => {
return String(value).toLowerCase() === 'true';
});
// Autoplay on init.
if (this.getSetting('autoplay')) {
this.play();
}
}
/**
* Get the carousel container element.
* @returns {Element}
*/
getCarousel() {
return this.carousel;
}
/**
* Get a setting value.
* @param {string} name
* @param defaultValue
* @returns {*}
*/
getSetting(name, defaultValue) {
if (!defaultValue && name in this.defaultSettings) {
defaultValue = this.defaultSettings[name]
}
/**
* Apply value filters.
* @example carousel.addFilter('setting.delay', function(value) { return value + 500; });
*/
return this.applyFilters(`setting.${name}`, name in this.settings ? this.settings[name] : defaultValue);
}
/**
* Get hooks by type and name. Ordered by priority.
* @param {string} type
* @param {string} name
* @returns {array}
*/
getHooks(type, name) {
let hooks = [];
if (type in this.hooks) {
let localHooks = this.hooks[type];
localHooks = localHooks.filter(el => el.name === name);
localHooks = localHooks.sort((a, b) => a.priority - b.priority);
hooks = hooks.concat(localHooks);
}
return hooks;
}
/**
* Add a hook.
* @param {string} type
* @param {object} hookMeta
*/
addHook(type, hookMeta) {
// Create new local hook type array.
if (!(type in this.hooks)) {
this.hooks[type] = [];
}
this.hooks[type].push(hookMeta);
}
/**
* Add action listener.
* @param {string} action Name of action to trigger callback on.
* @param {function} callback
* @param {number} priority
*/
addAction(action, callback, priority = 10) {
this.addHook('actions', {
name: action,
callback: callback,
priority: priority
});
}
/**
* Trigger an action.
* @param {string} name Name of action to run.
* @param {*} args Arguments passed to the callback function.
*/
doAction(name, ...args) {
this.getHooks('actions', name).forEach(hook => {
hook.callback(...args);
});
}
/**
* Register filter.
* @param {string} filter Name of filter to trigger callback on.
* @param {function} callback
* @param {number} priority
*/
addFilter(filter, callback, priority = 10) {
this.addHook('filters', {
name: filter,
callback: callback,
priority: priority
});
}
/**
* Apply all named filters to a value.
* @param {string} name Name of action to run.
* @param {*} value The value to be mutated.
* @param {*} args Arguments passed to the callback function.
* @returns {*}
*/
applyFilters(name, value, ...args) {
this.getHooks('filters', name).forEach(hook => {
value = hook.callback(value, ...args);
});
return value;
}
/**
* Get all the children (slides) elements.
* @returns {Element[]}
*/
getSlides() {
return Array.from(this.getCarousel().children);
}
/**
* Get a specific slide by index.
* @param {int} index
* @returns {Element|null}
*/
getSlide(index) {
return this.getSlides()[index];
}
/**
* Show a specific slide by index.
* @param {int} index
* @returns {int}
*/
goTo(index) {
const slides = this.getSlides();
const slide = this.getSlide(index);
if (slide) {
slides.forEach((el) => {
el.classList.remove('active');
});
slide.classList.add('active');
this.current = slides.indexOf(slide);
/**
* Trigger goto event.
* @example carousel.addAction('goto', function(slide, index) { ... });
*/
this.doAction('goto', slide, this.current);
}
return this.current;
}
/**
* Show the next slide (if has one).
*/
next() {
let replay = false;
// Check if carousel is looping through slides automatically.
if (this.playing) {
replay = true;
}
const slides = this.getSlides();
let nextIndex = this.current + 1;
// If the next slide is greater than the total, reset to 0 if looping else use -1 to stop `goTo` method.
if (nextIndex > (slides.length - 1)) {
if (this.getSetting('loop')) {
nextIndex = 0;
} else {
nextIndex = -1;
}
}
// Only go to slide if next index is valid.
if (nextIndex >= 0) {
this.goTo(nextIndex);
// Continue with auto play.
if (replay) {
this.play();
}
}
}
/**
* Show the previous slide (if has one).
*/
previous() {
let replay = false;
// Check if carousel is looping through slides automatically.
if (this.playing) {
replay = true;
}
const slides = this.getSlides();
let prevIndex = this.current - 1;
// If the prev slide is less than 0, reset to the last slide if looping else use -1 to stop `goTo` method.
if (prevIndex < 0) {
if (this.getSetting('loop')) {
prevIndex = slides.length - 1;
} else {
prevIndex = -1;
}
}
// Only go to slide if next index is valid.
if (prevIndex >= 0) {
this.goTo(prevIndex);
// Continue with auto play.
if (replay) {
this.play();
}
}
}
/**
* Automatically go to the next slide (or start if loop is true).
* @returns {number}
*/
play() {
this.stop();
this.goTo(this.current);
this.playing = setInterval(() => {
this.next();
}, this.getSetting('delay'));
return this.playing;
}
/**
* Stop the automatic carousel if running.
*/
stop() {
if (this.playing) {
clearInterval(this.playing);
}
}
}
/**
* Get the carousel container element.
* @type {Element}
*/
const carouselContainer = document.querySelector('.carousel-container');
/**
* Create a new controller instance for our carousel.
* @type {CarouselController}
*/
const carousel = new CarouselController({
element: carouselContainer.querySelector('.carousel'),
loop: true,
delay: 3000,
autoplay: true
});
/**
* Lazy load each image only when the slide is in view.
*/
carousel.addAction('goto', function(slide, index) {
let images = [];
if (slide.tagName.toLowerCase() === 'img') {
images.push(slide);
} else {
images.concat(slide.querySelectorAll('img'));
}
images.forEach((img) => {
if (!img.src && img.dataset.src) {
img.src = img.dataset.src;
}
});
});
/**
* Show previous slide (if has one) when clicking previous button.
*/
document.querySelector('.carousel-prev').addEventListener('click', function(event) {
event.preventDefault();
carousel.previous();
});
/**
* Show next slide (if has one) when clicking next button.
*/
document.querySelector('.carousel-next').addEventListener('click', function(event) {
event.preventDefault();
carousel.next();
});
/* Container styles */
.carousel-container {}
/* Hide all carousel slides by default */
.carousel-container .carousel > * {
display: none;
}
/* Only show the active carousel slide */
.carousel-container .carousel > *.active {
display: block;
}
<div class="carousel-container">
<div class="carousel">
<img data-src="https://picsum.photos/100/300" />
<img data-src="https://picsum.photos/200/300" />
<img data-src="https://picsum.photos/300/300" />
<img data-src="https://picsum.photos/400/300" />
<img data-src="https://picsum.photos/500/300" />
</div>
<button class="carousel-prev">
❮
</button>
<button class="carousel-next">
❯
</button>
</div>
我添加了一个挂钩机制,允许在每次幻灯片更改时回调 运行 carousel.addAction('goto', (slide, index) => { ... });