How do I simulate a mouseover in pure JavaScript that activates the CSS ":hover"?
Asked Answered
C

7

122

I've been trying to find code to simulate mouseover in Chrome but even though the "mouseover" listener gets fired, the CSS "hover" declaration is never set!

I tried also doing:

//Called within mouseover listener
theElement.classList.add("hover");

But nothing seems to change the element to what is declared in its hover declaration.

Is this possible?

Constantin answered 21/6, 2013 at 2:8 Comment(6)
@PSL I think what he wants to do is force :hover state on an element.Jeannajeanne
@BenjaminGruenbaum Yup you are right. I misunderstood.Incestuous
This isn't a duplicate. The other question is about jQuery. This question is about pure JS.Cleghorn
@Monk The OQ included the jQuery tag. This is a duplicate.Norwood
@Norwood The question (and its answer) is about pure JS. Do you not think it's more likely they mistagged it.Cleghorn
@Chuck, This is a duplicate. See https://mcmap.net/q/182537/-how-to-trigger-css-quot-hover-state-quot-using-javascript-duplicate/632951 which already has 4k views.Superabound
J
137

You can't. It's not a trusted event.

Events that are generated by the user agent, either as a result of user interaction, or as a direct result of changes to the DOM, are trusted by the user agent with privileges that are not afforded to events generated by script through the DocumentEvent.createEvent("Event") method, modified using the Event.initEvent() method, or dispatched via the EventTarget.dispatchEvent() method. The isTrusted attribute of trusted events has a value of true, while untrusted events have a isTrusted attribute value of false.

Most untrusted events should not trigger default actions, with the exception of click or DOMActivate events.

You have to add a class and add/remove that on the mouseover/mouseout events manually.

Jeannajeanne answered 21/6, 2013 at 2:18 Comment(7)
@Tim, It doesn't actually answer why. It just changes the question.Superabound
@Superabound it's not a trusted event because it was not initiated by the user. It says so right there in the quote above in my answer. It's literally what the answer starts with.Jeannajeanne
@BenjaminGruenbaum, I'm quoting "with the exception of click or DOMActivate events". What is so special about "hover" that it isn't in this exception list? Why are we allowed to call "focus" but not "hover"? These are the questions we have to answer, answering anything else is just changing the question.Superabound
I would assume that hover (and combined with click or mouse up & down for drag & drop), could behave or appear like hijacking a user's system (if you ignore automation cases), so it is not ideal to support. Click I can't say, but focus would allow auto-focus to a target form field or element the site designer wants the user to focus on say for example a text field with missing or incorrect data (e.g. form field validation checks). But that's just my assumptions.Langlois
@Superabound it was in the right place at the right time - the events that are trusted are only trusted because of historical reasons to not break compatibility across websites. They simply did not allow what they could get away with. Hindsight is always 20/20.Jeannajeanne
@BenjaminGruenbaum, So you are saying that "focus" is actually a security loophole and should not be trusted?Superabound
Yes, as absurd as it sounds you can make it appear that focus was caused by a user interaction where it was not. In practice - browser APIs will gladly disobey the spec and ignore focus and click as untrusted events (at least Chrome). Try for example simulating a click on an extension install button for a chrome extension and see what happens :)Jeannajeanne
L
89

You can simulate the mouseover event like this:

HTML

<div id="name">My Name</div>

JavaScript

var element = document.getElementById('name');
element.addEventListener('mouseover', function() {
  console.log('Event triggered');
});

var event = new MouseEvent('mouseover', {
  'view': window,
  'bubbles': true,
  'cancelable': true
});

element.dispatchEvent(event);
Latecomer answered 7/5, 2018 at 3:34 Comment(1)
Thanks, All I needed was bubbles! Right-click an element in the developer console Inspector and do "Use in console" (Firefox) or "Store as global variable" (Chrome). Then you can, e.g. temp0.dispatchEvent(new MouseEvent('mouseover', {bubbles: true})) Very useful for styling tooltips or other content that only appears on mouse-over.Cellulosic
S
21

Background

I stumbled upon this question while trying to write automated tests, to verify, that a certain set of elements on a given page all receive have the some set of css properties set by the css for on hover events.

While the above answer perfectly explains, why it is not possible to simply trigger the hover event by JS and then probe some css value of interest, it does answer the initial question "How do I simulate a mouseover in pure JavaScript that activates the CSS “:hover”?" only partly.

Disclaimer

This is not a performant solution. We use it only for automated testing, where performance is not a concern.

Solution

simulateCssEvent = function(type){
    var id = 'simulatedStyle';

    var generateEvent = function(selector){
        var style = "";
        for (var i in document.styleSheets) {
            var rules = document.styleSheets[i].cssRules;
            for (var r in rules) {
                if(rules[r].cssText && rules[r].selectorText){
                    if(rules[r].selectorText.indexOf(selector) > -1){
                        var regex = new RegExp(selector,"g")
                        var text = rules[r].cssText.replace(regex,"");
                        style += text+"\n";
                    }
                }
            }
        }
        $("head").append("<style id="+id+">"+style+"</style>");
    };

    var stopEvent = function(){
        $("#"+id).remove();
    };

    switch(type) {
        case "hover":
            return generateEvent(":hover");
        case "stop":
            return stopEvent();
    }
}

