/*
* @title カーソル位置のElement・Node・文字をハイライト
* @description Range.prototype.getClientRectsで表示範囲が取れることを利用したデモ
* @include http://*
* @include https://*
* @license MIT License
* @javascript_url
*/
/*
https://bl.ocks.org/unarist/2966e034b3f2995e97cd58d479a1fff1 をどこでも動かせるようにしたやつ。
あまり手直ししてないのでコードはやっつけ。そのうち整理するかも。
*/
// Note: Hatena::Let のpackerは Template literals に対応していないので空白が圧縮されることに注意
(function(){
const MASK = "range-visualizer-mask";
const MASK_ELEM = `${MASK}-elem`;
const MASK_NODE = `${MASK}-node`;
const MASK_CHAR = `${MASK}-char`;
document.head.appendChild(Object.assign(document.createElement("style"), {
textContent: `
.${MASK} { position: absolute; z-index: 9999; pointer-events: none}
.${MASK_ELEM} { opacity: 0.1; background: blue; }
.${MASK_NODE} { opacity: 0.3; background: green; }
.${MASK_CHAR} { opacity: 0.5; background: red; }
`}));
[MASK_ELEM, MASK_NODE, MASK_CHAR]
.map(type => Object.assign(document.createElement("div"), { className: MASK + " " + type }))
.forEach(elem => document.documentElement.appendChild(elem));
document.addEventListener("mousemove", function(e){
hideMask(MASK);
var elemRange = getElementRange(e.target);
showMask(MASK_ELEM, [elemRange.getBoundingClientRect()]);
var nodeRange = findNodeRange(elemRange, e.clientX, e.clientY);
showMask(MASK_NODE, nodeRange.getClientRects());
if (nodeRange.startContainer.nodeType === Node.TEXT_NODE) {
var charRange = findCharRange(nodeRange, e.clientX, e.clientY);
showMask(MASK_CHAR, charRange.getClientRects());
}
}, true);
function getElementRange(element) {
var range = document.createRange();
range.selectNodeContents(element);
return range;
}
function findNodeRange(elemRange, clientX, clientY) {
var range = elemRange.cloneRange();
// childNodesから探す
for (var node of elemRange.startContainer.childNodes) {
if (node.nodeType !== Node.TEXT_NODE) continue;
range.selectNodeContents(node);
if (pointInRange(range, clientX, clientY))
break;
}
if (!pointInRange(range, clientX, clientY))
range.collapse();
return range;
}
function findCharRange(nodeRange, clientX, clientY) {
range = nodeRange.cloneRange();
var lc = 0;
while (range.endOffset - range.startOffset > 1) {
var s = range.startOffset,
e = range.endOffset,
mid = s + (e - s) / 2;
range.setEnd(range.startContainer, mid);
if (!pointInRange(range, clientX, clientY)) {
range.setStart(range.startContainer, mid);
range.setEnd(range.startContainer, e);
}
// 16回もやれば2^16=65536文字までいけるはずなので
// 超えていたら中止
if (++lc >= 16) break;
}
return range;
}
function pointInRange(range, x, y) {
for (var rect of range.getClientRects())
if (rect.left <= x && x <= rect.right &&
rect.top <= y && y <= rect.bottom)
return true;
return false;
}
function hideMask(className) {
var masks = document.getElementsByClassName(className);
for (var mask of masks)
mask.style.display = "none";
}
function showMask(className, clientRects) {
var masks = document.getElementsByClassName(className);
var maskNum = 0;
for (var rect of clientRects) {
var mask = masks[maskNum++];
if (mask === undefined) {
mask = masks[0].cloneNode();
masks[0].parentElement.appendChild(mask);
}
mask.style.display = "block";
mask.style.left = (window.scrollX + rect.left) + "px";
mask.style.top = (window.scrollY + rect.top) + "px";
mask.style.width = rect.width + "px";
mask.style.height = rect.height + "px";
}
}
})();