2

I'm working on a userscript that does various things with images.

The problem is, sometimes images are added to the page AFTER the initial load, and these images are not processed by my userscript.

I need some code in javascript/jQuery which will automatically run a function every time a new image is loaded into the browser.

This is what I have tried:

$('img').on('load', function() {
    handleImgTag();
});

This does not work. When a new image is added to the page (I'm using chat.stackoverflow.com as a test, and sending a new message meaning my profile picture is added to the page again which is not processed by my code) the image is not processed and nothing happens to it.

I've tried searching around but I cannot seem to find a way to do this, perhaps because of bad search terms?

Is there an event or something that I can use in a Tampermonkey userscript which will run a function when a new image is added to the page the user is on?

EDIT:

I disagree with the duplicates mentioned because this is specifically about images, AND use inside a Tampermonkey userscript. Also, the duplicates are 5-8 years old and may not reflect best practices or current technologies.

Brock Adams
  • 90,639
  • 22
  • 233
  • 295
GrumpyCrouton
  • 8,486
  • 7
  • 32
  • 71
  • Possible duplicate of (https://stackoverflow.com/questions/3219758/detect-changes-in-the-dom) – Ryan Wilson May 01 '18 at 16:57
  • Possible duplicate of [Detect changes in the DOM](https://stackoverflow.com/questions/3219758/detect-changes-in-the-dom) – Asons May 01 '18 at 16:58

2 Answers2

1

$('img').on('load',... won't work because it spams anonymous event handlers onto all existing images. They never get attached to future, AJAX'd/Dynamically inserted images.

The correct form would be: $('body').on ('load', 'img', handleImgTag);, EXCEPT load events do not bubble.

So, you must use either: the techniques (mainly MutationObserver) from the linked question, or something like waitForKeyElements().
And, you must attach the load event dynamically (for slow loading images).

The following complete working script illustrates the process. Note that waitForKeyElements will fire once for each <img> whether it is loaded statically or dynamically. :

// ==UserScript==
// @name     _Process select images on or after load
// @match    *://chat.stackoverflow.com/rooms/*
// @match    *://chat.stackexchange.com/rooms/*
// @match    *://chat.meta.stackexchange.com/rooms/*
// @require  https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js
// @require  https://gist.github.com/raw/2625891/waitForKeyElements.js
// @grant    GM_addStyle
// @grant    GM.getValue
// ==/UserScript==
//- The @grant directives are needed to restore the proper sandbox.

waitForKeyElements ("#chat img", loadHandling);

function loadHandling (jNode) {
    if (jNode[0].complete) {
        handleImgTag (jNode);
    }
    else {
        jNode.on ('load', handleImgTag);
    }
}
function handleImgTag (jNodeOrEvent) {
    var newImg;  //  will be jQuery node
    if (jNodeOrEvent instanceof jQuery) {
        newImg = jNodeOrEvent;
    }
    else {
        newImg = $(jNodeOrEvent.target);
    }
    console.log ("Found new image with width ", newImg.width () );
}

Note: recommend tuning the jQuery selector, passed to waitForKeyElements, to narrow the target images as much as reasonable.

Brock Adams
  • 90,639
  • 22
  • 233
  • 295
  • I ended up using this in my project - [Imgur.com blocked, what are my options?](//meta.stackoverflow.com/a/367073) - thank you! – GrumpyCrouton May 02 '18 at 12:59
-1

In a browser scope, a globalEventHandler or eventListeners would be the way to go.

But because userscripts have some differents scopes for window and document, it does not work as it is. This scoping problem is the same using web extensions (next gen userscipts..), but at least it force us to much take care of the DOM.

Here is some workarounds for userscripts, following own experiments. Usually my experiments leads to downvotes by incurious «programmers», so no worries and have fun!

First instead of load events, inside the usercript, loading scripts with a delay. It is dirty but help a lot, at least for debugging.

setTimeout(function(){    }, 3000)

To load event handlers into the main document, we got to create an element and append it to the main DOM.

In most cases, issues comes just because browsers loads local resources much faster, we must specify where the script should be on the target page, or it may be positioned at the very top of the DOM. (Meaning no event handler, we can't call id by their name, we would need to use getElementById, etc,etc)

s = document.createElement("script")
s.innerText = "" // The events scripts
document.body.appendChild(s)

The good point here, the script can be external. By loading external scripts, browsers will take care to append it to the DOM, actually if the DOM is well loaded. in most cases stuffs start to works with just this action.

An other alternative is to directly set a onchange attribute on each element of the target DOM.

This is valid, if the elements already exists. (If you are loading your images by changing the src). You can as well set many (hidden) element as need for later and set their attribute. This is a bit redundant, but avoid any event listeners. It is flat approach. It can be done server side too.

To set an onchange on all images tags, without knowing their id, we can use querySelectorAll, which is on this case very useful because we can use forEach on it. (Unlike getElementsBytagName)

document.querySelectorAll("img").forEach(function(element){
  element.setAttribute("onchange","handleImgTag()")
})

Now all together:

setTimeout(function(){
  // the querySelectorAll function encoded in base64
  var all = "ZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgnaW1nJykuZm9yRWFjaChmdW5jdGlvbihlbGVtZW50KXsNCiAgZWxlbWVudC5zZXRBdHRyaWJ1dGUoJ29ubG9hZCcsJ2FsZXJ0KCknKQ0KfSk=" 
  s = document.createElement("script")
  // The events scripts, base64 decoded
  s.innerText = atob(all) + "" 
  document.body.appendChild(s)  // End of DOM, at trigger.

}, 1000) // Onloads sets after 1s.
<img src="http://icons.iconarchive.com/icons/martin-berube/flat-animal/64/pony-icon.png"></img>

<img src="" id="more"></img>

<button onclick="more.src='http://icons.iconarchive.com/icons/martin-berube/flat-animal/64/crab-icon.png'">LOAD MORE</button>

Scoping is the key for good userscripts.

Try to put all your script at the DOM end. Good luck!

NVRM
  • 11,480
  • 1
  • 88
  • 87
  • I appreciate the answer but it was a bit hard to follow, I also don't really understand how it is supposed to detect new images being loaded into the DOM – GrumpyCrouton May 02 '18 at 13:04
  • Use your debugger, see the image tags. they both have a onload attribute, set after one second. Coming from the encoded function, appended to the DOM. Come on! – NVRM May 02 '18 at 13:08
  • But that only works if I know information about the image right? – GrumpyCrouton May 02 '18 at 13:11
  • No, on this case, just need the element itself. Nothing else. i don't how your images are set, on this case, as soon as any element `img` change, it trigger a function that's all. this is exactly the same as event listeners.. You can set eventListeners on the exact same way, as soon as they are appended at the end of the document. Code is executed only when base64 decoded. You can as well set the src of the script instead of the text. – NVRM May 02 '18 at 13:19
  • Relevant: https://stackoverflow.com/questions/6432984/adding-script-element-to-the-dom-and-have-the-javascript-run – NVRM May 02 '18 at 13:25
  • This approach does not work for fully dynamic pages, nor for the chat scenario the OP outlined. Also, the code snippet throws an error. And needlessly injecting scripts is a poor practice/recommendation. – Brock Adams May 02 '18 at 17:48
  • 1/ basically you didn't read 2/ It perfectly resolve the OP problem. 3/ There is no error in a document context, no errors in Firefox, there is just no document body, forEach is somewhat too advanced for your browser. 4/ You didn't read at all, just need to wait 1s before clicking, this is a demo for peoples that already knows javascript, not for full noob babies. – NVRM May 02 '18 at 22:53
  • 5/ You are talking about script injection and you use 200kb jquery to parse a div.. Let me tell you something: your knowledge is out. Show me something about «needlessly injecting scripts is a poor practice/recommendation.» that is not coming from the prehistoric ages. And after i will let you open the javascript console on big websites like twitter, youtube.. And then back again, let's talk about bad practice one time more. your downvote is out. This is not a «I am out» button... – NVRM May 02 '18 at 22:53