もどる TOP

[JavaScript] 閲覧中のページをネガポジ反転するブックマークレット

 なんとなく作ったはいいが、力及ばず、お遊び程度のクオリティしか出なかったよ……(^ω^)

 もちろん画像には効きません。効くページと効かないページがあります。

 (※自分が見てる画面が変化するだけなので、危ないものではありません。飽きたらページを更新すると元に戻ります。)

 ブックマークレットとして使うには、適当なページをブックマークしたあと、ブックマークの設定をいじって、URLをまるごと以下のコードに書き換えてください。そのブックマークにアクセスすると実行できると思います。PCで閲覧している人は、↓のコードを選択してブックマークバーにドラッグ&ドロップでもいけるかも。

 iPhoneのSafariでは無理でしたが、ネガポジ反転したいページを開いて、URL欄に以下のコードをコピペして実行すると動かせる場合もあります。ブラウザのおせっかいで、コピペした時に先頭の『javascript:』が消えるかもしれないので、消えたら書き足してください。

 動作確認はYahooなどで行いました。

中身

 中身の話をすると、画像はともかくグラデーションにも効かないのは怠慢!! そして、DOM操作ができない疑似要素にも効かない。このページで言うと、↑の見出しの赤い矢印が反転してないでしょ? 他にもいろいろ穴があるらしい。あと、実行後の要素を見ると、styleの属性値がめちゃくちゃグロい!!

 読める状態のソースコードは以下である。window.getComputedStyleは初めて存在を知った。これを使うと、スタイルが全て適用された上で、今のところその要素がどんなスタイルをしてるのかを確認できるらしい。なお、これを通すと、もともとの指定がWebカラーコードや色名でも、rgb()あるいはrgba()で取得された。

読めるソースコード
(function() {
	// スタイルのRGB文字列またはRGBA文字列から、RGBA値を配列で取得する関数
	var rgbaStringToRGBA = function(rgbaStr) {
		var rgba = rgbaStr.replace(/rgba?\(|\)|\s/g, '').split(',');
		if (rgba.length === 3) {
			rgba[3] = 1;
		}
		for (var i = 0; i < 4; ++i) {
			rgba[i] = Number(rgba[i]);
		}
		return rgba;
	};

	// RGBA値を反転させる関数
	var invertRGBA = function(rgba) {
		for (var i = 0; i < 3; ++i) {
			rgba[i] = 255 - rgba[i];
		}
		return rgba;
	};

	// 画面内の全要素を取得
	var elems = document.querySelectorAll('*');
	var newStyles = [];

	Array.prototype.forEach.call(elems, (elem) => {
		// 計算済みスタイルを取得
		var style = window.getComputedStyle(elem);
		var newStyle = {};
		for (var prop in style) {
			// 名前にcolorとつき、値がrgbで始まるスタイルだけ処理する
			if (prop.toLowerCase().indexOf('color') < 0) {
				continue;
			}
			var color = style[prop].toLowerCase();
			if (color.indexOf('rgb') === 0) {
				// スタイルの文字列からRGBA値を抽出
				var rgba = rgbaStringToRGBA(color);
				
				// html要素でbackgroundColorのα値がゼロだったら、不透明の白として扱う
				if (elem.tagName.toLowerCase() == 'html' && prop == 'backgroundColor' && rgba[3] == 0) {
					rgba[0] = rgba[1] = rgba[2] = 255;
					rgba[3] = 1;
				}

				// 色を反転してとっておく
				newStyle[prop] = 'rgba(' + invertRGBA(rgba).join(',') + ')';
			}
		}
		newStyles.push(newStyle);
	});

	// 反転した色を適用する (※全て色を作ってからまとめて反転しないと、親が反転したせいで子が再反転するので)
	Array.prototype.forEach.call(elems, (elem, i) => {
		var newStyle = newStyles[i];
		for (var prop in newStyle) {
			elem.style[prop] = newStyle[prop];
		}
	});
})();

