简体   繁体   中英

Highlight Deepest Child DOM Element with Div Overlay when Moused Over in Pure Javascript

The short version:

I want to mimic Google Chrome's Inspect Element tool in pure JavaScript.

The long version:

I am working on an extension for Google Chrome. I want to implement something similar to the Inspect Element tool in pure JavaScript (no jQuery or any other libraries/frameworks/dependencies) . If anyone uses AdBlock, I specifically want to mimic the "Block an ad on this page" functionality that allows the user to select an element on the page.

While the extension is active, the user should be able to:

  • Mouse over an element
  • If a 'P' element with a 'CODE' element nested inside is being moused over, the entire 'P' element should have the 'DIV' overlay over it
  • If the mouse moves over the nested 'CODE' element it should re-size the 'DIV' overlay to cover only the deeper nested 'CODE' element
  • When clicked, the underlying element should be stored and the 'DIV' overlay hidden

Why use a 'DIV' overlay instead of just adding a class to the element that adds a border/outline/background color?

The overlay prevents the user from directly interacting with the element. If there is an href/onClick attribute I do not want it to activate when clicked.

Can you just write something that stores all inline 'onclick' attributes, sets them to null/return false; and then restores the original 'onclick' code?

Tried that:

onclicks = document.querySelectorAll('[onclick]');
saved = [];

// disable onclicks
function disableOnclicks() {
    for (var i = 0; i < onclicks.length; i++) {
        saved[i] = onclicks[i].cloneNode();
        onclicks[i].onclick = "return false;";
    };
    console.log("onclicks disabled");
}

// enable onclicks
function enableOnclicks() {
    for (var i = 0; i < onclicks.length; i++) {
        onclicks[i].onclick = saved[i].onclick;
    };
    console.log("onclicks enabled");
}

// if injecting script, make sure all elements have loaded
window.onload = function(){
    disableOnclicks();
    console.log("onclicks disabled");
    enableOnclicks();
    console.log("onclicks enabled");
}

unfortunately, it doesn't handle any invisible event listeners that may be on a given page.

My current code formatted to paste into console:

// CSS to highlight element
var overlayCSS = document.createElement("style");
overlayCSS.type = "text/css";
overlayCSS.innerHTML = "body .highlight-element{z-index:2147483647!important}.highlight-element{background-color:rgba(130,180,230,.4);outline:#0F4D9A solid 1px;box-sizing:border-box;position:absolute;display:none}";
document.body.appendChild(overlayCSS);  // append style tag to body

// create div overlay to highlight elements & prevent onclick events
var overlay = document.createElement("div");
overlay.className = "highlight-element";
document.body.appendChild(overlay);  // append div overlay to body

// check if overlay was appended
if (overlay === document.getElementsByClassName('highlight-element')[0]) {
    console.log("overlay exists");
};

// positions div overlay based on targetRect AKA element.getBoundingClientRect()
function moveOverlay (targetRect) {
    var overlay = document.getElementsByClassName('highlight-element')[0];
    // correct for page scroll
    overlay.style.top = (targetRect.top + window.scrollY) + "px";
    overlay.style.left = targetRect.left + "px";
    overlay.style.height = targetRect.height + "px";
    overlay.style.width = targetRect.width + "px";
    overlay.style.display = "block";
}

// set start time for lastfire
var lastfire = Date.now();

function getInnermostHovered(e) {
    // difference between now and the last event
    var difference = Date.now() - lastfire;
    // console.log(difference);
    
    // delay handling mousemove by some milliseconds
    // making 0 may induce epileptic shock...
    if (difference > 100) {
        // prevent endless highlight loop
        if (e.target.getAttribute('class') == "highlight-element") {
            e.target.style.display = "none";
        }

        // get element under mouse
        var n = document.querySelector(":hover");
        var nn;

        // get deepest child element that has :hover
        while (n) {
            nn = n;
            n = nn.querySelector(":hover");
        }
        console.log("nn: " + nn);

        // get dimensions to pass to div overlay
        var targetRect = nn.getBoundingClientRect();
        // console.log(targetRect.top);
        
        // display overlay div at element position under mouse
        moveOverlay(targetRect);

        // event fired, so overwrite last fire time
        lastfire = Date.now();
    }
}

