Everyday thoughts, but not every day

Inline emojis

This page has been edited from the original.
Date
03.08.2023
Changes
  1. removed misleading/irrelevant comments regarding line height
  2. improved JS to introduce line-height, in addition to font-size
  3. added comment on unicode OS emojis
  4. changed accented characters to fully show the extent of font ascender and descender

As I was updating the emojis for this site, I thought about having them scalable to fit within the line, no matter the font size. It's easy enough to scale SVG images nicely, compared to GIF. They are, after all, vectors.

But, how to determine the line height calculated from CSS?

I saw this question being asked several times online, with several different answers. After trying a few, I found one that worked, only to realise that it doesn't work. At least not as I'd intended. And that's partly because I was asking the wrong question in the first place.

The calculated line height as rendered to screen comes with a problem. Its value isn't the height of the text; it's the height of the text plus line spacing (leading). If the height of an inline image is set to the line-height, extra spacing is still added, which blows the line out of whack in comparison to its neighbours.

The obvious alternative would be to use calculated font size. The problem with this is that it sets the emojis too small; smaller than OS-installed ones which are unicode characters, essentially designed with similar metrics to typeface characters. I don't have that level of sophistication. (shrug)

So, line-height is too large, and font-size is too small. The perfect solution would be one of the typographical metrics that are unavailable, or not reliably available, through CSS. Consequently, I looked to the best traditions of bodging it, and took the average of the two values available to me. Job's a good 'un. (thumbup)

How it looks (proud)

Font size 3em: (smile) Ẫņ

Font size 2em: (smile) Ẫņ

Font size 1.5em: (smile) Ẫņ

Font size 1em: (smile) Ẫņ

Font size 0.8em: (smile) Ẫņ

How it works (nerd)

Wrapping it up, the image filename in this example is emojiname.svg, and the code is as follows.

HTML <span class="emoji">(emojiname)</span> JS function swapEmojis() { if (document.getElementsByClassName('emoji').length > 0 && document.createElement) { var e, els = document.querySelectorAll('.emoji'), i, img; for (e = 0; e < els.length; e++) { var lineheight = parseInt(window.getComputedStyle(els[e].parentNode).getPropertyValue('line-height')), fontsize = parseInt(window.getComputedStyle(els[e].parentNode).getPropertyValue('font-size')); i = els[e].innerText; /* retrieves '(emojiname)' from the SPAN element */ i = i.slice(1,-1); /* removes parentheses */ img = document.createElement('img'); img.src = '../images/' + i.toLowerCase() + '.svg'; if (!isNaN(lineheight)) { img.style.height = Math.ceil((lineheight + fontsize) / 2) + 'px'; img.style.verticalAlign = 'text-bottom'; } else { img.style.height = fontsize + 'px'; /* in the event that line-height cannot be calculated */ } img.style.width = img.style.height; img.alt = 'emoji: ' + i; /* the W3C standard is to apply alt='' to icons, but I consider emojis to convey meaning, so please yourself */ img.title = i; /* if the emoji is unclear to the viewer, holding the pointer over it will bring its name up as a 'tooltip' */ els[e].parentNode.replaceChild(img, els[e]); } } }

Filenames are lowercase, but the emoji call may be uppercase. Thus, if the image filename is lol.svg, and the HTML is <span class="emoji">(LOL)</span>, the result is (LOL)


* I set the font size in the CSS in em units—em being the width of the letter m—but the browser converts this to pixels for rendering to screen.


That method of image replacement causes the page to jump around, especially so for slower-loading ones, as text is removed and replaced. It's irritated me for a while, but I've only just got around to fixing it.

So I'm trying a different approach, whereby a blank img element is used as the placeholder and the emoji name is taken from the alt attribute. There may be some displacement as larger emojis are added to the page, but this will be minor in comparison to the removal of, say, (confused).

  • Previous method: (confused)
  • New method: confused

I've kept the old code so as to maintain backwards-compatibility, but going forward I'll see if this improves page rendering.

The line in the JS code to set the image width was also removed, so as to support wider, non-square emojis. Browsers seem to maintain aspect ratio for images when one of the dimensions is unspecified. Has that always been the case? Dunno. shrug

I guess I could read the image dimensions and set the height accordingly, but this seems to work for now.

HTML <img src="data:," alt="emojiname" class="emoji">

The src attribute is included solely for validation against standards; data:, is an empty local data file which will make no call to the server while at the same time satisfying the requirements of the HTML specification. In order to minimise layout jumping and not display an empty image icon, the image is hidden and given a rough estimate of width until the emoji is applied.

CSS img.emoji[src='data:,'] { visibility: hidden; width: 1rem; } JS function swapEmojis() { if (document.getElementsByClassName('emoji').length > 0 && document.createElement) { var e, els = document.querySelectorAll('.emoji'), i, img; for (e = 0; e < els.length; e++) { var lineheight = parseInt(window.getComputedStyle(els[e].parentNode).getPropertyValue('line-height')), fontsize = parseInt(window.getComputedStyle(els[e].parentNode).getPropertyValue('font-size')); if (els[e].hasAttribute('alt')) { i = els[e].alt; } else { i = els[e].innerText; i = i.slice(1,-1); } img = document.createElement('img'); img.src = '../images/' + i.toLowerCase() + '.svg'; if (!isNaN(lineheight)) { img.style.height = Math.ceil((lineheight + fontsize) / 2) + 'px'; img.style.verticalAlign = 'text-bottom'; } else { img.style.height = fontsize + 'px'; } img.alt = 'emoji: ' + i; img.title = i; els[e].parentNode.replaceChild(img, els[e]); } } }

I guess a criticism of this approach might be that there's no trace of an emoji in the event that JavaScript is disabled in the browser, whereas the previous version left a textual representation in parentheses. Emojis are only decoration though, albeit with some semantic meaning. So, it's unlikely to be a real-world problem here. Besides, in the event that JavaScript were disabled, a hell of a lot more important stuff would break.

Keep it simple, stupid

Yeah, I know what you're thinking: why not just include the emoji directly into the page code as an image element, fatty? And the truthful answer is that I can't recall why I opted for an image swap in the first place. It may have had something to do with sizing the images to fit the line height. It may have had something to do with being able to change the location of emojis within the site and only having to update a single line of JS code. It may have just been the challenge. Dunno.

HTML <img src="path/to/emojiname.svg" alt="emoji: emojiname" title="emojiname">

Whatever, this is just a lot more cumbersome and it would still need CSS and JS to hide the image while the page is loading (otherwise they blow the layout way out), and set the dimensions once loading is complete.


An alternative approach to using the alt attribute would be to add a second class to the img element. But identifying and stripping out multiple class names is less elegant IMO. Besides, alt is a requirement for validation; if it has to be present anyway it might as well be useful, right?