Front Ends, Single-Page Apps, шаблоны JS, CSS и SVG анимация

5 (100%) 3 vote[s]

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



Single-Page Apps

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

А для одностраничных приложений характерно получение контента, который обычно находится на нескольких страницах или на нескольких разных маршрутах Flask, вы можете объединить их в одну страницу. Страница просто будет получать новую информацию с сервера всякий раз, когда ей нужна дополнительная информация для отображения в браузере. Поэтому в прошлый раз мы взглянули на технологию AJAX, которая позволяла нам асинхронно запрашивать у сервера дополнительную информацию всякий раз, когда мы хотели включить дополнительную информацию на нашу веб-страницу. И мы рассмотрели конвертер валют, где мы могли ввести валюту, а затем получить информацию о курсе конвертации этой валюты с сервера и затем отобразить эту информацию прямо в веб-браузере.

Одностраничные приложения берут контент, который обычно находится на нескольких разных страницах (или маршрутах), и объединяют их в одну страницу, которая извлекает новую информацию с сервера всякий раз, когда это необходимо (с помощью таких методов, как AJAX).

Для начала, это приложение использует несколько страниц:

  @app.route("/")
  def first():
      return render_template("first.html")
 
  @app.route("/second")
  def second():
      return render_template("second.html")
 
  @app.route("/third")
  def third():
      return render_template("third.html")

Вот шаблон макета для этих страниц:

<html>
      <head>
          <title>My Webpage</title>
      </head>
      <body>
          <ul id="nav">
              <li><a href="">First Page</a></li>
              <li><a href="">Second Page</a></li>
              <li><a href="">Third Page</a></li>
          </ul>
          <hr>
 
          {% block body %}
          {% endblock %}
 
      </body>
  </html>
  • Панель навигации — это просто неупорядоченный список ссылок.

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

@app.route("/")
  def index():
      return render_template("index.html")
 
  texts = ["text 1", "text 2", "text 3"]
 
  @app.route("/first")
  def first():
      return texts[0]
 
  @app.route("/second")
  def second():
      return texts[1]
 
  @app.route("/third")
  def third():
      return texts[2]
    • Обратите внимание, что другие «маршруты» не возвращают новую веб-страницу, а только текст, который должен отображаться.

Чтобы обработать эту структуру, в index.html должен быть добавлен JavaScript:

<pre lang="html5"><html>
      <head>
          <script>
              document.addEventListener('DOMContentLoaded', () => {

                  // Начните с загрузки первой страницы.
                  load_page('first');

                  // Установить ссылки для загрузки новых страниц.
                  document.querySelectorAll('.nav-link').forEach(link => {
                      link.onclick = () => {
                          load_page(link.dataset.page);
                          return false;
                      };
                  });
              });

              // Отображает содержимое новой страницы в главном окне.
              function load_page(name) {
                  const request = new XMLHttpRequest();
                  request.open('GET', `/${name}`);
                  request.onload = () => {
                      const response = request.responseText;
                      document.querySelector('#body').innerHTML = response;
                  };
                  request.send();
              }
          </script>
      </head>
      <body>
          <ul id="nav">
              <li><a href="" class="nav-link" data-page="first">First Page</a></li>
              <li><a href="" class="nav-link" data-page="second">Second Page</a></li>
              <li><a href="" class="nav-link" data-page="third">Third Page</a></li>
          </ul>
          <hr>
          <div id="body">
          </div>
      </body>
  </html></pre>
    • load_page делает AJAX-запрос к серверу, чтобы получить текст, который должен быть отображен, и вставляет body div.

Такая одностраничная реализация позволяет избежать повторной загрузки страницы, чтобы отобразить очень похожий контент (например, одну и ту же панель навигации). Однако это исключает функциональность URL-адреса в качестве локатора, поскольку весь контент находится на одном маршруте.

HTML5 History API

HTMuL5 History API позволяет манипулировать историей браузера и URL, даже если страница все еще реализуется с использованием одностраничного дизайна. При каждом обращении к новой «странице» клиент может «выдвинуть» новое состояние URL.

Изменения в коде JavaScript находятся внутри функции load_page:

