49

Note: I am keeping an up-to-date version of the bookmarklet in my question which works well and is based on Jacob's answer. If you are looking for a bookmarklet to use, use that one. See leosok's fantastic answer if you just want something amazing that works on chrome.

I want to be able to invert the color of all the elements on a page with a JavaScript bookmarklet. I know that to invert a color you subtract each of the RGB hex values from 255(xFF), but beyond that I am unsure of how to proceed.

How can I accomplish this?

Using jQuery is acceptable, and it only needs to work on Chrome, although if it worked in Firefox that'd be a plus.

This is excluding images - background, text and links colors should all be inverted. Basically anything that gets its color from CSS.

UPDATE Here is an updated bookmarklet that fixes the nested element issue and will work on a lot of different sites (including this one)

UPDATE2 Added some support for transparency, handling elements that have default background-color rgba(0, 0, 0, 0). More sites should be working now with the updated one.

javascript: (function ($) {
    function load_script(src, callback) {
        var s = document.createElement('script');
        s.src = src;
        s.onload = callback;
        document.getElementsByTagName('head')[0].appendChild(s);
    }

    function invertElement() {
        var colorProperties = ['color', 'background-color'];
        var color = null;
        for (var prop in colorProperties) {
            prop = colorProperties[prop];
            if (!$(this).css(prop)) continue;
            if ($(this).data(prop) != $(this).css(prop)) continue;

            if (($(this).css(prop) === 'rgba(0, 0, 0, 0)') || ($(this).css(prop) === 'transparent')) {
                if ($(this).is('body')) {
                    $(this).css(prop, 'black');
                    continue;
                } else {
                    continue;
                }
            }
            color = new RGBColor($(this).css(prop));
            if (color.ok) {
                $(this).css(prop, 'rgb(' + (255 - color.r) + ',' + (255 - color.g) + ',' + (255 - color.b) + ')');
            }
            color = null;
        }
    }

    function setColorData() {
        var colorProperties = ['color', 'background-color'];
        for (var prop in colorProperties) {
            prop = colorProperties[prop];
            $(this).data(prop, $(this).css(prop));
        }
    }

    function invertColors() {
        $(document).live('DOMNodeInserted', function(e) {
            var $toInvert = $(e.target).find('*').andSelf();
            $toInvert.each(setColorData);
            $toInvert.each(invertElement);
        });
        $('*').each(setColorData);
        $('*').each(invertElement);
        $('iframe').each(function () {
            $(this).contents().find('*').each(setColorData);
            $(this).contents().find('*').each(invertElement);
        });
    }
    load_script('http://www.phpied.com/files/rgbcolor/rgbcolor.js', function () {
        if (!window.jQuery) load_script('https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js', invertColors);
        else invertColors();
    });

})(jQuery);

Now works with most sites I've tried. Background images can pose a problem, however.

Muhd
  • 24,305
  • 22
  • 61
  • 78
  • Apparently you can't inline comments in a bookmarklet since it gets converted into one line. – Muhd Mar 19 '12 at 19:28
  • I have removed the comment which was breaking the bookmarklet, and now just do `$('*')` for selecting all elements so that it now includes the `` element (which can apparently have a background color). – Muhd Mar 19 '12 at 19:49
  • It's preferred to write a [self-answer](https://stackoverflow.com/help/self-answer) than to keep editing the original post like this. I realize this is a community wiki now, but it still makes of an awkward thread to get information from--answers should go into the, well, "answers" field so they can be voted upon and evaluated on their own merits. – ggorlen Dec 23 '20 at 20:19

5 Answers5

72

First things first, grab the awesome RGBColor class here.

Here goes:

jsFiddle example

//set up color properties to iterate through
var colorProperties = ['color', 'background-color'];

//iterate through every element in reverse order...
$("*").get().reverse().each(function() {
    var color = null;

    for (var prop in colorProperties) {
        prop = colorProperties[prop];

        //if we can't find this property or it's null, continue
        if (!$(this).css(prop)) continue; 

        //create RGBColor object
        color = new RGBColor($(this).css(prop));

        if (color.ok) { 
            //good to go, let's build up this RGB baby!
            //subtract each color component from 255
            $(this).css(prop, 'rgb(' + (255 - color.r) + ', ' + (255 - color.g) + ', ' + (255 - color.b) + ')');
        }
        color = null; //some cleanup
    }
});

Screenshot:

alt text

