pure javascript to check if something has hover (without setting on mouseover/out) - javascript

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.

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

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.

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;};
}
})();

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>

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);

Related

Javascript display div only once

So I have a calculator with an error message that displays, if they press the "calculate" button if the input is NaN. The error message keeps getting created everytime the user presses calculate. How do i make it so it only shows even after pressing "calculate" multiple times?
function displayErr() {
const formBox = document.querySelector("form");
const errorBox = document.createElement("div");
errorBox.className = "errorBox";
const errorText = document.createTextNode("Those are not numbers!");
errorBox.appendChild(errorText);
formBox.appendChild(errorBox);
}
if ((isNaN(billInput)) || (isNaN(peopleAmount)) || (billInput === "") || (peopleAmount === "")) {
displayErr();
}
The most straightforward way is to check if the element already exists.
function displayErr() {
// Get error element
const errorElement = document.getElementsByClassName('errorBox');
// If it already exists
if (errorElement && errorElement.length > 0) {
// Dont add another one
return;
}
// Add new errorBox
const formBox = document.querySelector("form");
const errorBox = document.createElement("div");
errorBox.className = "errorBox";
const errorText = document.createTextNode("Those are not numbers!");
errorBox.appendChild(errorText);
formBox.appendChild(errorBox);
}
Another option would to be using css classes to 'hide' the element;
Always render the element, but hide it with display: none
In the displayErr(), make the element visible with something like document.getElementsByClassName('errorBox')[0].style.display = block;
a better way of doing this is
to show and hide the element using CSS classes
create the element and hide it using
display: none;
and show it by adding a class to the element
display: block;
const element = document.getElementById("myDIV");
const button = document.getElementById("btn");
button.addEventListener("click", () => element.classList.toggle("show"));
#myDIV {
display: none;
}
.show {
display: block !important;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<button id="btn">Try it</button>
<div id="myDIV">
This is a DIV element.
</div>
</body>
</html>
For what it's worth, here is a pure JavaScript example of a show/hide interpretation:
function CheckInput() {
const billInput = document.getElementById("b").value;
const peopleAmount = document.getElementById("p").value;
if ((isNaN(billInput)) || (isNaN(peopleAmount)) || (billInput === "") || (peopleAmount === "")) {
showErr();
}
else{
hideErr();
}
}
function hideErr(){
console.log("hide");
const el = document.getElementById("error");
el.style.display = "none";
}
function showErr(){
console.log("show");
const el = document.getElementById("error");
el.style.display = "block";
el.innerHTML = "Hey sorry wrong input";
}
window.onload = function() {
hideErr();
}
You can see the HTML and try the code here: https://jsfiddle.net/0mrx5ev7/
You can pass a parameter to your displayErr function, then use it to set the hidden HTML attribute and textContent of a single target div, identified by its HTML id.
This way, the functionality becomes reusable, and you can set/unset the error message whenever you need.
const input = document.querySelector('#input')
const errDisplay = document.querySelector('#err-display')
function displayErr(msg) {
errDisplay.textContent = msg || ''
errDisplay.hidden = msg ? null : 'hidden'
}
input.addEventListener('input', () => {
displayErr(isNaN(input.value) ? "Not a number" : null)
})
#err-display {
font-family: sans-serif;
background: red;
color: white;
margin: .5em 0;
padding: .5em;
}
<input id='input' placeholder='Start typing'>
<div id='err-display' hidden></div>
try to use a counter. like if int i == 0 --> do the function. i would do so
int i = 0;
function displayErr() {
const formBox = document.querySelector("form");
const errorBox = document.createElement("div");
errorBox.className = "errorBox";
const errorText = document.createTextNode("Those are not numbers!");
errorBox.appendChild(errorText);
formBox.appendChild(errorBox);
}
if ((isNaN(billInput)) && i == 0 || (isNaN(peopleAmount)) && i == 0 ||
(billInput === "") && i == 0 || (peopleAmount === "") && i == 0)
{
displayErr();
i += 1;
}
now it will display an error only once, because i is never going to be 0 anymore

