JavaScript - overwriting .onload prototype of HTMLImageElement(s)

前端 未结 4 2017
花落未央
花落未央 2020-12-21 17:50

Is it possible to bind an onload event to each image, declaring it once? I tried, but can\'t manage to get it working... (this error is thrown: Uncaught

相关标签:
4条回答
  • 2020-12-21 18:22

    You can't set a handler on the prototype, no.

    In fact, I'm not aware of any way to get a proactive notification for image load if you haven't hooked load on the specific image element, since load doesn't bubble.

    I only two know two ways to implement a general "some image somewhere has loaded" mechanism:

    1. Use a timer loop, which is obviously unsatisfying on multiple levels. But it does function. The actual query (document.getElementsByTagName("img")) isn't that bad as it returns a reference to the continually updated (live) HTMLCollection of img elements, rather than creating a snapshot like querySelectorAll does. Then you can use Array.prototype methods on it (directly, to avoid creating an intermediary array, if you like).

    2. Use a mutation observer to watch for new img elements being added or the src attribute on existing img elements changing, then hook up a load handler if their complete property isn't true. (You have to be careful with race conditions there; the property can be changed by the browser even while your JavaScript code is running, because your JavaScript code is running on a single UI thread, but the browser is multi-threaded.)

    0 讨论(0)
  • 2020-12-21 18:23

    I've written something similar some time ago to check if an image is loaded or not, and if not, show a default image. You can use the same approach.

    $(document).ready(function() {
        // loop every image in the page
        $("img").each(function() {
            // naturalWidth is the actual width of the image
            // if 0, img is not loaded
            // or the loaded img's width is 0. if so, do further check
            if (this.naturalWidth === 0) { // not loaded
                this.dataset.src = this.src; // keep the original src
                this.src = "image404.jpg";
            } else {
                // loaded
            }
        });
    });
    
    0 讨论(0)
  • 2020-12-21 18:24

    You get that error because onload is an accessor property defined in HTMLElement.prototype.

    You are supposed to call the accessor only on HTML elements, but you are calling the setter on HTMLImageElement.prototype, which is not an HTML element.

    If you want to define that function, use defineProperty instead.

    Object.defineProperty(HTMLImageElement.prototype, 'onload', {
      configurable: true,
      enumerable: true,
      value: function () {
        console.log(this, "loaded");
      }
    });
    var img = new Image();
    img.onload();

    Warning: Messing with builtin prototypes is bad practice.

    However, that only defines a function. The function won't be magically called when the image is loaded, even if the function is named onload.

    That's because even listeners are internal things. It's not that, when an image is loaded, the browser calls the onload method. Instead, when you set the onload method, that function is internally stored as an event listener, and when the image is loaded the browser runs the load event listeners.

    Instead, the proper way would be using Web Components to create a custom element:

    var proto = Object.create(HTMLElement.prototype);
    proto.createdCallback = function() {
      var img = document.createElement('img');
      img.src = this.getAttribute('src');
      img.addEventListener('load', function() {
        console.log('loaded');
      });
      this.appendChild(img);  
    };
    document.registerElement('my-img', {prototype: proto});
    <my-img src="/favicon.ico"></my-img>

    There is not much browser support yet, though.

    0 讨论(0)
  • 2020-12-21 18:42

    This provides a notification for any image loading, at least in Opera (Presto) and Firefox (haven't tried any other browser). The script tag is placed in the HEAD element so it is executed and the event listener installed before any of the body content is loaded.

    document.addEventListener('load', function(e) {
        if ((!e.target.tagName) || (e.target.tagName.toLowerCase() != 'img')) return;
        // do stuff here
    }, true);
    

    Of course, by changing the filtering on tagName it will also serve to respond to the loading of any other element that fires a load event, such as a script tag.

    0 讨论(0)
提交回复
热议问题