Не поймите меня неправильно, мне нравится интерфейс поиска эмодзи в Twitter. Как кодировщик, мне просто нравится думать о том, как что-то можно улучшить. Так что в этом нет ничего особенного.



Сначала я хочу показать вам готовую версию созданного мной средства поиска эмодзи. Затем мы рассмотрим решенные проблемы. Это идеально? Конечно, нет. Может быть, даже лучше. Но определенно отличается и имеет несколько преимуществ.

Вот пользовательский интерфейс для поиска смайлов, который я создал на обычном JavaScript с добавлением CSS.

Пользовательский интерфейс Twitter великолепен, однако… существует несколько теоретических проблем:

  1. Трудно перемещаться между большими группами смайликов. Иногда нажатие на вкладку группы смайликов не помогает мне перейти к смайликам, о которых я думаю - мне все равно приходится много просматривать, чтобы найти их один раз в категории фильтра.
  2. Слова не всегда соответствуют правильным смайликам. Одним из улучшений определенно было бы увеличение словарного запаса. Я набираю «звезды», и смайлы с одной звездочкой исчезают, потому что поиск во множественном / единственном числе слишком строг и не тщателен.
  3. Цветовая кодировка. Если я наберу «желтый», я хочу увидеть полный список всех желтых смайликов. Я хочу видеть звездочки, желтые автомобили, лимон и т. Д. Если вы введете «синий», не все синие элементы появятся в отфильтрованных поиск.

Решить эти вопросы несложно с технической точки зрения. Я обнаружил, что сопоставить 3200 эмодзи с реальными объектами и событиями JavaScript немного сложно с точки зрения того, сколько времени это заняло. После того, как составление карты завершено, все просто.

Лист Emoji Sprite с 3200 элементами

Есть 3200 смайликов. Чтобы избежать тысяч запросов HTTP, все они хранятся в одном файле, который называется лист спрайтов или атлас изображений. Вот частичный вид официального листа спрайтов смайликов Twitter:

Фактическое официальное изображение таблицы спрайтов смайлов Twitter даже больше, чем это.

Идея таблицы спрайтов состоит в том, чтобы хранить все смайлы в одном файле… таким образом, требуется только один HTTP-запрос от сервера вместо сотен. И в данном случае тысячи, потому что по состоянию на 17 марта 2020 года в Твиттере 3200 смайликов.

Затем каждый отдельный смайлик отображается в отдельном элементе DIV, и к нему можно перейти, изменив это свойство:

background-position: x y;

Предоставляя этому свойству смещение x и y в пикселях, мы можем перейти к любому эмодзи в наборе. Нам просто нужно запомнить положение каждого смайлика и сохранить его в объекте JavaScript.

Не самая веселая работа… но вам нужно сделать это только один раз.

Вкладки Emoji

Я начал с перестройки интерфейса вкладок смайликов. Я просто не согласен с некоторыми выбранными значками и количеством категорий. Поэтому я увеличил количество доступных вкладок. Есть более конкретные значимые тематические группы, чем предлагает 9 вкладок Twitter. Природа, Автомобили, Самолеты, Поезда…. это могло быть более конкретным.

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

<div id = "emoji-root">
<div class = "tabs">
<!-- recent -->
    <div class = "tab recent"
     data-type = "recent"
    data-title = "Recent"></div>
<!-- positive -->
    <div class = "tab positive"
     data-type = "positive"
    data-title = "Positive, Smiley, Smileys"></div>
<!-- negative-->
    <div class = "tab negative"
     data-type = "negative"
    data-title = "Negative, Smiley, Smileys"></div>
<!-- ...all other tabs... -->
<!-- filtered results box -->
    <div id = "emoji-results"></div>
<!-- selected emojis -->
    <div id = "emoji-output"></div>
</div>

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

Я разделил положительные и отрицательные смайлы. Сколько раз вы пытаетесь выбрать из положительного набора? Трудно провести грань между двумя разными типами, когда все эмоции показаны одновременно… и все же это очень полезное и практическое различие.

Моя функция insert_all_emoji вызывается один раз после завершения загрузки DOM.

window.addEventListener('DOMContentLoaded', (event) => {
    insert_all_emoji();
});

Он отвечает за физическое создание каждого смайлика в главном окне результатов поиска. Когда он выполняется впервые во время инициализации приложения ... он динамически создает каждый смайлик как элемент ‹div› с display: inline-block. Это означает, что каждый смайлик автоматически «переносится» на следующую строку в родительском контейнере.

