How to create a pure CSS tooltip with HTML content for inline elements

流过昼夜 提交于 2021-02-17 21:52:05

问题


TL;DR - See Adam's answer (accepted) or Update #5 in my question for the current solution. My question shows my journey getting to that solution in a rather long but explanatory way, describing pitfalls and limitations.

I am creating a module that adds glossary term descriptions to the respective words used in a text. Everything works fine with plain text descriptions. For example, if there is a description for onomasticon (Another word for thesaurus), the following HTML

<p>An onomasticon is not a dinosaur.</p>

gets converted to

<p>An <dfn title="Another word for thesaurus">onomasticon</dfn> is not a dinosaur.</p>

Since the content (both article text and glossary term descriptions) come out of a CMS which allows the user to use HTML, the term's description may contain HTML, e.g. Another word for <strong>thesaurus</strong>. Using the above technique, this starts to get messy:

<p>An <dfn title="Another word for <strong>thesaurus</strong>">onomasticon</dfn> is not a dinosaur.</p>

This is by default since tag attributes may not contain tags themselves. This limitation can be bypassed by adding an additional element inside the dfn tag and adding some CSS to hide the element initially:

dfn span {
  display: none;
}
dfn:hover span {
  display: block;
  position: absolute;
  top: 2.2em;
  background: #ccc;
}
<p>An <dfn><span>Another word for <strong>thesaurus</strong></span>onomasticon</dfn> is not a dinosaur.</p>

But now comes the part, I am failing to overcome. HTML tags like <strong> are inline elements which do not pose a problem. However, if the term's description contains a block element, this fails, e.g. if the description itself is contained in a <p> tag:

dfn span {
  display: none;
}
dfn:hover span {
  display: block;
  position: absolute;
  top: 2.2em;
  background: #ccc;
}
<p>An <dfn><span><p>Another word for <strong>thesaurus</strong></p></span>onomasticon</dfn> is not a dinosaur.</p>

What happens here? Well, since block elements are not allowed inside most inline elements, the outer inline element is getting terminated and the inner block element is placed right after. Thus, the CSS selectors are not working anymore, plus you'll end up with extra line breaks. Of course, one might think of adding something like dfn span p { display: inline-block; } but this doesn't work either (neither does inline), it's invalid HTML5.

Although semantically incorrect, I tried to turn <dfn> into <div class="dfn"> and the inside <span> to a <div> as well since dfn, abbr and cite elements may only contain phrasing context as well (just as span). This fails again due to <div> not being allowed inside <p>, again an unwanted line-break is added:

.dfn > div {
  display: none;
}
.dfn:hover div {
  display: inline-block;
  position: absolute;
  top: 2.2em;
  background: #ccc;
}
<p>An <div class="dfn"><div><p>Another word for <strong>thesaurus</strong></p></div>onomasticon</div> is not a dinosaur.</p>

That has been the summary of my journey so far, trying to reach the holy grail of adding a tooltip - containing HTML - to an inline element - such as <dfn>, <abbr> or <cite> - using CSS only.

Before I now look into javascript-based solutions, my question is whether or not I missed an option which fulfils my requirements as listed below?

  • CSS only
  • tooltip must be added to <dfn>, <abbr> or <cite> inside a paragraph
  • tooltip content may contain the following HTML tags: div, p, h2, img, figure, ul, ol, li

Update 1: Since I wrote the module (PHP), I am of course able to manipulate the term's description. However, I don't just want to strip all <p> tags but try to keep the intended markup.

Update 2: I found a rather crude solution. Let's say the description for onomasticon is some HTML snippet like:

<p>Another word for <strong>thesaurus</strong></p>
<p><img src="thesaurus.png" /></p>

My module will now convert all block elements to spans with appropriate class names, so the description becomes

<span class="p">Another word for <strong>thesaurus</strong></span>
<span class="p"><img src="thesaurus.png" /></span>

This way, I have inline elements which shouldn't break the phrasing context. Adding some more CSS to style the tooltip and make span.p behave somewhat like a p again, it all ends up to this.

