Делаем кнопку-индикатор прогресса в виде кольца

Делаем кнопку-индикатор прогресса в виде кольца 2014-07-19

Урок, показывающий, как можно претворить в жизнь концепцию кнопки индикатора прогресса в виде кольца, предложенную Колином Гарвеном. Для анимации прогресса в виде заполнения кольца будем использовать технику анимации линии в SVG, описанную Джейком Арчибальдом, а также будем отображать кнопку в успешном и ошибочном состояниях.

Сегодня я покажу, как можно реализовать интересную концепцию кнопки-индикатора прогресса. В основе концепции лежит следующий алгоритм: при клике на кнопку, она преобразуется в кольцо, в котором будет показана анимация прогресса, используя для этого границу кольца. Как только анимация завершается, кольцо снова разворачивается до состояния кнопки, отображая состояние операции (успешна/не успешна).

Для реализации подобного поведения существует несколько возможностей. При попытках реализации данной задумки только с помощью CSS, сталкиваемся с тем, что самой сложной частью было бы кольцо прогресса. Существует техника, позволяющая реализовать подобный эффект на базе свойства clip. Но мы реализуем всю эту красоту на основе SVG, с использованием CSS-переходов и небольшого количества JavaScript.

Заметьте, что анимация SVG может не поддерживаться некоторыми браузерами, и не везде работать так, как этого ожидалось. Подобного рода техники находятся лишь в зачаточном состоянии, так что будем считать данный урок экспериментальным, быть может, он будет вам полезен в ваших будущих изысканиях.
Итак, приступим.

План реализации

При внимательном рассмотрении можно уже сейчас сказать, что нам необходимо работать с несколькими состояниями кнопки. Любопытная часть - переход кнопки из одного состояния в другое. Изначально у нас есть прозрачная кнопка с текстом и цветной границей. При наведении на кнопку она заполняется цветом границы, а текст становится белым.


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


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



В случае не успешной отправки мы отобразим кнопку в ошибочном состоянии.


Давайте определим разметку со всеми необходимыми нам элементы.

Разметка

В нашей разметке нам понадобится: основной контейнер, кнопка со span’ом внутри для текста, а также три SVG.
PHP:
<!-- кнопка прогресса -->
<div id="progress-button" class="progress-button">
    <!-- кнопка с текстом -->
    <button><span>Submit</span></button>

    <!-- SVG-кольцо для индикации прогресса -->
    <svg class="progress-circle" width="70" height="70">
        <path d="m35,2.5c17.955803,0 32.5,14.544199 32.5,32.5c0,17.955803 -14.544197,32.5 -32.5,32.5c-17.955803,0 -32.5,-14.544197 -32.5,-32.5c0,-17.955801 14.544197,-32.5 32.5,-32.5z"/>
    </svg>

    <!-- знак галочки для показа при успешном завершении -->
    <svg class="checkmark" width="70" height="70">
        <path d="m31.5,46.5l15.3,-23.2"/>
        <path d="m31.5,46.5l-8.5,-7.1"/>
    </svg>

    <!-- знак крестика для показа при ошибке -->
    <svg class="cross" width="70" height="70">
        <path d="m35,35l-9.3,-9.3"/>
        <path d="m35,35l9.3,9.3"/>
        <path d="m35,35l-9.3,9.3"/>
        <path d="m35,35l9.3,-9.3"/>
    </svg>

</div><!-- /кнопка прогресса -->
Для отрисовки галочки и крестика я использовал онлайн SVG редактор Method Draw. Размеры всех SVG равны 70x70, так как высота нашей кнопки составляет 70px. Так как мы хотим, чтобы линия кольца прогресса имела ширину в 5px, необходимо учесть правильный радиус при рисовании в редакторе, так чтобы кольцо-подложка и кольцо-индикатор вписывались в высоту кнопки в 70px. Обращаю ваше внимание, что штрихи в SVG рисуются наполовину внутри, наполовину снаружи от базовой линии. Например, штрих в 2px увеличит размер кольца радиусом в 10px до высоты в 20px + 2px вместо 20 + 4 (двойная ширина границы), так что наша формула в этом случае выглядит как 2r + граница =>2r + 5 = 70, имеем, что радиус кольца должен составлять 32.5. В конечном итоге выводим следующую базовую фигуру: <circle cx="35" cy="35" r="32.5"/>

