如何仅使用 CSS 来实现剧透引用?

How can I implement a spoiler quote with just CSS?

我有一个 blockquote 这样的:

<blockquote class="spoiler">Soopah sekkrit!</blockquote>

我想将其隐藏,仅当用户将鼠标悬停在其上时才显示。我现在正在用 JS 来做:

blockquote.addEventListener('mouseover', function() {
    this.style.height = this.offsetHeight + 'px';
    this.dataset.contents = this.innerHTML;
    this.innerHTML = '';
});
blockquote.addEventListener('mouseout', function() {
    this.style.height = '';
    this.innerHTML = this.dataset.contents;
});

有没有更好的方法,CSS?

它必须保持其 background-color 大小,并适用于具有自定义颜色的内容。如果可能的话,我也想做动画,让内容逐渐淡入。

是的,这可以通过 CSS 实现。本质上,您想让所有内容都不可见。在CSS中,这意味着透明。

首先使用hover pseudo-class inside the not pseudo-class:

.spoiler:not(:hover)

但是我们还需要select悬停剧透的所有子元素,来设置它们的颜色和背景:

.spoiler:not(:hover) *

并且我们将颜色和背景(仅针对子元素)设置为 transparent 以使它们对用户不可见。总计:

.spoiler:not(:hover), .spoiler:not(:hover) * { color: transparent }
.spoiler:not(:hover) * { background: transparent }