function insert_all_emoji() {
    let emojis = 3200;
    let max_per_row = 50;
    let c = 0;
    let y = 0;
    let root = document.getElementById("emoji-results");
    // Walk through all 3,200 emojis and physically create them
    for (let i = 0; i < emojis; i++, c++) {
        let E = document.createElement("div");
        E.style.width = "48px";
        E.style.height = "48px";
        E.style.position = "relative";
        E.style.display = "inline-block";
        E.style.border = "0";
        E.style.margin = "4px";
        E.style.cursor = "pointer";
        E.setAttribute("id", "emoji_id_" + i);
        E.setAttribute("class", "emoji");
        let x = i * -48;
        if (c > max_per_row) { c = 0; y -= 48; }
        E.style.backgroundPositionX = x + 'px';
        E.style.backgroundPositionY = y + 'px';
        // When this emoji is clicked,
        // clone it and copy to send message box
        E.addEventListener("click", event => {
            let clone = event.target.cloneNode();
            let what = "#emoji-output";
            document.querySelector(what).appendChild(clone);
        });
        root.appendChild(E);
    }
}

Сопоставление уникальных идентификаторов с позицией фона и размером эмодзи

Каждый смайлик имеет уникальный числовой идентификатор в диапазоне от 0 до 3199 (так как всего 3200 смайликов). Также важно отметить… изначально все смайлы по умолчанию отображаются как видимые. Когда нажимаются вкладки, я использую другую функцию, чтобы «отфильтровать» нежелательные смайлы из этой категории.

Позиция эмодзи рассчитывается с помощью свойства CSS background-position. Это может стать настоящим кошмаром с набором в 3200 смайликов. Поэтому мне пришлось создать еще один сценарий, который я назвал Emoji Finder. По сути, он показывает весь набор в виде изображения. Щелчок по нему мышью приведет к смещению фона x и y для этого смайлика в консоли разработчика. эти результаты я перебором скопировал в свой CSS.

Зная, что каждый смайлик имеет размер 48px на 48px, относительно легко сопоставить его с правильной позицией фона с помощью простых математических расчетов.

Создание интерактивных вкладок

Чтобы создать каждую вкладку, я просто щелкнул смайлик прямо на изображении, и сценарий дал мне точное смещение для него на всем листе спрайтов. Затем я жестко запрограммировал его на своих вкладках. Это был болезненный процесс, но результаты говорят сами за себя. Кроме того, что было бы лучше или быстрее? Я получил все любимые смайлы за 15 минут.

Вот CSS, который у меня получился:

.tab.recent { background-position: 0 -1536px !important; }
.tab.positive { background-position: -1728px -1680px !important; }
.tab.negative { background-position: -1680px -1680px !important; }
.tab.gestures { background-position: -96px -720px !important; }
.tab.relationship { background-position: -1104px -1344px !important}
.tab.things { background-position: -1056px -1440px !important; }
.tab.nature { background-position: -1488px -288px !important; }
.tab.flowers { background-position: -1728px -288px !important; }
.tab.heavenly { background-position: -192px -288px !important; }
.tab.food { background-position: -672px -336px !important; }
.tab.sports { background-position: -288px -2928px !important; }
.tab.cats { background-position: -1392px -624px !important; }
.tab.animals { background-position: -1392px -624px !important; }
.tab.identity { background-position: -1920px -864px !important; }
.tab.tech { background-position: -2160px -1584px !important; }
.tab.family { background-position: -384px -912px !important; }
.tab.fruit { background-position: -432px -336px !important; }
.tab.vegetable { background-position: -480px -2256px !important; }
.tab.cars { background-position: -1728px -1824px !important; }
.tab.airplanes { background-position: -144px -2976px !important; }
.tab.trains { background-position: -720px -1824px !important; }
.tab.water { background-position: -2016px -240px !important; }
.tab.buildings { background-position: -720px -576px !important; }
.tab.travel { background-position: 0 0 !important; }
.tab.objects { background-position: 0 0 !important; }
.tab.symbols { background-position: 0 0 !important; }
.tab.flags { background-position: -912px -576px !important; }
.tabs.japanese { background-position: -912px -3024px !important; }

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

Почему мне нравятся функции JavaScript высшего порядка.