К сожалению, мы не можем использовать базовую фигуру напрямую, так как начальная точка “пути” отличается в разных браузерах, так что мы не сможем контролировать, с какой точки начинать анимацию прогресса. Преобразуем данное кольцо в путь, в дальнейшем будем использовать его. В Method Draw это делается через меню Object > Convert to path.

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

Что-ж, теперь у нас есть все необходимые элементы. Давайте подумаем, как их оживить и оформить!

CSS

Для начала стилизуем контейнер для нашей кнопки. Это что-то вроде второй кожи для нашей кнопки, сделаем ее строчно-блочной, так, чтобы такие кнопки можно было использовать в потоке:

Код:
.progress-button {
    position: relative;
    display: inline-block;
    text-align: center;
}
Наша кнопка требует небольшой раскраски и типографики.

Код:
.progress-button button {
    display: block;
    margin: 0 auto;
    padding: 0;
    width: 250px;
    height: 70px;
    border: 2px solid #1ECD97;
    border-radius: 40px;
    background: transparent;
    color: #1ECD97;
    letter-spacing: 1px;
    font-size: 18px;
    font-family: 'Montserrat', sans-serif;
    -webkit-transition: background-color 0.3s, color 0.3s, width 0.3s, border-width 0.3s, border-color 0.3s;
    transition: background-color 0.3s, color 0.3s, width 0.3s, border-width 0.3s, border-color 0.3s;
}
Также необходимо задать эффекты перехода всем свойствам, которые мы позже собираемся анимировать, например, фоновому цвету, ширине кнопки и прочее.

При наведении на кнопку будем менять фоновый цвет и цвет текста:

Код:
.progress-button button:hover {
    background-color: #1ECD97;
    color: #fff;
}
Также уберем надоедливый контур, присущий элементам формы:
Код:
.progress-button button:focus {
    outline: none;
}
Все SVG должны быть спозиционированы абсолютно в центре, также любые события указателя для них также будут запрещены:
Код:
.progress-button svg {
    position: absolute;
    top: 0;
    left: 50%;
    -webkit-transform: translateX(-50%);
    transform: translateX(-50%);
    pointer-events: none;
}
Пути SVG не должны иметь заполнения, так как нам нужно будет только манипулировать длиной штриха. Нам также не нужно отображать их ни в одном из состояний, кроме их специальных состояний, по завершении анимации прогресса, так что спрячем их, сделав прозрачными:
Код:
.progress-button svg path {
    opacity: 0;
    fill: none;
}
Кольцу прогресса зададим толщину штриха, равную 5:
Код:
.progress-button svg.progress-circle path {
    stroke: #1ECD97;
    stroke-width: 5;
}
Штрихи индикаторов успешного/ошибочного состояния будут тоньше, окрасим их в белый цвет. Также сделаем концы штрихов закругленными, чтобы они имели более приятный вид. Также для них будет установлена небольшая анимация перехода по прозрачности:
Код:
.progress-button svg.checkmark path,
.progress-button svg.cross path {
    stroke: #fff;
    stroke-linecap: round;
    stroke-width: 4;
    -webkit-transition: opacity 0.1s;
    transition: opacity 0.1s;
}
Давайте отвлечемся немного, и вспомним наш план по реализации. Нам необходимо стилизовать три дополнительных состояния кнопки и ее спецэлементов (помимо основных состояний): состояние загрузки, состояния успешности и ошибочности операции. Итак, будем использовать классы “loading”, “success” и “error” для индикации этих состояний.

Кнопка будет трансформироваться в кольцо, и будет похожа на кольцо прогресса в начале анимации процесса:
Код:
.loading.progress-button button {
    width: 70px; /* превращаем кнопку в кольцо */
    border-width: 5px;
    border-color: #ddd;
    background-color: transparent;
    color: #fff;
}
Напомню, что мы уже задали переходы для основного стиля кнопки.