function load_page(name) {
      const request = new XMLHttpRequest();
      request.open('GET', `/${name}`);
      request.onload = () =&gt; {
          const response = request.responseText;
          document.querySelector('#body').innerHTML = response;
 
          // Push state to URL.
          document.title = name;
          history.pushState(null, name, name);
      };
      request.send();
  }
  • document.title — это просто эстетическое свойство, которое настроено на отображение текущей страницы.
  • В функции history.pushState(), которая используется для изменения истории браузера, первый аргумент — это любые данные, которые должны быть связаны с push-уведомлением, второй аргумент — это заголовок страницы, которую нужно нажать, а третий аргумент — это URL-адрес.

Однако недостаток заключается в том, что на самом деле мы не добьёмся полного подражания многостраничному поведению. Если пользователь попытается использовать кнопку «Назад» в своем браузере, изменится только URL-адрес, но не содержимое. Чтобы исправить это, можно использовать полное, стековое поведение API истории HTML5. Возвращаясь к истории, вы должны просто «вытолкнуть» любой URL-адрес сверху стека:

 // Отображает содержимое новой страницы в главном окне.
  function load_page(name) {
      const request = new XMLHttpRequest();
      request.open('GET', `/${name}`);
      request.onload = () =&gt; {
          const response = request.responseText;
          document.querySelector('#body').innerHTML = response;
 
          document.title = name;
          history.pushState({'title': name, 'text': response}, name, name);
      };
      request.send();
  }
 
  // Обновление текста в состоянии появления.
  window.onpopstate = e =&gt; {
      const data = e.state;
      document.title = data.title;
      document.querySelector('#body').innerHTML = data.text;
  };
      • Теперь при нажатии на новое состояние, заголовок и текстовые данные передаются вместе с ним.
      • Когда состояние выталкивается, событие, которое только что произошло, имеет свойство state, которое содержит все данные, которые были переданы с этим состоянием. Затем эти данные просто используются для обновления содержимого страницы, как и ожидалось.

Window and Document

В переменных window и document , которые были в прошлых примерах, являются лишь примерами JavaScripts объектов , c которыми могут быть выполнены операции и имеющие свойство, которые могут быть доступны. В частности, они содержат информацию об их размере и положении.

      • window.innerWidth : ширина окна
      • window.innerHeight : высота окна
      • document.body.offsetHeight : вся высота документа тела HTML, высота окна которого, вероятно, является небольшой частью
      • window.scrollY: как далеко вниз прокручивается страница (в пикселях)

Одним из потенциальных применений этих свойств является возможность обнаружения прокрутки пользователем до нижней части страницы.

window.onscroll = () =&gt; {
      console.log('----');
      console.log(window.innerHeight);
      console.log(window.scrollY);
      console.log(document.body.offsetHeight);
      if (window.innerHeight + window.scrollY &gt;= document.body.offsetHeight) {
          document.querySelector('body').style.background = 'green';
      } else {
          document.querySelector('body').style.background = 'white';
      }
  };
      • console.log — это, по сути, оператор print, который печатает на консоль веб-браузера.
      • Все, что делает этот код, это изменяет цвет фона веб-страницы на зеленый, когда достигается нижняя часть документа, что определяется с помощью математических отношений свойств window и document.

Более полезным применением этого обнаружения нижней части документа будет динамическая загрузка большего количества контента, когда достигается нижняя часть веб-страницы. application.py для такой веб-страницы может выглядеть так:

import time
 
  from flask import Flask, jsonify, render_template, request
 
  app = Flask(__name__)
 
  @app.route("/")
  def index():
      return render_template("index.html")
 
  @app.route("/posts", methods=["POST"])
  def posts():
 
      # Получить начальную и конечную точку для постов для генерации.
      start = int(request.form.get("start") or 0)
      end = int(request.form.get("end") or (start + 9))
 
      # Создать список сообщений.
      data = []
      for i in range(start, end + 1):
          data.append(f"Post #{i}")
 
      # Искусственно задерживать скорость реакции.
      time.sleep(1)
 
      # Вернуть список сообщений.
      return jsonify(data)

