基于SVG和Segment.js的Loading加载按钮特效

这是一款基于SVG和Segment.js制作的Loading加载按钮特效。该特效在点击按钮之后会出现基于SVG路径的无限圆形加载效果,加载成功或失败时会转换为不同的图形,显示不同的状态。

在线预览    源码下载

使用方法

SVG可以绘制很多基本图形,如圆形、椭圆、矩形等,但是只有SVG路径可以制作线条动画。因此,我们要制作一个圆形的线条动画必须使用路径。关于如何绘制一条SVG路径,可以参考这里

在这个特效中,为了制作线条动画,使用了Segment.js插件。Segment.js是一个可以制作SVG路径片段动画的js库。

绘制路径

要制作出这个特效,首先是要了解线条如何动画。所有的线条必须手动画出来。下面是第一个loading效果的SVG线条。

<svg width="120px" height="120px">
    <path class="outer-path" stroke="#fff" d="M 60 60 m 0 -50 a 50 50 0 1 1 0 100 a 50 50 0 1 1 0 -100"></path>
    <path class="inner-path" stroke="rgba(255, 255, 255, 0.5)" d="M 60 60 m 0 -30 a 30 30 0 1 1 0 60 a 30 30 0 1 1 0 -60"></path>
    <path class="success-path" stroke="#fff" d="M 60 10 A 50 50 0 0 1 91 21 L 75 45 L 55 75 L 45 65"></path>
    <path class="error-path" stroke="#fff" d="M 60 10 A 50 50 0 0 1 95 25 L 45 75"></path>
    <path class="error-path2" stroke="#fff" d="M 60 30 A 30 30 0 0 1 81 81 L 45 45"></path>
</svg>
  

为它添加一些CSS样式:

body{
  background: #354458;
}

svg path{
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 4;
  fill: none;
}    
  

上面的代码通过直线和弧线一步步的创建了整个loading所需的线条。创建后会得到如下图左边所示的效果。右边的图是失败状态的效果。

svg loading加载按钮

通过Segment.js进行动画

下面是创建这个loading动画的关键js代码。

var outer = document.querySelector('.outer-path'),
    inner = document.querySelector('.inner-path'),
    outerSegment = new Segment(outer, 0, 0.1),
    innerSegment = new Segment(inner, 0, 0.1);

function outerAnimation(){
    outerSegment.draw('15%', '25%', 0.2, {callback: function(){
        outerSegment.draw('75%', '150%', 0.3, {circular:true, callback: function(){
            outerSegment.draw('70%', '75%', 0.3, {circular:true, callback: function(){
                outerSegment.draw('100%', '100% + 0.1', 0.4, {circular:true, callback: function(){
                    outerAnimation();
                    innerAnimation();
                }});
            }});
        }});
    }});
}

function innerAnimation(){
    innerSegment.draw('20%', '80%', 0.6, {callback: function(){
        innerSegment.draw('100%', '100% + 0.1', 0.6, {circular:true});
    }});
}

outerAnimation();
innerAnimation();    
  
创建一个通用js库

通过上面的代码我们已经可以进行loading加载线条动画了。但是我们还没有处理loading成功和失败时的状态。另外如果想添加新的loader该如何做呢?最好的方法是创建一个通用的js库或插件来处理这些问题。下面就是这个通用库的js代码:

function LoadingButton(el, options){
    this.el = el;
    this.options = options;
    this.init();
}