Текст должен быстро исчезнуть при начале анимации прогресса:
Код:
.loading.progress-button span {
    -webkit-transition: opacity 0.15s;
    transition: opacity 0.15s;
}
Реализуем это простым выставлением прозрачности в 0:
Код:
.loading.progress-button span,
.success.progress-button span,
.error.progress-button span {
    opacity: 0; /* во всех остальных состояниях текст невидим */
}
При переходе из состояния loading в состояние success или error нам не нужен переход, текст должен быть спрятан.

Когда мы убираем все классы с кнопки, и возвращаем ее в исходное состояние, нужно, чтобы span с текстом появился с немного большей задержкой. Так что для пеерхода в обычное состояние необходимо задать другую длительность перехода.
Код:
/* Переход при возвращении в исходное состояние */
.progress-button button span {
    -webkit-transition: opacity 0.3s 0.1s;
    transition: opacity 0.3s 0.1s;
}
Как только мы достигли последнего состояния, и отправка была успешной или неуспешной, необходимо переопределить переходы для кнопки, так как мы не хотим, чтобы ширина кнопки или цвет границы анимировались:
Код:
.success.progress-button button,
.error.progress-button button {
    -webkit-transition: background-color 0.3s, width 0.3s, border-width 0.3s;
    transition: background-color 0.3s, width 0.3s, border-width 0.3s;
}
Давайте определим дополнительный класс с несколько иной анимацией кнопки:
Код:
.elastic.progress-button button {
    -webkit-transition: background-color 0.3s, color 0.3s, width 0.3s cubic-bezier(0.25, 0.25, 0.4, 1), border-width 0.3s, border-color 0.3s;
    -webkit-transition: background-color 0.3s, color 0.3s, width 0.3s cubic-bezier(0.25, 0.25, 0.4, 1.6), border-width 0.3s, border-color 0.3s;
    transition: background-color 0.3s, color 0.3s, width 0.3s cubic-bezier(0.25, 0.25, 0.4, 1.6), border-width 0.3s, border-color 0.3s;
}

.loading.elastic.progress-button button {
    -webkit-transition: background-color 0.3s, color 0.3s, width 0.3s cubic-bezier(0.6, 0, 0.75, 0.75), border-width 0.3s, border-color 0.3s;
    -webkit-transition: background-color 0.3s, color 0.3s, width 0.3s cubic-bezier(0.6, -0.6, 0.75, 0.75), border-width 0.3s, border-color 0.3s;
    transition: background-color 0.3s, color 0.3s, width 0.3s cubic-bezier(0.6, -0.6, 0.75, 0.75), border-width 0.3s, border-color 0.3s;
}
Ну вот и все со стилями, давайте добавим немного магии :)

JavaScript
Начнем с инициализации и кеширования некоторых элементов: button - HTML-элемент кнопки, progressEl - элемент SVG, в котором содержится путь, представляющий собой кольцо прогресса, а также successEl и errorEl - элементы SVG, содержащие пути для значка галочки и крестика.

Код:
function UIProgressButton( el, options ) {
    this.el = el;
    this.options = extend( {}, this.options );
    extend( this.options, options );
    this._init();
}

UIProgressButton.prototype._init = function() {
    this.button = this.el.querySelector( 'button' );
    this.progressEl = new SVGEl( this.el.querySelector( 'svg.progress-circle' ) );
    this.successEl = new SVGEl( this.el.querySelector( 'svg.checkmark' ) );
    this.errorEl = new SVGEl( this.el.querySelector( 'svg.cross' ) );
    // инициализация событий
    this._initEvents();
    // включаем кнопку
    this._enable();
}
Добавим функцию SVGEl, которая будет представлять собой элемент SVG и его пути. Для каждого элемента будем кешировать его пути и соответствующие длины. Для начала “сотрем” все пути, манипулируя значениями свойствstrokeDasharray и strokeDashoffset. Позже мы их снова “нарисуем”, когда нам будет нужно будет отобразить кольцо прогресса и значки галочки/крестика. Мы просто приравняем свойство stroke-dasharray длине пути, а позже “вытянем” его обратно, также приравняв значение свойства stroke-dashoffset длине пути. Когда возникнет необходимость “нарисовать” значок, мы сбросим смещение до 0, что даст эффект “прорисовки” пути.
Код:
function SVGEl( el ) {
    this.el = el;
    // элементы пути
    this.paths = [].slice.call( this.el.querySelectorAll( 'path' ) );
    // будем хранить пути и их длины в массиве
    this.pathsArr = new Array();
    this.lengthsArr = new Array();
    this._init();
}