// onMouseMove get deepest child element under the mouse
document.addEventListener('mousemove', getInnermostHovered, false);

Feel free to test it out on this page (refresh the page to cancel), currently I have the overlay sort-of working by alternating displaying/hiding the overlay 'DIV' on the 'mousemove' event. Ideally I want it to run as smooth Chrome's Inspect Element and give me the element data. Any and all ideas are appreciated, but I must stress that I really want to do this in pure JavaScript.

Solve it with document.elementFromPoint(x,y) .

This code will work pasted in Chrome's console, press ESC to cancel.

var rootNode = document.documentElement;
var currentNode = currentNode || {};
var lastNode = lastNode || {};
var nodeClone = nodeClone || {};
var prevOnClick = prevOnClick || {};

function nodeHandler (e) {

    var x = e.clientX;
    var y = e.clientY;
    // console.log(x+", "+y);

    currentNode = document.elementFromPoint(x, y);

    if (currentNode === rootNode || currentNode === document.body){
        // If current node is HTML or BODY, do nothing
    
    } else {

        if (currentNode === lastNode) {
            // If still on same node, do nothing
            // console.log('Same node');
            
        } else {
            // console.log('Different node');

            // if lastNode has classList, check for onclick attribute and remove highlight-element class
            if (lastNode.classList) {

                // If lastNode had onclick attribute, replace with the untouched value from nodeClone
                if (lastNode.getAttribute("onclick") != null) {
                    prevOnClick = nodeClone.getAttribute("onclick");
                    lastNode.setAttribute("onclick", prevOnClick);
                }

                lastNode.classList.remove('highlight-element');
            }

            // Save currentNode and preserve any inline event (onclick) attributes
            nodeClone = currentNode.cloneNode();

            // if currentNode has onclick attribute, disable it
            if (currentNode.getAttribute("onclick")) {
                currentNode.setAttribute("onclick", "return false;");
            };

            // Add highlight class to currentNode
            currentNode.classList.add('highlight-element');
        }

        // store node
        lastNode = currentNode;
    }
    
}

function clickHandler (e) {
    e.preventDefault();
    e.stopPropagation();
    e.stopImmediatePropagation();

    console.log("Clicked Node:\n");
    console.log(nodeClone);
}

function cancelNodeSelect (e) {
    if (e.keyCode == 27) {
        // if lastNode has classList, check for onclick attribute and remove highlight-element class
        if (lastNode.classList) {

            if (lastNode.getAttribute("onclick") != null) {
                prevOnClick = nodeClone.getAttribute("onclick");
                lastNode.setAttribute("onclick", prevOnClick);
            }

            lastNode.classList.remove('highlight-element');
        }

        // remove event listeners
        document.removeEventListener('click', clickHandler, false);
        document.removeEventListener('mousemove', nodeHandler, false);
        document.removeEventListener('keyup', cancelNodeSelect, false);

        console.log("escape pressed");
    };
}

document.addEventListener('click', clickHandler, false);
document.addEventListener('mousemove', nodeHandler, false);
document.addEventListener('keyup', cancelNodeSelect, false);

And the css:

.highlight-element {
    background-color: rgba(130, 180, 230, 0.4);
    outline: solid 1px #0F4D9A;
    box-sizing: border-box;
}

This answer was posted as edits https://stackoverflow.com/revisions/33050714/2 and https://stackoverflow.com/revisions/33050714/3 to the question Highlight Deepest Child DOM Element with Div Overlay when Moused Over in Pure Javascript by the OP g_let under CC BY-SA 3.0.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM