Front-end Challenge: Prevent Clicks
Md Abu Taher

Md Abu Taher @entrptaher

About: Full Stack Developer, focused on web automation, open source enthusiast and contributor. Passionate about programming, specializing in JavaScript.

Location:
Dhaka, Bangladesh
Joined:
Sep 29, 2017

Front-end Challenge: Prevent Clicks

Publish Date: May 29 '19
43 12

This is a challenge for frontend developers. Test your DOM manipulation and CSS skills.

Create two functions,

addBlocker()

  • Should block all clicks on the page. Nothing should be clickable anymore.
  • Should print the current mouse position on viewport, and the current element under the mouse on click.

removeBlocker()

  • Should remove the blocker created by addBlocker(), everything should be clickable as before adding the blocker.

The two functions should work on any website including, dev.to and producthunt.com

Here is a video preview of what should be done.

Share your code in the comment section or in a github gist.

Good luck!

Comments 12 total

  • Jonathan Silvestri
    Jonathan SilvestriMay 30, 2019

    For adding all the events:

    const addBlocker = (event) => {
      event.preventDefault()
      event.stopPropagation()
      console.log({
        clientX: event.clientX,
        clientY: event.clientY,
        element: event.currentTarget
      })
    }
    
    const allElements = document.getElementsByTagName("*");
    for (let i = 0; i < allElements.length; i++) {
      allElements[i].addEventListener('click', (event) => addBlocker(event))
    }
    

    And for removing the added event:

    // Below: Passing in true for the third parameter causes the event to be captured on the way down. Stopping propagation means that the event never reaches the listeners that are listening for it.
    
    const removeBlocker = () => {
      window.addEventListener('click', (event) => {
        event.stopPropagation()
      }, true)
    }
    
    // Sometime later...
    removeBlocker()
    
    • Md Abu Taher
      Md Abu TaherMay 30, 2019

      Thanks for answering. Did you test this on producthunt?

      Apparently there are sites (ie: producthunt) where this should not work because not every click event is a click event. Sometimes there are mousedown and other events such as virtual dom based events as well.

      • Jonathan Silvestri
        Jonathan SilvestriMay 30, 2019

        Nah, just did a rudimentary test in my console.

        Sounds like there would need to be bindings for each event, with associated un-bindings for each event as well. Probably doesn't change my solution too much, just needs to be applied to more things.

  • Abhishek Ghosh
    Abhishek GhoshMay 30, 2019

    I was tempted to do something like this initially, with event capturing and cancelling:

    const blocker = (e) => {
        console.log({ target: e.target, x: e.clientX, y: e.clientY});
        e.stopImmediatePropagation();
        e.preventDefault();
    };
    
    const addBlocker = () => { document.documentElement.addEventListener('click', blocker, true); }
    const removeBlocker = () => { document.documentElement.removeEventListener('click', blocker, true); }
    

    Which is a bit similar in construct to Jonathan's answer below. But there's some scenarios this will not work, for example, HTML form elements (see comment box on this page) and IFrames (see embed youtube video on this page).

    So, here's a little different approach :)

    The idea is to put a viewport "mask" that captures clicks to everything in view, but to get the element beneath uses the elementFromPoint API by temporarily hiding the mask and re-enabling it again (so as to not capture itself) :)

    const addBlocker = () => {
        const el = document.createElement('div');
        el.id = 'blocker';
        el.style.position = 'fixed';
        el.style.top = 0;
        el.style.bottom = 0;
        el.style.left = 0;
        el.style.right = 0;
        el.style.zIndex = 2147483647;
        document.documentElement.appendChild(el);
    
        el.addEventListener('click', (e) => {
            el.style.display = 'none';
            console.log({ target: document.elementFromPoint(e.clientX, e.clientY), x: e.clientX, y: e.clientY});
            el.style.display = 'block';
        });
    };
    
    const removeBlocker = () => {
        document.documentElement.removeChild(document.getElementById('blocker'));
    };
    
    

    I didn't really put this code in different pages and test though, but it should hopefully work :)

    • Md Abu Taher
      Md Abu TaherMay 30, 2019

      Great answer.

      I have two questions,

      • How did you come up with this idea of putting a viewport "mask"?
      • Approximately how long did it take to find and write the above two solutions?
      • Abhishek Ghosh
        Abhishek GhoshMay 30, 2019

        Thanks!

        Well.. I believe this is textbook clickjacking :P

        I think about 5 minutes of trying out stuff on the dev tools. I didn't actually have to go searching for answer somewhere. I should tell that I was lucky though, to actually detect on this page with the youtube link and input box and that the first method does not work, so I naturally shifted attention trying out the alternate.

        • Md Abu Taher
          Md Abu TaherMay 30, 2019

          5 minutes and textbook clickjacking 👍 .

          Those who searched for blocking events would get "event.preventDefault" no matter how much they want to search.

          Just a little different search term, specially a common term that they never thought of, would result in a obviously common way.

          I will post my solution used in the video later on :D .

          I highly suggest others to try out different solutions other than these two already submitted.

          • Abhishek Ghosh
            Abhishek GhoshMay 30, 2019

            Nice! Eager to see other approaches that you may have had :)

    • Jelena Smiljkovic
      Jelena SmiljkovicMay 31, 2019

      This is such super cool solution!

  • Naimur Rahman
    Naimur RahmanJun 8, 2019

    I just tried on devTools using pointer-events.

    const addBlock = () => document.querySelector('body').style.pointerEvents = "none";
    const removeBlock = () => document.querySelector('body').style.pointerEvents = "auto";
    

    This worked on producthunt too with newly loaded products on scroll.

    • Md Abu Taher
      Md Abu TaherJun 9, 2019

      This is a straightforward solution.

      However, only this will not print the element under the mouse on click.

  • Developer Ruhul
    Developer RuhulJun 26, 2019

    This works ->

    function addBlocker() {
      let html = document.querySelector("html")
      html.style.pointerEvents = "none"
    
      const handleClick = e => {
        html.style.pointerEvents = "auto"
        console.log({
          target: document.elementFromPoint(e.clientX, e.clientY),
          x: e.clientX,
          y: e.clientY,
        })
        html.style.pointerEvents = "none"
      }
    
      html.addEventListener("click", handleClick)
    
      return () => {
        html.style.pointerEvents = "auto"
        html.removeEventListener("click", handleClick)
      }
    }
    
    // also removes the click event listener
    var removeBlocker = addBlocker();
    
    
Add comment