How can I detect rendering support for emoji in JavaScript?
Asked Answered
K

5

26

I want to add emoji to my site, but hide them if they're not supported on the platform, rather than showing little squares.

I have a feeling that this isn't possible, but does anybody disagree? How can this be done?

Kaminski answered 8/8, 2017 at 19:37 Comment(9)
I suppose the next evolution will be starting to code in emoji emojicode.orgAltorilievo
You can use Modernizr. Highly recommended for browser capability detection.Event
@Amy so Modernizr has Emoji detection?Prescribe
@RokoC.Buljan yes. browserleaks.com/featuresEvent
@Amy shouldn't that be an answer?Digiovanni
@Digiovanni i guess, but recommendations for off-site resources are off-topic, so its a comment. Feel free to submit as an answer if you like. I won't object.Event
I never realised Modernizr had emoji detection — thank you!Kaminski
@Amy questions asking to find off-site resources are off-topic. Citing off-site resource as an answer and telling how to use it to reach your goal is not. Existing answer was already updated with your comment though, I see.Digiovanni
@Digiovanni I understand perfectly well what the rules state. Do you think I have the freedom to take it a step further at my own discretion? Like I said, if it matters to you, you can submit it.Event
P
31

Paint a glyph (in the Emoji range that is the one most popular by vendors, in the range of Emojis by the Unicode Consortium like Happy face, Kiss, Sad face etc) to canvas and read a pixel using getImageData. If the pixel's Alpha channel data[3] you're interested-in is not transparent (like for example in the center of ￿) , else, it might be an Emoji 😗

function supportsEmoji () {
  const ctx = document.createElement("canvas").getContext("2d");
  ctx.canvas.width = ctx.canvas.height = 1;
  ctx.fillText("😗", -4, 4);
  return ctx.getImageData(0, 0, 1, 1).data[3] > 0; // Not a transparent pixel
}

console.log( supportsEmoji() );

or something like that...

Tested the above in Chrome, Firefox, IE11, Edge, Safari
Safari returns false and IE11 although has Emoji but without colors returned true.


Edit:
(As pointed in comments) Modernizr has a similar detection for Emoji - so you might give it also a go

Prescribe answered 8/8, 2017 at 19:40 Comment(26)
Thank you! Much appreciated.Kaminski
Interesting method.. I wonder if there's a more canonical solution though.Managua
@PatrickRoberts yeah, I'm afraid that some browsers might not show the empty square... rather the diamond-questionmark... therefore the transparent check would lead to false positive... Till now seems quite OK :)Prescribe
Indeed the detection source in Modernizr does something almost equivalent to thisManagua
This returns false for me on both Chrome and Firefox (on Linux), even though I can see the 😗 in both the text and the code of the answer.Quarter
@T.J.Crowder Thank you for reporting. I don't have Linux to debug 😑 (Returns true on Windows 10) What do you get for console.log(ctx.getImageData(0, 0, 1, 1))?Prescribe
@RokoC.Buljan - I get { "data": { "0": 0, "1": 0, "2": 0, "3": 0 }, "width": 1, "height": 1 }.Quarter
@T.J.Crowder I get for data {"0": 255,"1": 200,"2": 61,"3": 255}Prescribe
@T.J.Crowder might be something to do with positions... ctx.fillText("😗", -2, 4); on Linux?..Prescribe
@RokoC.Buljan - Isn't that what's in the code already?Quarter
@T.J.Crowder yes, I was just asking myself if that line and the position values -2, 4 might be erracit on LunixPrescribe
Can you detect if this is a mammoth? 🦣Pursuer
@Pursuer I'm not sure to followPrescribe
I see a mammoth on my iPhone but not on pcPursuer
If anyone knows another way to verify rendering support, please share. This approach is expensive in terms of resources, and it may not work properly depending on the device...Donothing
@BorisDetry I don't know of any other way frankly. The above can be optimized creating a smaller canvas. It was just a proof of concept (back in 2017) and I still use it in several projects without any issues so far (besides some comments here from whom I got no reply or improvement suggestions for the code above whatsoever).Prescribe
@Pursuer the code above is not made to detect the exact icon (mammoth, cat or fish) - it's just to check for if a given character has alpha at a certain x/y. As I said already we could together find a better value. Perhaps -2, 4 are not the best ones? The code works for me, and I cannot currently test on other OS. I'm kindly open for any sugg. & improvementsPrescribe
@RokoC.Buljan thank you for the answer. Maybe this should be integrated into the navigators API like window.navigatorDonothing
So the issue is ctx.fillText("🦣", -2, 4); gives true on both OSX where I see a mammoth AND on PC where I see a square with a question mark. The Emoji itself is a \u1F9A3 5 char emoji.Pursuer
@Pursuer a -4, 4 seems like better centering - but than again as pointed in Scott's answer might be faulty as well.Prescribe
I tested your script some more using the array in Scott's answer plus a duck emoji 🦆. For some reason I get ☹ supported:false on Win 10 Chrome latest. The rest work - any thoughts?Pursuer
@Pursuer my script does not involves any other specific emoji but ctx.fillText("😗", -4, 4); - therefore the 😗 emoji. Picking some coordinate of it and trying to get a color. A duck emoji was not in the context of this answer. Neither any other emoji symbol but 😗. The function does not accepts any argument emoji. So perhaps it's not the best to test for a specific emoji in particular (at least not in the current state).Prescribe
But it works for all except the ☹ when I change it to do function supportsEmoji(emoji) ... ctx.fillText(emoji, -4, 4);Pursuer
@Pursuer interesting. Any specific OS/browser on your side? I.e: here on Brave, Windows11 the ☹ seems like a variant (no colors) and the -4 4 with ctx.getImageData(0, 0, 1, 1) might be picking up a transparent pixel - invalidating it...Prescribe
Chrome latest Win 10Pursuer
@Pursuer as I said, that specific emoji you posted in comment looks like a non non-colorised Standardized Variant. Picking a color from such an emoji can result in false positives, since the picked color might end up as transparent.Prescribe
E
10