dfn {
  display: inline-block;
  position: relative;
}
dfn > span {
  display: none;
}
dfn:hover > span {
  display: block;
  position: absolute;
  top: calc(1em + 5px);
  max-width: 20em;
  background: #ccc;
  padding: .5em;
}
dfn img {
  max-width: 12em;
}
dfn > span span.p {
  display: block;
  margin-bottom: .5em;
}
<p>An <dfn><span><span class="p">Another word for <strong>thesaurus</strong></span>
    <span class="p"><img src="http://i.imgur.com/G0bl4k7.png" /></span></span></span>onomasticon</dfn> is not a dinosaur.</p>

This is my best looking option so far supporting limited HTML for inline element's tooltips. However, it's probably not the best option since it requires the hacky rewrite of HTML block elements.

Update 3: ChristianF's answer had a very important clue how to preserve HTML in glossary term definitions. If the description is not an element inside the dfn tag but a following sibling, we could still hide and show it, e.g. with the CSS selector dfn + div.dfn-tooltip. However, since most glossary terms will be found in a phrasing context, this will again break the layout, since divs are not permitted inside a paragraph.

So we would need an element, that can be both used in a phrasing context and contain block elements. According to this beautiful HTML structure diagram, the <button> tag would be the only suitable one.

p {
  display: relative;
}
dfn + .dfn-tooltip {
  display: none;
}
dfn:hover + .dfn-tooltip {
  display: block;
  position: absolute;
  top: 3em;
  max-width: 20em;
  background: #ccc;
  padding: .5em;
  border: none;
}
.dfn-tooltip img {
  max-width: 12em;
}
<p>An <dfn>onomasticon</dfn><button class="dfn-tooltip"><p>Another word for <strong>thesaurus</strong></p><p><img src="http://i.imgur.com/G0bl4k7.png" /></p></button> is not a dinosaur.</p>

This looks pretty promising. Of course, there are two major drawbacks: (1) a button element at this place is semantically incorrect and (2) the tooltip doesn't stay open when hovering itself since it's not a child of the dfn tag anymore.

Update 4: Taking one little step further to avoid the second drawback is to simply move the button element back into the dfn tag, since the context is the same.

dfn {
  position: relative;
  display: inline-block;
}
dfn > .dfn-tooltip {
  display: none;
}
dfn:hover > .dfn-tooltip {
  display: block;
  position: absolute; 
  top: calc(1em + 5px);
  max-width: 20em;
  background: #ccc;
  padding: .5em;
  border: none;
}
.dfn-tooltip img {
  max-width: 12em;
}
<p>An <dfn>onomasticon<button class="dfn-tooltip"><p>Another word for <strong>thesaurus</strong></p><p><img src="http://i.imgur.com/G0bl4k7.png" /></p></button></dfn> is not a dinosaur.</p>

Update 5 (final): Adam's answer brought up some nice technique to incorporate the title attribute's original intention, putting everything together, this is what I ended up with.

dfn {
  position: relative;
  display: inline-block;
  cursor: help;
}
dfn:before {
  content: attr(title);
}
dfn > .dfn-tooltip {
  display: none;
}
dfn:hover > .dfn-tooltip {
  display: block;
  position: absolute; 
  top: calc(1em + 5px);
  max-width: 20em;
  background: #ccc;
  padding: .5em;
  border: none;
  cursor: help;
}
.dfn-tooltip img {
  max-width: 12em;
}
<p>An <dfn title="onomasticon"><button disabled class="dfn-tooltip"><p>Another word for <strong>thesaurus</strong></p><p><img src="http://i.imgur.com/G0bl4k7.png" /></p></button></dfn> is not a dinosaur.</p>

By the way, if anyone is interested, I am using this in a module called Onomasticon, which provides a basic glossary functionality for Drupal 8.


回答1:


Note that W3C says :

Defining term: If the dfn element has a title attribute, then the exact value of that attribute is the term being defined.

