Статьи

Mobile Web: создание загрузочного блесна SVG

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


В этом руководстве предполагается, что у вас уже есть базовые знания в области масштабируемой векторной графики (SVG), HTML, CSS, JavaScript и jQuery. Тем не менее, контент представлен в пошаговой манере, которая должна быть достаточно простой для отслеживания.

Как насчет Рафаэля? Мы будем использовать проект Raphaël для выполнения рисунка SVG в этом уроке. Цитировать с официального веб-сайта проекта Raphaël:

Raphaël использует Рекомендацию SVG W3C и VML в качестве основы для создания графики. Это означает, что каждый создаваемый вами графический объект также является объектом DOM, поэтому вы можете присоединить обработчики событий JavaScript или изменить их позже. Цель Raphaël — предоставить адаптер, который сделает рисование векторной графики совместимым и кросс-браузерным.

Чтобы использовать Raphaël в своем проекте, вам просто нужно выполнить следующие шаги:

  1. Импортируйте библиотеку на свою веб-страницу.
  2. Создайте объект Raphael, передавая идентификатор div, где будет нарисован ваш SVG, примерно так:
    1
    var paper = Raphael(divID, width, height);
  3. Создайте нужные элементы в недавно созданном объекте Raphael, например:
    1
    2
    3
    4
    // Creates circle at x = 50, y = 40, with radius 10
    var circle = paper.circle(50, 40, 10);
    // Sets the fill attribute of the circle to red (#f00)
    circle.attr(«fill», «#f00»);

Достаточно теории! Давайте начнем кодировать!


Давайте начнем с создания нашей демонстрационной страницы в HTML. Это должно выглядеть следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!DOCTYPE html>
<html>
    <head>
        <meta charset=»utf-8″>
        <meta name=»viewport» content=»width=device-width, initial-scale=1.0″>
        <title>Loading Spinner Example</title>
         
         <!— CSS —>
        <link href=»spinner/spinner.css» rel=»stylesheet»>
    </head>
    
    <body>
        <p>
            <a id=»createSpinner» href=»»>Unleash</a> the power of the loading spinner.
        </p>
 
        <!— SPINNER —>
        <div id=»spinnerFullScreen»>
            <div id=»floater»>
                <div id=»spinner»></div>
            </div>
        </div>
 
        <!— Placed at the end of the document so the pages load faster and without blocking —>
        <script src=»https://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js»
type=»text/javascript»></script>
        <script src=»https://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js»
type=»text/javascript»></script>
        <script src=»spinner/spinner.js» type=»text/javascript»></script>
    </body>
</html>

Наконец, но не в последнюю очередь, мы добавляем ссылку, по которой вы можете щелкнуть, чтобы «раскрутить» вращатель (т.е. запустить анимацию вращения).


Теперь, когда у нас есть готовая разметка, нам нужно начать заполнение отсутствующего стиля.

С точки зрения CSS, внешний div (т. Е. Id = «spinnerFullScreen») должен быть черным и занимать весь экран поверх всех элементов, которые не принадлежат вращателю.

Два других элемента div (т.е. id = «floater» и id = «spinner») используют небольшой «хак» для правильного центрирования счетчика по центру экрана независимо от того, какой размер экрана или где установлена ​​прокрутка. , Я не буду объяснять это в этом уроке, так как CSS относится только к «фиктивной» демонстрационной странице, а не к центральной цели этого урока.

В конце файл spinner.css должен выглядеть так:


Теоретически, наш счетчик состоит из определенного числа секторов (8 на изображении), которые имеют длину («sectorLength») и ширину («sectorWidth»). Конечно, эти сектора также имеют расстояние до центра («centerRadius»).

Spinner математика

Но разве это статично? А как насчет анимации? Что ж, анимация — это всего лишь маленькая хитрость: имея все непрозрачности секторов в диапазоне от 0,0 до 1,0, мы постоянно меняем непрозрачность каждого сектора, чтобы она равнялась непрозрачности следующего сектора. Смущенный? Скорее всего, он станет более прозрачным, когда вы увидите реализацию в JavaScript.

Чтобы создать библиотеку многократного использования, мы будем использовать объектно-ориентированную парадигму, реализованную в JavaScript. Библиотека построена на основе конструктора ( function Spinner(data) ) и двух отдельных функций:

  • создать — используя переменные экземпляра, определенные в конструкторе, он создает бланк SVG и анимирует его.
  • destroy — уничтожает блесну SVG и скрывает полноэкранный режим.

В ранее созданном файле spinner.js мы сначала создаем конструктор Spinner, позволяющий пользователю библиотеки устанавливать некоторые значения, такие как количество секторов, расстояние секторов до центра и т. Д.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
 * creates the object Spinner with data values or default values in the case they are missing
 * @param data
 * @constructor
 */
 
function Spinner(data) {
 
    //number of sectors of the spinner — default = 12
    this.sectorsCount = data.sectorsCount ||
 
    //the distance from each sector to the center — default = 70
    this.centerRadius = data.centerRadius ||
 
    //the length/height of each sector — default = 120
    this.sectorLength = data.sectorLength ||
 
    //the width of each sector of the spinner — default = 25
    this.sectorWidth = data.sectorWidth ||
 
    //color of the spinner — default = white
    this.color = data.color ||
 
    //the opacity of the fullScreen
    this.fullScreenOpacity = data.fullScreenOpacity;
 
    //array of spinner sectors, each spinner is a svg path
    this.sectors = [];
 
    //array with the opacity of each sector
    this.opacity = [];
 
    //the raphael spinner object
    this.spinnerObject = null;
 
    //id of the timeout function for the rotating animation
    this.spinnerTick = null;
}

Теперь перейдем к самому большому методу объекта spinner — методу create. Этот метод вызывается каждый раз, когда пользователь хочет показать счетчик. Обратите внимание на использование jQuery для выбора наших элементов. Вот куда приходят идентификаторы, о которых мы говорили выше:

1
2
3
4
5
6
7
8
9
Spinner.prototype.create = function() {
    //shows the full screen spinner div
    $(‘#spinnerFullScreen’).show();
 
    //animates the opacity of the full screen div containing the spinner from 0 to 0.8
    $(‘#spinnerFullScreen’).animate({
        opacity: this.fullScreenOpacity
    }, 1000, function() {
    });

Продолжая метод create, мы делаем некоторые начальные вычисления, такие как общий размер счетчика, и подготавливаем объект Raphael для рисования сечений:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
//center point of the canvas/spinner/raphael object
var spinnerCenter = this.centerRadius + this.sectorLength + this.sectorWidth;
 
//angle difference/step between each sector
var beta = 2 * Math.PI / this.sectorsCount;
 
//params for each sector/path (stroke-color, stroke-width, stroke-linecap)
var pathParams = {
    «stroke»: this.color,
    «stroke-width»: this.sectorWidth,
    «stroke-linecap»: «round»
};
 
/**
 * creates the Raphael object with a width and a height
 * equals to the double of the spinner center
 * “spinner” is the id of the div where the elements will be drawn
 */
var paperSize = 2 * spinnerCenter;
this.spinnerObject = Raphael(‘spinner’, paperSize, paperSize);

Далее следует отрисовка цикла и построение массива с текущей непрозрачностью каждого сектора:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//builds the sectors and the respective opacity
for (var i = 0; i < this.sectorsCount; i++) {
 
    //angle of the current sector
    var alpha = beta * i;
    var cos = Math.cos(alpha);
    var sin = Math.sin(alpha);
 
    //opacity of the current sector
    this.opacity[i] = 1 / this.sectorsCount * i;
 
    /**
     * builds each sector, which in reality is a SVG path
     * note that Upper case letter means that the command is absolute,
     * lower case means relative to the current position.
     * (http://www.w3.org/TR/SVG/paths.html#PathData)
   * we move the «cursor» to the center of the spinner
   * and add the centerRadius to center to move to the beginning of each sector
     * and draws a line with length = sectorLength to the final point
     * (which takes into account the current drawing angle)
    */
    this.sectors[i] = this.spinnerObject.path([
        [«M», spinnerCenter + this.centerRadius * cos, spinnerCenter + this.centerRadius * sin],
        [«l», this.sectorLength * cos, this.sectorLength * sin]
    ]).attr(pathParams);
}

Теперь, когда у нас построен и показан наш счетчик, нам нужно его анимировать. Это последняя часть метода create:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    /**
     * does an animation step and calls itself again
     * @param spinnerObject this param needs to be passed
     * because of scope changes when called through setTimeout function
     */
    (function animationStep(spinnerObject) {
 
        //shifts to the right the opacity of the sectors
        spinnerObject.opacity.unshift(spinnerObject.opacity.pop());
 
        //updates the opacity of the sectors
        for (var i = 0; i < spinnerObject.sectorsCount; i++) {
            spinnerObject.sectors[i].attr(«opacity», spinnerObject.opacity[i]);
        }
 
        /**
         * safari browser helper
         * There is an inconvenient rendering bug in Safari (WebKit):
         * sometimes the rendering should be forced.
         * This method should help with dealing with this bug.
         * source: http://raphaeljs.com/reference.html#Paper.safari
         */
        spinnerObject.spinnerObject.safari();
 
        /**
         * calls the animation step again
         * it’s called in each second, the number of sectors the spinner has.
         * So the spinner gives a round each second, independently the number of sectors it has
         * note: doesn’t work on IE passing parameter with the settimeout function 🙁
         */
        spinnerObject.spinnerTick = setTimeout(animationStep, 1000 / spinnerObject.sectorsCount, spinnerObject);
 
    })(this);
};//end of the create method

Наконец, метод уничтожения нашего блесны:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
/**
 * destroys the spinner and hides the full screen div
 */
Spinner.prototype.destroy = function() {
    //stops the animation function
    clearTimeout(this.spinnerTick);
 
    //removes the Raphael spinner object
    this.spinnerObject.remove();
    this.spinnerObject = null;
 
    //animates the opacity of the div to 0 again and hides it (display:none) in the end
    $(‘#spinnerFullScreen’).animate({
        opacity: 0
    }, 2000, function() {
        $(‘#spinnerFullScreen’).hide();
    });
};

С вращающимся кодом на месте теперь пришло время прикрепить событие к ссылке, чтобы, когда пользователь щелкает по нему, мы отображали счетчик с интервалом в 6 секунд. Лично я использую это для асинхронных запросов к серверу, и когда запрос закончен, я просто удаляю счетчик.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
$(document).ready(function() {
    $(‘#createSpinner’).click(unleashSpinner);
});
 
function unleashSpinner() {
    var data = {};
    data.centerRadius = 35;
    data.sectorLength = 50;
    data.sectorsCount = 10;
    data.sectorWidth = 20;
    data.color = ‘white’;
    data.fullScreenOpacity = 0.8;
 
    var spinner = new Spinner(data);
 
    spinner.create();
    setTimeout(function(){spinner.destroy();}, 6000);
 
    return false;
}

Мы можем использовать переменную счетчика столько раз, сколько захотим.


Спиннер, продемонстрированный в этом руководстве, можно использовать на веб-страницах, предназначенных не только для мобильных устройств, но и для «обычных» веб-страниц. Я уже попробовал это с обоими методами, и это работало просто отлично!

Чтобы проверить свои знания, вы можете улучшить текущую реализацию блесны несколькими уникальными способами. Например, вы можете попробовать изменить формат / форму сечений, включить движение по часовой стрелке или против часовой стрелки или позволить разработчику выбрать любой идентификатор для счетчика, чтобы избежать столкновения идентификаторов.

Вот и все на этот раз. Я надеюсь, вам понравился этот урок!