HTML + Javascript Button click again to undo

I was wondering how it is possible to make the button undo something too after clicking it. In my scenario just simple formatting of Text(Color,size etc), when you first click it, it formats the text as described in Javascript, but I would like to add a function, that when you click it again, that it undoes that.
`<script>
function myFunction(){
document.getElementById("demo").style.fontsize="25px";
document.getElementById("demo").style.color="#3AF702";
document.getElementById("demo").style.backgroundcolor="red";
}
</script>`
<button type="change" onclick="myFunction()">Change!</button>
I checked other articles already, which seemed to be related, but I did not get any smarter out of those, so my apologies in advance if it is a dup and thanks for your help!
<script>
var flag = true;
function myFunction(){
let el = document.getElementById("demo");
el.style.fontsize = flag ? "25px" : "";
el.style.color= flag ? "#3AF702" : "";
el.style.backgroundcolor=flag ? "red" : "";
flag = !flag;
}
</script>`
<button type="change" onclick="myFunction()">Change!</button>
The easiest way to do this is to add and remove a class
<style>
.change {
font-size: 25px;
color: #3AF702;
background-color="red"
}
</style>
<script>
var x = 0;
function myFunction() {
if (x == 0) {
document.getElementById("demo").classList.add("change");
x = 1;
} else {
document.getElementById("demo").classList.remove("change");
x = 0;
}
}
</script>
<button type="change" onclick="myFunction()">Change!</button>
Create an object that stores the initial values of your button and a variable which holds the state of it.
var state = 0;
var backup = {};
backup.fontSize = document.getElementById("demo").style.fontsize;
backup.color = document.getElementById("demo").style.color;
backup.background = document.getElementById("demo").style.backgroundcolor;
Now you can easily switch between the backup and the new values like this:
function myFunction() {
if (state == 0) {
document.getElementById("demo").style.fontsize = "25px";
document.getElementById("demo").style.color = "#3AF702";
document.getElementById("demo").style.backgroundcolor = "red";
state = 1;
} else {
document.getElementById("demo").style.fontsize = backup.fontSize;
document.getElementById("demo").style.color = backup.color;
document.getElementById("demo").style.backgroundcolor = backup.background;
state = 0;
}
}
var flag = true;
function myFunction(){
var x = document.getElementById("demo");
if (flag) {
x.style.backgroundColor = "red";
x.style.color="#3AF702";
x.style.fontSize="25px"
} else {
x.style.backgroundColor = "blue";
x.style.color="#dddddd";
x.style.fontSize="10px"
}
flag = !flag
}
function myFunction(){
demo.className = demo.className ? "" : "style"
}
.style {
font-size: 25px;
color: red;
background: blue;
}
<p id="demo">Hi!</p>
<button type="change" onclick="myFunction()">Change!</button>

Javascript/jQuery parse inline style as each object

I want to be able to parse inline css and have it as object in key pairs. Something like:
<div background-image: url('http://domain.com/images/image.jpg');background-size: cover;padding: 100px 0;">
{
backgroundImage : "http://domain.com/images/image.jpg",
backgroundSize: "cover",
padding: "100px 0"
}
This function works great for the most of the part. I'm having problem with background-image
it strips it completely and I end up with "url(http" instead.
function parseCss(el) {
var output = {};
if (!el || !el.attr('style')) {
return output;
}
var camelize = function camelize(str) {
return str.replace(/(?:^|[-])(\w)/g, function(a, c) {
c = a.substr(0, 1) === '-' ? c.toUpperCase() : c;
return c ? c : '';
});
}
var style = el.attr('style').split(';');
for (var i = 0; i < style.length; ++i) {
var rule = style[i].trim();
if (rule) {
var ruleParts = rule.split(':');
var key = camelize(ruleParts[0].trim());
output[key] = ruleParts[1].trim();
}
}
return output;
}
I'm pretty sure that "replace" function needs to be modified to make it work with image url
You might be able to do something like this, it would still fail for some edge cases with content. It is not running your camel case, but that is simple enough to call.
var x = document.getElementById("x");
var str = x.getAttribute("style"); //x.style.cssText;
console.log(str);
var rules = str.split(/\s*;\s*/g).reduce( function (details, val) {
if (val) {
var parts = val.match(/^([^:]+)\s*:\s*(.+)/);
details[parts[1]] = parts[2];
}
return details;
}, {});
console.log(rules);
div {
font-family: Arial;
}
<div style="color: red; background: yellow; background-image: url('http://domain.com/images/image.jpg');background-size: cover;padding: 100px 0;" id="x">test</div>
Instead of reading the the style attribute, you could iterate over the style properties. This way you avoid the problems with delimiters that are embedded in style values:
function parseCss(el) {
function camelize(key) {
return key.replace(/\-./g, function (m) {
return m[1].toUpperCase();
});
}
var output = {};
for (var a of el.style) {
output[camelize(a)] = el.style[a];
}
return output;
}
// Demo
var css = parseCss(document.querySelector('div'));
console.log(css);
<div style="background-image: url('http://domain.com/images/image.jpg');background-size: cover;padding: 100px 0;">
</div>
This does expand some consolidated styles you can have in the style attribute, such as padding, which splits into paddingLeft, paddingRight, ...etc.
With the use of some more ES6 features the above can be condensed to:
function parseCss(el) {
let camelize = key => key.replace(/\-./g, m => m[1].toUpperCase());
return Object.assign(
...Array.from(el.style, key => ({[camelize(key)]: el.style[key]})));
}
// Demo
let css = parseCss(document.querySelector('div'));
console.log(css);
<div style="background-image: url('http://domain.com/images/image.jpg');background-size: cover;padding: 100px 0;">
</div>
You can try with this, tested on few examples:
styleObj={};
style=$('div').attr('style');
rules=style.split(';');
rules = rules.filter(function(x){
return (x !== (undefined || ''));
}); // https://solidfoundationwebdev.com/blog/posts/remove-empty-elements-from-an-array-with-javascript
for (i=0;i<rules.length;i++) {
rules_arr=rules[i].split(/:(?!\/\/)/g); // split by : if not followed by //
rules_arr[1]=$.trim(rules_arr[1]).replace('url(','').replace(')','');
if(rules_arr[0].indexOf('-')>=0) {
rule=rules_arr[0].split('-');
rule=rule[0]+rule[1].charAt(0).toUpperCase()+rule[1].slice(1);
}
else {
rule=rules_arr[0];
}
styleObj[$.trim(rule)]=rules_arr[1];
}
console.log(styleObj);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div style="font-size: x-large; color: #ff9900; background-image: url('http://placehold.it/100x100');">Using inline style sheets - or is that inline styles?</div>
Demo (easier for testing of different inline styles): https://jsfiddle.net/n0n4zt3f/2/
P.S. Trimming and camel case are left... can be added, of course...

How do I supplant jQuery's toggleClass method with pure JavaScript?

How can I turn this piece of jQuery code into JavaScript?
$('#element').click(function(){
$(this).toggleClass('class1 class2')
});
I have already tried the following pieces of code, but to no avail.
First one is:
var element = document.getElementById('element'),
classNum = 0; // Supposing I know that the first time there will be that class
element.onmousedown = function() {
if (classNum === 0) {
this.classList.remove("class1");
this.classList.add("class2");
classNum = 1;
}
else if (classNum === 1) {
this.classList.remove("class2");
this.classList.add("class1");
classNum = 0;
}
}
Second one is:
var element = document.getElementById('element'),
classNum = 0; // Supposing I know that the first time there will be that class
element.onmousedown = function() {
if (classNum === 0) {
this.className -= "class1";
this.classList += "class2";
classNum = 1;
}
else if (classNum === 1) {
this.classList -= "class2";
this.classList += "class1";
classNum = 0;
}
}
Any answer that doesn't suggest that I stick with jQuery will be greatly appreciated.
[EDIT]
I've tried all of your solutions, but haven't been able to get it right. I believe it's because I didn't state clearly that the element has multiple classes like so:
class="class1 class3 class4"
And what I want is basically to replace class1 with class2 and toggle between them.
Update:
In response to comments, classList.toggle is a pure javascript solution. It has nothing to do with jQuery as one comment implies. If there is a requirement to support old versions of IE then there is a shim (pollyfill) at the MDN link below. And this shim, if needed, is far superior to the accepted answer.
Using classList.toggle certainly seems like the simplest solution. Also see Can I Use classList for browser support.
element.onclick = function() {
'class1 class2'.split(' ').forEach(function(s) {
element.classList.toggle(s);
});
}
Run the snippet to try
box.onclick = function() {
'class1 class2'.split(' ').forEach(function(s) {
box.classList.toggle(s);
stdout.innerHTML = box.className;
});
}
/* alternative
box.onclick = function() {
['class1', 'class2'].forEach(function(s) {
box.classList.toggle(s);
stdout.innerHTML = box.className;
});
}
*/
.class1 { background-color: red;}
.class2 { background-color: blue;}
.class3 { width: 100px; height: 100px; border: 1px black solid;}
click me:
<div id="box" class="class1 class3"></div>
<div id="stdout"></div>
classNum is a local variable.
Every time the event handler is called, you get a new variable, which has nothing to do with the value from the last call.
You want that to be a global variable.
Or, better yet, check classList.contains instead.
From: You might not need jQuery
$(el).toggleClass(className);
Is replaced by:
if (el.classList) {
el.classList.toggle(className);
} else {
var classes = el.className.split(' ');
var existingIndex = classes.indexOf(className);
if (existingIndex >= 0)
classes.splice(existingIndex, 1);
else
classes.push(className);
el.className = classes.join(' ');
}
Then simply wrap that function call within a document.getElementById('elementId').click
See fiddle: https://jsfiddle.net/2ch8ztdk/
var s = document.getElementById('element');
s.onclick=function(){
if(s.className == "class1"){
s.className = "class2"
} else {
s.className = "class1"
}
}
Your code is close, but your classNum variable isn't iterative. Try this:
var element = document.getElementById("element");
var numCount = 0;
element.addEventListener('click', function() {
if (numCount === 0) {
this.className = "";
this.className += " class1";
numCount++;
} else {
this.className = "";
this.className += " class2";
numCount = 0;
}
});
.class1 {
color: red;
}
.class2 {
color: blue;
}
<div id="element">click me</div>
you can use classList, but it only support IE 10+
Demo
var eles = document.querySelectorAll('#element');
var classNames = 'one two';
for(var i = 0; i < eles.length; i ++){
eles[i].onclick = function(e){
toggleClass.call(this, classNames);
}
}
function toggleClass(names){
var sp = names.split(' ');
for(var i = 0; i < sp.length; i++){
this.classList.toggle(sp[i]);
}
}
UPDATED MY ANSWER TO SUPPORT MULTIPLE CLASSES PER ELEMENT
https://jsfiddle.net/pwyncL8r/2/ This will now allow the element to already have n classes and still swap only one, retaining the other classes.
HTML
<div id="div1" style="width: 100px; height: 100px;" class="backBlack left100"</div>
<input type="button" id="swapButton" value="Css Swap" />
CSS
.backBlack {
background-color: black;
}
.backRed {
background-color: red;
}
.left100 {
margin-left: 100px;
}
JS
swapButton.onclick = function() {
var curClassIsBlack = (' ' + document.getElementById("div1").className + ' ').indexOf(' backBlack ') > -1
if (curClassIsBlack) {
document.getElementById("div1").className =
document.getElementById("div1").className.replace(/(?:^|\s)backBlack(?!\S)/g, '')
document.getElementById("div1").className += " backRed";
} else {
document.getElementById("div1").className =
document.getElementById("div1").className.replace(/(?:^|\s)backRed(?!\S)/g,'')
document.getElementById("div1").className += " backBlack";
}
}

Javascript run a function at the same time with different vars

Sorry about the confusing title, I'll explain better.
I have a 20x20 grid of div's, so its 400 of them each with an id, going from 0 to 399.
Each div is given one of three random values - red, green or blue - and when a div is clicked, a function is run to check if the div to the left, right, over and under are of the same value, if it is of the same value it will be simulated a click and the same function will run again.
The problem, is that the function sets vars, so if it finds that the div below has the same value, it will overwrite the vars set by the first click, hence never click any of the others.
JSfiddle - http://jsfiddle.net/5e52s/
Here is what I've got:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>untiteled</title>
<style>
body {
width: 420px;
}
.box {
width: 19px;
height: 19px;
border: 1px solid #fafafa;
float: left;
}
.box:hover {
border: 1px solid #333;
}
.clicked {
background: #bada55 !important;
}
</style>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script>
$().ready(function(){
var colors = ['red', 'green', 'blue'];
var i = 0;
while(i<400){
var color = colors[Math.floor(Math.random() * colors.length)];
$('.test').append('<div class="box" id="'+i+'" value="'+color+'" style="background:'+color+';">'+i+'</div>');
i++;
}
$('.box').click(function(){
var t = $(this);
t.addClass('clicked');
id = t.attr('id');
val = t.attr('value');
//Set color
up = parseInt(id) - 20;
right = parseInt(id) + 1;
down = parseInt(id) + 20;
left = parseInt(id) - 1;
clickup = false;
clickdown = false;
if($('#'+down).attr('value') === val){
clickdown = true;
}
if(up > -1 && ($('#'+up).attr('value') === val)){
clickup = true;
}
if(clickdown == true){
$('#'+down).click();
}
if(clickup == true){
$('#'+up).click();
}
});
});
</script>
</head>
<body>
<div class="test">
</div>
</body>
I think the biggest root cause of your problem is you don't check if it already has class 'clicked' or not. That could make the infinite recursive. For example, if you click on the div#2 then the div#1 receives a simulated click, and div#2 receives a simulated click from div#1.
$('.box').click(function(){
var t = $(this);
if(t.hasClass('clicked')) {
return;
}
t.addClass('clicked');
var id = t.attr('id');
var val = t.attr('value');
//Set color
var up = parseInt(id) - 20;
var right = (id%20 != 19) ? ((0|id) + 1) : 'nothing' ;
var down = parseInt(id) + 20;
var left = (id%20 != 0) ? ((0|id) - 1) : 'nothing';
console.log(up, right, down, left);
if($('#'+down).attr('value') === val) {
$('#'+down).click();
}
if($('#'+right).attr('value') === val) {
$('#'+right).click();
}
if($('#'+up).attr('value') === val) {
$('#'+up).click();
}
if($('#'+left).attr('value') === val) {
$('#'+left).click();
}
});
You can schedule the clicks onto the event loop instead of calling them directly, eg:
if(clickdown == true){
setTimeout(function () {
$('#'+down).click();
});
}
I don't think that's your root cause though, it's probably a combination of global vars and scope issues. Try reformatting as such:
$('.box').click(function (event){
var t = $(this), id, val, up, right, down, left, clickup, clickdown;
//...
Your variables id and val are not in a var statement, thus are implicitly created as members of the window object instead of being scoped to the local function. Change the semicolon on the line before each to a comma so that they become part of the var statement, and your code should begin working.

Categories