80

I have seen this jQuery syntax:

if($(element).is(':hover')) { do something}

Since I am not using jQuery, I am looking for the best way to do this in pure javascript.

I know I could keep a global variable and set/unset it using mouseover and mouseout, but I'm wondering if there is some way to inspect the element's native properties via the DOM instead? Maybe something like this:

if(element.style.className.hovered === true) {do something}

Also, it must be cross browser compatible.

Neithan Max
  • 11,004
  • 5
  • 40
  • 58
mulllhausen
  • 4,225
  • 7
  • 49
  • 71
  • I've spent 20 minutes looking for a way to find this state. I suspect you might just have to set a propery or data on element as hovered or not on mouseover and mouseout, which is most likely the first thing you and any of us reading this has thought. – Popnoodles Feb 10 '13 at 06:11
  • Check the [jQuery source code](http://code.jquery.com/jquery-latest.js). I think they are using `mouseover` and `mouseout` for hover. – Antony Feb 10 '13 at 06:13
  • @Antony: I don't see it being referred to anywhere else, so it is likely that `fn.hover` has nothing to do with `:hover` selector. I may be wrong, though. – nhahtdh Feb 10 '13 at 06:18
  • I don't think jquery supports that in the first place: `$(element).is(':hover') // => Error: Syntax error, unrecognized expression: unsupported pseudo: hover` [jsfiddle](http://jsfiddle.net/rwaldin/Msw26/) – Ray Waldin Feb 10 '13 at 06:33
  • One **crappy** solution: http://jsfiddle.net/czpkz/1/ – nhahtdh Feb 10 '13 at 07:00

6 Answers6

98

Simply using element.matches(':hover') seems to work well for me, you can use a comprehensive polyfill for older browsers too: https://developer.mozilla.org/en-US/docs/Web/API/Element/matches

Hugheth
  • 1,802
  • 17
  • 14
  • 2
    This doesn't seem to work for me on Chromium or Firefox unless I do `[element]:hover` or `#[id]:hover`. `*:hover` crashes, and `body *:hover`, `:root *:hover`, `body :hover` and `:root :hover` don't work either. – Will Chen Jul 01 '20 at 23:47
69

You can use querySelector for IE>=8:

const isHover = e => e.parentElement.querySelector(':hover') === e;    

const myDiv = document.getElementById('mydiv');
document.addEventListener('mousemove', function checkHover() {
  const hovered = isHover(myDiv);
  if (hovered !== checkHover.hovered) {
    console.log(hovered ? 'hovered' : 'not hovered');
    checkHover.hovered = hovered;
  }
});
.whyToCheckMe {position: absolute;left: 100px;top: 50px;}
<div id="mydiv">HoverMe
  <div class="whyToCheckMe">Do I need to be checked too?</div>
</div>

to fallback I think it is ok @Kolink answer.

Nic Scozzaro
  • 6,651
  • 3
  • 42
  • 46
zb'
  • 8,071
  • 4
  • 41
  • 68
  • 2
    I would extend this to `!!(e.querySelector(":hover") || e.parentNode.querySelector(":hover") === e)` – megawac Mar 08 '14 at 20:53
  • 1
    @megawac just made anexample why I wouldn't – zb' May 09 '17 at 15:21
  • 1
    @zb Hi I know this is an old post, but if you are still there, do you mind explaining me your code? specially those lines **document.addEventListener('mousemove', function checkHover() {** and **console.log(hovered ? 'hovered' : 'not hovered');** never seen this before dont even know how it works –  Feb 19 '18 at 15:47
  • @Luke, last is ternar condition, google it. First is just event, to listen, (trigger to check the hover, it could be setTimeout, but mousemove looks more related) – zb' Feb 21 '18 at 15:17
  • @user8782879 managed to modify it to suit a particular use case .ie. marking a chat message as read **markMessagesAsReadIfUserHovers(): void { const myDiv = document.getElementById('lastChatRoomMessage'); myDiv?.addEventListener('mousemove', () => { // this.markAsRead.emit(this.service.payloadToMarkChatAsRead); }); }** – D. Sackey Nov 28 '22 at 23:16
  • @user8782879 not looks like javascript... – zb' Dec 27 '22 at 10:54
8

First you need to keep track of which elements are being hovered on. Here's one way of doing it:

(function() {
    var matchfunc = null, prefixes = ["","ms","moz","webkit","o"], i, m;
    for(i=0; i<prefixes.length; i++) {
        m = prefixes[i]+(prefixes[i] ? "Matches" : "matches");
        if( document.documentElement[m]) {matchfunc = m; break;}
        m += "Selector";
        if( document.documentElement[m]) {matchfunc = m; break;}
    }
    if( matchfunc) window.isHover = function(elem) {return elem[matchfunc](":hover");};
    else {
        window.onmouseover = function(e) {
            e = e || window.event;
            var t = e.srcElement || e.target;
            while(t) {
                t.hovering = true;
                t = t.parentNode;
            }
        };
        window.onmouseout = function(e) {
            e = e || window.event;
            var t = e.srcElement || e.target;
            while(t) {
                t.hovering = false;
                t = t.parentNode;
            }
        };
        window.isHover = function(elem) {return elem.hovering;};
   }
})();
Niet the Dark Absol
  • 320,036
  • 81
  • 464
  • 592
  • 1
    Edited to make use of the `matches` native function (including vendor prefixes) – Niet the Dark Absol Feb 10 '13 at 06:24
  • 1
    Why is this the correct answer? The question requests not using mouseover? – Aaron May 01 '13 at 17:42
  • 1
    This answer uses the native functions where available, falling back to `mouseover/mouseout` to keep the "browser-compatible" part of the question. – Niet the Dark Absol May 01 '13 at 17:45
  • @Niet Hi I cant get this code to work, its quite complicated im not sure what its doing. could you help me understand it? –  Feb 20 '18 at 13:31
  • 1
    I wondered why this code got 8 upvotes. It is overcomplicated and does not solve the problem. Just trying to figure out what you are trying to do with this took me 30 minutes. It is unclear and confusing. – Miloš Leng May 07 '21 at 16:14
  • @MilošLeng Check the date. This answer is from 2013. Back then, `querySelector` was not ubiquitous and so a lot of extra code had to be written to do things that can be done in a single line now. These days you can just have `const isHover = e=>e.matches(":hover")` and call it a day. – Niet the Dark Absol May 08 '21 at 02:43
7

it occurred to me that one way to check if an element is being hovered over is to set an unused property in css :hover and then check if that property exists in javascript. its not a proper solution to the problem since it is not making use of a dom-native hover property, but it is the closest and most minimal solution i can think of.

<html>
    <head>
        <style type="text/css">
#hover_el
{   
    border: 0px solid blue;
    height: 100px;
    width: 100px;
    background-color: blue;
}   
#hover_el:hover
{   
    border: 0px dashed blue;
}
        </style>
        <script type='text/javascript'>
window.onload = function() {check_for_hover()};
function check_for_hover() {
    var hover_element = document.getElementById('hover_el');
    var hover_status = (getStyle(hover_element, 'border-style') === 'dashed') ? true : false;
    document.getElementById('display').innerHTML = 'you are' + (hover_status ? '' : ' not') + ' hovering';
    setTimeout(check_for_hover, 1000);
};
function getStyle(oElm, strCssRule) {
    var strValue = "";
    if(document.defaultView && document.defaultView.getComputedStyle) {
        strValue = document.defaultView.getComputedStyle(oElm, "").getPropertyValue(strCssRule);
    }
    else if(oElm.currentStyle) {
        strCssRule = strCssRule.replace(/\-(\w)/g, function (strMatch, p1) {
            return p1.toUpperCase();
        });
        strValue = oElm.currentStyle[strCssRule];
    }
    return strValue;
};
        </script>
    </head>
    <body>
        <div id='hover_el'>hover here</div>
        <div id='display'></div>
    </body>
</html>

(function getStyle thanks to JavaScript get Styles)

if anyone can think of a better css property to use as a flag than solid/dashed please let me know. preferably the property would be one which is rarely used and cannot be inherited.

EDIT: CSS variable are probably better to use to check this. E.g.

const fps = 60;

setInterval(function() {
  if(getComputedStyle(document.getElementById('my-div')).getPropertyValue('--hovered') == 1) {
    document.getElementById('result').innerHTML = 'Yes';
  } else {
    document.getElementById('result').innerHTML = 'No';
  };
}, 1000 / fps);
#my-div {
  --hovered:0;
  color: black;
}

#my-div:hover {
  --hovered:1;
  color: red;
}
<!DOCTYPE html>
<html>
  <head>
    <title>Detect if div is hovered with JS, using CSS variables</title>
  </head>
  <body>
    <div id="my-div">Am I hovered?</div>
    <div id="result"></div>
  </body>
</html>
mulllhausen
  • 4,225
  • 7
  • 49
  • 71
  • 1
    It's a bit hacky, but I definitely like this way most, as being pretty logical, simplisic and free of compability worries. – Max Yari Jan 13 '15 at 20:47
1

You can use an if statement with a querySelector. If you add ":hover" to the end of the selector, it will only return the element if it is being hovered. This means you can test if it returns null. It is like the element.matches(":hover) solution above, but I have had more success with this version.

Here is an example:

if (document.querySelector("body > p:hover") != null) {
    console.log("hovered");
}

You can put it in an interval to run the code every time you hover:

setInterval(() => {
    if (document.querySelector("body > p:hover") != null) {
        console.log("hovered");
    }
}, 10);
Tyler2P
  • 2,324
  • 26
  • 22
  • 31
EchoGamer
  • 41
  • 2
0
var mytext = document.getElementById("myId");
if(myval.matches(":hover")) {
    //has hover
} else {
   //no hover
}

You can also use if(myval.parentNode.matches(":hover")) { }

Dexter
  • 74
  • 8