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

Пишем приложение на 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

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

Текст:

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

Редактор:

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

Художник:

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

Корректор:

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

Вёрстка:

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

Соцсети:

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

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