Русский
Русский
English
Статистика
Реклама

Разработка веб-сайтов

Перевод Как убрать из Git-репозитория файлы с конфиденциальной информацией

16.09.2020 16:23:32 | Автор: admin
Файлы проиндексированы, написано сообщение коммита, данные отправлены на сервер И вдруг хочется повернуть время вспять. В коммит попал файл, которого там быть не должно. Когда такое случается, приходит время обращаться к поисковику.

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

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


Удаление файлов с конфиденциальной информацией из Git-репозитория (изображение большого размера)

Минимизация ущерба


Итак, вы случайно закоммитили файл с конфиденциальной информацией. Назовём этот файл .env. Сразу после того, как это случилось, надо задать себе пару вопросов:

  • Отправлен ли коммит в удалённый репозиторий?
  • Является ли удалённый репозиторий общедоступным?

Коммит пока не отправлен в удалённый репозиторий


Если вы пока не отправили коммит в репозиторий, то, в общем-то, возникшая ситуация никакой угрозы не несёт. Для того чтобы всё исправить, достаточно просто вернуться к предыдущему коммиту:

git reset HEAD^ --soft

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

Если же вы хотите сохранить коммит и вам нужно просто удалить из него определённые файлы, тогда поступите так:

git rm .env --cachedgit commit --amend

Параметр --amend можно использовать только для работы с самым свежим коммитом. Если вы, после неудачного коммита, добавили ещё несколько, воспользуйтесь такой командой:

git rebase -i HEAD~{на сколько коммитов нужно вернуться?}

Это позволит исправить неправильный коммит и поможет не потерять изменения, внесённые в проект остальными коммитами.

Коммит отправлен в удалённый репозиторий


Если вы уже отправили коммит в удалённый репозиторий, то, в первую очередь, вам нужно знать о том, чем отличаются публичные и приватные репозитории.

Если ваш репозиторий является приватным, и при этом он не доступен ботам или людям, которым вы не доверяете, вы можете просто внести поправки в последний коммит, воспользовавшись парой вышеприведённых команд.

Если вы отправили в репозиторий, после проблемного коммита, и другие коммиты, это не помешает вам убрать файлы с конфиденциальными данными из истории Git, воспользовавшись командой git filter-branch или инструментом BFG Repo-Cleaner.

Вот пример использования git filter-branch:

git filter-branch --force --index-filter "git rm --cached --ignore-unmatch .env" --prune-empty --tag-name-filter cat -- --all

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

  • Вы меняете историю Git. Если на текущее состояние репозитория полагаются другие люди, если от этого состояния зависят какие-то ветки того же репозитория, его форки, открытые PR, то это нарушит их работу. В подобных случаях относитесь к репозиторию как к общедоступному и постарайтесь не вносить изменения в его историю.
  • Вам нужно будет очистить кеш. Вам понадобится обратиться в службу поддержки платформы, на которой хранится ваш репозиторий, и попросить очистить его кеш. Несмотря на то, что вы исправили проблемный коммит или переписали историю репозитория, старый коммит, содержащий конфиденциальные данные, останется в кеше. Для того чтобы к нему обратиться, нужно будет знать его ID, но к нему можно будет получить доступ до тех пор, пока кеш не очистят.

Нужно ли создавать новые секретные ключи в том случае, если их актуальные версии попали в публичный репозиторий?


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

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

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

Рекомендации по хранению конфиденциальных файлов в проектах, в которых для контроля версий применяется Git


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

Храните секретные данные в файле .env (или в другом подобном файле)


Ключи к API и другие подобные сведения стоит хранить в единственном файле .env. При таком подходе, если Git не отслеживает состояние файла .env, вы, добавив в этот файл новый ключ, не отправите его случайно в репозиторий.

Ещё одно преимущество такого подхода заключается в том, что так у вас будет доступ ко всем ключам через глобальную переменную process.

Используйте, если это возможно, ключи API


Скомпрометированные ключи API легко деактивировать, такие ключи легко создать заново. Если это возможно используйте именно их, а не нечто вроде логинов и паролей.

Храните ключи API, пользуясь средствами вашего инструмента для сборки проектов


Ключи API обычно нужны при сборке приложений. Инструменты для сборки проектов, вроде Netlify, позволяют держать ключи в защищённых хранилищах. Такие ключи автоматически внедряются в приложение с использованием глобальной переменной process.


Управление переменными окружения

Добавьте запись о файле .env в файл .gitignore


Сделайте так, чтобы Git не отслеживал бы файлы, содержащие конфиденциальную информацию.

Подготовьте шаблонный файл .env.template


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

Не меняйте историю Git в удалённых репозиториях


Постарайтесь строго придерживаться этого правила. Если вы следовали вышеприведённым рекомендациям, то историю Git вам менять и не потребуется.

Итоги


Надеюсь, мой материал поможет вам в безопасной работе с конфиденциальными данными.

А вам случалось отправлять в общедоступный репозиторий что-то такое, что туда попадать не должно?



Подробнее..

Перевод Выбираем лучший бэкенд-фреймворк 2021 года

18.09.2020 16:04:09 | Автор: admin
Недавно мы опубликовали статью, автор которой размышлял о том, какой язык программирования, JavaScript, Python или Go, лучше всего подойдёт для бэкенд-разработки в 2021 году. Сегодня мы представляем вашему вниманию перевод ещё одного материала того же автора. Здесь он пытается найти ответ на вопрос о том, на какой фреймворк для разработки серверных частей приложений стоит обратить внимание тем, кто хочет во всеоружии встретить 2021 год.



О роли фреймворков в IT-индустрии


Я, проанализировав результаты опроса разработчиков, проведённого в 2020 году Stack Overflow, понял, что использование фреймворков играет огромную роль в IT-индустрии. А именно, речь идёт об ответах на вопрос о том, что разработчики считают самым важным при поиске работы.


Факторы, влияющие на выбор места работы

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

Разработчик обычно знаком хотя бы с одним фреймворком. Здесь я собираюсь рассказать о трёх фреймворках о Node.js/Express, Django и Spring Boot. Полагаю, что тот, кто, готовясь к 2021 году, решит сделать своим основным инструментом один из них, в любом случае, не прогадает. Но у каждого из них есть свои особенности, которые мне и хотелось бы здесь обсудить.

Результаты исследований и другие данные


Данные с GitHut


Ресурс GitHut позволяет узнать различные сведения о репозиториях. В частности, речь идёт о количестве активных репозиториев, об общем количестве PR, и о разных показателях, рассчитываемых по состоянию на один репозиторий: количество PR и форков, число открытых задач, количество новых подписчиков.


Популярность языков программирования на GitHub

Исследование Stack Overflow


Если взглянуть на результаты исследования Stack Overflow, то окажется, что Express находится на первом месте среди бэкенд-фреймворков, которые любят разработчики. Два других интересующих нас фреймворка, Spring и Django, следуют за Express с небольшим отрывом. В результате оказывается, что проект, основанный на самом перспективном скриптовом языке, на JavaScript, лидирует, а за ним идёт проект, в котором используется один из языков, обладающих самыми широкими возможностями, то есть Python.


Популярность веб-фреймворков

GitHub-репозитории


Некоторые люди не любят перемен, но изменения необходимо принимать, если альтернативой является катастрофа.
Илон Маск


Фреймворк Express создан на базе платформы Node.js, поэтому тут мы будем сравнивать репозитории Node.js, Spring Boot и Django


Репозиторий node


Репозиторий spring-boot


Репозиторий django

Как видно, больше всего звёзд у репозитория Node.js. Но разница между звёздами проектов не так сильна, как различие в количестве их форков. У Spring Boot и Django имеется гораздо больше форков, чем у Node.js.

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

Node.js/Express


Node.js представляет собой серверную платформу, которая является частью стека технологий, охватывающих все нужды веб-разработки, и основанных на JavaScript. В Node.js используется JavaScript-движок V8, тот же самый, что применяется в браузере Chrome и в других браузерах, основанных на Chromium. В результате оказывается, что благодаря использованию Node.js код, предназначенный для выполнения на сервере, можно писать на JavaScript. На базе платформы Node.js создано множество фреймворков, включая такие популярные, как Express.

Сильные стороны Node.js


  • Появление Node.js сделало возможным фуллстек-разработку веб-проектов на JavaScript. В результате в распоряжении разработчиков серверных частей приложений оказались и сильные возможности JavaScript, и наработки экосистемы JS, библиотеки, которыми стало реально воспользоваться в серверном окружении.
  • JavaScript-код, аналогичный по функционалу, например, коду, написанному на C, оказывается компактнее. Производительность JavaScript-кода при этом достаточно высока для применения его в проектах, в которых важна скорость работы кода.
  • Код клиентских и серверных частей проектов легче поддерживать в согласованном состоянии, так как и там и там используется один и тот же язык.
  • Один и тот же код можно совместно использовать и на клиенте, и на сервере.
  • Благодаря существованию модулей Node.js, которые, в сущности, представляют собой особым образом оформленные фрагменты кода, разработчики могут с удобством использовать в своих проектах чужой код, а так же собственные наработки.
  • Платформа Node.js, и, соответственно, основанные на ней фреймворки, отличаются нетребовательностью к ресурсам и масштабируемостью. Именно поэтому Node.js это платформа, к которой часто прибегают те, кто пользуется микросервисными архитектурами.
  • Эта платформа хорошо подходит для разработки микросервисов ещё и из-за существования системы модулей Node.js, которые можно представить себе в виде строительных блоков серверных приложений.
  • В Node.js JavaScript код компилируется в машинный код, что позволяет получить гораздо более высокую производительность, чем при интерпретации кода. Сообщество JavaScript-разработчиков видит постоянное улучшение производительности Node.js за счёт того, что Google постоянно работает над совершенствованием V8.
  • Благодаря тому, что в Node.js имеется система ввода-вывода, не блокирующая главный поток, эта платформа демонстрирует высокую производительность. Достойная скорость обработки запросов достигается благодаря использованию JavaScript-механизмов конкурентного однопоточного выполнения кода.
  • Node.js это опенсорсный проект, вокруг которого собралось огромное сообщество разработчиков. Это значит, что тот, кто столкнётся с какой-то проблемой, сможет достаточно быстро найти её решение.
  • Node.js, в ближайшем будущем, может стать платформой, которую будут использовать для проведения тяжёлых вычислений, наподобие тех, которые применяются для решения задач машинного обучения.

Компании, которые используют Node.js


  • Paypal
  • Netflix
  • LinkedIn
  • Uber
  • eBay
  • Yahoo

Если учесть сильные стороны Node.js и то, в каких компаниях используется эта платформа, становятся понятными причины её огромной популярности. Зарплата Node.js-разработчиков в США варьируется в пределах $40,000-$130,000 в год. В результате можно сказать, что если вы чувствуете, что Node.js и фреймворки, основанные на этой платформе, вам интересны, вы вполне можете выбрать именно их в качестве базы для своих разработок 2021 года.

Spring Boot


Проект Spring Boot это фреймворк для разработки бэкенд-приложений, основанный на Java, который, как и Node.js, используется для разработки микросервисов. Этот фреймворк упрощает создание приложений, основанных на Spring, его можно представить себе в виде инструмента для создания самостоятельных Spring-приложений. Если вы планируете в 2021 году перейти на Spring, то вам, определённо, стоит знать о том, чем вам в этом деле сможет помочь Spring Boot.

Сильные стороны Spring Boot


  • Spring Boot позволяет с минимальными усилиями создавать самостоятельные Spring-приложения, облегчает процесс их конфигурирования, упрощает работу над ними. Подобные приложения легко запускаются с помощью команды java -jar.
  • Если в процессе создания Spring Boot-приложения произошла ошибка, встроенный анализатор ошибок поможет справиться с проблемой.
  • Spring Boot поддерживает встроенные серверы, вроде Tomcat и Jetty. Это значит, что тем, кто пользуется Spring Boot, не нужно развёртывать .war-файлы на внешних серверах.
  • Использование Spring Boot позволяет облегчить конфигурирование Maven за счёт наличия в системе начальных вариантов файла pom.xml.
  • В возможности фреймворка входит автоматическое конфигурирование Spring.
  • Spring Boot хорошо интегрируется с другими фреймворками.
  • Фреймворк предоставляет разработчику конфигурации, готовые для продакшн-использования. Сюда входят, например, метрики состояния проекта и внешние конфигурации.
  • При использовании Spring Boot нет нужды в применении XML-конфигураций или средств для генерирования кода.
  • Применение Spring Boot облегчает труд разработчиков за счёт применения принципа проектирования ПО, известного как Convention over Configuration.

Компании, которые используют Spring


  • Platform
  • Intuit
  • MIT
  • Zillow
  • TransferWise

Честно говоря, я не фанат Java. И я не будут использовать Spring Boot для серверной разработки в 2021 году. Но, если верить статистике, существует много программистов, применяющих этот фреймворк. Если говорить о зарплатах соответствующих специалистов, то это что-то около $50,000-$104,000 в год. Это немного меньше, чем зарплаты Node.js-разработчиков.

Django


Django это опенсорсный бэкенд-фреймворк, написанный на Python. Как известно, Python это один из таких языков, которые пользуются наибольшей любовью разработчиков. И это одна из основных причин того, что Django является одним из самых популярных серверных фреймворков. Но у того, чтобы выбрать Django в качестве своего фреймворка 2021 года, есть и другие причины.

Сильные стороны Django


  • Django позволяет без особых сложностей создавать динамические веб-приложения с использованием Python. Данный фреймворк написан на Python. В этом заключается одно из главных достоинств Django.
  • Фреймворк поддерживает паттерн проектирования MVC. Это помогает разработчикам в разделении пользовательского интерфейса и бизнес-логики Django-приложений.
  • Это быстрый фреймворк, не перегруженный ненужными возможностями. Я имеют в виду то, что использование Django позволяет быстро выйти на работоспособный проект.
  • Django не относится к минималистичным фреймворкам, широко используемым для разработки микросервисов. Он отличается мощностью, универсальностью и определённым своеобразием.
  • Создатели этого фреймворка серьёзно относятся к безопасности. Поэтому они дают разработчикам, использующим Django, соответствующие инструменты. Я уверен, что все вы знаете о том, как много проблем существует в наши дни в сфере кибербезопасности. Поэтому чрезвычайно важными являются вопросы защиты веб-проектов. Django поддерживает систему аутентификации пользователей, содержит инструменты для защиты от различных атак. Среди них средства защиты от SQL-инъекций, от межсайтового скриптинга, от межсайтовой подделки запросов, от кликджекинга.
  • Django-проекты отличаются компактностью кода.
  • Разработчики, использующие Django, могут моделировать базовые классы. Это значит, что в их распоряжении всегда имеется ORM.
  • Django это кросс-платформенный проект. Он отлично работает на различных операционных системах. Кроме того, он поддерживает взаимодействие с различными базами данных.
  • Это фреймворк, приложения, созданные с использованием которого, хорошо поддаются масштабированию. Поэтому тот, кто выбирает Django, может быть уверенным в том, что сможет эффективно развивать свой проект по мере его роста.
  • Вокруг Django сформировалось активное сообщество. Поэтому тот, кто столкнулся с какой-то проблемой, сможет без особых сложностей её решить.

Компании, которые используют Django


  • Mozilla
  • NASA
  • Pinterest
  • Bitbucket
  • Instagram

Так как Django основан на Python, о производительности этого фреймворка и о его поддержке можно не беспокоиться. Кроме того, если взглянуть на список компаний, использующих Django, можно сделать вывод о том, что это фреймворк, достойный внимания и в этом, и в будущем году. Если проанализировать зарплаты Django-разработчиков, то окажется, что это что-то между $90,000-$120,000 в год. В результате оказывается, что спрос на Django-специалистов достаточно высок.

Итоги


Вышеприведённые факты позволяют сделать вывод о том, что платформа Node.js, в лице фреймворка Express, и Django показывают себя очень хорошо. Но, в то же время, Spring Boot тоже представляет собой достаточно интересное явление. Полагаю, что самым интересной платформой для разработки серверных частей приложений в 2021 году будет Node.js. Хотя и Django это отличный, развитый фреймворк, который вполне может стать чьим-то выбором в будущем году.

Каким фреймворком для разработки серверных частей веб-проектов вы планируете пользоваться в 2021 году?



Подробнее..

Перевод Программисту. 10 ценных GitHub-репозиториев

19.09.2020 16:20:36 | Автор: admin
GitHub это платформа, дающая программистам отличные инструменты для организации работы над кодом. Но в GitHub-репозиториях, помимо кода, можно найти массу ценных учебных материалов. Я, например, занимаюсь разработкой ПО и постоянно ищу репозитории, которые могут чем-то мне пригодиться. Вот 10 моих любимых GitHub-проектов.



1. danistefanovic/build-your-own-x


Звёзды GitHub: около 82 тысяч

Репозиторий build-your-own-x это фантастический ресурс, который предназначен для всех, кто хочет что-то создать. Такие люди могут найти тут массу идей, каждая из которых способна лечь в основу их собственного проекта. Кроме того, в этом репозитории, если даже не использовать его для поиска идей, можно найти много интересных материалов.

2. trekhleb/javascript-algorithms


Звёзды GitHub: около 81 тысячи

Одно из отличий программиста (Software Engineer) и разработчика (Software Developer) заключается в том, что программист, скорее всего, лучше, чем разработчик, разбирается в алгоритмах и структурах данных. Но вне зависимости от того, что вы уже знаете, вы сможете найти в репозитории javascript-algorithms что-то такое, что вас заинтересует. А именно, здесь имеется большой набор материалов по алгоритмам и структурам данных с примерами их реализации на JavaScript. Здесь же можно найти ответы на вопросы, которые встречаются на собеседованиях.

3. ossu/computer-science


Звёзды GitHub: около 64 тысяч

Репозиторий computer-science содержит ссылки на бесплатные учебные материалы по компьютерным наукам, которые, полагаю, будут интересны многим от людей, которые только подумывают о том, чтобы заняться программированием, до программистов-самоучек, которые уже нашли работу.

4. 30-seconds/30-seconds-of-code


Звёзды GitHub: около 59 тысяч

В репозитории 30-seconds-of-code имеется более сотни коротких примеров кода, написанного на JavaScript. Среди этих примеров можно встретить, скажем, реализации неких алгоритмов, а так же код, который может пригодиться программисту для решения разных рабочих задач. Полагаю, этот репозиторий, определённо, достоин вашего внимания.

5. gothinkster/realworld


Звёзды GitHub: около 46 тысяч

Хотелось вам когда-нибудь узнать о том, как создают реальные приложения с использованием некоего языка или некоей технологии? Если так значит репозиторий realworld, представляющий проект RealWorld example apps, создан специально для вас. Многие приложения-примеры представляют собой клоны типичного списка дел, известного как TODO-приложение. Репозиторий realworld идёт другим путём. А именно, здесь можно найти тщательно проработанный проект, похожий на medium.com. При этом тут показаны варианты данного проекта, созданные с использованием различных технологий разработки клиентских и серверных частей приложений.

6. EbookFoundation/free-programming-books


Звёзды GitHub: около 160 тысяч

В репозитории free-programming-books можно найти множество ссылок на бесплатные книги по самым разным темам, имеющим отношение к программированию.

7. donnemartin/system-design-primer


Звёзды GitHub: около 107 тысяч

В репозитории system-design-primer можно найти пособие по проектированию систем. Умение проектировать крупномасштабные системы это весьма ценный навык, который способен найти практическое применение во многих сферах. Кроме того, большие компании, нанимающие специалистов на должности старших программистов (и на более высокие должности) ожидают наличия у таких специалистов подобных навыков. Если вы хотите подготовиться к созданию крупномасштабных систем, то вам, несомненно, стоит заглянуть в этот репозиторий.

8. vinta/awesome-python


Звёзды GitHub: около 86 тысяч

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

9. goldbergyoni/nodebestpractices


Звёзды GitHub: около 52 тысяч

Я постоянно читаю материалы с различными рекомендациями и лучшими практиками по Node.js и никак не могу начитаться. Именно поэтому я, когда нашёл репозиторий nodebestpractices, понял, что он увлечёт меня надолго. Одна из бед программистов-самоучек заключается в том, что они далеко не всегда начинают с того, что относится к разряду лучших практик. А ресурсы, подобные этому репозиторию, помогают профессиональному росту таких программистов.

10. josephmisiti/awesome-machine-learning


Звёзды GitHub: около 46 тысяч

Репозиторий awesome-machine-learning похож на вышерассмотренный awesome-python. Здесь можно найти массу ценных материалов по машинному обучению.

Итоги


Надеюсь, вы нашли среди вышеперечисленных репозиториев такие, материалы которых принесут вам пользу.

А у вас есть любимые GitHub-репозитории, которые вы могли бы порекомендовать другим?



Подробнее..

Перевод Секреты JavaScript-функций

20.09.2020 18:23:47 | Автор: admin
Каждый программист знаком с функциями. В JavaScript функции отличаются множеством возможностей, что позволяет называть их функциями высшего порядка. Но, даже если вы постоянно пользуетесь JavaScript-функциями, возможно, им есть чем вас удивить.



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

Чистые функции


Функция, которая соответствует двум следующим требованиям, называется чистой:

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

Рассмотрим пример:

function circleArea(radius){return radius * radius * 3.14}

Если этой функции передаётся одно и то же значение radius, она всегда возвращает один и тот же результат. При этом в ходе выполнения функции не изменяется ничего за её пределами, то есть у неё нет побочных эффектов. Всё это значит, что перед нами чистая функция.

Вот ещё один пример:

let counter = (function(){let initValue = 0return function(){initValue++;return initValue}})()

Испытаем эту функцию в консоли браузера.


Испытание функции в консоли браузера

