教程参考Colin Garven提出的实现圆形进度按钮的思路。我们将使用Jake Archibald讲过的SVG素描动画技术来实现圆形进度过程,然后提供一个成功或失败的状态来显示完成后的最终状态。
【查看DEMO 】
今天给大家展示如何实现一个漂亮的进度按钮的思路,这个思路来自于Colin Garven的不可思议的提交按钮。我们首先看一下实现这个思路需要的步骤,顺便欣赏一下这个动画^^。正如Colin在评论中提到的,实现这个按钮背后的想法如下:一旦点击,提交按钮就变成一个圆环,并且使用这个圆环的边界展示一个进度动画。当这个进度动画完成时,按钮将再次恢复成原来大小,同时会显示一个标记用来确认提交已完成。接下来我们将完成这个思路,并且针对提交失败的情况我们添加另一个标记。
如果只考虑CSS技术,也是有可能实现这样的按钮和动画效果的。其中最具挑战的部分还是环形的进度效果,这里提供一个聪明的技巧——使用Clip属性来实现进度效果。关于这个技巧,Anders Ingemann写了一篇详实的教程。但是这次我们还是基于SVG技术,CSS转换和一点儿JavaScript来实现。关于环形进度效果和叉号,我们将使用Jake Archibald介绍的动画素描技术。
需要注意的是动画SVG在浏览器的兼容上还是存在问题,这些类型的技术仍在起步阶段,所以本教程只用来实验练习,期待在未来可以派上用场。
所以,让我们开始吧。
总体规划: 如果你有仔细观察Colin的Dribbble上展示的效果动画,可能已经注意到,我们需要关注按钮的几个状态。
有意思的部分是按钮从一个状态转换成另一个状态。
首先,我们要使用透明背景和边框颜色来展示一个简单的按钮,鼠标悬停时,我们使用边框色来填充该按钮,并且把按钮上的文本变成白色。
当我们点击按钮(例如,为了提交一个表单),我们要淡出文本,减少按钮的宽度使它变成一圆圈,并且使按钮的边框变厚,在边框上开始一个进度动画。我们将使用SVG圆圈来实现进度动画,因此我们需要确保动画开始的瞬间,按钮圆圈和SVG圆圈大小一致,位置一致。然后我们绘制圆的边线,模拟提交的过程。
一旦提交完成,即边线都绘制完。我们必须使按钮再次扩大到原来大小,并且在提交成功时绘制对号,把按钮的颜色变成相应的颜色。
提交失败的情况下,我们也需要一个错误状态的风格。
接下来,让我们创建标记与我们所需要的元素。
标记:
为创建我们的标记,我们需要一个主容器,一个按钮(其中有包含文本的一个span元素)和三个SVG:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <div  id ="progress-button"  class ="progress-button" >          <button > <span > Submit</span > </button >             <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 > 
 
我们预先使用Method Draw(一个易于使用的在线SVG编辑器)绘制对号和叉号。所有SVG的尺寸为70×70,因为我们按钮的高度为70像素。我们希望圆的边线为5像素,这样看起来更像Colin的效果。当我们在图形编辑器中绘制时,需要设置正确的半径,整个圆连同它的边线为70像素。需要注意在SVG中边线为半嵌入式(即一半在圆内一半在圆外),例如,边线为2,半径为10的圆,它的宽和高为20+2,而不是10+4(边线的2倍),因此公式为2r+边界。所以在我们的案例中我们知道2r+5 = 70,因此我们需要一个半径为32.5的圆。最终得到的形状为:
1 <circle  cx = "35"  cy="35"  r="32.5" /> 
 
不幸的是,我们不能仅仅使用这个基本的形状,因为“路径”的起点在浏览器中是不同的,所以我们不能控制“进度动画”的起始点。所以,我们需要把这个圆转成路径,而不是(上面的基本形状)。你可以使用Method Draw中Object菜单下的Convert to Path很容易实现。
叉号我们将使用4个路径,这样我们可以从中心点开始绘制,使它看起来跟对号的动画类似。
现在我们有了我们需要的所有元素。让我们想想操作的流程,开始样式!
CSS 首先,我们需要给按钮容器添加样式。它就像按钮的外层皮肤,让我们使它更像一个按钮,我们把它的显示设置为inline-block。这样我们可以在文档流里使用它。
1 2 3 4 5 .progress-button  {     position : relative  ;     display : inline-block  ;     text-align : center  ; } 
 
我们的按钮需要一些着色和排版,为了让它看起来更像Colin的按钮,我们需要设置正确的边框,使用Montserrat字体:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 .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   ; } 
 
