如何使我的用户脚本在使用历史记录 api 的站点的特定页面上工作?

How to make my userscript work on specific pages of a site that uses the history api?

(这是 this discussion 的延续)

我一直在尝试制作一个脚本(用于 instagram),显示在查看个人资料页面时当前可见的图像总数中有多少 (example profile)。它在屏幕右上角显示计数器并支持无限滚动。

这里是:

// ==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, "");                       // 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, "");
    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);

我的脚本运行正常,但我遇到了这个问题:

Instagram,在最近重新设计之后 - 我认为 - 似乎使用 单页应用程序工作流程
即获取点击的 link 内容并用它替换当前页面,然后更改浏览器的当前 URL, 而无需实际重新加载页面.

因此,我的脚本仅在我打开配置文件时有效 URL 在新选项卡中
打开配置文件时不起作用URL在同一选项卡中而在我的时间轴中(https://instagram.com)) .
换句话说,它不起作用(在我打开 https://instagram.com 并登录后)
如果我点击查看我关注的用户的个人资料 URL,例如。 https://instagram.com/instagram/

我该如何解决?

Someone 亲切地建议了这 3 种方式:

  1. Try an event handler for some event fired by the Instagram site like pjax:end on github, for example.
  2. Use a mutation observer that waits a profile-specific html element.
  3. Or use setInterval to check location.href periodically.*


所以,我一直在尝试各种方法(包括建议 #2 和 #3)但没有成功。
(关于建议 #1:我找不到任何类似于 pjax:end 的元素)。
所以,到目前为止我已经尝试过:


  1. (根据建议#2)添加另一个mutation observer检查显示'posts count'元素的元素是否存在,如果存在,则运行我的代码。

    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);
    

  1. (基于建议 #3)每 1 秒检查一次 location.href 当前位置是否是配置文件(即不是时间线(https://instagram.com/)。如果为真,则清除周期函数和 运行 我以前的代码。

    var interval = setInterval(testHref() , 1000);
    
    function testHref(){
        if (location.href != "https://instagram.com/")
            clearInterval(interval);
            myCode();   
    }
    

  1. 只是在我的代码之上添加了 1 秒的延迟(并将 @require 规则更改为仅应用于配置文件 URLs // @include https://instagram.com/*/),但没有成功:

    setTimeout(function() { }, 1000);
    

  1. 我什至尝试过使用 waitForKeyElements 实用程序功能,它可以检测和处理 AJAXed 内容。
    我认为以这种方式实现起来要容易得多,作为一个简单的 "wait until an element exists" 工作(我只是使用主要个人资料图片作为等待的选择器,因为我找不到任何相关的 AJAX 选择器。也我没有在我的代码中使用 jNode。
    所以我只是将我的整个代码包含在一个函数 visibleCounter 中,并添加了一个 waitForKeyElements 行(见下文),但不幸的是它也不起作用:

    waitForKeyElements (".-cx-PRIVATE-ProfilePage__avatar", visibleCounter);         
    
    function visibleCounter(jNode){
        myCode()
    }
    

我使用 arrive.js 库解决了这个问题。它提供事件来监视 DOM 元素的创建和删除。它在内部使用 Mutation Observers。该库不依赖于 jQuery,您可以将下面示例中的 jQuery 元素替换为纯 javascript 元素,它会正常工作。

我引用它的作者comment

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.

以及它的两个用途:

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);
});

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

而且,这是 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, '');    // 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();
});