Рубрики
Без рубрики

Как построить адаптивный и динамический прогресс-бар с помощью HTML, CSS и JavaScript

Пару лет назад я написал небольшую статью о создании отзывчивого прогресс-бара. С тех пор мои методы усовершенствовались, поэтому необходимо сделать обновление. Самое большое изменение заключается в том, что псевдоэлементы (before, after) больше не нужны.

Автор оригинала: Michael Xavier.

Пару лет назад я написал небольшую статью о создании отзывчивого прогресс-бара. С тех пор мои методы усовершенствовались, поэтому необходимо обновить статью.

Самое большое изменение заключается в том, что псевдоэлементы (before, after) больше не нужны. Теперь CSS более прост, DOM легче читать, и он гораздо более динамичен.

Итак, давайте попробуем еще раз.

Наша цель – создать простой и эффективный отзывчивый прогресс-бар, который делает следующее:

  • Имеет четыре шага до завершения.
  • Каждый шаг имеет состояние по умолчанию, активное и завершенное.
  • Можно переходить от этапа к этапу до завершения.


Посмотрите живой пример на CodePen здесь.

HTML

Чтобы уменьшить избыточность и увеличить возможность повторного использования, мы отслеживаем все состояния в компоненте Vue. В DOM он динамически генерирует любое количество необходимых шагов.

Примечание: Этого можно добиться с помощью нативного JavaScript (ECMAScript) или любого другого фреймворка. Использование Vue приведено в демонстрационных целях.

Прогресс-бар использует базовую разметку. В нем есть:

  • контейнер с вычисляемыми классами, основанными на текущем шаге: progressClasses
  • статическая фоновая дорожка: progress__bg
  • цикл, который итерирует каждый шаг и применяет классы stepClasses на основе текущего шага.


Каждый шаг имеет:

  • progress__indicator, содержащий значок галочки, который виден, если шаг завершен.
  • метку progress__label, содержащую текст метки для этого шага.
<div
  id="app"
  :class="progressClasses"
>
  <div class="progress__bg"></div>
  
  <template v-for="(step, index) in steps">
    <div :class="stepClasses(index)">
      <div class="progress__indicator">
        <i class="fa fa-check"></i>
      </div>
      <div class="progress__label">
        {{step.label}}
      </div>
    </div>
  </template>
  
  <div class="progress__actions">
    <div
      class="btn"
      v-on:click="nextStep(false)"
    >
      Back
    </div>
    <div
      class="btn"
      v-on:click="nextStep"
    >
      Next
    </div>
    <div>
      Step:
      {{currentStep ? currentStep.label : "Start"}}
    </div>
  </div>
</div>

Для простоты progress__actions, управляющие направлением движения, вложены в саму полосу прогресса.

CSS (SCSS)

Здесь мы выполняем тяжелую работу. Определенные здесь классы будут динамически применяться JS в зависимости от текущего шага.

Во-первых, давайте выберем несколько цветов для работы:

$gray:  #E5E5E5;
$gray2: #808080;
$blue:  #2183DD;
$green: #009900;
$white: #FFFFFF;

Теперь определите класс .progress: контейнер, в котором хранится содержимое индикатора выполнения.

.progress {
  position: absolute;
  top: 15vh;
  width: 0%;
  height: 10px;
  background-color: $blue;
  transition: width .2s;
}

Нашему индикатору прогресса нужен .progress__bg, по которому будут бежать шаги прогресса, как по дорожке. Он будет серым и будет покрываться цветной полосой по мере продвижения к следующему шагу.

.progress__bg {
  position: absolute;
  width: 100vw;
  height: 10px;
  background-color: $gray;
  z-index: -1;
}

Каждый .progress__step содержит круглый шаг, который будет выделяться и заполняться по мере продвижения индикатора прогресса.

.progress__step {
  position: absolute;
  top: -8px;
  left: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  
  @for $i from 1 through 5 {
    &.progress__step--#{$i} {
      left: calc(#{$i * 20}vw - 9px);
    }
  }
}

Он также содержит круглый .progress__indicator и текст метки .progress__label. Их стили по умолчанию определены вне .progress__step.

.progress__indicator {
  width: 25px;
  height: 25px;
  border: 2px solid $gray2;
  border-radius: 50%;
  background-color: $white;
  margin-bottom: 10px;
  
  .fa {
    display: none;
    font-size: 16px;
    color: $white;
  }
}

.progress__label {
  position: absolute;
  top: 40px;
}

Теперь снова продолжим вложение внутри .progress__step и определим шаг в его активном состоянии.

&.progress__step--active {
  color: $blue;
  font-weight: 600;
}

Затем определите шаг в его завершенном состоянии. Примечание: стили по умолчанию для .progress__indicator и .progress__label переписываются, когда шаг находится в завершенном состоянии.

&.progress__step--complete {
  .progress__indicator {
    background-color: $green;
    border-color: $blue;
    color: $white;
    display: flex;
    align-items: center;
    justify-content: center;
  }
    
  .progress__indicator .fa {
    display: block;
  }
  
  .progress__label {
    font-weight: 600;
    color: $green;
  }
}

JavaScript

Как упоминалось ранее, все зависит от того, как вы реализуете логику шага, в каком контексте она реализуется, какие фреймворки и паттерны вы используете и т.д.

В данном примере для демонстрации используется компонент Vue:

  • расчет классов для индикатора выполнения на основе текущего состояния.
  • расчет классов для каждого шага на основе текущего состояния.
var app = new Vue({
  el: '#app',
  
  data: {
    currentStep: null,
    steps: [
      {"label": "one"},
      {"label": "two"},
      {"label": "three"},
      {"label": "complete"}
    ]
  },
  
  methods: {
    nextStep(next=true) {
      const steps = this.steps
      const currentStep = this.currentStep
      const currentIndex = steps.indexOf(currentStep)
      
      // handle back
      if (!next) {
        if (currentStep && currentStep.label === 'complete') {
          return this.currentStep = steps[steps.length - 1]           
        }

        if (steps[currentIndex - 1]) {
          return this.currentStep = steps[currentIndex - 1] 
        }

        return this.currentStep = { "label": "start" }   
      }
      
      // handle next
      if (this.currentStep && this.currentStep.label === 'complete') {
        return this.currentStep = { "label": "start" }
      }
      
      if (steps[currentIndex + 1]) {
        return this.currentStep = steps[currentIndex + 1]
      }

      this.currentStep = { "label": "complete" }   
    },
    
    stepClasses(index) {
      let result = `progress__step progress__step--${index + 1} `
      if (this.currentStep && this.currentStep.label === 'complete' ||
          index < this.steps.indexOf(this.currentStep)) {
        return result += 'progress__step--complete'
      }
      if (index === this.steps.indexOf(this.currentStep)) {
        return result += 'progress__step--active'
      }
      return result
    }
  },
  
  computed: {
     progressClasses() {
      let result = 'progress '
      if (this.currentStep && this.currentStep.label === 'complete') {
        return result += 'progress--complete'
      }
      return result += `progress--${this.steps.indexOf(this.currentStep) + 1}`
    }
  }
})

Заключение

В конце всего этого у вас есть вот это:

Посмотрите живой пример на CodePen.

Оригинал: “https://www.freecodecamp.org/news/how-to-build-a-responsive-and-dynamic-progress-bar/”