<div class="a-progress">
<small class="a-progress__max"> CHF</small>
<div class="a-progress__container">
<div class="a-progress__bar" role="progressbar" style="width: 28%" aria-valuenow="14000" aria-valuemin="1" aria-valuemax="50000">
<span class="a-progress__current a-progress__current--right">
<span class="a-progress__value">14000</span> CHF
</span>
</div>
</div>
</div>
<div class="a-progress">
{{#if legend}}
<p class="a-progress__legend"><strong><span class="a-progress__legend-value">{{current}}</span> haben unterschrieben.</strong> Erreichen wir {{goal}}?</p>
{{else}}
<small class="a-progress__max">{{max}}{{#if unit}} {{unit}}{{/if}}</small>
{{/if}}
<div class="a-progress__container">
<div
class="a-progress__bar"
role="progressbar"
style="width: {{current_percent}}%"
aria-valuenow="{{current}}"
aria-valuemin="{{min}}"
aria-valuemax="{{goal}}"
>
<span class="a-progress__current{{#if low_counter}} a-progress__current--right{{/if}}">
<span class="a-progress__value">{{ current }}</span>{{#if unit}} {{unit}}{{/if}}
</span>
</div>
</div>
</div>
.a-progress {
margin: 1em 0;
width: 100%;
&__container {
width: 100%;
height: 2.5rem;
background: $color-grey-1;
overflow: hidden;
}
&__bar {
background: $color-primary;
height: 100%;
position: relative;
transition: opacity $transition-mid $transition-ease;
@include high-contrast {
background: $color-primary-dark;
}
&::after {
content: ' ';
display: block;
background-color: white;
width: 1px;
height: 110%;
transform: rotateZ(8deg);
opacity: 0.1;
box-shadow: 0 0 1em 0.75em white;
position: absolute;
animation-iteration-count: infinite;
animation-timing-function: $transition-ease;
animation-duration: 2.5s;
animation-name: flash;
@keyframes flash {
from {
left: -1000%;
}
75% {
left: 0
}
to {
left: 100%
}
}
}
}
&__max {
display: block;
width: 100%;
text-align: right;
padding-bottom: 0.125em;
color: $color-text-light;
font-size: 0.875rem;
}
&__current {
position: absolute;
right: 0.5em;
top: 50%;
transform: translateY(-50%);
color: $color-white;
font-weight: $font-weight-bold;
font-size: 1.4rem;
transition: opacity $transition-mid $transition-ease;
}
&__current--right {
right: -0.5em;
transform: translateY(-50%) translateX(100%);
color: $color-text-light;
@include high-contrast {
color: $color-text;
}
}
&__legend {
line-height: 1.4em;
margin: 0 0 0.25em;
transition: opacity $transition-mid $transition-ease;
}
}
.a-progress--loading {
.a-progress {
&__legend {
opacity: 0;
}
&__current {
opacity: 0;
}
&__bar {
opacity: 0;
}
}
}
import BaseView from 'base-view';
import inView from '../../../js/service/inview';
import ajax from '../../../js/service/ajax';
const BAR_SELECTOR = '.a-progress__bar';
const VALUE_SELECTOR = '.a-progress__value';
const LEGEND_SELECTOR = '.a-progress__legend';
const LEGEND_VALUE_SELECTOR = '.a-progress__legend-value';
const LOADING_CLASS = 'a-progress--loading';
const DEBOUNCE_DELAY_MS = 300;
const STEPS = 200;
const STEP_DELAY = 25; // ms
// sync with m-form.js
const SUBMISSION_NOTIFICATION_EVENT = 'supt_form_submission';
export default class AProgress extends BaseView {
bind() {
super.bind();
this.firstRun = true;
this.bar = this.getScopedElement( BAR_SELECTOR );
this.listenForSubmissions();
this.updateData().finally( () => {
inView( this.element, DEBOUNCE_DELAY_MS ).
then( () => this.startAnimation() );
} );
}
updateData() {
if (!( 'url' in this.bar.dataset )) {
return new Promise( resolve => resolve() );
}
const url = this.bar.dataset.url;
return ajax( url, 'GET' ).then( ( resp ) => {
if (resp instanceof Object
&& 'current' in resp
&& 'goal' in resp
&& 'legend' in resp
) {
this.bar.setAttribute( 'aria-valuenow', resp.current );
this.bar.setAttribute( 'aria-valuemax', resp.goal );
this.getScopedElement( LEGEND_SELECTOR ).innerHTML = resp.legend;
}
} );
}
startAnimation() {
this.value = this.getScopedElement( VALUE_SELECTOR );
this.legend_value = this.getScopedElement( LEGEND_VALUE_SELECTOR );
this.min = this.bar.getAttribute( 'aria-valuemin' );
this.max = this.bar.getAttribute( 'aria-valuemax' );
this.current = this.bar.getAttribute( 'aria-valuenow' );
if (this.firstRun) {
this.step_count = 0;
this.state = 0;
this.state_percent = 0;
}
this.sumOfSteps = 1 / 2 * STEPS * ( STEPS + 1 );
this.spread = this.current - this.min;
this.timer = setInterval( this.animate.bind( this ), STEP_DELAY );
}
addSubmission() {
this.updateData().then( this.startAnimation.bind( this ) );
}
animate() {
if (this.firstRun) {
this.removeClass( this.element, LOADING_CLASS );
this.firstRun = false;
}
if (this.step_count > STEPS) {
clearInterval( this.timer );
// we need this extra step to circumvent float calculation errors
const current = ( this.spread / ( this.max - this.min ) );
this.bar.style.width = current * 100 + '%';
this.setLabelValue( this.current );
}
else {
this.step_count ++;
const easing = ( STEPS - this.step_count ) / this.sumOfSteps;
const step_size = this.spread * easing;
const step_percent = step_size / ( this.max - this.min );
this.state += step_size;
this.state_percent += step_percent;
this.bar.style.width = this.state_percent * 100 + '%';
this.setLabelValue( Math.round( this.state ) );
}
}
setLabelValue( value ) {
if (this.legend_value) {
this.legend_value.innerText = value;
}
this.value.innerText = value;
}
listenForSubmissions() {
document.addEventListener( SUBMISSION_NOTIFICATION_EVENT, event => {
if (!( 'form' in this.bar.dataset )) {
return;
}
const progressFormId = parseInt( this.bar.dataset.form );
const eventFormId = parseInt( event.detail.formId );
if (progressFormId === eventFormId) {
this.addSubmission();
}
} );
}
destroy() {
window.removeEventListener( 'scroll', this.eventHandler );
window.removeEventListener( 'resize', this.eventHandler );
super.destroy();
}
}
{
"min": 1,
"goal": 50000,
"current": 14000,
"current_percent": 28,
"unit": "CHF",
"low_counter": true
}
No notes defined.