カーソル位置のElement・Node・文字をハイライト

  • /*
     * @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";
        }
      }
    })();
    
    
  • Permalink
    このページへの個別リンクです。
    RAW
    書かれたコードへの直接のリンクです。
    Packed
    文字列が圧縮された書かれたコードへのリンクです。
    Userscript
    Greasemonkey 等で利用する場合の .user.js へのリンクです。
    Loader
    @require やソースコードが長い場合に多段ロードする Loader コミのコードへのリンクです。
    Metadata
    コード中にコメントで @xxx と書かれたメタデータの JSON です。

History

  1. 2017/03/20 20:37:30 - 2017-03-20
  2. 2017/03/20 20:24:38 - 2017-03-20
  3. 2017/03/20 20:21:37 - 2017-03-20