Наш путь по переходу на нативные эмодзи из Юникода

Переход с эмодзи картинок на нативные эмодзи из Юникода не был простым и включал в себя много сложностей. Например, большинство Linux-систем не поддерживают эмодзи Юникода, если вы не установили шрифт вручную. Я считаю эту статью руководством по выживанию, которое хотел бы иметь, когда сам пытался внедрить эмодзи.

Почему нужно переходить на юникод-эмодзи?

Решили перейти на эмодзи Unicode, чтобы использовать системные шрифты. Это также снижает количество изображений, которые загружаются на странице.Мы также были заинтересованы в улучшении производительности меню оценок на основе эмодзи, чтобы оно открывалось быстро, без запроса AJAX и с меньшим диапазоном прокрутки.

Сначала нужно было определить, поддерживается ли определённая эмодзи. Новые эмодзи-символы представлены в новых версиях спецификаций Unicode от Консорциума по Юникоду. Поэтому можно считать, что любая эмодзи, представленная в этой версии, поддерживается, если тест одного символа из спецификации проходит удачно.

Unicode10 – это текущий стабильный релиз. Но Консорциум по Юникоду работает над завершением версии Юникод 11. На официальном сайте организации есть полная таблица эмодзи с их представлением на разных платформах.

Тестирование поддержки нативных эмодзи

Можно протестировать эмодзи из каждой версии Юникода и кэшировать результаты локально (в localStorage) в карте соответствия. И затем проверять, поддерживается ли определенный символ. Если эмодзи не поддерживается, будем отображать картинку или CSS-спрайт в зависимости от ситуации.

Я не смог найти ни одной библиотеки или JSON документа, который связывал бы определённую эмодзи с соответствующей версией Юникода. Поэтому я создал собственный проектe mojipedia, который генерирует JSON карту привязки, emoji-unicode-version на npm.

Чтобы протестировать, работает ли эмодзи, мы отрисовываем его в теге <canvas> и проверяем пиксели в центре на цвет (если это чёрный, то тест провален). Также нужно убедиться, что эмодзи отрисовывается как один символ, поскольку некоторые из них состоят из нескольких знаков.

При выборе эмодзи для теста в каждой версии выбирайте цветную. Например, я изначально выбрал⚽:soccer: в версии Юникод 5.2. Но это чёрно-белая эмодзи, и тест всегда проваливался, поэтому я переключился на ⛵:sailboat:.

Мы сбрасываем карту привязки в случае, если меняется user-agent. Так как поддержка эмодзи меняется после обновления браузера или операционной системы. Мы также вручную добавляем GL_EMOJI_VERSION для очистки кэша, когда обновляем код проверки поддержки.

Вы можете посмотреть на нашу реализацию здесь: app/assets/javascripts/emoji/support/unicode_support_map.js, app/assets/javascripts/emoji/support/is_emoji_unicode_supported.js

Особенности отрисовки эмодзи в Internet Explorer

При отрисовке эмодзи в элементе <canvas> IE11 не понравился набор шрифтов, и он выводит маленькие чёрно-белые эмодзи, которые далеки от идеала.

Виновник этого – правило - apple-system

ctx.font=`${fontSize}px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`;

Но если вы укажете набор, состоящий только из эмодзи-шрифтов, IE11 отрисовывает красивые цветные эмодзи, как и ожидалось

ctx.font=`${fontSize}px "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`;
Полный набор шрифтовТолько эмодзи-шрифты
Особенности отрисовки эмодзи в Internet Explorer
Особенности отрисовки эмодзи в Internet Explorer - 2

Эмодзи Юникод 1.1 не отрисовываются как цветные при использовании полного набора шрифтов

Мы перешли на использование короткого списка только шрифтов в CSS, чтобы некоторые эмодзи из Юникод 1.1отображались как цветные.

Полный набор шрифтовТолько эмодзи-шрифты
Эмодзи Юникод 1.1 не отрисовываются как цветные при использовании полного набора шрифтов
Эмодзи Юникод 1.1 не отрисовываются как цветные при использовании полного набора шрифтов - 2

