如何知道是否所有 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);">&#10094;</button>
<button class="slider-button slider-black slider-display-right" onclick="changeSlide(1);">&#10095;</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);">&#10094;</button>
<button class="slider-button slider-black slider-display-right" onclick="changeSlide(1);">&#10095;</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">
    &#10094;
  </button>
  <button class="carousel-next">
    &#10095;
  </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">
    &#10094;
  </button>
  <button class="carousel-next">
    &#10095;
  </button>
</div>

我添加了一个挂钩机制,允许在每次幻灯片更改时回调 运行 carousel.addAction('goto', (slide, index) => { ... });