提交按钮动画效果(基于SVGi)

时间:2022-11-20 18:32:24

翻译整理一篇外文前端效果,基于SVG的按钮动画。

提交按钮动画效果(基于SVGi)

效果:

1、hover:按钮颜色过渡;
2、click:按钮转换为圆环,开始环形描边动画;
3、完成环形描边动画,显示成功或错误状态 (对勾和差);
4、恢复初始按钮状态

虽然存在其他可选方案,但本文将主要通过基于SVG和CSS transition技术实现这一效果。基于SVG的动画可能不会被所有浏览器支持,可视本文为前端实验性案例。

按钮状态:

提交按钮动画效果(基于SVGi)


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);