LoadingButton.prototype = {
    // Initialize everything
    init: function(){
        this.infinite = true;
        this.succeed = false;
        this.initDOM();
        this.initSegments();
        this.initEvents();
    },

    // Create an span element with inner text of the button and insert the corresponding SVG beside it
    initDOM: function(){
        this.el.innerHTML = '' + this.el.innerHTML + '';
        this.span = this.el.querySelector('span');
        var div = document.createElement('div');
        div.innerHTML = document.querySelector(this.options.svg).innerHTML;
        this.svg = div.querySelector('svg');
        this.el.appendChild(this.svg);
    },

    // Initialize the segments for all the paths of the loader itself, and for the success and error animations
    initSegments: function(){
        for(var i = 0, paths = this.options.paths, len = paths.length; i < len;="" i++){="" paths[i].el="this.svg.querySelector(paths[i].selector);" paths[i].begin="paths[i].begin" paths[i].begin="" :="" 0;="" paths[i].end="paths[i].end" paths[i].end="" :="" 0.1;="" paths[i].segment="new" segment(paths[i].el,="" paths[i].begin,="" paths[i].end);="" }="" this.success="this.el.querySelector('.success-path');" this.error="this.el.querySelector('.error-path');" this.error2="this.el.querySelector('.error-path2');" this.successsegment="new" segment(this.success,="" 0,="" 0.1);="" this.errorsegment="new" segment(this.error,="" 0,="" 0.1);="" this.errorsegment2="new" segment(this.error2,="" 0,="" 0.1);="" },="" initialize="" the="" click="" event="" in="" loading="" buttons,="" that="" trigger="" the="" animation="" initevents:="" function(){="" var="" self="this;" self.el.addeventlistener('click',="" function(){="" self.el.disabled='disabled' ;="" classie.add(self.el,="" 'open-loading');="" self.span.innerhtml='Sending' ;="" for(var="" i="0," paths="self.options.paths," len="paths.length;" i="">< len;="" i++){="" paths[i].animation.call(self,="" paths[i].segment);="" }="" },="" false);="" },="" make="" it="" fail="" triggerfail:="" function(){="" this.infinite="false;" this.succeed="false;" },="" make="" it="" succeed="" triggersuccess:="" function(){="" this.infinite="false;" this.succeed="true;" },="" when="" each="" animation="" cycle="" is="" completed,="" check="" whether="" any="" feedback="" has="" triggered="" and="" call="" the="" feedback="" handler,="" otherwise="" it="" restarts="" again="" completed:="" function(reset){="" if(this.infinite){="" for(var="" i="0," paths="this.options.paths," len="paths.length;" i="">< len;="" i++){="" if(reset){="" paths[i].segment.draw(0,="" 0.1);="" }="" paths[i].animation.call(this,="" paths[i].segment);="" }="" }else{="" this.handleresponse();="" }="" },="" handle="" the="" feedback="" request,="" and="" perform="" the="" success="" or="" error="" animation="" handleresponse:="" function(){="" for(var="" i="0," paths="this.options.paths," len="paths.length;" i="">< len;="" i++){="" paths[i].el.style.visibility='hidden' ;="" }="" if(this.succeed){="" this.success.style.visibility='visible' ;="" this.successanimation();="" }else{="" this.error.style.visibility='visible' ;="" this.error2.style.visibility='visible' ;="" this.erroranimation();="" }="" },="" success="" animation="" successanimation:="" function(){="" var="" self="this;" self.successsegment.draw('100%="" -="" 50',="" '100%',="" 0.4,="" {callback:="" function(){="" self.span.innerhtml='Succeed' ;="" classie.add(self.el,="" 'succeed');="" settimeout(function(){="" self.reset();="" },="" 2000);="" }});="" },="" error="" animation="" erroranimation:="" function(){="" var="" self="this;" self.errorsegment.draw('100%="" -="" 42.5',="" '100%',="" 0.4);="" self.errorsegment2.draw('100%="" -="" 42.5',="" '100%',="" 0.4,="" {callback:="" function(){="" self.span.innerhtml='Failed' ;="" classie.add(self.el,="" 'failed');="" settimeout(function(){="" self.reset();="" },="" 2000);="" }});="" },="" reset="" the="" entire="" loading="" button="" to="" the="" initial="" state="" reset:="" function(){="" this.el.removeattribute('disabled');="" classie.remove(this.el,="" 'open-loading');="" this.span.innerhtml='Send' ;="" classie.remove(this.el,="" 'succeed');="" classie.remove(this.el,="" 'failed');="" this.resetsegments();="" this.infinite="true;" for(var="" i="0," paths="this.options.paths," len="paths.length;" i="">< len;="" i++){="" paths[i].el.style.visibility='visible' ;="" }="" this.success.style.visibility='hidden' ;="" this.error.style.visibility='hidden' ;="" this.error2.style.visibility='hidden' ;="" },="" reset="" the="" segments="" to="" the="" initial="" state="" resetsegments:="" function(){="" for(var="" i="0," paths="this.options.paths," len="paths.length;" i="">< len;="" i++){="" paths[i].segment.draw(paths[i].begin,="" paths[i].end);="" }="" this.successsegment.draw(0,="" 0.1);="" this.errorsegment.draw(0,="" 0.1);="" this.errorsegment2.draw(0,="" 0.1);="" }="" };="">

当然还应该为动画添加一些必要的CSS样式,下面使用的是SCSS代码:

