如何为内联元素创建具有HTML内容的纯CSS工具提示

11
TL;DR - 请查看我问题中的Adam's answer(已被接受)或Update#5,获取当前解决方案。我的问题以较长但详细的方式展示了到达该解决方案的过程,并描述了陷阱和限制。
<p>An onomasticon is not a dinosaur.</p>

被转换为

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

由于内容(包括文章文本和词汇表术语描述)来自允许用户使用HTML的CMS,因此术语描述可能包含HTML,例如Another word for <strong>thesaurus</strong>。使用上述技术,这种情况开始变得混乱:

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

这是默认行为,因为标签属性本身不能包含标签。可以通过在 dfn 标签内部添加一个额外的元素,并添加一些 CSS 来隐藏初始元素来绕过此限制:


这是默认行为,因为标签属性本身不能包含标签。可以通过在 dfn 标签内部添加一个额外的元素,并添加一些 CSS 来隐藏初始元素来绕过此限制:

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>

但现在有个问题,我无法克服。像<strong>这样的HTML标签是内联元素,不会造成问题。但是,如果该术语的描述包含块级元素,则会失败,例如,如果描述本身包含在<p>标记中:

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>

这里发生了什么?由于大多数行内元素不允许包含块级元素,所以外部行内元素被结束,内部块级元素直接放在其后。因此,CSS选择器不再起作用,并且你会得到额外的换行符。当然,有人可能想添加类似于 dfn span p { display: inline-block; } 的东西,但这也不起作用(inline 也是如此),它是无效的 HTML5。

虽然这样语义上不正确,但我尝试将 <dfn> 改为 <div class="dfn">,内部的 <span> 也改为 <div>,因为 dfn、abbr 和 cite 元素只能包含短语内容(就像 span 一样)。但由于 <p> 不允许包含 <div>,所以还是会加入不需要的换行符:

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

这是我迄今为止的旅程总结,试图通过仅使用CSS将包含HTML的工具提示添加到行内元素(如<dfn><abbr><cite>)。

现在,在我查看基于javascript的解决方案之前,我的问题是是否错过了符合我以下要求的选项?

  • CSS only
  • 必须将工具提示添加到段落中的<dfn><abbr><cite>
  • 工具提示内容可以包含以下HTML标签:div、p、h2、img、figure、ul、ol、li

更新1: 由于我编写了该模块(PHP),因此我当然能够操作术语的描述。但是,我不想剥离所有<p>标签,而是尝试保留预期的标记。

更新2: 我找到了一个相当粗糙的解决方案。假设 onomasticon 的描述是一些HTML代码片段,例如:

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

我的模块现在将所有块级元素转换为带有适当类名的,因此描述变为

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

这样,我就有了内联元素,它们不应该破坏短语上下文。添加一些CSS来样式化工具提示并使 span.p 的行为类似于 p,最终结果如下。

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>

这是迄今为止我所支持的具有限制的行内元素工具提示的最佳外观选项。然而,它可能不是最佳选择,因为它需要对HTML块元素进行hacky重写。

更新3: ChristianF的答案给出了如何在词汇术语定义中保留HTML的一个非常重要的线索。如果描述不是dfn标签内的元素,而是后面的兄弟元素,则仍然可以隐藏和显示它,例如使用CSS选择器dfn + div.dfn-tooltip。然而,由于大多数词汇表术语将在短语上下文中找到,这将再次破坏布局,因为在段落内不允许div。

因此,我们需要一个既可用于短语上下文可以包含块元素的元素。根据这张美丽的HTML结构图<button>标签将是唯一合适的标签。

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>

这看起来非常有前途。当然,有两个主要的缺点:(1)此处的按钮元素在语义上不正确;(2)由于工具提示不再是 dfn 标签的子元素,因此悬停在其上时工具提示不会保持打开状态。

更新4:为了避免第二个缺点,可以更进一步地将按钮元素简单移回 dfn 标签中,因为上下文是相同的。

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>

更新 5(最终版):Adam 的答案提供了一些很好的技巧来融合标题属性的原始意图,将所有内容整合在一起,这就是我最终得出的结果。

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>

顺便说一下,如果有人感兴趣的话,我正在一个名为Onomasticon的模块中使用它,该模块为Drupal 8提供了基本的词汇表功能。


如果你的/需求/要求你能够在段落内添加一个段落,那么你就遇到了一个障碍。这些要求来自哪里?你是否愿意改变这些要求? - andi
@andi 这是一个基于Drupal的网站模块。目前,客户对术语描述中只有纯文本感到满意,但在启动后,他们希望能够添加段落和可能的图像到描述中。这本身不是问题,因为Drupal提供了一个富文本编辑器来完成这个任务。我正在努力将富文本内容放入工具提示中。由于Drupal本身依赖于jQuery,我可以使用jQuery,但我想避免向我的模块添加另一个JS库(依赖项)。 - Paul
有什么硬性要求阻止您使用一个(或两个)br标签代替p标签吗?诚然,从语义上讲不是最干净的解决方案,但它完全合法。 - ChristianF
基本上,这就是我现在所做的,虽然有点不同(请参见更新2)。 我将<p>替换为<span class="p">,并添加以下CSS:span.p {display: block; margin-bottom: .5em;} - Paul
4个回答