Отрисовка эмодзи при 16px

Мы используем размер шрифта в 16px при отрисовке эмодзи в элементе <canvas>. Мобильная версия Safari (iOS 9.3) всегда будет отображать их размером в 16 пикселей независимо от указанного размера шрифта.

Font-size в 32px отображается таким же размером, как и при 16px.

Размеры и положение отличаются

Выравнивание по линии текста в каждой платформе разное. Поэтому элементы, выровненные по центру на одной платформе, вылетают со строки в другой. Мы не нашли решения для вертикального центрирования, и решили пока оставить всё как есть.

Обратная совместимость эмодзи

Мы определили обратную совместимость для изображений и CSS-спрайтов в каждом элементе. Если у элемента эмодзи задан класс data-fallback-css-class, используем CSS-спрайт.

Применяем спрайты только для таких элементов, как меню оценок, которые показывают только одну эмодзи за раз и нуждаются в обратной совместимости на всех платформах, которые не поддерживают

<gl-emoji data-fallback-src="emoji-xxx.png" data-fallback-css-class="emoji-xxx">
xxx
</gl-emoji>

Мы используем document.registerElement(), чтобы привязать все теги <gl-emoji> на странице и проверить, нужна ли обратная совместимость. Применяем устаревший document.registerElement(), а не новый CustomElementRegistry.define(). Потому что последний работает только с синтаксисом классов ES2015.В нашем случае Babel транспилирует всё, а это делает невозможным использование синтаксиса классов.

Также необходимо использовать document.registerElement() polyfill для браузеров, которые его не поддерживают, например Safari.

Когда используем для обратной совместимости CSS-спрайт, то добавляем класс .emoji-icon в тэг <gl-emoji>. Этот класс CSS скрывает эмодзи, поэтому видно только фоновое изображение.

.emoji-icon {
/* Скрываем юникод-эмодзи */
color: transparent;
/* Скрываем юникод-эмодзи в IE */
text-indent: -99em;
/* ... */
}

Вы можете посмотреть на нашу реализацию <gl-emoji> здесь.

Эмодзи, состоящие из нескольких символов

Некоторые эмодзи состоят из нескольких символов, что может вызывать затруднения при работе с JavaScript. Array.from, String.prototype.codePointAt()– это ваши друзья в этом случае.

Последовательности без пробелов

Zero Width Joiner (ZWJ) последовательности или последовательности без пробелов состоят из нескольких символов эмодзи, соединённых символом ZWJ u{200D}, &zwj;(непечатный символ). Вы можете прочитать подробнее о ZWJ последовательностях здесь.

‍‍‍:family_mwgb:

[...'‍‍‍']
// ["", "‍", "", "‍", "", "‍", ""]

Модификатор цвета кожи

Модификаторы цвета кожи не нуждаются в символе ZWJ, чтобы быть отнесёнными к другому эмодзи. Вы можете прочитать подробнее о модификаторах цвета кожи здесь.

:man_tone5:

[...'']
// ["", ""]

Я решил протестировать несколько наборов модификаторов цвета кожи.И только если все пройдут тесты, считать модификаторы поддерживаемыми. Конечно, нашёлся ещё один отщепенец в macOS 10.12, где нет: horse_racing_toneX:, и я добавил отдельный тест на этот случай.

Расхождения в эмодзи

Эмодзи флагов

В Windows все эмодзи: flag_xx: отображаются как двухбуквенные международные коды стран вместо цветного флага, как это происходит в операционных системах Apple.

Эмодзи флагов

В Android 6 неизвестные флаги отображаются как двухбуквенные коды стран.

Эмодзи флагов - 2

В Android 7 неизвестные флаги отображаются как белые флаги с синими знаками вопроса.

Эмодзи клавиш в Windows

Эмодзи клавиш (цифр) неверно выглядят в Windows, но правильно отображаются в Chrome 57+, 3️⃣4️⃣5️⃣

