Урок по реализации круглой кнопки загрузки (далее progress button) by Colin Garven. Будем использовать, описанную by Jake Archibald, технику анимации SVG линий для того, чтобы анимировать progress button и показать пользователю состояния «success and fail».
Сегодня мы покажем вам как создать изящную progress button. Это уникальный концепт submit button, предложенный by Colin Garven. Сперва взгляните на это (Demo ), постарайтесь придумать, как реализовать, и просто наслаждайтесь анимацией. Идея состоит в следующем: при первом клике submit button трансформируется в круг, который покажет анимацию загрузки, используя свои границы (далее бордер). Когда анимация закончится, кнопка вернет исходную форму и покажет отметку, указывающую, что подтверждение прошло успешно или нет.
Существует несколько способов реализовать кнопку с таким эффектом. Думая о реализации только посредствам CSS, самой тяжёлой частью кажется круг прогресса. Существует техника реализации с помощью свойства clip. Anders Ingemann написал отличный полный tutorial (на LESS). Но мы будем использовать технику, основанную на SVG, CSS transitions и немного JS. Что касается круга прогресса, отметок success /fail, мы воспользуемся техникой рисования SVG линий, описанную by Jake Archibald.
Стоит отметить, что анимирование SVG может быть проблематичным для некоторых браузеров. Так что воспринимайте этот tutorial только как экспериментальное упражнение.
Поехали!
Если вы внимательно изучили Colin’s Dribbble shot, то должны были заметить, что нам следует позаботиться о нескольких состояниях кнопки. Интересная часть – переход между двумя такими состояниями. Сперва мы должны показать простую кнопку с прозрачным фоном и цветным бордером. При наведении мышки кнопка заполняется цветом бордера, а текст становится белым.
При нажатии на кнопку (для того, что бы, например, подтвердить отправку формы), текст должен плавно исчезнуть, ширина кнопки — уменьшится до состояния круга, бордер — стать уже, и должна начаться анимация. Мы будем использовать SVG-круг для анимации, так что нам необходимо убедиться, что получившаяся кнопка такого же размера и в той же позиции, что и SVG-круг, который виден в этот момент. Затем мы рисуем круг, изображающий загрузку подтверждения.
Когда подтверждение завершено, а круг полностью нарисован, кнопка должна снова расшириться, нарисовать галочку в случае успешного подтверждения. И закрашиваем кнопку соответственно.
В случае неудачи подтверждения, нужно показать состояние ошибки.
Давайте создадим нашу разметку со всеми необходимыми элементами.
Для разметки нам потребуется главный контейнер, кнопка со спаном(span), содержащим текст, и три SVG:
Используем Method Draw, ведь проще всего воспользоваться онлайн SVG генератором, чтобы нарисовать галочку и крестик для кнопки. Размеры всех SVG будут 70х70, так как высота нашей кнопки 70рх. Если мы хотим круг с бордером толщиной в 5 единиц нам нужно установить правильный радиус, когда будим рисовать его в графическом редакторе, да так чтобы весь круг с его бордером имел высоту 70рх. Заметьте, что в SVG обводка рисуется симметрично границе объекта. К примеру, обводка толщиной в 2рх увеличит круг радиусом в 10рх к реальным толщине и высоте 20+2 вместо 20+4(ширина бордера дважды), то есть формула 2*r+border. Для нашего случая мы знаем 2*r+5=70, от сюда наш круг должен иметь радиус в 32,5рх. Таким образом выходит: />.
К сожалению, мы не можем использовать только эти базовые размеры, потому что дефолтные параметры вставки у каждого браузера свои и мы не можем контролировать, где «анимация загрузки начнётся». Таким образом, нам придётся конвертировать эту форму в path и использовать его. Вы можете легко осуществить это в Method Draw under Object > Convert to Path.
Для крестика мы будем использовать четыре элемента, которые можно нарисовать со средней точки, и анимировать так же как галочку.
Итак, у нас есть все необходимые элементы. Продумаем порядок действий и начнем стилизацию.
Сперва необходимо стилизовать контейнер для кнопки:
Укажем нашей кнопке цвета и шрифты. Что бы она соответствовала концепту, установим правильный бордер и шрифт Montserrat:
Также ставим transition для всех свойств, которые будут анимироваться (background-color, width etc.).
При наведении курсора мыши меняем цвет фона и цвет текста:
Уберем все обводки (outline):
Все SVG должны быть находиться по центру, все pointer-events отключены:
Формы не должны иметь цвета заливки, так как мы хотим играть только с обводкой. Мы не показываем элементы ни в каких состояниях кнопки, кроме нужного, так что давайте спрячем их, сделав прозрачными:
Наш загрузочный круг будет создан установкой stroke-width в 5 единиц:
Success/fail индикаторы будут иметь обводку тоньше, и она должна быть белой. Свойству stroke-linecap установим значение round, так они будут красивее. У них установим быстрое изменение прозрачности:
Теперь давайте подрезюмируем и вспомним наш master plan. Нам необходимо было иметь возможность стилизовать три добавленных состояния (помимо дефолтного) кнопки и специальных элементов. Будем использовать классы “loading”, “success” и “error” для их индикации.
Кнопка станет круглой и будет выглядеть в точности как загрузочный круг SVG, когда мы начнем процесс загрузки:
Помним, что мы уже установили transition, когда задавали стили для кнопки. Текст должен быстро исчезнуть, когда начнется анимация загрузки,…
… путем установки нулевого значения для прозрачности:
При смене состояний с loading на success/error нам не нужен transition, просто оставим текст скрытым.
Когда мы убираем все классы и возвращаем кнопку к исходному состоянию. Тексту нужно немного больше времени для появления, так что установим другие значения длительности анимации и ее задержки для нормального состояния:
При достижении последнего состояния необходимо переопределить transition, так как нам не надо анимировать цвет бордера или широту кнопки:
Зададим цвета для последнего состояния:
Когда будет применен необходимый класс, нужно показать нашу SVG и анимировать stroke-dashoffset установкой следующео значения для transition:
Добавим easing для анимации широты кнопки:
Если вы любите играть с другими easing-функциями – пробуйте Ceaser, CSS Easing Animation Tool by Matthew Lein.
Со стилями определились, творим магию дальше.
Начнем с инициализации некоторых элементов: button – это html-элемент button, progressEl – SVG элемент кольцо progress bar, а successEl, errorEl – SVG-элементы галочка и крестик соответственно:
Добавили функцию SVGEI, которая будет использоваться для того, то бы предоставить SVG-элементы и их paths. Мы кэшируем path и соответственно длину для каждого. Изначально мы «оттягиваем» все paths, управляя значениями свойств strokeDasharray и strokeDashoffset. Позже мы «втянем» их обратно, когда покажем загрузочный круг, или галочку, или крестик. Эту технику хорошо объясняет Jake Archibald в статье Animated line drawing in SVG. Устанавливаем значение stroke-dasharray равное длине path и оттягиваем его. Установив значение stroke-dashoffset также равное его длине, мы больше не видим его. Когда нам нужно будет показать фигуру — установим offset на 0, имитируя рисование фигуры:
Далее, мы должны инициализировать onclick event для нашей кнопки. Сначала кнопка превратится в круг (с помощью добавления класса loading). После окончания анимации, либо будет вызвана callback функция, либо прогресс установится на 100%. В данный момент кнопка отключается (это событие должно бы быть самым первым, однако такой браузер как firefox, например, не сможет удалить transitionend event):
Как только прогресс достигнет 100%, необходимо обновить заполнение загрузочного круга. Затем нужно показать либо галочку, либо крестик. Через некоторое время (options.statusTime) мы уберём все индикаторы состояния и заново включаем кнопку. Обратите внимание, что все переходы контролируются посредством CSS.
Кнопка готова!
«We hope you enjoyed this tutorial and find it useful!»
Сегодня мы покажем вам как создать изящную progress button. Это уникальный концепт submit button, предложенный by Colin Garven. Сперва взгляните на это (Demo ), постарайтесь придумать, как реализовать, и просто наслаждайтесь анимацией. Идея состоит в следующем: при первом клике submit button трансформируется в круг, который покажет анимацию загрузки, используя свои границы (далее бордер). Когда анимация закончится, кнопка вернет исходную форму и покажет отметку, указывающую, что подтверждение прошло успешно или нет.
Существует несколько способов реализовать кнопку с таким эффектом. Думая о реализации только посредствам CSS, самой тяжёлой частью кажется круг прогресса. Существует техника реализации с помощью свойства clip. Anders Ingemann написал отличный полный tutorial (на LESS). Но мы будем использовать технику, основанную на SVG, CSS transitions и немного JS. Что касается круга прогресса, отметок success /fail, мы воспользуемся техникой рисования SVG линий, описанную by Jake Archibald.
Стоит отметить, что анимирование SVG может быть проблематичным для некоторых браузеров. Так что воспринимайте этот tutorial только как экспериментальное упражнение.
Поехали!
The Master plan
Если вы внимательно изучили Colin’s Dribbble shot, то должны были заметить, что нам следует позаботиться о нескольких состояниях кнопки. Интересная часть – переход между двумя такими состояниями. Сперва мы должны показать простую кнопку с прозрачным фоном и цветным бордером. При наведении мышки кнопка заполняется цветом бордера, а текст становится белым.
При нажатии на кнопку (для того, что бы, например, подтвердить отправку формы), текст должен плавно исчезнуть, ширина кнопки — уменьшится до состояния круга, бордер — стать уже, и должна начаться анимация. Мы будем использовать SVG-круг для анимации, так что нам необходимо убедиться, что получившаяся кнопка такого же размера и в той же позиции, что и SVG-круг, который виден в этот момент. Затем мы рисуем круг, изображающий загрузку подтверждения.
Когда подтверждение завершено, а круг полностью нарисован, кнопка должна снова расшириться, нарисовать галочку в случае успешного подтверждения. И закрашиваем кнопку соответственно.
В случае неудачи подтверждения, нужно показать состояние ошибки.
Давайте создадим нашу разметку со всеми необходимыми элементами.
Разметка
Для разметки нам потребуется главный контейнер, кнопка со спаном(span), содержащим текст, и три SVG:
<!-- progress button -->
<div id="progress-button" class="progress-button">
<!-- button with text -->
<button><span>Submit</span></button>
<!-- svg circle for progress indication -->
<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>
<!-- checkmark to show on success -->
<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>
<!-- cross to show on error -->
<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><!-- /progress-button -->
Используем Method Draw, ведь проще всего воспользоваться онлайн SVG генератором, чтобы нарисовать галочку и крестик для кнопки. Размеры всех SVG будут 70х70, так как высота нашей кнопки 70рх. Если мы хотим круг с бордером толщиной в 5 единиц нам нужно установить правильный радиус, когда будим рисовать его в графическом редакторе, да так чтобы весь круг с его бордером имел высоту 70рх. Заметьте, что в SVG обводка рисуется симметрично границе объекта. К примеру, обводка толщиной в 2рх увеличит круг радиусом в 10рх к реальным толщине и высоте 20+2 вместо 20+4(ширина бордера дважды), то есть формула 2*r+border. Для нашего случая мы знаем 2*r+5=70, от сюда наш круг должен иметь радиус в 32,5рх. Таким образом выходит: />.
К сожалению, мы не можем использовать только эти базовые размеры, потому что дефолтные параметры вставки у каждого браузера свои и мы не можем контролировать, где «анимация загрузки начнётся». Таким образом, нам придётся конвертировать эту форму в path и использовать его. Вы можете легко осуществить это в Method Draw under Object > Convert to Path.
Для крестика мы будем использовать четыре элемента, которые можно нарисовать со средней точки, и анимировать так же как галочку.
Итак, у нас есть все необходимые элементы. Продумаем порядок действий и начнем стилизацию.
CSS
Сперва необходимо стилизовать контейнер для кнопки:
.progress-button {
position: relative;
display: inline-block;
text-align: center;
}
Укажем нашей кнопке цвета и шрифты. Что бы она соответствовала концепту, установим правильный бордер и шрифт Montserrat:
.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;
}
Также ставим transition для всех свойств, которые будут анимироваться (background-color, width etc.).
При наведении курсора мыши меняем цвет фона и цвет текста:
.progress-button button:hover {
background-color: #1ECD97;
color: #fff;
}
Уберем все обводки (outline):
.progress-button button:focus {
outline: none;
}
Все SVG должны быть находиться по центру, все pointer-events отключены:
.progress-button svg {
position: absolute;
top: 0;
left: 50%;
-webkit-transform: translateX(-50%);
transform: translateX(-50%);
pointer-events: none;
}
Формы не должны иметь цвета заливки, так как мы хотим играть только с обводкой. Мы не показываем элементы ни в каких состояниях кнопки, кроме нужного, так что давайте спрячем их, сделав прозрачными:
.progress-button svg path {
opacity: 0;
fill: none;
}
Наш загрузочный круг будет создан установкой stroke-width в 5 единиц:
.progress-button svg.progress-circle path {
stroke: #1ECD97;
stroke-width: 5;
}
Success/fail индикаторы будут иметь обводку тоньше, и она должна быть белой. Свойству stroke-linecap установим значение round, так они будут красивее. У них установим быстрое изменение прозрачности:
.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;
}
Теперь давайте подрезюмируем и вспомним наш master plan. Нам необходимо было иметь возможность стилизовать три добавленных состояния (помимо дефолтного) кнопки и специальных элементов. Будем использовать классы “loading”, “success” и “error” для их индикации.
Кнопка станет круглой и будет выглядеть в точности как загрузочный круг SVG, когда мы начнем процесс загрузки:
.loading.progress-button button {
width: 70px; /* make a circle */
border-width: 5px;
border-color: #ddd;
background-color: transparent;
color: #fff;
}
Помним, что мы уже установили transition, когда задавали стили для кнопки. Текст должен быстро исчезнуть, когда начнется анимация загрузки,…
.loading.progress-button span {
-webkit-transition: opacity 0.15s;
transition: opacity 0.15s;
}
… путем установки нулевого значения для прозрачности:
.loading.progress-button span,
.success.progress-button span,
.error.progress-button span {
opacity: 0; /* keep it hidden in all states */
}
При смене состояний с loading на success/error нам не нужен transition, просто оставим текст скрытым.
Когда мы убираем все классы и возвращаем кнопку к исходному состоянию. Тексту нужно немного больше времени для появления, так что установим другие значения длительности анимации и ее задержки для нормального состояния:
/* Transition for when returning to default state */
.progress-button button span {
-webkit-transition: opacity 0.3s 0.1s;
transition: opacity 0.3s 0.1s;
}
При достижении последнего состояния необходимо переопределить transition, так как нам не надо анимировать цвет бордера или широту кнопки:
.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;
}
Зададим цвета для последнего состояния:
.success.progress-button button {
border-color: #1ECD97;
background-color: #1ECD97;
}
.error.progress-button button {
border-color: #FB797E;
background-color: #FB797E;
}
Когда будет применен необходимый класс, нужно показать нашу SVG и анимировать stroke-dashoffset установкой следующео значения для transition:
.loading.progress-button svg.progress-circle path,
.success.progress-button svg.checkmark path,
.error.progress-button svg.cross path {
opacity: 1;
-webkit-transition: stroke-dashoffset 0.3s;
transition: stroke-dashoffset 0.3s;
}
Добавим easing для анимации широты кнопки:
.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;
}
Если вы любите играть с другими easing-функциями – пробуйте Ceaser, CSS Easing Animation Tool by Matthew Lein.
Со стилями определились, творим магию дальше.
JavaScript
Начнем с инициализации некоторых элементов: button – это html-элемент button, progressEl – SVG элемент кольцо progress bar, а 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' ) );
// init events
this._initEvents();
// enable button
this._enable();
}
Добавили функцию SVGEI, которая будет использоваться для того, то бы предоставить SVG-элементы и их paths. Мы кэшируем path и соответственно длину для каждого. Изначально мы «оттягиваем» все paths, управляя значениями свойств strokeDasharray и strokeDashoffset. Позже мы «втянем» их обратно, когда покажем загрузочный круг, или галочку, или крестик. Эту технику хорошо объясняет Jake Archibald в статье Animated line drawing in SVG. Устанавливаем значение stroke-dasharray равное длине path и оттягиваем его. Установив значение stroke-dashoffset также равное его длине, мы больше не видим его. Когда нам нужно будет показать фигуру — установим offset на 0, имитируя рисование фигуры:
function SVGEl( el ) {
this.el = el;
// the path elements
this.paths = [].slice.call( this.el.querySelectorAll( 'path' ) );
// we will save both paths and its lengths in arrays
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();
} );
// undraw stroke
this.draw(0);
}
// val in [0,1] : 0 - no stroke is visible, 1 - stroke is visible
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 event для нашей кнопки. Сначала кнопка превратится в круг (с помощью добавления класса loading). После окончания анимации, либо будет вызвана callback функция, либо прогресс установится на 100%. В данный момент кнопка отключается (это событие должно бы быть самым первым, однако такой браузер как firefox, например, не сможет удалить transitionend event):
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.
UIProgressButton.prototype.stop = function( status ) {
var self = this,
endLoading = function() {
self.progressEl.draw(0);
if( typeof status === 'number' ) {
var statusClass = status >= 0 ? 'success' : 'error',
statusEl = status >=0 ? self.successEl : self.errorEl;
statusEl.draw( 1 );
// add respective class to the element
classie.addClass( self.el, statusClass );
// after options.statusTime remove status and undraw the respective stroke and enable the button
setTimeout( function() {
classie.remove( self.el, statusClass );
statusEl.draw(0);
self._enable();
}, self.options.statusTime );
}
else {
self._enable();
}
classie.removeClass( self.el, 'loading' );
};
// give it a little time (ideally the same like the transition time) so that the last progress increment animation is still visible.
setTimeout( endLoading, 300 );
}
Кнопка готова!
«We hope you enjoyed this tutorial and find it useful!»
Комментарии (8)
kahi4
29.11.2015 16:29+17Выглядит красиво. Но хватит отъедать впустую ресурсы моего компьютера! В 99.9% вы не сможете ничего адекватного выводить в виде процентов, а без них все делается в 10 строчек css: накидано за 20 секунд. В моем примере весь js только классы переключает.
Ни svg, ни сотни вложенных элементов, простой и чистый стиль (правда, не без своих недостатков).
P.S. Для галочки и крестика стоит использовать иконочный шрифт, картинку или что угодно, а так же паддинг немного съехал, поэтому прыгает, нужно бы поправитьmapron
30.11.2015 05:37Мне гораздо больше нравится. Даже не внешне, а с точки зрения понимания и поддержки кода. Открыл — и мне, даже нифига не фронтенд-разработчику, усе ясно.
taliban
01.12.2015 14:01Супер, гораздо предпочтительней того что в тстатье описано, но есть одно но:
.button.load { background-color: transparent; }
Иначе при анимации и наведении мыши фон меняется и смотрится это все не ахти
namespace
Какой же отвратительный перевод!
SerDIDG
Как и JS код )
mapron
Простите, я не понимаю, зачем здесь столько JS кода? Несклько gif взамен не были бы лучше? (с точки зрения и веса, и производительности). Может я что-то не понял в новомодных трендах?
mannaro
Я думаю, что тут используется SVG+JS для легкой кастомизации кнопки (нужно будет сменить цвет на красный, например). Да и SVG на retina выглядит гораздо лучше (знаю, можно использовать @2x, но тут-то все из коробки).
Но вот качество конкретно этого JS страдает, это да.
Andchir
Кода не так уж и много. Как вы с помощью GIF сделаете показ процесса загрузки в процентах?