index.html (немного сложнее):

 <html>
      <head>
          <script>
              // Начните с первого поста.
              let counter = 1;

              // Загружайте 20 постов одновременно.
              const quantity = 20;

              // Когда DOM загружается, рендеринг первых 20 постов.
              document.addEventListener('DOMContentLoaded', load);

              // Если достигнут низ документа, загрузите следующие 20 сообщений.
              window.onscroll = () => {
                  if (window.innerHeight + window.scrollY >= document.body.offsetHeight) {
                      load();
                  }
              };
//Загрузить следующий набор сообщений
              function load() {

                  // Установить начальный и конечный номера сообщений и обновить счетчик.
                  const start = counter;
                  const end = start + quantity - 1;
                  counter = end + 1;

                  // Откройте новый запрос, чтобы получить новые сообщения.
                  const request = new XMLHttpRequest();
                  request.open('POST', '/posts');
                  request.onload = () => {
                      const data = JSON.parse(request.responseText);
                      data.forEach(add_post);
                  };

                  // Добавить начальную и конечную точки для запроса данных.
                  const data = new FormData();
                  data.append('start', start);
                  data.append('end', end);

                  // Послать запрос.
                  request.send(data);
              };

              // Добавить новый пост с заданным содержанием в DOM.
              function add_post(contents) {

                  // Создать новый пост.
                  const post = document.createElement('div');
                  post.className = 'post';
                  post.innerHTML = contents;

                  // Добавить пост в DOM.
                  document.querySelector('#posts').append(post);
              };
          </script>
      </head>
      <body>
          <div id="posts">
          </div>
      </body>
  </html>

Для большей функциональности функцию add_post можно изменить, добавив кнопку для скрытия неинтересных постов:

function add_post(contents) {

      // Создать новый пост.
      const post = document.createElement('div');
      post.className = 'post';
      post.innerHTML = contents;

      // Добавить кнопку для скрытия поста.
      const hide = document.createElement('button');
      hide.className = 'hide';
      hide.innerHTML = 'Hide';
      post.append(hide);

      // Когда кнопка нажата, удалить пост.
      hide.onclick = function() {
          this.parentElement.remove();
      };

      // Добавить пост в DOM.
      document.querySelector('#posts').append(post);
  };
  • Вызов post.append(hide) добавляет кнопку скрытия внутри сообщения div.
  • parentElement - это элемент, содержащий рассматриваемый элемент. В этом случае this.parentElement используется ссылка на post содержащую кнопку hide.
  • remove является встроенной функцией для удаления всех элементов вместе.

Шаблоны JavaScript

Проблема с использованием JavaScript для создания более сложных пользовательских интерфейсов и добавления элементов в DOM заключается в том, что код становится немного запутанным. Каждый элемент должен быть создан, имена классов должны быть назначены, внутренний HTML должен быть установлен и т.д. В идеале весь HTML должен быть написан где-то еще. Но точное содержимое в настоящее время все еще неизвестно.

Решением этой проблемы является использование JavaScript-шаблонов. Они позволяет создавать шаблоны, которые определяют HTML, а также допускает подстановку внутри этого шаблона для добавления другого контента. Очень простая версия этого — шаблонные строки JavaScript. Есть много разных библиотек JavaScript, которые продвигают эту идею. В приведенных примерах используется библиотека Handlebars.

Следующая серия примеров будет игровым приложением для бросания костей

Вот отправная точка:

 <html>
      <head>
          <script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.11/handlebars.min.js"></script>
          <script>
              // Шаблон для результатов
              const template = Handlebars.compile("<li>You rolled a </li>");

              document.addEventListener('DOMContentLoaded', () => {
                  document.querySelector('#roll').onclick = ()  => {

                      // Создать случайный бросок.
                      const roll = Math.floor((Math.random() * 6) + 1);

                      // Добавить результат броска в DOM.
                      const content = template({'value': roll});
                      document.querySelector('#rolls').innerHTML += content;
                  };
              });
          </script>
      </head>
      <body>
          <button id="roll">Roll</button>
          <ul id="rolls">
          </ul>
      </body>
  </html>
      • template используется повторно для каждого броска. Это похоже на клиентский аналог шаблонов Flask / Jinja2.
      • Math.random() возвращает случайное число от 0 до 1. Умножение его на 6 возвращает число в диапазоне от 0 до, но не включая 6. Добавление 1 дает диапазон от 1 до 7, а использование Math.floor() возвращает 1, 2, 3, 4, 5 или 6.
      • template  используется как функция: передается значение (я) и возвращает содержимое HTML.

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

