How to make svg added as css ::after content scale to the size of the font

473 Views Asked by At

I have some generated html that has headers with appended anchors (no content, but with a unique id), and I'm adding a 'link icon' that pops up when you hover the header to make it easy to get a link directly to that header. Like github does with rendered markdown.

What I have so far works OK and has some nice benefits for me:

  • I don't have to modify the generated html
  • I can target all headers with the same selector, so the long SVG icon embedded via content() is not duplicated

The issue is that the icon size doesn't scale with with the font size of the header. Notice how the icon is a somewhat reasonable size when you hover h1-h2, but then it is way too big for h3-h6:

Target code shape

:where(h1, h2, h3, h4, h5, h6):hover span.icon.icon-link:after {
  /* example svg; real svg is much bigger */
  content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 100 100" class="keyicon"><circle cx="50" cy="50" r="50" fill="currentColor"/></svg>');
  margin-left: 0.1em;
  cursor: pointer;
}
<h1>Hello h1<a id="hello"><span class="icon icon-link"></span></a></h1>
<h2>World h2<a id="world"><span class="icon icon-link"></span></a></h2>
<h3>Test h3<a id="test"><span class="icon icon-link"></span></a></h3>
<h4>Foo h4<a id="foo"><span class="icon icon-link"></span></a></h4>
<h5>Bar h5<a id="bar"><span class="icon icon-link"></span></a></h5>
<h6>Blah h6<a id="blah"><span class="icon icon-link"></span></a></h6>

Non-solution: Just embed the svg into the html

This is visually what I'm looking for, but is problematic for two reasons: 1. it duplicates the svg for each header; the real svg is big and this would be wasteful. 2. it's easier for me to modify css than the html generated by the tool I'm using.

:where(h1, h2, h3, h4, h5, h6) span.icon.icon-link svg {
  display: none;
  
  margin-left: 0.1em;
  cursor: pointer;
}

:where(h1, h2, h3, h4, h5, h6):hover span.icon.icon-link svg {
  display: inline;
  
  height: 0.8em;
  width: 0.8em;
}
<h1>Hello h1<a id="hello"><span class="icon icon-link"><svg viewBox="0 0 100 100" class="keyicon"><circle cx="50" cy="50" r="50" fill="currentColor"/></svg></span></a></h1>
<h2>World h2<a id="world"><span class="icon icon-link"><svg viewBox="0 0 100 100" class="keyicon"><circle cx="50" cy="50" r="50" fill="currentColor"/></svg></span></a></h2>
<h3>Test h3<a id="test"><span class="icon icon-link"><svg viewBox="0 0 100 100" class="keyicon"><circle cx="50" cy="50" r="50" fill="currentColor"/></svg></span></a></h3>
<h4>Foo h4<a id="foo"><span class="icon icon-link"><svg viewBox="0 0 100 100" class="keyicon"><circle cx="50" cy="50" r="50" fill="currentColor"/></svg></span></a></h4>
<h5>Bar h5<a id="bar"><span class="icon icon-link"><svg viewBox="0 0 100 100" class="keyicon"><circle cx="50" cy="50" r="50" fill="currentColor"/></svg></span></a></h5>
<h6>Blah h6<a id="blah"><span class="icon icon-link"><svg viewBox="0 0 100 100" class="keyicon"><circle cx="50" cy="50" r="50" fill="currentColor"/></svg></span></a></h6>

Notes

  • If I remove width="18" height="18" from the svg within content() then the icon becomes huge and takes up the entire view.
  • The question How to Make an SVG Image Dynamically Match the Height (1em) and the Baseline of Surrounding Text? is very similar, but the answer doesn't work in this context.
    • Specifically, setting width: 0.8em; height: 0.8em; in this selector has no effect
  • I also tried setting width and height with selector span.icon.icon-link svg { ... } which didn't work. (Are document nodes added by css content() selectable at all?)
  • I tried these in every combination I could think of.
1

There are 1 best solutions below

0
infogulch On

In this case I used content: url( ... ) but I could use background-image: url( ... ) instead. Thanks to @enxaneta for the observation.

This also has a nice little animation when hovering over the link icons:

body {
  font-size: 1.6em;
}

:where(h1, h2, h3, h4, h5, h6) a[id] {
  display: inline;
  background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg>');
  background-repeat: no-repeat;
  background-position: center center;
  background-size: 0;
  height: 1em;
  width: 1em;
  padding-left: 1em;
  padding-right: 0.5em;
  cursor: pointer;
  transition: background-size 32ms;
}

:where(h1, h2, h3, h4, h5, h6):hover a[id] {
  background-size: 0.8em;
  transition-duration: 64ms;
}

:where(h1, h2, h3, h4, h5, h6):hover a[id]:hover {
  background-size: 0.9em;
  transition-duration: 128ms;
}
<h1>Hello h1<a id="hello" href="#hello" title="Link to header"></a></h1>
<h2>World h2<a id="world" href="#world" title="Link to header"></a></h2>
<h3>Test h3<a id="test" href="#test" title="Link to header"></a></h3>
<h4>Foo h4<a id="foo" href="#foo" title="Link to header"></a></h4>
<h5>Bar h5<a id="bar" href="#bar" title="Link to header"></a></h5>
<h6>Blah h6<a id="blah" href="#blah" title="Link to header"></a></h6>

Editable: https://codepen.io/infogulch/pen/BaYBZxa?editors=1100