code { padding: 2px; background: #bbb }
a { color: #00f }
Hover: <blockquote class="spoiler">Some stuff <a>and a colored link</a> <code>and some code!</code></blockquote>

我们还可以加一个transition让它更流畅:

.spoiler { transition: color 0.5s } /* we have to put this outside the :hover to make it work fading both in and out */
.spoiler:not(:hover), .spoiler:not(:hover) * { color: transparent }
.spoiler * { transition: color 0.5s, background 0.5s }
.spoiler:not(:hover) * { background: transparent }

code { padding: 2px; background: #bbb; color: #000 } /* add color to prevent double transition */
a { color: #00f }
Hover: <blockquote class="spoiler">Some stuff <a>and a colored link</a> <code>and some code!</code></blockquote>

为了让用户清楚地知道块引用是可悬停的,您可以添加一些带有 ::after pseudo-element 的文本,以便在块引用未悬停时显示:

.spoiler { transition: color 0.5s; position: relative } /* relative position for positioning the pseudo-element */
.spoiler:not(:hover), .spoiler:not(:hover) * { color: transparent }
.spoiler * { transition: color 0.5s, background 0.5s }
.spoiler:not(:hover) * { background: transparent }
.spoiler::after {
    content: 'hover to view spoiler';
    position: absolute;
    top: 0; left: 0;
    color: transparent;
}
.spoiler:not(:hover)::after {
    color: #666;
    transition: color 0.3s 0.3s; /* delayed transition to keep the text from overlapping */
}

code { padding: 2px; background: #bbb; color: #000 }
a { color: #00f }
<blockquote class="spoiler">
    Some stuff <a>and a colored link</a> <code>and some code!</code>
    <blockquote class="spoiler">Nesting bonus!</blockquote>
</blockquote>

对于图像、svg(内联 SVG 可以非常精细地控制)、画布和所有这些花哨的东西,您必须使用 opacity 而不是 color。我们可以通过添加以下内容使其与这些一起使用:

.spoiler img { transition: opacity 0.5s, background 0.5s }
.spoiler:not(:hover) img { opacity: 0 }

这与我在 SOUP 中使用的非常相似:

.spoiler, .spoiler > * { transition: color 0.5s, opacity 0.5s }
.spoiler:not(:hover) { color: transparent }
.spoiler:not(:hover) > * { opacity: 0 }
/* fix weird transitions on Chrome: */
blockquote, blockquote > *:not(a) { color: black }

.spoiler, .spoiler > * { transition: color 0.5s, opacity 0.5s }
.spoiler:not(:hover) { color: transparent }
.spoiler:not(:hover) > * { opacity: 0 }
/* fix weird transitions on Chrome: */
blockquote, blockquote > *:not(a) { color: black }

/* some basic bg styles for demonstration purposes */
blockquote { background: #fed; margin: 1em 0; padding: 8px; border-left: 2px solid #cba }
code { background: #ccc; padding: 2px }
img { vertical-align: middle }
<blockquote class="spoiler">
  Soopah sekkrit text with <code>code</code> and <a href="#">links</a> and <img src="//sstatic.net/stackexchange/img/logos/so/so-logo-med.png" width="100" /> images!
  <p>You can also have paragraphs in here.</p>
  <ul><li>And lists too!</li></ul>
  <blockquote class="spoiler">Even nested spoilers work!</blockquote>
</blockquote>

这比 简单一些,适用于任意内容,包括图像甚至嵌套剧透! (参见上面的演示片段。)

唉,如果扰流板的任何子元素具有 color: inherit,此方法似乎会对 Chrome 产生奇怪的过渡效果。 (基本上,发生的事情是这些元素将 both 它们的文本颜色设置为透明并且它们的不透明度设置为 0。因为不透明度以乘法组合,因此组合过渡将显得更慢 - 中途淡入,当元素本身的不透明度为 50% 时,其中的 text 的不透明度为 50% × 50% = 25%。)我添加了一个额外的 CSS 上面例子的规则来解决这个问题,但它确实让事情有点复杂。


实际上在 SOUP 中做的事情略有不同。我将每个扰流板的内容包装在一个额外的内部 <div> 中,这让我可以将 CSS 进一步简化为:

.spoiler > div { opacity: 0; transition: opacity 0.5s }
.spoiler:hover > div { opacity: 1 }

.spoiler > div { opacity: 0; transition: opacity 0.5s }
.spoiler:hover > div { opacity: 1 }

/* some basic bg styles for demonstration purposes */
blockquote { background: #fed; margin: 1em 0; padding: 8px; border-left: 2px solid #cba }
code { background: #ccc; padding: 2px }
img { vertical-align: middle }
<blockquote class="spoiler"><div>
  Soopah sekkrit text with <code>code</code> and <a href="#">links</a> and <img src="//sstatic.net/stackexchange/img/logos/so/so-logo-med.png" width="100" /> images!
  <p>You can also have paragraphs in here.</p>
  <ul><li>And lists too!</li></ul>
  <blockquote class="spoiler"><div>Even nested spoilers work!</div></blockquote>
<div></blockquote>

这种方法的主要优点是简单和健壮:我不必使用 :not() 选择器,提高了与旧浏览器的兼容性,并且 transition 样式不会与其他样式冲突过渡可能定义在 inside 扰流板的元素上。此方法也不会受到上述 Chrome 颜色过渡异常的影响,因为它仅使用不透明度过渡。

总的来说,这是我推荐的方法。当然,缺点是您需要在 HTML.

中包含额外的 <div>

Ps. 请考虑提供一些方法让剧透永久可见,特别是对于触摸屏用户,他们可能会觉得很难 "hover" 光标在元素上。一个简单的解决方案是使用 JavaScript 单击事件处理程序来切换 spoiler class,例如像这样(使用 jQuery):

$('.spoiler').on( 'click', function (e) {
  $(this).toggleClass('spoiler');
  e.stopPropagation();
} );

$('.spoiler').on( 'click', function (e) {
  $(this).toggleClass('spoiler');
  e.stopPropagation();
} );
.spoiler > div { opacity: 0; transition: opacity 0.5s }
.spoiler:hover > div { opacity: 1 }

/* some basic bg styles for demonstration purposes */
blockquote { background: #fed; margin: 1em 0; padding: 8px; border-left: 2px solid #cba }
code { background: #ccc; padding: 2px }
img { vertical-align: middle }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<blockquote class="spoiler"><div>
  Soopah sekkrit text with <code>code</code> and <a href="#">links</a> and <img src="//sstatic.net/stackexchange/img/logos/so/so-logo-med.png" width="100" /> images!
  <p>You can also have paragraphs in here.</p>
  <ul><li>And lists too!</li></ul>
  <blockquote class="spoiler"><div>Even <a href="//example.com">nested</a> spoilers work!</div></blockquote>
<div></blockquote>

或者,如果您更喜欢使用委托事件处理(这样您就不必在每次通过 Ajax 加载包含剧透的新内容时都不断添加新的点击处理程序):

$(document).on( 'click', '.spoiler, .spoiler-off', function (e) {
  $(this).toggleClass('spoiler').toggleClass('spoiler-off');
  e.stopPropagation();
} );

$(document).on( 'click', '.spoiler, .spoiler-off', function (e) {
  $(this).toggleClass('spoiler').toggleClass('spoiler-off');
  e.stopPropagation();
} );
.spoiler > div { opacity: 0; transition: opacity 0.5s }
.spoiler:hover > div { opacity: 1 }

/* some basic bg styles for demonstration purposes */
blockquote { background: #fed; margin: 1em 0; padding: 8px; border-left: 2px solid #cba }
code { background: #ccc; padding: 2px }
img { vertical-align: middle }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<blockquote class="spoiler"><div>
  Soopah sekkrit text with <code>code</code> and <a href="#">links</a> and <img src="//sstatic.net/stackexchange/img/logos/so/so-logo-med.png" width="100" /> images!
  <p>You can also have paragraphs in here.</p>
  <ul><li>And lists too!</li></ul>
  <blockquote class="spoiler"><div>Even <a href="//example.com">nested</a> spoilers work!</div></blockquote>
<div></blockquote>

(这些应该适用于上面显示的任何 CSS 变体。)

这是一个效果很好、看起来不错并且过渡非常干净的策略

.spoiler {
    position: relative;
    display: inline-block;
    cursor: help;
}
.spoiler::before {
    content: 'psst026'; /* &hellip; */
    position: absolute;
    left: -2px;
    top: -2px;
    right: -2px;
    bottom: -2px;
    border-radius: 1px;
    font-size: .9rem;
    color: #e6578c;
    background: #ffe5e5;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    opacity: 1;
    transition: opacity 0.7s ease, transform 0.3s ease; /* hide faster than reveal */
}
.spoiler:hover::before {
    opacity: 0;
    transform: translateY(-50%)rotateX(80deg);
    transition: opacity 1.0s ease, transform 0.5s ease; /* slower reveal */
}

如果您在没有悬停的情况下使用 opacity: 0 设置父块的样式,则您无法添加任何样式来说明用户应该将鼠标悬停在页面的哪一部分上。

相反,如果我们添加一个 ::before 元素来覆盖子内容,那么我们可以在悬停时淡出它,并且仍然提供一个视觉指示去哪里。

堆栈片段中的演示

.spoiler {
    position: relative;
    display: inline-block;
    cursor: help;
}
.spoiler::before {
    content: 'psst026'; /* &hellip; */
    position: absolute;
    left: -2px;
    top: -2px;
    right: -2px;
    bottom: -2px;
    border-radius: 1px;
    font-size: .9rem;
    color: #e6578c;
    background: #ffe5e5;
    text-align: center;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 1;
    transition: opacity 0.7s ease, transform 0.3s ease; /* hide faster than reveal */
}
.spoiler:hover::before {
    opacity: 0;
    transform: translateY(-50%)rotateX(80deg);
    transition: opacity 1.0s ease, transform 0.5s ease; /* slower reveal */
}




/* demo styles */
blockquote {
  margin: 0
}
<p>
  Inline Spoiler <span class="spoiler" > Word </span>
</p>

<p class="spoiler">
  Paragraph Text Block of a Spoiler
</p>

<blockquote class="spoiler">
  Block quote spoiler with super long text that wraps and wraps and wraps some more.
  
  Block quote spoiler with super long text that wraps and wraps and wraps some more.
  
  Block quote spoiler with super long text that wraps and wraps and wraps some more.
</blockquote>