简体   繁体   中英

How to make a GreaseMonkey script affect elements in a page before they're displayed?

I'm trying to ensure that images in a certain website are not displayed, but the alt text is still displayed. Initially I attempted to accomplish this with Stylish (using Firefox) and asked the following question:

How to force an image's alt text to display instead of the image?

The accepted answer provided me with an alternative solution using Greasemonkey. The script uses waitForKeyElements to hide images even if they're added using AJAX.

I changed the given script to the following:

// ==UserScript==
// @name     _Hide pics except for alt text
// @include  http://YOUR_SERVER.COM/YOUR_PATH/*
// @require  http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js
// @require  https://gist.github.com/raw/2625891/waitForKeyElements.js
// @grant    GM_addStyle
// ==/UserScript==

GM_addStyle ( "                                 \
    * {                                         \
        background-image: none !important;      \
    }                                           \
" );

waitForKeyElements ("img", hideImageExceptForAltText);

function hideImageExceptForAltText (jNode) {
    var imgAlt = jNode.attr("alt");
    var imgTitle = jNode.attr("title");

    jNode.css("display", "none");

    var newSpan = $("<span></span>");
    newSpan.attr("title", imgTitle);
    newSpan.append(imgAlt);

    jNode.parent().append(newSpan);
}

Just like the original script, this has the problem that the images are still displayed for a few moments as the page is loading.

Is it possible to ensure that the given function will prevent images on a page from being displayed immediately, so that they won't be visible at all?

EDIT: Brock Adams' reply had the clue I was missing. In case anyone is looking for something like this, the following is what I ended up using. It works fine on the site I needed it for, but I can't guarantee it will work on other sites or other browsers than Firefox.

The following hides images and replaces them with a link (except for background images). Clicking that link will display the image.

// ==UserScript==
// @name        TCRF images
// @namespace   SOMETHING
// @include     http://YOUR_SERVER.COM/YOUR_PATH/*
// @require     http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js
// @require     https://gist.github.com/raw/2625891/waitForKeyElements.js
// @version     1
// @grant       GM_addStyle
// @run-at      document-start
// ==/UserScript==

GM_addStyle ( "\
    * {\
       background-image: none !important;\
    }\
\
    img.gmImgHideHidden {\
       display: none !important;\
    }\
" );

var num = 0;

function gmImgHideShowImg(imgId, linkId)
{
    // Using plain JavaScript because the page itself may not have jquery
    var img = document.getElementById(imgId);
    img.className = img.className.replace( /(?:^|\s)gmImgHideHidden(?!\S)/g , '' );

    var lnk = document.getElementById(linkId);
    lnk.parentNode.removeChild(lnk);
}

// Exporting the "show image" function so that it can be used in the webpage
unsafeWindow.gmImgHideShowImg = exportFunction(gmImgHideShowImg, unsafeWindow);

waitForKeyElements ("img", hideImageExceptForAltText);

function hideImageExceptForAltText (jNode) {
    var imgId = jNode.attr("id");

    // Ensuring an id exists so the image can be searched for later
    if(typeof(imgId) == "undefined")
    {
        imgId = "gmImgHideImg" + num;
        jNode.attr("id", imgId);
    }

    var imgDisp = jNode.css("display");

    var imgAlt = jNode.attr("alt");
    jNode.addClass("gmImgHideHidden");

    var linkId = "gmImgHideLink" + num;

    var linkNode = $("<a></a>");
    linkNode.attr("id", linkId);
    linkNode.append("Image: " + imgAlt);
    linkNode.attr("onclick", "gmImgHideShowImg('" + imgId + "', '" + linkId + "'); return false;");

    jNode.parent().append(linkNode);

    num++;
}

MutationObserver is without a doubt the best solution here. Combined with early injection by @run-at document-start we can make the script pretty much bullet-proof. Check out this fiddle (tested with Firefox 40) to see it in action.

I think the code is pretty self-explanatory. I've annotated the subtleties, but leave a comment if there's anything you don't understand.

// ==UserScript==
// @run-at document-start
// ==/UserScript==
"use strict";

/* part one: <img> elements */

(new MutationObserver(function(Records, Obs) {
    for (let R of Records) {/* examine each mutation record: */
        /* if the record specifies an attribute mutation… */
        if (
            R.attributeName === "src" &&
            (R.target instanceof Element) && /* this check might be necessary */
            R.target.tagName.toLowerCase() === "img" &&
            R.target.getAttribute("src") !== "" /* avoid infinite loop */
        ) {
            R.target.setAttribute("src", "");
        };

        /* if the record specifies a sub-element mutation… */
        for (let N of R.addedNodes) {
            if (
                (N instanceof Element) && /* this check might be necessary */
                N.tagName.toLowerCase() === "img" &&
                N.getAttribute("src") !== "" /* avoid infinite loop */
            ) {
                N.setAttribute("src", "");
            };
        };
    };
})).observe(document, {
    /* changes wot we listen for */
    childList : true,
    subtree : true,
    attributes : true
});

/* part two: background-image styles */

let check_for_head_elem = function(_, Obs) {
    if (!document.head) {return;};
    Obs.disconnect();

    /* apply our style */
    let Style = document.createElement("style");
    document.head.appendChild(Style);
    Style.sheet.insertRule("* {background-image : none !important;}", 0);
};

let check_for_root_elem = function(_, Obs) {
    if (!document.documentElement) {return;};
    Obs.disconnect();

    /* observe until the <head> element is added */
    Obs = new MutationObserver(check_for_head_elem)
    Obs.observe(document.documentElement, {childList : true});
    check_for_head_elem(null, Obs); /* check here because it might exist already */
};

{/* observe until the <html> element is added */
    let Obs = new MutationObserver(check_for_root_elem);
    Obs.observe(document, {childList : true});
    check_for_root_elem(null, Obs); /* check here because it might exist already */
};

There are some other ways to get images on the page that I haven't taken into consideration ( <iframe> , <svg> , <canvas> , <li> bullet points), but if necessary you should be able to use mutation observers or CSS to take care of those too.

A simple, robust way to do this is to set CSS first-thing, before any of the rest of the page loads.

@run-at document-start and GM_addStyle() do this. (on Firefox; not tested on latest Tampermonkey)

That way, the images are not displayed even for a fraction of a second, like they are with the original code or with a complicated, finicky MutationObserver approach.

This complete script shows the process:

// ==UserScript==
// @name     _Hide pics except for alt text
// @include  http://YOUR_SERVER.COM/YOUR_PATH/*
// @require  http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js
// @require  https://gist.github.com/raw/2625891/waitForKeyElements.js
// @grant    GM_addStyle
// @run-at   document-start
// ==/UserScript==
GM_addStyle ( "                                 \
    * {                                         \
        background-image: none !important;      \
    }                                           \
    img {                                       \
        display: none !important;               \
    }                                           \
" );

/*--- $(document).ready() is not always needed for modern Firefox, but
    use for maximum portability, when script runs at document-start.
*/
$(document).ready ( function () {
    waitForKeyElements ("img", hideImageExceptForAltText);
} );

function hideImageExceptForAltText (jNode) {
    var imgAlt      = jNode.attr("alt")     || "";
    var imgTitle    = jNode.attr("title")   || "";

    jNode.after ('<span title="' + imgTitle + '">' + imgAlt + '</span>');
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM