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

所属分类:其他-动画效果

 29135  387  查看评论 (4)
分享到微信朋友圈
X
基于SVG和Segment.js的Loading加载按钮特效 ie兼容9

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

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

1
2
3
4
5
6
7
<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样式:

1
2
3
4
5
6
7
8
9
body {
    background:#354458;
}
svg path {
    stroke-linecap:round;
    stroke-linejoin:round;
    stroke-width:4;
    fill:none;
}

Segment.js 是创建这个loading动画的关键js代码。

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
33
34
35
36
37
38
39
40
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();

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

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
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代码:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
// Loading button
.loading-button{
  // When loading button is open
  &.open-loading{
    color: rgba(2552552550.8);
    &.infinity{
      padding-top80px;
    }
    svg{
      display: inline-block;
      visibilityvisible;
      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{
    visibilityhidden;
    positionabsolute;
    left50%;
    transform: translateX(-50%);
    opacity: 0;
    transition: 0s;
    path{
      stroke-linecap: round;
      stroke-linejoin: round;
      stroke-width4;
      fill: none;
      // To hide success and error paths
      &.success-path, &.error-path, &.error-path2{
        visibilityhidden;
      }
    }
  }
}
 
// Handle positions
.loading-button {
  &.top {
    svg{
      top10px;
    }
  }
  &.bottom {
    svg{
      bottom10px;
    }
  }
  &.left {
    svg {
      top50%;
      transform: scale(0.25) translateY(-50%);
      transform-origin: 0 0 0;
      left20px;
    }
  }
  &.right {
    svg {
      top50%;
      transform: scale(0.25) translateY(-50%);
      transform-origin: 100% 0 0;
      leftauto;
      right20px;
    }
  }
  &.open-loading {
    &.left {
      padding-left60px;
    }
    &.right {
      padding-right60px;
    }
    &.top, &.bottom {
      svg{
        transition-delay: 0.2s;
      }
    }
    &.circular-loading, &.circle-loading {
      &.top {
        padding-top140px;
      }
      &.bottom {
        padding-bottom140px;
      }
    }
    &.infinity-loading {
      &.top {
        padding-top80px;
      }
      &.bottom {
        padding-bottom80px;
      }
    }
  }
}

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

1
2
3
4
5
6
7
8
9
10
11
<script type="text/template" id="circle-loading">
        <svg width="120px" height="120px">
            <circle r="50" cx="60" cy="60" fill="none" stroke="rgba(255, 255, 255, 0.3)"></circle>
            <circle r="30" cx="60" cy="60" fill="none" stroke="rgba(255, 255, 255, 0.3)"></circle>
            <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="#fff" 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 0 16 36  L 45 65 L 55 75 L 75 45"></path>
            <path class="error-path" stroke="#fff" d="M 60 10 A 50 50 0 0 0 25 95 L 75 45"></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动画。

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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;
}
相关插件-动画效果

css动画库Mimic.css

Mimic.css是一个css动画库,可以通过简单的调用,得到您想要的动画效果
  动画效果
 33490  384

小球碰撞(完全弹性碰撞)

给自己主页目录用这个的,顺便传上来。
  动画效果
 50294  472

新年快乐粒子烟花

根据其他插件改编拜年烟花效果
  动画效果
 46682  448

3D云 (酷!)

3D云在云端!
  动画效果
 80743  1566

讨论这个项目(4)回答他人问题或分享插件使用方法奖励jQ币 评论用户自律公约

    小小小蕾。 0
    2018/6/25 14:16:39
    怎么改变圆环的大小呢 回复
    东大大 0
    2018/1/17 15:32:08

    这个帅,支持作者!!!!!我就拿去小用一哈

    回复
    a'╃ ?? 0
    2017/8/29 14:51:28
    `莫欺少年穷 0
    2017/8/29 10:17:38
😃
  • 😀
  • 😉
  • 😥
  • 😵
  • 😫
  • 😘
  • 😡
  • 👍
  • 🌹
  • 👏
  • 🍺
  • 🍉
  • 🌙
  • 💖
  • 💔
😃
  • 😀
  • 😉
  • 😥
  • 😵
  • 😫
  • 😘
  • 😡
  • 👍
  • 🌹
  • 👏
  • 🍺
  • 🍉
  • 🌙
  • 💖
  • 💔
取消回复