БраузерРезультат
Chrome 55.0.2883.87 (64-bit) ❌
Эмодзи клавиш в Windows
Chrome 56.0.2924.87 (64-bit) ❌
Эмодзи клавиш в Windows - 2
Chromium 57.0.2984.0 (64-bit) ✅
Chrome 58.0.2999.4 (Official Build) canary (64-bit) ✅

Цвет кожи отделяется от базовой эмодзи, когда ширина ограничена

Начиная с Chrome 60+ (может с 59.1+), эмодзи

:wrestlers_toneX: и:handshake_toneX: стали разваливаться на базовый элемент и тон кожи, когда у их контейнера ограничена ширина.Что вызывает выход за границы или перенос на другую строку.

Я создал отчёт об ошибке в трекере Chromium. Но он был закрыт с пометной "WontFix" (не будем исправлять), поскольку эмодзи wrestlers и handshake больше не "классифицируются как Emoji_Base" в новом International Components for Unicode (ICU), который используются в Chrome.

Эти эмодзи были переклассифицированы. Но тогда они должны отображаться как два разных символа. Эмодзи:wrestlers_toneX: постоянно отображаются как два символа, но handshake_toneX всё ещё делятся на два, только когда ширина ограничена.

Windows 10MacOS
Цвет кожи отделяется от базовой эмодзи, когда ширина ограничена
Цвет кожи отделяется от базовой эмодзи, когда ширина ограничена - 2

Конфликт с функцией watch прототипа объекта

Я столкнулся с небольшой проблемой, когда код искал объект для связи с ключом watch⌚. В Firefox он вызывал Object.prototype.watch() и провоцировал хаос.

constemojiAliases={foo:'bar'};

// Ожидаем значение `undefined`, но получаем какую-то функцию
emojiAliases['watch']

Я исправил этот код, используя безопасный поиск Object.prototype.hasOwnProperty,

constemojiAliases={foo:'bar'};
Object.prototype.hasOwnProperty.call(emojiAliases,'watch')

Object.prototype.watch() на данный момент удалена из Firefox 58, а текущий стабильный релиз – это Firefox 59.0.2, поэтому вы, скорее всего, не столкнётесь с этим. Но всё ещё рекомендуется использовать Object.prototype.hasOwnProperty().

Что ещё нужно улучшить

Эмодзи на заказ

Мы работаем над добавлением пользовательских эмодзи на заказ (с поддержкой анимированных GIF). Он пока не слит с основным кодом, поскольку ещё нужно убедиться, что он работает с георепликацией.

Обратная совместимость на стороне сервера

Чтобы ускорить время показа эмодзи, если требуется обратная совместимость в виде картинок, можно сразу использовать их на стороне сервера.

Чтобы определить, поддерживаются ли эмодзи на сервере, при первом визите на страницу нужно устанавливать куки на стороне клиента (при помощи JavaScript) на основе карты соответствия поддержки Unicode. Куки отправляются при каждом запросе, поэтому мы могли бы читать их на сервере.

Есть несколько слоёв кэша в процессе отрисовки, что делает эту процедуру сложной для реализации. Поскольку нам бы потребовался ответ для обоих случаев, когда эмодзи поддерживаются, и когда нет. Также можно для каждого запроса обновлять отрисованную HTML- разметку соответствующим образом.

Обратная совместимость с помощью SVG

Использование для обратной совместимости EmojiOne SVG выгоднее текущих PNG-изображений. Это бы сэкономило трафик, и мы получили красивые, четкие эмодзи.

Мы даже могли бы извлекать SVG прямо из шрифтов операционной системы. Для более старых версий Windows можно использовать шрифты Windows 10, чтобы все элементы имели правильную форму.

EmojiOne SVG хорошо работают в macOS, поэтому здесь менять нечего.

Улучшение производительности

В настоящий момент нам приходится передавать большой файл digests.json в JavaScript-код, чтобы получать необходимую хеш-информацию для картинок обратной совместимости.

В качестве простого решения можно удалить эти хеши, чтобы уменьшить размер файла и передавать данные JSON асинхронно.