Пишем приложение на Vue.js

Пишем приложение на Vue.js

Простой проект с большими возможностями

У наших друзей в «Кинжале» вышла статья про выгорание и колесо баланса, в которой было много красивых визуализаций сфер жизни. Колесо баланса — это такая техника, когда круг делится на несколько секторов, по одному на каждую сферу жизни: семью, здоровье, деньги и всё такое. Смысл колеса в том, что оно позволяет наглядно увидеть, в каких сферах есть перекос и что нужно чинить первее всего.

В статье все картинки — красивые, но статичные. Сегодня мы используем Vue.js, чтобы их оживить — сделать колесо интерактивным и удобным в использовании. Вот что у нас получится в итоге:

Пишем приложение на Vue.js

Что нам понадобится

Так как мы будем использовать Vue.js, то нам нужно будет прописать логику поведения и разместить элементы на странице, а остальное фреймворк сделает сам. 

Элементы, которые мы будем использовать:

  • Область для рисования, где будет отображаться колесо баланса. Используем для этого SVG-графику — ей легко управлять из скриптов.
  • Раздел с управлением — слайдеры со значениями параметров.
  • Кнопка для удаления элемента — если какой-то параметр не нужен.
  • Форму для добавления новых параметров, в дополнение к стандартным.
  • Блок для вывода всех значений элементов — для управления оно не нужно, но выглядит классно и подключается за минуту. В этом и мощь Vue.js.

В итоге у нас должен получиться интерфейс для управления колесом баланса на веб-странице. Используем стандартный шаблон страницы и добавим в него все эти элементы.

<!DOCTYPE html>
<html lang="ru" >
  <head>
    <meta charset="UTF-8">
    <title>Колесо баланса на Vue.js</title>

  <body>
    <!-- основной блок, внутри которого будет всё остальное -->
    <div id="demo">
      <!-- рисуем многоугольник в области заданного размера -->
      <svg width="250" height="250">
        <polygraph :stats="stats"></polygraph>
      </svg>

      <!-- управление колесом баланса -->
      <div v-for="stat in stats">
        <!-- выводим название элемента -->
        <label>{{stat.label}}</label>
        <!-- показываем шкалу -->
        <input type="range" v-model="stat.value" min="0" max="100" />
        <!-- выводим текущее значение элемента -->
        <span>{{stat.value}}</span>
        <!-- кнопка для удаления элемента -->
        <button @click="remove(stat)" class="remove">❌</button>
      </div>

      <!-- форма для добавления нового элемента на колесо баланса -->
      <form id="add">
        <!-- поле ввода -->
        <input name="newlabel" v-model="newLabel" />
        <!-- кнопка -->
        <button @click="add">Добавить направление</button>
      </form>

      <!-- выводим сбоку все элементы и их значения -->
      <pre id="raw">{{ stats }}</pre>
    </div>
    <!-- подключаем Vue.js -->
    <script src="https://unpkg.com/vue@2"></script>
  </body>
</html>
Пишем приложение на Vue.js
Выглядит странно, потому что внутри пока нет никакой логики

Директивы и декораторы

Обратите внимание в коде на команды, которые начинаются с v- — v-for и v-model. Директивы добавляют особые свойства блокам, в котором они прописаны. 

Например, директива v-for может использоваться для организации цикла — она переберёт все элементы из указанного источника и для каждого сформирует один и тот же набор тегов, но со своими параметрами. Это произойдёт только при загрузке страницы в браузер — до этого момента на странице будет только один элемент, как раз то, что мы видим на скриншоте выше.

Директива v-model двунаправленно связывает элементы между собой. Двунаправленно — значит изменения в одном будут влиять на другой элемент. В нашем случае мы, например, связываем значение слайдера и текстового поля.

Получается, что нам достаточно указать, что мы хотим сделать с HTML-элементами на странице, а Vue.js сделает остальное за нас.

Декораторы — это то, что начинается с символа @, например @click. Задача декоратора — взять существующую функцию, немного её переделать внутри и вернуть результат её работы. Мы используем декоратор @click="remove(stat)", чтобы добавить обработчик события onclick(), привязать к нему функцию remove и передать в неё параметр stat. 

Добавляем шаблоны

Ещё одно преимущество Vue.js — возможность использовать шаблоны. Смысл в том, что если нам нужно определить на странице много однотипных элементов, например подписей на колесе баланса, то можно сделать шаблон и скормить его фреймворку. Vue.js возьмёт шаблон и сгенерирует по нему много HTML-кода, причём каждому элементу добавит свои уникальные свойства.

