Динамический фавикон на JavaScript и Canvas

Не знаю как вы, но при посещении любого сайта, мой взгляд в первую очередь падает не на его долго грузящуюся «морду», а как ни странно на его заголовок в табе браузера и соответственно на его фавикон. И в основном, на месте фавикона я вижу всегда унылую, квадратную или размытую статическую картинку, которая не «цепляет взгляд». Поэтому, в этом посте я покажу вам, как можно легко сделать анимированный фавикон для сайта…

Для начала немного полезной «воды» о фавикон

Вообще, если затрагивать тему с фавиконом, то оказывается не все так просто, т.к об этой маленькой иконке порой забывают на своем сайте, а она является очень важной частью онлайн брендинга и дает визуальный сигнал для пользователя, помогая отличить ваш сайт от других. В любом случае, не смотря на то, что большинство иконок являются статическими (т.к в интернете огромное количество уже готовых фавиконов + можно сделать фавикон онлайн самому используя любой онлайн фавикон генератор), их все равно можно «оживить» и сфокусировать взгляд доп. посетителя. 

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

Анимированный фавикон за 7 шагов

Ниже, я постарался описать 7 простых шагов, после которых вы сможете получить готовую анимированную иконку, которая будет срабатывать после нажатия на кнопку.

1. Создаем холст через <canvas> элемент

Для начала, нам нужно создать canvas  анимацию, которая будет рисовать полный круг на все 100% (для нас это будет очень важно кода нужно будет увеличивать дугу). Поэтому вставляем следующую разметку:

<button id=lbtn>Загрузить</button>
<canvas id=cvl width=16 height=16></canvas>

Для canvas я использую стандартный размер favicon — 16×16 пикселей, однако если хотите, то можно использовать размер побольше, но только помните, что canvas картинка будет уменьшена до площади 162 пикселя, при использовании в качестве фавиконки.

2. Проверяем поддержку canvas

Естественно, как же без нее… Я советую всегда при работе с канвасом, проверять поддерживает ли его браузер, дабы не возникало неприятных сюрпризов в дальнейшем. Поэтому, создадим событие onload(), где получим ссылку cv для элемента холста используя метод querySelector(),  и отдадим ее 2D объекту ctx с помощью getContext() метода.

onload = function() {
    cv = document.querySelector('#cvl'),
    ctx = cv.getContext('2d');
 
    if (!!ctx) {

    }
};

В конце необходимо проверить поддержку canvas по User Agent и убедиться в том, что объект области рисования ctx существует и не undefined. Поэтому создадим условие if и проверим переменную.

3. Создаем начальные переменные

Давайте теперь создадим 3 глобальные переменные:

  1. s — для начала угла дуги;
  2. tc — для идентификатора таймера setInterval();
  3. pct — для «процентного значения» самого таймера.

Возможно у вас возникнут вопросы по коду ниже в строчке: tc = pct = 0, но тут ничего сложного нет т.к 2 переменные просто принудительно «зануляются».  Вообще считается хорошим тоном, всегда, перед написанием какого-либо кода, обнулять все переменные, что бы избежать проблем/багов в дальнейшем.

onload = function() {
    cv = document.querySelector('#cvl'),
    ctx = cv.getContext('2d');
 
    if (!!ctx) {
        s = 1.5 * Math.PI,
        tc = pct = 0;
    }
};

Как видите помимо обнуления с переменной s тоже творится какая-то магия 🙂 Но и тут все просто, т.к в этой переменной просто рассчитывается по формуле как должен себя вести/работать угол дуги чтобы отрисовывался полный круг. Ниже я постараюсь объяснить, что и откуда берется.

Стягивающий угол (угол, образованный двумя лучами, задающими дугу) окружности круга равен 2π rad, где rad – это радиан – обозначение единицы измерения углов. Тогда угол, соответствующий дуге, составляющей 1/4 длины окружности, равен 0.5π rad.

Расчет стягивающего угла

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

Если начать чертить круг по часовой стрелке (направление, используемое по умолчанию для отрисовки дуги в канвасе) от точки, лежащей справа, то дуга достигнет верхней точки через три четверти окружности, то есть при угле, равном 1.5π rad. Следовательно, необходимо ввести переменную s = 1.5 * Math.PI для обозначения начального угла, при котором мы и начнем чертить дугу в холсте.

4. Стилизация круга

Теперь, для прорисовки всего круга необходимо минимально стилизовать его внешний вид. Для этого, определим lineWidth и strokeStyle свойства круга:

onload = function() {
    cv = document.querySelector('#cvl'),
    ctx = cv.getContext('2d');
 
    if (!!ctx) {
        s = 1.5 * Math.PI,
        tc = pct = 0;
 
        ctx.lineWidth = 2;
        ctx.strokeStyle = 'fuchsia';
    }
};

5. Рисуем круг