Функции высшего порядка - спаситель в ситуациях, когда вам приходится иметь дело с большим количеством элементов. Я не могу представить, как писать циклы for для подобных случаев. Функции HO также делают мой код намного чище и интуитивно понятным за счет абстракции!

Помните, как на предыдущем шаге весь набор смайлов был встроен в представление результатов по умолчанию после завершения загрузки DOM?

Теперь нам нужно отфильтровать этот набор. Угадайте, что, я воспользуюсь функцией высшего порядка под названием .map. Я, наверное, мог бы использовать .filter. В данном конкретном случае разницы нет.

filter_emojis - моя любимая функция из всего этого проекта. Писать было очень весело. Обычно, когда возникает конкретная проблема, которую необходимо решить, кодирование становится гораздо более полезным. И это был один из таких случаев!

// Filter emojis by category
function filter_emojis(type) {
let positive = [308, 311, 312, 969, 970, 1018, 1019, 1028, 1036, 1051, 1352, 1747, 1748, 1770, 1771, 1772, 1773, 1774, 1775, 1776, 1777, 1778, 1779, 1780, 1781, 1782, 1783, 1784, 1786, 1787, 1830, 1826, 1827, 1828, 2164, 2208, 2209, 2211, 2212, 2234, 2235, 2437, 2439, 2769, 2770, 2778, 3106];
let negative = [679, 814, 1785, 1788, 1800, 1801, 1802, 1803, 1804, 1805, 1806, 1807, 1808, 1809, 1810, 1811, 1812, 1813, 1814, 1815, 1816, 1817, 1818, 1819, 1820, 1821, 1822, 1823, 1824, 1825, 1830, 1832, 1833, 1834, 1835, 2156, 2157, 2159, 2161, 2162, 2210, 2213, 2232, 2233, 2236, 2237, 2239, 2440, 2441, 2442, 2443, 2618, 3000, 3001, 3002, 3065];
let gestures = [716, 722, 728, 734, 740, 746, 752, 758, 770, 776, 802, 808, 814, 1445, 1687, 1693, 1731, 1789, 1834, 1842, 1843, 1844, 1845, 1846, 1849, 1862, 1874, 1887, 1900, 1901, 1911, 1912, 1913, 1961, 2144, 2145, 2156, 2157, 2161, 2164, 2170, 2176, 2182, 2188, 2201, 2207, 2208, 2213, 2217, 2218, 2219, 2229, 2230, 2231, 2234, 2236, 2238, 2258, 2438, 2448, 2498, 2504, 2548, 2551, 2552, 3166, 3172, 3178];
let relationship = [409, 421, 422, 1428, 1429, 1454, 1455, 1458, 1459, 1463, 1465, 1466, 1467, 1468, 1469, 1470, 1471, 1472, 1473, 1474, 1475, 1476, 1477, 1478, 1617, 1672, 1744, 1745, 1743, 1746, 1780, 1783, 1829, 1831, 2150, 2437, 3084, 3067, 3068, 3100, 3196, 3197, 3185, 3150, 2087, 2088, 2089, 2090, 362, 340, 339, 338, 337, 336, 335, 294];
let cats = [629, 630, 634, 635, 679, 687, 692, 700, 701, 1826, 1827, 1828, 1829, 1830, 1831, 1832, 1833, 1834, 2450];
let things = [284, 409, 444, 445, 435, 448, 449, 454, 456, 457, 470, 472, 504, 507, 508, 516, 517, 524, 583, 586, 626, 628, 777, 778, 779, 781, 782, 783, 784, 785, 786, 788, 789, 791, 792, 1408, 1433, 1431, 1464, 1480, 1481, 1482, 1492, 1493, 1485, 1499, 1501, 1512, 1513, 1514, 1515, 1516, 1517, 1529, 1541, 1548, 1549, 1550, 1551, 1552, 1564, 1572, 1573, 1574, 1575, 1576, 1577, 1578, 1618, 1619, 1620, 1621, 1622, 1623, 1624, 1625, 1626, 1672, 1673, 1695, 1696, 1697, 1698, 1711, 1714, 1722, 1723, 1724, 1725, 1750, 1751, 1752, 1753, 1754, 1755, 1756, 1757, 1758, 1759, 1764, 2117, 2119, 2135, 2128, 2389, 2391, 2393, 2394, 2396, 2397, 2398, 2400, 2401, 2408, 2409, 2410, 2406, 2416, 2415, 2426, 2427, 2428, 2446, 2553, 2555, 2557, 2958, 2959, 2961, 2963, 2964, 2966, 2967, 2968, 2969, 2970, 2971, 2972, 2973, 2974, 2975, 2976, 2978, 2980, 2982, 2988, 2991, 2996, 2997, 2998, 2999, 3005, 3006, 3007, 3008, 3014, 3015, 3016, 3025, 3026, 3027, 3028, 3043, 3046, 3048, 3049, 3090, 3091, 3092, 3094, 3095, 3096, 3107, 3112, 3130, 3154, 3155, 3156, 3157, 3179, 3180, 3185];
let trees = [329, 330, 331, 332, 333, 342, 343, 344, 345, 346, 347, 348, 1495, 3100];
let flowers = [329, 332, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 1464, 3100];
let heavenly = [308, 309, 310, 311, 312, 313, 314, 316, 317, 318, 319, 320, 321, 306, 307, 294, 286, 287, 288, 289, 290, 291, 350, 351, 352, 353, 354, 355, 356, 1437, 1625, 1626, 3041, 3159, 3160, 3185, 3150];
let water = [318, 319, 320, 513, 526, 527, 542, 543, 681, 674, 711, 712, 713, 686, 1436, 1437, 1764, 2015, 2043, 2027, 2028, 2124, 2132, 2129, 2369, 2370, 2389, 2425, 3160, 3188, 3122, 3123, 3095, 2926, 2927, 2928, 292];
let food = [349, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 410, 450, 451, 452, 453, 454, 455, 456, 457, 2406, 2405, 2407, 2408, 2409, 2410, 2411, 2412, 2413, 2414, 2415, 2416, 2417, 2418, 2419, 2420, 2421, 2422, 2423, 2424, 2425, 2426, 2427, 2428, 2429, 2430, 2431, 2432, 2433, 2434, 2435, 2436, 2553, 2554, 2555, 2556, 2557, 2558, 2559, 2560, 2561, 3049, 3086];
let all = { "positive"     : positive,
            "negative"     : negative,
            "gestures"     : gestures,
            "relationship" : relationship,
            "cats"         : cats,
            "things"       : things,
            "trees"        : trees,
            "flowers"      : flowers,
            "heavenly"     : heavenly,
            "water"        : water,
            "food"         : food,};
// get clicked tab's type
    let selected = all[type];
// walk through each .emoji class and hide all
    let classes = document.querySelectorAll(".emoji");
    classes.forEach(emoji => { emoji.style.display = "none"; });
// display only search-filtered emojis
    selected.map(item => {
        let em = document.getElementById("emoji_id_" + item);
        em.style.display = "inline-block";
    });
}

