Simulate external stroke ::before pseudo elements: problem with transparent text

夙愿已清 提交于 2019-12-08 14:35:18

问题


I find it difficult to believe that there is no standard and simple (and browser-independent) way to put a stroke effect around the outside of text using CSS.

We do have -webkit-text-stroke but for some odd reason the stroke is centred around the border of the text rather than outside it, as bemoaned here.

So I'm trying to implement a workaround based on this idea, which places the stroked text in a pseudo element behind the original un-stroked text. I've demonstrated this in this jsfiddle, with the following code:

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 works fine, except that if the text itself has some transparency then you see the dark stroke underneath, as shown in this variant (the only change is to peg the opacity of the text back to 0.3). As you can see, the black from the stroked element is leaching through into the text (in the top line).

So is there another neat trick to use to overcome this problem? I guess it's possible to add another pseudo element between the stroked layer and the un-stroked layer, with a pure white text (or one to match the background), but I'd like to apply this technique in a context where I don't know the colour of the background in advance... e.g. where this is laid over an arbitrary user-selected image. For this reason, I've set the background of the body to none in the above example.


回答1:


Here is an idea where you can consider mix-blend-mode and a combination of text-shadow to approximate this. The tricky part is to adjust the shadow in case you want a bigger stroke:

.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);
}
<div class="text"><span>Some text here</span></div>

Using CSS variable will probably make it easier to adjust:

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

If you want a transparent color for the text you can duplicate it using pseudo element:

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



回答2:


The easiest, and with best browser support might actually be SVG.
You can set up approximately the same thing you did with the ::before, with the difference that the background stroked version can have a mask, which will let only the outer-line visible.
From there, you can simply append a copy of the same text over, and you'll be able to apply the opacity as you wish, both on the stroke and the fill:

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>



回答3:


Solution using SVG filters

To get a stroke around the text, you can use a combined SVG filter consisting of successively applied filters: feMorphology, feComposite and feColorMatrix.

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:


The CSS-property paint-order could do the trick:

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

Note: This is an experimental technology

Unfortunately its not supported by every browser (browser compatibility table) and the behavior could change in the future.


The result will look like this in Firefox 65.0.2:



来源:https://stackoverflow.com/questions/54999526/simulate-external-stroke-before-pseudo-elements-problem-with-transparent-text

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