Why does a simple JS autoclicker break the page?

Viewed 291

I am trying to make an autoclicker script that runs while a mouse is held in a left click (e.g. holding down the left click spams clicks), and want it to not just run on a specific element, for example button-2, but wherever the mouse is. I came up with this little script:

var mouseDown = 0;
document.onmouseup = function() {
  --mouseDown;
}

function printMousePos(event) {
    ++mouseDown;
    while(mouseDown){
        document.elementFromPoint(event.clientX, event.clientY).click();
    }
}

document.addEventListener("mousedown", printMousePos);


let clicks = 0;

document.body.onclick = function(){ 
  clicks++; 
  document.body.innerText = clicks; 
 }

However, as shown when you run the snippet, it appears to break the page (make it unresponsive). Why does this happen?

Thanks!

EDIT:

Thanks to @VLAZ's comment, I tried to fix the unescapable while loop by using setInterval, however, it still doesn't seem to work. Why is this happening?

var mouseDown = 0;
let removeMouse = function() {
    console.log(mouseDown)
  --mouseDown;
}

function click(x,y){
    var ev = document.createEvent("MouseEvent");
    var el = document.elementFromPoint(x,y);
    ev.initMouseEvent(
        "click",
        true, true,
        window, null,
        x, y, 0, 0,
        false, false, false, false,
        0, null
    );
    el.dispatchEvent(ev);
}

function printMousePos(event) {
    ++mouseDown;
    setInterval(()=> {
        if(mouseDown){
            console.log(mouseDown)
            click(event.clientX, event.clientY);
        } else {
            clearInterval()
        }
    }, 1)
}

document.addEventListener("mousedown", printMousePos);
document.addEventListener("mouseup", removeMouse);

var count = 0;
document.documentElement.addEventListener("mousedown", function() {
    count++;
    document.body.innerText = count.toString();
})

EDIT:

I managed to piece together a cool little script based off of @Roko's answer, however, it doesn't seem to work on websites like https://clickspeedtest.com/ or https://www.clickspeedtester.com/, or really any website for that matter. Is their a reason for this? Thanks!

Here's the code:

let intervalHandler = null;

const Mouse = {
  mousedown(ev){
    this.isDown = true;
    this.autoClick(ev)
  },
  mouseup(){ 
    this.isDown = false; 
    clearInterval(intervalHandler)
  },
  mouseover(ev) { 
    if(intervalHandler) clearInterval(intervalHandler)
    if (this.isDown) this.autoClick(ev); 
  },
};
Object.defineProperties(Mouse, {
  isDown: {
    value: false,
    writable: true,
  },
  autoClick: {
    value: (ev) => {
      const ELFP = document.elementFromPoint(ev.clientX, ev.clientY);
      intervalHandler = setInterval(() => {
        ELFP.click();
      }, 100)
    }
  }
});
Object.keys(Mouse).forEach(t => document.addEventListener(t, Mouse[t].bind(Mouse)));

// Demo with buttons
document.querySelectorAll("button").forEach(EL => 
  EL.addEventListener("click", () => console.log(EL.id))
);
<button id="a" type="button">BUTTON 1</button>
<button id="b" type="button">BUTTON 2</button>

1 Answers
  • Create a Singleton Mouse Object with default browser listeners iterable Methods for
    "mousedown" "mouseup" "mouseover" Events.
  • Afterwards assign to your Singleton additional non-enumerable properties:
    isDown (Boolean) to handle if the mouse is down or up
    autoClick (Function) that will trigger on the "mouseover" event.
  • Assign your enumerable keys ("EventTypes") method in a forEach manner to the Window.document object

const Mouse = {
  mousedown()   { this.isDown = true; },
  mouseup()     { this.isDown = false; },
  mouseover(ev) { if (this.isDown) this.autoClick(ev); },
};
Object.defineProperties(Mouse, {
  isDown: {
    value: false,
    writable: true,
  },
  autoClick: {
    value: (ev) => {
      const ELFP = document.elementFromPoint(ev.clientX, ev.clientY);
      ELFP.click();
    }
  }
});
Object.keys(Mouse).forEach(t => document.addEventListener(t, Mouse[t].bind(Mouse)));

// DEMO TIME:
document.querySelectorAll("button").forEach(EL => 
  EL.addEventListener("click", () => console.log(EL.id))
);
<button id="a" type="button">BUTTON 1</button>
<button id="b" type="button">BUTTON 2</button>

To continuously spam the "click" event (as fast as possible) you could do like:

const spamClick = (ev) => {
  const ms = 1; // Repeat every 1 millisecond
  const EL = ev.currentTarget;
  ev.type === "mousedown" ? EL._itv = setInterval(() => EL.click(), ms) : clearInterval(EL._itv) ;
};
["mousedown", "mouseup", "mouseleave"].forEach(evtName => 
  document.querySelectorAll("button").forEach(EL =>
    EL.addEventListener(evtName, spamClick)
  )
);

// FOR THIS DEMO ONLY:
document.querySelectorAll("button").forEach(EL =>
  EL.addEventListener("click", () => console.log(EL.id))
);
<button id="a" type="button">BUTTON 1</button>
<button id="b" type="button">BUTTON 2</button>

Additional read:

Related