Default
22300.54%
Each variation will be presented in the following section.
component
)Include a Counter with the option to attach a unit and a description text before or after the counter
Veams#5.0.0
jquery
- Veams Query or jQuery.veams install bp absolute/filepath/to/counter
counter.hbs
Parameter | Type | Default | Description |
---|---|---|---|
settings.counterClasses | String | '' |
Modifier classes for component |
settings.counterContextClass | String | 'default' |
Context class of component |
Parameter | Type | Description |
---|---|---|
content.counterbox | Object | Object with properties to make settings for the counter |
counter__box.hbs
Parameter | Type | Description |
---|---|---|
counterbox.count-from | Int/Float | the number the element should start at |
counterbox.count-to | Int/Float | the number the element should end at |
counterbox.count-speed | Int | how long it should take to count between the target numbers count-from-number to count-to-number |
counterbox.count-decimals | Int | the number of decimal places to show |
counterbox.count-refreshInterval | Int | how often the element should be updated in the speed-time |
counterbox.count-unit | String | unit of the number (f.e. kg, liter, …) |
The module gives you the possibility to override default options:
Option | Type | Default | Description |
---|---|---|---|
classes.activeClass | String | 'is-inview' |
Define that the counter was shown in View class for counter elements |
classes.activeClass | String | 'is-inactive' |
Define the counter is not in action for counter elements (in use for Print-, Screenreaderversion, ) |
classes.activeClass | String | 'is-active' |
Define the counter is in action for counter elements |
counterOptions | Object | Define a object for the counter with relevant informations | |
selectors.counterWrapper | String | '[data-js-item="counter__js__timer"]' |
Define the counterbox, relevant for starting the animation,… |
selectors.counterNumber | String | '[data-js-item="counter__js__number"]' |
Define the area in which the counting number is animated |
selectors.counterNumberTo | String | '[data-js-item="counter__js__numberto"]' |
Define the area in which the end result (number) is located, relevant for printing and screenreading |
There are multiple variables which you can change:
Variable | Value | Description |
---|
There are modifier classes you can add to counter__timer
:
Modifier | Description |
---|---|
is-inview | For the Counter in Viewport |
is-inactive | For the inactive Counter |
is-active | For the active animated Counter |
<div class="c-counter{{#if settings.counterContextClass}}--{{settings.counterContextClass}}{{else}}--default{{/if}}{{#if settings.counterClasses}} {{settings.counterClasses}}{{/if}}"
data-css="c-counter" data-js-module="counter"{{#if
settings.counterJsOptions}}
data-js-options='{{stringify settings.counterJsOptions}}'{{/if}}>
{{#if (isObject content.counterbox)}}
{{> counter__box content.counterbox}}
{{/if}}
</div>
{{#if count-to}}<div class="counter__timer is-inactive" data-js-item="counter__js__timer" {{#if count-lng}}data-js-lng="{{count-lng}}"{{else}}data-js-lng="de"{{/if}} data-to="{{count-to}}"{{#if count-from}} data-from="{{count-from}}"{{/if}}{{#if count-speed}} data-speed="{{count-speed}}"{{/if}}{{#if count-refreshInterval}} data-refreshInterval="{{count-refreshInterval}}"{{/if}}{{#if count-decimals}} data-decimals="{{count-decimals}}"{{/if}}><span class="counter__number" data-js-item="counter__js__number" aria-hidden="true">{{count-to}}</span><span class="counter__numberto" data-js-item="counter__js__numberto">{{count-to}}</span>{{#if count-unit}}<span class="counter__unit">{{count-unit}}</span>{{/if}}</div>{{/if}}
{
"title": "Counter",
"variations": {
"default": {
"docs": {
"variationName": "Default"
},
"settings": {
"counterContextClass": "default",
"counterClasses": ""
},
"content": {
"counterbox": {
"count-from": "100",
"count-to": "22300.54",
"count-speed": "2500",
"count-decimals": "2",
"count-unit": "%",
"count-lng": "en"
}
}
}
}
}
/* ===================================================
component: counter
=================================================== */
/* ---------------------------------------------------
Global Styles
--------------------------------------------------- */
[data-css="c-counter"] {
.counter__timer {
color: $color-green;
font-family: $font-bold;
font-size: 7rem;
line-height: (82/70);
letter-spacing: 0;
display: flex;
justify-content: center;
align-items: center;
@include bp($bp-tablet-p) {
font-size: 9.5rem;
line-height: (100/95);
letter-spacing: .03rem;
}
@include bp($bp-tablet-l) {
font-size: 7rem;
line-height: (82/70);
letter-spacing: 0;
}
@include bp($bp-desktop-l) {
font-size: 9.5rem;
line-height: (100/95);
letter-spacing: .03rem;
}
&.is-inactive {
.counter__number {
display: none;
}
.counter__numberto {
display: block;
}
}
&.is-active {
.counter__number {
display: block;
@include print() {
display: none;
}
}
.counter__numberto {
@include hidden-text();
@include print() {
color: $color-black;
font-family: $font-bold;
font-size: 7rem;
line-height: (82/70);
letter-spacing: 0;
}
}
}
}
}
/* ---------------------------------------------------
Context: Default
--------------------------------------------------- */
.c-counter--default {
width: 100%;
font-family: $font-regular;
padding: 5% 0;
position: relative;
}
/**
* Description of Counter.
* Counter with start and end number (Int and float) and the opportunity to append a unit
* Animate the counter by in Viewport
*
* Class properties and decorators are supported.
*
* @module Counter
* @version v1.0.0
*
* @author Ralf Arnert
*/
// Imports
import $ from 'jquery';
import Component from '@veams/component';
import isInViewport from '@veams/helpers/lib/detection/is-in-viewport';
class Counter extends Component {
/**
* Class Properties
*/
$el = $(this.el);
$counterWrapper = $(this.options.selectors.counterWrapper, this.el);
$counterNumber = $(this.options.selectors.counterNumber, this.el);
$counterNumberTo = $(this.options.selectors.counterNumberTo, this.el);
/**
* Constructor for our class
*
* @see module.js
*
* @param {Object} obj - Object which is passed to our class
* @param {Object} obj.el - element which will be saved in this.el
* @param {Object} obj.options - options which will be passed in as JSON object
*/
constructor(obj) {
let options = {
selectors: {
counterWrapper: '[data-js-item="counter__js__timer"]',
counterNumber: '[data-js-item="counter__js__number"]',
counterNumberTo: '[data-js-item="counter__js__numberto"]'
},
classes: {
inview: 'is-inview',
inactive: 'is-inactive',
active: 'is-active'
},
counterOptions: {
from: 0, // the number the element should start at
to: 0, // the number the element should end at
toFormated: 0, // the number the element should end at in special format
speed: 800, // how long it should take to count between the target numbers
refreshInterval: 100, // how often the element should be updated
decimals: 0, // decimals to show
countnr: 0, //the active count number
intervalOne: '', // contain the Interval to clear it
countlng: 'de'
}
};
super(obj, options);
}
/** =================================================
* EVENTS
* ================================================ */
// Event Handlers
get events() {
return {
'{{context.EVENTS.mouseover}} {{this.options.selectors.counterWrapper}}':
'wrapperMouseOver',
'{{context.EVENTS.touchstart}} {{this.options.selectors.counterWrapper}}':
'wrapperMouseOver'
};
}
get subscribe() {
return {
'{{context.EVENTS.pagescrollThrottled}}': 'onPageScroll'
};
}
/**
* Adjust height and vertical position of controller depending on components viewport height and vertical map
* position
*
* @param {Object} [e] - Event object
*/
onPageScroll(e) {
this.startCounter(500);
}
/**
* Render class
*/
render() {
let cdec = this.options.counterOptions.decimals;
if (
this.$counterWrapper.attr('data-decimals') != null &&
!isNaN(parseFloat(this.$counterWrapper.attr('data-decimals')))
) {
this.options.counterOptions.decimals = parseFloat(
this.$counterWrapper.attr('data-decimals')
);
cdec = this.options.counterOptions.decimals;
}
if (
this.$counterWrapper.attr('data-from') != null &&
!isNaN(parseFloat(this.$counterWrapper.attr('data-from')))
) {
this.options.counterOptions.from = parseFloat(
this.$counterWrapper.attr('data-from')
).toFixed(cdec);
}
this.options.counterOptions.countnr = this.options.counterOptions.from;
if (
this.$counterWrapper.attr('data-to') != null &&
!isNaN(parseFloat(this.$counterWrapper.attr('data-to')))
) {
this.options.counterOptions.to = parseFloat(
this.$counterWrapper.attr('data-to')
).toFixed(cdec);
}
if (
this.$counterWrapper.attr('data-speed') != null &&
!isNaN(parseFloat(this.$counterWrapper.attr('data-speed')))
) {
this.options.counterOptions.speed = parseFloat(this.$counterWrapper.attr('data-speed'));
}
if (
this.$counterWrapper.attr('data-refreshInterval') != null &&
!isNaN(parseFloat(this.$counterWrapper.attr('data-refreshInterval')))
) {
this.options.counterOptions.refreshInterval = parseFloat(
this.$counterWrapper.attr('data-refreshInterval')
);
}
if (
this.$counterWrapper.attr('data-js-lng') != null
) {
this.options.counterOptions.countlng = this.$counterWrapper.attr('data-js-lng');
}
// how many times to update the value, and how much to increment the value on each update
this.options.counterOptions.loops = Math.ceil(
this.options.counterOptions.speed / this.options.counterOptions.refreshInterval
);
this.options.counterOptions.increment =
(this.options.counterOptions.to - this.options.counterOptions.from) /
this.options.counterOptions.loops;
this.options.counterOptions.loopCount = 0;
this.options.counterOptions.toFormated = this.formatter(this.options.counterOptions.to);
this.$counterNumberTo.html(this.options.counterOptions.toFormated);
this.$counterNumber.html(this.options.counterOptions.toFormated);
this.startCounter(1500);
return this;
}
/** =================================================
* CUSTOM COUNTER METHODS
* ================================================= */
/**
* Start Counter depending on viewport and a class to prevent a second start
*
* @param {number} timeout - Milliseconds waiting to start the function
*
*/
startCounter(timeout) {
setTimeout(() => {
if (
isInViewport(this.el) &&
!this.$counterWrapper.hasClass(this.options.classes.inview)
) {
this.$counterWrapper.addClass(this.options.classes.inview);
this.updateTimer();
}
}, timeout);
}
/**
* Count with the settings
*/
countTo() {
// references & variables that will change with each update
let value = this.options.counterOptions.countnr;
this.options.counterOptions.countnr =
parseFloat(this.options.counterOptions.countnr) +
parseFloat(this.options.counterOptions.increment);
if (
parseFloat(this.options.counterOptions.countnr) >=
parseFloat(this.options.counterOptions.to)
) {
value = this.options.counterOptions.to;
this.counterRender(value);
} else {
this.counterRender(value);
}
}
/**
* Update the Count after a time defined by JSON or default
*/
updateTimer() {
this.setActive();
this.options.counterOptions.intervalOne = setInterval(() => {
if (
parseFloat(this.options.counterOptions.countnr) >=
parseFloat(this.options.counterOptions.to)
) {
clearInterval(this.options.counterOptions.intervalOne);
this.setInactive();
} else {
this.countTo();
}
}, this.options.counterOptions.loops);
}
/**
* Format the Number to show for the counting
*
* @param {string} value - Nummer
*
* @return {string}
*/
formatter(value) {
if (this.options.counterOptions.countlng == "de") {
return parseFloat(value)
.toFixed(this.options.counterOptions.decimals)
.replace(/\B(?=(?:\d{3})+(?!\d))/g, '.');
} else {
return parseFloat(value)
.toFixed(this.options.counterOptions.decimals)
.replace(/\B(?=(?:\d{3})+(?!\d))/g, ',');
}
}
/**
* Write the formated number in the element
*
* @param {string} value - Nummer
*
*/
counterRender(value) {
let formattedValue = this.formatter(value);
this.$counterNumber.html(formattedValue);
}
/**
* Event handler for mouse over on counter
*
* @param {Object} [e] - Event object
*
*/
wrapperMouseOver(e) {
if (!this.$counterWrapper.hasClass(this.options.classes.inactive)) {
clearInterval(this.options.counterOptions.intervalOne);
this.options.counterOptions.countnr = this.options.counterOptions.to;
this.countTo();
this.setInactive();
}
}
/**
* set a counter active by removing a class in-active
*/
setActive() {
if (this.$counterWrapper.hasClass(this.options.classes.inactive)) {
this.$counterWrapper.removeClass(this.options.classes.inactive);
this.$counterWrapper.addClass(this.options.classes.active);
}
}
/**
* set a counter inactive by adding a class in-active
*/
setInactive() {
if (this.$counterWrapper.hasClass(this.options.classes.active)) {
this.$counterWrapper.removeClass(this.options.classes.active);
this.$counterWrapper.addClass(this.options.classes.inactive);
}
}
}
export default Counter;
<div class="c-counter--default" data-css="c-counter" data-js-module="counter">
<div class="counter__timer is-inactive" data-js-item="counter__js__timer" data-js-lng="en" data-to="22300.54" data-from="100" data-speed="2500" data-decimals="2"><span class="counter__number" data-js-item="counter__js__number" aria-hidden="true">22300.54</span><span class="counter__numberto" data-js-item="counter__js__numberto">22300.54</span><span class="counter__unit">%</span></div>
</div>