const template = Handlebars.compile("<li>You rolled: <img src=\"img/.png\"></li>");
      • Обратите внимание, как двойными кавычками "экранируются символы, так как они находятся внутри строки.

Тем не менее, включение всего шаблона JavaScript внутри строки начинает становиться беспорядочным при включении изображений и т.д. В идеале должен быть чистый HTML, который затем компилируется с помощью Handlebars:

<script id="result" type="text/x-handlebars-template">
      <li>
          You rolled:
            
          <img alt="{{ value }}" title="{{ value }}" src="img/{{ value }}.png"></img>
            
      </li>
  </script>
  <script>
      // Шаблон для результатов проверки
      const template = Handlebars.compile(document.querySelector('#result').innerHTML);

      document.addEventListener('DOMContentLoaded', () => {
          document.querySelector('#roll').onclick = ()  => {

              // Создать случайный бросок.
              const roll = Math.floor((Math.random() * 6) + 1);

              // Добавить результат броска в DOM.
              const content = template({'value': roll});
              document.querySelector('#rolls').innerHTML += content;
          };
      });
  </script>
      • Обратите внимание, что есть два script элемента. Элемент с идентификатором result  представляет результат броска. У него есть специальный атрибут type , определенный Handlebars. Внутри этого script элемента будет HTML-код, представляющий шаблон Handlebars.
      • В alt и title атрибутах изображения просто предоставляют ту же информацию об изображении в виде текста, на случай,если изображение зависло или для браузеров , которые не поддерживают изображения.
      • Теперь вместо компиляции строки шаблон просто выбирается с помощью document.querySelector.

Handlebars, как и Jinja, поддерживают лупы. В этом примере loops могут быть использованы для броска нескольких кубиков одновременно:

