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.
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>
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With