【问题标题】:Simulate external stroke ::before pseudo elements: problem with transparent text模拟外部笔划 ::before 伪元素:透明文本的问题
【发布时间】:2019-07-26 17:26:30
【问题描述】:

我很难相信没有标准且简单(且独立于浏览器)的方式来使用 CSS 在文本的 外部 周围放置描边效果。

我们确实有-webkit-text-stroke,但出于某种奇怪的原因,笔划以文本的边框为中心,而不是在它之外,如bemoaned here。 p>

所以我正在尝试实现一个基于this idea 的解决方法,它将描边文本放置在原始未描边文本后面的伪元素中。我已经在this jsfiddle 中演示了这一点,代码如下:

var jQueryAttr = function(selector, attr, setterFunction) {
  document.querySelectorAll(selector).forEach((el, i) => {
    el.setAttribute(attr, setterFunction.call(el, i, attr));
  });
};

jQueryAttr('.myclass', 'data-myclass', function(index, attr) {
  return this.innerHTML;
});
body {
  background: none;
}

.basic {
  color: rgba(186, 218, 85, 1);
  font: 2.5em Georgia, serif;
}

.myclass {
  position: relative;
  background: transparent;
  z-index: 0;
}

.myclass::before {
  content: attr(data-myclass);
  position: absolute;
  -webkit-text-stroke: 0.2em rgba(0, 0, 0, 1);
  z-index: -1;
}

.anotherclass {
  -webkit-text-stroke: 0.2em rgba(0, 0, 0, 1);
}
<p class="basic">Text without any stroke</p>
<p class="myclass basic">Text with outer stroke</p>
<p class="anotherclass basic">Without the trick applied</p>

这很好用,除了如果文本本身有一些透明度,那么你会看到下面的黑色笔划,如this variant 所示(唯一的变化是将文本的不透明度重新设置为 0.3)。如您所见,描边元素中的黑色正在渗入文本(在顶行)。

那么有没有另一个巧妙的技巧可以用来解决这个问题?我想可以在描边层和未描边层之间添加另一个伪元素,带有纯白色文本(或与背景匹配的文本),但我想在我 事先不知道背景的颜色...例如它放置在任意用户选择的图像上。为此,我在上面的示例中将bodybackground 设置为none

