翻译整理一篇外文前端效果,基于SVG的按钮动画。
效果:
1、hover:按钮颜色过渡;2、click:按钮转换为圆环,开始环形描边动画;
3、完成环形描边动画,显示成功或错误状态 (对勾和差);
4、恢复初始按钮状态
虽然存在其他可选方案,但本文将主要通过基于SVG和CSS transition技术实现这一效果。基于SVG的动画可能不会被所有浏览器支持,可视本文为前端实验性案例。
按钮状态:
Html代码段:
<div id="progress-button" class="progress-button">
<!-- button元素,显示button初始状态-->
<button><span>Submit</span></button>
<!-- svg 圆形图案,通过path元素进行描边动画 -->
<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>
CSS代码段:
.progress-button {
position: relative;
display: inline-block;
text-align: center;
width: 45%;
min-width: 250px;
margin: 10px;}
.progress-button button {
display: block;
margin: 0 auto;
padding: 0;
width: 250px;
height: 70px;
border: 2px solid #1ECD97;
border-radius: 35px;
background: transparent;
color: #1ECD97;
letter-spacing: 1px;
font-size: 18px;
font-family: 'Montserrat', sans-serif;
/* button元素过渡效果应用属性:背景色,前景色,宽度,边框宽,边框颜色 */
-webkit-transition: background-color 0.3s, color 0.3s, width 0.3s, border-width 0.3s, border-color 0.3s;
-moz-transition: background-color 0.3s, color 0.3s, width 0.3s, border-width 0.3s, border-color 0.3s;
-o-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;
cursor:pointer;
}
.progress-button svg {
position: absolute;
top: 0;
left: 50%;
-webkit-transform: translateX(-50%);
transform: translateX(-50%);
pointer-events: none;
}
.progress-button svg path {/*隐藏svg path元素*/
opacity:0;
fill:none;
}
.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:4px;
-webkit-transition:opacity 0.1s;
transition:opacity 0.1s;
}
.loading.progress-button button {
width: 70px; /* make a circle */
border-width: 5px;
border-color: #ddd;
background-color: transparent;
color: #fff;
}
.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; /*在整个动画过程中,隐藏button 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;
}
.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;/*改变svg path元素的stroke-dashoffeset的属性实现动画*/
transition: stroke-dashoffset 0.3s;
}
.success.progress-button button {
border-color: #1ECD97;
background-color: #1ECD97;
}
.error.progress-button button {
border-color: #FB797E;
background-color: #FB797E;
}
.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.6), 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代码段:
依赖库:Modernizr.js(自定义下载,主要用于判断浏览器对transition的支持);classie.js(判断元素应用了样式,为元素添加、删除样式,或进行样式切换)。
html内嵌javascript代码:
<script>
[].slice.call(document.querySelectorAll('.progress-button')).forEach(function(btn,pos){
new ProgressButton(btn,{
callback:function(instance){
var progress=0;
var interval=setInterval(function(){
progress=Math.min(progress+Math.random()*0.1,1);
instance.setProgress(progress);
if(progress===1){
instance.stop(pos===1 || pos ===3?-1:1);
clearInterval(interval);
}
},150);
}
});
});
</script>
外部js:
(function(window){
var transEndEventNames={'WebkitTransition': 'webkitTransitionEnd','MozTransition': 'transitionend','OTransition': 'oTransitionEnd','msTransition': 'MSTransitionEnd','transition': 'transitionend'};
var transEndEventName=transEndEventNames[Modernizr.prefixed( 'transition' ) ];
var support={transitions:Modernizr.csstransitions};
function extend( a, b ) {
for( var key in b ) {
if( b.hasOwnProperty( key ) ) {
a[key] = b[key];
}
}
return a;
}
function SVGEL(el){//SVG 元素的构造函数
//alert(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(value){
for( var i = 0, len = this.pathsArr.length; i < len; ++i ){
this.pathsArr[i].style.strokeDashoffset = this.lengthsArr[i] * ( 1 - value );
}
};
function ProgressButton(element,options){//ProgressButton类的构造函数入口
this.element=element;
this.options = extend( {}, this.options );
extend( this.options, options );
this._init();
}
ProgressButton.prototype._init=function(){//ProgessButton类的_init实例方法,用于初始化。
this.button = this.element.querySelector( 'button' );
this.progressElement = new SVGEL( this.element.querySelector( 'svg.progress-circle' ) );
this.successElement= new SVGEL( this.element.querySelector( 'svg.checkmark' ) );
this.errorElement = new SVGEL( this.element.querySelector( 'svg.cross' ) );
this._initEvents();
this._enable();
};
ProgressButton.prototype.options={statusTime : 1500};
ProgressButton.prototype._initEvents=function(){
var self=this;
this.button.addEventListener("click",function(){
self._submit();
});
};
ProgressButton.prototype._submit=function(){
classie.addClass( this.element, 'loading' );
var self=this;
var onEndBtnTransitionFn=function(event){
if(support.transitions){
if(event.propertyName!=='width'){return false;}
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();
}
};
ProgressButton.prototype.stop=function(status){
var self=this;
var endloading=function(){
self.progressElement.draw(0);
if(typeof status==='number'){
var statusClass=status>=0?'success':'error';
var statusElement=status>=0?self.successElement:self.errorElement;
statusElement.draw(1);
classie.addClass( self.element, statusClass );
setTimeout(function(){
classie.removeClass(self.element,statusClass);
statusElement.draw(0);
self._enable();
},self.options.statusTime);
}else{
self._enable();
}
classie.removeClass(self.element,'loading');
};
setTimeout(endloading,300);
};
ProgressButton.prototype.setProgress=function(val){
this.progressElement.draw(val);
};
ProgressButton.prototype._enable=function(){
this.button.removeAttribute('disabled');
};
window.ProgressButton=ProgressButton;//将ProgressButton类附加到window对象上,便于外部调用
})(window);