Anderson Green
Anderson Green

Reputation: 31850

Highlight all elements with same class when one of them is moused over

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 and 2 paragraphs with the class p2, I'd want both elements of p1 to be highlighted on mouseover, and I'd also want both elements of p2 to be highlighted on mouseover.

<p class = "p1">This should be highlighted on mouseover</p>
<p class = "p2">This should be highlighted on mouseover</p>
<p class = "p1">This should be highlighted on mouseover</p>
<p class = "p2">This should be highlighted on mouseover</p>

Upvotes: 2

Views: 3416

Answers (3)

David Thomas
David Thomas

Reputation: 253506

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:

Upvotes: 2

Andrey Tyukin
Andrey Tyukin

Reputation: 44992

Here is a zero-dependency solution that should work with very old JS versions:

  1. 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.

  2. 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.

Upvotes: 2

Anderson Green
Anderson Green

Reputation: 31850

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.

Upvotes: 2

Related Questions