<html>
      <head>
          <script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.11/handlebars.min.js"></script>
          <script id="result" type="text/template">
              <li>
                  You rolled:
                    
                  {{#each values}}
                      <img alt="{{ this }}" title="{{ this }}" src="img/{{ this }}.png">
                  {{/each}}
                  (Total: {{ total }})
                    
              </li>
          </script>
          <script>

              const template = Handlebars.compile(document.querySelector('#result').innerHTML);

              document.addEventListener('DOMContentLoaded', () => {
                  document.querySelector('#roll').onclick = ()  => {

                      const counter = parseInt(document.querySelector('#counter').value);
                      const rolls = [];
                      let total = 0;
                      for (let i = 0; i < counter; i++) {
                          const value = Math.floor(Math.random() * 6) +  1;
                          rolls.push(value);
                          total += value;
                      };

                      const content = template({'values': rolls, 'total': total});
                      document.querySelector('#rolls').innerHTML += content;
                  };
              });
          </script>
      </head>
      <body>
          <input id="counter" type="number" placeholder="Number of Rolls" min="1" value="1">
          <button id="roll">Roll</button>
          <ul id="rolls">
          </ul>
      </body>
  </html>
      • #each это Handlebars ‘block helper’. Есть много таких помощников с различными функциями, будь то циклические, в этих примерах условные ( #if) и т.д. Если встроенных помощников недостаточно, Handlebars также позволяет создавать настраиваемые помощники.
      • Внутри цикла Handlebars вызывает каждый элемент в наборе элементов (в этом случае набор вызывается valuesthis,.

При добавлении шаблонов Handlebars в приложения Flask следует иметь в виду, что Jinja сначала просканирует HTML-файл и увидит в синтаксисе двойных фигурных скобок место, в которое следует вставить значение. Поскольку это нежелательно, нужно попросить Джинджу игнорировать блоки кода с шаблонами Handlebars с raw блоком Джинджи:

{% raw -%}
      {{ contents }}
  {%- endraw %}

См. в исходных кодах.

CSS анимация

CSS-анимация позволяет переходить от одного свойства CSS к другому в течение некоторого промежутка времени, пока страница открыта.

@keyframes grow {
      from {
          font-size: 20px;
      }
      to  {
          font-size: 100px;
      }
  }


  h1 {
      animation-name: grow;
      animation-duration: 2s;
      animation-fill-mode: forwards;
  }
      • @keyframes grow определяет CSS-анимацию grow, которая задаёт исходный стиль from и конечный стиль to.
      • animation-name cвойство используется для связывания grow анимации с элементом h1.
      • animation-duration устанавливает время, в течение которого происходит анимация.
      • animation-fill-mode устанавливает направление анимации. Значение forwards означает, что после достижения конца анимации этот финальный стиль должен быть сохранен.

Еще один простой пример:

My Webpage

Welcome!

@keyframes move {
      from {
          left: 0%;
      }
      to  {
          left: 20%;
      }
  }

  h1 {
      position: relative;
      animation-name: move;
      animation-duration: 3s;
      animation-fill-mode: forwards;
  }

См. пример бегущая строка на СSS3.

      • left указывает относительную позицию элемента HTML. Для h1 свойство position: relative означает, что его позиция определяется относительно к другим частям окна.
      • move перемещает элемент от 0% от левого края экрана до 20% от этого же края (в соответствии с шириной окна).

Наряду с начальной и конечной точкой, также можно указать промежуточные точки:

 @keyframes move {
      0% {
          left: 0%;
      }
      50% {
          left: 50%;
      }
      100% {
          left: 0%;
      }
  }

Добавление JavaScript

CSS анимация запускается всегда сразу, как только загружается веб-страница. Для управления анимацией можно использовать JavaScript для изменения свойств CSS animationPlayState, то есть paused или running:

Welcome!

<style>
      @keyframes move {
          0% {
              left: 0%;
          }
          50% {
              left: 50%;
          }
          100% {
              left: 0%;
          }
      }

      h1 {
          position: relative;
          animation-name: move;
          animation-duration: 3s;
          animation-fill-mode: forwards;
          animation-iteration-count: infinite;
      }
      
  </style>
  <script>
      document.addEventListener('DOMContentLoaded', () => {
          const h1 = document.querySelector('h1');
          h1.style.animationPlayState  = 'paused';
          document.querySelector('button').onclick = () => {
              if (h1.style.animationPlayState  === 'paused')
                  h1.style.animationPlayState = 'running';
              else
                  h1.style.animationPlayState  = 'paused';
          };
      });
  </script>
      • animation-iteration-count указывает, сколько раз анимация должна быть запущена.
      • Когда страница загружается впервые, анимация приостанавливается. Затем, каждый раз, когда нажимается какая-либо кнопка animationPlayState, она изменяется.

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

<style>
    
      @keyframes hide {
          from {
              opacity: 1;
          }
          to {
              opacity: 0;
          }
      }

      .post {
          background-color: #77dd11;
          padding: 20px;
          margin-bottom: 10px;
          animation-name: hide;
          animation-duration: 2s;
          animation-fill-mode: forwards;
          animation-play-state: paused;
      }
  </style>
  <script>
      // ...остальной код JavaScript...

      // Если кнопка скрыть нажата, удалить пост.
      document.addEventListener('click', event => {
          const element = event.target;
          if (element.className === 'hide') {
              element.parentElement.style.animationPlayState = 'running';
              element.parentElement.addEventListener('animationend', () =>  {
                  element.parentElement.remove();
              });
          }
      });
  </script>
      • Здесь немного другая логика, чтобы выяснить, когда нажимается кнопка. Теперь, каждый раз, когда происходит щелчок мышью, переменная event.target, которая является щелкаемым элементом, присваивается переменной element .
      • Если была нажата кнопка «Скрыть», в сообщении анимация запускается, и конец анимации прослушивается с обратным вызовом, чтобы фактически удалить сообщение.

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

 @keyframes hide {
      0% {
          opacity: 1;
          height: 100%;
          line-height: 100%;
          padding: 20px;
          margin-bottom: 10px;
      }
      75% {
          opacity: 0;
          height: 100%;
          line-height: 100%;
          padding: 20px;
          margin-bottom: 10px;
      }
      100% {
          opacity: 0;
          height: 0px;
          line-height: 0px;
          padding: 0px;
          margin-bottom: 0px;
      }
  }
      • Для первых 75% анимации пост исчезает.
      • В последних 25% анимации размер сообщения уменьшается до тех пор, пока он не будет иметь высоту, в результате чего все остальные сообщения ниже будут заполнять это пространство.

SVG Animation

Масштабируемая векторная графика (SVG) — это графический элемент, определяемый линиями, углами и формами. SVG можно использовать для рисования вещей, которые простые элементы HTML, такие как div, не допускают.

<body>
      <svg style="width:100%; height:180px">
          <circle cx="200" cy="120" r="50" style="fill:blue"/>
      </svg>
</body>
      • Элементу SVG присваивается фиксированное значение height и  width, которые автоматически корректируются на основе содержимого для поддержания этой фиксированной высоты.
      • Элемент circle является одним из элементов SVG, поддерживаемых SVG. Ему даны x- и y-координаты для его центра cx и cy, радиус r, и, наконец, немного CSS-стиля.

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

<!DOCTYPE html>
<html>
    <head>
        <script src="https://d3js.org/d3.v4.min.js"></script>
    </head>
    <body>
        <svg id="svg" style="width:100%; height:800px"/>
    </body>
    <script>

        const svg = d3.select('#svg');

        svg.append('circle')
           .attr('cx', 200)
           .attr('cy', 200)
           .attr('r', 90)
           .style('fill', 'green');

    </script>
</html>
      • d3.select  получает доступ к элементу HTML.
      • Затем функции D3 используются для добавления круга к выбранному элементу SVG со всеми теми же атрибутами и стилями, что и раньше.

Как и в CSS, в SVG могут быть добавлены анимации:

const svg = d3.select('#svg');

  const c = svg.append('circle')
               .attr('cx', 200)
               .attr('cy', 200)
               .attr('r', 50)
               .style('fill', 'blue');

  c.transition()
   .duration(1000)
   .attr('cx', 500)
   .attr('cy', 500)
   .style('fill', 'red');
      • Дается продолжительность (в миллисекундах) для transition (анимации) вместе с окончательными значениями для каждого атрибута, который должен быть анимирован.

Анимации могут также задерживаться или запускаться при определенных событиях:

<!DOCTYPE html>
<html>
    <head>
        <script src="https://d3js.org/d3.v4.min.js"></script>
    </head>
    <body>
        <svg id="svg" style="width:100%; height:800px"/>
    </body>
    <script>

        const svg = d3.select('#svg');

        const c = svg.append('circle')
                     .attr('cx', 200)
                     .attr('cy', 200)
                     .attr('r', 50)
                     .style('fill', 'blue');

        c.transition()
         .duration(1000)
         .delay(1000)
         .attr('cx', 500);

        c.on('click', function() {
            d3.select(this).transition()
                           .duration(3000)
                           .style('fill', 'red');
        });

    </script>
</html>

Приложение для рисования

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

<!DOCTYPE html>
<html>
    <head>
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
        <script src="https://d3js.org/d3.v4.min.js"></script>
        <style>
            .container {
                text-align: center;
            }
        </style>
        <script src="draw2.js"></script>
    </head>
    <body>
        <div class="container">
            <h1>CSCI E-33a Draw</h1>
            <div id="options" class="row">
                <select id="color-picker">
                    <option value="black">Black</option>
                    <option value="red">Red</option>
                    <option value="blue">Blue</option>
                    <option value="green">Green</option>
                </select>
                <select id="thickness-picker">
                    <option value=1>1</option>
                    <option value=2>2</option>
                    <option value=3 selected>3</option>
                    <option value=4>4</option>
                    <option value=5>5</option>
                    <option value=6>6</option>
                    <option value=7>7</option>
                    <option value=8>8</option>
                    <option value=9>9</option>
                    <option value=10>10</option>
                </select>
                <button id="erase">Erase</button>
            </div>
        </div>
        <svg id="draw">
        </svg>
    </body>
</html>

Предыдущая статья «Интеграция JavaScript с Python и Flask«

Следующая статья «Django«

Читайте больше по теме:

Подписаться
Уведомление о
guest
0 Комментарий
Inline Feedbacks
View all comments
Просмотры: 287

Популярные записи