In JavaScript, is it possible to highlight all items with the same class when one of them is moused over?
For example, if I had two paragraphs with the class p1
I can't help but feel this should be more concise (the use of three for (...)
loops feels unnecessarily expensive), but one approach:
Object.prototype.classHighlight = function (over, out) {
var that = this.length ? this : [this];
function onOver() {
for (var i = 0, len = that.length; i < len; i++) {
that[i].style.backgroundColor = over;
}
}
function onOut() {
for (var i = 0, len = that.length; i < len; i++) {
that[i].style.backgroundColor = out;
}
}
for (var i = 0, len = that.length; i < len; i++) {
that[i].onmouseover = onOver;
that[i].onmouseout = onOut;
}
};
document.getElementsByClassName('test').classHighlight('#f90', '#fff');
JS Fiddle demo.
Six years later, following a link to this question and answer, I'm editing to update the above approach, and to add snippets and references.
Updated code:
// extending the Object prototype to allow chaining of this method,
// 'over' : String, the class-name to add when the element(s) of the
// HTMLCollection/NodeList are hovered-over. We also set the default
// value of the 'over' variable in order that a class-name will always
// be present:
Object.prototype.classHighlight = function(over = 'over') {
// taking the 'this' and using the spread operator to expand
// the iterable collection to an Array:
const that = [...this],
// creating a named function to act as the event-handler for
// 'mouseenter' and 'mouseleave':
toggleHighlight = (event) => {
// iterating over the array using Array.prototype.forEach():
that.forEach(
// we're not using 'this' in here, so using an Arrow function
// to use the Element.classList API to toggle the supplied
// class on each element of the collection. If the event-type
// is exactly equal to 'mouseenter' we add the class otherwise
// we remove the class:
(el) => el.classList.toggle(over, event.type === 'mouseenter')
);
};
// iterating over the collection, again using Array.prototype.forEach():
that.forEach(
// and another Arrow function:
(element) => {
// here we bind the toggleHighlight function - created above - as
// the event-handler for both the 'mouseenter' and 'mouseleave'
// events:
element.addEventListener('mouseenter', toggleHighlight);
element.addEventListener('mouseleave', toggleHighlight);
});
};
// here we use document.getElementsByClassName() to retrieve an HTMLCollection
// of elements matching the supplied class-name; and then using chaining - which
// is why we extended the Object prototype - to pass that HTMLCollection to
// the classHighlight() function:
document.getElementsByClassName('test').classHighlight('whenOver');
.whenOver {
background-color: #f90;
}
<p class="test">Testing</p>
<div>No classes here</div>
<ul>
<li class="test">Something in a 'test' element</li>
</ul>
Note that this updated approach, because we're toggling a class-name – as opposed to adding and clearing inline styles in the elements' style
attribute – means that selector-specificity may interfere with application of the style, for example:
// extending the Object prototype to allow chaining of this method,
// 'over' : String, the class-name to add when the element(s) of the
// HTMLCollection/NodeList are hovered-over. We also set the default
// value of the 'over' variable in order that a class-name will always
// be present:
Object.prototype.classHighlight = function(over = 'over') {
// taking the 'this' and using the spread operator to expand
// the iterable collection to an Array:
const that = [...this],
// creating a named function to act as the event-handler for
// 'mouseenter' and 'mouseleave':
toggleHighlight = (event) => {
// iterating over the array using Array.prototype.forEach():
that.forEach(
// we're not using 'this' in here, so using an Arrow function
// to use the Element.classList API to toggle the supplied
// class on each element of the collection. If the event-type
// is exactly equal to 'mouseenter' we add the class otherwise
// we remove the class:
(el) => el.classList.toggle(over, event.type === 'mouseenter')
);
};
// iterating over the collection, again using Array.prototype.forEach():
that.forEach(
// and another Arrow function:
(element) => {
// here we bind the toggleHighlight function - created above - as
// the event-handler for both the 'mouseenter' and 'mouseleave'
// events:
element.addEventListener('mouseenter', toggleHighlight);
element.addEventListener('mouseleave', toggleHighlight);
});
};
// here we use document.getElementsByClassName() to retrieve an HTMLCollection
// of elements matching the supplied class-name; and then using chaining - which
// is why we extended the Object prototype - to pass that HTMLCollection to
// the classHighlight() function:
document.getElementsByClassName('test').classHighlight('whenOver');
li.test {
background-color: fuchsia;
}
.whenOver {
background-color: #f90;
}
<p class="test">Testing</p>
<div>No classes here</div>
<ul>
<li class="test">Something in a 'test' element</li>
</ul>
This can be resolved by increasing the selector specificity of the assigned class-name:
li.test {
background-color: fuchsia;
}
html body .whenOver {
background-color: #f90;
}
Object.prototype.classHighlight = function(over = 'over') {
const that = [...this],
toggleHighlight = (event) => {
that.forEach(
(el) => el.classList.toggle(over, event.type === 'mouseenter')
);
};
that.forEach(
(element) => {
element.addEventListener('mouseenter', toggleHighlight);
element.addEventListener('mouseleave', toggleHighlight);
});
};
document.getElementsByClassName('test').classHighlight('whenOver');
li.test {
background-color: fuchsia;
}
html body .whenOver {
background-color: #f90;
}
<p class="test">Testing</p>
<div>No classes here</div>
<ul>
<li class="test">Something in a 'test' element</li>
</ul>
Or, you could instead use the !important
keyword to force that !important
-ified property to apply regardless of specificity (unless another rule also uses !important
and is itself more specific), for example:
/* Note the ridiculous and overly-specific selector: */
html > body > ul > li.test {
background-color: fuchsia;
}
.whenOver {
/ and here, as the demo shows, !important still
wins: */
background-color: #f90 !important;
}
Object.prototype.classHighlight = function(over = 'over') {
const that = [...this],
toggleHighlight = (event) => {
that.forEach(
(el) => el.classList.toggle(over, event.type === 'mouseenter')
);
};
that.forEach(
(element) => {
element.addEventListener('mouseenter', toggleHighlight);
element.addEventListener('mouseleave', toggleHighlight);
});
};
document.getElementsByClassName('test').classHighlight('whenOver');
html > body > ul > li.test {
background-color: fuchsia;
}
.whenOver {
background-color: #f90 !important;
}
<p class="test">Testing</p>
<div>No classes here</div>
<ul>
<li class="test">Something in a 'test' element</li>
</ul>
When it comes to !important
, though, try to avoid using it wherever possible because, as MDN notes:
Using
!important
, however, is bad practice and should be avoided because it makes debugging more difficult by breaking the natural [cascade] in your stylesheets."The !important exception," MDN.
References:
Here's a working example (which requires JQuery). When a member of p1
is moused over, all other elements of p1
will be highlighted as well. The same is true of p2
.
JavaScript:
function highlightAllOnMouseover(className){
$(className).mouseover(function() {
$(className).css("opacity", 0.4);
$(className).css("opacity", 1);
}).mouseleave(function() {
$(className).css("opacity", 0.4);
});
}
highlightAllOnMouseover(".thing1");
highlightAllOnMouseover(".thing2");
HTML:
<p class = "thing1">This is thing1.</p>
<p class = "thing2">This is thing2.</p>
<p class = "thing1">This is also thing1.</p>
<p class = "thing2">This is also thing2.</p>
To cause all elements with a specific class to be highlighted on mouseover, you only need to call the function highlightAllOnMouseover(className)
, which I created here.
Here is a zero-dependency solution that should work with very old JS versions:
Add class = 'grp_N hovergrp'
to all elements that should be highlighted on hover, replacing N
by some number (or id) that uniquely describes a group of elements. The groups may not intersect, every element with hovergrp
class should belong to exactly one grp_N
class.
Append the following JS
snippet in a <script>...</script>
to the end of your <html>
:
// collect all highlighted elements and group them by their group
// name for faster access;
// Attach onmouseover and onmouseout listeners.
var groups = {};
var hovergrp = document.getElementsByClassName("hovergrp");
for (var i = 0; i < hovergrp.length; i++) {
var e = hovergrp.item(i);
var eClasses = e.classList;
for (var j = 0; j < eClasses.length; j++) {
var c = eClasses[j];
if (c.startsWith("grp_")) {
if (!groups[c]) {
groups[c] = [];
}
groups[c].push(e);
e.onmouseover = (function(c_capture) {
return function(_event) {
highlightGroup(c_capture, "orange");
};
})(c);
e.onmouseout = (function(c_capture) {
return function(_event) {
highlightGroup(c_capture, "transparent");
};
})(c);
break;
}
}
}
function highlightGroup(groupName, color) {
var g = groups[groupName];
for (var i = 0; i < g.length; i++) {
g[i].style.backgroundColor = color;
}
}
<pre><code>
// hover over variable names `<span class='grp_0 hovergrp'>x</span>` and `<span class='grp_1 hovergrp'>f</span>`
kroneckerDelta(<span class='grp_0 hovergrp'>x</span>) {
return function(<span class='grp_1 hovergrp'>f</span>) {
<span class='grp_1 hovergrp'>f</span>(<span class='grp_0 hovergrp'>x</span>)
}
}
</code></pre>
<p class = "grp_p1 hovergrp">This should be highlighted on mouseover</p>
<p class = "grp_p2 hovergrp">This should be highlighted on mouseover</p>
<p class = "grp_p1 hovergrp">This should be highlighted on mouseover</p>
<p class = "grp_p2 hovergrp">This should be highlighted on mouseover</p>
The HTML snippet shows usage example: a little <pre>
-formatted snippet of code with variables that are grouped into two groups. Whenever you hover over a variable, all the usages of the variable as well as the binding site are highlighted.