Но как функция фильтра узнает, какие смайлы оставить, а какие удалить из набора? Все идентификаторы хранятся в отдельных массивах, представляющих эти смайлы.

Я создал сценарий, который, когда я нажимаю на любой смайлик в главном представлении, добавляет его уникальный идентификатор в массив. Обойдя весь список и нажав на смайлики, я смог создать списки, которые, как мне казалось, были связаны с какой-либо конкретной вкладкой. Один за другим я создавал каждый список, просто нажимая на смайлики. Сгенерированные идентификаторы были помещены в массивы, показанные выше (положительные, отрицательные, жесты , отношения, кошки и т. д.)

Что происходит сейчас: сначала я устанавливаю display на none для всего набора. Затем я использую функцию .map высшего порядка, чтобы вернуть display обратно в inline-block, но только для всех смайликов, содержащихся в выбранном наборе.

Событие onclick этой функции привязано к каждой вкладке с помощью этой функции:

// Attach onclick event listeners to emoji category tabs
function attach_tab_filters() {
    document.querySelectorAll(".tab").forEach(item =>
        item.addEventListener("click", event =>
            filter_emojis(item.getAttribute("data-type"))));
}

Но как насчет поиска?

На этом пока все. Мне нужно, чтобы все, что я создал за последние два дня, на время утонуло. Затем я добавлю поиск во вторую часть этого руководства.

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

Это необходимый шаг… и он обязательно улучшит ситуацию! Например, я заметил, что поиск эмодзи в Twitter в некоторых местах не работает. Цветовой кодировки нет. Если вы наберете «синий», вы не получите абсолютно все синие объекты в наборе. Но цветовая кодировка IMO так важна для чего-то вроде этого.



Спасибо за прочтение! Надеюсь, этот урок был в чем-то полезен 😊