Используем шаблоны, чтобы описать два повторяющихся элемента на странице: подписи на колесе баланса и углы многоугольника, который лежит внутри колеса. Прямо сейчас они ничего не поменяют, но пригодятся дальше:

<!-- шаблон для каждого элемента колеса баланса -->
<script type="text/x-template" id="polygraph-template">
  <!-- используем этот тег для группировки SVG-элементов -->
  <g>
    <!-- внутри — обычный HTML, где мы задаём параметры многоугольника -->
    <polygon :points="points"></polygon>
    <!-- параметры круга -->
    <circle cx="100" cy="100" r="80"></circle>
    <!-- параметры размещения элементов -->
    <axis-label
      v-for="(stat, index) in stats"
      :stat="stat"
      :index="index"
      :total="stats.length">
    </axis-label>
  </g>
</script>

<!-- шаблон для подписи каждого элемента в колесе баланса -->
<script type="text/x-template" id="axis-label-template">
  <!-- координаты зависят от координат соответствующей точки многоугольника -->
  <text :x="point.x" :y="point.y">{{stat.label}}</text>
</script>

Создаём переменные и компоненты

Нам понадобятся стартовые значения на колесе баланса — насколько по шкале от 0 до 100 мы оцениваем состояние каждой области жизни. Можно всё оставить по нулям и вручную управлять каждым значением, но проще отталкиваться от чего-то уже готового. 

Также нам понадобится один компонент polygraph — мы его сделаем сами и будем использовать в основном приложении Vue.js. Задача этого компонента — отрисовывать содержимое колеса баланса при изменении любого из параметров. 

Компонент состоит из нескольких частей:

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

Чуть выше, когда мы описывали многоугольник в SVG-области, то использовали тег <polygraph>. Это как раз оно — с помощью Vue.js мы создали новый тег и объяснили, как он должен работать.

<script>
  // элементы на старте
  var stats = [
    { label: "Карьера", value: 100 },
    { label: "Саморазвитие", value: 46 },
    { label: "Друзья", value: 100 },
    { label: "Здоровье", value: 90 },
    { label: "Хобби", value: 80 },
    { label: "Деньги", value: 100 },
    { label: "Отдых", value: 40 },
    { label: "Семья", value: 55 }
  ];

  // создаём новый компонент Vie.js для отрисовки многоугольника
  Vue.component("polygraph", {
    props: ["stats"],
    // используем шаблон
    template: "#polygraph-template",
    // раздел с вычисляемыми значениями
    computed: {
      // получаем положение каждой вершины многоугольника
      points: function() {
        var total = this.stats.length;
        return this.stats
          .map(function(stat, i) {
            // пересчитываем значение элемента в круговые координаты
            var point = valueToPoint(stat.value, i, total);
            return point.x + "," + point.y;
          })
          .join(" ");
      }
    },

    // внутренние компоненты
    components: {
      // описываем характеристики и поведение надписей на колесе баланса
      "axis-label": {
        props: {
          stat: Object,
          index: Number,
          total: Number
        },
        // используем свой шаблон
        template: "#axis-label-template",
        computed: {
          point: function() {
            return valueToPoint(
              // смещаем положение надписи на 10 пикселей, чтобы она не прилипала к вершинам многоугольника
              +this.stat.value + 10,
              this.index,
              this.total
            );
          }
        }
      }
    }
  });

  // преобразовываем значение элемента в круговые координаты
  function valueToPoint(value, index, total) {
    var x = 0;
    var y = -value * 0.8;
    // находим угол
    var angle = ((Math.PI * 2) / total) * index;
    var cos = Math.cos(angle);
    var sin = Math.sin(angle);
    // используем тригонометрию и считаем координаты вершины многоугольника
    var tx = x * cos - y * sin + 100;
    var ty = x * sin + y * cos + 100;
    return {
      x: tx,
      y: ty
    };
  }
</script>

Переводим значения в координаты

У нас есть параметры и их значения, а теперь нам нужно научить страницу переводить эти значения в координаты на колесе баланса. Для этого вспомним школьную тригонометрию, синусы и число пи:

// преобразовываем значение элемента в круговые координаты
function valueToPoint(value, index, total) {
  var x = 0;
  var y = -value * 0.8;
  // находим угол
  var angle = ((Math.PI * 2) / total) * index;
  var cos = Math.cos(angle);
  var sin = Math.sin(angle);
  // используем тригонометрию и считаем координаты вершины многоугольника
  var tx = x * cos - y * sin + 100;
  var ty = x * sin + y * cos + 100;
  return {
    x: tx,
    y: ty
  };
}

Создаём новое приложение

