javascript - How to make my userscript work on specific pages of a site that uses the history api? -
(this in continuation of this discussion)
i've been trying make script (for instagram) shows how many images out of total visible when viewing profile page (example profile). shows counter in upper right corner of screen , supports infinite scrolling.
here 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 strip thousand comma seperator posts number var z = (p.length/ww)*100; var counter = p.length+' / '+m[0].innerhtml +' ' +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 posts grid 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 +' ' +z.tofixed(1) + '%'; mycounter.innerhtml = counter; }); }) var config = { attributes: true, childlist: true, characterdata: true } observer1.observe(target1, config);
my script works ok, have issue:
instagram, after it's recent redesign -i think-, seems use single-page application workflow,
i.e fetches clicked link content , replaces current page it, , changes browser's current url, all without reloading page.
so, script works when open profile url in new tab.
it doesn't work when opening profile url in same tab while in timeline (https://instagram.com)).
in other words, doesn't work (after open https://instagram.com , login)
if click view profile url of user follow, eg. https://instagram.com/instagram/
how can fix this?
someone has kindly suggested these 3 ways:
- try
event handler
event fired instagram site pjax:end on github, example.- use
mutation observer
waits profile-specific html element.- or use
setinterval
check location.href periodically.*
so, i've been trying various approaches (including suggestions #2 , #3) no success.
(about suggestion #1: can't find element similar pjax:end
).
so, what i've tried far:
(based on suggestion #2) adding
mutation observer
check whether element shows 'posts count' element exists, , if yes, run 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 current location profile (i.e. not timeline (https://instagram.com/). if true clear periodic function , run previous code.var interval = setinterval(testhref() , 1000); function testhref(){ if (location.href != "https://instagram.com/") clearinterval(interval); mycode(); }
simply adding 1 sec delay on top of code (and changing @require rule apply on profile urls
// @include https://instagram.com/*/
), no success:settimeout(function() { }, 1000);
i've tried using waitforkeyelements utility function, detects , handles ajaxed content.
thought it's easier implement way, working simple "wait until element exists" (i used main profile pic selector wait for, because couldn't find relevant ajax selector. didn't use jnode inside code).
enclosed whole code in single functionvisiblecounter
, , added waitforkeyelements line (see below), unfortunately doesn't work:waitforkeyelements (".-cx-private-profilepage__avatar", visiblecounter); function visiblecounter(jnode){ mycode() }
i solved using arrive.js library. provides events watch dom elements creation , removal. makes use of mutation observers internally. library not depend on jquery, can replace jquery elements in examples below pure javascript elements , work fine.
i quote comment author:
i've found mutationobserver api bit complex i've built library, arrive.js, provide simpler api listen elements creation/removal.
as 2 of uses:
watch elements creation:
use arrive event watch elements creation:
// watch creation of element satisfies selector ".test-elem" $(document).arrive(".test-elem", function() { // 'this' refers newly created element var $newelem = $(this); });
and
watch elements removal
use leave event watch elements removal. first arugument leave must not descendent or child selector i.e. cannot pass .page .test-elem, instead, pass .test-elem. it's because of limitation in mutationobserver's api.
// watch removal of element satisfies selector ".test-elem" $(".container-1").leave(".test-elem", function() { var $removedelem = $(this); }); [1]: https://github.com/uzairfarooq/arrive
and, 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(); // 'total' value (it's string) var total = totalstring.replace(/(\d+),(?=\d{3}(\d|$))/g, '$1'); // apply regex 'total' string strip thousand comma seperator if (visiblecount > total){ visiblecount = total; } var visiblepercent = ((visiblecount / total) * 100).tofixed(1); // visible images percentage var counter = visiblecount + ' / ' + totalstring + ' ' + visiblepercent + '%'; return counter; } function creatediv(){ // creation of counter element document.body.appendchild(div); div.innerhtml = showcounter(); // initial display of counter div.style.top = '1px'; div.style.right = '1px'; div.style.position = 'fixed'; } function observer(){ /// --------------------------------- /// mutation observer -monitors posts grid 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 observer { // attributes: true, childlist: true, // characterdata: true, }); // config of 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(); });
Comments
Post a Comment