疑似要素も反転したい!!

 ちなみに、疑似要素まで反転してやろうと、『ページに読み込まれている全てのスタイルシートをさらって色の指定値を反転させる』バージョンも作ってみたが、ダメだった!!

 確かに疑似要素の色は反転できたが、Cross-Originうんたらに引っかかって、違うドメインから読み込んでるスタイルシートのCSSStyleSheet.cssRulesが取得できない。取得できないのはともかく、存在を確認しようとするだけでエラー (Failed to read the 'cssRules' property from 'CSSStyleSheet': Cannot access rules) が出るのも困った。Chromeのバグだという噂があるが……。

 うちのようなサイトはともかく、今どきのWebサービスは基本的に外部のドメインにCSSを置いてるだろうから、使えるサイトがまったく限られる。長すぎてブックマークレットにできないし、以下のコードは私のメモなので見ないように!!

ゴリゴリ版・これはダメ
(function() {
	var _namedColors = { black: '#000000', silver: '#c0c0c0', gray: '#808080', white: '#ffffff', maroon: '#800000', red: '#ff0000', purple: '#800080', fuchsia: '#ff00ff', green: '#008000', lime: '#00ff00', olive: '#808000', yellow: '#ffff00', navy: '#000080', blue: '#0000ff', teal: '#008080', aqua: '#00ffff', orange: '#ffa500', aliceblue: '#f0f8ff', antiquewhite: '#faebd7', aquamarine: '#7fffd4', azure: '#f0ffff', beige: '#f5f5dc', bisque: '#ffe4c4', blanchedalmond: '#ffebcd', blueviolet: '#8a2be2', brown: '#a52a2a', burlywood: '#deb887', cadetblue: '#5f9ea0', chartreuse: '#7fff00', chocolate: '#d2691e', coral: '#ff7f50', cornflowerblue: '#6495ed', cornsilk: '#fff8dc', crimson: '#dc143c', cyan: '#00ffff', darkblue: '#00008b', darkcyan: '#008b8b', darkgoldenrod: '#b8860b', darkgray: '#a9a9a9', darkgreen: '#006400', darkgrey: '#a9a9a9', darkkhaki: '#bdb76b', darkmagenta: '#8b008b', darkolivegreen: '#556b2f', darkorange: '#ff8c00', darkorchid: '#9932cc', darkred: '#8b0000', darksalmon: '#e9967a', darkseagreen: '#8fbc8f', darkslateblue: '#483d8b', darkslategray: '#2f4f4f', darkslategrey: '#2f4f4f', darkturquoise: '#00ced1', darkviolet: '#9400d3', deeppink: '#ff1493', deepskyblue: '#00bfff', dimgray: '#696969', dimgrey: '#696969', dodgerblue: '#1e90ff', firebrick: '#b22222', floralwhite: '#fffaf0', forestgreen: '#228b22', gainsboro: '#dcdcdc', ghostwhite: '#f8f8ff', gold: '#ffd700', goldenrod: '#daa520', greenyellow: '#adff2f', grey: '#808080', honeydew: '#f0fff0', hotpink: '#ff69b4', indianred: '#cd5c5c', indigo: '#4b0082', ivory: '#fffff0', khaki: '#f0e68c', lavender: '#e6e6fa', lavenderblush: '#fff0f5', lawngreen: '#7cfc00', lemonchiffon: '#fffacd', lightblue: '#add8e6', lightcoral: '#f08080', lightcyan: '#e0ffff', lightgoldenrodyellow: '#fafad2', lightgray: '#d3d3d3', lightgreen: '#90ee90', lightgrey: '#d3d3d3', lightpink: '#ffb6c1', lightsalmon: '#ffa07a', lightseagreen: '#20b2aa', lightskyblue: '#87cefa', lightslategray: '#778899', lightslategrey: '#778899', lightsteelblue: '#b0c4de', lightyellow: '#ffffe0', limegreen: '#32cd32', linen: '#faf0e6', magenta: '#ff00ff', mediumaquamarine: '#66cdaa', mediumblue: '#0000cd', mediumorchid: '#ba55d3', mediumpurple: '#9370db', mediumseagreen: '#3cb371', mediumslateblue: '#7b68ee', mediumspringgreen: '#00fa9a', mediumturquoise: '#48d1cc', mediumvioletred: '#c71585', midnightblue: '#191970', mintcream: '#f5fffa', mistyrose: '#ffe4e1', moccasin: '#ffe4b5', navajowhite: '#ffdead', oldlace: '#fdf5e6', olivedrab: '#6b8e23', orangered: '#ff4500', orchid: '#da70d6', palegoldenrod: '#eee8aa', palegreen: '#98fb98', paleturquoise: '#afeeee', palevioletred: '#db7093', papayawhip: '#ffefd5', peachpuff: '#ffdab9', peru: '#cd853f', pink: '#ffc0cb', plum: '#dda0dd', powderblue: '#b0e0e6', rosybrown: '#bc8f8f', royalblue: '#4169e1', saddlebrown: '#8b4513', salmon: '#fa8072', sandybrown: '#f4a460', seagreen: '#2e8b57', seashell: '#fff5ee', sienna: '#a0522d', skyblue: '#87ceeb', slateblue: '#6a5acd', slategray: '#708090', slategrey: '#708090', snow: '#fffafa', springgreen: '#00ff7f', steelblue: '#4682b4', tan: '#d2b48c', thistle: '#d8bfd8', tomato: '#ff6347', turquoise: '#40e0d0', violet: '#ee82ee', wheat: '#f5deb3', whitesmoke: '#f5f5f5', yellowgreen: '#9acd32', rebeccapurple: '#663399' };
	Array.prototype.forEach.call(document.styleSheets, (ss) => {
		var rules;
		try {
			rules = ss.cssRules;
		} catch (e) {
			try {
				rules = ss.rules;
			} catch (e) {
				console.log('cannot read cssRules', ss);
				return;
			}
		}
		Array.prototype.forEach.call(rules, (rule) => {
			var style = rule.style;
			if (!style) {
				return;
			}
			for (var i = 0, len = style.length; i < len; ++i) {
				var prop = style.item(i);
				if (prop.indexOf('color') < 0) {
					continue;
				}
				var rgba = colorToRGBA(style.getPropertyValue(prop));
				if (rgba === false) {
					continue;
				}
				var newColor = 'rgba('+ invertRGBA(rgba).join(',') +')';
				style.setProperty(prop, newColor);
			}
		});
	});
	function colorToRGBA(colorStr) {
		colorStr = colorStr.toLowerCase().trim();
		if (colorStr == 'initial' || colorStr == 'inherit' || colorStr == 'transparent') {
			return false;
		}
		if (_namedColors[colorStr] !== undefined) {
			colorStr = _namedColors[colorStr];
		}
		if (colorStr.indexOf('#') === 0) {
			return webColorCodeToRGBA(colorStr);
		}
		if (colorStr.indexOf('rgb') === 0) {
			return rgbaStringToRGBA(colorStr);
		}
		console.log('unsupported!', colorStr);
		return false;
	}
	function webColorCodeToRGBA(webColorCode) {
		var rgba = [0,0,0,0];
		var code = webColorCode.substr(1);
		if (code.length == 3 || code.length == 4) {
			c = code.split('');
			code = c[0] + c[0] + c[1] + c[1] + c[2] + c[2];
			if (c[3] === undefined) {
				code += c[3] + c[3];
			}
		}
		if (code.length < 6) {
			console.log('invalid color code', code);
			return rgba;
		}
		c = code.split('');
		rgba.r = parseInt(c[0] + c[1], 16);
		rgba.g = parseInt(c[2] + c[3], 16);
		rgba.b = parseInt(c[4] + c[5], 16);
		rgba.a = (c[6] !== undefined && c[7] !== undefined) ? parseInt(c[6] + c[7], 16) / 255 : 1;
		return rgba;
	}
	function rgbaStringToRGBA(rgbaStr) {
		var rgba = rgbaStr.replace(/rgba?\(|\)/g, '').split(',');
		if (rgba.length === 3) {
			rgba[3] = 1;
		}
		for (var i = 0; i < 4; ++i) {
			rgba[i] = Number(rgba[i]);
		}
		return rgba;
	}
	function invertRGBA(rgba) {
		for (var i = 0; i < 3; ++i) {
			rgba[i] = 255 - rgba[i];
		}
		return rgba;
	}
})();
サイドバーを表示する
ブログ
ShortCircuit
ShortCircuit
花火大会
天使
去る512時間前、キリウ君は折れてない千歳飴を渡してきて、ぼくが折るよう仕向けた。1024時間前、彼はこの世のものではないハッシュアルゴリズムでひとりブロックチェーンを始めていた。