我们还需要为那些将使用动画的属性添加一个过渡效果,如background-color,width等等。
在鼠标悬停时,我们将更改背景色和字体色:
1 2 3 4 .progress-button  button :hover {     background-color: #1ECD97   ;    color : #fff   ; } 
 
让我们移除任何高亮的轮廓:
1 2 3 .progress-button  button :focus {     outline: none  ;} 
 
所有的SVG需要绝对定位在中心位置,并且我们将不允许任何pointer-events:
1 2 3 4 5 6 7 8 .progress-button  svg  {     position : absolute  ;     top : 0   ;     left : 50%   ;     -webkit-transform : translateX (-50% )  ;     transform : translateX (-50% )  ;     pointer-events : none  ; } 
 
因为我们只想操作边线,所以路径不应该有任何填充。路径除了在特殊状态下,其他状态都不需要展示它们,所以我们通过设置它们的透明度为0来隐藏它们。
1 2 3 4 .progress-button  svg  path  {     opacity : 0   ;     fill : none  ; } 
 
我们的进度环将通过设置圆形路径的边线为5来实现:
1 2 3 4 .progress-button  svg .progress-circle  path  {     stroke : #1ECD97   ;     stroke-width : 5   ; } 
 
成功/错误的指示符号将由细的白色边线绘制,我们还将设置边线的linecap为round,这样看起来更加柔和。这两个符号都会有一个快速的不透明度的过渡效果。
1 2 3 4 5 6 7 8 .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”来表示这三种状态。
当我们开始加载过程时,这个按钮将转变成一个圆,看起来更像一个表示进度的圆环。
1 2 3 4 5 6 7 .loading .progress-button  button  {     width : 70px   ;      border-width : 5px   ;     border-color : #ddd   ;     background-color : transparent  ;     color : #fff   ; } 
 
还记得么,我们在定义按钮样式时已经设置了过渡效果。
当我们开始进度动画时,文本应该快速淡出。
1 2 3 4 .loading .progress-button  span  {     -webkit-transition : opacity 0.15s   ;     transition : opacity 0.15s   ; } 
 
……通过设置opacity为0:
1 2 3 4 5 .loading .progress-button  span ,.success .progress-button  span ,.error .progress-button  span  {     opacity : 0   ;  } 
 
当从加载状态变成成功或错误的状态时,我们不需要设置过渡,只需把文本简单的隐藏掉就可以。
当我们删除所有类返回到默认状态时,我们需要稍长一点儿的时间来显示文本。所以我们需要定义不同的过渡和延时时间来回到正常的显示状态。
1 2 3 4 5 .progress-button  button  span  {     -webkit-transition : opacity 0.3s  0.1s   ;     transition : opacity 0.3s  0.1s   ; } 
 
当我们到达最后的状态,提交成功或失败。这时我们需要重新定义一下按钮的过渡效果。
1 2 3 4 5 .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   ; } 
 
接下来我们为最终状态设置颜色相关的样式:
1 2 3 4 5 6 7 8 9 .success .progress-button  button  {     border-color : #1ECD97   ;     background-color : #1ECD97   ; }   .error .progress-button  button  {     border-color : #FB797E   ;     background-color : #FB797E   ; } 
 
当我们应用相应类的同时,需要展示SVG路径,并通过下面设置的过渡效果来实现stroke-dashoffset的动画。
1 2 3 4 5 6 7 .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
1 2 3 4 5 6 7 8 9 10 11 .elastic.progress-button button {     -webkit-transition: background-color 0.3 s, color 0.3 s, width 0.3 s cubic-bezier(0.25 , 0.25 , 0.4 , 1 ), border-width 0.3 s, border-color 0.3 s;     -webkit-transition: background-color 0.3 s, color 0.3 s, width 0.3 s cubic-bezier(0.25 , 0.25 , 0.4 , 1.6 ), border-width 0.3 s, border-color 0.3 s;     transition: background-color 0.3 s, color 0.3 s, width 0.3 s cubic-bezier(0.25 , 0.25 , 0.4 , 1.6 ), border-width 0.3 s, border-color 0.3 s; }   .loading.elastic.progress-button button {     -webkit-transition: background-color 0.3 s, color 0.3 s, width 0.3 s cubic-bezier(0.6 , 0 , 0.75 , 0.75 ), border-width 0.3 s, border-color 0.3 s;     -webkit-transition: background-color 0.3 s, color 0.3 s, width 0.3 s cubic-bezier(0.6 , -0.6 , 0.75 , 0.75 ), border-width 0.3 s, border-color 0.3 s;     transition: background-color 0.3 s, color 0.3 s, width 0.3 s cubic-bezier(0.6 , -0.6 , 0.75 , 0.75 ), border-width 0.3 s, border-color 0.3 s; } 
 
如果你想研究其他的easing函数,可以使用Ceaser,这个工具是由Matthew Lein提供的CSS Easing动画工具。
目前为止样式已经OK,让我们继续我们的魔术^^
JAVASCRIPT 我们将首先初始化/缓存一些元素:button是一个HTML的按钮元素,progressEl是SVG元素用来表示圆形进度条,successEl,errorEl两个SVG元素分别用来表示对号和叉号。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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元素及其路径。对于每个SVG元素,我们将缓存其路径及各路径的长度。首先我们通过stokeDasharray和strokeDashoffset两个值来初始完成路径,稍后当需要展示圆形进度和对号或叉号时再动画绘制该路径。SVG实现动画素描中Jack Archibald已经详细的给我们介绍这门技术。我们大体上设置stroke-dasharray为路径的长度,再设置stroke-dashoffset也为路径的长度,这样我们会什么也看不到,当需要绘制时,只需要把偏移量设置为0(即stoke-dashoffset为0)来模拟出动画绘制该路径的过程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 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 ); }   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 );     } } 
 
接下来我们需要给按钮绑定click事件。这个按钮最初为一个圆形动画(通过添加loading类)。该动画结束后,现有的回调函数被调用(如果在options里有指定的话)或者我们只是将进行到100%(这个“假”动画的速度与css中定义的stroke-dashoffset的过渡是相同的),在这个点时按钮是不可点击的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 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控制转换。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 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  );                                  classie.addClass( self .el, statusClass );                                  setTimeout( function ()   {                     classie.remove( self .el, statusClass );                     statusEl.draw(0 );                     self ._enable();                 }, self .options.statusTime );             }             else  {                 self ._enable();             }               classie.removeClass( self .el, 'loading'  );         };            setTimeout( endLoading, 300  ); } 
 
按钮完成!
我们希望你喜欢这个教程,觉得它有用!