EDIT: Here's a bookmarklet you can now copy-paste into your browser (http://jsfiddle.net/F7HqS/1/)

javascript:function load_script(src,callback){var s=document.createElement('script');s.src=src;s.onload=callback;document.getElementsByTagName('head')[0].appendChild(s);}function invertColors(){var colorProperties=['color','background-color'];$('*').each(function(){var color=null;for(var prop in colorProperties){prop=colorProperties[prop];if(!$(this).css(prop))continue;color=new RGBColor($(this).css(prop));if(color.ok){$(this).css(prop,'rgb('+(255-color.r)+','+(255-color.g)+','+(255-color.b)+')');}color=null;}});}load_script('http://www.phpied.com/files/rgbcolor/rgbcolor.js',function(){if(!window.jQuery)load_script('https://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js',invertColors);else invertColors();});
Jacob Relkin
  • 161,348
  • 33
  • 346
  • 320
  • 1
    +1 This is very nice, but I am having trouble making it into a bookmarklet – Muhd Jan 22 '11 at 21:29
  • 1
    @Muhd See my updated answer, just grab that code and stick it in a browser's address bar with `javascript:` preceding it and voila! :) – Jacob Relkin Jan 22 '11 at 22:25
  • 1
    This is super cool! However, when used on this page, it isn't properly inverting the text of my question, or your answer. I wonder why that is. – Muhd Jan 22 '11 at 22:37
  • 1
    +1 I was just browsing, but this bookmarklet will come in handy :) – Kranu Jan 22 '11 at 22:41
  • @Muhd I think I might have to modify the `RGBColor` class for this one, apparently it doesn't support alpha. :( – Jacob Relkin Jan 22 '11 at 23:02
  • You know, I actually thought that alpha might be a major hurdle even before asking the question. Also, it might be nice if border colors were factored in, although they are a little more complicated since they can be top, bottom, left, and right. Some pages look bad without the borders inverted (like a google search result). – Muhd Jan 22 '11 at 23:25
  • I thing it's StackOverflow Nightshow. well don't show this to jeffatwood. they thing that you make a another clone OF SO. –  Jan 23 '11 at 14:16
  • A number of other sites have the problem as well, so if you need more references just look around. google search and google finance seem to have the problem. – Muhd Jan 23 '11 at 20:39
  • @Muhd I'm working on it. Hang on a bit - i'll let you know when it's done. – Jacob Relkin Jan 23 '11 at 20:40
  • @Jacob, I modified the code to handle iframe contents: http://jsfiddle.net/F7HqS/5/ . – Muhd Jan 24 '11 at 22:22
  • @Muhd Cool. I'm almost done with my modifications to the `RGBColor` class. I'll integrate your changes in the finished package. – Jacob Relkin Jan 24 '11 at 22:23
  • 1
    @Jacob, I have actually found a reason that a number of pages are not working properly. The text is being inverted multiple times: http://ScrnSht.com/cvkner This is because the text color will be inverted for an element, and then that element's child will invert the color back, and then THAT element's child will invert it again. When the CSS is custom defined for lowest level element in the stylesheet, or the number of inversions just happens to be odd, it works out to be the correct color. When there is an even number of generations from the last color definition the color is incorrect. – Muhd Jan 24 '11 at 23:52
  • @Muhd See my latest question. http://stackoverflow.com/questions/5000108/how-can-i-tell-if-a-particular-css-property-is-inherited-with-jquery – Jacob Relkin Feb 15 '11 at 19:04
  • One solution might be to store the color properties of each element prior to any inversion and then use that as a reference. If it changed, then it is inherited. Otherwise not. Perhaps less complicated than any solution you get for the above question. – Muhd Feb 16 '11 at 04:18
  • @Muhd Okay thanks. Will let you know when I get around to finishing this. – Jacob Relkin Feb 25 '11 at 05:25
  • 3
    @JacobRelkin: I have found a very simple fix for your code. Just loop the elements in reverse order: `$($("*").get().reverse()).each(...` – musefan Mar 07 '13 at 10:48
  • 4
    Here's a version with @musefan's fix, stripped down and inlined rgbcolor.js, and minified: https://gist.github.com/JesseBuesking/5260743 – JesseBuesking Mar 28 '13 at 05:11
  • 2
    You can also use "-webkit:invert(100%);" I made this Bookmarklet, which will also invert the Page back: http://jsfiddle.net/nikita_turing/jVKw6/2/ – leosok Apr 16 '13 at 21:50
  • @leosok That's really awesome... It does images and form elements and everything. – Muhd Apr 23 '13 at 23:13
  • @Muhd I know. I only now realize that that's special :-) If You think it's worth an answer, I will answer it here! – leosok Apr 25 '13 at 19:47
  • @leosok, I think you should. It will give your script more visibility. – Muhd Apr 26 '13 at 00:10