The flag emoji are a bit of an edge case to the answers posted here. Since if the flag emoji is not supported on a device, a fallback of the country code is rendered instead. E.g. the union jack flag (🇬🇧) will become -> GB.

Windows 10 doesn't support the Emoji Flags.

This script uses a similar method to the existing answer, but instead checks to see if the canvas is greyscale. If there are colours in the canvas, we know an emoji was rendered.

function supportsFlagEmoji() {
  var canvas = document.createElement("canvas");
  canvas.height = 10;
  canvas.width = canvas.height * 2;
  var ctx = canvas.getContext("2d");
  ctx.font = canvas.height + "px Arial";
  ctx.fillText("🇬🇧", 0, canvas.height);
  var data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
  var i = 0;
  while (i < data.length) {
    if (data[i] !== data[i + 1] || data[i] !== data[i + 2]) return true;
    i += 4;
  }
  return false;
}
console.log(supportsFlagEmoji())
Expunge answered 4/9, 2019 at 13:29 Comment(1)
Exactly what I needed, thank you!. @RokoC.Buljan, there is a specific issue about flags for example in windows 10, and we dont want to check for flag support for each and every possible flag. Testing up against one flag is enough, and the method above is right on target.Neritic
N
4

The accepted answer isn't working on my system (Windows 10/chrome). It returns true for the unsupported glyphs, and it does not appear to be accurate for emojis that don't cover the center pixel but are in fact rendered (☹).

The unsupported glyphs that my system paints, seemingly interchangeably, are : enter image description here and enter image description here. Certainly the center pixel of the first is opaque, perhaps that's why.

