在这个特效中,为了制作线条动画,使用了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++) { pat
|