-1

I am writing a tab control where I would like each non-active tab link (menu item, not the body), to use the color assigned as an attribute on that element as the :hover background property.

Example html (dramatically simplified)

$(document).ready(function(){

  function initTabs() {
    var active = $('nav > ul > li.active');
    if(active === undefined) { active = $('nav > ul > li:first-of-type'); }

    var color = active.attr('data-bgcolor');
    if(color === undefined) { color = 'purple'; }
    
    $('body').css('background', color );
    active.css('background', color );
  }
  initTabs();

  $('nav > ul > li:not(.active)').click(function(){
    var color = $(this).attr('data-bgcolor');
    if(color === undefined) { color = 'black'; }

    $(this).parent().find('li').each(function() {
      $(this).css('background', '').removeClass('active');
    })
    
    $('body').css('background', color);
    $(this).css('background', color).addClass('active');
    
  });


});
body {
  margin: 0;
  background: black;
}

nav {
  display: flex;
  flex-direction: column;
  background: black;
}

nav > ul {
  padding: 0;
  list-style-type: none;
  display: flex;
  flex: 1;
  margin: 0px;
}

nav > ul > li {
  padding: 5px;
  font-size: 1.2em;
  color: white;
  font-family: sans-serif;
}

/***
I have tried :
    background: attr(data-bgcolor);
***/
nav > ul > li:not(.active) {
  background: black;
  color: white;
  cursor: pointer;
}

nav > ul > li:not(.active):hover {
  background: white;
  color: black;
}

nav > ul > li.active {
  cursor: default;
}
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<nav>
  <ul>
    <li data-bgcolor="red">Home</li>
    <li data-bgcolor="blue">News</li>
    <li data-bgcolor="orange">Blog</li>
  </ul>
</nav>
    

Using the above example, I would like to set the value for

nav > ul > li:not(.active) {
  background: black;
  ...
}

Using the data-bgcolor value from the li element currently being hovered over.

I am looking for a CSS specific way to handle this (as you can see I can handle things fairly well in jQuery) because CSS just performs much faster and isn't (as) prone to quirks that occur on mouseover/mouseleave events when they happen to fast.

I have tried using the attr() value in CSS but for some reason this doesn't work for me or perhaps due to the nesting in my selectors, there is an issue there.

For the full project currently in the works, you can see my pen here

zer00ne
  • 41,936
  • 6
  • 41
  • 68
Kraang Prime
  • 9,981
  • 10
  • 58
  • 124
  • There's no CSS property that can change the values of attributes. There are ways to change styles through certain events without JavaScript. – zer00ne Oct 18 '18 at 08:38

3 Answers3

2

use CSS variable:

body {
  margin: 0;
  background: yellow;
}

nav {
  display: flex;
  flex-direction: column;
  background: black;
}

nav > ul {
  padding: 0;
  list-style-type: none;
  display: flex;
  flex: 1;
  margin: 0px;
}

nav > ul > li {
  padding: 5px;
  font-size: 1.2em;
  color: white;
  font-family: sans-serif;
}

nav > ul > li:not(.active) {
  background: #000;
  color: white;
  cursor: pointer;
}

nav > ul > li:not(.active):hover {
  background: var(--bgcolor);
  color: black;
}

nav > ul > li.active {
  cursor: default;
}
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<nav>
  <ul>
    <li style="--bgcolor:red">Home</li>
    <li style="--bgcolor:blue">News</li>
    <li style="--bgcolor:orange">Blog</li>
  </ul>
</nav>
Temani Afif
  • 245,468
  • 26
  • 309
  • 415
  • Indeed. This is much cleaner than injecting a stylesheet, but I feel I can refactor my code to still use the custom attribute to set the style in the above manner at runtime. Do you know if jQuery allows for setting `--somevar` or is there render issues. You also wouldn't happen to know what browsers support this ? – Kraang Prime Oct 18 '18 at 09:10
  • @KraangPrime check this for more details : https://stackoverflow.com/questions/5041494/selecting-and-manipulating-css-pseudo-elements-such-as-before-and-after-usin/49618941#49618941 .. and they pretty well support, they only fail on IE but It's not a surprise I guess – Temani Afif Oct 18 '18 at 09:12
  • This worked like a charm. I have updated my code on my codepen, and the result is so purrrrty. While I am using jQuery to set the style attribute, I was looking for a style specific method that works, and is the reason I accepted your answer as the correct solution for this. I still don't recall the method I used in early 2000's but having something that works is more important. Thank you :) – Kraang Prime Oct 18 '18 at 09:19
