darkmode
by
nanikamado
2018-06-19 [2018/06/19 21:24:26]
サイトの背景色を黒にし、文字色を明るくします。詳しい説明はソースコード内の先頭のコメントを見てください。
@@ -15,100 +15,208 @@
// 稀に色を変えられないこともあります。仕様です。
// 画像の色は変わりません。
// 一度実行した状態でもう一度実行すると、もとに戻ります。
-//http://let.hatelabo.jp/nanikamado/let/hLHU5aii3YU5 の改良版です。
+// 'use strict'の次の行の、bgcolorに入れる値を変更することで、背景色を変えることができます。
+// http://let.hatelabo.jp/nanikamado/let/hLHU5aii3YU5 の改良版です。
(() => {
'use strict';
- const set_color = (nodes) => {
- nodes.forEach(node => {
- if (!node.style) return;
- if (node.getAttribute('dark_before_style') === null) node.setAttribute('dark_before_style', node.style.cssText);
- const computed_style = window.getComputedStyle(node);
- if (!computed_style.color) return;
- const [r, g, b] = computed_style.color.match(/\d+/g).slice(0, 3).map(Number);
- const s = 255 - Math.max(r, g, b);
- //color
- if (node.tagName === 'A') node.style.color = `rgb(${r+s},${g+s},${b+s})`;
- else node.style.setProperty('color', `rgb(${r+s},${g+s},${b+s})`, 'important');
- //backgroundColor
- if (computed_style.backgroundColor.indexOf('a') === -1)
- node.style.setProperty('background-color', '#000', 'important');
- else if (computed_style.backgroundColor.indexOf('rgba(0, 0, 0, 0)') === -1)
- node.style.setProperty('background-color', 'rgba(0, 0, 0,' +
- computed_style.backgroundColor.replace(/[^,]*,[^,]+,[^,]+,[^,\d]*(\d?.?\d+)/, '$1'), 'important');
- //backgroundImage gradient
- if (computed_style.backgroundImage.indexOf('gradient') !== -1)
- node.style.backgroundImage = computed_style.backgroundImage
- .replace(/[^,]*gradient\((?:[^\(\)]*\((?:[^\(\)]*\([^\(\)]*\))*[^\(\)]*[^\(\)]*\))*[^\(\)]*\),?/gi, '') || 'none';
- });
+ let bgcolor = '#111';
+ let time = {
+ all: Date.now()
+ };
+ const computedStyleMap = new WeakMap();
+ const formatColorCode = bgcolor => {
+ if (bgcolor.slice(0, 1) === '#')
+ return 'rgb(' +
+ (bgcolor.length === 4 ? bgcolor.slice(1, 4).split('').map(st => st + st) : bgcolor.slice(1, 7).match(/../g))
+ .map(_bgc => parseInt(_bgc, 16)).join(",") + ')';
+ if (bgcolor.slice(0, 3) !== 'rgb') {
+ let _cDiv = document.createElement('div');
+ _cDiv.style.setProperty('color', bgcolor, 'important');
+ if (!_cDiv.style.color) { console.log('color code is invalid'); return };
+ document.body.appendChild(_cDiv);
+ let color = window.getComputedStyle(_cDiv).color;
+ _cDiv.remove();
+ return color;
+ }
+ console.error('bgcolor=' + bgcolor);
+ },
+ setColor = (node, ignoreDCStyle) => {
+ let dCStyle;
+ let nodeStyleText = node.style.cssText;
+ if (!ignoreDCStyle && (dCStyle = node.getAttribute('darkComputedStyle'))) {
+ if (!~nodeStyleText.indexOf(dCStyle)) node.style.cssText = dCStyle;
+ return;
+ };
+ if (node.getAttribute('darkBeforeStyle') === null) node.setAttribute('darkBeforeStyle', nodeStyleText);
+ let CS_C, CS_BC, CS_BI, CS;
+ if (computedStyleMap.has(node)) {
+ let sCS = computedStyleMap.get(node);
+ CS_C = sCS.C;
+ CS_BC = sCS.BC;
+ CS_BI = sCS.BI;
+ CS = sCS.CS;
+ } else {
+ CS = window.getComputedStyle(node);
+ CS_C = CS.color;
+ CS_BC = CS.backgroundColor;
+ CS_BI = CS.backgroundImage;
+ };
+ if (!CS_C) return;
+ const [r, g, b] = CS_C.match(/\d+/g).slice(0, 3).map(Number);
+ const s = 255 - Math.max(r, g, b);
+ //color
+ node.style.setProperty('color', `rgb(${r+s},${g+s},${b+s})`, node.tagName === 'A' ? '' : 'important');
+ //backgroundColor
+ if (CS_BC !== 'rgba(0, 0, 0, 0)') {
+ if (CS_BC.slice(3, 4) !== 'a') {
+ node.style.setProperty('background-color', bgcolor, 'important');
+ } else {
+ if (bgcolor.slice(0, 3) !== 'rgb') {
+ bgcolor = formatColorCode(bgcolor);
+ }
+ node.style.setProperty('background-color', bgcolor.replace('rgb', 'rgba').replace(')', ',') +
+ CS_BC.replace(/[^,]*,[^,]+,[^,]+,[^,\d]*(\d?.?\d+)/, '$1'), 'important');
+ }
+ }
+ //backgroundImage gradient
+ if (CS_BI.indexOf('gradient') !== -1)
+ node.style.backgroundImage = CS_BI
+ .replace(/[^,]*gradient\((?:[^\(\)]*\((?:[^\(\)]*\([^\(\)]*\))*[^\(\)]*[^\(\)]*\))*[^\(\)]*\),?/gi, '') || 'none';
+
+ node.setAttribute('darkComputedStyle', node.style.cssText.split(';').filter(cssplo => {
+ switch (cssplo.split(':')[0].replace(' ', '')) {
+ case 'background-color':
+ case 'color':
+ return true;
+ default:
+ return false;
+ }
+ }).join(';'));
},
- set_color_all = () => {
- set_color(document.body.querySelectorAll('*'));
- set_color([document.body, document.body.parentNode]);
- document.body.querySelectorAll('iframe[src]').forEach(iframe => {
- if (!iframe.src) return;
- if ((new URL(iframe.src, location.href)).origin === location.origin || iframe.src === 'about:blank') {
- set_color(iframe.contentDocument.body.querySelectorAll('*'));
- const if_dc_b = iframe.contentDocument.body || console.error(iframe);
- if_dc_b.style.backgroundColor = '#000';
+ allElm = () => {
+ let all = [document.body, document.body.parentNode, ...document.body.querySelectorAll('*')];
+ document.body.querySelectorAll('iframe').forEach(iframe => {
+ if ((new URL(iframe.src, location.href)).origin === location.origin || !iframe.src || iframe.src === 'about:blank') {
+ all.push(...[
+ ...iframe.contentDocument.body.querySelectorAll('*'),
+ iframe.contentDocument.body
+ ]);
};
});
- document.body.style.setProperty('background-color', '#000', 'important');
+ return all;
+ },
+ setColorAll = () => {
+ allElm().forEach(setColor);
+ document.body.style.setProperty('background-color', bgcolor, 'important');
+ document.body.querySelectorAll('A').forEach(node => {
+ if (getComputedStyle(node).color !== node.style.color) node.style.setProperty('color', node.style.color, 'important');
+ });
},
main = () => {
- let dark_visited_style;
- if (dark_visited_style = document.body.querySelector('.dark_visited_style')) { //even times
- document.querySelectorAll('*').forEach(node => {
+ let darkVisitedStyle = document.body.querySelector('.darkVisitedStyle');
+ if (darkVisitedStyle) { //even times
+ allElm().forEach(node => {
if (!node.style) return;
- const style = node.getAttribute('dark_before_style');
+ const style = node.getAttribute('darkBeforeStyle');
if (style === null) return;
node.style.cssText = style;
});
- dark_visited_style.remove();
+ darkVisitedStyle.remove();
console.log('1');
} else { //odd times
- const observer = new MutationObserver((maa) => {
- if (!document.body.querySelector('.dark_visited_style')) {
+ allElm().forEach(node => {
+ const CS = window.getComputedStyle(node);
+ computedStyleMap.set(node, {
+ C: CS.color,
+ BC: CS.backgroundColor,
+ BI: CS.backgroundImage,
+ CS: CS
+ });
+ });
+ let elmsSetedStyle = [];
+ const observer = new MutationObserver(maa => {
+ if (!document.body.querySelector('.darkVisitedStyle')) {
observer.disconnect();
+ return;
};
maa.forEach(ma => {
- set_color(ma.addedNodes);
- ma.addedNodes.forEach(added_node => {
- if (added_node.nodeName === '#text') return;
- set_color(added_node.querySelectorAll('*'));
- });
+ if (ma.type === 'childList') {
+ ma.addedNodes.forEach(addedNode => {
+ if (addedNode.nodeType !== 1) return;
+ [addedNode, ...addedNode.querySelectorAll('*')].forEach(setColor);
+ });
+ } else {
+ const darkComputedStyle = ma.target.getAttribute('darkComputedStyle') || '';
+
+ if (~ma.target.style.cssText.indexOf(darkComputedStyle)) {
+ console.log('return cssText===DCS');
+ return
+ };
+ let DCStyleOb = {};
+ for (const styleProp of darkComputedStyle.split(';')) {
+ const s = styleProp.split(':');
+ DCStyleOb[s[0].replace(' ', '')] = s[1].replace('!important', '').replace(/ /g, '');
+ };
+ if ((!DCStyleOb['color'] || DCStyleOb['color'] === ma.target.style.color.replace(/ /g, '')) &&
+ (!DCStyleOb['background-color'] || DCStyleOb['background-color'] === ma.target.style.backgroundColor.replace(/ /g, ''))) {
+ console.log('return DCSColor===color&&DCSBackgroundColor===backgroundcolor');
+ return
+ };
+
+ let ind;
+ if ((ind = elmsSetedStyle.map(e => e.elm).indexOf(ma.target)) >= 0) {
+ if (++elmsSetedStyle[ind].num >= 10) {
+ console.log({
+ 0: 'avoided infinite loop =>',
+ num: elmsSetedStyle[ind].num,
+ color: ma.target.style.color.replace(/ /g, ''),
+ bgcolor: ma.target.style.backgroundColor.replace(/ /g, '')
+ });
+ return;
+ }
+ } else elmsSetedStyle.push({
+ elm: ma.target,
+ num: 0
+ });
+ setColor(ma.target, true);
+ }
});
- console.log('reseted');
+ console.log('Mutation');
});
- let resize_timer;
- const resize_listener = () => {
+ let resizeTimer;
+ const resizeListener = () => {
console.log('resizing');
- if (resize_timer) clearTimeout(resize_timer);
- resize_timer = setTimeout(() => {
- if (document.body.querySelector('.dark_visited_style')) set_color_all();
- else window.removeEventListener('resize', resize_listener);
+ if (resizeTimer) clearTimeout(resizeTimer);
+ resizeTimer = setTimeout(() => {
+ if (document.body.querySelector('.darkVisitedStyle')) setColorAll();
+ else window.removeEventListener('resize', resizeListener);
}, 500);
};
- window.addEventListener('resize', resize_listener);
+ window.addEventListener('resize', resizeListener);
- const dark_style = document.createElement('style');
- dark_style.textContent = `a:visited{color:#b553ff!important}
+ const darkStyle = document.createElement('style');
+ darkStyle.textContent = `a:visited{color:#b553ff!important}
*{text-shadow:none!important}:after,:before{background-color:transparent!important}
::-webkit-scrollbar{overflow:hidden;width:.8rem;background:#000}
::-webkit-scrollbar-thumb{overflow:hidden;border-radius:.4rem;background:#ddd}`;
- dark_style.className = 'dark_visited_style';
- document.body.appendChild(dark_style);
+ darkStyle.className = 'darkVisitedStyle';
+ document.body.appendChild(darkStyle);
- set_color_all();
+ setColorAll();
observer.observe(document.body, {
childList: true,
subtree: true,
+ attributeFilter: ['style'],
+ attributes: true,
});
console.log('0');
};
};
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', main);
else main();
+ time.all = Date.now() - time.all;
+ console.log(time);
})();
/*
* @title darkmode
* @description サイトの背景色を黒にし、文字色を明るくします。詳しい説明はソースコード内の先頭のコメントを見てください。
* @include http://*
* @include https://*
* @contributor noromanba http://let.hatelabo.jp/noromanba/let/hJmc5cn7v_h5
* @license MIT license https://opensource.org/licenses/MIT
* @javascript_url
*/
// 背景の色を黒にします。ただし、背景が透明の場合は変更しません。
// 文字色の彩度を最大にします。
// 訪問したことのあるリンクの色を紫にします。
// 稀に色を変えられないこともあります。仕様です。
// 画像の色は変わりません。
// 一度実行した状態でもう一度実行すると、もとに戻ります。
// 'use strict'の次の行の、bgcolorに入れる値を変更することで、背景色を変えることができます。
// http://let.hatelabo.jp/nanikamado/let/hLHU5aii3YU5 の改良版です。
(() => {
'use strict';
let bgcolor = '#111';
let time = {
all: Date.now()
};
const computedStyleMap = new WeakMap();
const formatColorCode = bgcolor => {
if (bgcolor.slice(0, 1) === '#')
return 'rgb(' +
(bgcolor.length === 4 ? bgcolor.slice(1, 4).split('').map(st => st + st) : bgcolor.slice(1, 7).match(/../g))
.map(_bgc => parseInt(_bgc, 16)).join(",") + ')';
if (bgcolor.slice(0, 3) !== 'rgb') {
let _cDiv = document.createElement('div');
_cDiv.style.setProperty('color', bgcolor, 'important');
if (!_cDiv.style.color) { console.log('color code is invalid'); return };
document.body.appendChild(_cDiv);
let color = window.getComputedStyle(_cDiv).color;
_cDiv.remove();
return color;
}
console.error('bgcolor=' + bgcolor);
},
setColor = (node, ignoreDCStyle) => {
let dCStyle;
let nodeStyleText = node.style.cssText;
if (!ignoreDCStyle && (dCStyle = node.getAttribute('darkComputedStyle'))) {
if (!~nodeStyleText.indexOf(dCStyle)) node.style.cssText = dCStyle;
return;
};
if (node.getAttribute('darkBeforeStyle') === null) node.setAttribute('darkBeforeStyle', nodeStyleText);
let CS_C, CS_BC, CS_BI, CS;
if (computedStyleMap.has(node)) {
let sCS = computedStyleMap.get(node);
CS_C = sCS.C;
CS_BC = sCS.BC;
CS_BI = sCS.BI;
CS = sCS.CS;
} else {
CS = window.getComputedStyle(node);
CS_C = CS.color;
CS_BC = CS.backgroundColor;
CS_BI = CS.backgroundImage;
};
if (!CS_C) return;
const [r, g, b] = CS_C.match(/\d+/g).slice(0, 3).map(Number);
const s = 255 - Math.max(r, g, b);
//color
node.style.setProperty('color', `rgb(${r+s},${g+s},${b+s})`, node.tagName === 'A' ? '' : 'important');
//backgroundColor
if (CS_BC !== 'rgba(0, 0, 0, 0)') {
if (CS_BC.slice(3, 4) !== 'a') {
node.style.setProperty('background-color', bgcolor, 'important');
} else {
if (bgcolor.slice(0, 3) !== 'rgb') {
bgcolor = formatColorCode(bgcolor);
}
node.style.setProperty('background-color', bgcolor.replace('rgb', 'rgba').replace(')', ',') +
CS_BC.replace(/[^,]*,[^,]+,[^,]+,[^,\d]*(\d?.?\d+)/, '$1'), 'important');
}
}
//backgroundImage gradient
if (CS_BI.indexOf('gradient') !== -1)
node.style.backgroundImage = CS_BI
.replace(/[^,]*gradient\((?:[^\(\)]*\((?:[^\(\)]*\([^\(\)]*\))*[^\(\)]*[^\(\)]*\))*[^\(\)]*\),?/gi, '') || 'none';
node.setAttribute('darkComputedStyle', node.style.cssText.split(';').filter(cssplo => {
switch (cssplo.split(':')[0].replace(' ', '')) {
case 'background-color':
case 'color':
return true;
default:
return false;
}
}).join(';'));
},
allElm = () => {
let all = [document.body, document.body.parentNode, ...document.body.querySelectorAll('*')];
document.body.querySelectorAll('iframe').forEach(iframe => {
if ((new URL(iframe.src, location.href)).origin === location.origin || !iframe.src || iframe.src === 'about:blank') {
all.push(...[
...iframe.contentDocument.body.querySelectorAll('*'),
iframe.contentDocument.body
]);
};
});
return all;
},
setColorAll = () => {
allElm().forEach(setColor);
document.body.style.setProperty('background-color', bgcolor, 'important');
document.body.querySelectorAll('A').forEach(node => {
if (getComputedStyle(node).color !== node.style.color) node.style.setProperty('color', node.style.color, 'important');
});
},
main = () => {
let darkVisitedStyle = document.body.querySelector('.darkVisitedStyle');
if (darkVisitedStyle) { //even times
allElm().forEach(node => {
if (!node.style) return;
const style = node.getAttribute('darkBeforeStyle');
if (style === null) return;
node.style.cssText = style;
});
darkVisitedStyle.remove();
console.log('1');
} else { //odd times
allElm().forEach(node => {
const CS = window.getComputedStyle(node);
computedStyleMap.set(node, {
C: CS.color,
BC: CS.backgroundColor,
BI: CS.backgroundImage,
CS: CS
});
});
let elmsSetedStyle = [];
const observer = new MutationObserver(maa => {
if (!document.body.querySelector('.darkVisitedStyle')) {
observer.disconnect();
return;
};
maa.forEach(ma => {
if (ma.type === 'childList') {
ma.addedNodes.forEach(addedNode => {
if (addedNode.nodeType !== 1) return;
[addedNode, ...addedNode.querySelectorAll('*')].forEach(setColor);
});
} else {
const darkComputedStyle = ma.target.getAttribute('darkComputedStyle') || '';
if (~ma.target.style.cssText.indexOf(darkComputedStyle)) {
console.log('return cssText===DCS');
return
};
let DCStyleOb = {};
for (const styleProp of darkComputedStyle.split(';')) {
const s = styleProp.split(':');
DCStyleOb[s[0].replace(' ', '')] = s[1].replace('!important', '').replace(/ /g, '');
};
if ((!DCStyleOb['color'] || DCStyleOb['color'] === ma.target.style.color.replace(/ /g, '')) &&
(!DCStyleOb['background-color'] || DCStyleOb['background-color'] === ma.target.style.backgroundColor.replace(/ /g, ''))) {
console.log('return DCSColor===color&&DCSBackgroundColor===backgroundcolor');
return
};
let ind;
if ((ind = elmsSetedStyle.map(e => e.elm).indexOf(ma.target)) >= 0) {
if (++elmsSetedStyle[ind].num >= 10) {
console.log({
0: 'avoided infinite loop =>',
num: elmsSetedStyle[ind].num,
color: ma.target.style.color.replace(/ /g, ''),
bgcolor: ma.target.style.backgroundColor.replace(/ /g, '')
});
return;
}
} else elmsSetedStyle.push({
elm: ma.target,
num: 0
});
setColor(ma.target, true);
}
});
console.log('Mutation');
});
let resizeTimer;
const resizeListener = () => {
console.log('resizing');
if (resizeTimer) clearTimeout(resizeTimer);
resizeTimer = setTimeout(() => {
if (document.body.querySelector('.darkVisitedStyle')) setColorAll();
else window.removeEventListener('resize', resizeListener);
}, 500);
};
window.addEventListener('resize', resizeListener);
const darkStyle = document.createElement('style');
darkStyle.textContent = `a:visited{color:#b553ff!important}
*{text-shadow:none!important}:after,:before{background-color:transparent!important}
::-webkit-scrollbar{overflow:hidden;width:.8rem;background:#000}
::-webkit-scrollbar-thumb{overflow:hidden;border-radius:.4rem;background:#ddd}`;
darkStyle.className = 'darkVisitedStyle';
document.body.appendChild(darkStyle);
setColorAll();
observer.observe(document.body, {
childList: true,
subtree: true,
attributeFilter: ['style'],
attributes: true,
});
console.log('0');
};
};
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', main);
else main();
time.all = Date.now() - time.all;
console.log(time);
})();
- Permalink
- このページへの個別リンクです。
- RAW
- 書かれたコードへの直接のリンクです。
- Packed
- 文字列が圧縮された書かれたコードへのリンクです。
- Userscript
- Greasemonkey 等で利用する場合の .user.js へのリンクです。
- Loader
- @require やソースコードが長い場合に多段ロードする Loader コミのコードへのリンクです。
- Metadata
- コード中にコメントで @xxx と書かれたメタデータの JSON です。