Explanation

generateEvent reads all css files, , replaces :hover with an empty string and applies it. This has the effect, that all :hover styles are applied. Now one can probe for a howered style and set back to initial state by stopping the Simulation.

Why do we apply the hover effect for the whole document and not just for the element of interest by getting the from the sheets and then perform a element.css(...)?

Done as that, the style would be applied inline, this would override other styles, which might not be overriden by the original css hover-style.

How would I now simulate the hover for a single element?

This is not performant, so better don't. If you must, you could check with the element.is(selectorOfInterest) if the style applies for your element and only use those styles.

Example

In Jasmine you can e.g. now perform:

describe("Simulate CSS Event", function() {
    it("Simulate Link Hover", function () {
      expect($("a").css("text-decoration")).toBe("none");
      simulateCssEvent('hover');
      expect($("a").css("text-decoration")).toBe("underline");
      simulateCssEvent('stop');
      expect($("a").css("text-decoration")).toBe("none");
    });
});
Stairwell answered 19/5, 2016 at 8:8 Comment(2)
This answer is great! Unfortunately, it fails on recent versions of Chrome on pages with stylesheets served from other hosts due to Chrome limiting access to cross-origin requests. See my answer for a solution that supports this :)Sutphin
I would have never came up with this solution.. thanks!Lipo
H
7

What I usually do in this case is adding a class using javascript.. and attaching the same CSS as the :hover to this class

Try using

theElement.addEventListener('onmouseover', 
    function(){ theElement.className += ' hovered' });

Or for older browsers:

theElement.onmouseover = function(){theElement.className += ' hovered'};

you will of course have to use onmouseout to remove the "hovered" class when you leave the element...

Haug answered 21/6, 2013 at 2:9 Comment(5)
This will not do what the OP asks, though it's probably more-or-less along the right lines. It would be better to use modern event handler attachment techniques.Josejosee
sorry, misunderstood questionHaug
@Josejosee the question asks for pure javascript.. how can the events be attached otherwise?Haug
Not good enough? It's "pure" JavaScript, and it in fact will attach a function as a handler for an event. In Internet Explorer, the (almost) equivalent attachEvent() would be used.Josejosee
Worth mentioning. IE9 and 10 support addEventListener and it's the best way to attach events, shimming attachEvent is only needed in IE8 (and below). Yotam, if he adds another handler to onmouseover by setting the attribute directly (rather then adding the event listener) it will override the event currently set. See this question on the difference.Jeannajeanne
S
7

You can use pseudo:styler, a library which can apply CSS pseudo-classes to elements.

(async () => {
  let styler = new PseudoStyler();
  await styler.loadDocumentStyles();
  document.getElementById('button').addEventListener('click', () => {
    const element = document.getElementById('test')
    styler.toggleStyle(element, ':hover');
  })
})();

Disclaimer: I am a coauthor of this library. We designed it to additionally support cross-origin stylesheets, specifically for use in Chrome extensions where you likely lack control over the CSS rules of the page.

Sutphin answered 16/1, 2019 at 8:26 Comment(1)
This seems to actually work fairly nice, even when combined with window.getComputedStyle(<youElement>) after setting the pseudo style. Thanks!Crutcher
G
4

I'm assuming you want to inspect the CSS after dom manipulation, but as soon as you move your mouse back to the devtools, the event isn't active on that html element anymore. You probably would like to have something like the :hover option in devtools for your javascript events. That doesn't exist, but you can simulate it.

  1. Open your devtools and click in it to make it active.
  2. Trigger the event on the element you're interested in.
  3. Without moving the mouse, open the devtools command panel with ctrl + shift + p and select 'disable javascript' with your keyboard.

Since javascript is disabled, it doesn't get the chance to modify the element(s) back again. You can go to the devtools and inspect the css and html as if you were hovering, clicking or doing something else with it. After you're done, go to the command panel again and select 'enable javascript'.

Gush answered 30/4, 2020 at 8:16 Comment(1)
Neat technique, but unless I misunderstood your intent, this does already exist in devtools! In both Chrome/Chromium and Firefox's devtools, there's a :hov button at the top of the style inspector (next to the "Filter" input box). Clicking :hov expands a series of checkboxes, each labeled with a pseudoclass: :active, :focus, :focus-within, :hover, and :visited. And checking any one of these boxes activates its respective pseudoclass on the element being inspected.Aziza
E
0

I would like to mention a different approach to this question using css only but I am assuming here that :hover will be triggered in circumstance of a change in one of the parent tags as follows:

  function btn_activated(){
      const button = document.querySelector('button.btn');
      if(button.classList.contains('active')){
          button.classList.remove('active');
      }else{
           button.classList.add('active');
      }
  }
    .btn{
        width: 100px;
        height: 30px;
    }
    .btn:hover > span.visually-hidden,
    .active> span.visually-hidden{
        display: inline;
    }
    .visually-hidden{
        display: none;
    }
<button class="btn" onClick="btn_activated()">
    <span class="visually-hidden">
       hovered
    </span>
</button>

It is possible to apply this concept to any nested tags but one of the parent tags should have its classList changed at least.

Entoderm answered 22/5, 2023 at 20:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.