6
请注意,W3C表示:
定义术语:如果dfn元素具有title属性,则该属性的确切值是被定义的术语
因此,您可以采用简单的解决方案,在title属性内放置被定义的术语,并在内部放置定义。

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>

我没有找到任何标签可以替换这里的 button 元素,它似乎是唯一有效的。

因此,我们必须向 button 元素添加 disabled 属性,以禁用其 button 行为(聚焦),并在 dfn 元素上设置 tabindex 以启用箭头导航。


很好的发现,使用 dfn 的 title 属性。我以前读过这个,一直很困惑,因为即使在 w3schools 的示例中,title 属性也包含“超文本标记语言”,而 dfn 的内部文本是“HTML”(在我看来应该是被定义的术语)。我喜欢你使用 ::before 伪元素的方式。 - Paul

3
如评论中所述,最简单的方法是将p标签替换为其他内容。这需要最少的解决办法,因此最不容易出错。
最快速的方法是用两个br标签替换开头的p标签,用空字符串替换结尾。这样,您将在两个文本块之间获得一个可见的空行,类似于正常情况下段落分隔符会给您带来的效果。大多数时候,这足以产生段落分隔符的幻觉。
另一种解决方案是,如您所发现的,用标签替换它们,并将其样式设置为与普通P标签完全相同。这需要更多的代码和工作,但有一个好处,即表现得像站点上的段落一样,创造出一个完美的段落分隔符幻觉。
如果段落已经以不寻常的方式进行了样式设置,因此简单的空行不足以创建幻觉,则可以使用此方法。例如当第一行缩进,第一个字符或类似情况需要特殊样式设置。
在您的情况下,我认为简单的双倍br就足够了,因为段落只有.5em的底部边距。 编辑,添加: 根据您对我的答案的评论,我有一个更多的想法。那就是创建一个隐藏的 div,并将评论存储在其中。给它一个与 dfn 标签的 rel 属性对应的 ID。然后,使用 CSS 和 JS,在悬停时将 DIV 样式化为工具提示。这需要更多的工作,但它将允许您保留所有格式和标记而不会出现问题。
对于那些不支持必要功能的人,您可以在标题标签中使用缩减版本。然后使用相同的 JS 在显示 "增强" 工具提示时抑制它。

但是,你肯定要在这里使用 JS。除非你可以用 CSS3 相邻兄弟选择器 搞定。


谢谢您的回答。请注意,我的示例故意保持非常简单。在生产网站上,用户可能会添加更多的HTML,而不仅仅是段落和图像,例如列表<ul>/<ol>或标题<hx>。虽然模块可以像评论和您的答案中提到的那样为这些标签添加替换,但我实际上仍在寻找一种解决方案,它不需要篡改最初由用户添加的HTML。 - Paul
你编辑关于相邻兄弟选择器的最后一句话是最重要的部分。我已经根据这个线索更新了我的问题,并提供了一个例子。好主意,我喜欢你的想法。 ;) - Paul
在上述第3个更新之后,我刚刚进行了另一个更新#4,将描述移回到dfn标签中。尽管使用按钮元素存在语义错误,但现在似乎是最佳选择。由于你的帖子引导我到这里,如果没有人提出更好的解决方案,我将在本周末之前接受你的答案。在此期间,我会做一些测试,非常感谢让我找到正确方向。 - Paul
@Paul 我想现在我更喜欢JS,但是你能找到解决方案真的很棒!尽管有点hacky。 :P 不用谢,很高兴能帮忙。 - ChristianF
先生,您通过将我引入正确的轨道赢得了悬赏。然而,为了日后的访问者着想,我标记了Adam的答案为已接受,因为它最接近我在生产网站上使用的内容。 - Paul
@Paul 谢谢!很高兴知道我能帮到你,即使它不是最接近你最终得到的结果。 :) - ChristianF

0
我使用了 object 标签。对我来说更有意义,因为你可以在文本中包含信息。

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>


正如我在问题中所述,特定词汇表术语的描述是来自CMS的实体,用户可以以许多不同的方式进行样式设置(CKEditor可用)。通过我的模块,我想提取描述并将其添加到其他内容中的相应术语中。虽然我知道短语上下文元素内的段落(也不是按钮)不符合HTML5标准,但我无法控制术语的描述。 - Paul
我调整了我的答案。 - Rudi Urbanek
寻找合适的标签时,我当然也遇到了<object>。然而,尽管这个标签和<button>都是语义上不正确的,但<button>的优点在于它可以在没有任何属性的情况下进行验证。对象标签需要设置数据或类型(其中数据是一个URL,类型是有效的IANA媒体类型)。 - Paul

-2

另一个值得考虑的解决方案是使用纯CSS,结合只有元素。忘记标题,或者将其添加为纯文本。

如果您可以生成并使用内联CSS,并在输出被服务之前将其注入到头部中,那么怎么样:

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

别忘了转义单引号 ;)


1
标签不会在 CSS 元素的 content 属性中被解释。 - Adam

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接