Как видно, функция counter, реализующая счётчик, при каждом её вызове возвращает разные результаты. Поэтому её нельзя назвать чистой.

А вот ещё пример:

let femaleCounter = 0;let maleCounter = 0;function isMale(user){if(user.sex = 'man'){maleCounter++;return true}return false}

Здесь показана функция isMale, которая, при передаче ей одного и того же аргумента, всегда возвращает один и тот же результат. Но у неё есть побочные эффекты. А именно, речь идёт об изменении глобальной переменной maleCounter. В результате эту функцию чистой назвать нельзя.

Зачем нужны чистые функции?


Почему мы проводим границу между обычными и чистыми функциями? Дело в том, что у чистых функций есть множество сильных сторон. Их применение позволяет повысить качество кода. Поговорим о том, что нам даёт использование чистых функций.

1. Код чистых функций понятнее, чем код обычных функций, его легче читать


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

2. Чистые функции лучше поддаются оптимизации при компиляции их кода


Предположим, имеется такой фрагмент кода:

for (int i = 0; i < 1000; i++){console.log(fun(10));}

Если fun это функция, не являющаяся чистой, то во время выполнения этого кода данную функцию придётся вызвать в виде fun(10) 1000 раз.

А если fun это чистая функция, то компилятор сможет оптимизировать код. Он может выглядеть примерно так:

let result = fun(10)for (int i = 0; i < 1000; i++){console.log(result);}

3. Чистые функции легче тестировать


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

Вот простой пример. Чистая функция принимает в виде аргумента массив чисел и прибавляет к каждому элементу этого массива число 1, возвращая новый массив. Вот её сокращённое представление:

