(This is in continuation of this discussion)
I’ve been trying to make a script (for instagram) that shows how many images out of total are currently visible when viewing a profile page (example profile). It shows the counter in the upper right corner of the screen and supports infinite scrolling.
Here it is:
// ==UserScript== // @name Instagram - visible images counter // @name Instagram - visible images counter // @include https://instagram.com/* // @grant none // ==/UserScript== var p = document.getElementsByClassName('-cx-PRIVATE-PostsGridItem__root'); var m = document.getElementsByClassName('-cx-PRIVATE-PostsStatistic__count'); var ww = m[0].innerHTML.replace(/(d+),(?=d{3}(D|$))/g, "$1"); // Regex to strip the thousand comma seperator from the posts number var z = (p.length/ww)*100; var counter = p.length+' / '+m[0].innerHTML +' that is ' +z.toFixed(1) + '%'; var div = document.createElement("div"); div.style.top = "1px"; div.style.right = "1px"; div.style.position = "fixed"; document.body.appendChild(div); div.id="mycounter"; mycounter = document.getElementById('mycounter'); mycounter.innerHTML = counter; mycounter.style.top = "1px"; mycounter.style.right = "1px"; mycounter.style.position = "fixed"; /// --------------------------------- /// mutation observer -monitors the Posts grid for infinite scrolling event- /// --------------------------------- var target1 = document.querySelector('.-cx-PRIVATE-PostsGrid__root'); var observer1 = new MutationObserver(function (mutations) { mutations.forEach(function (mutation) { p=document.getElementsByClassName('-cx-PRIVATE-PostsGridItem__root'); m = document.getElementsByClassName('-cx-PRIVATE-PostsStatistic__count'); ww = m[0].innerHTML.replace(/(d+),(?=d{3}(D|$))/g, "$1"); z = (p.length/ww)*100; counter = p.length+' / '+m[0].innerHTML +' that is ' +z.toFixed(1) + '%'; mycounter.innerHTML = counter; }); }) var config = { attributes: true, childList: true, characterData: true } observer1.observe(target1, config);
My script works ok, but I have this issue:
Instagram, after it’s recent redesign -I think-, seems to use single-page application workflow,
i.e fetches the clicked link content and replaces the current page with it, and then changes the browser’s current URL, all without actually reloading the page.
So, my script only works when I open a profile URL in a new tab.
It doesn’t work when opening a profile URL in the same tab while in my timeline (https://instagram.com)).
In other words, it doesn’t work (after I open https://instagram.com and login)
if I click to view a profile URL of a user I follow, eg. https://instagram.com/instagram/
How can I fix this?
Someone has kindly suggested these 3 ways:
- Try an
event handler
for some event fired by the Instagram site like pjax:end on github, for example.- Use a
mutation observer
that waits a profile-specific html element.- Or use
setInterval
to check location.href periodically.*
So, I’ve been trying various approaches (including the suggestions #2 and #3) but no success.
(about suggestion #1: I can’t find any element similar to pjax:end
).
So, what I’ve tried so far:
(based on suggestion #2) adding another
mutation observer
to check whether the element that shows the ‘posts count’ element exists, and if yes, then run my code.var target0 = document.querySelector('.-cx-PRIVATE-PostsStatistic__count'); var observer0 = new MutationObserver(function (mutations) { mutations.forEach(function (mutation) { myCode(); }); }) var config0 = { attributes: true, childList: true, characterData: true } observer0.observe(target0, config0);
(based on suggestion #3) checking
location.href
every 1 second whether the current location is a profile (i.e. not the timeline (https://instagram.com/). If true then clear the periodic function and run my previous code.var interval = setInterval(testHref() , 1000); function testHref(){ if (location.href != "https://instagram.com/") clearInterval(interval); myCode(); }
simply adding a 1 sec delay on top of my code (and changing the @require rule to apply only on profile URLs
// @include https://instagram.com/*/
), but no success:setTimeout(function() { }, 1000);
I’ve even tried using the waitForKeyElements utility function, which detects and handles AJAXed content.
I thought it’s much easier to implement this way, working as a simple “wait until an element exists” (I just used the main profile pic as the selector to wait for, because I couldn’t find any relevant AJAX selector. Also I didn’t use jNode inside my code).
So I just enclosed my whole code in a single functionvisibleCounter
, and added a waitForKeyElements line (see below), but unfortunately it also doesn’t work:waitForKeyElements (".-cx-PRIVATE-ProfilePage__avatar", visibleCounter); function visibleCounter(jNode){ myCode() }
Advertisement
Answer
I solved this using the arrive.js library. It provides events to watch for DOM elements creation and removal. It makes use of Mutation Observers internally. The library does not depend on jQuery, you can replace jQuery elements in the examples below with pure javascript elements and it would work fine.
I quote this comment by its author:
I’ve always found MutationObserver api a bit complex so I’ve built a library, arrive.js, to provide a simpler api to listen for elements creation/removal.
as well as just two of its uses:
Watch for elements creation:
Use arrive event to watch for elements creation:
// watch for creation of an element which satisfies the selector ".test-elem" $(document).arrive(".test-elem", function() { // 'this' refers to the newly created element var $newElem = $(this); });
and
Watch for elements removal
Use leave event to watch for elements removal. The first arugument to leave must not be a descendent or child selector i.e. you cannot pass .page .test-elem, instead, pass .test-elem. It’s because of a limitation in MutationObserver’s api.
// watch for removal of an element which satisfies the selector ".test-elem" $(".container-1").leave(".test-elem", function() { var $removedElem = $(this); }); [1]: https://github.com/uzairfarooq/arrive
And, this is the complete script:
// ==UserScript== // @name Instagram - visible images counter // @include https://www.instagram.com/* // @grant none // @require https://code.jquery.com/jquery-3.0.0.min.js // @require https://greasyfork.org/scripts/21927-arrive-js/code/arrivejs.js?version=139586 // ==/UserScript== function showCounter() { var visibleCount = $( "a[href*='taken-by']" ).length; // Count of visible images var totalString = $("span:contains('posts')").eq(1).children().eq(0).html(); // The 'total' value (it's a string) var total = totalString.replace(/(d+),(?=d{3}(D|$))/g, '$1'); // Apply regex to 'total' string to strip the thousand comma seperator if (visibleCount > total){ visibleCount = total; } var visiblePercent = ((visibleCount / total) * 100).toFixed(1); // Visible images as percentage var counter = visibleCount + ' / ' + totalString + ' that is ' + visiblePercent + '%'; return counter; } function createDiv(){ // Creation of the counter element document.body.appendChild(div); div.innerHTML = showCounter(); // Initial display of the counter div.style.top = '1px'; div.style.right = '1px'; div.style.position = 'fixed'; } function observer(){ /// --------------------------------- /// mutation observer -monitors the Posts grid for infinite scrolling event-. /// --------------------------------- observer1 = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { div.innerHTML = showCounter(); // In each infinite scrolling event, re-calculate counter }); }).observe($('article').children().eq(1).children()[0], // target of the observer { // attributes: true, childList: true, // characterData: true, }); // config of the observer } var div = document.createElement('div'); // global variable var observer1; // global variable if (document.URL !== 'https://www.instagram.com/' && document.URL.indexOf('https://www.instagram.com/p/') === -1 ){ createDiv(); observer(); } $(document).arrive('article ._5axto', function() { // 'article .5axto' createDiv(); observer(); }); $(document).leave('article ._5axto', function() { div.remove(); observer1.disconnect(); });