SVGEl.prototype._init = function() {
    var self = this;
    this.paths.forEach( function( path, i ) {
        self.pathsArr[i] = path;
        path.style.strokeDasharray = self.lengthsArr[i] = path.getTotalLength();
    } );
    // прячем штрихи
    this.draw(0);
}

// val in [0,1] : 0 - штрих не отображается, 1 - штрих видим
SVGEl.prototype.draw = function( val ) {
    for( var i = 0, len = this.pathsArr.length; i < len; ++i ){
        this.pathsArr[ i ].style.strokeDashoffset = this.lengthsArr[ i ] * ( 1 - val );
    }
}
Далее необходимо назначить обработчик на событие onclick по кнопке. Кнопка будет свернута до формы кольца (с помощью добавления класса loading). После того, как закончится эта анимация, будет вызвана функция обратного вызова (если таковая была задана в опциях), или же мы просто выставляем прогресс в 100% (скорость такой анимации будет такой же, каковая задана для эффекта перехода для stroke-dashoffset в CSS). Кнопка на это время отключается. (Вообще, это нужно сделать первым делом при клике на кнопку, но, если сделать так, то Firefox в таком случае не выбрасывает событие transitioned)
Код:
UIProgressButton.prototype._initEvents = function() {
    var self = this;
    this.button.addEventListener( 'click', function() { self._submit(); } );
}

UIProgressButton.prototype._submit = function() {
    classie.addClass( this.el, 'loading' );

    var self = this,
        onEndBtnTransitionFn = function( ev ) {
            if( support.transitions ) {
                this.removeEventListener( transEndEventName, onEndBtnTransitionFn );
            }

            this.setAttribute( 'disabled', '' );

            if( typeof self.options.callback === 'function' ) {
                self.options.callback( self );
            }
            else {
                self.setProgress(1);
                self.stop();
            }
        };

    if( support.transitions ) {
        this.button.addEventListener( transEndEventName, onEndBtnTransitionFn );
    }
    else {
        onEndBtnTransitionFn();
    }
}
Как только прогресс достигнет 100%, необходимо сбросить штрих пути кольца прогресса. Также мы должны показать кнопку в состоянии “успешно” или “ошибка”. После некоторого времени (options.statusTime), мы убираем индикатор статуса и возвращаем кнопку в ее исходное состояние. Запомните, что все переходы мы контролируем посредством CSS.
Код:
01
UIProgressButton.prototype.stop = function( status ) {
02
    var self = this,
03
        endLoading = function() {
04
            self.progressEl.draw(0);
05
06
            if( typeof status === 'number' ) {
07
                var statusClass = status >= 0 ? 'success' : 'error',
08
                    statusEl = status >=0 ? self.successEl : self.errorEl;
09
10
                statusEl.draw( 1 );
11
                // добавляем элементу соответствующий класс
12
                classie.addClass( self.el, statusClass );
13
                // после options.statusTime убираем статус и возвращаем кнопку в исходное состояние
14
                setTimeout( function() {
15
                    classie.remove( self.el, statusClass );
16
                    statusEl.draw(0);
17
                    self._enable();
18
                }, self.options.statusTime );
19
            }
20
            else {
21
                self._enable();
22
            }
23
24
            classie.removeClass( self.el, 'loading' );
25
        };
26
27
    // даем немного времени (в идеале столько же, сколько выставлено во времени перехода в CSS)
28
    setTimeout( endLoading, 300 );
29
}
И вот наша кнопка готова!

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

PS: Вы так же можете скачать исходник примера, перейдя по этой ссылке: >>Тырк<<
Автор
Wolf88
Скачивания
39
Просмотры
261
Первый выпуск
Обновление
Оценка
0,00 звёзд 0 оценок
Сверху