An alternate method would be to paint the unsupported glyph into a canvas (you can use \uFFFF which is a guaranteed non-character, see https://en.wikipedia.org/wiki/Specials_(Unicode_block)), then "checksum" or really just sum the results of getImageData(), and check your emojis against those numbers. This way you aren't dependent on the implementation of the glyph, as long as the system is displays a 'not found' symbol. Firefox appears to show a unique hex code of an unsupported emoji, so this method wouldn't work for that browser or similar configurations.

A second way, more accurate but far slower in my usage, would be to compare using toDataURL():

I've included the "double unsupported" emoji as well in the snippet tests that run all three methods. For the record, I'm using the "rgb sum" method right now in a demo on CodePen to make sure blank emojis don't appear in the output, and it doesn't filter out anything erroneously.

var c = document.createElement("canvas")
var ctx = c.getContext("2d");
const em = 16;
c.width = em;
c.height = em;

["\uFFFF", "\uFFFF\uFFFF", "🖤", "☺", "☹", "💀", "☠", "🩴"].forEach(e => console.log(e + " isSupported (center pixel method):" + supports(e) + ", (checksum method):" + supports2(e) + ", (toDataURL method):" + supports3(e)));


//using center pixel detection
function supports(e) {
  let ctx = document.createElement("canvas").getContext("2d");
  ctx.fillText(e, -2, 4);
  return ctx.getImageData(0, 0, 1, 1).data[3] > 0; // Not a transparent pixel
}

//using checksum method
function supports2(e) {
  //https://en.wikipedia.org/wiki/Specials_(Unicode_block) (NON-Character)
  var unsupported = ["\uFFFF", "\uFFFF\uFFFF"].map(b => {
    ctx.clearRect(0, 0, em, em);
    ctx.fillText(b, 0, em);
    let d = ctx.getImageData(0, 0, em, em).data
    let sum = d.reduce((acc, cur) => {
      return acc + cur
    })
    return sum
  });

  ctx.clearRect(0, 0, em, em);
  ctx.fillText(e, 0, em);
  let d = ctx.getImageData(0, 0, em, em).data
  let sum = d.reduce((acc, cur) => {
    return acc + cur
  })
  return !unsupported.some(b => b == sum)
}

//using toDataURL() method
function supports3(e) {
  ctx.clearRect(0, 0, em, em);
  ctx.fillText(e, 0, em);
  let emo = c.toDataURL()

  ctx.clearRect(0, 0, em, em);
  ctx.fillText('\uFFFF', 0, em);
  let bad1 = c.toDataURL()
  ctx.clearRect(0, 0, em, em);
  ctx.fillText('\uFFFF\uFFFF', 0, em);
  let bad2 = c.toDataURL()

  return (emo != bad1) && (emo != bad2)
}
Nummary answered 22/4, 2021 at 0:7 Comment(3)
A :mammoth: \u1F9A3 is \uD83E\uDDA3 according to russellcottrell.com/greek/utilities/SurrogatePairCalculator.htmPursuer
The same, accepted answer isn't working for me. Anyways all these canvas methods look pretty heavy if you need to do it on multiple emojis.Donothing
The "checksum" (it's just summation of all red/green/blue values) method is pretty fast. ToDataURL might be accurate, but I couldn't use it, too slow.Nummary
E
1

In case if someone is looking for a library, then here is the one if-emoji. It uses the same approach Roko has used in his answer.

Escargot answered 31/1, 2019 at 6:6 Comment(0)
S
0

Here's a method that relies on the fact that emojis are almost always square aspect ratio (or close to it). Browsers that don't support emojis will usually render them as a thin rectangle, "", or for grapheme cluster emojis, a sequence of characters, "🤦​🏻♂".

/** Check if browser probably supports native emoji rendering
 * @param {string} emoji - which emoji to use to test; a multicharacter emoji (grapheme
 *  cluster) will have lower chance of false positives; common emojis (smiley face),
 *  sometimes have unicode alternatives the browser will render
 * @returns {boolean}
 */
function supports_emoji(emoji="🤦🏼‍♂️"){
    const el = document.createElement("div");
    el.style = "position:absolute;visibility:hidden;";
    el.textContent = emoji;
    document.body.appendChild(el);
    const size = el.getBoundingClientRect();
    el.remove();
    // expecting a roughly square emoji; incorrectly rendered grapheme clusters will be
    // rendered as multiple characters (width > height), while single character emojis are
    // usually rendered as a thinner rectangle (height > width)
    const square = Math.abs(1-size.width/size.height);
    return square < .23;
}

As a side note, you may consider using the Noto Emoji font and not worry about checking for emoji support.

Surgeonfish answered 16/1, 2023 at 17:23 Comment(1)
I wanted to make a minimal reproducible example using the snippet editor and then run against ["\uFFFF", "\uFFFF\uFFFF", "🖤", "☺", "☹", "💀", "☠", "🩴"] from Scott's answer. But for some reasons I could not make it work. Would you be kind to make a snippet from your code? And perhaps explain the strange rendering of "🤦🏼‍♂️" in the snippet editorPursuer

© 2022 - 2024 — McMap. All rights reserved.