Accordingly, you can have a simple solution where you put the term being defined inside the title attribute, and the definition inside

dfn::before {content: attr(title);padding: 0 0 1em;}      
dfn button {display: none; position: absolute}
dfn:hover button, dfn:focus button {display: block;}
<p>An
     <dfn title="onomasticon" tabindex="0"><button disabled>
       <p>Another word for <strong>thesaurus</strong></p>
       <p><img src="http://i.imgur.com/G0bl4k7.png" /></p>
      </button></dfn> is not a dinosaur.
</p>

I do not find any tag that can replace here the button element which seems to be the only one working here.

So we have to add the disabled attribute to the button element to disable its button behavior (focus) and set the tabindex on the dfn element to enable arrow navigation.




回答2:


As already established in the comments, the easiest way to do this is by simply replacing the p tags with something else. This requires the least amount of workaround, and thus the approach least likely to break.

The quickest is simply replacing the opening p tag with two br tags, and the closing with an empty string. That way you'll get a visible empty line between the two text blocks, similar to what a paragraph break would normally give you. Most of the time this is enough to give the illusion of a paragraph break.

Another solution is, as you've found, to replace them with span-tags and style these to behave exactly like a normal P-tag. This requires a bit more code and work, but has the benefit of behaving, well... Exactly like the paragraphs on your site, creating a perfect illusion of there actually being a paragraph break there.
This is the method I'd use if the paragraphs have been styled in an unusual manner, so a simple empty line would not be sufficient to create the illusion. For example when the first line is indented, special styling for the first character or so forth.

In your case, I think a simple double br would suffice. Seeing as you only have a .5em bottom margin on paragraphs.

Edit, added: In light of your comment to my answer, I have one more idea. That is to create a hidden div, and store the comment in this one. Giving it an ID that corresponds to a rel attribute of the dfn tag. Then, using CSS and JS, style the DIV as a tooltip upon hover. Will require a bit more work, but it will allow you to preserve all formatting and tags without issues.
For those who don't support the necessary features, you could use a trimmed down version inside the title tag. Then use the same JS to suppress it when showing the "enhanced" tooltip.

But yeah, you're most certainly looking at using JS here. Unless you can make do with the CSS3 adjacent sibling selector




回答3:


I used the object tag. It makes more sense to me because you include the info in your text.

dfn {
  position: relative;
  display: inline-block;
}
dfn > .dfn-tooltip {
  display: none;
}
dfn:hover > .dfn-tooltip,
.dfn-tooltip:hover {
  display: block;
  position: absolute;
  top: calc(1em + 4px);
  max-width: auto;
  background: #ccc;
  padding: .5em;
  border: none;
}
.dfn-tooltip span {
  max-width: 12em;
  float: left;
}
.dfn-tooltip img {
  max-width: 12em;
  float: left;
  clear: both;
}
<p>An <dfn>onomasticon
  <object class="dfn-tooltip">
    <p>Another word for <strong>thesaurus</strong></p>
    <p><img src="http://i.imgur.com/G0bl4k7.png" /></p>
  </object>
  </dfn> is not a dinosaur.
</p>



回答4:


Another solution worth looking into would be to use pure css, in combination with just the element. Forget about the title, or add it plain.

If you can afford to generate and use inline css, and inject it into the head before the output gets served, then how about:

The <abbr id="abbr_1" title="Fox is an animal">fox</abbr>. Another <abbr id="abbr_2">word</abbr>

abbr
{
   display: none;
   position: absolute;
   z-index: 1; //probably not required, but I hope the title won't show up above it.... I guess it won't.
}
abbr:hover{display: block;}
#abbr_1:before{
   content: '<p>Fox is a <i class="">beautiful</i> \'animal\'</p>'
}
...etc

Just don't forget escaping the single quotes ;)



来源:https://stackoverflow.com/questions/40531029/how-to-create-a-pure-css-tooltip-with-html-content-for-inline-elements

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!