1

You cannot use attr the way you want:

https://developer.mozilla.org/en-US/docs/Web/CSS/attr

attr. The attr() CSS function is used to retrieve the value of an attribute of the selected element and use it in the style sheet. ... Note: The attr() function can be used with any CSS property, but support for properties other than content is experimental, and support for the type-or-unit parameter is sparse.

So

[data-bgcolor] { color: attr(data-bgcolor); }

does not work but

[data-bgcolor]::before {
  content: attr(data-bgcolor) " ";
}

does work in Chrome

I assumed you do NOT want to have

[data-bgcolor="green"] { background-color: green; }

$(document).ready(function(){

  function initTabs() {
    var active = $('nav > ul > li.active');
    if(active === undefined) { active = $('nav > ul > li:first-of-type'); }

    var color = active.attr('data-bgcolor');
    if(color === undefined) { color = 'purple'; }
    
    $('body').css('background', color );
    active.css('background', color );
  }
  initTabs();

  $('nav > ul > li:not(.active)').click(function(){
    var color = $(this).attr('data-bgcolor');
    if(color === undefined) { color = 'black'; }

    $(this).parent().find('li').each(function() {
      $(this).css('background', '').removeClass('active');
    })
    
    $('body').css('background', color);
    $(this).css('background', color).addClass('active');
    
  });


});
body {
  margin: 0;
  background: black;
}

nav {
  display: flex;
  flex-direction: column;
  background: black;
}

nav > ul {
  padding: 0;
  list-style-type: none;
  display: flex;
  flex: 1;
  margin: 0px;
}

nav > ul > li {
  padding: 5px;
  font-size: 1.2em;
  color: white;
  font-family: sans-serif;
}

/***
I have tried :
    background: attr(data-bgcolor);
***/
nav > ul > li:not(.active) {
  background: black;
  color: white;
  cursor: pointer;
}

nav > ul > li:not(.active):hover {
  background: white;
  color: black;
}

nav > ul > li.active {
  cursor: default;
}

[data-bgcolor] { color: attr(data-bgcolor); }

[data-bgcolor]::before {
  content: attr(data-bgcolor) " ";
}
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<nav>
  <ul>
    <li data-bgcolor="red">Home</li>
    <li data-bgcolor="blue">News</li>
    <li data-bgcolor="orange">Blog</li>
  </ul>
</nav>
mplungjan
  • 169,008
  • 28
  • 173
  • 236
  • The sample above is just prepending the color name to the menu item text. That is rather disappointing. I knew of another way in early 2000's to do this in CSS alone, but I don't recall. I think it had something to do with properties on the element itself. – Kraang Prime Oct 18 '18 at 08:26
  • 1
    @KraangPrime I know I was surprised too – mplungjan Oct 18 '18 at 08:36
0

If you have a limited selection of colors you could use the attribute selector in css to overcome your current problem

[data-bgcolor="green"] { background-color: green; }
[data-bgcolor="blue"] { background-color: blue; }

If this makes sense depends on your use case.

Michael
  • 308
  • 1
  • 12
  • I want to allow the freedom of being able to change that by simply changing the attribute value to whatever color. – Kraang Prime Oct 18 '18 at 08:10
  • Change it were - in the back end, or dynamically at run-time via JavaScript? If only the former, then you could loop through all relevant elements and dynamically _create_ stylesheet rules like this based on the values read from the elements. – misorude Oct 18 '18 at 08:18
  • @misorude - that is a last resort just to make it work. I know I have seen this in use before where CSS controls the background element on specific events,, I just can't remember for the life of me where I saw that or the specific method used. There is a really old-school way to do this in pure CSS. Hoping that someone can remember this :). `attr` is relatively new in comparison to what I used in the early 2000's. – Kraang Prime Oct 18 '18 at 08:24
  • Wouldn't inline styles pretty much cover your problem, since you're already adding a data-attribute to your elements? I can't think of any solutions without script – Michael Oct 18 '18 at 08:36
  • 1
    I’ve been “in the game” for quite a while now myself, but I have no idea what Y2K trick you might have in mind here :-) Inline styles would be the only other way I see, too - problem being, that you can not define styles for :hover “inline”. With an additional element that could work though, `
  • Home
  • ` - position that span absolutely behind the content, and make it visible when the `li` gets hovered … – misorude Oct 18 '18 at 08:42
  • 1
    @misorude with CSS variable it's now possible to define hover state and even pseudo element state inline ;) https://stackoverflow.com/a/52870311/8620333 – Temani Afif Oct 18 '18 at 09:25