Чтобы всё заработало, нужно создать приложение — объект класса Vue — и привязать его к элементу на странице. Сразу добавим в него два метода — add, который добавит новый параметр, и remove, который удалит то, что уже не нужно. 

Прелесть фреймворка в том, что даже в методах мы привязываем их выполнение к определённым тегам на странице — например к кнопкам. Как только мы нажмём на кнопку, сработает метод, который сам выяснит, какая кнопка нажата и как нужно на неё отреагировать. Мы просто задаём логику работы и поведения, не думая о том, как именно Vue.js будет ловить события. 

// создаём новое приложение Vue.js
new Vue({
  // привязываем его к блоку на странице по id
  el: "#demo",
  // стартовые значения приложения
  data: {
    newLabel: "",
    stats: stats
  },
  // объявляем методы — что будет уметь наше приложение
  methods: {
    // добавить новый элемент
    add: function(e) {
      e.preventDefault();
      // если не нажата кнопка «Добавить направление» — выходим из метода
      if (!this.newLabel) return;
      this.stats.push({
        // иначе — добавляем новый элемент и присваиваем ему значение по умолчанию
        label: this.newLabel,
        value: 100
      });
      // очищаем поле ввода
      this.newLabel = "";
    },
    // удалить элемент
    remove: function(stat) {
      // если элементов больше трёх
      if (this.stats.length > 3) {
        // удаляем выбранный
        this.stats.splice(this.stats.indexOf(stat), 1);
      } else {
        // иначе выводим сообщение
        alert("Должно остаться как минимум три направления");
      }
    }
  }
});
Пишем приложение на Vue.js
Выглядит не очень, зато видно, что всё заработало

Добавляем красоту

Последнее, что осталось сделать, — добавить стили, чтобы все элементы стали на свои места, а интерфейс получился красивым:

<!-- стилей мало, поэтому добавим их в этот же файл -->
<style type="text/css">
  body {
    font-family: Helvetica Neue, Arial, sans-serif;
  }

  /* настройки многоугольника в колесе */
  polygon {
    /* цвет заливки и прозрачность */
    fill: #42b983;
    opacity: 0.75;
  }

  /* настройки круга */
  circle {
    /* делаем его прозрачным и добавляем рамку */
    fill: transparent;
    stroke: #999;
  }

  /* настройки текстовых меток в колесе баланса */
  text {
    font-family: Helvetica Neue, Arial, sans-serif;
    font-size: 10px;
    fill: #666;
  }

  /* настроки текста под колесом */
  label {
    display: inline-block;
    margin-bottom: 10px;
    margin-left: 10px;
    width: 120px;
  }

  /* настройки блока с описанием параметров слева от колеса */
  #raw {
    /* задаём абсолютное позиционирование и сдвигаем блок вправо от колеса */
    position: absolute;
    top: 0;
    left: 370px;
  }
</style>
Пишем приложение на Vue.js

Посмотреть колесо баланса на странице проекта.

Текст:

Михаил Полянин

Редактор:

Максим Ильяхов

Художник:

Даня Берковский

Корректор:

Ирина Михеева

Вёрстка:

Кирилл Климентьев

Соцсети:

Алина Грызлова

Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.
Начать карьеру в ИТ
Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию
Еще по теме
Чем занимаются бэкенд-разработчики
Чем занимаются бэкенд-разработчики

Никто не видит, но все пользуются.

easy
Зачем вам jQuery
Зачем вам jQuery

Каждый год говорят о том, что jQuery уже не тот, но продолжают его использовать. Почему? Вот почему.

medium
Можно ли обыграть казино?
Можно ли обыграть казино?

Краткий курс математического ожидания.

medium
Как устроена и зачем нужна двухфакторная аутентификация
Как устроена и зачем нужна двухфакторная аутентификация

Когда нужно ещё что-то кроме пароля.

easy
Как работает пузырьковая сортировка
Как работает пузырьковая сортировка

Самый простой, но не самый эффективный алгоритм.

easy
Как пройти собеседование в ИТ-компанию
Как пройти собеседование в ИТ-компанию

Советы джунам, которые боятся отказа.

easy
Почему в Windows нельзя создать папку или файл с именем Con
Почему в Windows нельзя создать папку или файл с именем Con

Всё дело в обратной совместимости

easy
Все сидят на Вордпрессе. Вам оно надо?

По понятным причинам в наше неспокойное время Вордпресс нужен всем

easy
Что такое прошивка
Что такое прошивка

Заглядываем внутрь простой электроники

easy
Что такое Linux (и другие вопросы)
Что такое Linux (и другие вопросы)

Быстрое знакомство с самой многогранной операционной системой

medium
hard