Для начала необходимо повесить обработчик события на кнопку «Загрузить» т.е на ее id [#lbtn], который запустит setInterval таймер в 60 миллисекунд, в процессе которых будет выполняться наша функция отрисовки favicon иконки-круга — updateLoader(). В свою очередь метод setInterval() вернет id таймера, который мы как раз положим в ранее объявленную переменную tc.

onload = function() {
    cv = document.querySelector('#cvl'),
    ctx = cv.getContext('2d');
 
    if (!!ctx) {
        s = 1.5 * Math.PI,
        tc = pct = 0,
        btn = document.querySelector('#lbtn');
 
        ctx.lineWidth = 2;
        ctx.strokeStyle = 'fuchsia';
 
        btn.addEventListener('click', function() {
            tc = setInterval(updateLoader, 60);
        });
    }
};

6. Создаем функцию для обновления favicon анимации

Теперь пришло время создать, на мой взгляд самую главную и важную функцию updateLoader(), которая будет вызываться setInterval методом при клике на кнопку. Давайте для начала я покажу код, а затем постараюсь объяснить как он работает и что делает:

function updateLoader() {
    ctx.clearRect(0, 0, 16, 16);
    ctx.beginPath();
    ctx.arc(8, 8, 6, s, (pct * 2 * Math.PI / 100 + s));
    ctx.stroke();
 
    if (pct === 100) {
        clearInterval(tc);
        return;
    }
 
    pct++;
}

Функция clearRect() очищает прямоугольную область холста, заданную параметрами: (x, y) – координаты левого верхнего угла. Строка clearRect(0, 0, 16, 16) полностью очищает созданную нами область размером 16x16 пикселей.

Функция beginPath() создает новый контур для отрисовки, а функция stroke() начинает отрисовку круга по вновь созданному контуру. В конце функции updateLoader() используется процентный счетчик [pct] с шагом приращения в 1 процент, и прежде чем программа выполнит очередное приращение, необходимо будет проверить, не равно ли значение счетчика 100. Если значение счетчика составляет 100 процентов, то значение таймера setInterval() (определяемого параметром timer id, tc) обнуляется с помощью функции clearInterval().

А подробнее?

Первые три параметра функции arc() – это координаты центра дуги (x, y) и ее радиус. Четвёртый и пятый параметры представляют собой начальный и конечный углы, при которых начинается и заканчивается отрисовка дуги.

Мы уже обозначили начальную точку отрисовки круга favicon-а, которой является угол s. Эта же точка будет являться начальной на всех итерациях. Однако конечный угол будет увеличиваться с увеличением значения процентного счетчика. Мы можем рассчитать размер шага приращения следующим образом. Например, шаг приращения в 1% (1 процент из 100) составляет некий угол α от угла (где – это угол всей окружности). Таким образом, мы можем составить следующее выражение:

1/100 = α/2π

Преобразовав которое, получим:

α = 1 * 2π /100
α = 2π/100

Таким образом, шаг в 1% равен углу 2π/100. Следовательно конечный угол с увеличением значения счетчика на каждый последующий процент можно вычислить, умножив угол 2π/100 на процентное значение. Полученный результат прибавляется к значению s (начальный угол) с тем, чтобы дуга каждый раз начинала рисоваться из одной и той же начальной точки. Именно по этой причине во фрагменте кода, представленного выше, мы использовали  формулу pct * 2 * Math.PI / 100 + s для расчета конечного угла.

7. Добавляем favicon

Итак, мы уже выполнили все шаги и теперь остается дело за малым. Давайте положим тег link в head нашего HTML документа, либо динамически через JavaScript

<link rel="icon" type="image/ico" >

В функции updateLoader() для начала мы вытащим фавикон используя querySelector() метод и привяжем его к lnk переменной. Затем мы должны экспортировать canvas изображение каждый раз когда дуга отрисовывается и кодировать изображение с помощью toDataURL(), которое затем будем класть в href иконки. Таким образом мы получаем полностью анимированный favicon такой же как и на canvas холсте. Полный код:

onload = function() {
    cv = document.querySelector('#cvl'),
    ctx = cv.getContext('2d');
 
    if (!!ctx) {
        s = 1.5 * Math.PI,
        tc = pct = 0,
        btn = document.querySelector('#lbtn'),
        lnk = document.querySelector('link[rel="icon"]');
 
        ctx.lineWidth = 2;
        ctx.strokeStyle = 'fuchsia';
 
        btn.addEventListener('click', function() {
            tc = setInterval(updateLoader, 60);
        });
    }
};
 
function updateLoader() {
    ctx.clearRect(0, 0, 16, 16);
    ctx.beginPath();
    ctx.arc(8, 8, 6, s, (pct * 2 * Math.PI / 100 + s));
    ctx.stroke();
 
    lnk.href= cv.toDataURL('image/png');
 
    if (pct === 100) {
        clearTimeout(tc);
        return;
    }
 
    pct++;
}

Бонус: favicon для асинхронных событий

При желании, код выше можно допилить с сделать так что бы он работал при выполнении какого-либо AJAX запроса в фоне:

onload = function() {
    cv = document.querySelector('#cvl'),
    ctx = cv.getContext('2d');
 
    if (!!ctx) {
        s = 1.5 * Math.PI,
        lnk = document.querySelector('link[rel="icon"]');
 
        ctx.lineWidth = 2;
        ctx.strokeStyle = 'fuchsia';
    }
 
    var xhr = new XMLHttpRequest();
    xhr.addEventListener('progress', updateLoader);
    xhr.open('GET', 'http://site.com');
    xhr.send();
};
 
function updateLoader(evt) {
    ctx.clearRect(0, 0, 16, 16);
    ctx.beginPath();
    ctx.arc(8, 8, 6, s, (evt.loaded*2*Math.PI/evt.total+s));
    ctx.stroke();
 
    lnk.href = cv.toDataURL('image/png');
}

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

Подписаться на новые статьи