1/** 2 * UpSolution Element: Counter 3 */ 4! function( $, _undefined ) { 5 6 /** 7 * Counter Number part animations. 8 * 9 * @param container 10 * @constructor 11 */ 12 function usCounterNumber( container ) { 13 const self = this; 14 15 self.$container = $( container ); 16 self.initialString = $ush.toString( self.$container.html() ); 17 self.finalString = $ush.toString( self.$container.data( 'final' ) ); 18 self.format = self.getFormat( self.initialString, self.finalString ); 19 self.isNoAnimation = self.$container.closest( '.w-counter.animation_none' ).length; 20 if ( self.format.decMark ) { 21 const pattern = new RegExp( '[^0-9\/' + self.format.decMark + ']+', 'g' ); 22 self.initial = parseFloat( self.initialString.replace( pattern, '' ).replace( self.format.decMark, '.' ) ); 23 self.final = parseFloat( self.finalString.replace( pattern, '' ).replace( self.format.decMark, '.' ) ); 24 } else { 25 self.initial = parseInt( self.initialString.replace( /[^0-9]+/g, '' ) ); 26 self.final = parseInt( self.finalString.replace( /[^0-9]+/g, '' ) ); 27 } 28 if ( self.format.accounting ) { 29 if ( self.initialString.length > 0 && self.initialString[0] == '(' ) { 30 self.initial = -self.initial; 31 } 32 if ( self.finalString.length > 0 && self.finalString[0] == '(' ) { 33 self.final = -self.final; 34 } 35 } 36 37 if ( ! self.isNoAnimation ) { 38 self.initSlideAnimation(); 39 } 40 } 41 42 /** 43 * Export API 44 */ 45 usCounterNumber.prototype = { 46 47 /** 48 * Function to be called at each animation frame. 49 * @param now float Relative state between 0 and 1 50 */ 51 step: function( now ) { 52 const self = this; 53 var value = ( 1 - now ) * self.initial + self.final * now, 54 intPart = Math[ self.format.decMark ? 'floor' : 'round' ]( value ).toString(), 55 result = ''; 56 if ( self.format.zerofill ) { 57 // Check how many zeros we need to add to this step value 58 var amountOfZeros = ( self.format.intDigits - intPart.length ); 59 if ( amountOfZeros > 0 ) { 60 intPart = '0'.repeat( amountOfZeros ) + intPart; 61 } 62 } 63 if ( self.format.groupMark ) { 64 if ( self.format.indian ) { 65 result += intPart.replace( /(\d)(?=(\d\d)+\d$)/g, '$1' + self.format.groupMark ); 66 } else { 67 result += intPart.replace( /\B(?=(\d{3})+(?!\d))/g, self.format.groupMark ); 68 } 69 } else { 70 result += intPart; 71 } 72 if ( self.format.decMark ) { 73 var decimalPart = ( value % 1 ).toFixed( self.format.decDigits ).substring(2); 74 result += self.format.decMark + decimalPart; 75 } 76 if ( self.format.accounting && result.length > 0 && result[0] === '-' ) { 77 result = '(' + result.substring( 1 ) + ')'; 78 } 79 80 // No Animation 81 if ( self.isNoAnimation ) { 82 self.$container.html( result ); 83 } else { // Slide Animation 84 const digitsOnly = result.replace( /\D/g, '' ); 85 86 // Start animate from the last digit 87 var digitIndex = digitsOnly.length - 1; 88 89 for ( var slotIndex = self.slots.length - 1; slotIndex >= 0; slotIndex-- ) { 90 91 const slot = self.slots[ slotIndex ]; 92 93 if ( slot.type !== 'digit' ) { 94 continue; 95 } 96 97 const num = $ush.parseInt( digitsOnly[ digitIndex ] ); 98 const offset = num * slot.el.find( 'li' ).innerHeight(); 99 100 slot.el.find( 'ul' ) 101 .css( 'transform', 'translateY(-' + offset + 'px)' ); 102 103 // Hide non-active digits after animation (fonts with different num heights case) 104 $ush.timeout( () => { 105 slot.el.find( 'li:nth-child(' + num + ')' ).addClass( 'pre-active' ); 106 }, ( self.$container.closest( '.w-counter' ).data( 'duration' ) || 2 ) * 1000 ); 107 108 digitIndex--; 109 } 110 } 111 }, 112 113 /** 114 * Get number format based on initial and final number strings. 115 * @param initial string 116 * @param final string 117 * @returns {{groupMark, decMark, accounting, zerofill, indian}} 118 */ 119 getFormat: function( initial, final ) { 120 const self = this; 121 var iFormat = self._getFormat( initial ), 122 fFormat = self._getFormat( final ), 123 // Final format has more priority 124 format = $.extend( {}, iFormat, fFormat ); 125 // Group marks detector is more precise, so using it in controversial cases 126 if ( format.groupMark == format.decMark ) { 127 delete format.groupMark; 128 } 129 return format; 130 }, 131 132 /** 133 * Get number format based on a single number string. 134 * @param str string 135 * @returns {{groupMark, decMark, accounting, zerofill, indian}} 136 */ 137 _getFormat: function( str ) { 138 var marks = str.replace( /[0-9\(\)\-]+/g, '' ), 139 format = {}; 140 if ( str.charAt(0) == '(' ) { 141 format.accounting = true; 142 } 143 if ( /^0[0-9]/.test( str ) ) { 144 format.zerofill = true; 145 } 146 str = str.replace( /[\(\)\-]/g, '' ); 147 if ( marks.length != 0 ) { 148 if ( marks.length > 1 ) { 149 format.groupMark = marks.charAt(0);
150 if ( marks.charAt(0) != marks.charAt( marks.length - 1 ) ) { 151 format.decMark = marks.charAt( marks.length - 1 ); 152 } 153 if ( str.split( format.groupMark ).length > 2 && str.split( format.groupMark )[1].length == 2 ) { 154 format.indian = true; 155 } 156 } else/*if (marks.length == 1)*/ { 157 format[ ( ( ( str.length - 1 ) - str.indexOf( marks ) ) == 3 && marks !== '.' ) ? 'groupMark' : 'decMark' ] = marks; 158 } 159 if ( format.decMark ) { 160 format.decDigits = str.length - str.indexOf( format.decMark ) - 1; 161 } 162 } 163 if ( format.zerofill ) { 164 format.intDigits = str.replace( /[^\d]+/g, '' ).length - ( format.decDigits || 0 ); 165 } 166 167 return format; 168 }, 169 170 /** 171 * Create slots(digits and chars) for each character in final number string 172 * Fill DOM with slots 173 * Needed for slide animation 174 */ 175 initSlideAnimation: function() { 176 const self = this; 177 178 self.$container.empty(); 179 self.slots = []; // Numbers and chars 180 181 const chars = self.finalString.split( '' ); 182 183 chars.forEach( char => { 184 if ( /\d/.test( char ) ) { // Digits 185 const $mainDigit = $( '<span class="w-counter-value-digit"></span>' ); 186 const $digits = $( '<div class="w-counter-digits"></div>' ); 187 188 $digits.append( '<ul></ul>' ); 189 190 for ( let i = 0; i < 10; i++ ) { 191 $( 'ul', $digits ).append( '<li>' + i + '</li>' ); 192 } 193 194 $mainDigit 195 .append( '<span>' + char + '</span>' ) 196 .append( $digits ); 197 198 self.$container.append( $mainDigit ); 199 200 self.slots.push( { type: 'digit', el: $digits } ); 201 202 } else { // Chars like '.' or ',' 203 const $char = $( '<span class="w-counter-value-delimeter">' + char + '</span>' ); 204 self.$container.append( $char ); 205 206 self.slots.push( { type: 'char', el: $char } ); 207 } 208 } ); 209 } 210 }; 211 212 /** 213 * Counter Number part animations. 214 * 215 * @param container 216 * @constructor 217 */ 218 function usCounterText( container ) { 219 const self = this; 220 self.$container = $( container ); 221 self.initial = $ush.toString( self.$container.text() ); 222 self.final = $ush.toString( self.$container.data( 'final' ) ); 223 self.partsStates = self.getStates( self.initial, self.final ); 224 self.len = 1 / ( self.partsStates.length - 1 ); 225 // The text value wonât change on every frame, so weâll update it only when needed. 226 self.curState = 0; 227 } 228 /** 229 * Export API 230 */ 231 usCounterText.prototype = { 232 233 /** 234 * Function to be called at each animation frame. 235 * 236 * @param now float Relative state between 0 and 1 237 */ 238 step: function( now ) { 239 const self = this; 240 const state = Math.round( Math.max( 0, now / self.len ) ); 241 if ( state == self.curState ) { 242 return; 243 } 244 self.$container.html( self.partsStates[ state ] ); 245 self.curState = state; 246 }, 247 248 /** 249 * Slightly modified Wagner-Fischer algorithm to obtain the shortest edit distance with intermediate states. 250 * 251 * @param initial string The initial string 252 * @param final string The final string 253 * @returns {[]} 254 */ 255 getStates: function( initial, final ) { 256 var min = Math.min, 257 dist = [], 258 i, j; 259 for ( i = 0; i <= initial.length; i ++ ) { 260 dist[ i ] = [ i ]; 261 } 262 for ( j = 1; j <= final.length; j ++ ) { 263 dist[ 0 ][ j ] = j; 264 for ( i = 1; i <= initial.length; i ++ ) { 265 dist[ i ][ j ] = ( initial[ i - 1 ] === final[ j - 1 ] ) ? dist[ i - 1 ][ j - 1 ] : ( Math.min( dist[ i - 1 ][ j ], dist[ i ][ j - 1 ], dist[ i - 1 ][ j - 1 ] ) + 1 ); 266 } 267 } 268 // Obtaining the intermediate states 269 var states = [ final ]; 270 for ( i = initial.length, j = final.length; i > 0 || j > 0; i --, j -- ) { 271 var min = dist[ i ][ j ]; 272 if ( i > 0 ) { 273 min = Math.min( min, dist[ i - 1 ][ j ], ( j > 0 ) ? dist[ i - 1 ][ j - 1 ] : min ); 274 } 275 if ( j > 0 ) { 276 min = Math.min( min, dist[ i ][ j - 1 ] ); 277 } 278 if ( min >= dist[ i ][ j ] ) { 279 continue; 280 } 281 if ( min == dist[ i ][ j - 1 ] ) { 282 // Remove 283 states.unshift( states[0].substring( 0, j - 1 ) + states[0].substring( j ) ); 284 i ++; 285 } else if ( min == dist[ i - 1 ][ j - 1 ] ) { 286 // Modify 287 states.unshift( states[0].substring( 0, j - 1 ) + initial[ i - 1 ] + states[0].substring( j ) ); 288 } else if ( min == dist[ i - 1 ][ j ] ) { 289 // Insert 290 states.unshift( states[0].substring( 0, j ) + initial[ i - 1 ] + states[0].substring( j ) ); 291 j ++; 292 } 293 } 294 return states; 295 } 296 }; 297 298 /** 299 * @param container 300 * @constructor 301 */ 302 function usCounter( container ) { 303 const self = this; 304 305 // Commonly used DOM elements 306 self.$container = $( container ); 307 self.parts = []; 308 self.duration = parseFloat( self.$container.data( 'duration' ) || 2 ) * 1000; 309 310 // Hide final values on init 311 $( '.w-counter-value-part', self.$container ).each( ( _, part ) => { 312 const $part = $( part ); 313 if ( $part.hasClass( 'final' ) ) { 314 $part.remove(); 315 } else { 316 $part.removeClass( 'hidden' ); 317 } 318 } ); 319 320 $( '.w-counter-value-part', self.$container ).each( ( _, part ) => { 321 const $part = $( part ); 322 // Skip the ones that won't be changed 323 if ( $ush.toString( $part.html() ) === $ush.toString( $part.data( 'final' ) ) ) { 324 return; 325 } 326 if ( $part.usMod( 'type' ) === 'number' ) { 327 self.parts.push( new usCounterNumber( $part ) ); 328 } else { 329 self.parts.push( new usCounterText( $part ) ); 330 } 331 } ); 332 if ( window.$us !== _undefined && window.$us.scroll !== _undefined ) { 333 // Animate element when it becomes visible
334 $us.waypoints.add( self.$container, /* offset */'15%', self.animate.bind( self ) ); 335 } else { 336 // No waypoints available: animate right from the start 337 self.animate(); 338 } 339 } 340 /** 341 * Export API 342 */ 343 usCounter.prototype = { 344 animate: function() { 345 const self = this; 346 if ( self.$container.hasClass( 'animation_none' ) ) { 347 self.$container.css( 'w-counter', 0 ).animate( { 'w-counter': 1 }, { 348 duration: self.duration, 349 step: self.step.bind( self ) 350 } ); 351 } else { 352 self.step( 1 ); 353 } 354 }, 355 356 /** 357 * Function to be called at each animation frame. 358 * 359 * @param now float Relative state between 0 and 1 360 */ 361 step: function( now ) { 362 const self = this; 363 for ( var i = 0; i < self.parts.length; i++ ) { 364 self.parts[ i ].step( now ); 365 } 366 } 367 }; 368 369 $.fn.usCounter = function( options ) { 370 return this.each( function() { 371 $( this ).data( 'usCounter', new usCounter( this, options ) ); 372 } ); 373 }; 374 375 $( () => $( '.w-counter' ).usCounter() ); 376 377}( jQuery );
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.