/** * UpSolution Element: Counter */ ! function( $, _undefined ) { /** * Counter Number part animations. * * @param container * @constructor */ function usCounterNumber( container ) { const self = this; self.$container = $( container ); self.initialString = $ush.toString( self.$container.html() ); self.finalString = $ush.toString( self.$container.data( 'final' ) ); self.format = self.getFormat( self.initialString, self.finalString ); self.isNoAnimation = self.$container.closest( '.w-counter.animation_none' ).length; if ( self.format.decMark ) { const pattern = new RegExp( '[^0-9\/' + self.format.decMark + ']+', 'g' ); self.initial = parseFloat( self.initialString.replace( pattern, '' ).replace( self.format.decMark, '.' ) ); self.final = parseFloat( self.finalString.replace( pattern, '' ).replace( self.format.decMark, '.' ) ); } else { self.initial = parseInt( self.initialString.replace( /[^0-9]+/g, '' ) ); self.final = parseInt( self.finalString.replace( /[^0-9]+/g, '' ) ); } if ( self.format.accounting ) { if ( self.initialString.length > 0 && self.initialString[0] == '(' ) { self.initial = -self.initial; } if ( self.finalString.length > 0 && self.finalString[0] == '(' ) { self.final = -self.final; } } if ( ! self.isNoAnimation ) { self.initSlideAnimation(); } } /** * Export API */ usCounterNumber.prototype = { /** * Function to be called at each animation frame. * @param now float Relative state between 0 and 1 */ step: function( now ) { const self = this; var value = ( 1 - now ) * self.initial + self.final * now, intPart = Math[ self.format.decMark ? 'floor' : 'round' ]( value ).toString(), result = ''; if ( self.format.zerofill ) { // Check how many zeros we need to add to this step value var amountOfZeros = ( self.format.intDigits - intPart.length ); if ( amountOfZeros > 0 ) { intPart = '0'.repeat( amountOfZeros ) + intPart; } } if ( self.format.groupMark ) { if ( self.format.indian ) { result += intPart.replace( /(\d)(?=(\d\d)+\d$)/g, '$1' + self.format.groupMark ); } else { result += intPart.replace( /\B(?=(\d{3})+(?!\d))/g, self.format.groupMark ); } } else { result += intPart; } if ( self.format.decMark ) { var decimalPart = ( value % 1 ).toFixed( self.format.decDigits ).substring(2); result += self.format.decMark + decimalPart; } if ( self.format.accounting && result.length > 0 && result[0] === '-' ) { result = '(' + result.substring( 1 ) + ')'; } // No Animation if ( self.isNoAnimation ) { self.$container.html( result ); } else { // Slide Animation const digitsOnly = result.replace( /\D/g, '' ); // Start animate from the last digit var digitIndex = digitsOnly.length - 1; for ( var slotIndex = self.slots.length - 1; slotIndex >= 0; slotIndex-- ) { const slot = self.slots[ slotIndex ]; if ( slot.type !== 'digit' ) { continue; } const num = $ush.parseInt( digitsOnly[ digitIndex ] ); const offset = num * slot.el.find( 'li' ).innerHeight(); slot.el.find( 'ul' ) .css( 'transform', 'translateY(-' + offset + 'px)' ); // Hide non-active digits after animation (fonts with different num heights case) $ush.timeout( () => { slot.el.find( 'li:nth-child(' + num + ')' ).addClass( 'pre-active' ); }, ( self.$container.closest( '.w-counter' ).data( 'duration' ) || 2 ) * 1000 ); digitIndex--; } } }, /** * Get number format based on initial and final number strings. * @param initial string * @param final string * @returns {{groupMark, decMark, accounting, zerofill, indian}} */ getFormat: function( initial, final ) { const self = this; var iFormat = self._getFormat( initial ), fFormat = self._getFormat( final ), // Final format has more priority format = $.extend( {}, iFormat, fFormat ); // Group marks detector is more precise, so using it in controversial cases if ( format.groupMark == format.decMark ) { delete format.groupMark; } return format; }, /** * Get number format based on a single number string. * @param str string * @returns {{groupMark, decMark, accounting, zerofill, indian}} */ _getFormat: function( str ) { var marks = str.replace( /[0-9\(\)\-]+/g, '' ), format = {}; if ( str.charAt(0) == '(' ) { format.accounting = true; } if ( /^0[0-9]/.test( str ) ) { format.zerofill = true; } str = str.replace( /[\(\)\-]/g, '' ); if ( marks.length != 0 ) { if ( marks.length > 1 ) { format.groupMark = marks.charAt(0); if ( marks.charAt(0) != marks.charAt( marks.length - 1 ) ) { format.decMark = marks.charAt( marks.length - 1 ); } if ( str.split( format.groupMark ).length > 2 && str.split( format.groupMark )[1].length == 2 ) { format.indian = true; } } else/*if (marks.length == 1)*/ { format[ ( ( ( str.length - 1 ) - str.indexOf( marks ) ) == 3 && marks !== '.' ) ? 'groupMark' : 'decMark' ] = marks; } if ( format.decMark ) { format.decDigits = str.length - str.indexOf( format.decMark ) - 1; } } if ( format.zerofill ) { format.intDigits = str.replace( /[^\d]+/g, '' ).length - ( format.decDigits || 0 ); } return format; }, /** * Create slots(digits and chars) for each character in final number string * Fill DOM with slots * Needed for slide animation */ initSlideAnimation: function() { const self = this; self.$container.empty(); self.slots = []; // Numbers and chars const chars = self.finalString.split( '' ); chars.forEach( char => { if ( /\d/.test( char ) ) { // Digits const $mainDigit = $( '' ); const $digits = $( '
' ); $digits.append( '