如何判斷當(dāng)前瀏覽器是否支持某一個emoji

By Yuanyue.韋

理論上所有聊天時能用的小圖表情(繪文字)都算是 emoji,本文所說的 emoji 是指 Unicode 存在對應(yīng)編碼的系統(tǒng)內(nèi)置的的小圖表情。

在我們平時輸入的文本中,emoji十分常見,但在顯示和編碼上它卻是一個幺蛾子般的存在。

它詭異就詭異在:

  1. 每一個設(shè)備對于 emoji 的實現(xiàn)差異性太大:不僅和系統(tǒng)有關(guān),還和系統(tǒng)版本、系統(tǒng)的某個補(bǔ)丁、系統(tǒng)內(nèi)的某些字體、瀏覽器品牌、瀏覽器版本都有關(guān)系,所以可以這么說,不到瀏覽器渲染完成的最后一刻,你都沒辦法判斷一個 emoji 是否在當(dāng)前頁面內(nèi)被支持。
  2. emoji 的編碼問題:首先,emoji在 unicode 中的分布非常零散。網(wǎng)上能搜到很多所謂能替換字符串中 emoji 的正則表達(dá)式,它們實現(xiàn)的原理都是在 unicode 中圈定了一個范圍,這個范圍是大部分 emoji 所在的區(qū)域(大多落在U+1f300 - U+1f9cf),以此來區(qū)分是否是 emoji。但這樣的方式只能選出部分 emoji,因為emoji 在 unicode 中是隨著 unicode 版本的增加而不斷增加的,“地盤”也不斷擴(kuò)大。從 Unicode 的官網(wǎng)(http://www.unicode.org/charts/PDF/ )和維基百科(https://en.wikipedia.org/wiki/Emoji )中我們也可以看到 U+2030 - U+3000 和其他零散的地方其實也穿插著包含了不少 emoji。不僅如此,它的編碼也不僅僅只有2或4字節(jié),因為有的 emoji 還包含零寬連字(zero-width joiner),比如一家四口人的“???????????”,它就有25字節(jié),UTF16編碼是“1f469-200d-1f469-200d-1f466-200d-1f466”,可以看出是由四個4字節(jié)的人臉 emoji + 三個零寬連字符(U+200d)組成??梢哉J(rèn)為,emoji 的字節(jié)數(shù)是無上限的,萬一哪天出了一個 AKB48 全體成員的 emoji 呢?太多的情況普通的正則不能匹配出來。

emoji 這些詭異的特性給開發(fā)者們帶來不少問題,比如:

  1. MySQL 5.5.3以下的版本用UTF8編碼存儲最多只支持3字節(jié),然而 emoji 這貨哪怕忽略零寬連字,多數(shù)都有4字節(jié)。
  2. 要怎么判斷當(dāng)前頁面上哪些 emoji 能正常顯示?畢竟把頁面上所有 emoji 無腦替換成小圖片太不優(yōu)雅了,能用系統(tǒng)自帶的 emoji 也沒必要多一個網(wǎng)絡(luò)請求去載入圖片。

對于第一個問題,升級 MySQL 并且用CHARACTER SET utf8mb4把字符集設(shè)置為UTF8mb4是最合適的處理方法,否則會出現(xiàn)在 emoji 處字符串被截斷或存入數(shù)據(jù)庫后全變成了“???”。

對于第二個問題,google 和 stackoverflow 上搜了一大圈都沒找到和我有相同疑問的開發(fā)者,所以在群里討論后想了一個方法,綜合考慮了以下幾個方面:

  1. Modernizr 判斷是否支持 emoji 使用的是在 canvas 上打印出一個考拉??的 emoji(Modernizr只是檢測瀏覽器特性,不對某個 emoji 做針對性判斷),通過判斷畫布上是否依然為空白畫布來區(qū)分。這個方法的弊端是,有的系統(tǒng)對于不支持的字符顯示的并非是空白,而是一個方框,也是可以在 canvas 上畫出來的;其次是效率堪憂,每一次需要遍歷長度為(devicePixelRatio*12 - 1)^2的數(shù)組,即121或529個像素,講真對 canvas 做 getImageData 操作效率上并不高。
  2. Twitter開源了一個關(guān)于 emoji 的庫:twemoji,為了統(tǒng)一各個平臺對 emoji 的顯示,把所有 emoji 都替換成小圖片。它用了一個巨大無比的list(https://github.com/twitter/twemoji/blob/gh-pages/twemoji.js#L236 )把所有 emoji 正則匹配出來。這需要有人一直不斷去維護(hù)這個list讓它和最新的 unicode 保持同步。
  3. 群里某位穿女裝成名的前端工程師提出,emoji 的一個特性是作為一張圖片它不能用代碼上色,考慮采取對 canvas 上的 emoji 做兩次 fillStyle的方法,判斷前后像素的 RGBA 是否完全相同,相同則為 emoji。這個方法的弊端是,并不是所有系統(tǒng)的 emoji 都是圖片,有的系統(tǒng)里 emoji 就是一種字體,是可以被上色的。

綜上,現(xiàn)在給出一種解決方案,在之前這位穿女裝成名現(xiàn)在在度蜜月沒辦法碰代碼的前端工程師考慮的基礎(chǔ)上改進(jìn),主要利用了在大部分系統(tǒng)下emoji不能被上色的原理,對于那些 emoji 可以被上色的平臺做降級處理,在2*2的 canvas 上做像素比對。

這個方法對于用字體顯示 emoji 并且對不識別的字符會顯示方框或問號的平臺不能準(zhǔn)確區(qū)分,但這樣的平臺是相當(dāng)罕見的,至少目前還沒有見到過。

const getTextFeature = (text, color) => {
  try {
    const canvas = document.createElement('canvas');
    /*
      因為進(jìn)行scale以后的圖案區(qū)域?qū)嶋H上不能確定,
      理論上應(yīng)該只在(0,0,1,1),但有的也會在它周圍的像素里,
      綜合效率的考慮,給一個2*2的范圍是比較合適的;
    */
    canvas.width = 2;
    canvas.height = 2;

    const ctx = canvas.getContext('2d');
    ctx.textBaseline = 'top';
    ctx.font = '100px sans-serif';
    ctx.fillStyle = color;
    ctx.scale(0.01, 0.01);
    ctx.fillText(text, 0, 0);

    const imageData = ctx.getImageData(0, 0, 2, 2).data;
    // 在一些系統(tǒng)里Uint8ClampedArray不支持常規(guī)的數(shù)組方法,需要轉(zhuǎn)換一下
    const imageDataArr = [];
    for (let i = 0; i < imageData.length; i++) {
      imageDataArr[i] = imageData[i];
    }

    return imageDataArr.reduce((a, b) => (a + b), 0) > 0 ? 
      imageDataArr.toString() : false;
  } catch (e) {
    return false;
  }
};

const distribute = (text, mode) => {
  const feature = getTextFeature(text, '#000');
  return mode ? (feature && feature === getTextFeature(text, '#FFF'))
    : feature;
};

const ifEmoji = (text) => {
  /*
    用一個最悠久而常見 emoji 來判斷當(dāng)前系統(tǒng)是使用圖片還是字體來顯示 emoji,
    若是圖片則去做上色比對,否則只對可見性做判斷。
  */
  const mode = distribute('??');
  return distribute(text, mode);
}

export default ifEmoji;

Usage:

ifEmoji('蛤') // => false
ifEmoji('??') // => If your system / browser supports this emoji character correctly, the returned value will be true.

推薦閱讀:
http://crocodillon.com/blog/parsing-emoji-unicode-in-javascript

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容