Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I detect rendering support for emoji in JavaScript?

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?

like image 944
Connor Gurney Avatar asked Aug 08 '17 19:08

Connor Gurney


Video Answer


3 Answers

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

like image 57
Roko C. Buljan Avatar answered Oct 18 '22 20:10

Roko C. Buljan


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;
}
like image 33
pureth Avatar answered Oct 18 '22 20:10

pureth


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)
}
like image 4
Scott Weaver Avatar answered Oct 18 '22 20:10

Scott Weaver