// Loading button
.loading-button{
  // When loading button is open
  &.open-loading{
    color: rgba(255, 255, 255, 0.8);
    &.infinity{
      padding-top: 80px;
    }
    svg{
      display: inline-block;
      visibility: visible;
      opacity: 1;
      transition: 1s opacity;
      transform: translateX(-50%);
    }
  }
  // Loading failed
  &.failed{
    background-color: #EB7260;
  }
  // Loading succeed
  &.succeed{
    background-color: #29ABA4;
  }
  // Remove transition when changing demo position
  &.no-transition{
    transition: 0s;
    *{
      transition: 0s;
    }
  }
  // SVG element, centered and hidden initially
  svg{
    visibility: hidden;
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    opacity: 0;
    transition: 0s;
    path{
      stroke-linecap: round;
      stroke-linejoin: round;
      stroke-width: 4;
      fill: none;
      // To hide success and error paths
      &.success-path, &.error-path, &.error-path2{
        visibility: hidden;
      }
    }
  }
}

// Handle positions
.loading-button {
  &.top {
    svg{
      top: 10px;
    }
  }
  &.bottom {
    svg{
      bottom: 10px;
    }
  }
  &.left {
    svg {
      top: 50%;
      transform: scale(0.25) translateY(-50%);
      transform-origin: 0 0 0;
      left: 20px;
    }
  }
  &.right {
    svg {
      top: 50%;
      transform: scale(0.25) translateY(-50%);
      transform-origin: 100% 0 0;
      left: auto;
      right: 20px;
    }
  }
  &.open-loading {
    &.left {
      padding-left: 60px;
    }
    &.right {
      padding-right: 60px;
    }
    &.top, &.bottom {
      svg{
        transition-delay: 0.2s;
      }
    }
    &.circular-loading, &.circle-loading {
      &.top {
        padding-top: 140px;
      }
      &.bottom {
        padding-bottom: 140px;
      }
    }
    &.infinity-loading {
      &.top {
        padding-top: 80px;
      }
      &.bottom {
        padding-bottom: 80px;
      }
    }
  }
}    
  
使用新的SVG loading加载效果

如果要使用新的SVG loading加载效果,可以像下面这样将SVG代码放置在一个模板中:

<script type="text/template" id="circular-loading">
    <svg width="120px" height="120px">
        <path class="outer-path" stroke="#fff" d="M 60 60 m 0 -50 a 50 50 0 1 1 0 100 a 50 50 0 1 1 0 -100"></path>
        <path class="inner-path" stroke="rgba(255, 255, 255, 0.5)" d="M 60 60 m 0 -30 a 30 30 0 1 1 0 60 a 30 30 0 1 1 0 -60"></path>
        <path class="success-path" stroke="#fff" d="M 60 10 A 50 50 0 0 1 91 21 L 75 45 L 55 75 L 45 65"></path>
        <path class="error-path" stroke="#fff" d="M 60 10 A 50 50 0 0 1 95 25 L 45 75"></path>
        <path class="error-path2" stroke="#fff" d="M 60 30 A 30 30 0 0 1 81 81 L 45 45"></path>
    </svg>
</script>    
  

然后使用下面的代码来驱动新的loading动画。

function circularLoading(){
    var button = document.querySelector('.loading-button'),
        options = {
            svg: '#circular-loading',
            paths: [
                {selector: '.outer-path', animation: outerAnimation},
                {selector: '.inner-path', animation: innerAnimation}
            ]
        },
        loading = new LoadingButton(button, options);

    function outerAnimation(segment){
        var self = this;
        segment.draw('15%', '25%', 0.2, {callback: function(){
            segment.draw('75%', '150%', 0.3, {circular:true, callback: function(){
                segment.draw('70%', '75%', 0.3, {circular:true, callback: function(){
                    segment.draw('100%', '100% + 0.1', 0.4, {circular:true, callback: function(){
                        self.completed(true);
                    }});
                }});
            }});
        }});
    }

    function innerAnimation(segment){
        segment.draw('20%', '80%', 0.6, {callback: function(){
            segment.draw('100%', '100% + 0.1', 0.6, {circular:true});
        }});
    }

    return loading;
}    
  

原文地址:Creating Loading Buttons with SVG and Segment

在线预览    源码下载

爱编程-编程爱好者经验分享平台
版权所有 爱编程 © Copyright 2012. All Rights Reserved.
闽ICP备12017094号-3