50

My solution seems to work only for Chrome right now, but it inverts everything (including images and iframes) as seen here:

enter image description here

Also it does not make use of external libraries and is very simple: adding a -webkit-filter: invert(100%) to the html-selector.

javascript: (
function () { 
// the css we are going to inject
var css = 'html {-webkit-filter: invert(100%);' +
    '-moz-filter: invert(100%);' + 
    '-o-filter: invert(100%);' + 
    '-ms-filter: invert(100%); }',

head = document.getElementsByTagName('head')[0],
style = document.createElement('style');

// a hack, so you can "invert back" clicking the bookmarklet again
if (!window.counter) { window.counter = 1;} else  { window.counter ++;
if (window.counter % 2 == 0) { var css ='html {-webkit-filter: invert(0%); -moz-filter:    invert(0%); -o-filter: invert(0%); -ms-filter: invert(0%); }'}
 };

style.type = 'text/css';
if (style.styleSheet){
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}

//injecting the css to the head
head.appendChild(style);
}());

Here's the fiddle: http://jsfiddle.net/nikita_turing/jVKw6/3/ with the bookmarklet included. If someone has an idea of how to make it work for Firefox (SVG-Filters?), go ahead!

ggorlen
  • 44,755
  • 7
  • 76
  • 106
leosok
  • 302
  • 1
  • 5
  • 15
  • 1
    I found a solution for Firefox. I added it to your code. Fiddle here: http://jsfiddle.net/jVKw6/4/ – renanleandrof Nov 20 '13 at 17:31
  • 1
    And here is a full code that works on both IE and Firefox too! jsfiddle.net/jVKw6/8 – renanleandrof Nov 20 '13 at 19:20
  • 1
    @Renanlf I created a bookmarklet with your JSFiddle code, but nothing happens. Please help. – Oxwivi Dec 22 '13 at 18:30
  • For chrome there is an extension doing that job, already: https://chrome.google.com/webstore/detail/night-reading-mode/mhbfhbljmehldmmoeoeelnlafloiifmf – Martin Braun Nov 26 '14 at 13:47
  • A minor tweak on the above. – astryk Nov 18 '15 at 23:08
  • The only answer meeting the OPs requirements of excluding images (and actually works) - is the answer utilizing the RGBColor class (hence why it has today triple the upvotes of the accepted answer). A picture is worth a thousand words: We'll just use the LifeHacker front page as a test. `Original page:` http://i.imgur.com/1oxgM1d.png `Using the webkit invert filter via bookmarklet:` http://i.imgur.com/1RLBjvw.png `Using the RGBColor class provided above by Jason via bookmarklet (minified version):` http://i.imgur.com/ghCCVBU.png Any questions?I know which I want to read!:) – Collin Chaffin Mar 28 '17 at 06:59
  • Couldn't you just do `document.body.parentElement.style='-webkit-filter: invert(100%);-moz-filter: invert(100%);-o-filter: invert(100%);-ms-filter: invert(100%);'`? – Jenna Sloan Sep 07 '17 at 01:47
  • That code doesnt always work. For example, try to run that code (with Inspect>console) on this page : protectpages.com/blog/night-mode-for-windows-pc – T.Todua Nov 26 '17 at 21:23
  • You can reduce the x-ray effect with [`hue-rotate`](https://developer.mozilla.org/en-US/docs/Web/CSS/filter-function/hue-rotate()), e.g. `document.body.style.cssText = "filter: invert(100%) hue-rotate(180deg) !important;"`. – ggorlen Dec 23 '20 at 20:12
4

I cleaned up the comments from one of the answers (by leosok) above, so it will work as a bookmarklet in chrome. Note that this solution is more efficient than the current highest-point here, plus it works even if the html changes after you run the script.

javascript:(function () { 
    var css = 'html {-webkit-filter: invert(100%);' + '-moz-filter: invert(100%);' + '-o-filter: invert(100%);' + '-ms-filter: invert(100%); }';
    var head = document.getElementsByTagName('head')[0];
    var style = document.createElement('style');
    if (!window.counter) { 
        window.counter = 1;
    } else { 
        window.counter++;
        if (window.counter % 2 == 0) { 
            var css = 'html {-webkit-filter: invert(0%); -moz-filter: invert(0%); -o-filter: invert(0%); -ms-filter: invert(0%); }'
        } 
    }
    style.type = 'text/css';
    if (style.styleSheet) {
        style.styleSheet.cssText = css;
    } else {
        style.appendChild(document.createTextNode(css));
    }
    head.appendChild(style);
}());

One line for bookmarklet. create a bookmark, then edit the url to this: javascript:(function () { var css = 'html {-webkit-filter: invert(100%);' + '-moz-filter: invert(100%);' + '-o-filter: invert(100%);' + '-ms-filter: invert(100%); }'; var head = document.getElementsByTagName('head')[0]; var style = document.createElement('style'); if (!window.counter) { window.counter = 1; } else { window.counter++; if (window.counter % 2 == 0) { var css = 'html {-webkit-filter: invert(0%); -moz-filter: invert(0%); -o-filter: invert(0%); -ms-filter: invert(0%); }' } } style.type = 'text/css'; if (style.styleSheet) { style.styleSheet.cssText = css; } else { style.appendChild(document.createTextNode(css)); } head.appendChild(style); }());

Zig Mandel
  • 19,571
  • 5
  • 26
  • 36
  • See my comment above I posted in the accepted answer. Your solution along with the accepted answer is just incorrect does not meet the OPs requirement of EXCLUDING images, and I even posted screenshots using your posted bookmarklet as the invert filter one doing a full close and reload in between each one using the front page of the LifeHacker site as example as you can see, only the solution here that produces a page that is even remotely readable, and actually looks pretty darn good, is the one utilizing Stoyan's RGBcolor class. – Collin Chaffin Mar 28 '17 at 14:00
2

The accepted answer is totally correct, with one minor flaw. Each time you toggle the invert it adds ANOTHER style tag to the head. Do this instead

  // the css we are going to inject
  let css = 'html {-webkit-filter: invert(100%);' +
    '-moz-filter: invert(100%);' +
    '-o-filter: invert(100%);' +
    '-ms-filter: invert(100%); }';

  let head = $('head')[0];
  let invertStyle = $('#invert')[0];

  if (invertStyle) {
    head.removeChild(invertStyle);
  } else {
    let style = document.createElement('style');

    style.type = 'text/css';
    style.id = 'invert';
    if (style.styleSheet){
      style.styleSheet.cssText = css;
    } else {
      style.appendChild(document.createTextNode(css));
    }

    //injecting the css to the head
    head.appendChild(style);
  }

That way you simply remove the tag if you want to undo your invert. Works great!

astryk
  • 1,256
  • 13
  • 20
  • First of all you are absolutely incorrect that the accepted answer is correct I even posted screenshots in the above comments showing just how incorrect it really is. This updated one posted here unfortunately produces exactly the same totally illegible, horrible results that simply invert it all images included, but miss some and are generally not usable, whereas years later Stoyan's RGBcolor class still produces very acceptable results, and also still meets the OPs original question requirement of EXCLUDING images - even after several years of changes to standards, etc.! – Collin Chaffin Mar 28 '17 at 14:05
1

I figured it would be fun to try inverting images. Didn't take long to find an appropriate Javascript library for image editing: http://www.pixastic.com/lib/

You probably can't load that whole library into a bookmarklet, but if you host it yourself you can add something like this to the end of the bookmarklet (after invertColors):

load_script('http://www.example.com/pixastic.invert.js', function () {$('img').each(function() {try{$(this).pixastic("invert");} catch(e) {}})})

I think it's worth noting that if your goal is to take a page with a white background and make it black (or vice versa), something simpler might be in order.

I just tried the bookmarklet from Jacob and compared it to a more naive version I found on the Google support forums: http://www.google.com/support/forum/p/Chrome/thread?tid=26affebdd0da12d9&hl=en

Jacob's invert seems to work less frequently and takes quite a bit longer on large pages. I think I'll end up using the naive version more frequently.

i_grok
  • 638
  • 7
  • 9
  • It would be more useful if you could apply it to background images. – Muhd Mar 19 '12 at 19:36
  • And I think my updated version of Jacob's bookmarklet is way better than the one you linked (especially since it preserves color variety). – Muhd Mar 19 '12 at 19:37