const incrementNumbers = function(numbers){// ...}

Для проверки такой функции достаточно написать модульный тест, напоминающий следующий:

let list = [1, 2, 3, 4, 5];assert.equals(incrementNumbers(list), [2, 3, 4, 5, 6])

Если функция не является чистой, то для её проверки нужно учесть множество внешних факторов, способных повлиять на её поведение. В результате протестировать подобную функцию будет сложнее, чем чистую.

Функции высшего порядка.


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

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

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

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

Если не пользоваться возможностями функций высшего порядка, то решение этой задачи может выглядеть так:

const arr1 = [1, 2, 3];const arr2 = [];for (let i = 0; i < arr1.length; i++) {arr2.push(arr1[i] * 2);}

Если же над задачей поразмыслить, то окажется, что у объектов типа Array в JavaScript есть метод map(). Этот метод вызывают в виде map(callback). Он создаёт новый массив, заполненный элементами массива, для которого его вызывают, обработанными с помощью переданной ему функции callback.

Вот как выглядит решение этой задачи с использованием метода map():

const arr1 = [1, 2, 3];const arr2 = arr1.map(function(item) {return item * 2;});console.log(arr2);

Метод map() это пример функции высшего порядка.

Правильное использование функций высшего порядка помогает улучшить качество кода. В следующих разделах этого материала мы ещё не раз вернёмся к таким функциям.

Кеширование результатов работы функций


Предположим, есть чистая функция, которая выглядит так:

function computed(str) {// Представим, что в этой функции проводятся ресурсозатратные вычисленияconsole.log('2000s have passed')// Представим, что тут возвращается результат вычисленийreturn 'a result'}

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

Как оснастить функцию кешем? Для этого можно написать особую функцию, которую можно использовать в качестве обёртки для целевой функции. Этой особой функции мы дадим имя cached. Данная функция принимает целевую функцию в виде аргумента и возвращает новую функцию. В функции cached можно организовать кеширование результатов вызова оборачиваемой ей функции с использованием обычного объекта (Object) или с помощью объекта, представляющего собой структуру данных Map.

Вот как может выглядеть код функции cached:

function cached(fn){// Создаёт объект для хранения результатов, возвращаемых после каждого вызова функции fn.const cache = Object.create(null);// Возвращает функцию fn, обёрнутую в кеширующую функцию.return function cachedFn (str) {// Если в кеше нет нужного результата - вызывается функция fnif ( !cache[str] ) {let result = fn(str);// Результат, возвращённый функцией fn, сохраняется в кешеcache[str] = result;}return cache[str]}}

Вот результаты экспериментов с этой функцией в консоли браузера.


Эксперименты с функцией, результаты работы которой кешируются

Ленивые функции


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

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

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

Её код может выглядеть так:

let fooFirstExecutedDate = null;function foo() {if ( fooFirstExecutedDate != null) {return fooFirstExecutedDate;} else {fooFirstExecutedDate = new Date()return fooFirstExecutedDate;}}

Каждый раз, когда вызывается эта функция, нужно проверить условие. Если это условие будет очень сложным, то вызовы такой функции приведут к падению производительности программы. Здесь-то мы и можем воспользоваться методикой создания ленивых функций для оптимизации кода.

А именно, функцию мы можем переписать так:

var foo = function() {var t = new Date();foo = function() {return t;};return foo();}

После первого вызова функции мы заменяем исходную функцию новой. Эта новая функция возвращает значение t, представленное объектом Date, созданное при первом вызове функции. В результате никаких условий при вызове подобной функции проверять не нужно. Такой подход способен улучшить производительность кода.

Это был очень простой условный пример. Давайте теперь рассмотрим нечто, более близкое к реальности.

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

function addEvent (type, el, fn) {if (window.addEventListener) {el.addEventListener(type, fn, false);}else if(window.attachEvent){el.attachEvent('on' + type, fn);}}

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

function addEvent (type, el, fn) {if (window.addEventListener) {addEvent = function (type, el, fn) {el.addEventListener(type, fn, false);}} else if(window.attachEvent){addEvent = function (type, el, fn) {el.attachEvent('on' + type, fn);}}addEvent(type, el, fn)}

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

Каррирование функций


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

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

Какая от этого польза?

  • Каррирование помогает избежать ситуаций, когда функцию нужно вызывать, снова и снова передавая ей один и тот же аргумент.
  • Эта техника помогает создавать функции высшего порядка. Она чрезвычайно полезна при обработке событий.
  • Благодаря каррированию можно организовать предварительную подготовку функций к выполнению неких действий, а потом с удобством многократно использовать такие функции в коде.

Рассмотрим простую функцию, складывающую передаваемые ей числа. Назовём её add. Она принимает три операнда в виде аргументов и возвращает их сумму:

function add(a,b,c){return a + b + c;}

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

add(1,2,3) --> 6add(1,2) --> NaNadd(1,2,3,4) --> 6 //Дополнительный параметр игнорируется.

Как каррировать такую функцию?

Вот код функции curry, которая предназначена для каррирования других функций:

function curry(fn) {if (fn.length <= 1) return fn;const generator = (...args) => {if (fn.length === args.length) {return fn(...args)} else {return (...args2) => {return generator(...args, ...args2)}}}return generator}

Вот результаты экспериментов с этой функцией в браузерной консоли.


Эксперименты с функцией curry в консоли браузера

Композиция функций


Предположим, надо написать функцию, которая, принимая на вход, например, строку bitfish, возвращает строку HELLO, BITFISH.

Как видно, эта функция выполняет две задачи:

  • Конкатенация строк.
  • Преобразование символов результирующей строки к верхнему регистру.

Вот как может выглядеть код такой функции:

let toUpperCase = function(x) { return x.toUpperCase(); };let hello = function(x) { return 'HELLO, ' + x; };let greet = function(x){return hello(toUpperCase(x));};

Поэкспериментируем с ней.


Испытание функции в консоли браузера

Эта задача включает в себя две подзадачи, оформленные в виде отдельных функций. В результате код функции greet получился достаточно простым. Если бы нужно было выполнить больше операций над строками, то функция greet содержала бы в себе конструкцию наподобие fn3(fn2(fn1(fn0(x)))).

Упростим решение задачи и напишем функцию, которая выполняет композицию других функций. Назовём её compose. Вот её код:

let compose = function(f,g) {return function(x) {return f(g(x));};};

Теперь функцию greet можно создать, прибегнув к помощи функции compose:

let greet = compose(hello, toUpperCase);greet('kevin');

Использование функции compose для создания новой функции на основе двух существующих подразумевает создание функции, вызывающей эти функции слева направо. В результате мы получаем компактный код, который легко читается.

Сейчас наша функция compose принимает лишь два параметра. А нам хотелось бы, чтобы она могла бы принимать любое количество параметров.

Подобная функция, способная принимать любое количество параметров, имеется в широко известной опенсорсной библиотеке underscore.

function compose() {var args = arguments;var start = args.length - 1;return function() {var i = start;var result = args[start].apply(this, arguments);while (i--) result = args[i].call(this, result);return result;};};

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

Применяете ли вы в своих JavaScript-проектах какие-то особенные способы работы с функциями?



Подробнее..

Перевод Введение в React, которого нам не хватало

21.09.2020 16:09:27 | Автор: admin
React это самая популярная в мире JavaScript-библиотека. Но эта библиотека не потому хороша, что популярна, а потому популярна, что хороша. Большинство существующих вводных руководств по React начинается с примеров того, как пользоваться этой библиотекой. Но эти руководства ничего не говорят о том, почему стоит выбрать именно React.

У такого подхода есть свои сильные стороны. Если кто-то стремится к тому, чтобы, осваивая React, тут же приступить к практике, ему достаточно заглянуть в официальную документацию и взяться за дело.



Этот материал (вот, если интересно, его видеоверсия) написан для тех, кто хочет найти ответ на следующие вопросы: Почему React? Почему React работает именно так? С какой целью API React устроены так, как устроены?.

Почему React?


Жизнь становится проще в том случае, если компоненты не знают об обмене данными по сети, о бизнес-логике приложения или о его состоянии. Такие компоненты, получая одни и те же входные параметры, всегда формируют одни и те же визуальные элементы.

Когда появилась библиотека React это на фундаментальном уровне изменило то, как работают JavaScript-фреймворки и библиотеки. В то время как другие подобные проекты продвигали идеи MVC, MVVM и прочие подобные, в React был выбран другой подход. А именно, тут рендеринг визуальной составляющей приложения был изолирован от представления модели. Благодаря React во фронтенд-экосистеме JavaScript появилась совершенно новая архитектура Flux.

Почему команда разработчиков React поступила именно так? Почему такой подход лучше тех, что появились раньше него, вроде архитектуры MVC и спагетти-кода, который пишут на jQuery? Если вы из тех, кого интересуют эти вопросы, можете посмотреть это выступление 2013 года, посвящённое разработке JavaScript-приложений в Facebook.

В 2013 году компания Facebook только что завершила серьёзную работу по интеграции в свою платформу чата. Эта новая возможность была встроена практически в каждую страницу проекта, чат влиял на обычные сценарии работы с платформой. Это было сложное приложение, встроенное в другое приложение, которое и до этого нельзя было назвать простым. Команде Facebook пришлось столкнуться с решением нетривиальных задач, справляясь с неконтролируемой мутацией DOM и с необходимостью обеспечить параллельную асинхронную работу пользователей в новой среде.

Например, как заранее узнать о том, что будет выведено на экране в ситуации, когда что угодно, в любое время и по любой причине, может обратиться к DOM и внести туда изменения? Как обеспечить правильность построения того, что увидит пользователь?

Используя популярные фронтенд-инструменты, существовавшие до React, ничего такого гарантированно обеспечить было нельзя. В ранних веб-приложениях состояние гонок в DOM было одной из самых распространённых проблем.

Отсутствие детерминизма = параллельные вычисления + мутабельное состояние.

Мартин Одерски


Главной задачей команды разработки React было решение этой проблемы. Они с ней справились, применив два основных инновационных подхода:

  • Однонаправленная привязка данных с использованием архитектуры Flux.
  • Иммутабельность состояния компонента. После того, как состояние компонента установлено, оно уже не может быть изменено. Изменения состояния не затрагивают визуализированные компоненты. Вместо этого подобные изменения приводят к выводу нового представления, обладающего новым состоянием.

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

Том Оччино, JSConfUS 2013


Библиотека React смогла серьёзно снизить остроту проблемы неконтролируемых мутаций благодаря использованию архитектуры Flux. Вместо того чтобы присоединять к произвольному количеству произвольных объектов (моделей) обработчики событий, вызывающие обновления DOM, библиотека React дала разработчикам единственный способ управления состоянием компонента. Это диспетчеризация действий, влияющих на хранилище данных. Когда меняется состояние хранилища, система предлагает компоненту перерендериться.


Архитектура Flux

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

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

Если бы детерминированный рендеринг компонентов был бы единственной фишкой React, то одно это уже было бы замечательной инновацией. Но команда разработчиков React на этом не остановилась. Эта команда представила миру библиотеку, обладающую и другими интереснейшими, уникальными возможностями. А по мере развития проекта в React появилось ещё больше всего полезного.

JSX


JSX это расширение JavaScript, позволяющее декларативно создавать компоненты пользовательского интерфейса. JSX обладает следующими заметными возможностями:

  • Применение простой декларативной разметки.
  • Код разметки расположен там же, где и код компонента.
  • Реализация принципа разделения ответственностей (например отделение описания интерфейса от логики состояния и от побочных эффектов). Причём, реализация, основанная не на использовании различных технологий (например HTML, CSS, JavaScript).
  • Абстрагирование управления изменениями DOM.
  • Абстрагирование от особенностей различных платформ, для которых создают React-приложения. Дело в том, что благодаря использованию React можно создавать приложения, предназначенные для множества платформ (речь идёт, например, о разработке для мобильных устройств с использованием React Native, о приложениях для систем виртуальной реальности, о разработке для Netflix Gibbon, о создании Canvas/WebGL-интерфейсов, о проекте react-html-email).

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

В наши дни, если взглянуть на разные фронтенд-инструменты, окажется, что без специального синтаксиса, вроде директивы *ngFor из Angular, тоже не обойтись. Но, так как JSX можно назвать надмножеством JavaScript, создавая JSX-разметку можно пользоваться существующими возможностями JS.

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

Вот пример JSX-кода:

const ItemList = ({ items }) => (<ul>{items.map((item) => (<li key={item.id}><div>{item.name}</div></li>))}</ul>);

Правда, при работе с JSX нужно учитывать некоторые особенности, которые, поначалу, могут показаться непривычными.

  • Тут используется подход к именованию атрибутов элементов, отличающийся от того, который принят в HTML. Например, class превращается в className. Речь идёт о применении стиля именования camelCase.
  • У каждого элемента списка, который нужно вывести, должен быть постоянный уникальный идентификатор, предназначенный для использования в JSX-атрибуте key. Значение идентификатора должно оставаться неизменным в ходе различных манипуляций с элементами списка. На практике большинство элементов списков в моделях данных имеют уникальные id, эти идентификаторы обычно отлично показывают себя в роли значений для key.

React не навязывает разработчику единственно правильный способ работы с CSS. Например, компоненту можно передать JavaScript-объект со стилями, записав его в свойство style. При таком подходе большинство привычных имён стилей будет заменено на их эквиваленты, записанные по правилам camelCase. Но этим возможности по работе со стилями не ограничиваются. На практике я одновременно пользуюсь разными подходами к стилизации React-приложений. Выбор конкретного подхода зависит от того, что именно нужно стилизовать. Например, глобальные стили я применяю для оформления тем приложений и макетов страниц, а локальные стили для настройки внешнего вида конкретного компонента.

Вот мои любимые возможности React, касающиеся работы со стилями:

  • CSS-файлы, которые можно загружать в заголовочной части страницы. Они могут использоваться для настройки макетов страниц, шрифтов и прочих подобных элементов. Это надёжный, работоспособный механизм стилизации.
  • CSS-модули это CSS-файлы область применения которых ограничена локальной областью видимости. Их можно импортировать непосредственно в JavaScript-файлы. Для того чтобы применять CSS-модули, нужно воспользоваться правильно настроенным загрузчиком модулей. В Next.js, например, этот механизм активирован по умолчанию.
  • Пакет styled-jsx, который позволяет объявлять стили прямо в коде React-компонентов. Это напоминает использование тега <style> в HTML. Область видимости таких стилей можно назвать гиперлокальной. Речь идёт о том, что стили воздействуют только на элементы, к которым они применяются, и на их дочерние элементы. При применении Next.js пакетом styled-jsx можно пользоваться без необходимости самостоятельно что-то подключать и настраивать.

Синтетические события


React даёт в наше распоряжение кроссбраузерную обёртку SyntheticEvents, представляющую синтетические события и предназначенную для унификации работы с событиями DOM. Синтетические события весьма полезны по нескольким причинам:

  1. Они позволяет унифицировать особенности различных платформ, связанные с обработкой событий. Это упрощает разработку кроссбраузерных приложений.
  2. Они автоматически решают задачи по управлению памятью. Если вы, например, собираетесь создать некий список с бесконечной прокруткой, пользуясь лишь чистыми JavaScript и HTML, то вам придётся делегировать события или подключать и отключать обработчики событий по мере появления и скрытия элементов списка. Всё это нужно будет делать для того чтобы избежать утечек памяти. Синтетические события автоматически делегируются корневому узлу, что приводит к тому, что React-разработчикам не приходится решать задачи по управлению памятью.
  3. В их работе используются пулы объектов. Механизмы поддержки синтетических событий способны генерировать тысячи объектов в секунду и организовывать высокопроизводительную работу с такими объектами. Если решать подобные задачи, каждый раз создавая новые объекты, это приведёт к частой потребности в вызове сборщика мусора. А это, в свою очередь, может привести к замедлению программы, к видимым задержкам в работе пользовательского интерфейса и анимаций. Объекты синтетических событий создаются заранее и помещаются в пул объектов. Когда надобности в событии нет, оно возвращается обратно в пул. В результате разработчик может не беспокоиться о том, что сборщик мусора заблокирует главный поток JavaScript, очищая память от ставших ненужными объектов.

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

Жизненный цикл компонента


Концепция жизненного цикла React-компонентов ориентирована на защиту состояния компонента. Состояние компонента не должно меняться в процессе его вывода на экран. Это достигается благодаря следующей схеме работы: компонент оказывается в некоем состоянии и рендерится. Затем, благодаря событиям жизненного цикла, оказывается возможным применение к нему эффектов, можно воздействовать на его состояние, работать с событиями.

Понимание особенностей жизненного цикла компонентов React крайне важно для того чтобы разрабатывать интерфейсы и при этом не сражаться с React, а пользоваться этой библиотекой так, как задумано её разработчиками. Сражение с React, вроде неправильного изменения состояния компонентов или чтения данных из DOM, сводит на нет сильные стороны этой библиотеки.

В React, начиная с версии 0.14, появился синтаксис описаний компонентов, основанных на классах, позволяющий обрабатывать события жизненного цикла компонентов. В жизненном цикле React-компонентов можно выделить три важнейших этапа: Mount (монтирование), Update (обновление) и Unmount (размонтирование).


Жизненный цикл компонента

Этап Update можно разделить на три части: Render (рендеринг), Precommit (подготовка к внесению изменений в дерево DOM), Commit (внесение изменений в дерево DOM).


Структура этапа Update

Остановимся на этих этапах жизненного цикла компонента подробнее:

  • Render на этом этапе жизненного цикла компонента производится его рендеринг. Метод компонента render() должен представлять собой детерминированную функцию, не имеющую побочных эффектов. Эту функцию стоит рассматривать как чистую функцию, получающую данные из входных параметров компонента и возвращающую JSX.
  • Precommit на этом этапе можно прочитать данные из DOM, пользуясь методом жизненного цикла компонента getSnapShotBeforeUpdate. Это может оказаться очень кстати, например, если перед повторным рендерингом компонента нужно узнать нечто вроде позиции скроллинга или размеров визуализированного элемента.
  • Commit на этой фазе жизненного цикла компонента React обновляет DOM и рефы. Здесь можно воспользоваться методом componentDidUpdate или хуком useEffect. Именно здесь можно выполнять эффекты, планировать обновления, использовать DOM и решать другие подобные задачи.

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


Жизненный цикл React-компонентов

Я полагаю, что представление компонентов в виде долгоживущих классов это не самая лучшая ментальная модель React. Помните о том, что состояние React-компонентов не должно мутировать. Устаревшее состояние должно заменяться на новое. Каждая такая замена вызывает повторный рендеринг компонента. Это даёт React его, пожалуй, самую главную и самую ценную возможность: поддержку детерминированного подхода к созданию визуальных представлений компонентов.

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

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

  • Получая одни и те же входные данные, они всегда возвращают одни и те же выходные данные (они являются детерминированными).
  • У них нет побочных эффектов (то есть они не работают с сетевыми ресурсами, не выводят что-либо в консоль, не записывают ничего в localStorage и так далее).

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

Хуки React


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

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

Хук useEffect позволяет ставить побочные эффекты в очередь для их последующего выполнения. Они будут вызываться в подходящее время жизненного цикла компонента. Это время может настать сразу после монтирования компонента (например при вызове метода жизненного цикла componentDidMount), во время фазы Commit (метод componentDidUpdate), непосредственно перед размонтированием компонента (componentWillUnmount).

Обратили внимание на то, что с одним хуком связано целых три метода жизненного цикла компонента? Дело тут в том, что хуки позволяют объединять связанную логику, а не раскладывать её, как было до них, по разным методам жизненного цикла компонента.

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

Вот что дают нам хуки React:

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

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

Компоненты-контейнеры и презентационные компоненты


Я, стремясь улучшить модульность компонентов и их пригодность для многократного использования, ориентируюсь на разработку компонентов двух видов:

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

Чистые компоненты не следует отождествлять с базовым классом React.PureComponent, который назван именно так из-за того, что его небезопасно использовать для создания компонентов, не являющихся чистыми.

Презентационные компоненты


Рассмотрим особенности презентационных компонентов:

  • Они не взаимодействуют с сетевыми ресурсами.
  • Они не сохраняют данные в localStorage и не загружают их оттуда.
  • Они не выдают неких непредсказуемых данных.
  • Они не обращаются напрямую к текущему системному времени (например, путём вызова метода Date.now()).
  • Они не взаимодействуют напрямую с хранилищем состояния приложения.
  • Они могут использовать локальное состояние компонента для хранения чего-то наподобие данных, введённых в формы, но при этом они должны поддерживать возможность приёма таких данных и задания на их основе своего исходного состояния, что облегчает их тестирование.

Именно из-за последнего пункта этого списка я, говоря о презентационных компонентах, упомянул о том, что это, по большей части, чистые компоненты. Эти компоненты считывают своё состояние из глобального состояния React. Поэтому хуки вроде useState и useReducer дают в их распоряжение неявным образом определённые данные (то есть данные, не описанные в сигнатуре функции), что, с технической точки зрения, не позволяет назвать такие компоненты чистыми. Если нужно, чтобы они были бы по-настоящему чистыми, можно делегировать все задачи по управлению состоянием компоненту-контейнеру, но я полагаю, что делать этого не стоит, по крайней мере, до тех пор, пока правильность работы компонента можно проверить с помощью модульных тестов.

Лучшее враг хорошего.

Вольтер


Компоненты-контейнеры


Компоненты-контейнеры это такие компоненты, которые отвечают за управление состоянием, за выполнение операций ввода-вывода и за решение любых других задач, которые можно отнести к побочным эффектам. Они не должны самостоятельно рендерить некую разметку. Вместо этого они делегируют задачу рендеринга презентационным компонентам, а сами служат обёрткой для таких компонентов. Обычно компонент-контейнер в React+Redux-приложении просто вызывает mapStateToProps() и mapDispatchToProps(), после чего передаёт соответствующие данные презентационным компонентам. Контейнеры, кроме того, могут использоваться для решения некоторых задач общего характера, о которых мы поговорим ниже.

Компоненты высшего порядка


Компонент высшего порядка (Higher Order Component, HOC) это компонент, который принимает другие компоненты и возвращает новый компонент, реализующий новый функционал, основанный на исходных компонентах.

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

В отличие от хуков React и от механизма render props, компоненты высшего порядка поддаются композиции с использованием стандартного подхода к композиции функций. Это позволяет декларативно описывать результаты композиции возможностей, предназначенных для использования в разных местах приложения. При этом готовые компоненты не должны знать о существовании тех или иных возможностей. Вот пример HOC с EricElliottJS.com:

import { compose } from 'lodash/fp';import withFeatures from './with-features';import withEnv from './with-env';import withLoader from './with-loader';import withCoupon from './with-coupon';import withLayout from './with-layout';import withAuth from './with-auth';import { withRouter } from 'next/router';import withMagicLink from '../features/ethereum-authentication/with-magic-link';export default compose(withEnv,withAuth,withLoader,withLayout({ showFooter: true }),withFeatures,withRouter,withCoupon,withMagicLink,);

Тут показана смесь множества возможностей, совместно используемых всеми страницами сайта. А именно, withEnv читает настройки из переменных окружения, withAuth реализует механизм GitHub-аутентификации, withLoader показывает анимацию во время загрузки данных пользователя, withLayout({ showFooter: true }) выводит стандартный макет с подвалом, withFeature показывает настройки, withRouter загружает маршрутизатор, withCoupon отвечает за работу с купонами, а withMagicLing поддерживает аутентификацию пользователей без пароля с использованием Magic.

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

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

import LessonPage from '../features/lesson-pages/lesson-page.js';import pageHOC from '../hocs/page-hoc.js';export default pageHOC(LessonPage);

У подобных компонентов высшего порядка есть альтернатива, но она представляет собой сомнительную конструкцию, называемую pyramid of doom (пирамида погибели) и ей лучше не пользоваться. Вот как это выглядит:

import FeatureProvider from '../providers/feature-provider';import EnvProvider from '../providers/env-provider';import LoaderProvider from '../providers/loader-provider';import CouponProvider from '../providers/coupon-provider';import LayoutProvider from '../providers/layout-provider';import AuthProvider from '../providers/auth-provider';import RouterProvider from '../providers/RouterProvider';import MagicLinkProvider from '../providers/magic-link-provider';import PageComponent from './page-container';const WrappedComponent = (...props) => (<EnvProvider { ...props }><AuthProvider><LoaderProvider><LayoutProvider showFooter={ true }><FeatureProvider><RouterProvider><CouponProvider><MagicLinkProvider><YourPageComponent /></MagicLinkProvider></CouponProvider></RouterProvider></FeatureProvider></LayoutProvider></LoaderProvider></AuthProvider></EnvProvider>);

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

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

Итоги


  • Почему React? React даёт нам детерминированный рендеринг визуальных представлений компонентов, в основе которого лежит однонаправленная привязка данных и иммутабельное состояние компонентов.
  • JSX даёт нам возможность простого декларативного описания интерфейсов в JavaScript-коде.
  • Синтетические события сглаживают кросс-платформенные различия систем обработки событий и облегчают управление памятью.
  • Концепция жизненного цикла компонентов направлена на защиту состояния компонентов. Жизненный цикл компонента состоит из фаз монтирования, обновления и размонтирования. Фаза обновления состоит из фазы рендеринга, фазы подготовки к внесению изменений в DOM и фазы внесения изменений в DOM.
  • Хуки React позволяют подключаться к методам жизненного цикла компонентов без использования синтаксиса, основанного на классах. Применение хуков, кроме того, облегчает совместное использование одного и того же кода в разных компонентах.
  • Компоненты-контейнеры и презентационные компоненты позволяют отделить задачи формирования визуального представления интерфейсов от задач по управлению состоянием приложения и от побочных эффектов. Это улучшает возможности по многократному использованию и тестированию компонентов и бизнес-логики приложения.
  • Компоненты высшего порядка упрощают совместное использование возможностей, представляющих собой композицию других возможностей. При этом компонентам не нужно знать об этих возможностях (и не нужно, чтобы компоненты были бы тесно связаны с ними).

Что дальше?


В этом материале о React мы затронули множество концепций функционального программирования. Если вы стремитесь к глубокому пониманию принципов разработки React-приложений, вам полезно будет освежить свои знания о чистых функциях, об иммутабельности, о каррировании и частичном применении функций, о композиции функций. Соответствующие материалы вы можете найти на EricElliottJS.com.

Я рекомендую использовать React совместно с Redux, Redux-Saga и RITEway. Redux рекомендуется использовать совместно с Autodux и Immer. Для организации сложных схем работы с состоянием можно попробовать воспользоваться Redux-DSM.

Когда вы разберётесь с основами и будете готовы к созданию реальных React-приложений, обратите внимание на Next.js и Vercel. Эти инструменты помогут автоматизировать настройку системы сборки проекта и CI/CD-конвейера, с их помощью можно подготовить проект к оптимизированному развёртыванию на сервере. Они дают тот же эффект, что и целая команда DevOps-специалистов, но пользоваться ими можно совершенно бесплатно.

Какие вспомогательные инструменты вы применяете при разработке React-приложений?



Подробнее..

Перевод Первое знакомство с Moon.js

24.09.2020 16:22:36 | Автор: admin
Сегодня речь пойдёт об очередной JavaScript-библиотеке, предназначенной для разработки интерфейсов. Возникает такое ощущение, что такие библиотеки появляются всё чаще и чаще. В этом материале мы рассмотрим библиотеку Moon.js и раскроем её особенности, о которых нужно знать для того чтобы приступить к работе с ней. В частности, мы поговорим о том, как создавать новые Moon.js-проекты, о том, как создавать элементы интерфейсов, как обрабатывать события. Освоив это руководство, вы сможете пользоваться Moon.js для разработки собственных приложений.



Библиотека Moon.js


Moon.js это минималистичная JavaScript-библиотека, предназначенная для разработки быстрых и функциональных интерфейсов. Она имеет сравнительно небольшие размеры, что позволяет создавать на её основе достаточно компактные приложения. Библиотека отличается очень высокой производительностью

В Moon.js используется подход к проектированию интерфейсов, основанный на компонентах. Для создания компонентов применяются шаблоны. Эта библиотека весьма похожа на Vue.js.

Сильные стороны Moon.js


  • Moon.js отличается компактными размерами (в минифицированном и сжатом виде это около 2 Кб). Это меньше, чем размеры других библиотек и фреймворков вроде React и Angular.
  • Эта библиотека отличается высокой скоростью рендеринга интерфейсов.
  • Moon.js это библиотека, основанная на функциональных методах разработки. При работе с ней используется подход к проектированию интерфейсов, основанный на так называемых драйверах.

Начало работы


Библиотеку Moon.js можно включить в проект двумя способами. Первый заключается в установке её из NPM. Второй в её подключении непосредственно к странице, на которой её планируется использовать.

Если решено воспользоваться NPM-вариантом библиотеки, то сначала нужно будет установить пакет moon-cli, инструмент командной строки:

$ npm i moon-cli -g

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

Для создания проекта, основанного на Moon.js, можно выполнить следующую команду:

$ moon create moon-prj

Эта команда создаёт новый проект в папке moon-prj. После того, как будет завершено создание проекта, в вашем распоряжении окажется основа будущего приложения.

Второй вариант использования Moon.js предусматривает её подключение к странице, на которой её планируется использовать. У библиотеки есть модуль moon-browser, который позволяет пользоваться её возможностями непосредственно на странице, к которой она подключена.

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

<script src="http://personeltest.ru/aways/unpkg.com/moon"></script><script src="http://personeltest.ru/aways/unpkg.com/moon-browser"></script>

Как видите, соответствующие скрипты загружаются с CDN unpkg. В первом теге импортируется основная библиотека. Во втором библиотека moon-browser. Она отвечает за компиляцию шаблонов Moon.js, за приведение их к виду, пригодному для вывода браузером.

Теперь, для того чтобы воспользоваться синтаксическими конструкциями Moon.js на странице, нужно будет включить их в тег <script>, не забыв задать его атрибут type как text/moon.

<!-- Подключение к странице внешнего скрипта --><script src="./main-script.js" type="text/moon"></script><!-- Описание интерфейса в коде, встроенном в страницу --><script type="text/moon">...</script>

Подключение Moon.js-приложения к странице


Moon.js, как и другие библиотеки и фреймворки, используемые для создания одностраничных приложений, подключается к определённому элементу страницы. Обычно роль контейнера для Moon.js-приложения играет элемент <div>:

<div id="root"></div>

Подобный элемент, являющийся корневым элементом Moon.js-приложения, размещают в коде файла index.html, представляющего собой точку входа в проект.

Для подключения Moon.js-приложения к этому элементу используется драйвер view (ниже мы поговорим о драйверах подробнее):

Moon.use({view: Moon.view.driver("#root")})

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

Moon.use({view: Moon.view.driver(document.getElementById("root"))})

Теперь поговорим о том, как в Moon.js организована работа с данными, и о том, как с помощью этой библиотеки создавать элементы интерфейсов.

Синтаксис описания элементов интерфейса


Для описания Moon.js-интерфейсов используется язык программирования Moon View Language (MVL), который был разработан специально для решения данной задачи. Он напоминает JSX. Этот язык применяется для описания элементов и для настройки их взаимоотношений. Вот пример:

<script type="text/moon">function aView(data) {return (<div>Hi from Moon</div>)}</script>

Несложно заметить то, что в этом фрагменте Moon.js-кода, ответственного за формирование элемента <div>, используются синтаксические структуры, напоминающие HTML. Но эти структуры используются в JavaScript-коде. Такой JavaScript-код браузер выполнить не сможет, но это от него и не требуется, так как Moon.js компилирует подобные конструкции в обычный JavaScript.

Работа с данными


В Moon.js для управления визуальными представлениями элементов и для работы с данными используется концепция драйверов. Здесь мы взглянем на драйвер, позволяющий работать с данными, а в следующем разделе поговорим о драйвере, предназначенном для работы с элементами интерфейса.

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

Задать начальные данные Moon.js-приложения можно с помощью API Moon.use:

Moon.use({data: Moon.data.driver})

Записывать новые данные в состояния можно, возвращая их из соответствующих функций:

Moon.run(({ data }) => {console.log(data) // undefinedreturn {data: "Nnamdi"}})

API Moon.run отвечает за запуск приложения. Коллбэк, переданный этому API, получает ссылку на глобальные данные в аргументе data. Так как на момент вызова этой функции в data пока ничего нет, команда console.log из этого примера выведет undefined.

Мы возвращаем из коллбэка объект, у которого имеется свойство data со значением Nnamdi. Этот объект будет представлять собой новое состояние приложение, данными которого смогут воспользоваться любые другие функции, обращающиеся к data.

Механизм работы с данными в Moon.js мы рассмотрели. Теперь подробнее поговорим о работе с элементами интерфейса.

Работа с элементами интерфейса


В Moon.js имеется драйвер view, который предназначен для создания элементов и для монтирования их в DOM.

Мы уже рассматривали фрагмент кода, повторённый ниже, в котором к элементу <div> подключается базовый элемент Moon.js:

Moon.use({view: Moon.view.driver("#root")})

Именно тут производится монтирование элемента. Теперь функции могут возвращать элементы, которые способны заменять старые элементы. Их можно представлять в виде объектов, содержащих свойство view, в которое записаны соответствующие данные. Библиотека берёт значение свойства view из возвращаемого функцией объекта и записывает его в элемент, подключённый к элементу с идентификатором root.

В Moon.js используется концепция виртуальной DOM и мощный алгоритм сравнения старой и новой версий интерфейса. Это позволят библиотеке принимать решения о том, когда нужно обновлять DOM, и о том, какие именно части DOM нуждаются в обновлении.

function handleClick() {return {};}Moon.run(() => ({view: <button @click=handleClick>Click Me!</button>}));

Здесь коллбэк, передаваемый Moon.run, выводит в DOM кнопку. Происходит это из-за того, что функция возвращает объект со свойством view. Значение, назначенное этому свойству, попадает в DOM.

У кнопки имеется обработчик события click, представленный функцией handleClick. Эта функция возвращает пустой объект, её вызов не приводит к внесению изменений в DOM.

Создание элементов


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

const { div, text, node, p } = Moon.view.m

Moon.js экспортирует функции, имена которых соответствуют именам создаваемых с их помощью элементов. Так, функция div позволяет создавать элементы <div>. Функция text создаёт текстовые узлы. Функция node позволяет создавать пользовательские элементы. Функция p создаёт элементы <p>. Как видите, имена этих функций ясно указывают на их предназначение.

Создадим элемент <div>:

const Div = div({});

Назначать элементам атрибуты можно, передавая соответствующей функции объект со свойствами:

const Div = div({class: "DivClass"});

Здесь мы описали элемент <div>, в атрибут class которого должно быть записано значение DivClass.

Вот как создать текстовый элемент:

const Text = text({ data: "A text node" });

В свойстве data объекта, переданного функции text, имеется текст для элемента.

Создадим пользовательский элемент:

const CustomEl = node("custom-el");

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

CustomEl({ "attr": "attr-value"})

События


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

function handleClick() {return {};}Moon.run(() => ({view: <button @click=handleClick>Click Me!</button>}));

В результате на страницу будет выведена кнопка с текстом Click Me, по нажатию на которую будет вызвана функция handleClick.

Компоненты


В Moon.js функции это компоненты. Это означает, что функции можно упоминать в описании элементов интерфейса. То, что возвращает функция, будет включено в состав элемента.

Предположим, у нас есть такая функция:

function aView({ data }) {return <div>A View</div>}

Эта функция, aView, возвращает элемент, который может быть отрендерен:

Moon.run(() => {view: <div><aView /></div>})

Имя функции в этом примере используется в роли имени элемента. В результате выполнения этого кода окажется, что то, что возвращает функция, будет помещено в тег <div>. Когда всё это попадёт в DOM, там окажется такая разметка:

<div><div>A View</div></div>

Разработка приложений, основанных на Moon.js


Для того чтобы собрать воедино всё то, о чём мы только что говорили, давайте создадим на Moon.js простое TODO-приложение. Здесь мы воспользуемся соответствующим примером, который подготовлен разработчиками Moon.js.

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

Вот как выглядит страница этого приложения.


Страница приложения

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

Начнём работу с создания файла index.html. Здесь мы подключим Moon.js непосредственно к странице:

<html><body><div id="root"></div></body><script src="http://personeltest.ru/aways/unpkg.com/moon"></script><script src="http://personeltest.ru/aways/unpkg.com/moon-browser"></script><!-- Воспользуемся скриптом, встроенным в страницу --><script type="text/moon">function viewTodos({data, view}) {return (<div><input type="text" value=data.todo @input=updateTodo/><button @click=createTodo>Create</button><ul children=(data.todos.map(todo =><li>{todo}</li>))/></div>)}function updateTodo({ data, view }) {const dataNew = { ...data, todo: view.target.value };return {data: dataNew,view: <viewTodos data=dataNew/>}}function createTodo({ data }) {const dataNew = {todo: "",todos: [...data.todos, data.todo]};return {data: dataNew,view: <viewTodos data=dataNew/>}}<!-- Настройка драйверов data и view -->Moon.use({data: Moon.data.driver,view: Moon.view.driver("#root")})<!-- Запуск приложения -->Moon.run(() => {data: [],view: <viewTodos data=[]>})</script></html>

Функция viewTodos выводит элементы, необходимые для ввода сведений о новых делах и для вывода их в виде списка. Её аргументами являются data и view.

Функция createTodo создаёт новое дело и возвращает его в свойстве data возвращаемого ей объекта.

Функция updateTodo записывает новое дело в состояние приложения.

Обратите внимание на обработчики событий @click и @input, которые имеются в функции viewTodos. Событие @input вызывается при вводе текста, описывающего дело, в соответствующее поле. При обработке этого события вызывается функция updateTodo. Аргумент view в этой функции представляет произошедшее событие. Пользуясь им, мы обращаемся к DOM и получаем данные, введённые в поле. Затем эти данные попадают в состояние в виде свойства todo.

Событие @click вызывается после нажатия на кнопку. Оно выполняет запись нового дела в список дел. Для решения этой задачи используется функция createTodo. Эта функция обращается к свойству состояния todo и записывает новые данные в свойство todos, после чего, в свойстве view возвращаемого ей объекта, возвращает элемент <viewTodos>, представленный соответствующей функцией, в атрибут data которого записано значение dataNew.

Это приведёт к выполнению повторного рендеринга viewTodos и к обновлению DOM. В список дел, выводимый на странице, будет добавлено новое дело.

Запустите это приложение в браузере и поэкспериментируйте с ним.

Итоги


Мы разобрали основы Moon.js. А именно, поговорили о том, что пользоваться библиотекой можно, устанавливая её из NPM и подключая к страницам напрямую. Далее, мы обсудили внутренние механизмы библиотеки: работу с данными, обработку событий, разработку компонентов.

Мне кажется, что Moon.js это приятная библиотека. И, если даже не говорить о других её достоинствах, она мне симпатична из-за её компактного размера.

Пользовались ли вы Moon.js?



Подробнее..

Из песочницы Делаем модальные окна для сайта. Заботимся об удобстве и доступности

18.09.2020 12:15:01 | Автор: admin

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


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


В итоге было задумано сделать собственное простое решение.



Вообще говоря, есть несколько готовых скриптов, JavaScript библиотек, реализующих функционал модальных окон, например:


  • Arctic Modal,
  • jquery-modal,
  • iziModal,
  • Micromodal.js,
  • tingle.js,
  • Bootstrap Modal (из библиотеки Bootstrap) и др.

(в статье не рассматриваем решения на базе Frontend-фреймворков)


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


Что мы ждём от модальных окон? Отвечая на этот вопрос, я основывался на докладе Знакомьтесь, модальное окно Анны Селезнёвой, а так-же на относительно старой статье NikoX arcticModal jQuery-плагин для модальных окон.


Итак, чтобы нам хотелось видеть?


  • Окна должны открываться как можно быстрее, без тормозов браузера, с возможностью анимировать открытие и закрытие.
  • Под окном должен быть оверлей. Клик/тап по оверлею должен закрывать окно.
  • Страница под окном не должна прокручиваться.
  • Окон может быть несколько. Открытие одного определенного окна должно осуществляться кликом на любой элемент страницы с data-атрибутом, который мы выберем.
  • Окно может быть длинным прокручиваемым.
  • Желательно поработать над доступностью, а также с переносом фокуса внутрь окна и обратно.
  • Должно работать на IE11+

Дисклеймер: Прежде чем мы рассмотрим подробности, сразу дам ссылку на готовый код получившейся библиотеки (HystModal) на GitHub, а также ссылку на демо+документацию.


Начнём с разметки.


1. Разметка HTML и CSS


1.1. Каркас модальных окон


Как открыть окно быстро? Самое простое решение: разместить всю разметку модального окна сразу в HTML странице. Затем скрывать/показывать это окно при помощи переключения классов CSS.


Набросаем такую разметку HTML (я назвал этот скрипт hystmodal):


<div class="hystmodal" id="myModal">    <div class="hystmodal__window">        <button data-hystclose class="hystmodal__close">Close</button>          Текст модального окошка.        <img src="img/photo.jpg" alt="Изображение в окне" />    </div></div>

Итак, разместим перед закрывающим тегом </body> наш блок с окном (.hystmodal). Он будет фоном. Удобно указать уникальный атрибут id (например #myModal) каждому окну (ведь их у нас может быть несколько).


Сделаем так, чтобы .hystmodal растягивался на всё окно браузера и закрывал собой содержимое страницы. Чтобы этого добиться, установим фиксированное позиционирование в CSS и приравняем свойства top, bottom, left и right к нулю.


.hystmodal {    position: fixed;    top: 0;    bottom: 0;    right: 0;    left: 0;    overflow: hidden;    overflow-y: auto;    -webkit-overflow-scrolling: touch;    display: flex;    flex-flow: column nowrap;    justify-content: center; /* см. ниже */    align-items: center;    z-index: 99;    /* Чтобы окно не прилипало к границе    браузера установим отступы */    padding:30px 0;}

В этом коде сделаны ещё две вещи:


  1. Так как мы хотим центрировать окно внутри страницы, превращаем .hystmodal в flex-контейнер с выравниваем его потомков по центру по вертикали и горизонтали.
  2. Окно может быть больше высоты экрана браузера, поэтому мы устанавливаем overflow-y: auto, чтобы при переполнении возникала полоса прокрутки. Также, для сенсорных экранов (в основном для Safari) нам стоит установить свойство -webkit-overflow-scrolling: touch, чтобы сенсорная прокрутка работала именно на этом блоке а не на странице.

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


.hystmodal__window {    background: #fff;    /* Установим по умолчанию ширину 600px    но она будет не больше ширины браузера */    width: 600px;    max-width: 100%;    /* Заготовка для будущих анимаций */    transition: transform 0.15s ease 0s, opacity 0.15s ease 0s;    transform: scale(1);}

Кажется возникли сложности.


Проблема 1. Если высота окна больше высоты окна браузера, то контент окна будет обрезан сверху.



Это возникает из-за свойства justify-content: center. Оно позволяет нам удобно выровнять потомков по основной оси (по вертикали), но если потомок становится больше родителя то часть его становится недоступной даже при прокручиваемом контейнере. Подробнее можно посмотреть на stackoverflow. Решение установить justify-content: flex-start, а потомку установить margin:auto. Это выровняет его по центру.


Проблема 2. В ie-11 если высота окна больше высоты окна браузера, то фон окна обрезается.


Решение: мы можем установить flex-shrink:0 потомку тогда обрезки не происходит.


Проблема 3. В браузерах кроме Chrome нет отступа от нижней границы окна (т.е. padding-bottom не сработал).


Сложно сказать баг это браузеров или наоборот соответствует спецификации, но решения два:


  • установить псевдоэлемент ::after после потомка и дать ему высоту вместо padding
  • обернуть элемент в дополнительный блок и дать отступы уже ему.

Воспользуемся вторым методом. Добавим обертку .hystmodal__wrap. Так мы заодно обойдём и проблему 1, а вместо padding у родителя установим margin-top и margin-top у самого .hystmodal__window.


Наш итоговый html:


<div class="hystmodal" id="myModal" aria-hidden="true" >    <div class="hystmodal__wrap">        <div class="hystmodal__window" role="dialog" aria-modal="true" >            <button data-hystclose class="hystmodal__close">Close</button>              <h1>Заголовок модального окна</h1>            <p>Текст модального окна ...</p>            <img src="img/photo.jpg" alt="Изображение" width="400" />            <p>Ещё текст модального окна ...</p>        </div>    </div></div>

В код также добавлены некоторые aria и role атрибуты для обеспечения доступности.


Обновленный код CSS для обертки и окна.


.hystmodal__wrap {    flex-shrink: 0;    flex-grow: 0;    width: 100%;    min-height: 100%;    margin: auto;    display: flex;    flex-flow: column nowrap;    align-items: center;    justify-content: center;}.hystmodal__window {    margin: 50px 0;    flex-shrink: 0;    flex-grow: 0;    background: #fff;    width: 600px;    max-width: 100%;    overflow: visible;    transition: transform 0.2s ease 0s, opacity 0.2s ease 0s;    transform: scale(0.9);    opacity: 0;}

1.2 Скрываем окно


Сейчас наше окно всегда видно. Когда говорят о скрытии элементов, первое что приходит на ум это переключать свойство display со значения none до нашего значения flex.


Но этот подход нас не устроит, ведь свойство display не анимируется. Все переходы дочерних элементов, указанные в свойстве transition, работать не будут.


Нам поможет другое свойство visibility:hidden. Оно скроет окно визуально, хотя и зарезервирует под него место. А так как все будущие окна на странице имеют фиксированное
позиционирование они будут полностью скрыты и не повлияют на остальную страницу. Кроме того, на элементы с visibility:hidden нельзя установить фокус с клавиатуры, а от скрин-ридеров мы уже скрыли окна с помощью атрибута aria-hidden="true".


Добавим также классы для открытого окна:


.hystmodal--active{    visibility: visible;}.hystmodal--active .hystmodal__window{    transform: scale(1);    opacity: 1;}

1.3 Оформление подложки


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


Просто разместим элемент .hysymodal__shadow прямо перед закрывающие </body>. В будущем, сделаем так, чтобы этот элемент создавался автоматически из js при инициализации библиотеки.


Его свойства:


.hystmodal__shadow{    position: fixed;    border:none;    display: block;    width: 100%;    top: 0;    bottom: 0;    right: 0;    left: 0;    overflow: hidden;    pointer-events: none;    z-index: 98;    opacity: 0;    transition: opacity 0.15s ease;    background-color: black;}/* активная подложка */.hystmodal__shadow--show{    pointer-events: auto;    opacity: 0.6;}

1.4 Отключение прокрутки страницы


Когда модальное окна открывается, мы хотим, чтобы страница под ним не прокручивалась.
Самый простой способ этого добиться повесить overflow:hidden для body или html, когда окно открывается. Однако с этим есть проблема:


Проблема 4. В браузере Safari на iOS страница будет прокручиваться, даже если на тег html или body повешен overflow:hidden.
Решается двумя способами, либо блокированием событий прокрутки (touchmove, touchend или touchsart) из js вида:


targetElement.ontouchend = (e) => {    e.preventDefault();};

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


ps: можно конечно применить библиотеку scroll-lock, в которую заложено это решение, но в статье было решено воспользоваться другим вариантом.


Другое решение основано частично на CSS. Пусть когда окно открывается, на элемент <html> будет добавляться класс .hystmodal__opened:


.hystmodal__opened {    position: fixed;    right: 0;    left: 0;    overflow: hidden;}

Благодаря position:fixed, окно не будет прокручиваться даже в safari, однако здесь тоже не всё гладко:


Проблема 5. При открытии/закрытии окна страница прокручивается в начало.
Действительно, это происходит из-за изменения свойства position, текущая прокрутка окна сбрасывается.


Для решения, нам нужно написать следующий JS (упрощенно):


При открытии:


// Находим тег html и сохраняем егоlet html = document.documentElement;//сохраним текущую прокрутку:let scrollPosition = window.pageYOffset;//установим свойство top у html равное прокруткеhtml.style.top = -scrollPosition + "px";html.classList.add("hystmodal__opened");

При закрытии:


html.classList.remove("hystmodal__opened");//прокручиваем окно туда где оно былоwindow.scrollTo(0, scrollPosition);html.style.top = "";

Отлично, приступим к JavaScript коду.


2. Код JavaScript


2.2 Каркас библиотеки


Нам нужна совместимость со старыми браузерами включая IE11 поэтому нам нужно выбрать из 2 вариантов кодинга:


  • Разрабатывать на старом стандарте ES5, и использовать только те фичи, которые поддерживают все браузеры.
  • Применить современный синтаксис ES6, но подключить транспайлер Babel, который автоматически преобразует код для всех браузеров и встроит необходимые полифилы.
    Было принято решение использовать второй вариант, с прицелом на будущее.
    Приступим.

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


class HystModal{    /**     * При создании экземпляра класса, мы передаём в него     * js-объект с настройками. Он становится доступен     * в конструкторе класса в виде переменной props     */    constructor(props){        /**         * Для удобства некоторые свойства можно не передавать         * Мы должны заполнить их начальными значениями         * Это можно сделать применив метод Object.assign         */        let defaultConfig = {            linkAttributeName: 'data-hystmodal',            // ... здесь остальные свойства        }        this.config = Object.assign(defaultConfig, props);        // сразу вызываем метод инициализации        this.init();    }    /**      * В свойство _shadow будет заложен div с визуальной     * подложкой. Оно сделано статическим, т.к. при создании     * нескольких экземпляров класса, эта подложка нужна только     * одна     */    static _shadow = false;    init(){        /**         * Создаём триггеры состояния, полезные переменные и.т.д.         */        this.isOpened = false; // открыто ли окно        this.openedWindow = false; //ссылка на открытый .hystmodal        this._modalBlock = false; //ссылка на открытый .hystmodal__window        this.starter = false, //ссылка на элемент "открыватель" текущего окна        // (он нужен для возвращения фокуса на него)        this._nextWindows = false; //ссылка на .hystmodal который нужно открыть        this._scrollPosition = 0; //текущая прокрутка (см. выше)        /**         * ... остальное         */        // Создаём только одну подложку и вставляем её в конец body        if(!HystModal._shadow){            HystModal._shadow = document.createElement('div');            HystModal._shadow.classList.add('hystmodal__shadow');            document.body.appendChild(HystModal._shadow);        }        //Запускаем метод для обработки событий см. ниже.        this.eventsFeeler();    }    eventsFeeler(){        /**          * Нужно обработать открытие окон по клику на элементы с data-атрибутом         * который мы установили в конфигурации - this.config.linkAttributeName         *          * Здесь мы используем делегирование события клика, чтобы обойтись одним         * лишь обработчиком события на элементе html         *          */        document.addEventListener("click", function (e) {            /**             * Определяем попал ли клик на элемент,             * который открывает окно             */             const clickedlink = e.target.closest("[" + this.config.linkAttributeName + "]");            /** Если действительно клик был на              * элементе открытия окна, находим              * подходящее окно, заполняем свойства             *  _nextWindows и _starter и вызываем             *  метод open (см. ниже)             */            if (clickedlink) {                 e.preventDefault();                this.starter = clickedlink;                let targetSelector = this.starter.getAttribute(this.config.linkAttributeName);                this._nextWindows = document.querySelector(targetSelector);                this.open();                return;            }            /** Если событие вызвано на элементе             *  с data-атрибутом data-hystclose,             *  значит вызовем метод закрытия окна             */            if (e.target.closest('[data-hystclose]')) {                this.close();                return;            }        }.bind(this));        /** По стандарту, в обработчике события в this         * помещается селектор на котором события обрабатываются.         * Поэтому нам нужно вручную установить this на наш          * экземпляр класса, который мы пишем с помощью .bind().         */         //обработаем клавишу escape и tab        window.addEventListener("keydown", function (e) {               //закрытие окна по escape            if (e.which == 27 && this.isOpened) {                e.preventDefault();                this.close();                return;            }            /** Вызовем метод для управления фокусом по Tab             * и всю ответственность переложим на него             * (создадим его позже)             */             if (e.which == 9 && this.isOpened) {                this.focusCatcher(e);                return;            }        }.bind(this));    }    open(selector){        this.openedWindow = this._nextWindows;        this._modalBlock = this.openedWindow.querySelector('.hystmodal__window');        /** Вызываем метод управления скроллом         * он будет блокировать/разблокировать         * страницу в зависимости от свойства this.isOpened         */        this._bodyScrollControl();        HystModal._shadow.classList.add("hystmodal__shadow--show");        this.openedWindow.classList.add("hystmodal--active");        this.openedWindow.setAttribute('aria-hidden', 'false');        this.focusContol(); //вызываем метод перевода фокуса (см. ниже)        this.isOpened = true;    }    close(){        /**         * Метод закрытия текущего окна. Код упрощён         * подробнее в статье далее.         */        if (!this.isOpened) {            return;        }        this.openedWindow.classList.remove("hystmodal--active");        HystModal._shadow.classList.remove("hystmodal__shadow--show");        this.openedWindow.setAttribute('aria-hidden', 'true');        //возвращаем фокус на элемент которым открылось окно        this.focusContol();        //возвращаем скролл        this._bodyScrollControl();        this.isOpened = false;    }    _bodyScrollControl(){        let html = document.documentElement;        if (this.isOpened === true) {            //разблокировка страницы            html.classList.remove("hystmodal__opened");            html.style.marginRight = "";            window.scrollTo(0, this._scrollPosition);            html.style.top = "";            return;        }        //блокировка страницы        this._scrollPosition = window.pageYOffset;        html.style.top = -this._scrollPosition + "px";        html.classList.add("hystmodal__opened");    }}

Итак, мы описали класс HystModal. Чтобы всё работало, нужно всего лишь подключить наш скрипт и создать экземпляр класса:


const myModal = new HystModal({    linkAttributeName: 'data-hystmodal', });

Тогда по клику по ссылке/кнопке с атрибутом data-hystmodal, например такой: <a href="#" data-hystmodal="#myModal">Открыть окно</a> будет
открываться окно. Однако у нас появляются новые нюансы:


Проблема 6: если в браузере есть фиксированный скроллбар (который влияет на ширину страницы), то при открытии/закрытии окна происходит сдвиг контента, когда полоса прокрутки то появляется то пропадает.



Действительно скроллбар пропадает и контент страницы перераспределяется. Чтобы решить эту проблему, можно добавить отступ справа к тегу html, равный ширине скроллбара когда он пропадает.


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


Дополним метод _bodyScrollControl()


//при открытии окнаlet marginSize = window.innerWidth - html.clientWidth;//ширина скроллбара равна разнице ширины окна и ширины документа (селектора html)if (marginSize) {    html.style.marginRight = marginSize + "px";} //при закрытии окнаhtml.style.marginRight = "";

Почему код метода close() упрощён? Дело в том, что просто убирая классы CSS у элементов, мы не можем анимировать закрытие окна.


Проблема 7. При закрытии окна, свойство visibility:hidden применяется сразу и не даёт возможности анимировать закрытие окна.


Причина этого известна: свойство visibility:hidden не анимируется. Конечно, можно обойтись без анимации, но, если она нужна, сделаем следующее.


  • Создадим дополнительный CSS-класс .hystmodalmoved почти такой-же как и .hystmodal--active

.hystmodal--moved{    visibility: visible;}

  • Затем при закрытии сначала добавим этот класс к окну и повесим обработчик события transitionend на модальном окне. Затем удалим класс `.hystmodalactive, таким образом вызывая css-переход. Как только переход завершится, сработает обработчик события transitionend, в котором сделаем всё остальное и удалим сам обработчик события.

Ниже: новая версия методов закрытия окна:


close(){    if (!this.isOpened) {        return;    }    this.openedWindow.classList.add("hystmodal--moved");    this.openedWindow.addEventListener("transitionend", this._closeAfterTransition);    this.openedWindow.classList.remove("hystmodal--active");}_closeAfterTransition(){    this.openedWindow.classList.remove("hystmodal--moved");    this.openedWindow.removeEventListener("transitionend", this._closeAfterTransition);    HystModal._shadow.classList.remove("hystmodal__shadow--show");    this.openedWindow.setAttribute('aria-hidden', 'true');    this.focusContol();    this._bodyScrollControl();    this.isOpened = false;}

Вы заметили, что мы создали ещё один метод _closeAfterTransition() и перенесли основную логику закрытия туда. Это нужно, чтобы удалить обработчик события transitionend после закрытия окна, ведь в метод removeEventListener необходимо передать именно ту функцию, которую мы привязывали.


Кроме того, если анимация не будет нужна, можно просто вызвать this._closeAfterTransition() не вешая его на событие.


Как мы помним, внутри addEventListener, this будет указывать на селектор где происходит событие, а не на наш экземпляр класса, поэтому в конструкторе нужно добавить ещё одну строчку для жёсткой привязки метода к this.


//внутри конструктораthis._closeAfterTransition = this._closeAfterTransition.bind(this)

2.2 Закрытие окна по клику на оверлей


Нам нужно обработать ещё одно событие закрытие окна по клику на элемент подложки .hystmodal__wrap. Мы можем повесить обработчик клика на документ для делегирования события как при открытии и проверить что событие произошло на .hystmodal__wrap примерно так:


document.addEventListener("click", function (e) {    const wrap = e.target.classList.contains('hystmodal__wrap');    if(!wrap) return;    e.preventDefault();    this.close();}.bind(this));

Это будет работать, но есть один малозаметный недостаток.


Проблема 8. Если кнопку мыши нажать внутри окна, а отпустить за его пределами (над подложкой), окно закрывается.


Представьте, что в окне форма. Вы выделяете текст в поле ввода и случайно двигаете мышь чуть дальше, курсор заходит на подложку и вдруг окно закрывается, всё вдруг пропадает из виду. Не хотелось бы, чтобы так было.


Окно закрывается потому что по спецификации, если нажатие и отпускание мыши были на разных элементах, то событие click сработает на самом ближайшем общем для них элементе, а у нас это как раз .hystmodal__wrap.


Мы могли бы решить это изменением html, добавляя ещё один div сразу после .hystmodal__window и размещая его визуально под окном. Но нам бы не хотелось добавлять лишний пустой div ещё сильнее усложняя разметку.


Мы можем разбить наш addEventListener на два отдельных обработчика: для событий mousedown и mouseup и будем проверять чтобы оба события происходили именно на .hystmodal__wrap. Добавим новые обработчики событий в наш метод eventsFeeler()


document.addEventListener('mousedown', function (e) {    /**    * Проверяем было ли нажатие над .hystmodal__wrap,    * и отмечаем это в свойстве this._overlayChecker    */    if (!e.target.classList.contains('hystmodal__wrap')) return;    this._overlayChecker = true;}.bind(this));document.addEventListener('mouseup', function (e) {    /**    * Проверяем было ли отпускание мыши над .hystmodal__wrap,    * и если нажатие тоже было на нём, то закрываем окно    * и обнуляем this._overlayChecker в любом случае    */    if (this._overlayChecker && e.target.classList.contains('hystmodal__wrap')) {        e.preventDefault();        !this._overlayChecker;        this.close();        return;    }    this._overlayChecker = false;}.bind(this));

2.3 Управление фокусом


У нас заготовлено два метода для управления фокусом: focusContol() для переноса фокуса внутрь окна и обратно при его закрытии, а также focusCatcher(event) для блокирования ухода фокуса из окна.


Решения для фокуса были реализованы аналогично js-библиотеке Micromodal (Indrashish Ghosh). А именно:


1.В служебный массив сохраним все css селекторы на которых может быть установлен фокус (свойство помещаем в init()):


//внутри метода init или конструктораthis._focusElements = [    'a[href]',    'area[href]',    'input:not([disabled]):not([type="hidden"]):not([aria-hidden])',    'select:not([disabled]):not([aria-hidden])',    'textarea:not([disabled]):not([aria-hidden])',    'button:not([disabled]):not([aria-hidden])',    'iframe',    'object',    'embed',    '[contenteditable]',    '[tabindex]:not([tabindex^="-"])'];

2.В методе focusContol() находим первый такой селектор в окне и устанавливаем на него фокус, если окно открывается. Если же окно закрывается то переводим фокус на this.starter:


focusContol(){    /** Метод переносит фокус с элемента открывающего окно     * в само окно, и обратно, когда окно закрывается     * см. далее в тексте.     */    const nodes = this.openedWindow.querySelectorAll(this._focusElements);    if (this.isOpened && this.starter) {        this.starter.focus();    } else {        if (nodes.length) nodes[0].focus();    }}

3.В методе focusCatcher() находим в окне и превращаем в массив коллекцию всех элементов на которых может быть фокус. И проверяем, если фокус должен был выйти на пределы окна, то вместо этого устанавливаем фокус снова на первый или последний элемент (ведь фокус можно переключать как по Tab так и по Shift+Tab в обратную сторону).


Результирующий код метода focusCatcher:


focusCatcher(e){    /** Метод не позволяет фокусу перейти вне окна при нажатии TAB     * элементы в окне фокусируются по кругу.     */    // Находим все элементы на которые можно сфокусироваться    const nodes = this.openedWindow.querySelectorAll(this._focusElements);    //преобразуем в массив    const nodesArray = Array.prototype.slice.call(nodes);    //если фокуса нет в окне, то вставляем фокус на первый элемент    if (!this.openedWindow.contains(document.activeElement)) {        nodesArray[0].focus();        e.preventDefault();    } else {        const focusedItemIndex = nodesArray.indexOf(document.activeElement)        if (e.shiftKey && focusedItemIndex === 0) {            //перенос фокуса на последний элемент            focusableNodes[nodesArray.length - 1].focus();        }        if (!e.shiftKey && focusedItemIndex === nodesArray.length - 1) {            //перерос фокуса на первый элемент            nodesArray[0].focus();            e.preventDefault();        }    }}

По сути мы реализовали все необходимое для успешного создания модальных окон, осталось ещё несколько дел:


Проблема 9. В IE11 не работают методы Element.closest() и Object.assign().


Для поддержки Element.closest, воспользуемся полифилами для closest и matches от MDN.


Можно их вставить просто так, но так как у нас проект всё равно собирается webpack, то удобно воспользоваться пакетом element-closest-polyfill который просто вставляет этот код.


Для поддержки Object.assign, можно воспользоваться уже babel-плагином @babel/plugin-transform-object-assign


3. Заключение и ссылки


Повторяя начало статьи, всё изложенное выше, я оформил в маленькую библиотеку hystModal под MIT-лицензией. Вышло 3 кБ кода при загрузке с gzip. Ещё написал для неё подробную документацию на русском и английском языке.


Что вошло ещё в библиотеку hystModal, чего не было в статье:


  • Настройки (вкл/выкл управление фокусом, варианты закрытия, ожидание анимации закрытия)
  • Коллбеки (функции вызывающиеся перед открытием окна и после его закрытия (в них передаётся объект модального окна))
  • Добавлен запрет на какие-либо действия пока анимация закрытия окна не завершится, а также ожидание анимации закрытия текущего окна перед открытием нового (если окно открывается из другого окна).
  • Оформление кнопки-крестика закрытия в CSS
  • Минификация CSS и JS плагинами Webpack.

Если вам будет интересна эта библиотека, буду рад звёздочке в GitHub, или напишите в Issues о найденных багах. (Особенно большие проблемы, наверное, в грамматике английской версии документации, так как мои знания языка пока на начальном уровне. Связаться со мной также можно в Instagram

Подробнее..

Дайджест свежих материалов из мира фронтенда за последнюю неделю 433 (14 20 сентября 2020)

21.09.2020 00:19:49 | Автор: admin
Предлагаем вашему вниманию подборку с ссылками на новые материалы из области фронтенда и около него.


Медиа|Веб-разработка|CSS|JavaScript|Браузеры|Занимательное



Медиа


podcast UnderJS Podcast #26 Балдежный выпуск (React 18, GraphQL, RN 0.63)
podcast Фронтенд Юность (18+) #155: TypeScript за лимон
podcast Подкаст Сделайте мне красиво, Выпуск 44: Не переживайте, это безопасно
podcast Подкаст Веб-стандарты 247. Edge, браузеры, AVIF, генераторы статики, математика для фронтендера, Хабр, чтение, переводы
podcast Подкаст CSSSR: Security-релизы NodeJS, Ruby 3, разбор AVIF, JIT-компилятор Erlang, Web Skills, Laravel 8
video Видеоблог Вадима Макеева 34. Type.Today, base64, шрифты и сабсеттинг

Веб-разработка


habr Web Cryptography API: пример использования
en Факторы ранжирования Google, которые повлияют на поиск в 2021 году: Core Web Vitals, E-A-T или AMP?
en Подборка креативных сайтов Inspirational Websites Roundup #18





CSS


en Центрирование в CSS
en Как использовать маски в CSS
en Как использовать CSS clipping
en Изучение CSS 3D Transforms и Perspective
en Введение в CSS-анимацию
en Min, Max и Clamp
en Interaction Media Features и их потенциал

JavaScript


Работаем с медиа-запросами через JavaScript
Картинка, которая одновременно является кодом на Javascript
en Drag and Drop с помощью линейной интерполяции в JavaScript
en Практическое реактивное программирование с помощью RxJS
en Шпаргалка HTML5 Node: 21 API, которые вы должны знать в 2020 году
en Как мы написали самый быстрый JavaScript UI Framework, опять








Браузеры


Вышла новая версия браузера Safari 14 с улучшенной работой вкладок
Уязвимость в Firefox для Android, позволяющая управлять браузером через общий Wi-Fi

Занимательное


Mozilla прекращает разработку Firefox Send и Firefox Notes
Mozilla запустила сервис для отслеживания оскорбительного контента в рекомендациях YouTube
Хакерская атака впервые стала причиной смерти человека
Почему громкий стартап Slack стал скучным и это нормально
Семь мифов о программировании
Microsoft рассказала об итогах эксперимента с подводными серверами
Чем программирование сегодня отличается от программирования 20 лет назад?
Факторы ранжирования в локальном поиске Google в 2020 году

Дайджест за прошлую неделю.
Материал подготовили dersmoll и alekskorovin.
Подробнее..

Моментальная загрузка с instant.page

18.09.2020 12:15:01 | Автор: admin


instant.page это небольшой скрипт, позволяющий ускорять навигацию по сайту с помощью just-in-time предзагрузки. Когда пользователь наводит курсор на ссылку, страница предзагружается в фоне, и при переходе по ссылке открывается моментально. По тому же принципу работает InstantClick, но он предоставляется уже как отдельная библиотека на pushState и Ajax, с дополнительными модулями вроде прогресс-бара предзагрузки.

Установка


Просто добавьте скрипт в конец body:

<script src="http://personeltest.ru/aways/instant.page/5.1.0" type="module" integrity="sha384-by67kQnR+pyfy8yWP4kPO12fHKRLHZPfEsiSXR8u2IKcTdxD805MGUXBzVPnkLHw"></script>

Может показаться, что в современном вебе бессмысленно распространять своё решение через скрипт вместо размещения в npm, но это не JQuery и весь код буквально умещается в чуть больше 200 строк, поэтому подключить его может себе позволить даже самое легковесное приложение. К тому же, в виде модуля уже есть сверхпопулярный но спорный quicklink от Google.

Десктоп


У разных исследований получаются разные значения, поэтому нельзя точно сказать, с какой вероятностью человек нажмёт на ссылку, на которую он навёл курсор, и как быстро это произойдёт. Автор скрипта утверждает, что если указатель уже наведён на ссылку более 65 миллисекунд, пользователь в половине случае перейдёт по ней.

Для понижения количества ложных срабатываний и в instant.page, и в InstantClick предлагают опционально триггерить предзагрузку в момент нажатия (mousedown), что в среднем должно ускорить загрузку на 80 миллисекунд. Правда, скрипт загружает только HTML, а другие жирные ресурсы всё равно будут загружаться в обычном режиме.

В instant.page есть и совсем безумный триггер для маньяков (и любителей quicklink), загружающий страницу, когда ссылка попадает в зону видимости. К счастью, в противовес ему есть и режим белого списка.

Телефон


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

Настройка


  • Белый список: предзагрузка работает только для ссылок с атрибутом data-instant (для этого в body надо добавить атрибут data-instant-whitelist)
  • Чёрный список: не будут загружаться ссылки с атрибутом data-no-instant
  • Внешние ссылки по умолчанию не загружаются, изменить это можно, добавив data-instant-allow-external-links в body
  • Ссылки с вопросительным знаком по умолчанию не загружаются, потому что могут вызывать нежелательные действия. Чтобы разрешить их, нужно добавить data-instant-allow-query-string в body


Проблемы


  • uBlock Origin и другие блокировщики, использующие список правил EasyPrivacy, блокируют скрипт как потенциальную угрозу приватности. Автор пытался убедить его мейтейнеров убрать instant.page из списка, но в итоге ему отказали и закрыли issue. При использовании скрипта на своём сайте можно просто захостить его у себя, что позволит обойти подобные блокировки. Однако у пользователей на Firefox с включённым uBlock Origin вырезается вообще любая предзагрузка, поэтому для них это решение не поможет.
  • В Safari 13 поддержка выключена по умолчанию. Должно быть исправлено в Safari 14.


Заключение


Меньше чем за год (а популярность пришла к instant.page даже меньше полугода назад) технология закрепилась на рынке, обзавелась крупными клиентами вроде Spotify и Pepsico и подбирается по количеству звёзд на GitHub к InstantClick, который там находится с 2014 года. Автор заявляет (со ссылкой на builtwith) о более чем 7000 сайтов, использующих instant.page, с суммарной аудиторией более 76 миллионов пользователей ежемесячно.



На правах рекламы


VDS для сайтов любых масштабов это про наши эпичные серверы! Они бесплатно защищены от DDoS-атак, скорость интернет-канала 500 Мегабит. Предоставляем возможность автоматически установить удобную панель управления VestaCP для размещения сайтов. Поспешите заказать!

Подробнее..

PWA не для всех

20.09.2020 22:11:04 | Автор: admin

В комментах к моей предыдущей статье о service worker'ах была высказана мысль, что PWA на десктопах - вещь малополезная. Примерно полгода назад я разбирался с тем, как прикрутить PWA Vue Storefront к магазинам на платформе Magento и мне понравилось, как шустро крутилось в моём компьютере это приложение по сравнению с оригинальным web-интерфейсом. Мой персональный опыт показывал, что PWA на десктопах имеет неплохую перспективу, но дальнейшее углубление в тему показало, что коллега @sumanai со своим отрицанием PWA на десктопах был прав.

Offline

Какая основная "фишка" прогрессивных web-приложений?

Способность работать в режиме offline.

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

Ввод-вывод

По способу ввода ("сенсорный экран" против "клавиатура + мышь") и отображения информации (размер дисплея в дюймах) устройства можно разделить на две больших группы:

  • смартфоны и планшеты

  • ноутбуки и десктопы

Web-интерфейсы

Современные браузеры предлагают разнообразный API для web-приложений, большая часть которого работает для обеих групп устройств. Тем не менее вот эти три интерфейса актуальны именно для смартфонов/планшетов:

IndexedDB

Для "осознанного" кэширования запросов (например, на уровне service worker'ов) современные браузеры предоставляют Cache API, но основным хранилищем данных при работе в режиме offline является IndexedDB. Серверная база данных общего пользования (MySQL, Postgres, Oracle, MongoDB, ...) в этот момент заменяется базой данных персонального пользования (IndexedDB).

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

SEO

В моей статье "Влияние service worker'ов на web-приложения" я пришёл вот к такой схеме работы PWA:

При общении браузера с сервером возникают два потока информации:

  • статика: это код (HTML/CSS/JS) и медиа-файлы (в основном изображения), которые могут кэшироваться service worker'ом;

  • API: это пользовательские данные, которые так или иначе должны мигрировать между серверным хранилищем (DB) и персональным хранилищем (IndexedDB);

PWA - это прежде всего альтернатива native apps для мобильных устройств. Native apps также можно рассматривать с позиции, что статика (код + медиа) сворачивается в один пакет и распространяется через App Store или Google Play, а данные передаются между приложением и сервером через API (с учётом offline/online состояния подключения). И при этом никто не ожидает, что поисковики должны индексировать API-запросы native apps. От web-приложений же наоборот ждут, что любой адрес (страница) на сервере должен иметь возможность быть собранным на стороне сервера, в том числе и для индексации поисковиками.

Резюме

PWA - это, в первую очередь, альтернатива native apps для мобильных устройств.

PWA должны обеспечивать работу в режиме offline, что сильно усложняет разработку клиентской части, но является бесполезным функционалом для десктопов с их стабильным online-режимом работы сети.

Особенности взаимодействия пользователя со смартфоном/планшетом (ввод-вывод, гео-позиционирование) достаточно сильно отличаются от особенностей взаимодействия пользователя с десктопом/ноутбуком. Создание универсального решения ("амфибии") добавляет сложности по отношению к двум решениям - отдельно для смартфонов/планшетов и отдельно для ноутбуков/десктопов. Причём не столько с точки зрения кодирования, сколько с точки зрения UX.

PWA хорошо подходят для мобильных пользователей, но очень плохо подходят для поисковых роботов, что бы там себе ни думал по этому поводу Google.

Подробнее..

Server-Sent Events пример использования

21.09.2020 14:20:44 | Автор: admin


Доброго времени суток, друзья!

В этом туториале мы рассмотрим Server Sent Events: встроенный класс EventSource, который позволяет поддерживать соединение с сервером и получать от него события.

О том, что такое SSE и для чего он используется можно почитать здесь.

Что конкретно мы будем делать?

Мы напишем простой сервер, который будет по запросу клиента отправлять ему данные 10 случайных пользователей, а клиент с помощью этих данных будет формировать карточки пользователей.

Сервер будет реализован на Node.js, клиент на JavaScript. Для стилизации будет использоваться Bootstrap, в качестве API Random User Generator.

Код проекта находится здесь.

Поиграть с кодом можно здесь.

Если вам это интересно, прошу следовать за мной.

Подготовка


Создаем директорию sse-tut:

mkdir sse-tut

Заходим в нее и инициализируем проект:

cd sse-tutyarn init -y// илиnpm init -y

Устанавливаем axios:

yarn add axios// илиnpm i axios

axios будет использоваться для получения данных пользователей.

Редактируем package.json:

"main": "server.js","scripts": {    "start": "node server.js"},

Структура проекта:

sse-tut    --node_modules    --client.js    --index.html    --package.json    --server.js    --yarn.lock

Содержание index.html:

<head>    <!-- Bootstrap CSS -->    <link rel="stylesheet" href="http://personeltest.ru/aways/stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">    <style>        .card {            margin: 0 auto;            max-width: 512px;        }        img {            margin: 1rem;            max-width: 320px;        }        p {            margin: 1rem;        }    </style></head><body>    <main class="container text-center">        <h1>Server-Sent Events Tutorial</h1>        <button class="btn btn-primary" data-type="start-btn">Start</button>        <button class="btn btn-danger" data-type="stop-btn" disabled>Stop</button>        <p data-type="event-log"></p>    </main>    <script src="client.js"></script></body>

Сервер


Приступаем к реализации сервера.

Открываем server.js.

Подключаем http и axios, определяем порт:

const http = require('http')const axios = require('axios')const PORT = process.env.PORT || 3000

Создаем функцию получения данных пользователя:

const getUserData = async () => {    const response = await axios.get('https://randomuser.me/api')    // проверяем полученные данные    console.log(response)    return response.data.results[0]}

Создаем счетчик количества отправленных пользователей:

let i = 1

Пишем функцию отправки данных клиенту:

const sendUserData = (req, res) => {    // статус ответа - 200 ок    // соединение должно оставаться открытым    // тип содержимого - поток событий    // не кэшировать    res.writeHead(200, {        Connection: 'keep-alive',        'Content-Type': 'text/event-stream',        'Cache-Control': 'no-cache'    })    // данные будут отправляться каждые 2 секунды    const timer = setInterval(async () => {        // если отправлено 10 пользователей        if (i > 10) {            // останавливаем таймер            clearInterval(timer)            // сообщаем о том, что было отправлено 10 пользователей            console.log('10 users has been sent.')            // отправляем клиенту идентификатор со значением -1            // для того, чтобы клиент закрыл соединение            res.write('id: -1\ndata:\n\n')            // закрываем соединение            res.end()            return        }        // получаем данные        const data = await getUserData()        // записываем данные в ответ        // event - название события        // id - идентификатор события; используется при повторном подключении        // retry - время повторного подключения        // data - данные        res.write(`event: randomUser\nid: ${i}\nretry: 5000\ndata: ${JSON.stringify(data)}\n\n`)        // сообщаем о том, что данные отправлены        console.log('User data has been sent.')        // увеличиваем значение счетчика        i++    }, 2000)    // обрабатываем закрытие соединения клиентом    req.on('close', () => {        clearInterval(timer)        res.end()        console.log('Client closed the connection.')      })}

Создаем и запускаем сервер:

http.createServer((req, res) => {    // обязательный заголовок для преодоления CORS    res.setHeader('Access-Control-Allow-Origin', '*')    // если адрес запроса - getUser    if (req.url === '/getUsers') {        // отправляем данные        sendUserData(req, res)    } else {        // иначе, сообщаем о том, что запрашиваемая страница не найдена,        // и закрываем соединение        res.writeHead(404)        res.end()    }}).listen(PORT, () => console.log('Server ready.'))

Полный код сервера:
const http = require('http')const axios = require('axios')const PORT = process.env.PORT || 3000const getUserData = async () => {    const response = await axios.get('https://randomuser.me/api')    return response.data.results[0]}let i = 1const sendUserData = (req, res) => {    res.writeHead(200, {    Connection: 'keep-alive',    'Content-Type': 'text/event-stream',    'Cache-Control': 'no-cache'    })    const timer = setInterval(async () => {    if (i > 10) {        clearInterval(timer)        console.log('10 users has been sent.')        res.write('id: -1\ndata:\n\n')        res.end()        return    }    const data = await getUserData()    res.write(`event: randomUser\nid: ${i}\nretry: 5000\ndata: ${JSON.stringify(data)}\n\n`)    console.log('User data has been sent.')    i++    }, 2000)    req.on('close', () => {    clearInterval(timer)    res.end()    console.log('Client closed the connection.')    })}http.createServer((req, res) => {    res.setHeader('Access-Control-Allow-Origin', '*')    if (req.url === '/getUsers') {    sendUserData(req, res)    } else {    res.writeHead(404)    res.end()    }}).listen(PORT, () => console.log('Server ready.'))


Выполняем команду yarn start или npm start. В терминале появляется сообщение Server ready.. Открываем http://localhost:3000:



С сервером закончили, переходим к клиентской части приложения.

Клиент


Открываем файл client.js.

Создаем функцию генерации шаблона пользовательской карточки:

const getTemplate = user => `<div class="card">    <div class="row">        <div class="col-md-4">            <img src="${user.img}" class="card-img" alt="user-photo">        </div>        <div class="col-md-8">            <div class="card-body">                <h5 class="card-title">${user.id !== null ? `Id: ${user.id}` : `User hasn't id`}</h5>                <p class="card-text">Name: ${user.name}</p>                <p class="card-text">Username: ${user.username}</p>                <p class="card-text">Email: ${user.email}</p>                <p class="card-text">Age: ${user.age}</p>            </div>        </div>    </div></div>`

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

Начинаем реализовывать основной функционал:

class App {    constructor(selector) {        // основной элемент - контейнер        this.$ = document.querySelector(selector)        // запускаем частный метод        this.#init()    }    #init() {        this.startBtn = this.$.querySelector('[data-type="start-btn"]')        this.stopBtn = this.$.querySelector('[data-type="stop-btn"]')        // контейнер для сообщений о событиях        this.eventLog = this.$.querySelector('[data-type="event-log"]')        // устанавливаем контекст для обработчика        this.clickHandler = this.clickHandler.bind(this)        // делегируем обработку события        this.$.addEventListener('click', this.clickHandler)    }    clickHandler(e) {        // если кликнули по кнопке        if (e.target.tagName === 'BUTTON') {            // получаем тип кнопки            // и либо начинаем получать события от сервера,            // либо закрываем соединение            const {                type            } = e.target.dataset            if (type === 'start-btn') {                this.startEvents()            } else if (type === 'stop-btn') {                this.stopEvents()            }            // управление состоянием кнопок            this.changeDisabled()        }    }    changeDisabled() {        if (this.stopBtn.disabled) {            this.stopBtn.disabled = false            this.startBtn.disabled = true        } else {            this.stopBtn.disabled = true            this.startBtn.disabled = false        }    }//...

Сначала реализуем закрытие соединения:

stopEvents() {    this.eventSource.close()    // сообщаем о том, что соединение закрыто пользователем    this.eventLog.textContent = 'Event stream closed by client.'}

Переходим к открытию потока событий:

startEvents() {    // создаем экземпляр для получения данных по запросу на указанный адрес    this.eventSource = new EventSource('http://localhost:3000/getUsers')    // сообщаем о том, что соединение открыто    this.eventLog.textContent = 'Accepting data from the server.'    // обрабатываем получение от сервера идентификатора со значением -1    this.eventSource.addEventListener('message', e => {        if (e.lastEventId === '-1') {            // закрываем соединение            this.eventSource.close()            // сообщаем об этом            this.eventLog.textContent = 'End of stream from the server.'            this.changeDisabled()        }        // мы можем получить такой идентификатор лишь раз    }, {once: true})}

Обрабатываем кастомное событие randomUser:

this.eventSource.addEventListener('randomUser', e => {    // парсим полученные данные    const userData = JSON.parse(e.data)    // проверяем их    console.log(userData)    // извлекаем данные с помощью деструктуризации    const {        id,        name,        login,        email,        dob,        picture    } = userData    // продолжаем формировать данные, необходимые для генерации пользовательской карточки    const i = id.value    const fullName = `${name.first} ${name.last}`    const username = login.username    const age = dob.age    const img = picture.large    const user = {        id: i,        name: fullName,        username,        email,        age,        img    }    // генерируем шаблон    const template = getTemplate(user)    // рендерим карточку на странице    this.$.insertAdjacentHTML('beforeend', template)})

Не забываем реализовать обработку ошибок:

this.eventSource.addEventListener('error', e => {    this.eventSource.close()    this.eventLog.textContent = `Got an error: ${e}`    this.changeDisabled()}, {once: true})

Наконец, инициализируем приложение:

const app = new App('main')

Полный код клиента:
const getTemplate = user => `<div class="card">    <div class="row">        <div class="col-md-4">            <img src="${user.img}" class="card-img" alt="user-photo">        </div>        <div class="col-md-8">            <div class="card-body">                <h5 class="card-title">${user.id !== null ? `Id: ${user.id}` : `User hasn't id`}</h5>                <p class="card-text">Name: ${user.name}</p>                <p class="card-text">Username: ${user.username}</p>                <p class="card-text">Email: ${user.email}</p>                <p class="card-text">Age: ${user.age}</p>            </div>        </div>    </div></div>`class App {    constructor(selector) {        this.$ = document.querySelector(selector)        this.#init()    }    #init() {        this.startBtn = this.$.querySelector('[data-type="start-btn"]')        this.stopBtn = this.$.querySelector('[data-type="stop-btn"]')        this.eventLog = this.$.querySelector('[data-type="event-log"]')        this.clickHandler = this.clickHandler.bind(this)        this.$.addEventListener('click', this.clickHandler)    }    clickHandler(e) {        if (e.target.tagName === 'BUTTON') {            const {                type            } = e.target.dataset            if (type === 'start-btn') {                this.startEvents()            } else if (type === 'stop-btn') {                this.stopEvents()            }            this.changeDisabled()        }    }    changeDisabled() {        if (this.stopBtn.disabled) {            this.stopBtn.disabled = false            this.startBtn.disabled = true        } else {            this.stopBtn.disabled = true            this.startBtn.disabled = false        }    }    startEvents() {        this.eventSource = new EventSource('http://localhost:3000/getUsers')        this.eventLog.textContent = 'Accepting data from the server.'        this.eventSource.addEventListener('message', e => {            if (e.lastEventId === '-1') {                this.eventSource.close()                this.eventLog.textContent = 'End of stream from the server.'                this.changeDisabled()            }        }, {once: true})        this.eventSource.addEventListener('randomUser', e => {            const userData = JSON.parse(e.data)            console.log(userData)            const {                id,                name,                login,                email,                dob,                picture            } = userData            const i = id.value            const fullName = `${name.first} ${name.last}`            const username = login.username            const age = dob.age            const img = picture.large            const user = {                id: i,                name: fullName,                username,                email,                age,                img            }            const template = getTemplate(user)            this.$.insertAdjacentHTML('beforeend', template)        })        this.eventSource.addEventListener('error', e => {            this.eventSource.close()            this.eventLog.textContent = `Got an error: ${e}`            this.changeDisabled()        }, {once: true})    }    stopEvents() {        this.eventSource.close()        this.eventLog.textContent = 'Event stream closed by client.'    }}const app = new App('main')


На всякий случай перезапускаем сервер. Открываем http://localhost:3000. Нажимаем на кнопку Start:



Начинаем получать данные от сервера и рендерить карточки пользователей.

Если нажать на кнопку Stop, отправка данных будет приостановлена:



Снова нажимаем Start, отправка данных продолжается.

При достижении лимита (10 пользователей) сервер отправляет идентификатор со значением -1 и закрывает соединение. Клиент, в свою очередь, также закрывает поток событий:



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

Поддержка данной технологии на сегодняшний день составляет 95%:



Надеюсь, статья вам понравилась. Благодарю за внимание.
Подробнее..

Перевод Вышел Vue.js 3.0 One piece

21.09.2020 16:09:27 | Автор: admin

Название - это отсылка к манге "One piece" про парня, который хочет стать пиратским королем, прим. перев.

Сегодня мы с гордостью объявляем об официальном выпуске Vue 3.0 One Piece. Эта новая мажорная версия фреймворка с повышенной производительностью, меньшим размером, лучшей поддержкой TypeScript, новыми API-интерфейсами для крупномасштабных проектов (а для средних - можно избавиться от vuex, прим. перев.) и прочной основой для будущих итераций платформы.

Версия 3.0 - это более двух лет усилий по разработке, включающих 30+ RFC, 2600+ коммитов, 628 пулл-реквестов от 99 участников, а также огромный объем работы по разработке и написанию документации за пределами основного репозитория. Мы хотим выразить нашу глубочайшую благодарность членам команды за то, что они взяли на себя эту работу, нашим контрибуторам за пулл-реквесты, нашим спонсорам за финансовую поддержку, а также сообществу за участие в обсуждениях дизайна и отзывам на предрелизные версии. Vue - это независимый проект, созданный для сообщества и поддерживаемый сообществом, и Vue 3.0 был бы невозможен без вашей постоянной поддержки.

Развитие концепции "Прогрессивного фреймворка"

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

Сегодня Vue используют более 1,3 миллиона пользователей (это число основано на количестве активных пользователей за неделю у расширения Chrome Vue Devtools в соответствии со статистикой Google) (я так и не понял, включает ли это число пользователей расширения для firefox, прим. перев.) и мы видим, что Vue используется в самых разнообразных сценариях, от добавления интерактивности на "традиционные" страницы, генерируемые на сервере, до полноценных одностраничных приложений с сотнями компонентов. Vue 3 расширяет эту гибкость.

Многослойные внутренние модули

Vue 3.0 по прежнему можно использовать из CDN с помощью тега <script>, однако его внутреннее устройство было переписано с нуля в набор раздельных модулей. Новая архитектура обеспечивает лучшую управляемость и позволяет сократить время выполнения в некоторых случаев в два раза, за счет tree-shaking.

Эти модули также предоставляют более низкоуровневое API, благодаря которому можно делать самые разные продвинутые вещи:

  • Компилятор поддерживает трансформацию абстрактного синтаксического дерева (AST) в процессе сборки, например для интернационализации

  • Ядро предоставляет первоклассное API для создания своих визуализаторов, для рендеринга не только в браузере, но и, например, на мобильных устройствах, в контексте WebGL или в консоли. Базовый DOM-визуализатор использует этот же API

  • Модуль @vue/reactivity экспортирует функции, которые обеспечивают прямой доступ к системе реактивности Vue, и может использоваться как отдельный пакет. Его можно использовать в паре с другими решениями для создания шаблонов (например, lit-html) или даже в сценариях без пользовательского интерфейса.

Новые API для крупных приложений

Объектный API версии 2.x практически полностью поддерживается в Vue 3. Однако в версии 3.0 также представлен Composition API - новый набор API, направленных на устранение проблем при использовании Vue в крупномасштабных приложениях. Composition API построен на основе ранее упоминавшегося API реактивности. Он обеспечивает логическую связность и возможность повторного использования, похожую на хуки React, что позволяет более гибко организовывать код и надежнее определять типы (по сравнению с объектным API 2.x).

Composition API также можно использовать в Vue 2.x через плагин @vue/composition-api, и уже существуют библиотеки на базе Composition API, которые работают как для Vue 2, так и для 3 (например, vueuse, vue-composable).

Улучшение производительности

Vue 3 показывает значительные улучшения производительности по сравнению с Vue 2: размер пакета (до 41% меньше при использовании tree-shaking), первичный рендеринг (до 55% быстрее) и обновление отображения (до 133% быстрее), использования памяти (до 54% меньше).

В Vue 3 мы использовали подход виртуальный DOM с информацией от компилятора (compiler-informed Virtual DOM, как лучше перевести - не знаю, прим. перев.): компилятор шаблона выполняет агрессивную оптимизацию и генерирует код рендер-функции, который отделяет статический контент, оставляет подсказки для определения типов и, что наиболее важно, выравнивает (flatten, прим. перев.) динамические узлы внутри шаблона для снижения стоимости обхода дерева документа во время выполнения. Таким образом пользователь получает лучшее из обоих миров: оптимизированную компилятором производительность при использовании шаблонов или непосредственное управление рендерингом с помощью рендер-функции, если нужно.

Улучшенная интеграция с TypeScript

Кодовая база Vue 3 написана на TypeScript с автоматически сгенерированными, протестированными и объединенными определениями типов, поэтому эти определения всегда актуальны. Composition API отлично работает с выводом типов. Vetur, наше официальное расширение VSCode, теперь поддерживает проверку типов у шаблона и свойств (props прим. перев.) с использованием улучшенной внутренней типизации Vue 3. Кстати, определение типов полностью поддерживаются в TSX, если он вам больше нравится.

Экспериментальные возможности

Предлагаем вам две новые функции для однофайловых компонентов (SFC, также известные как файлы .vue):

Эти функции уже реализованы и доступны в Vue 3.0, однако предоставляются только для сбора отзывов. Они останутся экспериментальными до тех пор, пока соответствующие RFC не будут приняты.

Мы также реализовали недокументированный компонент <Suspense>, который реализует ожидание вложенных асинхронных зависимостей (асинхронные компоненты или компоненты с async setup()) при начальном рендеринге или перерендеринге при изменении ветвей (v-if и т.п.). Мы тестируем эту функциональность вместе с командой Nuxt.js (Nuxt 3 уже готовится) и, вероятно, утвердим её к версии 3.1.

Постепенный выпуск

Выпуск версии 3.0 обозначает общую готовность фреймворка. Несмотря на то, что некоторым из подпроектов фреймворка может потребоваться доработка для достижения стабильного статуса (в частности, интеграция vue router и Vuex в инструменты разработки), мы считаем целесообразным начинать новые проекты с нуля с Vue 3 уже сейчас. Мы также рекомендуем авторам библиотек начать обновлять ваши проекты для поддержки Vue 3.

Ознакомьтесь с соответствующим разделом документации Vue 3 для получения подробной информации обо всех подпроектах фреймворка.

Миграция и поддержка IE11

Мы отозвали сборку для миграции (сборка v3 с поведением, совместимым с v2 + предупреждения о несовместимых возможностях для упрощения миграции) и сборку с поддержкой IE11 из-за ограничений по времени. Мы сосредоточимся на их выпуске в 4 квартале 2020 года. Если вы планируете миграцию существующего приложения v2 или вашему приложению требуется поддержка IE11, вы должны знать об этих ограничениях.

Дальнейшие шаги

В ближайшее время мы сосредоточимся на:

  • Сборка для миграции

  • Поддержка IE11

  • Интеграция Router и Vuex в новые инструменты разработчика

  • Дальнейшие улучшения вывода типов в шаблонах в Vetur

В настоящее время веб-сайты с документацией, ветки на GitHub и теги npm для проектов Vue 3 и подпроектов, ориентированных на третью версию Vue, будут оставаться со статусом next. Это означает, что npm install vue по-прежнему будет устанавливать Vue 2.x, а npm install vue@next установит Vue 3. К концу 2020 года мы планируем переключить все ссылки на документацию, ветки и теги по умолчанию на 3.0.

В то же время мы планируем выпуск версии 2.7, который будет последним минорным выпуском линейки 2.x. В 2.7 будет выполнен обратный перенос совместимых улучшений из версии 3 и выдача предупреждений об использовании API, которые были удалены/изменены в версии 3, чтобы помочь с потенциальной миграцией. Мы планируем работать над версией 2.7 в первом квартале 2021 года. Этот выпуск станет LTS-версией с периодом поддержки 18 месяцев.

Попробовать

Вы можете больше узнать о Vue 3.0 на новом веб-сайте. Если вы уже являетесь пользователем Vue 2.x, перейдите непосредственно к разделу, посвященному миграции.

Подробнее..

Проверяем формы по стандартам с Validation API

22.09.2020 06:06:12 | Автор: admin
В свое время мне почти всем нравился Angular 2+, это хорошо спроектированный фреймворк, который на голову выше остальных популярных фронтенд фреймворков по инженерному уровню исполнения. Но были у него и весьма странные недоработки. Одна из них это невозможность ручного вызова валидации или ревалидации формы, которая наблюдалась как минимум, до 8ой версии. Нельзя сказать чтобы там сильно увлекаются реактивностью, но вот в этой подсистеме похоже какие-то реактивные соображения побудили разработчиков реализовать валидацию только через привязку, вынудив разработчиков прикладных решений обращаться к костылям навроде установки состояния нетронуто для полей и вообще усложняя написание сложных валидаторов с продвинутой логикой и участием нескольких полей сразу. Опыт борьбы с ангуляровским валидатором и некоторыми другими особенностями фреймворка усилил мое впечатление от того насколько элегантным и простым оказалось после этого использование HTML5 API для валидации форм, которое просто работает в любом современном браузере даже без подключения фреймворков и библиотек.

Базой для работы валидаторов являются атрибуты элементов. С помощью атрибутов мы сразу можем задать следующие ограничения:
required поле обязательное, т.е. требует заполнения
min max step минимально и максимально допустимые значения, а также шаг изменения
minlength и maxlength ограничители по количеству допустимых символов ввода
pattern регулярное выражение
Вроде бы не густо, однако, pattern дает нам довольно богатые возможности по проверке значений, легко нагугливаются регулярки позволяющие сходу проверять номера телефонов, емейл адреса и урлы и многое другое востребованное.
Расставленные на элементы формы эти атрибуты автоматически не позволят сработать кнопке из той же формы выполняющей submit значений на сервер, правда сегодня такой кейс может показаться многим анахроничным. Но это еще не беда, т.к. с помощью клиентского кода на JavaScript мы можем точно также и даже лучше пользоваться всеми этими валидаторами. Поэтому мы не будем использовать input type=email, а попробуем сделать свое поле с проверкой вводимого на соответствие правилам формирования адресов электронной почты. Сделаем простую форму:
<form name="myform" id="myform">   <input type="text" pattern="^[a-zA-Z0-9.!#$%&*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$" placeholder="email here"/>   <input type="submit"></form>

Валидатор сразу работает и на попытку нажимать кнопку выдает предупреждение на языке локали браузера.



Соответственно ввод mail@example.com дает успешный сабмит формы.
Чтобы развить свое поведение надо получить доступ к инстансу формы, это можно сделать через глобальный document по имени, индексу (id) или порядковому номеру начиная с нуля.
<script type="module">   document.forms.myform.onsubmit = (event) => {       console.log('validate');       return false;   };</script>

или по селектору одним из методов, таким как document.getElementById() или document.querySelector()
для проверки результатов запустим http-server
npx http-server

после того как команда отработает, в браузере можно открывать 127.0.0.1:8080/ или тот адрес, что он напишет вам в консоли и отлаживать результаты.

Заменим сабмит на обычную кнопку, и будем вызывать проверку формы вручную, немного изменив пример.
<form id="myform" action="#">   <input type="text" pattern="^[a-zA-Z0-9.!#$%&*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$" required placeholder="email here" />   <input type="button" name="subm" value="OK" /></form><script type="module">;   myform.subm.onclick = (event) => {       console.log(form.checkValidity());       return false;   };</script>

В данном примере видно, что мапинг объектов формы по их id и name работает и для дочерних по отношению к форме элементов, что выглядит очень изящно. Теперь наш код выводит в консоль состояние валидности формы.
Наличие методов ручного запуска валидации, не означает, что она не осуществляется без их вызова.
Результаты ввода и других изменений на форме сразу отражаются на ее состоянии, что проявляется в наличии псевдоклассов стилей valid и invalid. Если добавить выделение цветом, то можно заметить как сразу срабатывает валидация.
<style>  :valid {       border: 1px solid green;   }  :invalid {       border: 1px solid red;   }</style>




Для того, чтобы форма не мозолила глаза красным до того как пользователь попробовал что-то в нее вводить, можно использовать лайфхак с плейсхолдером:
<style>   input:valid {       border: 1px solid green;   }   input:not(:placeholder-shown):invalid {       border: 1px solid red;   }</style>

На элементы формы можно навесить внешние обработчики для событий валидации.
<script type="module">   myform.email.oninvalid = (event) => {       alert('Wrong email !!11');   };   myform.subm.onclick = (event) => {       console.log(form.checkValidity());       return false;   };</script>

В данном случае используется механизм хуков по имени события, если есть какое-то событие поддерживаемое элементом, то присвоив ему функцию с именем on + <имя_эвента> мы можем быть уверены, что она будет вызвана когда он сработает.

И еще один замечательный момент тут в том, что они будут вызываться уже не при вводе данных, а только при программном дергании валидации, т.е. вызове метода checkValidity().

Соответственно мы можем, обработать это поведение:
myform.subm.onclick = (event) => {   if (myform.checkValidity()) {       alert('Valid !');   } else {       alert('Invalid !')   }   return false;};

В реальной жизни нам может потребоваться также вызов event.preventDefault() если валидация не пройдена, чтобы прервать процедуру сабмита формы.

У checkValidity() есть аналог reportValidity(), который возвращает результат не вызывая при этом повторной валидации.

А как узнать какое поле неправильное?

Свойство .validity есть у каждого элемента ввода формы как и возможность вызвать на нем методы валидации, свойство имеет следующую структуру:

ValueState: {
valid общий признак корректности значения
valueMissing значение требуется, но не задано
typeMismatch введено неправильное по типу значение
patternMismatch введен не соответствующее шаблону значение
tooLong значение больше чем maxlength
tooShort значение меньше чем minlength
rangeUnderflow значение меньше min
rangeOverflow значение больше max
stepMismatch значение не соответствует шагу
badInput ввод не может быть приведен к значению
customError произвольная ошибка
}

В основном представлены, как мы видим, свойства ошибок соответствующие стандартным атрибутам валидации, тогда как .customError это наш задел для расширения.
Вызвав метод .setCustomValidity() с аргументом в виде строки с текстом ошибки мы можем обозначить элемент формы как невалидный. Установить или получить текст ошибки можно также через свойство .validationMessage.
Чтобы не задавливать браузерные валидации можно использовать свойство .willValidate, которое сообщает будут ли вызваны стандартные валидации на поле.
Передав пустую строку в качестве аргумента .setCustomValidity() мы можем вернуть его состояние к валидному.
Давайте добавим поддержку собственного атрибута my-pattern, который для наглядности будет точно так же проверять значение на соответствие регулярному выражению.
В случае ошибки сообщение, помимо того как это предусмотрено в браузере, будет выводиться рядом с полем
Валидация будет срабатывать на изменении значения нашего альтернативного поля и при нажатии кнопки.
<form id="myform" action="#">   <div>       <input type="text" name="email" id="email" value="" pattern="^[a-zA-Z0-9.!#$%&*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$" required placeholder="email here" />       <span class="msg"></span>   </div>   <div>       <input type="text" name="customInput" id="customInput" my-pattern="^[a-zA-Z0-9.!#$%&*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$" required placeholder="text here" />       <span class="msg"></span>   </div>   <button type="submit" name="subm" value="OK">OK</button></form><style>   input:valid {       border: 1px solid green;   }   input:not(:placeholder-shown):invalid {       border: 1px solid red;   }</style><script type="module">   myform.customInput.oninvalid = (event) => {       let el = event.target;       let msg = el.parentElement.querySelector('.msg');       msg.innerText = el.validationMessage;       console.log('oninvalid, id: ', el.id);   };   myform.customInput.oninput = (event) => {       let el = event.currentTarget;       validateWithMyPattern(el);       markValidity(el);   };   function markValidity(el) {       el.checkValidity();       let msg = el.parentElement.querySelector('.msg');       if (el.validity.valid) {           msg.innerText = '';       } else {           msg.innerText = el.validationMessage;       }   }   function validateWithMyPattern(field) {       if (field.value) {           if (field.hasAttribute('my-pattern') &&               field.value.match(field.getAttribute('my-pattern'))) {               field.setCustomValidity('');           } else {               field.setCustomValidity('My pattern error');           }       }   }   myform.subm.onclick = (event) => {       for (let formEl of myform.querySelectorAll('input')) {           validateWithMyPattern(formEl);           markValidity(formEl);       }       if (myform.reportValidity()) {           alert('Valid !');       } else {           alert('Invalid !')       }       return false;   };</script>

Теперь у нас два подобных поля проверяющих значение стандартным валидатором и собственноручно написанным.



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

Из ограничений Validation API мне более запомнилось только исходная невалидность полей. Для ее кроме хитрости с placeholder или специальными состояниями a-la untouched можно производить всю валидацию программно на событиях input и submit сочетая собственные валидаторы со стандартными.
Решая свои задачи, я пришел к необходимости создать свой компонент, выполняющий задачи формы заодно для поддержки собственных элементов ввода, позволяющий задавать разное поведение валидации и уведомлений и вешать любые валидаторы и использующий при этом стандартизированный Validation API. Посмотреть на него можно вот тут: https://bitbucket.org/techminded/skinny-widgets/src/master/src/form/
а код примера из этой статьи найти вот тут:
https://bitbucket.org/techminded/myform/
Подробнее..

Математика верстальщику не нужна 2 Матрицы, базовые трансформации, построение 3D и фильтры для картинок

22.09.2020 12:22:19 | Автор: admin


В прошлый раз мы говорили о графиках и траекториях для покадровых анимаций, а сегодня речь пойдет про матрицы. Мы разберемся с тем, как строятся базовые трансформации в CSS, SVG и WebGL, построим отображение 3D-мира на экране своими руками, попутно проводя параллель с таким инструментом, как Three.js, а также поэкспериментируем с фильтрами для фотографий и разберемся, что же за магия такая лежит в их основе.

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

Скрипты для генерирования картинок в стиле этого цикла статей находятся на GitHub, так что если если вы хотите себе сообразить такие же вы знаете что делать.

Немного определений


Матрица в математике это такая абстракция, можно сказать, что это тип данных в каком-то смысле, и записываетя она в виде прямоугольной таблицы. Количество столбцов и строк может быть любым, но в вебе мы почти всегда имеем дело с квадратными матрицами 2x2, 3x3, 4x4, и 5x5.

Также нам понадобится такое определение, как вектор. Думаю из школьной геометрии вы можете вспомнить определение, связанное со словами длина и направление, но вообще в математике вектором может называться очень много чего. В частности мы будем говорить о векторе, как об упорядоченном наборе значений. Например координаты вида (x, y) или (x, y, z), или цвет в формате (r, g, b) или (h, s, l, a) и.т.д. В зависимости от того, сколько элементов вошло в такой набор мы будем говорить о векторе той или иной размерности: если два элемента двумерный, три трехмерный и.т.д. Также, в рамках рассматриваемых тем, может быть удобно иногда думать о векторе, как о матрице размера 1x2, 1x3, 1x4 и.т.д. Технически можно было бы ограничиться только термином матрица, но мы все же будем использовать слово вектор, чтобы отделить эти два понятия друг от друга, хотя бы в логическом смысле.

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

function multiplyMatrices(a, b) {    const m = new Array(a.length);    for (let row = 0; row < a.length; row++) {        m[row] = new Array(b[0].length);        for (let column = 0; column < b[0].length; column++) {            m[row][column] = 0;            for (let i = 0; i < a[0].length; i++) {                m[row][column] += a[row][i] * b[i][column];            }        }    }    return m;}

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

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

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

  • Умножать можно только матрицы одной размерности.
  • Умножаем матрицу на матрицу получаем матрицу.
  • Можно умножить матрицу на вектор получим вектор.
  • Порядок умножения важен.



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

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



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

В этом примере мы используем буквы (x, y), и как вы уже можете догадаться, дальше речь пойдет о координатах в 2D. Но зачем добавлять третью координату и оставлять ее единицей? спросите вы. Все дело в удобстве, или даже лучше сказать, что в универсальности. Мы очень часто добавляем +1 координату для упрощения расчетов, и работа с 2D идет с матрицами 3x3, работа с 3D с матрицами 4x4, а работа с 4D, например с цветами в формате (r, g, b, a) идет с матрицами 5x5. На первый взгляд это кажется безумной идеей, но дальше мы увидим, насколько это унифицирует все операции. Если вы захотите более подробно разобраться в этой теме, то можете загуглить выражение однородные координаты.

Но довольно теории, перейдем к практике.

I. Базовые трансформации в компьютерной графике


Давайте возьмем выражения из упомянутого примера и посмотрим на них как есть, вне контекста матриц:

newX = a*x + b*y + cnewY = d*x + e*y + f

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

newX = 1*x + 0*y + 0 = xnewY = 0*x + 1*y + 0 = y

Здесь ничего не меняется новые координаты (x, y) идентичны старым. Если подставить эти коэффициенты в матрицу и внимательно присмотреться, то увидим, что получится единичная матрица.

А что получится, если взять другие коэффициенты? Например вот такие:

newX = 1*x + 0*y + A = x + AnewY = 0*x + 1*y + 0 = y

Мы получим смещение по оси X. Впрочем, что еще здесь могло получиться? Если для вас это не очевидно, то лучше вернуться к первой части, где мы говорили про графики и коэффициенты.

Меняя эти 6 коэффициентов a, b, c, d, e, f и наблюдая за изменениями x и y, рано или поздно мы придем к четырем их комбинациям, которые кажутся полезными и удобными для практического применения. Запишем их сразу в форме матриц, возвращаясь к изначальному примеру:



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

Очень часто удобно воспринимать матрицы именно как трансформации для чего-либо, например для координат. Это еще раз к слову об абстракциях.

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



Базовые трансформации в CSS


Но это все слова. Давайте посмотрим, как это выглядит в реальном фронтенде. В CSS у нас (внезапно) есть функция matrix. Выглядит она в контексте кода как-то так:

.example {    transform: matrix(1, 0, 0, 1, 0, 0);}

Многих новичков, которые впервые видят ее, накрывает вопрос почему здесь шесть параметров? Это странно. Было бы 4 или 16 еще куда не шло, но 6? Что они делают?

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



Также в CSS есть функция matrix3d для того, чтобы задавать с помощью матрицы трансформацию в 3D. Там уже есть 16 параметров, ровно чтобы сделать матрицу 4x4 (не забываем, что мы добавляем +1 размерность).

Матрицы для базовых 3D трансформаций строятся так же, как и для 2D, только нужно больше коэффициентов, чтобы расставлять их не по двум координатам, а по трем. Но принципы те же.

Естественно, каждый раз городить матрицу и следить за корректной расстановкой коэффициентов при работе с простыми трансформациями в CSS было бы странно. Мы, программисты, обычно все же стараемся упростить себе жизнь. Так что у нас в CSS появились краткие функции для создания отдельных трансформаций translateX, translateY, scaleX и.т.д. Обычно мы используем именно их, но важно понимать, что внутри они создают эти же самые матрицы, о которых мы говорили, просто скрывая от нас этот процесс за еще одним слоем абстракции.

Эти же самые трансформации translate, rotate, scale и skew, а также универсальная функция matrix для задания трансформаций, присутствуют и в SVG. Синтаксис немного другой, но суть такая же. При работе с трехмерной графикой, например с WebGL, мы тоже будем прибегать к этим же трансформациям. Но об этом чуть позже, сейчас важно понять, что они есть везде, и работают везде по одному и тому же принципу.

Промежуточные итоги


Обобщим вышесказанное:

  • Матрицы могут быть использованы в качестве трансформаций для векторов, в частности для координат каких-то объектов на странице.
  • Почти всегда мы оперируем квадратными матрицами и добавляем +1 размерность для упрощения и унификации вычислений.
  • Есть 4 базовых трансформации translate, rotate, scale и skew. Они используются везде от CSS до WebGL и везде работают схожим образом.

II. Построение 3D сцены своими руками


Логичным развитием темы про трансформации координат будет построение 3D сцены и отображение ее на экране. В той или иной форме эта задача обычно есть во всех курсах по компьютерной графике, но вот в курсах по фронтенду ее обычно нет. Мы посмотрим может быть немного упрощенный, но тем не менее полноценный вариант того, как можно сделать камеру с разными углами обзора, какие операции нужны, чтобы посчитать координаты всех объектов на экране и построить картинку, а также проведем параллели с Three.js самым популярным инструментом для работы с WebGL.

Здесь должен возникнуть резонный вопрос зачееем? Зачем учиться делать все руками, если есть готовый инструмент? Ответ кроется в вопросах производительности. Вероятно вы бывали на сайтах с конкурсами вроде Awwwards, CSS Design Awards, FWA и им подобных. Вспомните, насколько производительные сайты принимают участие в этих конкурсах? Да там почти все тормозят, лагают при загрузке и заставляют ноутбук гудеть как самолет! Да, конечно, основная причина обычно это сложные шейдеры или слишком большое количество манипуляций с DOM, но вторая невероятное количество скриптов. Это катастрофическим образом влияет на загрузку подобных сайтов. Обычно все происходит так: нужно сделать что-то на WebGL берут какой-нибудь 3D движок (+500КБ) и немного плагинов для него (+500КБ); нужно сделать падение объекта или разлетание чего-то берут физический движок (+1МБ, а то и больше); нужно обновить какие-то данные на странице ну так добавляют какой-нибудь SPA-фреймворк с десятком плагинов (+500КБ) и.т.д. И таким образом набирается несколько мегабайтов скриптов, которые мало того, что нужно загрузить клиенту (и это еще вдобавок к большим картинкам), так еще и браузер с ними что-то будет делать после загрузки они же не просто так к нему прилетают. Причем, в 99% случаев, пока скрипты не отработают, пользователь не увидит всей той красоты, которую ему бы нужно с самого начала показывать.

Народная примета гласит, что каждые 666КБ скриптов в продакшене увеличивают время загрузки страницы на время, достаточное, чтобы пользователь отправил разработчика сайта на следующий круг ада. Three.js в минимальной комплектации весит 628КБ...

При этом часто задачи просто не требуют подключения сложных инструментов. Например чтобы показать пару плоскостей с текстурами в WebGL и добавить пару шейдеров, чтобы картинки расходились волнами, не нужен весь Three.js. А чтобы сделать падение какого-то объекта не нужен полноценный физический движок. Да, он скорее всего ускорит работу, особенно если вы с ним знакомы, но вы заплатите за это временем пользователей. Тут каждый решает сам для себя, что ему выгоднее.

Цепочка преобразований координат


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

Так вот. Допустим дизайнер нарисовал 3D-модель. Пусть это будет куб (в примерах будем использовать максимально простые построения, чтобы не усложнять иллюстрации на ровном месте):



Что эта модель из себя представляет? По сути это набор точек в какой-то системе координат и набор взаимосвязей между ними, чтобы можно было определить, между какими из точек должны находиться плоскости. Вопрос плоскостей в контексте WebGL будет лежать на плечах самого браузера, а для нас важны координаты. Нужно понять, как именно их преобразовать.

У модели, как мы и сказали, есть система координат. Но обычно мы хотим иметь много моделей, хотим сделать сцену с ними. Сцена, наш 3D-мир, будет иметь свою, глобальную систему координат. Если мы просто будем интерпретировать координаты модели как глобальные координаты то наша модель будет находиться как бы в центре мира. Иными словами, ничего не поменяется. Но мы хотим добавить много моделей в разные места нашего мира, примерно так:



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

Например для кубиков будут примерно такие матрицы:

// Центральный кубик в центре мира.// Единичная матрица ничего не меняет и координаты просто интерпретируются как глобальные.const modelMatrix1 = [    [1, 0, 0, 0],    [0, 1, 0, 0],    [0, 0, 1, 0],    [0, 0, 0, 1]];// Кубик, смещенный по глобальной оси X.const modelMatrix2 = [    [1, 0, 0, 1.5],    [0, 1, 0, 0  ],    [0, 0, 1, 0  ],    [0, 0, 0, 1  ]];// Кубик, смещенный по глобальной оси X в другую сторону.const modelMatrix3 = [    [1, 0, 0, -1.5],    [0, 1, 0, 0   ],    [0, 0, 1, 0   ],    [0, 0, 0, 1   ]];

Дальше мы будем действовать примерно следующим образом:

для каждой точки модели {    глобальные координаты точки = [ матрица этой модели ] * локальные кординаты}

Соответственно для каждой модели нужна своя матрица.

Аналогичным образом можно делать цепочку из каких-то объектов. Если птица должна махать крыльями, то будет уместно координаты точек крыла сначала перевести в координаты птицы, а потом уже в глобальные мировые координаты. Это будет куда проще угадывания траектории крыла сразу в глобальных координатах. Но это так, к слову.


Дальше нужно определиться с тем, с какой стороны мы будем смотреть на мир. Нужна камера.



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

для каждой точки в мире {    координаты точки для камеры = [ матрица камеры ] * глобальные кординаты}

Если у вас сложная сцена, то будет иметь смысл произвести некоторые оптимизации и не считать ничего для точек, которые точно не попадут в поле зрения камеры. Не забывайте о производительности!

Давайте посмотрим на сцену с того места, где стоит наша условная камера:



Сейчас, преобразовав все точки в систему координат камеры, мы можем просто выбросить ось Z, а оси X и Y интерпретировать как горизонтальную и вертикальную. Если нарисовать все точки моделей на экране, то получится картинка, как в примере никакой перспективы и сложно понять, какая часть сцены на самом деле попадает в кадр. Камера как бы бесконечного размера во все стороны. Мы можем как-то все подогнать, чтобы то, что нужно, влезло на экран, но было бы неплохо иметь универсальный способ определения того, какая часть сцены попадет в поле зрения камеры, а какая нет.

У физических камер мы можем говорить о такой штуке, как угол обзора. Почему бы и здесь его не добавить?

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

// Пусть угол обзора будет 90 градусовconst s = 1 / (Math.tan(90 * Math.PI / 360));const n = 0.001;const f = 10;const projectionMatrix  = [    [s, 0, 0,          0],    [0, s, 0,          0],    [0, 0, -(f)/(f-n), -f*n/(f-n)],    [0, 0, -1,         0]];

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

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

Теперь, произведя уже знакомые преобразования:

для каждой точки в системе координат камеры {    координаты точки = [ матрица проекции ] * координаты в камере}

Получим в нашем поле зрения именно то, что и ожидаем. Увеличивая угол видим больше всего по сторонам, уменьшая угол видим только то, что ближе к направлению, куда камера направлена. PROFIT!



Но на самом деле нет. Мы забыли про перспективу. Бесперспективная картинка нужна мало где, так что нужно ее как-то добавить. И здесь, внезапно, нам не нужны матрицы. Задача выглядит очень сложной, но решается банальным делением координат X и Y на W для каждой точки:



* Здесь мы сместили камеру в сторону, и добавили параллельные линии на полу, чтобы было понятнее, где эта самая перспектива появляется.

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

Теперь мы имеем полноценную картинку. Можно брать координаты X и Y для каждой точки и рисовать ее на экране любым удобным вам способом.

Вообще этого достаточно, чтобы построить сцену, но в реальных проектах вы также можете столкнуться с дополнительной трансформацией, связанной с масштабированием, в самом конце. Идея в том, что после проекции мы получаем координаты (x, y) в пределах 1, нормализованные координаты, а потом уже умножаем их на размер экрана или канваса, получая координаты для отображения на экране. Этот дополнительный шаг выносит размер канваса из всех вычислений, оставляя его только в самом конце. Иногда это удобно.

Здесь у вас, вероятно заболела голова от количества информации, так что притормозим и повторим еще раз все трансформации в одном месте:



Если эти трансформации объединить в одну, то получится паровозик.

Как это выглядит в Three.js?


Теперь, когда мы понимаем, откуда этот паровозик взялся, посмотрим на пример вершинного шейдера по умолчанию в Three.js, который ничего не делает:

void main() {    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);}

или в более полном варианте:

void main() {    gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);}

Ничего он вам не напоминает? Да, это именно этот паровозик. И под ничего не делает мы подразумеваем, что он как раз делает всю работу по пересчету координат, на основе заботливо переданных со стороны Three.js матриц. Но никто не мешает эти матрицы сделать своими руками, не так ли?

Типы камер в компьютерной графике и Three.js


Тема типов камер не касается непосредственно матриц, но все же уделим ей пару минут, раз уж все равно зашла речь про Three.js, а то порой у людей каша в голове по этому поводу.

Камера это абстракция. Она помогает нам мыслить в 3D мире таким же образом, как мы мыслим в мире реальном. Как мы уже сказали, у камеры есть положение в пространстве, направление, куда она смотрит, и угол обзора. Все это задается с помощью двух матриц и, возможно, дополнительного деления координат для создания перспекивы.

В компьютерной графике мы имеем два вида камер это с перспективой и без перспективы. Это два принципиально разных типа камер в техническом плане, требующих разных действий для получения изображения. И все. Больше ничего нет. Все остальное это их комбинации, какие-то более сложные абстракции. Например в Three.js есть стерео-камера это не какой-то особый тип камеры в техническом плане, а просто абстракция две камеры, немного разнесенные в пространстве и расположенные под углом:



Для каждой половины экрана мы берем свою камеру и получается стерео-картинка. А CubeCamera это 6 обычных камер, расположенных с разных сторон от какой-то точки, не более того.

Что дальше?


Следующий шаг, после получения координат объектов, это определение того, какие объекты будут видны, а какие скроются за другими. В контексте WebGL это браузер сделает сам. Ну и останутся еще сопутствующие задачи вроде наложения текстур на них, расчет освещения по нормалям, тени, постобработка картинки, и.т.д. Но самую важную и сложную для понимания часть мы уже сделали. Это очень здорово. На самом деле многие генеративные штуки не нуждаются в этих самых текстурах и освещении, так что очень может быть, что полученных сейчас знаний будет достаточно для работы с ними.

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

Про текстуры и эффекты для картинок на WebGL, в том числе без библиотек, мы говорили в предыдущих статьях уже не раз. Вы можете обратиться к ним, если вам эта тема интересна. Таким образом, объединив все эти знания воедино, мы сможем строить полноценные красочные 3D-штуки своими руками.

Мы действительно можем строить полноценные 3D-сцены своими руками. Но все же здесь важно соблюдать баланс для простой генеративной графики или каких-то эффектов для картинок это будет хорошей идеей. Для сложных сцен может быть проще все же взять готовый инструмент, потому что свой по объему будет приближаться к тому же Three.js и мы ничего не выиграем при избавлении от него. А если вам нужно естественное освещение, мех, стекло, множественные отражения или еще что-то в этом духе, то из соображений производительности может иметь смысл отказаться от самой идеи рендерить что-то в реальном времени и заранее заготовить картинки с видами на сцену с разных сторон и уже их показывать пользователю. Мыслите шире, не зацикливайтесь на определенном подходе к решению задач.

Промежуточные итоги


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

Итак:

  • Можно построить 3D мир и рассчитать координаты объектов на экране своими руками, используя паровозик из матриц.
  • В 3D мире мы оперируем такой абстракцией, как камера. Она имеет расмоложение, направление и угол обзора. Все это задается с помощью все тех же матриц. И есть два базовых вида камер с перспективой и без перспективы.
  • В контексте WebGL ручное построение картинки на экране или физические расчеты часто позволяют выбросить тяжелые зависимости и ускорить загрузку страниц. Но важно соблюдать баланс между своими скриптами, готовыми инструментами, а также альтернативными вариантами решения задач, уделяя внимание не только своему удобству, но и вопросам скорости загрузки и конечной производительности, в том числе на телефонах.

III. Фильтры для изображений


Напоследок мы рассмотрим такую область применения матриц, как фильтры для изображений. Если мы рассмотрим цвет в формате RGBA как вектор, то можно предположить, что здесь можно применить трансформацию, аналогичную той, что мы использовали с координатами:



И применить это для картинки по очевидному принципу:

для каждого пикселя картинки {    новый цвет пикселя = [ фильтр ] * старый цвет пикселя}

Если в качестве матрицы будет выступать единичная матрица ничего не изменится, это мы уже знаем. А что будет, если применить фильтры, схожие с трансформациями translate и scale?



Оу. Получились фильтры яркости и контраста. Занятно.

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

А как сделать черно-белое изображение из цветного? Первое, что может прийти в голову это сложить значения каналов RGB, поделить на 3, и использовать полученное значение для всех трех каналов. В формате матрицы это будет выглядеть как-то так:



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

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

А если мы еще и домножим главную диагональ немного, то получится универсальный фильтр насыщенности:



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

Вообще играться с фильтрами можно долго, получая самые разные результаты:



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

Но давайте все же уделим немного внимания тому, где это собственно применить. Понятно, что сама идея замены цвета для каждого пикселя наводит на мысли о шейдерах для обработки фото, либо для постобработки какой-то 3D-сцены, но может быть это еще где-то есть во фронтенде?

Фильтры в CSS


В CSS у нас есть свойство filter. И там, в частности, есть вот такие варианты фильтров, связанных с цветами:

  • brightness (мы его сделали)
  • contrast (сделали)
  • invert (то же, что и контраст, только коэффициенты по главной диагонали с другим знаком)
  • saturate (сделали)
  • grayscale (как уже отметили, это частный случай saturate)
  • sepia (очень размытое понятие, разные варианты сепии получаются игрой с коэффициентами, где мы так или иначе уменьшаем присутствие синего цвета)

И эти фильтры принимают на вход коэфициенты, которые потом в том или ином виде подставляются в те матрицы, которые мы сделали ранее. Теперь мы знаем, как эта магия устроена изнутри. И теперь понятно, как эти фильтры комбинируются в недрах интерпретатора CSS, ведь здесь все строится по тому же принципу, что и с координатами: умножаем матрицы складываем эффекты. Правда пользовательской функции matrix в этом свойстве в CSS не предусмотрено. Но зато она есть в SVG!

Фильтры-матрицы в SVG


В рамках SVG у нас есть такая штука, как feColorMatrix, которая применяется при создании фильтров для изображений. И здесь у нас уже есть полная свобода можем сделать матрицу на свой вкус. Синтаксис там примерно такой:

<filter id=my-color-filter>    <feColorMatrix in=SourceGraphics        type=matrix,        matrix=1 0 0 0 0                0 1 0 0 0                0 0 1 0 0                0 0 0 1 0                0 0 0 0 1    /></filter>

А еще SVG фильтры можно применить к обычным DOM-элементам в рамках CSS, там для этого есть специальная функция url Но я вам этого не говорил!

На самом деле SVG-фильтры в рамках CSS до сих пор не поддерживаются всеми браузерами (не показываю пальцем на IE), но ходят слухи, что Edge окончательно переезжает на хромиумный движок, и старые версии лишатся поддержки в обозримом будущем, так что пора эту технологию осваивать, с ней можно много всякого интересного делать.

А что еще бывает?


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

Kernel matrix


В частности во фронтенде мы встречаем такую штуку, как kernel matrix, и связанными с ней эффектами. Суть проста есть квадратная матрица, обычно 3x3 или 5x5, хотя может быть и больше, а в ней хранятся коэффициенты. В центре матрицы для текущего пикселя, вокруг центра для соседних пикселей. Если матрица 5x5 то появляется еще один слой вокруг центра для пикселей, находящихся через один от текущего. Если 7x7 то еще один слой и.т.д. Иными словами мы рассматриваем матрицу как такое двумерное поле, на котором можно расставить коэффициенты по своему усмотрению, уже без привязки к каким-то уравнениям. А трактоваться они будут следующим образом:

для каждого пикселя картинки {    новый цвет пикселя =        сумма цветов соседних пикселей, умноженных на их коэффициенты из матрицы}

Чистый канвас не очень подходит для таких задач, а вот шейдеры очень даже. Но нетрудно догадаться, что чем больше матрица, тем больше соседних пикселей мы будем использовать. Если матрица 3x3 мы будем складывать 9 цветов, если 5x5 25, если 7x7 49 и.т.д. Больше операций больше нагрузка на процессор или видеокарту. Это неизбежно повлияет на производительность страницы в целом.

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


В рамках SVG у нас есть специальный тег feConvolveMatrix, который сделан как раз для создания подобных эффектов:

<filter id=my-image-filter>    <feConvolveMatrix        kernelMatrix=0 0 0                      0 1 0                      0 0 0    /></filter>

Здесь мы сделали простейший фильтр для картинки, который не делает ничего новый цвет для каждого пикселя будет равен текущему, умноженному на 1, а значения для цветов соседних пикселей умножатся на 0.

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

Если мы начнем расставлять числа по слоям, от большего у меньшему, то получится blur:



Чем больше матрица, чем больше соседних пикселей мы затрагиваем, тем сильнее мылится картинка. Главное здесь не забывать нормализовать значения, а то изображение просто засветится.

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

Если начать экспериментировать со сменой знаков у коэффициентов и расставлять их в разные паттерны, то получатся эффекты sharpen, edge detection и некоторые другие. Попробуйте поиграть с ними самостоятельно. Это может быть полезно.



Таким образом можно делать разные эффекты для фотографий, или даже видео, в реальном времени, и делать их зависящими от каких-то действия пользователя. Здесь уже все зависит от вашей фантазии.

Промежуточные итоги


Обобщим сказанное в этой части:

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

Заключение


Если немного абстрагироваться от запутанных алгоритмов, то матрицы станут вполне доступным инструментом для решения практических задач. С их помощью можно своими руками рассчитывать геометрические трансформации, в том числе в рамках CSS и SVG, строить 3D сцены, а также делать всевозможные фильтры для фотографий или для постобработки изображений в рамках WebGL. Все эти темы обычно выходят за рамки классического фронтенда и больше связаны с компьютерной графикой в целом, но, даже если вы не будете решать эти задачи непосредственно, знание принципов их решения позволит лучше понимать, как устроены некоторые ваши инструменты. Это лишним никогда не будет.

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

Как быстро создать Bootstrap-сайт для бизнеса 6 полезных инструментов

26.09.2020 22:13:23 | Автор: admin


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

Что такое Bootstrap


Bootstrap это открытый и бесплатный фреймворк HTML, CSS и JS. Веб-разработчики по всему миру используют его для быстрого создания адаптивных сайтов и веб-приложений. Существуют и альтернативы, среди которых, например, фреймворки Foundation и UIkit, но Bootstrap считается самым популярным.

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

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

Startup


Startup это drag-n-drop конструктор Bootstrap-тем, который позволяет быстро создавать лендинги для бизнеса. Инструмент предлагает более 300 готовых блоков, которые можно использовать в интерфейсе. В несколько кликов собранный дизайн можно экспортировать в чистый HTMl, CSS, JavaScript.



При этом, итоговый результат будет оптимизирован под отображение на мобильных устройствах.

Pinegrow


Это десктоп-редактор для macOS, Windows и даже Linux, который позволяет создавать Bootstrap-сайты. Это инструмент уже скорее для разработчиков и верстальщиков, ведь он позволяет углубляться в такие моменты, как верстка CSS-сеток и правил, rich visual controls, SASS и LESS и т.п.



Помимо прочего, с помощью Pinegrow можно создавать интерфейсы под фреймворк Foundation и WordPress.

Bootstrap Magic


Еще один инструмент создания тем для Bootstrap 4.0, который подойдет более опытным разработчикам. Это продукт с открытым кодом, который позволяет писать HTML-код прямо в специальном редакторе и тут же генерировать его превью.



Bootstrap Build


Это бесплатный билдер тем на Bootstrap 4 (и как уточняется, скоро появится поддержка пятой версии). Пользователи могут использовать до 500 элементов UI, а также создавать собственные темы на основе готовых шаблонов в специальном редакторе, а затем экспортировать результат работы в SASS-файлы.



Bootstrap Studio


Как и Pinegrow, это десктоп-приложение, но которое работает в формате drag-n-drop. Здесь есть большая библиотека встроенных компонентов, включая хедеры, футеры, галереи и слайдшоу.



Все это позволяет его использование для людей с небольшим опытом веб-разработки.

Codeply


Это так называемый playground, в котором пользователи могут не только создавать темы с помощью редактора drag-n-drop, но и писать код с возможностью просмотра превью. Начать работу можно с помощью редактирования готовых шаблонов есть как простые для лендингов или статей, так и более сложные, вроде контрольных панелей веб-приложений.



Заключение


Это лишь некоторые интересные инструменты, которые позволят создавать красивые Bootstrap-сайты быстрее и делать их функциональными. Делитесь в комментариях ссылками на другие полезные продукты так наш список будет еще более полным.
Подробнее..

Выбираем недорогой CDN. Обзор сервисов и тарифов

23.09.2020 12:10:47 | Автор: admin
Подключить CDN для ускорения загрузки сайта рекомендует чуть ли не каждый инструмент по оптимизации. Беглый поиск показывает, что услугу предоставляют не менее десятка крупных сервисов, однако лишь у немногих можно найти действительно доступные тарифы.

После небольшого ресерча наш рейтинг недорогих CDN провайдеров возглавили BunnyCDN, BelugaCDN и OVH. Сравним их предложения.




BunnyCDN


Сайт: https://bunnycdn.com

По нашей оценке самый конкурентоспособный игрок на рынке недорогих CDN. Тарифы от $0.01/GB по факту потребления без необходимости покупать какую-либо месячную подписку. Есть вариант купить супердешевый план Volume в 7 точках присутствия за $0.005/GB

Огромная география точек присутствия (POP), включая Россию. Для попробовать сходу и без лишних вопросов дают 1ТБ трафика на 14 дней.

Достоинства:

  • 42 точки присутствия в 28 странах на всех континентах, включая Россию
  • отличный интерфейс администратора, вот прям не хотелось выходить
  • есть собственный Wordpress плагин
  • находится в ЕС, если кому важен GDPR


Недостатки*:

  • стоимость трафика для POP в Африке и Южной Америке



BelugaCDN


Сайт: https://belugacdn.com

Второй популярный лоукостер на рынке доставки контента с ценой $0.01/GB, плюс можно дополнительно сэкономить при покупке месячных пакетов трафика.

Здесь по дефолту доступны всего 9 так называемых superPOP, которые есть только в Европе и США. У сервиса есть 30-дневный пробный период, но нужно ввести номер кредитки, поэтому будьте внимательны, чтобы не было потом лишних списаний.

Достоинства:

  • есть свой плагин для Grafana
  • Anti-DDoS по умолчанию


Недостатки*:

  • 9 superPOP на 2 континентах: Европа и США
  • для trial периода хотят номер кредитки



OVH


Сайт: https://www.ovh.com

Пакетные тарифы на CDN от известного французского хостера сравнимы с ценами рассмотренных ранее сервисов только при заказе больших объемов трафика.

Для услуги заявлено всего 3 точки присутствия, что для сетей доставки контента кажется весьма необычным. В таких случаях может быть оптимальнее и дешевле собрать свой CDN на базе VPS или выделенных серверов. Бесплатного пробного периода у OVH нет.

Достоинства:

  • крупная европейская хостинг-компания со своей инфраструктурой
  • Anti-DDoS из коробки


Недостатки*:

  • небольшое число точек присутствия
  • покупка только фиксированного объема трафика (1ТБ, 10ТБ, 100ТБ, и т.д.)
  • нет бесплатного пробного периода

Итоги


Практически все CDN сервисы имеют схожий базовый функционал: API, возможность использовать собственный SSL сертификат, детальную статистику и др. Поэтому обычно ключевым моментом при выборе оказывается цена и расположение точек присутствия. С ценой всё ясно, с точками присутствия выбираем ту сеть, у которой есть POP в регионах с вашей аудиторией.

Если доставка контента ориентирована только на Европу и Северную Америку, можно подключать любой CDN. Это популярные регионы, которые есть у всех провайдеров. Если же на сайт ходят еще пользователи из России и других частей мира смотрите BunnyCDN.


* недостатки изложены по субъективному мнению и как личное оценочное суждение автора. Другими словами, что для автора недостаток для читателя может быть благо
Подробнее..

Перевод 5 самых неприятных фич на сайтах для слепого человека

17.09.2020 14:13:09 | Автор: admin
Вот пять самых раздражающих своей недоступностью веб-элементов, с которыми я сталкиваюсь как слепая пользователь скринридера каждый день.

Для слепых и слабовидящих людей, таких как я, доступность это не просто слово, это реальный выбор: или мы можем работать с сайтом, или не можем.

Как работают скринридеры


Скринридеры позволяют слепым и слабовидящим людям самостоятельно пользоваться компьютерами, телефонами и планшетами. В большинстве скринридеров работает движок Text To Speech (TTS), который преобразует текст с экрана в речь.

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

Вот наиболее распространённые проблемы, с которыми я сталкиваюсь ежедневно.

Неподписанные ссылки и кнопки


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

Например, в ссылке на описание компании подпись Нажмите здесь не даёт никакого представления о том, куда она ведёт, в отличие от ясной фразы Узнайте больше о том, кто мы такие.

Если ссылки и кнопки правильно помечены, то скринридеры могут зачитать надпись вслух. Это означает, что слепым и слабовидящим людям не придётся нажимать ссылку или кнопку, не зная, куда она приведёт.

Кроме немаркированных элементов, также действительно расстраивают ссылки и кнопки без ясного описания. Они должны сопровождаться чётким текстом, куда они ведут при нажатии, а не Нажмите здесь. Никогда не заставляйте пользователей гадать или действовать методом проб и ошибок. Это утомительно и неудобно.

Изображения без описания


Вероятно, это самая распространённая проблема, с которой я сталкиваюсь при просмотре веб-страниц. Описания изображений очень важны для доступности. Они известны как alt text (альтернативный текст).

Скринридеры зачитывают вслух эти описания, так что слепые и слабовидящие люди могут понять содержание изображения в доступной форме. Если у изображений нет альтернативного текста, то скринридеры просто произнесут слово изображение или графика, что не даёт никакого контекста или смысла.

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

Ознакомьтесь с нашими советами по написанию alt text.

Плохое использование заголовков


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

Логическая структура начинается с заголовка 1, затем следует заголовок 2. Заголовок третьего уровня размещается внутри заголовка 2 и так далее

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

Заголовки также помогают визуально разбить контент и улучшить читабельность. Кроме заголовков, пользователи скринридеров могут перемещаться на странице по ссылками, спискам и ориентирам WAI-ARIA.

Недоступные веб-формы


Большинство веб-сайтов так или иначе используют формы. Будь то поиск товара или форма обратной связи. Но если эти формы не маркированы или маркированы неправильно, то мы не можем их использовать.

Например, если поле поиска не помечено, то пользователи скринридеров понятия не имеют о назначении этого поля. Это означает, что люди, использующие программы чтения с экрана, не могут получить доступ к тем же функциям.

Формы обратной связи это эффективный способ для клиентов связаться с вашей компанией. И для пользователей скринридеров нет ничего более неприятного, чем неправильно помеченные эти формы.

Особенно использование капчи. Если она без звука, то мы не самостоятельно заполнить такую форму. Мне часто приходится прибегать к помощи зрячего человека, но такая возможность есть не у всех.

Автоматическое воспроизведение аудио и видео


Большинство людей знает, насколько раздражает веб-страница с громкими рекламными объявлениями, которые внезапно включаются на воспроизведение. Но для пользователей скринридеров ситуация ещё хуже. Автоматическое воспроизведение видео или аудио может заглушить звук скринридера. Это затрудняет поиск кнопок паузы или остановки.

(И если эти кнопки не помечены, то для меня практически невозможно быстро остановить видео, что дополнительное раздражает). Если я не могу остановить звук или видео, я обычно закрываю страницу.

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

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

LaravelДайджест (1420 сентября 2020)

20.09.2020 20:22:28 | Автор: admin

Меняем функционал Laravel UI на Laravel Fortify и кастомизируем Jetstream. Пишем SaaS-приложение. Взлом админки Laravel Nova. Разбираемся с Blade UI Kit.


Laravel Дайджест


На русском языке


  • Меняем Laravel UI на Laravel Fortify
    Как заменить функциональность устаревшего пакета laravel/ui новым пакетом Laravel Fortify.
  • Ускоряем Laravel Sanctum
    Решение проблемы с медленной работой персональных токенов.
  • Кастомизация Jetstream
    Новые параметры пакета каркаса аутентификации и как его настроить под свои нужды.
  • Исправление ошибки Target class does not exist в Laravel 8
    Восьмая версия фреймворка содержит множество изменений. Одно из них удаление дефолтного пространства имён маршрутов, что приводит к ошибкам. Покажу три способа исправить такие ошибки.

Релизы


  • Laravel 8.5
    В день по версии команда усиленным темпами шлифует восьмую Лару.
  • Laravel Honeypot 3.0
    Защита форм от спама. Добавлены blade-компонент и поддержка Inertia.
  • Bagisto 1.2
    Платформа для создания интернет-магазинов, построенная на Laravel+Vue
  • Laravel Weather 2

Уроки



Видео



Телеграм на русском


Подробнее..

Recovery mode Почему для информационных проектов из всех Headless CMS мы часто выбираем Strapi

24.09.2020 08:13:47 | Автор: admin

Существует большое количество (всего порядка 50) Headless CMS. Это системы управления, в которых реализован новый принцип разделения двух слоев данных и представления (логика Jamstack).




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



Какие бывают Headless CMS


Все системы управления, работающие по логике Jamstack, представлены на сайте headlesscms.org. Они разделены, прежде всего, на открытые и закрытые open source и closed source решения.


По способу развертывания, разные HCMS предполагают установку локально или через облачные сервисы.


По способу передачи данных, системы могут поддерживать REST API, GraphQL или оба синтаксиса.


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


Многие HCMS поддерживают управление проектами, изображениями, файлами, данными и доступами пользователей.



Почему Strapi


В списке Headless CMS с открытым исходным кодом Strapi недаром занимает первое место. Это решение пользуется большой популярностью и имеет свыше 28 тысяч звезд на GitHub.




Система предназначается для профессиональных разработчиков. Однако для работы с ней не требуется слишком глубоких и разносторонних познаний в области программирования. В этом главная ценность Strapi она позволяет максимально быстро и с минимальными ресурсами создавать API для работы с данными.



Основные особенности


Strapi представляет собой фреймворк для управления контентом, работающий на Node.js. Это open source-проект, полностью бесплатный. Система разворачивается локально на собственном сервере компании, что обеспечивает безопасность данных.


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


Главные особенности и преимущества Strapi:


  • Открытый исходный код.Система разработана энтузиастами и поддерживается сотнями участников GitHub, которые развивают ее в соответствии с новыми требованиями и технологиями. Она всегда будет доступна и бесплатна.
  • Широкие и гибкие настройки.Панель администратора, как и API, легко настраивается. Функционал расширяется за счет пользовательских плагинов в считанные секунды.
  • RESTful или GraphQL.CMS поддерживает передачу данных посредством и REST, и GraphQL. Это расширяет возможности взаимодействия с разными клиентами, мобильными приложения, IoT-устройствами.
  • Локальное размещение.Размещение на собственном сервере владельца системы гарантирует конфиденциальность и обеспечивает повышенный уровень защиты данных (в том числе в соответствии с европейским стандартом GDPR).
  • Один язык.Система использует JavaScript, что позволяет работать с одним языком как в CMS, так и во фронтенде.
  • Настройки доступа.В системе реализован контроль доступа с учетом разных уровней и ролей пользователей (администраторов).


Применение Strapi


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




На Strapi создаются ультра-быстрые современные сайты и мобильные приложения. Повышенная производительность достигается при использовании Headless CMS в связке со статическим генератором сайтов и обслуживании через CDN.


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


Strapi поддерживает любые статические генераторы сайтов и различные фреймворки для создания пользовательских интерфейсов. Самые популярные из них: Gatsby, React, Vue.js, Nuxt.js, Next.js, Angular.



Как использовать Strapi


Чтобы разработать API с помощью Strapi, предпочтительнее использовать PostgreSQL, MongoDB, MySQL или MariaDB. Установка происходит с использованием npm.


Дальнейшая последовательность действий:


  • Создается директория для нового проекта.
  • Внутри директории выполняется команда для создания нового API.
  • Запускается сервер на Node.js.
  • Регистрируется первый пользователь.
  • Создается тип контента (Content Type структуры данных в Strapi, эквивалент моделей).
  • Добавляются материалы в базу данных.
  • Настраиваются роли пользователей (администраторов, редакторов).

После этого можно взаимодействовать со Strapi через любимый API-протокол для генерации статического контента и/или использовать API-вызовы на регулярной основе, как и к любому бэкенду.




Далее выводится контент, настраивается отображение содержимого, создается страница автора. Впоследствии можно добавить категории материалов, возможность комментирования.


Алгоритм подходит для создания блогов, бизнес-сайтов, интернет-магазинов.



Обслуживание моделей через UI


Еще одна важная особенность Strapi возможность обслуживания моделей данных через пользовательский интерфейс.


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


В Strapi также можно действовать через программную среду, описывая модели данных. Но можно использовать и административный интерфейс, добавляя и редактируя необходимые поля базы данных на лету, сразу в продакшн-системе.


Например, вот как удобно можно указать связь двух моделей в базе данных с помощью UI:




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



Почему мы считаем, что Strapi лучше других Headless CMS


Мы используем бесплатную стабильную версию Strapi, которая позволяет создавать супер-быстрые статические сайты и экономит время разработки.


Система имеет удобную административную панель, легко масштабируется с помощью плагинов. Для работы с ней не требуются углубленные знания в веб-разработке.


Управление контентом осуществляется централизованно. А поскольку все технические решения скрыты на стороне сервера, система менее уязвима для атак.

Подробнее..

Как создать крутой сайт агрегатор

26.09.2020 10:21:13 | Автор: admin
Мы создаем и продвигаем сайты-агрегаторы уже 12 лет. За это время мы сделали и развиваем несколько своих и клиентских сайтов и накопили много опыта и знаний, которыми хотим поделиться в этой статье.

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

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

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

Как будет монетизироваться?


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

Способов монетизации агрегатора может быть несколько:
  • оплата за размещение
  • оплата за приоритетное размещение
  • оплата за заявки/звонки
  • комиссия с продажи
  • рекламные размещения
  • баннерная реклама
  • нативная реклама

Для определения способа монетизации ответьте на следующие вопросы:
  • Что вы предлагаете партнеру?
  • Почему он должен у вас размещаться?
  • Какие выгоды он получит?

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

Как привлекать трафик?


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

Как продумать структуру?


Чтобы написать полноценное техническое задание, вам нужно ответить на вопрос: Зачем этот сайт нужен пользователю?.
Для начала просто распишите всё, что сможет найти и сделать на вашем сайте абстрактный пользователь чем больше, тем лучше. Это и станет отправной точкой вашего проекта. После этого вам нужно оставить только то, на что есть спрос в интернете. Для этого мы собираем семантику будущего проекта.
Теперь, когда у нас мы можем составить список страниц, нужно проработать их структуру и содержание. Для этого нам нужно ответить на несколько вопросов:
  • Кто ваш пользователь?
  • Что он ищет?
  • По каким критериям он выбирает?
  • Какие у него проблемы, возражения, опасения?
  • Что может побудить его к выбору/покупке?

Если вы сразу не определитесь, чем ваш новый продукт будет лучше тех, что уже есть на рынке, то в дальнейшем вам будет крайне трудно их перешагнуть. Без УТП ваш новый агрегатор будет обречен стать одним из кучи сайтов в выдаче, даже если вы заходите в нишу без конкурентов вы же не думаете, что только у вас светлая голова с гениальными идеями?
На выходе у вас должен получится список страниц, которые будут на сайте, список блоков и элементов для каждой страницы.
После этого можно писать техническое задание документ, в котором будет отражена структура будущего проекта. Имея на руках чёткое ТЗ вы сэкономите время на поиски подрядчика и согласование стоимости разработки. Многие не хотят заморачиваться, а просто скидывают пример или спрашивают Сколько будет стоить сделать сайт как AliExpress?. Как говорится, какой вопрос, такой и ответ.

Как оптимизировать сайт под поисковые системы?


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

image

Как выбрать движок для сайта?


Во-первых, нужно выбрать язык программирования и подходящий под задачи сайта фреймворк или CMS. Во-вторых, нужно будет подобрать подходящий JavaScript-фреймворк для фронтенда.
Главный вопрос, который будет нужно решить CMS или фреймворк?. CMS позволяют быстро запустить базовый функционал сайта. В них уже есть готовые универсальные HTML-шаблоны и готовая панель управления, поэтому CMS неплохо подойдет для типового сайта, например, для каталога продуктов или организаций с фильтрацией по некоторым критериям. Но ТЗ проекта может выходить за стандартные рамки. Может оказаться, что для CMS не будет готового решения и тогда разработка может значительно усложниться. В этом случае можно посмотреть в сторону веб-фреймфорков, которые не имеют тех ограничений, что есть у CMS. CMS призваны лишь упростить и ускорить разработку сайта с нуля. На этапе проектирования технический специалист должен внимательно оценить сложность и типичность проекта, тогда можно будет ответить на вопрос CMS или фреймворк?.

Если выбран путь создания сайта на CMS, то определиться с инструментом легко. CMS должна быть просто популярной. Для неё можно будет найти максимальное количество готовых решений. Будет легко найти специалиста популярного направления. На вопросы при разработке можно будет легко найти ответы. Самые популярные CMS написаны на языке PHP, который также является одним из самых популярных языков в веб-программировании. Уже много лет лидером является CMS Wordpress. Скорее всего на нём и можно будет остановиться. Wordpress обладает всеми присущими CMS качествами, имеет самую большую базу модулей, шаблонов и плагинов. Следующим по популярности можно выделить 1С-Битрикс, хотя его использование не всегда может быть логично. Но что, если вы решили разрабатывать с нуля? Какими качествами должен обладать язык программирования для сайта-агрегатора? Мы не будем рассматривать разработку на чистом языке без фреймворка. Фреймворк является каркасом сайта и уже содержит функции, которые в любом случае пришлось бы писать программисту. Поэтому выбор языка сводится к выбору фреймворка. Как и в случае с CMS, важнейшим качеством будет популярность, как языка, так и самого фреймфорка. Для сайта агрегатора также будет важна скорость разработки. В нашем случае лучше подойдет легковесный и гибкий фреймворк. Последним, но не менее важным, будет наличие специалистов по выбранной платформе. Для языка PHP лучшим выбором будут фреймворки Laravel, Yii. Для Python Django, Flask. Ruby on Rails для Ruby и .NET тоже можно рассматривать как вариант, если есть разработчики под них.
После выбора бэкенд-платформы, нужно обратить внимание и на фронтенд. Простого JavaScript и популярной библиотеки JQuery может быть недостаточно для создания динамичного и современно выглядящего сайта. Современные сайты принято разрабатывать на так называемых реактивных JavaScript-фреймворках. Лидеры React и Vue, их популярность только растет. Angular тоже популярен, но может быть слишком сложным и медленным для сайта-агрегатора.

Как выбрать подрядчика


Вы можете обратиться в агентство или собрать свою команду фрилансеров, в которой должны быть следующие специалисты:
  • Проект-менеджер
  • Дизайнер
  • Программист
  • Верстальщик/фронтендер
  • Тестировщик
  • Контент-менеджеры
  • SEO-оптимизатор

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

Как поддерживать контент?


Помимо продвижения и технического сопровождения не стоит забывать, что главным преимуществом агрегатора является актуальный полноценный контент. Есть несколько возможных способов сбора данных:
когда данные собирает и размещает специалист на вашей стороне
когда партнеры сами размещают информацию, используя возможности сайта (обычно это личный кабинет)
когда данные обновляются автоматически (через интеграцию, фид, выгрузку и т.д.)
В зависимости от сферы могут использоваться комбинированные способы. Например, у нас на 32top.ru часть контента размещают наши специалисты, часть добавляют клиники через личный кабинет (но некоторые данные проходят обязательную модерацию), а актуальное расписание мы получаем посредством интеграции с медицинскими информационными системами клиник.

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

Категории

Последние комментарии

© 2006-2020, personeltest.ru