【问题讨论】:

    标签: javascript html css svg


    【解决方案1】:

    这是一个想法,您可以考虑mix-blend-mode 和文本阴影的组合来近似此。棘手的部分是调整阴影以防您想要更大的笔触:

    .text > span {
      font-family:sans-serif;
      font-size:60px;
      font-weight: bold;
      color:#fff; /*use white*/
      /*create the stroke around text*/
      text-shadow:
        2px 0  0px #000,
        0 2px 0px #000,
        2px 2px 0px #000,
        -2px 0 0px #000,
        0 -2px 0px #000,
        -2px -2px 0px #000,
        -2px 2px 0px #000,
        2px -2px 0px #000;
      mix-blend-mode: darken; /*everything is more dark than white so we always see the background */
    }
    .text {
      display:inline-block;
      padding:20px;
      background:linear-gradient(to right,red, blue);
    }
    &lt;div class="text"&gt;&lt;span&gt;Some text here&lt;/span&gt;&lt;/div&gt;

    使用 CSS 变量可能会更容易调整:

    .text > span {
      font-family:sans-serif;
      font-size:60px;
      font-weight: bold;
      color:#fff; /*use white*/
      /*create the stroke around text*/
      text-shadow:
        var(--s,2px) 0  var(--c,0) #000,
        0 var(--s,2px) var(--c,0) #000,
        var(--s,2px) var(--s,2px) var(--c,0) #000,
        calc(-1*var(--s,2px)) 0 var(--c,0) #000,
        0 calc(-1*var(--s,2px)) var(--c,0) #000,
        calc(-1*var(--s,2px)) calc(-1*var(--s,2px)) var(--c,0) #000,
        calc(-1*var(--s,2px)) var(--s,2px) var(--c,0) #000,
        var(--s,2px) calc(-1*var(--s,2px)) var(--c,0) #000;
      mix-blend-mode: darken; /*everything is more dark than white so we always see the background */
    }
    .text {
      display:inline-block;
      padding:20px;
      background:linear-gradient(to right,red, blue);
      background-size:cover;
      background-position:center;
    }
    <div class="text"><span>Some text here</span></div>
    
    <div class="text" style="--s:4px;--c:2px;background-image:url(https://picsum.photos/800/600?image=1069)"><span>Some text here</span></div>
    
    <div class="text" style="--s:6px;--c:4px;background-image:url(https://picsum.photos/800/600?image=1051)"><span>Some text here</span></div>

    如果你想要文本的透明颜色,你可以使用伪元素复制它:

    .text > span {
      font-family:sans-serif;
      font-size:60px;
      font-weight: bold;
      position:relative;
      display:inline-block;
    }
    .text > span::before,
    .text > span::after {
      content:attr(data-text);
    }
    .text > span::before {
      color:#fff; /*use white*/
      /*create the stroke around text*/
      text-shadow:
        var(--s,2px) 0  var(--c,0) #000,
        0 var(--s,2px) var(--c,0) #000,
        var(--s,2px) var(--s,2px) var(--c,0) #000,
        calc(-1*var(--s,2px)) 0 var(--c,0) #000,
        0 calc(-1*var(--s,2px)) var(--c,0) #000,
        calc(-1*var(--s,2px)) calc(-1*var(--s,2px)) var(--c,0) #000,
        calc(-1*var(--s,2px)) var(--s,2px) var(--c,0) #000,
        var(--s,2px) calc(-1*var(--s,2px)) var(--c,0) #000;
      mix-blend-mode: darken; /*everything is more dark than white so we always see the background */
    }
    .text > span::after {
      position:absolute;
      top:0;
      left:0;
      color:rgba(0,255,0,0.4); 
    }
    .text {
      display:inline-block;
      padding:20px;
      background:linear-gradient(to right,red, blue);
      background-size:cover;
      background-position:center;
    }
    <div class="text"><span data-text="Some text here"></span></div>
    
    <div class="text" style="--s:4px;--c:2px;background-image:url(https://picsum.photos/800/600?image=1069)"><span data-text="Some text here"></span></div>
    
    <div class="text" style="--s:6px;--c:4px;background-image:url(https://picsum.photos/800/600?image=1051)"><span data-text="Some text here"></span></div>

    【讨论】:

    • 在您的方法中,如果我们确实想要文本的特定颜色(不完全透明),例如rgba(186, 218, 85, 0.5),那么您是否建议根据我的帖子使用::before 技巧,以便将彩色文本(无笔划)放在顶部,背景(有笔划)在下方?此外,我之前尝试过多阴影解决方法(超过 4 或 8 个阴影......以获得平滑效果)但我拒绝它的部分原因是我需要控制笔画的不透明度......很难实现重叠阴影,其中不透明度趋于建立1.0
    • @drmrbrewer 是的,正确,这是在这种情况下使用影子的缺点。不容易控制且难以添加不透明度 .. 对于您的文本颜色,是的,我会使用伪元素,其上方(或下方)带有一些不透明度的透明父级文本……顺便说一下,我会尝试编辑我的回答以防我发现不透明度的技巧
    • 如果没有背景会怎样?
    • @drmrbrewer 您将拥有白色 .. 混合考虑两种颜色 .. 使用白色和变暗时的技巧是,在所有情况下,我们都会选择背景,因为它是最暗的(一切都更多比白色更黑)......没有背景,白色会被选中
    • @Kaiido 不,我应该添加 display:inline-block,忘了它.. 需要包装并且应该可以正常工作,但内联元素上的伪元素不好。 (已编辑)
    【解决方案2】:

    最简单、最好的浏览器支持实际上可能是 SVG。
    您可以设置与 ::before 大致相同的设置,不同之处在于背景描边版本可以有一个蒙版,这将只让外线可见。
    从那里,您可以简单地附加相同文本的副本,并且您可以根据需要在描边和填充上应用不透明度:

    body{
      background-image:url(https://picsum.photos/800/200?image=1051);
      font-family: sans-serif;
    }
    svg {
      font-size: 40px;
      font-weight: bold;
    }
    .textStroke {
      stroke: black;
      stroke-width: 12px;
      stroke-linejoin: round;
    }
    .visibleText {
      fill: rgba(186, 218, 85, 1);
      transition: fill-opacity .5s linear;
    }
    .visibleText:hover {
      fill-opacity: 0;
    }
    <svg width="350">
      <defs>
        <!-- we type it only once -->
        <text x="10" y="55" id="txt">Text with outline</text>
        <mask id="mymask">
          <!-- white => visible, black => tansparent -->
          <rect x="0" y="0" width="450" height="70" fill="#FFF"></rect>
          <use xlink:href="#txt" fill="#000"/>
        </mask>
      </defs>
      <!-- our stroked text, with the mask -->
      <use xlink:href="#txt" mask="url(#mymask)" class="textStroke"/>
      <!-- fill version -->
      <use xlink:href="#txt" class="visibleText"/>
    </svg>

    【讨论】:

    • 同意,我确信 SVG 可以轻松解决这个问题。太糟糕了,由于重叠,我们无法对笔画应用不透明度(OP 也希望在我的回答中这样评论)
    • 是的,但会导致颜色不均匀,因为我们会在相邻字母的笔划之间重叠。
    • @TemaniAfif 哦,我现在看到了,在 Chrome 上... stoopids。在 FF 上工作正常,我会检查那里发生了什么,但设置 opacityworks 无处不在:jsfiddle.net/rv7sjLof
    • 啊,又是 Chrome 和 Fiferox 之间的冲突 .. 是的,忘记了这里微不足道的不透明度。我试图使用 rgba 颜色:x
    • 对于文本的笔画不透明度,这将是一个 Chrome 错误,当我有时间找到回归范围时我会打开一个(webkit 因为它是正确的,所以它可能是回归)。 They do it correctly for pathes。虽然我不明白为什么不能控制每个元素的opacity(描边和填充)而不是控制它们的 的 alpha 值,但即使在有问题的 Chrome 上也可以通过使用two masks 来实现但是我们可以have fun
    【解决方案3】:

    使用 SVG 滤镜的解决方案

    要在文本周围画一个笔触,您可以使用由连续应用的过滤器组成的组合 SVG 过滤器:feMorphologyfeCompositefeColorMatrix

    body{
      background-image:url(https://picsum.photos/800/800?image=1061);
      background-size:cover;
      font-family: serif;
      
    }
    <svg  viewBox="0 0 350 350" >
      <defs>
        <filter id="groupborder" filterUnits="userSpaceOnUse"
                x="-20%" y="-20%" width="300" height="300">
          <feMorphology operator="dilate" in="SourceAlpha"
                        radius="5" result="e1" />
          <feMorphology operator="dilate" in="SourceAlpha"
                        radius="2" result="e2" />
          <feComposite in="e1" in2="e2" operator="xor"
                       result="outline"/>
          <feColorMatrix type="matrix" in="outline"
                         values="0 0 0 0 0.1
                                 0 0 0 0 0.2
                                 0 0 0 0 0.2
                                 0 0 0 1 0" result="outline2"/>
          <feComposite in="outline2" in2="SourceGraphic"
                       operator="over" result="output"/>
        </filter>
      </defs>
      <g id="group" filter="url(#groupborder)">
        <text x="10" y="100"  stroke-width="1" fill="#1D3A56"  font-family="serif" font-size="30" font-weight="700" > Text with outline </text>
      </g>
    </svg>

    【讨论】:

      【解决方案4】:

      CSS 属性 paint-order 可以解决问题:

      .stroke {
        -webkit-text-stroke: 0.2em rgba(0, 0, 0, 1);
        paint-order: stroke fill;
      }
      
      .basic {
        color: rgba(186, 218, 85, 1);
        font: 2.5em Georgia, serif;
      }
      <span class="basic">Text without stroke</span><br>
      <span class="basic stroke">Text with stroke</span>

      注意:这是experimental technology

      很遗憾,它不受每个浏览器 (browser compatibility table) 的支持,并且该行为将来可能会发生变化。


      在 Firefox 65.0.2 中,结果将如下所示:

      【讨论】:

      • 你能分享一下结果的截图,让我们看看它的样子吗?支持似乎很差
      • 好消息:浏览器支持似乎正在提高。 :)
      猜你喜欢
      • 1970-01-01
      • 2019-06-13
      • 1970-01-01
      • 1970-01-01
      • 2015-02-08
      • 1970-01-01
      • 2011-06-01
      • 2011-11-22
      • 2022-11-10
      相关资源
      最近更新 更多