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

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

Перевод Стилизация контейнеров для содержимого веб-страниц

03.07.2020 16:05:03 | Автор: admin
Содержимое веб-страниц должно быть размещено в некоем элементе, ширина которого, ограничивающая ширину содержимого, позволяет пользователям удобно работать с материалами сайта. Такие элементы называют обёртками (wrapper) или контейнерами (container). Стилизовать контейнеры средствами CSS можно по-разному. Некоторые способы работы с контейнерами ставят дизайнера перед необходимостью решать достаточно сложные задачи.



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

Общие сведения


Когда вы, при разговоре о некоем элементе веб-страницы, узнаёте о том, что речь идёт об обёртке или о контейнере, это значит, что, на самом деле, перед вами группа элементов, которая обёрнута в другой элемент или размещена внутри этого элемента. Если, настраивая веб-страницу, не пользоваться дополнительными элементами, отведя роль контейнера элементу <body>, то стилизовать этот элемент можно так:

body {max-width: 1170px;margin-left: auto;margin-right: auto;padding-left: 16px;padding-right: 16px;}

Но в современных условиях использование в качестве контейнера элемента <body> может оказаться непрактичным. Контейнер позволяет не допустить выхода дочерних элементов за его границы.


Контейнер не даёт дочерним элементам выходить за его границы

Здесь имеется боковая и основная области страницы. Обе эти области находятся внутри элемента-контейнера. Ему назначен класс .wrapper. Среди прочих свойств контейнера, естественно, задана и его ширина. Структура HTML-кода такой страницы выглядит так:

<div class="wrapper"><aside>...</aside><main>...</main></div>

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


Страница без элемента-контейнера, включающего в себя её содержимое

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

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


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

  1. Использование контейнера улучшает читабельность содержимого страницы. Без контейнера содержимое, вроде текста, может растягиваться на всю ширину экрана. На маленьких экранах подобное может давать вполне приемлемый результат. Но на больших экранах это выглядит очень плохо.
  2. Группировка элементов дизайна страницы упрощает настройку расстояния между ними.
  3. Если элементы дизайна нужно сгруппировать по столбцам, это будет сложно сделать без использования контейнера.

Настройка элемента-контейнера средствами CSS


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

Настройка ширины контейнера



Элемент-контейнер с настроенной шириной

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

.wrapper {width: 1170px;}

Здесь показана установка ширины элемента с классом .wrapper в 1170px, но, на самом деле, свойство width для настройки ширины контейнеров использовать не рекомендуется. Дело в том, что это приводит к необходимости горизонтального скроллинга страницы в том случае, если ширина области окна браузера, доступной для вывода страницы, меньше 1170px. Решить эту проблему можно, воспользовавшись свойством max-width:

.wrapper {width: 1170px;max-width: 100%;}

Хотя это вполне рабочий приём, можно полностью избавиться от свойства width и, как в следующем примере, пользоваться лишь свойством max-width:

.wrapper {max-width: 1170px;}

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

Выравнивание контейнера по центру страницы



Контейнер, выровненный по центру страницы

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

.wrapper {max-width: 1170px;margin: 0 auto;}

Вот как, в соответствии со спецификацией CSS, ведут себя отступы, которым назначено значение auto:

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

Если вас интересуют подробности об использовании ключевого слова auto в CSS взгляните на эту мою статью.

Я воспользовался здесь конструкцией margin: 0 auto. Она сбрасывает размеры верхнего и нижнего отступов в значение 0, а левый и правый отступы настраивает в соответствии с особенностями применения ключевого слова auto. У такого шага есть некоторые последствия, о которых я расскажу ниже. А пока же хочу отметить, что рекомендуется использовать полный вариант вышеописанной сокращённой конструкции для настройки внешних отступов:

.wrapper {max-width: 1170px;margin-left: auto;margin-right: auto;}

Настройка левого и правого внутренних отступов



Горизонтальные (левый и правый) внутренние отступы

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

.wrapper {max-width: 1170px;margin-left: auto;margin-right: auto;padding-left: 16px;padding-right: 16px;}

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

Использование процентных значений при настройке контейнеров


Мне, после публикации исходного варианта этого материала, написали об использовании процентных значений при настройке контейнеров. В частности, речь идёт о применении CSS-свойства max-width: 90% вместо использования свойств padding-left и padding-right.


Использование процентных значений при настройке контейнеров и ситуации, когда значение max-width: 90% приводит к приемлемым и неприемлемым результатам

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

.wrapper {max-width: 90%;margin-left: auto;margin-right: auto;}/* Медиа-запрос для больших экранов */@media (min-width: 1170px) {.wrapper {max-width: 1170px;}}

В результате оказывается, что, используя процентное значение, мы усложняем CSS-код. Для того чтобы избавить себя от необходимости применения медиа-запроса, мы можем использовать фиксированное значение для ширины. Ещё одно решение, предложенное в этом твите, заключается в применении комбинации свойств width: 90% и max-width: 1170px:

.wrapper {width: 90%;max-width: 1170px;margin-left: auto;margin-right: auto;}

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

Свойство display элемента-контейнера


Так как для оформления контейнеров используют теги <div>, контейнеры, по умолчанию, являются блочными элементами. Что если понадобится поменять свойство контейнера display на grid, сделав это для того чтобы разместить его дочерние элементы в сетке?

Я не рекомендую этого делать, так как это идёт вразрез с идеей разделения ответственностей. Элемент-контейнер, обёртка, это сущность, предназначение которой заключается в том, чтобы оборачивать другие элементы. Если нужно разместить дочерние элементы контейнера в сетке, тогда стоит добавить в контейнер ещё один <div>, включающий в себя другие элементы, свойство которого display установлено в значение grid. Это будет проще и чище, чем настройка сетки средствами основного контейнера. Такой подход, кроме того, позволяет говорить о том, что в будущем проект, в котором он используется, будет легче поддерживать.

Пусть имеется такой контейнер:

<div class="wrapper"><!-- Содержимое --></div>

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

.wrapper {display: grid;grid-template-columns: 2fr 1fr;grid-gap: 16px;}

Лучше будет использовать такой HTML-код:

<div class="wrapper"><div class="featured-news"><!-- Элементы, которые нужно разместить в сетке --></div></div>

Элемент с классом featured-news можно стилизовать так:

.featured-news {display: grid;grid-template-columns: 2fr 1fr;grid-gap: 16px;}

Обратите внимание на то, что в этом примере мы использовали отдельный элемент <div> в качестве ещё одной обёртки для содержимого страницы. На имена классов, использованных здесь, можете внимания не обращать. Для решения подобной задачи можно подобрать более удачные имена классов, которые подойдут для многократного использования на различных страницах сайта. Однако именование CSS-сущностей выходит за рамки данного материала.

Настройка внешних отступов, разделяющих элементы-контейнеры


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

.wrapper {margin: 0 auto;}

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

Я имею в виду такую схему стилизации:

.wrapper-variation {margin-top: 50px;}

Свойство margin для элемента с классом .wrapper-variation не будет применено к элементу из-за того, что его переопределяет свойство margin: 0 auto. Краткая форма настройки свойства переопределяет его полную форму. Для того чтобы подобного избежать, рекомендуется в таких случаях использовать полную форму записи свойств. То есть, при стилизации элемента с классом .wrapper нужно поступить так:

.wrapper {max-width: 1170px;margin-left: auto;margin-right: auto;padding-left: 16px;padding-right: 16px;}

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


Автономный контейнер и контейнер внутри элемента <section>

Вот HTML-код:

<div class="wrapper mb-5"></div><section><div class="wrapper"></div></section><div class="wrapper"></div>

Вот стиль:

.mb-5 {margin-bottom: 3rem !important;}

При таком подходе CSS-код для элемента-обёртки остаётся в неизменном виде, а расстояния между элементами настраиваются с использованием вспомогательных CSS-классов. Тут у вас может появиться вопрос о том, зачем мне понадобилось использовать на странице несколько контейнеров, когда можно обойтись одним. Обратите внимание на то, что в вышеприведённом HTML-коде имеется элемент <section>, расположенный между двух элементов-обёрток.

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

Контейнер внутри полноэкранного элемента


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

HTML-структура страницы в такой ситуации может выглядеть так:

<section><div class="wrapper"></div></section><section><div class="wrapper"></div></section>

Элемент <section> занимает 100% ширины области просмотра. Этому элементу можно назначить фоновое изображение или фоновый цвет. Контейнер, находящийся внутри этого элемента, не даёт содержимому занимать всю ширину области просмотра.


Элемент <section> занимает всю ширину области просмотра, контейнер ограничивает пространство, в котором выводится содержимое страницы

На этом рисунке у элемента <section> задано фоновое изображение. Он занимает всю ширину области просмотра, а содержимое страницы, выводимое в контейнере, ограничено шириной контейнера.

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


Нужен ли контейнер для оформления верхнего блока страницы, который часто называют Hero Section? Это зависит от каждой конкретной ситуации. Исследуем два самых распространённых подхода к оформлению верхних блоков страниц.

Первый подход предусматривает центровку содержимого блока и ограничение ширины содержимого.


Ширина содержимого верхнего блока страницы ограничена

Второй вариант предусматривает распределение содержимого в пределах верхнего блока.


Содержимое распределено в пределах верхнего блока страницы

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

Верхний блок страницы, содержимое которого выровнено по центру


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

<section class="hero"><h2><font color="#3AC1EF">How to make bread at home</font></h2><p>....</p><p><a href="http://personeltest.ru/aways/habr.com/sign-up">Sign up</a></p></section>

При стилизации вышеприведённого HTML-кода выровнять его содержимое по центру можно, воспользовавшись свойством text-align:

.hero { text-align: center; }

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

Проблема 1: содержимое раздела прижимается к краям области просмотра


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


Содержимое раздела прижато к его краям

Проблема 2: слишком большая длина строк текста на экранах большого размера


На экранах большого размера текст, оформленный тегом <p>, может быть очень тяжело читать из-за того, что длина абзаца окажется слишком большой. В соответствии с этим документом, рекомендованное число символов в строке составляет 45-75. Выход длины строки за пределы этого диапазона усложняет чтение.


Длина строки слишком велика

Решение проблем


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

<section class="hero"><div class="hero__wrapper"><h2><font color="#3AC1EF">How to make bread at home</font></h2><p>...</p><p><a href="http://personeltest.ru/aways/habr.com/sign-up">Sign up</a></p></div></section>

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

.hero__wrapper {max-width: 720px;margin-left: auto;margin-right: auto;padding-left: 16px;padding-right: 16px;}

Выровнять содержимое верхнего блока страницы по центру можно, используя любой удобный подход. Тут всё зависит от каждой конкретной ситуации. В данном примере для выравнивания контента достаточно воспользоваться свойством text-align: center.

Как выравнивать контейнер: по центру, или по левому краю страницы?


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

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


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

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

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


Редко бывает так, что все элементы-контейнеры, используемые в некоем проекте, имеют одну и ту же ширину. Ширина контейнера может меняться в зависимости от содержимого контейнера и от того, как именно он используется. Большую гибкость в работе с контейнерами даёт использование CSS-переменных. Это, кроме того, весьма современный подход к настройке контейнеров. Рассмотрим пример.

Вот HTML-код контейнера:

<div class="wrapper"></div>

Вот стиль:

.wrapper {max-width: var(--wrapper-width, 1170px);margin-left: auto;margin-right: auto;padding-left: 16px;padding-right: 16px;}

Если вы внимательно прочли CSS-код, вы могли заметить, что var() передаётся два значения: первое это переменная --wrapper-width, второе это обычное значение 1170px. Второе значение является запасным. Смысл его существования заключается в том, что оно будет использовано в том случае, если значение переменной --wrapper-width окажется неустановленным.

Что это значит? А это значит, что в наших руках оказывается инструмент для создания различных вариантов элементов-обёрток благодаря возможности переопределения значения переменной --wrapper-width. Выглядит это так:

<div class="wrapper" style="--wrapper-width: 720px"></div>

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

  • Добавление к элементу нового класса.
  • Копирование и дублирование существующих стилей.

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

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

Вот HTML-разметка:

<div class="wrapper wrapper--small"></div>

Так выглядит стиль:

.wrapper--small {--wrapper-width: 720px;/* благодаря этому стандартная ширина контейнера будет переопределена. */}

Здесь можно найти рабочий пример.

Использование display: contents


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

Вот разметка:

<header class="site-header"><div class="wrapper site-header__wrapper"><!-- Содержимое заголовочной области сайта --></div></header>

Вот стиль:

.site-header__wrapper {display: flex;flex-wrap: wrap;justify-content: space-between;}


Элемент-обёртка

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

.site-header__wrapper {display: contents;}.site-header {display: flex;flex-wrap: wrap;justify-content: space-between;}

Здесь, благодаря использованию свойства display: contents, элемент-обёртка будет как бы скрыт. Теперь, когда свойство display: flex применяется к элементу с классом .site-header, дочерние элементы контейнера становятся дочерними элементами .site-header.


Заголовочная часть сайта занимает, в ширину, всё доступное пространство

Отзывчивый фон и фиксированное содержимое


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

Вот HTML-разметка:

<section><div class="wrapper"></div></section>

Вот стили:

section {background-color: #ccc;}.wrapper {max-width: 1170px;margin-left: auto;margin-right: auto;padding-left: 16px;padding-right: 16px;}

Здесь значения свойств margin-left: auto и margin-right: auto вычисляются путём взятия половины ширины области просмотра и вычитания из неё ширины содержимого. Того же самого можно добиться с использованием внутренних отступов.


Внутренние отступы

section {padding: 1rem calc(50% - 585px);}

Но дело пока ещё не сделано. На мобильных устройствах содержимое будет прижато к краям области просмотра. Решить эту проблему можно, например, так:

section {padding: 1rem;}@media (min-width: 1170px) {section {padding: 1rem calc(50% - 585px);}}

В качестве альтернативного решения можно предложить применение новой CSS-функции max(). Используя её, мы задаём минимальный размер внутреннего отступа, равный 1rem, а в качестве второго значения, передаваемого ей, указываем выражение 50% 585px.

section {padding: 1rem max(1rem, (50% - 585px));}

Если вам интересны подробности о CSS-функциях min(), max() и clamp() вот мой материал на эту тему.

Как вы стилизуете элементы-контейнеры?

Подробнее..

Мир изменился CQRS и ES встречаются в PHP чаще, чем кажется

30.04.2021 20:15:00 | Автор: admin

Генри Форд чуть не прогорел на своей классической фразе про пятьдесят оттенков черного. General Motors стала предлагать разноцветные модели Chevrolet, Pontiac, Buick, Oldsmobile и Cadillac и не прогадала. Глядя на это, даже упрямый Форд изменил свое мышление и разработал новый Ford A, вернувший его на автомобильный Олимп. Бывают времена, когда парадигма мышления должна стать новой ибо человек умирает тогда, когда перестаёт меняться Генри Форд.

Пришло время и для разработчиков. Command Query Responsibility Segregation (CQRS) и Event Sourcing (ES) уже не миф они реально работают. Конечно, не для всех задач как и классический черный цвет Форда, PHP никуда не исчез и нужен по-прежнему. Но теперь уже есть задачи, где мы встречаемся с CQRS и ES чаще, чем нам кажется. Антон Шабовта на PHP Russia 2021 расскажет, как смена парадигмы и взгляд с другой стороны помогают разработчикам. А перед конференцией мы расспросили Антона подробнее о его новых взглядах на разработку, PHP и, конечно, оCQRS и ES.

Антон, расскажи о себе и о своей работе. Чем ты занимаешься?

Последние 12 лет я в коммерческой разработке и большая часть времени занимался проектами связанными с E-Commerce. Но 3 года назад мне захотелось применить свои знания в проектах другой сферы. Так я пришел в сервисную команду Onlinerа это крупнейший белорусский медиа портал с огромным количеством сервисов. Нашу команду разработки можно условно разделить на две части. Команда каталога занимается основном продуктом каталогом товаров. В нем почти два миллиона позиций от тысяч магазинов а это десятки миллионов товарных позиций. И этот действительно большой и сложный E-Commerce продукт, который продолжает развиваться и расти.

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

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

Твой доклад на конференции будет про долгий путь к CQRS и Event Sourcing. Это связано с твоей карьерой разработчика?

Да. Впервые я столкнулся с подходами Command Query Responsibility Segregation (CQRS) и Event Sourcing (ES) еще при работе с E-Commerce в 2015 году. И это стало важной вехой в моей карьере разработчика. Информации о CQRS и ES было много, но столько же возникало вопросов, мифов и недопонимания. Что именно представляют собой эти технологии? Как их использовать? Где стоит применять и какие проблемы они действительно призваны решать? Так вот одна из моих целей на PHP Russia 2021 развенчать часть этих мифов и показать, что мы сталкиваемся с CQRS и ES намного чаще, чем кажется, даже если раньше мы никогда не слышали эти слова.

В 2017 году, проработав два года с CQRS и ES, я сделал доклад об этом в рамках митапов Минской PHP User Group. Но, пересмотрев слайды, я понял, что в корне неверно подходил к объяснению этих технологий. За пять лет мое понимание значительно преобразилось, и я надеюсь, что на этот раз смогу лучше объяснить. Так что во многом доклад для PHP Russia 2021 это еще и работа над ошибками.

У тебя есть опыт с Erlang и Java (про С/С++, C# и Python знаем), или же ты целенаправленно изучаешь практики оттуда, чтобы рассмотреть их для PHP?

По-разному, it depends. Исторически так сложилось, что многие книги по архитектуре программных систем используют примеры и понятия из Java-мира. И чтобы не упустить какие-то ключевые моменты и важные нюансы, пришлось разбираться в Java. Недавно стряхнул пыль с этих знаний, когда искал решения для своих проектов и разбирался с фреймворками Axon и Akka. В целом же, практики из одного языка, по крайней мере, из Java, очень легко и просто переносятся на PHP.

С# я начал изучать, когда разбирался в устройстве фреймворка NServiceBus. В нем реализовано много классных решений, связанных с MBA (message-based architecture), SOA (service-oriented architecture) и сопутствующими технологиями.

Erlang это вообще отдельная история. Интерес к нему пришел в процессе изучения классического понятия ООП (объектно-ориентированного программирования) от Алана Кея и модели экторов. Это реально классный язык, совершенно непохожий на другие. Не могу сказать, что готов сейчас писать на нем production ready код, но изучать его концепции, конечно, продолжу.

Получается, что ты изучаешь, условно говоря, не какие-то языки, а перенимаешь практики из других языков, которые там успешны?

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

Помогает ли знание других языков лучше писать на PHP?

И да, и нет. С одной стороны, многие практики и идеи можно (и нужно!) применять в PHP, особенно из близких по духу по подходам языков (Java, C#). ООП-модель PHP очень близка к этим языкам. К тому же мир разработки в PHP очень сильно изменился, например, после выхода фреймворка Symfony 2. Команда Symfony проделала колоссальную работу по прививанию паттернов проектирования в PHP community. Но большинство паттернов были придуманы не для PHP, а для других языков, в том числе, для Smalltalk и Java.

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

Когда нужны подходы из других языков, а когда лучше по старинке или попроще?

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

Я стараюсь для себя придерживаться KISS-принципа, то есть Keep it simple, stupid: если есть возможность делать что-то проще и не усложнять, то лучше так и сделать. Такие серьезные подходы как CQRS и ES несут много изменений не только в коде, а даже в самой модели мышления программиста. Мы ставим себя в определенные рамки, и за это придется платить. Не бывает серебряной пули в программировании.

Поэтому внедрять CQRS и ES не глядя, просто потому что можем очень-очень плохая идея. Можно получить намного больше проблем, чем пользы. Конечно, когда-то это оправдано, но не всегда. Поэтому нужно хорошее изучение problems face, чтобы понимать, зачем мы внедряем эту технологию и какую проблему бизнеса пытаемся ею решить.

Что дают эти подходы разработчику, в чем помогают?

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

Поэтому нет популярных фреймворков для CQRS и ES?

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

С другой стороны, ES и CQRS неразрывно связанны именно с моделированием домена приложения, то есть это доменная модель. А хорошая доменная модель не должна зависеть от фреймворка. Но мы можем ее внедрить в любой фреймворк, с котором нам удобно будет работать.

Создатель этих подходов Грег Янг любит повторять в своих докладах, что для реализации ES и CQRS достаточно двух операций:

  1. Сравнение по образцам (pattern matching);

  2. Лево-ассоциативная свертка списка (left fold) функция высшего порядка.

И многие языки поддерживают эти операции уже на уровне стандартной библиотеки. Например, тот же left fold в PHP существует как array_reduce, и дополнительно можно придумать другие варианты реализации.

Pattern matching, к сожалению, полностью в PHP еще не реализован, хотя работа в этом направлении ведется. Но та малая часть из pattern matching, которая нужна для имплементации ES, легко эмулируется в user-land коде.

Есть также много технологий, которые работают вокруг и рядом с CQRS и ES те же message-based architecture и service-oriented architecture. Для этих технологий уже есть фреймворки, хотя достаточно популярных в PHP-мире пока не сформировалось. Какие-то работы сейчас ведутся и какие-то фреймворки вырастают. Но enterprise production ready, решений уровня того же NServiceBus в C# либо Axon в Java, пока в PHP не сложилось. Ждем!

А есть ли учебник, где на пальцах правильно объясняются эти подходы?

Изучать CQRS и ES стоит с просмотра докладов отца-основателя Грега Янга, с его публичных выступлений, статей, материалов и записей в блоге. Он подробно пишет, как он пришел к этим подходам, и из каких проблем они возникли. Для продолжения есть его книга Versioning in an Event Sourced System там вы найдете для себя кое-какие нюансы.

Много материалов по ES и CQRS подходам можно найти в документации Microsoft. У них есть даже более развернутый вариант, который вышел в виде отдельной книги Exploring CQRS and Event Sourcing. Предисловие к книге написал тот же Грег Янг.

Еще этим технологиям много внимания уделяют те, кто пишут и работают с DDD-подходом (Domain-Driven Design), например, Vaugh Vernon. И у него есть книга Implementing Domain-Driven Design, в которой большая глава посвящена именно ES и CQRS.

Кому можно верить, не проверяя, в мире разработки и PHP: Фаулеру, Мартину, кому-то еще?

Никому. Серьезно. Мартин Фаулер, Роберт Мартин, тот же Грег Янг, а также другие авторы тратят сотни часов времени, чтобы поделиться своими знаниями в статьях, в записях блогов, в докладах и в книгах. Иногда пишутся целые научные работы по каким-то подходам. Это действительно круто, что мы имеем доступ к этой информации.

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

Я считаю, что безоговорочное доверие авторитетам, этот аристократичный синдром, наравне с культом карго это одна из ключевых проблем современного IT.

То есть ты сам не придерживаешься этого всего?

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

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

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

Чего ты ждёшь от конференции в этом году?

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

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

  • Что нас ждет в PHP 9?

  • Какая будет следующая большая цель на этот релиз? Возможно, это будет асинхронный PHP это важный лично для меня вопрос. На прошлом PHP Russia я читал доклад именно про асинхронный PHP.

  • Вопрос, волнующий всех: появятся ли у нас Generic-типы? Недавно добавили Union-типы. Скорее всего, скоро добавят пересечения, но Generic это то, что возникает так или иначе в вопросах на каждой PHP-конференции.

И афтепати, конечно!

PHP Russia 2021 пройдёт в гибридном формате будет и офлайн, и онлайн. Онлайн-зрители смогут задавать вопросы спикерам в зале, принимать участие в дискуссиях и участвовать в активностях на стендах партнёров.

Мы с нетерпением ждём нашу встречу в офлайне 28 июня 2021 года. Сегодня последний день до повышения цены сейчас стоимость очного участия 27 000 рублей

Подписывайтесь на наши соцсети, чтобы быть в курсе новостей (FB,VK,Telegram-канал,Twitter), общайтесь с коллегами в чате.

Подробнее..

Простой план сохранения онлайн-бизнеса при пожаре в дата-центре

11.03.2021 18:13:13 | Автор: admin

Если инфошум о замедлении Твиттера вчера не позволил вам увидеть важное, то сообщаю. Вчера, 10 марта, произошел пожар в дата-центре провайдера OVH в Страсбурге, который входит в топ-5 крупнейших провайдеров. Полностью уничтожен центр обмена данными SBG2 (выгорел полностью) и на 30% ЦОД SBG1 (выгорело несколько боксов). Остальные 2 здания, находившиеся рядом, залиты водой.

Восстановление затопленных ЦОДов займет не менее 2 недель. В дата-центре размещались крупные европейские компании, часть из них уже заявили, что данные потеряны безвозвратно.

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

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

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

1. Купите отдельные IP-адреса для боевых проектов

Если у вас облако, сервер или VPS, то один IP-адрес уже в комплекте. Если у вас виртуальный хостинг, то докупите к нему выделенный IP-адрес. В нештатных ситуациях это поможет быстро сменить хостинг или перенести проект на другой сервер. Обойдется примерно в99 руб. в месяц.

2. Используйте NS-сервера регистратора домена

Хостинг провайдеры предлагают перенести обслуживание DNS на свой хостинг. Не соглашайтесь, оставьте домен на NS-серверах регистратора домена, а в А-записи домена пропишите IP-адрес хостинга. Так вы сможете быстрее сменить хостинг в случае аварии. При изменении А-записи обновления распространятся в течение 1 часа, и ваш проект через час уже будет доступен на новом хостинге. Но если же вы будете менять NS-сервера, то обновления могу занять до 72 часов. Обычно регистраторы предоставляют услугубесплатно, но RU-center хочет денег.

3. Включите локальное резервное копирование на хостинге

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

4. Храните 1-3 локальных бэкапа

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

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

Я рекомендую использовать облака как основное хранилище резервных копий, но вы можете использовать и простой сервер или дешевый хостинг от другого провайдера. Главное, чтобы сервера этого другого провайдера располагались не в том же дата-центре, что и основной сайт. Но облака гораздо дешевле, хранение одного терабайта там стоит обычнооколо 500 руб. в месяц. Можно использовать Google Cloud, AWS или Yandex Cloud для них есть готовые инструменты резервного копирования, поэтому их легко настроить. Обычно рекомендуют хранить 7 копий за неделю, 4 копии за месяц, 12 копий за год так и сделайте.

6. Поверяйте бэкапы раз в неделю

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

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

Хотя бы изредка восстанавливайте свои резервные копии на тестовом сервере. Идеально делать это раз в месяц, минимум хотя бы раз в 3 месяца. Одно дело иметь резервные копии, и совсем другое иметь реально работающие резервные копии. Вы должны быть уверены, что в нужный момент бэкапы вам реально помогут и у вас есть возможность их быстро развернуть. Удобно и дешево можно разворачивать облачные сервера на reg.ru или hetzner.cloud, потратитемаксимум 100-200 руб за раз. Процедура займет всего 1-2 часа, но убережет от проблем в будущем.

Вот и все, просто соблюдайте хотя бы эти простые правила.

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

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

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

Подробнее..

Как стать Front-End разработчиком

25.07.2020 02:14:21 | Автор: admin
image

Кто такой Front-End разработчик?


Front-End разработчик это человек который пишет код для внешнего вида сайта, также есть Back-End разработчик который пишет код для функциональной части сайта. Если скрестить эти две профессии получится Full-Stack разработчик

1. Азы которые нужно знать


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

image

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

Что бы хорошо писать дизайн для сайта вам не всегда хватит языка CSS, поэтому вам нужны азы JavaScript-а. Но если-же вы полностью выучите язык JavaScript и Node.js вы вполне сможете писать даже Back-End и стать Full-Stack разработчиком

image

2. Время


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

image

3. Математика


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

4. Обучающие ресурсы


Для азов HTML и CSS посоветую эти видео
HTML: www.youtube.com/watch?v=5pBcKKiZSGE
CSS: www.youtube.com/watch?v=iPV5GKeHyV4
После можете читать и узнавать аспекты HTML и CSS на htmlbook.ru

Для азов JavaScript советую это видео
www.youtube.com/watch?v=Bluxbh9CaQ0&t=5328s
Потом JavaScript во всех аспектах можно доучить на learn.javascript.ru
Подробнее..

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

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

Подробнее..

Веб-компоненты проще, чем вы думаете

14.03.2021 20:05:08 | Автор: admin

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

Однако в недавнем проекте, который создан для более легкого изучения HTML (Конечно, путем добавления зомби и глупых шуток), я решил, что необходимо описать каждый элемент HTML в спецификации. Не считая той конференции, я впервые начинал знакомство с <slot> и <template> элементами, и когда я захотел написать что-то интересное, мне пришлось углубиться в тему.

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

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

Я здесь, чтобы сказать вам: да, вы можете создать веб-компонент. Давайте оставим все отвлекающие факторы, страхи и даже закуски за дверью, чтобы сделать все вместе.

Начнем с <template>

<template> - это HTML элемент, позволяющий создать нам шаблон (HTML структуру для веб-компонентов).

Код
<template>  <p>The Zombies are coming!</p></template>

Элемент <template> - очень важный, потому что позволяет держать все вместе. Это как база для вашего дома, база, с которой начинает строиться все, что мы называем готовым зданием. Давайте использовать этот небольшой фрагмент кода для нашего <apocalyptic-warning> компонента, который оповещает нас о наступлении зомби-апокалипсиса.

Тогда есть компонент <slot>

<slot> - это всего лишь другой HTML элемент, как и <template>. Но в нашем случае <slot> настраивает то, что <template> отображает на странице.

Код
<template>  <p>The <slot>Zombies</slot> are coming!</p></template>

Здесь мы добавили слот с словом "Zombies" (Слово ли это?) в разметку <template>. Если мы ничего не делаем со слотом, по умолчанию он отображает контент между тегами. В нашем случае это будет "Zombies".

Использование <slot> похоже на placeholder. Мы можем использовать placeholder, тогда будет отображаться текст по умолчанию, или указать что-то другое с помощью атрибута name.

Код
<template>  <p>The <slot name="whats-coming">Zombies</slot> are coming!</p></template>

name атрибут сообщает веб-компоненту о том, какое содержание должно быть отображено в <template>. Прямо сейчас у нас есть "whats-coming" слот. Мы предполагаем, что зомби придут первыми при наступлении зомби-апокалипсиса, но <slot> дает нам гибкость, чтобы вставить что-то еще, если окажется, что первыми придут роботы или оборотни.

Использование веб-компонента

Технически, мы закончили писать компонент и уже можем вставить в любое место, где захотим.

Код
<apocalyptic-warning>  <span slot="whats-coming">Halitosis Laden Undead Minions</span></apocalyptic-warning><template>  <p>The <slot name="whats-coming">Zombies</slot> are coming!</p></template>

Видите, что мы делаем? Мы помещаем <apocalyptic-warning> компонент на страницу так же, как и любой другой HTML элемент. Однако мы добавили <span> в слот "whats-coming". <span> и его содержимое будет отображено на месте "Zombies", когда компонент отобразится.

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

Все еще со мной? Не так уж и страшно, не правда ли? Что ж, минус зомби. Нам еще надо будет немного поработать, чтобы сделать замену <slot>возможной, именно здесь мы начинаем

Регистрация компонента

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

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

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

Код
// Определяем кастомный веб-компонент с подходящим именемcustomElements.define("apocalyptic-warning", class extends HTMLElement {    // Наследование обеспечивает, что мы имеет свойства и методы по умолчанию встроенного HTML    элемента    // Вызывается всякий раз, когда создаеся новый элемент    constructor() {      // Выываем родительский конструктор, т.е конструктор для HTMLElement. Таким образом   устанавливаются свойства базового HTML элемента.      super();      // Берет <template> и хранит его в переменной `warinng`      let warning = document.getElementById("warningtemplate");            // Хранит контент шаблона в переменной `mywarning`      let mywarning = warning.content;      const shadowRoot = this.attachShadow({mode: "open"}).appendChild(mywarning.cloneNode(true));    }});

Я оставил в коде подробные построчные комментарии, но не объяснил код на последней строке.

Код
const shadowRoot = this.attachShadow({mode: "open"}).appendChild(mywarning.cloneNode(true));

Мы здесь делаем много работы. Во-первых, мы берем наш веб-компонент (this) и создаем скрытого шпиона, я имею в виду Shadow DOM.{ mode: open } означает, что JavaScript может извне :root обращаться к элементам Shadow DOM и управлять ими, что-то вроде настройки доступа к компоненту через черный вход. Был создан Shadow DOM и мы добавляем к ней узел (Примечание переводчика: HTML Node). Этот узел будет полной копией шаблона, включая все элементы и текст шаблона. С шаблоном, прикрепленным к Shadow DOM из пользовательского компонента, элемент <slot> и slot атрибут берут на себя задачу сопоставления содержимого с тем, где оно должно находиться.

Проверьте это. Теперь мы можем объединить экземпляра одного и того же веб-компонента, отображая разный контент, просто изменив один элемент.

Код

JS:

customElements.define('apocalyptic-warning',    class extends HTMLElement {      constructor() {        super();        let warning = document.getElementById('warningtemplate');        let mywarning = warning.content;                 const shadowRoot = this.attachShadow({mode: 'open'}).appendChild(mywarning.cloneNode(true));              }    });

HTML:

<p>The Apocalypse will never happen!</p><apocalyptic-warning>   <span slot="whats-coming">Undead</span></apocalyptic-warning><apocalyptic-warning>   <span slot="whats-coming">Halitosis Laden Zombie Minions</span></apocalyptic-warning><template id="warningtemplate">  <style>    p {      background-color: pink;      padding: 0.5em;      border: 1px solid red;    }  </style>    <p>The <slot name="whats-coming">Zombies</slot> are coming!</p></template>

Пример: Codepen

Стилизация компонента

Возможно, вы заметили стилизацию в нашем примере. Как и следовало ожидать, у нас есть абсолютно все возможности для стилизации наших компонентов в CSS. На самом деле, мы можем включить <style> элемент прямо в <template>.

Код
<template id="warningtemplate">  <style>    p {      background-color: pink;      padding: 0.5em;      border: 1px solid red;    }  </style>    <p>The <slot name="whats-coming">Zombies</slot> are coming!</p></template>

Таким образом, стили применяются только для компонента, что позволяет изолировать их благодаря теневой модели Shadow DOM.

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

И с этого момента контент технически находится вне шаблона, любые селекторы и классы, используемые в шаблонном <style> не будут действовать на контент внутри <slot>. Это не позволяет достичь полной инкапсуляции, чего я ожидал. Но поскольку веб-компонент является элементом, мы можем использовать его как селектор элемента в любом старом CSS файле, включая основной файл, используемый на странице. И хотя вставленный материал технически не находится в шаблоне, он находится в веб-компоненте, где доступна стилизация через селекторы потомков CSS.

Код
apocalyptic-warning span {  color: blue;}

Будьте осторожны, стили в основном файле CSS не могут получить доступ к внутренним элементам <template>.

Код JavaScript точно такой же, за исключением того, что сейчас мы работаем с компонентом, у которого другое имя, <zombie-profile>.

Код
customElements.define("zombie-profile",  class extends HTMLElement {    constructor() {      super();      let profile = document.getElementById("zprofiletemplate");      let myprofile = profile.content;      const shadowRoot = this.attachShadow({mode: "open"}).appendChild(myprofile.cloneNode(true));    }  });

Вот шаблон HTML и инкапсулированный CSS.

Код
<template id="zprofiletemplate">  <style>    img {      width: 100%;      max-width: 300px;      height: auto;      margin: 0 1em 0 0;    }    h2 {      font-size: 3em;      margin: 0 0 0.25em 0;      line-height: 0.8;    }    h3 {      margin: 0.5em 0 0 0;      font-weight: normal;    }    .age, .infection-date {      display: block;    }    span {      line-height: 1.4;    }    .label {      color: #555;    }    li, ul {      display: inline;      padding: 0;    }    li::after {      content: ', ';    }    li:last-child::after {      content: '';    }    li:last-child::before {      content: ' and ';    }  </style>  <div class="profilepic">    <slot name="profile-image"><img src="http://personeltest.ru/aways/assets.codepen.io/1804713/default.png" alt=""></slot>  </div>  <div class="info">    <h2><slot name="zombie-name" part="zname">Zombie Bob</slot></h2>    <span class="age"><span class="label">Age:</span> <slot name="z-age">37</slot></span>    <span class="infection-date"><span class="label">Infection Date:</span> <slot name="idate">September 12, 2025</slot></span>    <div class="interests">      <span class="label">Interests: </span>      <slot name="z-interests">        <ul>          <li>Long Walks on Beach</li>          <li>brains</li>          <li>defeating humanity</li>        </ul>      </slot>    </div>    <span class="z-statement"><span class="label">Apocalyptic Statement: </span> <slot name="statement">Moooooooan!</slot></span>  </div></template>

Вот CSS для нашего <zombie-profile> элемента и его потомков из нашего основного файла CSS. Обратите внимание, что мы используем дублирование стилей, чтобы обеспечить одинаковую стилизацию и у замененных элементов, и у элементов <template>.

Код
zombie-profile {  width: calc(50% - 1em);  border: 1px solid red;  padding: 1em;  margin-bottom: 2em;  display: grid;  grid-template-columns: 2fr 4fr;  column-gap: 20px;}zombie-profile img {  width: 100%;  max-width: 300px;  height: auto;  margin: 0 1em 0 0;}zombie-profile li, zombie-profile ul {  display: inline;  padding: 0;}zombie-profile li::after {  content: ', ';}zombie-profile li:last-child::after {  content: '';}zombie-profile li:last-child::before {  content: ' and ';}

А вот и результат:

Пример: Codepen

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

Вот и все. Чего вы сейчас боитесь больше: веб-компонентов или зомби-апокалипсиса? В недалеком прошлом я бы мог сказать, что веб-компонентов, но теперь зомби - единственное, что меня беспокоит (Ну, и покроют ли мои суточные выплаты закуски).

Подробнее..

Перевод Вы можете создавать эти элементы, не используя JavaScript

03.04.2021 20:16:48 | Автор: admin

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


Некоторые решения по-прежнему кажутся хакерскими и негибкими, но они полезны в небольших проектах и если это элементы для разового использования. Зачем писать JavaScript-функцию (или использовать jQuery, если вы ещё помните) для одного-единственного аккордеона на вашем веб-сайте? Это была моя мысль, когда я добавлял аккордеон в нижний колонтитул для мобильных устройств на моём личном веб-сайте.

Аккордеон без использования JavaScript!Аккордеон без использования JavaScript!

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

Адаптивное усечение текста

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

Компонент рейтинга

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

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

Эта реализация очень гибкая и может быть легко кастомизирована.

Всплывающая подсказка (тултип) / раскрывающееся меню (dropdown меню)

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

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

Модальные окна

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

Плавающий лейбл

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

Тоггл / Аккордеон

Недавно в HTML появился нативный элемент аккордеон (тоггл) с элементами <details> и <summary>, но недостатком использования этих элементов является то, что у них не так много вариантов стилизации, поэтому разработчики по-прежнему продолжают использовать свои собственные реализации. К счастью, с помощью логики чекбокса или радиокнопки мы можем создавать тогллы и аккордеоны, не полагаясь на JavaScript.

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

Заключение

Как видите, эти реализации полагаются на логику селектора CSS, например :focus и :placeholder-shown, чтобы заменить логику JavaScript. Некоторые из этих решений можно считать хакерскими, но они быстрые, гибкие и не полагаются на JavaScript.

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

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

Узнайте, как прокачаться в других специальностях или освоить их с нуля:

Другие профессии и курсы
Подробнее..

Полезности для разработчика на Django

05.04.2021 14:09:20 | Автор: admin

Предисловие

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

Тестирование handler 404

Если мы попытаемся тестировать ошибку 404 при заданном debug = True, то будет получать стандартный для Django отчет об ошибке с указанием о причине, но используя следующий метод вы сможете проверить работоспособность отработки 404 ошибки без лишних забот. На работающем сайте настоятельно рекомендую использовать nginx.

  1. Открываем для редактирования файл settings.py, находящийся в каталоге проекта и устанавливаем значение debug = False

  2. В том же каталоге открываем для редактирования файл urls.py и добавляем следующие строки:

from django.urls import re_pathfrom django.views.static import serve #добавляем в заголовкеre_path(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),re_path(r'^static/(?P<path>.*)$', serve, {'document_root': settings.STATIC_URL}),

При переключении debug в значение false, мы по умолчанию теряем статику и медиа, но используя данный метод, django продолжить обрабатывать эти данные вместо nginx, к примеру, а также, позволяет проверить отработку 404 или других ошибок в Django при работе на localhost, например при python manage.py runserver .

Формсеты и динамическое добавление форм

Для подготовки этого материала ушло достаточно много времени, сотни незакрытых вкладок в поисках полезной информации, а так множество вопросов в чатах разработчиков на Python/Djangoи даже появился на светсайт для создания резюмес динамическим добавлением полей формы, где представлен и используетсяданный функционал.
(Демо учетная запись:
Логин: habrhabr
Пароль: pp#6JZ2\a7y=

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

Для этих целей создал несколько моделей вида, где Worker - это FK для Experience:

class Worker(models.Model):    public_cv = models.BooleanField(default=False, verbose_name='Can everyone see your resume ?')    cv_name = models.CharField(max_length=250, verbose_name='CV name', blank=True)    author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, verbose_name='Author', default=0)# +много других полей    def __str__(self):        return self.name    def publish(self):        self.published_date = timezone.now()        self.save()class Experience(models.Model):    worker = models.ForeignKey(Worker, on_delete=models.CASCADE)    title = models.CharField(max_length=200, verbose_name='Position name')# +много других полей    def __str__(self):        return self.title    def publish(self):        self.published_date = timezone.now()        self.save()# +много других моделей

Следующим шагом, который приближал меня к цели - реализовать желаемое сначала в административной панели django-admin, для этого я использовал StackedInline:

class ExperienceInstance(admin.StackedInline):    model = Experience    extra = 1@admin.register(Worker)class PublishWorkers(admin.ModelAdmin):    inlines = [        ExperienceInstance,]

И получим желаемый вид пока что в Django-admin, создается пустая форма Experience связанная с Worker и кнопка "Добавить форму Experience":

Теперь нужно добавить во views.py код, который позволит выводить форму Experience отдельно и по нажатии кнопки создавать дополнительный экземпляр формы Experience, будем использовать Formset:

from django.forms import inlineformset_factoryfrom django.http import HttpResponseRedirectfrom .forms import ExperienceFormdef expformview(request, worker_uid):    worker = Worker.objects.get(uid=worker_uid)    ExperienceFormset = inlineformset_factory(        Worker, Experience, form=ExperienceForm, extra=1, max_num=15, can_delete=True    )    if request.method == 'POST':        formset = ExperienceFormset(request.POST, instance=worker)        if formset.is_valid():            formset.save()            return HttpResponseRedirect(request.META.get('HTTP_REFERER'))    formset = ExperienceFormset(instance=worker)    return render(request, 'site/expform.html',                  {                      'formset': formset,                      'worker': worker,                  }                  )

Также, создадим Форму в forms.py ExperienceForm:

class ExperienceForm(forms.ModelForm):    started = forms.DateField(        required=False,        label='Start date',        widget=forms.TextInput(attrs={'placeholder': 'YYYY-MM-DD'})    )    ended = forms.DateField(        required=False,        label='End date',        widget=forms.TextInput(attrs={'placeholder': 'YYYY-MM-DD'})    )    class Meta:        model = Experience        fields = ('title',                  'selfedu',                  )

Далее шаблон HTML. Я использую Crispy для лучшего отображения полей форм. {{formset.media}} нужен для вывода WYSIWYG-редактора ckeditor. При нажатии на кнопку с type="submit" данные текущей формы сохраняются в базы и снизу добавляется еще один, но пустой экземпляр формы:

Шаблон HTML
{% extends 'site/base.html' %}{% load crispy_forms_tags %}{% block content %}{% if worker.author == request.user%}<html lang="en">   <head>      <meta charset="UTF-8">      <meta name="viewport" content="width=device-width, initial-scale=1.0">      <meta http-equiv="X-UA-Compatible" content="ie=edge">      <title>Experience | {{worker}}</title>   </head>   <body>      <center>         <div class="col-lg-5" style="margin:1em;">            <nav aria-label="breadcrumb">               <ol class="breadcrumb">                   <li class="breadcrumb-item">Basic information</li>                   <li class="breadcrumb-item active" aria-current="page"><b>Experience</b></li>                   <li class="breadcrumb-item">Education</li>                   <li class="breadcrumb-item">Certification</li>                   <li class="breadcrumb-item">Awards</li>                   <li class="breadcrumb-item">Projects</li>               </ol>            </nav>         </div>      </center>   <h2 align="center" style="margin:1em;">{{worker}}'s Experience form</h2>      <form method="post">         {% csrf_token %}         <div class="row" style="margin:2em 0 2em 0;">            <div class="col-lg-5 mx-auto">               {{formset.media}}               {{formset|crispy}}            </div>             <div class="col-lg-12">                 <center><button type="submit" class="btn btn-outline-warning">Save & Add</button>                 <a href="edu"><button type="button" class="btn btn-outline-success">Next > Education</button></a></center>             </div>         </div>      </form>   </body></html>{%else%}      <div class="row">         <div class="col-lg-12" style="margin-top:6em;">            <center>               <h2>You have not access to this section</h2>            </center>         </div>      </div>      {%endif%}{% endblock %}

Так выглядит это на работающем сайте:

Экспорт данных в PDF с поддержкой кириллицы (русских букв)

Для экспорта данных, в данном случае страницы HTML в PDF мы будем использоватьXHTML2PDF; для его установки необходимо в venv запустить:

pip install xhtml2pdf

Далее добавляем следующий код в views.py:

from xhtml2pdf import pisadef render_pdf_view(request, worker_uid):    template_path = 'site/pdf.html'    worker = Worker.objects.get(uid=worker_uid)    exp = Experience.objects.filter(worker=worker)    context = {        'worker': worker,        'exp': exp,    }    response = HttpResponse(content_type='application/pdf')    response['Content-Disposition'] = 'filename="%s_%s.pdf"' % (worker.name, worker.created_date.strftime('%Y-%m-%d')) # правлю название выходного файла PDF вида: Имя_Год-М-Д    # Найти шаблон и вывести его    template = get_template(template_path)    html = template.render(context)    # Создаем PDF    pisa_status = pisa.CreatePDF(html, dest=response, )    # Вывод ошибок    if pisa_status.err:        return HttpResponse('We had some errors <pre>' + html + '</pre>')    return response

Шаблон HTML заполняем как обычный шаблон, но нужно учитывать, что парсер PDF видит только локальные стили, поэтому их нужно объявить между тегами <style></style> в данном шаблоне.

Чтобы русские символы корректно отображались в экспортируемом PDF необходимо загрузить шрифт с поддержкой кириллических (русских) букв и положить его в static/fonts/ , при этом указать до файла-шрифта полный путь с учетом системных каталогов, например в моем случае путь выглядит так:/var/www/cvmaker/static/fonts/arial.ttf , а между тегами <style/> добавляем следующее:

@font-face {         font-family: 'sans-serif';         src: url("/var/www/cvmaker/static/fonts/arial.ttf");         }         body{         font-family: "sans-serif";         }

Таким образом в экспортируемом PDF-файле мы видим вместо черных квадратиков на месте русских букв нормальные кириллические символы:

Подробнее..

Шпаргалка по JS-методам для работы с DOM

15.05.2021 10:13:08 | Автор: admin

Основные источники



Введение


JavaScript предоставляет множество методов для работы с Document Object Model или сокращенно DOM (объектной моделью документа): одни из них являются более полезными, чем другие; одни используются часто, другие почти никогда; одни являются относительно новыми, другие признаны устаревшими.


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


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


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


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


Вот как будет выглядеть наша начальная разметка:


<ul id="list" class="list">  <li id="item1" class="item">1</li>  <li id="item2" class="item">2</li>  <li id="item3" class="item">3</li></ul>

У нас есть список (ul) с тремя элементами (li). Список и каждый элемент имеют идентификатор (id) и CSS-класс (class). id и class это атрибуты элемента. Существует множество других атрибутов: одни из них являются глобальными, т.е. могут добавляться к любому элементу, другие локальными, т.е. могут добавляться только к определенным элементам.


Мы часто будем выводить данные в консоль, поэтому создадим такую "утилиту":


const log = console.log

Миксин NonElementParentNode


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


В чем разница между узлами (nodes) и элементами (elements)? Если кратко, то "узлы" это более общее понятие, чем "элементы". Узел может быть представлен элементом, текстом, комментарием и т.д. Элемент это узел, представленный разметкой (HTML-тегами (открывающим и закрывающим) или, соответственно, одним тегом).


У рассматриваемого миксина есть метод, наследуемый от объекта Document, с которого удобно начать разговор.


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


Для создания элементов используется метод createElement(tag) объекта Document:


const listEl = document.createElement('ul')

Такой способ создания элементов называется императивным. Он является не очень удобным и слишком многословным: создаем родительский элемент, добавляет к нему атрибуты по одному, внедряем его в DOM, создаем первый дочерний элемент и т.д. Следует отметить, что существует и другой, более изысканный способ создания элементов шаблонные или строковые литералы (template literals), но о них мы поговорим позже.


Одним из основных способов получения элемента (точнее, ссылки на элемент) является метод getElementById(id) объекта Document:


// получаем ссылку на наш списокconst listEl = document.getElementById('list')log(listEl)// ul#list.list - такая запись означает "элемент `ul` с `id === list`" и таким же `class`

Почему идентификаторы должны быть уникальными в пределах приложения (страницы)? Потому что элемент с id становится значением одноименного свойства глобального объекта window:


log(listEl === window.list) // true

Как мы знаем, при обращении к свойствам и методам window, слово window можно опускать, например, вместо window.localStorage можно писать просто localStorage. Следовательно, для доступа к элементу с id достаточно обратиться к соответствующему свойству window:


log(list) // ul#list.list

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


Миксин ParentNode


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


  • children потомки элемента

const { children } = list // list.childrenlog(children)/*HTMLCollection(3)  0: li#item1.item  1: li#item2.item  2: li#item3.item  length: 3*/

Такая структура называется коллекцией HTML и представляет собой массивоподобный объект (псевдомассив). Существует еще одна похожая структура список узлов (NodeList).


Массивоподобные объекты имеют свойство length с количеством потомков, метод forEach() (NodeList), позволяющий перебирать узлы (делать по ним итерацию). Такие объекты позволяют получать элементы по индексу, по названию (HTMLCollection) и т.д. Однако, у них отсутствуют методы настоящих массивов, такие как map(), filter(), reduce() и др., что делает работу с ними не очень удобной. Поэтому массивоподобные объекты рекомендуется преобразовывать в массивы с помощью метода Array.from() или spread-оператора:


const children = Array.from(list.children)// илиconst children = [...list.children]log(children) // [li#item1.item, li#item2.item, li#item3.item] - обычный массив

  • firstElementChild первый потомок элемент
  • lastElementChild последний потомок элемент

log(list.firstElementChild) // li#item1.itemlog(list.lastElementChild) // li#item2.item

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


const createEl = (id, text, tag = 'li', _class = 'item') => {  const el = document.createElement(tag)  el.id = id  el.className = _class  el.textContent = text  return el}

Наша утилита принимает 4 аргумента: идентификатор, текст, название тега и CSS-класс. 2 аргумента (тег и класс) имеют значения по умолчанию. Функция возвращает готовый к работе элемент. Впоследствии, мы реализует более универсальный вариант данной утилиты.


  • prepend(newNode) добавляет элемент в начало списка
  • append(newNode) добавляет элемент в конец списка

// создаем новый элементconst newItem = createEl('item0', 0)// и добавляем его в начало спискаlist.prepend(newItem)// создаем еще один элементconst newItem2 = createEl('item4', 4)// и добавляем его в конец спискаlist.append(newItem2)log(children)/*HTMLCollection(5)  0: li#item0.item  1: li#item1.item  2: li#item2.item  3: li#item3.item  4: li#item4.item*/

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


  • replaceChildren(nodes) заменяет потомков новыми элементами

const newItems = [newItem, newItem2]// заменяем потомков новыми элементамиlist.replaceChildren(...newItems) // list.replaceChildren(newItem, newItem2)log(children) // 2

Наиболее универсальными способами получения ссылок на элементы являются методы querySelector(selector) и querySelectorAll(selector). Причем, в отличие от getElementById(), они могут вызываться на любом родительском элементе, а не только на document. В качестве аргумента названным методам передается любой валидный CSS-селектор (id, class, tag и т.д.):


// получаем элемент `li` с `id === item0`const itemWithId0 = list.querySelector('#item0')log(itemWithId0) // li#item0.item// получаем все элементы `li` с `class === item`const allItems = list.querySelectorAll('.item')log(allItems) // массивоподобный объект/*NodeList(2)  0: li#item0.item  1: li#item4.item  length: 2*/

Создадим универсальную утилиту для получения элементов:


const getEl = (selector, parent = document, single = true) => single ? parent.querySelector(selector) : [...parent.querySelectorAll(selector)]

Наша утилита принимает 3 аргумента: CSS-селектор, родительский элемент и индикатор количества элементов (один или все). 2 аргумента (предок и индикатор) имеют значения по умолчанию. Функция возвращает либо один, либо все элементы (в виде обычного массива), совпадающие с селектором, в зависимости от значения индикатора:


const itemWithId0 = getEl('#item0', list)log(itemWithId0) // li#item0.itemconst allItems = getEl('.item', list, false)log(allItems) // [li#item0.item, li#item4.item]

Миксин NonDocumentTypeChildNode


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


  • previousElementSibling предыдущий элемент
  • nextElementSibling следующий элемент

log(itemWithId0.previousElementSibling) // nulllog(itemWithId0.nextElementSibling) // #item4

Миксин ChildNode


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


  • before(newNode) вставляет новый элемент перед текущим
  • after(newNode) вставляет новый элемент после текущего

// получаем `li` с `id === item4`const itemWithId4 = getEl('#item4', list)// создаем новый элементconst newItem3 = createEl('item3', 3)// и вставляем его перед `itemWithId4`itemWithId4.before(newItem3)// создаем еще один элементconst newItem4 = createEl('item2', 2)// и вставляем его после `itemWithId0`itemWithId0.after(newItem4)

  • replaceWith(newNode) заменяет текущий элемент новым

// создаем новый элементconst newItem5 = createEl('item1', 1)// и заменяем им `itemWithId0`itemWithId0.replaceWith(newItem5)

  • remove() удаляет текущий элемент

itemWithId4.remove()

Интерфейс Node


Данный интерфейс предназначен для обработки узлов.


  • nodeType тип узла

log(list.nodeType) // 1// другие варианты/* 1 -> ELEMENT_NODE (элемент) 3 -> TEXT_NODE (текст) 8 -> COMMENT_NODE (комментарий) 9 -> DOCUMENT_NODE (document) 10 -> DOCUMENT_TYPE_NODE (doctype) 11 -> DOCUMENT_FRAGMENT_NODE (фрагмент) и т.д.*/

  • nodeName название узла

log(list.nodeName) // UL// другие варианты/*  - квалифицированное название HTML-элемента прописными (заглавными) буквами  - квалифицированное название атрибута  - #text  - #comment  - #document  - название doctype  - #document-fragment*/

  • baseURI основной путь

log(list.baseURI) // .../dom/index.html

  • parentNode родительский узел
  • parentElement родительский элемент

const itemWithId1 = getEl('#item1', list)log(itemWithId1.parentNode) // #listlog(itemWithId1.parentElement) // #list

  • hasChildNodes() возвращает true, если элемент имеет хотя бы одного потомка
  • childNodes дочерние узлы

log(list.hasChildNodes()) // truelog(list.childNodes)/*NodeList(3)  0: li#item1.item  1: li#item2.item  2: li#item3.item*/

  • firstChild первый потомок узел
  • lastChild последний потомок узел

log(list.firstChild) // #item1log(list.lastChild) // #item3

  • nextSibling следующий узел
  • previousSibling предыдущий узел

log(itemWithId1.nextSibling) // #item2log(itemWithId1.previousSibling) // null

  • textContent геттер/сеттер для извлечения/записи текста

// получаем текстlog(itemWithId1.textContent) // 1// меняем текстitemWithId1.textContent = 'item1'log(itemWithId1.textContent) // item1// получаем текстовое содержимое всех потомковlog(list.textContent) // item123

Для извлечения/записи текста существует еще один (устаревший) геттер/сеттер innerText.


  • cloneNode(deep) копирует узел. Принимает логическое значение, определяющее характер копирования: поверхностное копируется только сам узел, глубокое копируется сам узел и все его потомки

// создаем новый список посредством копирования существующегоconst newList = list.cloneNode(false)// удаляем у него `id` во избежание коллизийnewList.removeAttribute('id')// меняем его текстовое содержимоеnewList.textContent = 'new list'// и вставляем его после существующего спискаlist.after(newList)// создаем еще один списокconst newList2 = newList.cloneNode(true)newList.after(newList2)

  • isEqualNode(node) сравнивает узлы
  • isSameNode(node) определяет идентичность узлов

log(newList.isEqualNode(newList2)) // truelog(newList.isSameNode(newList2)) // false

  • contains(node) возвращает true, если элемент содержит указанный узел

log(list.contains(itemWithId1)) // true

  • insertBefore(newNode, existingNode) добавляет новый узел (newNode) перед существующим (existingNode)

// создаем новый элементconst itemWithIdA = createEl('#item_a', 'a')// и вставляем его перед `itemWithId1`list.insertBefore(itemWithIdA, itemWithId1)

  • appendChild(node) добавляет узел в конец списка

// создаем новый элементconst itemWithIdC = createEl('#item_c', 'c')// и добавляем его в конец спискаlist.appendChild(itemWithIdC)

  • replaceChild(newNode, existingNode) заменяет существующий узел (existingNode) новым (newNode):

// создаем новый элементconst itemWithIdB = createEl('item_b', 'b')// и заменяем им `itemWithId1`list.replaceChild(itemWithIdB, itemWithId1)

  • removeChild(node) удаляет указанный дочерний узел

// получаем `li` с `id === item2`const itemWithId2 = getEl('#item2', list)// и удаляем егоlist.removeChild(itemWithId2)

Интерфейс Document


Данный интерфейс предназначен для обработки объекта Document.


  • URL и documentURI адрес документа

log(document.URL) // .../dom/index.htmllog(document.documentURI) // ^

  • documentElement:

log(document.documentElement) // html

  • getElementsByTagName(tag) возвращает все элементы с указанным тегом

const itemsByTagName = document.getElementsByTagName('li')log(itemsByTagName)/*HTMLCollection(4)  0: li##item_a.item  1: li#item_b.item  2: li#item3.item  3: li##item_c.item*/

  • getElementsByClassName(className) возвращает все элементы с указанным CSS-классом

const itemsByClassName = list.getElementsByClassName('item')log(itemsByClassName) // ^

  • createDocumentFragment() возвращает фрагмент документа:

// создаем фрагментconst fragment = document.createDocumentFragment()// создаем новый элементconst itemWithIdD = createEl('item_d', 'd')// добавляем элемент во фрагментfragment.append(itemWithIdD)// добавляем фрагмент в списокlist.append(fragment)

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


  • createTextNode(data) создает текст


  • createComment(data) создает комментарий


  • importNode(existingNode, deep) создает новый узел на основе существующего



// создаем новый список на основе существующегоconst newList3 = document.importNode(list, true)// вставляем его перед существующим спискомlist.before(newList3)// и удаляем во избежание коллизийnewList3.remove()

  • createAttribute(attr) создает указанный атрибут

Интерфейсы NodeIterator и TreeWalker


Интерфейсы NodeIterator и TreeWalker предназначены для обхода (traverse) деревьев узлов. Я не сталкивался с примерами их практического использования, поэтому ограничусь парочкой примеров:


// createNodeIterator(root, referenceNode, pointerBeforeReferenceNode, whatToShow, filter)const iterator = document.createNodeIterator(list)log(iterator)log(iterator.nextNode()) // #listlog(iterator.nextNode()) // #item_alog(iterator.previousNode()) // #item_alog(iterator.previousNode()) // #listlog(iterator.previousNode()) // null// createTreeWalker(root, whatToShow, filter)// применяем фильтры - https://dom.spec.whatwg.org/#interface-nodefilterconst walker = document.createTreeWalker(list, '0x1', { acceptNode: () => 1 })log(walker)log(walker.parentNode()) // nulllog(walker.firstChild()) // #item_alog(walker.lastChild()) // nulllog(walker.previousSibling()) // nulllog(walker.nextSibling()) // #item_blog(walker.nextNode()) // #item3log(walker.previousNode()) // #item_b

Интерфейс Element


Данный интерфейс предназначен для обработки элементов.


  • localName и tagName название тега

log(list.localName) // ullog(list.tagName) // UL

  • id геттер/сеттер для идентификатора
  • className геттер/сеттер для CSS-класса

log(list.id) // listlist.id = 'LIST'log(LIST.className) // list

  • classList все CSS-классы элемента (объект DOMTokenList)

const button = createEl('button', 'Click me', 'my_button', 'btn btn-primary')log(button.classList)/*DOMTokenList(2)  0: "btn"  1: "btn-primary"  length: 2  value: "btn btn-primary"*/

Работа с classList


  • classList.add(newClass) добавляет новый класс к существующим
  • classList.remove(existingClass) удаляет указанный класс
  • classList.toggle(className, force?) удаляет существующий класс или добавляет новый. Если опциональный аргумент force имеет значение true, данный метод только добавляет новый класс при отсутствии, но не удаляет существующий класс (в этом случае toggle() === add()). Если force имеет значение false, данный метод только удаляет существующий класс при наличии, но не добавляет отсутствующий класс (в этом случае toggle() === remove())
  • classList.replace(existingClass, newClass) заменяет существующий класс (existingClass) на новый (newClass)
  • classList.contains(className) возвращает true, если указанный класс обнаружен в списке классов элемента (данный метод идентичен className.includes(className))

// добавляем к кнопке новый классbutton.classList.add('btn-lg')// удаляем существующий классbutton.classList.remove('btn-primary')// у кнопки есть класс `btn-lg`, поэтому он удаляетсяbutton.classList.toggle('btn-lg')// заменяем существующий класс на новыйbutton.classList.replace('btn', 'btn-success')log(button.className) // btn-successlog(button.classList.contains('btn')) // falselog(button.className.includes('btn-success')) // true

Работа с атрибутами


  • hasAttributes() возвращает true, если у элемента имеются какие-либо атрибуты
  • getAttributesNames() возвращает названия атрибутов элемента
  • getAttribute(attrName) возвращает значение указанного атрибута
  • setAttribute(name, value) добавляет указанные атрибут и его значение к элементу
  • removeAttribute(attrName) удаляет указанный атрибут
  • hasAttribute(attrName) возвращает true при наличии у элемента указанного атрибута
  • toggleAttribute(name, force) добавляет новый атрибут при отсутствии или удаляет существующий атрибут. Аргумент force аналогичен одноименному атрибуту classList.toggle()

log(button.hasAttributes()) // truelog(button.getAttributeNames()) // ['id', 'class']log(button.getAttribute('id')) // buttonbutton.setAttribute('type', 'button')button.removeAttribute('class')log(button.hasAttribute('class')) // false

В использовании перечисленных методов для работы с атрибутами нет особой необходимости, поскольку многие атрибуты являются геттерами/сеттерами, т.е. позволяют извлекать/записывать значения напрямую. Единственным исключением является метод removeAttribute(), поскольку существуют атрибуты без значений: например, если кнопка имеет атрибут disabled, установка значения данного атрибута в false не приведет к снятию блокировки для этого нужно полностью удалить атрибут disabled с помощью removeAttribute().


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


Вместо этого, мы могли бы использовать атрибут data-id и получать ссылки на элементы с помощью getEl('[data-id="id"]').


Название data-атрибута после символа - становится одноименным свойством объекта dataset. Например, значение атрибута data-id можно получить через свойство dataset.id.


  • closest(selectors) возвращает первый родительский элемент, совпавший с селекторами

LIST.append(button)log(button.closest('#LIST', 'document.body')) // #LIST

  • matches(selectors) возвращает true, если элемент совпадает хотя бы с одним селектором

log(button.matches('.btn', '[type="button"]'))// у кнопки нет класса `btn`, но есть атрибут `type` со значением `button`,// поэтому возвращается `true`

  • insertAdjacentElement(where, newElement) универсальный метод для вставки новых элементов перед/в начало/в конец/после текущего элемента. Аргумент where определяет место вставки. Возможные значения:


    • beforebegin перед открывающим тегом
    • afterbegin после открывающего тега
    • beforeend перед закрывающим тегом
    • afterend после закрывающего тега

  • insertAdjacentText(where, data) универсальный метод для вставки текста


  • Text конструктор для создания текста


  • Comment конструктор для создания комментария



const text = new Text('JavaScript')log(text) // "JavaScript"const part = text.splitText(4)log(part) // "Script"log(part.wholeText()) // Scriptconst comment = new Comment('TODO')log(comment) // <!--TODO-->

Объект Document


  • location объект с информацией о текущей локации документа

log(document.location)

Свойства объекта location:


  • hash хэш-часть URL (символ # и все, что следует за ним), например, #top
  • host название хоста и порт, например, localhost:3000
  • hostname название хоста, например, localhost
  • href полный путь
  • origin protocol + host
  • pathname путь без протокола
  • port порт, например, 3000
  • protocol протокол, например, https
  • search строка запроса (символ ? и все, что следует за ним), например, ?name=John&age=30

Методы location:


  • reload() перезагружает текущую локацию


  • replace() заменяет текущую локацию на новую


  • title заголовок документа



log(document.title) // DOM

  • head метаданные документа


  • body тело документа


  • images псевдомассив (HTMLCollection), содержащий все изображения, имеющиеся в документе



const image = document.createElement('img')image.className = 'my_image'image.src = 'https://miro.medium.com/max/875/1*ZIH_wjqDfZn6NRKsDi9mvA.png'image.alt = "V8's compiler pipeline"image.width = 480document.body.append(image)log(document.images[0]) // .my_image

  • links псевдомассив, содержащий все ссылки, имеющиеся в документе

const link = document.createElement('a')link.className = 'my_link'link.href = 'https://github.com/azat-io/you-dont-know-js-ru'link.target = '_blank'link.rel = 'noopener noreferrer'link.textContent = 'Вы не знаете JS'document.body.append(link)log(document.links[0]) // .my_link

  • forms псевдомассив, содержащий все формы, имеющиеся в документе

const form = document.createElement('form')form.className = 'my_form'document.body.append(form)log(document.forms[0]) // .my_form

Следующие методы и свойство считаются устаревшими:


  • open() открывает документ для записи. При этом документ полностью очищается
  • close() закрывает документ для записи
  • write() записывает данные (текст, разметку) в документ
  • writeln() записывает данные в документ с переносом на новую строку
  • designMode управление режимом редактирования документа. Возможные значения: on и off. Наберите document.designMode = 'on' в консоли DevTools и нажмите Enter. Вуаля, страница стала редактируемой: можно удалять/добавлять текст, перетаскивать изображения и т.д.
  • execCommand() выполняет переданные команды. Со списоком доступных команд можно ознакомиться здесь. Раньше этот метод активно использовался для записи/извлечения данных из буфера обмена (команды copy и paste). Сейчас для этого используются методы navigator.clipboard.writeText(), navigator.clipboard.readText() и др.

Миксин InnerHTML


Геттер/сеттер innerHTML позволяет извлекать/записывать разметку в элемент. Для подготовки разметки удобно пользоваться шаблонными литералами:


const itemsTemplate = `  <li data-id="item1" class="item">1</li>  <li data-id="item2" class="item">2</li>  <li data-id="item3" class="item">3</li>`LIST.innerHTML = itemsTemplatelog(LIST.innerHTML)/*<li data-id="item1" class="item">1</li><li data-id="item2" class="item">2</li><li data-id="item3" class="item">3</li>*/

Расширения интерфейса Element


  • outerHTML геттер/сеттер для извлечения/записи внешней разметки элемента: то, что возвращает innerHTML + разметка самого элемента

log(LIST.outerHTML)/*<ul id="LIST" class="list">  <li data-id="item1" class="item">1</li>  <li data-id="item2" class="item">2</li>  <li data-id="item3" class="item">3</li></ul>*/

  • insertAdjacentHTML(where, string) универсальный метод для вставки разметки в виде строки. Аргумент where аналогичен одноименному аргументу метода insertAdjacentElement()

Метод insertAdjacentHTML() в сочетании с шаблонными литералами и их продвинутой версией тегированными шаблонными литералами (tagged template literals) предоставляет много интересных возможностей по манипулированию разметкой документа. По сути, данный метод представляет собой движок шаблонов (template engine) на стороне клиента, похожий на Pug, Handlebars и др. серверные движки. С его помощью (при участии History API) можно, например, реализовать полноценное одностраничное приложение (Single Page Application или сокращенно SPA). Разумеется, для этого придется написать чуть больше кода, чем при использовании какого-либо фронтенд-фреймворка.


Вот несколько полезных ссылок, с которых можно начать изучение этих замечательных инструментов:



Иногда требуется создать элемент на основе шаблонной строки. Как это можно сделать? Вот соответствующая утилита:


const createElFromStr = (str) => {  // создаем временный элемент  const el = document.createElement('div')  // записываем в него переданную строку - разметку  el.innerHTML = str  // извлекаем наш элемент  // если мы используем здесь метод `firstChild()`, может вернуться `#text`  // одна из проблем шаблонных строк заключается в большом количестве лишних пробелов  const child = el.fisrtElementChild  // удаляем временный элемент  el.remove()  // и возвращаем наш элемент  return child}// шаблон спискаconst listTemplate = `<ul id="list">  <li data-id="item1" class="item">1</li>  <li data-id="item2" class="item">2</li>  <li data-id="item3" class="item">3</li></ul>`// создаем список на основе шаблонаconst listEl = createElFromStr(listTemplate)// и вставляем его в тело документаdocument.body.append(listEl)

Существует более экзотический способ создания элемента на основе шаблонной строки. Он предполагает использование конструктора DOMParser():


const createElFromStr = (str) => {  // создаем новый парсер  const parser = new DOMParser()  // парсер возвращает новый документ  const {    body: { children }  } = parser.parseFromString(str, 'text/html')  // нас интересует первый дочерний элемент тела нового документа  return children[0]}const listTemplate = `<ul id="list">  <li data-id="item1" class="item">1</li>  <li data-id="item2" class="item">2</li>  <li data-id="item3" class="item">3</li></ul>`const listEl = createElFromStr(listTemplate)document.body.append(listEl)

Еще более экзотический, но при этом самый короткий способ предполагает использование расширения для объекта Range метода createContextualFragment():


const createElFromStr = (str) => {  // создаем новый диапазон  const range = new Range()  // создаем фрагмент  const fragment = range.createContextualFragment(str)  // и возвращаем его  return fragment}// или в одну строкуconst createFragment = (str) => new Range().createContextualFragment(str)const listTemplate = `<ul id="list">  <li data-id="item1" class="item">1</li>  <li data-id="item2" class="item">2</li>  <li data-id="item3" class="item">3</li></ul>`document.body.append(createFragment(listTemplate))

В завершение, как и обещал, универсальная утилита для создания элементов:


// функция принимает название тега и объект с настройкамиconst createEl = (tag, opts) => {  const el = document.createElement(tag)  // перебираем ключи объекта и записывает соответствующие свойства в элемент  for (const key in opts) {    el[key] = opts[key]  }  // возвращаем готовый элемент  return el}const button = createEl('button', {  // настройками могут быть атрибуты  id: 'my_button',  className: 'btn btn-primary',  textContent: 'Click me',  title: 'My button',  autofocus: true,  // стили  style: 'color: red; cursor: pointer;',  // обработчики и т.д.  onmouseenter: function () {    this.style.color = 'green'  },  onmouseout: function () {    this.style.color = 'blue'  },  onclick: () => alert('Привет!')})document.body.append(button)

Заключение


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


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




VDS от Маклауд быстрые и безопасные.


Зарегистрируйтесь по ссылке выше или кликнув на баннер и получите 10% скидку на первый месяц аренды сервера любой конфигурации!


Подробнее..

Перевод Самая серьёзная проблема HTML? Разработчики, разработчики, разработчики

27.05.2021 12:14:32 | Автор: admin
image

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

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

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

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

Список слабых оправданий


HTML это ненастоящий язык программирования

Это последовательность команд, которым следуют компьютеры для выполнения задачи. Если и есть другое определение языка программирования, то за четыре десятка лет написания ПО я его не слышал. Является ли от Тьюринг-полным? Нет но тем не менее он сообщает компьютеру, как передать грамматическое и структурное значение контента в машинонезависимом виде. Есть правила по использованию его тэгов, порядка и синтаксиса.

Никого не волнует код, если для пользователя он выглядит правильно

Ровно до того момента, как вы не столкнётесь с незрячими пользователями. HTML это не только то, как выглядит страница Нет! Исправлюсь HTML вообще не о том, как что-то выглядит. HTML нужен для того, что сообщить, какими должны быть элементы с точки зрения грамматики и структуры, чтобы user-agent мог передать это значение пользователю. Поэтому для описания того, как должны выглядеть элементы, у нас есть CSS. Если любой из ваших тэгов, id или классов сообщает о том, как элементы должны выглядеть, то вы выбрали неподходящий код, исходя из неверных предпосылок.

Скринридеры (ПО для чтения страницы вслух), электронные книги со шрифтом Брайля, TTY всё это невизуальные целевые объекты; и не забывайте, что у поисковых движков тоже нет глаз. Им нет ни малейшей разницы, как выглядит ваша страница.

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

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

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

А как насчёт следующего несчастного неудачника, которому придётся поддерживать результат вашего труда, в котором собраны все ошибки из списка, как НЕ нужно использовать HTML? Люди всегда болтают о том, как их ужасный поломанный фреймворк должен помогать в совместной работе. Как в два или десять раз больший объём кода, не соответствующий базовым правилам HTML и нарушающий само предназначение этого языка, может улучшить совместную работу?

Но примеры во фреймворках работают именно так, а их писали специалисты

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

С ванильным кодом работать сложнее

Сложным делаете его вы. В том-то и проблема: игнорируя семантические правила структурирования и весь смысл предназначения HTML, вы усложняете работу с ним. Способствует этому и то, что приманки для нубов наподобие W3Schools (aka W3fools) переполнены неточной информацией, вульгарными упрощениями и полным отсутствием большинства из базовых концепций HTML.

Контент должен определять разметку, контент + разметка + целевая среда/возможности user-agent должны определять структуру. Следуя базовой семантике и благодаря постепенному совершенствованию и использованию правильного разделения функциональности, вы в конечном итоге получите набор инструкций, позволяющий с лёгкостью создавать простые в поддержке страницы. Если у вас возникают с этим проблемы и вы считаете, что эти фреймворки HTML/CSS упрощают вашу жизнь, значит, вы недостаточно хорошо знаете HTML или CSS для выполнения хоть каких-то задач.

Вообще, Tailwind проще, чем ванильные HTML/CSS, достаточно просто выучить более 500 классов, 90% из которых уже существуют в виде свойств CSS, а затем игнорировать почти все правила того, как должен использоваться HTML!

Если вы не поняли, это был сарказм.

Вы придаёте слишком большую важность HTML



Я постоянно слышу эту чушь, и меня раздражает её недальновидность!

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

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

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

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

Но HTML не даёт нам инструментов, которые нужны для обеспечения UX

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

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

И результаты их работы вы УЖЕ СЕЙЧАС видите во всей нашей отрасли: хрупкие, распухшие, медленные решения, которые способен улучшить скриптинг, настолько затормаживают системы корзин интернет-магазинов, что многие из них даже неспособны поддерживать аптайм (привет, Zotac); при этом пользователи ожесточённо жмут на F5, надеясь, что им всё-таки удастся купить видеокарту. Из-за перезагрузки всей страницы целиком и повторного запуска скрипта все функции приложения приводят только к УМЕНЬШЕНИЮ скорости загрузки страницы. И ещё сильнее это проявляется, если вы плюёте на разметку, пользуясь presentational classes.

А поскольку скрипты можно отключать, и генерируемый скриптами контент сложнее для скринридеров, электронных книг со шрифтом Брайля, и так далее, одностраничные приложения (single-page application, SPA) нарушают правила доступности для людей с ограничениями.

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

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

То есть во всём виноват веб-разработчик?


Ни в коем случае. Вернёмся к началу статьи и к крикам Баллмера разработчики, разработчики, разработчики.

Когда он разыгрывал свою небольшую сценку, она была призвана решить проблему того, что в конце 90-х Windows ни в коем случае не была на первом месте, потому что разработчики часто не использовали предоставляемые компанией Microsoft инструменты. Лучшую документацию по Windows API напечатала Borland. Люди использовали инструменты не от Microsoft, потому что визуальные языки считались игрушками. Они так сильно отставали от технологий веб-разработки, то можно сказать, что они и сейчас пытаются их догнать!

У W3C и WhatWG есть похожие проблемы с тем что так называемые спецификации попросту написаны не для людей, которые пишут веб-сайты. Позвольте мне повторить: спецификация языка, используемого для написания веб-сайтов, предназначена не для людей, которые на самом деле пишут веб-сайты. Она написана для людей, которые пишут user-agents! Браузер это user-agent, но UA не всегда браузер.

На самом деле, это такой абсурд, что идиотическая версия динамичного документа WhatWG ссылается на MDN, чтобы её могли понять простые смертные.

Примечание: я даже не буду начинать рассуждения о том, насколько тупой является сама идея динамичного документа (living document), особенно потому что в реальных HTML-документах отсутствует отслеживание версий. HTML 5, который был валиден в прошлом году, становится сегодня невалидным HTML 5, а сегодняшний валидный HTML 5 может стать невалидным завтра? Отличный способ сделать валидацию совершенно бесполезной!

Простой факт: для получения описаний значений тэгов на простом английском приходится обращаться к сторонним источникам, многие из которых даже не согласуются друг с другом. Более того, W3C стала совершенно беззубой, она слепо соглашается со всем, что говорит WhatWG, даже несмотря на то, что WhatWG многократно доказала, что она не имеет достаточной квалификации для создания потомка HTML 4 Strict. Принятие EMBED в качестве валидного тэга, создание и/или поддержка тэгов, избыточных относительно OBJECT, больше не поддерживаемый (к счастью) тэг HGROUP, показавший, что они даже не понимают, для чего нужны нумерованные заголовки и как их использовать По признанию многих, кто над ним работал, задача HTML 5 на самом деле никогда не заключалась в создании спецификации или стандарта, говорящего нам, как создавать полезные веб-сайты! Она заключалась в документировании того, делают ли сегодня люди правильно или неправильно, и того, что браузеры могут поддерживать, но не того, что они должны поддерживать! Учитывая то, что во время разработки HTML 5 большинство разработчиков всё ещё вбивало HTML 3.2 и набрасывало поверх него извращённый doctype HTML 4, к чему удивляться, что всё оказалось таким скоплением плохих, устаревших и старомодных практик?

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

W3C и WhatWG даже не воспринимаются серьёзно другими организациями по стандартизации, и на то есть причина.

Каким же должно быть решение?


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

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

Но нам нужна не просто улучшенная официальная документация, необходимо урезать язык, сделать его более ориентированным на задачи. Возродить многие идеи, которые содержались в HTML 5 до того, как W3C выкинула их на помойку и приняла версию WhatWG. Тот факт, что Microsoft потратила десятки лет на то, чтобы IE препятствовал нам использовать OBJECT ещё не причина не только сохранять тэг IMG, но и добавлять множество новых тэгов без нужды (VIDEO, AUDIO). Просто потому, что художники и жулики от маркетинга любят открывать для пользователя новые окна, нравится ему это или нет, ещё не причина того, чтобы в спецификации было TARGET="_BLANK".

Более того, ЯВНОЕ использование и значения тэгов необходимо поместить в центр спецификации, а может быть, даже в отдельное руководство по использованию для тех, кто по-прежнему живёт в 1997 году.

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

Кроме того, нам полезно будет, если при её создании меньший вес будут иметь разработчики браузеров. Microsoft, Mozilla, Apple и Google имеют огромное влияние на W3C и WhatWG, и это совершенно неэтично. Их вес в процессе принятия решений противоречит самой концепции свободного и открытого веба.



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


Эпичные серверы это VDS для размещения сайтов от маленького интернет-магазина на Opencart до серьёзных проектов с огромной аудиторией. Создавайте собственные конфигурации серверов в пару кликов!

Присоединяйтесь к нашему чату в Telegram.

Подробнее..

Как быстро создать 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-сайты быстрее и делать их функциональными. Делитесь в комментариях ссылками на другие полезные продукты так наш список будет еще более полным.
Подробнее..

Перевод Практическое руководство по TypeScript для разработчиков

18.05.2021 14:13:42 | Автор: admin

Представляю вашему вниманию перевод статьи "Working With TypeScript: A Practical Guide for Developers".


Что такое TypeScript?


TypeScript это популярный статический типизатор (static type checker) или типизированное надмножество (typed superset) для JavaScript, инструмент, разработанный Microsoft и добавляющий систему типов к гибкости и динамическим возможностям JavaScript.


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


Установка TypeScript


Для того, чтобы начать работу с TypeScript, нужно либо установить специальный интерфейс командной строки (command line interface, CLI), либо воспользоваться официальной онлайн-песочницей или другим похожим инструментом.


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


# Создаем новую директорию для проектаmkdir typescript-intro# Делаем созданную директорию текущейcd typescript-intro# Инициализируем Node.js-проектnpm init -y# Устанавливаем компилятор TypeScriptnpm i typescript

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


console.log(1)

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


# Преобразуем index.ts в index.jsnpx tsc index.ts

Наконец, выполняем скомпилированный код с помощью команды node:


# Вы должны увидеть `1` в терминалеnode index.js

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


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


Определение TypeScript-проекта


Для определения TypeScript-проекта внутри Node.js-проекта, необходимо создать файл tsconfig.json. Присутствие данного файла в директории свидетельствует о том, что мы имеем дело с TypeScript-проектом.


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


Вы легко можете настроить TypeScript с помощью следующей команды:


# Создаем стандартный tsconfig.jsonnpx tsc --init

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


Мы еще вернемся к настройкам TypeScript, а сейчас давайте писать код.


Возможности TypeScript


Каждая возможность TypeScript подробно рассматривается в "Карманной книге по TypeScript". Мы сосредоточимся на практической составляющей некоторых из них. Я постараюсь пролить свет на некоторые возможности, которые часто упускаются из вида в литературе, посвященной TypeScript.


Основы типизации


Ключевая идея TypeScript заключается в контроле за динамической природой и гибкостью JavaScript с помощью типов. Давайте рассмотрим эту идею на практике.


В директории проекта создаем файл test.js следующего содержания:


function addOne(age) { return age + 1}const age = 'thirty two'console.log(addOne(age))

Выполняем данный код:


node test.js

  1. Что мы увидим в терминале?
  2. Как вы думаете, правильным ли будет вывод?

В терминале мы увидим thirty two1 без каких-либо предупреждений об очевидной некорректности вывода. Ничего нового: обычное поведение JavaScript.


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


Заменим содержимое созданного нами ранее index.ts следующим кодом:


function addOne(age: number): number { return age + 1}console.log(addOne(32))console.log(addOne('thirty two'))

Обратите внимание, что мы ограничили принимаемый функцией аргумент и возвращаемое функцией значение типом number.


Преобразуем файл:


npx tsc index.ts

Попытка преобразования проваливается:


index.ts:6:20 - error TS2345: Argument of type 'string' is notassignable to parameter of type 'number'. Аргумент типа 'строка' не может быть присвоен параметру с типом 'число'.

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


string и number это лишь два из основных типов, поддерживаемых TypeScript. TypeScript поддерживает все примитивные значения JavaScript, включая boolean и symbol.


Кроме того, TypeScript определяет несколько собственных типов, которые не имеют соответствия в JavaScript, но являются очень полезными с точки зрения используемой в данной экосистеме методологии:


  • enum ограниченный набор значений
  • any указывает на то, что переменная/параметр могут быть чем угодно, что, по сути, нивелирует типизацию
  • unknown типобезопасная альтернатива any
  • void указывает на то, что функция ничего не возвращает
  • never указывает на то, что функция выбрасывает исключение или на то, что ее выполнение никогда не заканчивается
  • литеральные типы, конкретизирующие типы number, string или boolean. Это означает, например, что 'Hello World' это string, но string это не 'Hello World' в контексте системы типов. Тоже самое справедливо в отношении false в случае с логическими значениями или для 3 в случае с числами:

// Данная функция принимает не любое число, а только 3 или 4declare function processNumber(s: 3 | 4)declare function processAnyNumber(n: number)const n: number = 10const n2: 3 = 3processNumber(n) // Ошибка: `number` - это не `3 | 4`processAnyNumber(n2) // Работает. 3 - это `number`

Множества


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


Карты (maps)


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


// Создаем ассоциативный типtype User = { id: number username: string name: string}// Создаем объект `user`, соответствующий ассоциативному типуconst user: User = { id: 1, username: 'Superman', name: 'Clark Kent',}

Векторы (vectors)


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


// Создаем ассоциативный типtype User = { id: number username: string name: string}// Создаем несколько объектов `user`, соответствующих ассоциативному типуconst user1: User = { id: 1, username: 'Superman', name: 'Clark Kent',}const user2: User = { id: 2, username: 'WonderWoman', name: 'Diana Prince',}const user3: User = { id: 3, username: 'Spiderman', name: 'Peter Parker',}// Создаем вектор пользователейconst userVector: User[] = [user1, user2, user3]

Кортежи (tuples)


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


// Создаем ассоциативный типtype User = { id: number username: string name: string}// Создаем объект `user`, соответствующий ассоциативному типуconst user1: User = { id: 1, username: 'Superman', name: 'Clark Kent',}// Создаем кортежconst userTuple: [User, number] = [user1, 10]

Объединения (unions)


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


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


Прежде всего, давайте установим node-fetch, чтобы иметь возможность использовать функцию fetch в Node.js:


npm i node-fetch @types/node-fetch

Затем с помощью typeof осуществляем разделение типов:


type User = { id: number username: string name: string email: string}async function fetchFromEmail(email: string) { const res = await fetch('https://jsonplaceholder.typicode.com/users') const parsed: User[] = await res.json() const user = parsed.find((u: User) => u.email === email) if (user) {   return fetchFromId(user.id) } return undefined}function fetchFromId(id: number) { return fetch(`https://jsonplaceholder.typicode.com/users/${id}`)   .then((res) => res.json())   .then((user) => user.address)}function getUserAddress(user: User | string) { if (typeof user === 'string') {   return fetchFromEmail(user) } return fetchFromId(user.id)}getUserAddress('Rey.Padberg@karina.biz').then(console.log).catch(console.error)

Здесь мы в явном виде реализовали предохранитель типов.


К слову, кортежи и объединения можно использовать совместно:


const userTuple: Array<User | number> = [u, 10, 20, u, 30]// Любой элемент может быть либо `User`, либо `number`

Можно определять размер и тип каждого элемента массива:


const userTuple: [User, number] = [u, 10, 20, u, 30]// Ошибка: массив должен состоять из двух элементов с типами `User` и `number`const anotherUserTuple: [User, number] = [u, 10] // Все верно

Предохранители типов (type guards)


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


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


Существуют и другие предохранители, такие как instanceof, !== и in, полный список можно найти в документации.


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


// Определяем предохранитель для `user`function isUser(u: unknown): u is User { if (u && typeof u === 'object') {   return 'username' in u && 'currentToken' in u } return false}function getUserAddress(user: User | string) { if (isUser(user)) {   return fetchFromEmail(user) } return fetchFromId(user.id)}

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


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


import Ajv from 'ajv'const ajv = new Ajv()const validate = ajv.compile({ type: 'object', properties: {   username: { type: 'string' },   currentToken: { type: 'string' }, },})function validateUser(data: unknown): data is User { return validate(data)}

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


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


Исключающие объединения (discriminated unions)


Объединения с общим литеральным полем называются исключающими. При работе с такими типами TypeScript предоставляет неявный предохраитель, позволяя избежать его создания в явном виде:


type Member = { type: 'member' currentProject: string}type Admin = { type: 'admin' projects: string[]}type User = Member | Adminfunction getFirstProject(u: User) { if (u.type === 'member') {   return u.currentProject } return u.projects[0]}

В функции getFirstProject() TypeScript сужает область аргумента без помощи предиката. Попытка получить доступ к массиву projects в первой ветке (блоке if) закончится ошибкой типа.


Валидация во время выполнения


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


При наличии ошибки в предикате, система типов может получить неверную информацию. Рассмотрим пример:


function validateUser(data: unknown): data is User { return true}

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


const invalidUser = undefinedif (validateUser(invalidUser)) { // Предыдущая инструкция всегда возвращает `true` console.log(invalidUser.name) // Ошибка, возникающая во время выполнения}

Существует несколько библиотек, которые позволяют обеспечить автоматическую синхронизацию между валидацией во время выполнения и соответствующим типом. Одним из самых популярных решений является runtypes, однако мы будем использовать io-ts и fp-ts.


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


import * as D from 'io-ts/Decoder';import * as E from 'io-ts/Either';import { pipe } from 'fp-ts/function';// Определяем декодер, представляющий `user`const UserDecoder = D.type({   id: D.number,   username: D.string,   name: D.string   email: D.string});// и используем его в отношении потенциально опасных данныхpipe(   UserDecoder.decode(data),   E.fold(       error => console.log(D.draw(error)),       decodedData => {           // типом `decodedData` является `User`           console.log(decodedData.username)       }   ));

Настройка TypeScript


Поведение транспилятора можно настраивать с помощью файла tsconfig.json, находящегося в корне проекта.


Данный файл содержит набор ключей и значений, отвечающих за 3 вещи:


  1. Структура проекта: какие файлы включаются/исключаются из процесса компиляции, зависимости разных TypeScript-проектов, связь между этими проектами через синонимы (aliases).
  2. Поведение типизатора: выполнять ли проверку на наличие null и undefined в кодовой базе, сохранение const enums и т.п.
  3. Процесс транспиляции.

Пресеты TSConfig


TypeScript может преобразовывать код в ES3 и поддерживает несколько форматов модулей (CommonJS, SystemJS и др.).


Точные настройки зависят от среды выполнения кода. Например, если вашей целью является Node.js 10, вы можете транспилировать код в ES2015 и использовать CommonJS в качестве стратегии разрешения модулей.


Если вы используете последнюю версию Node.js, например, 14 или 15, тогда можете указать в качестве цели ESNext или ES2020 и использовать модульную стратегию ESNext.


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


К счастью, команда TypeScript разработала хороший набор пресетов, которые вы можете просто импортировать в свой tsconfig.json:


{ "extends": "@tsconfig/node12/tsconfig.json", "include": ["src"]}

Среди наиболее важных настроек, можно отметить следующее:


  • declaration: определяет, должен ли TypeScript генерировать файлы определений (.d.ts) во время транспиляции. Данные файлы, как правило, используются при разработке библиотек
  • noEmitOnError: определяет, должен ли TypeScript прерывать процесс компиляции при возникновении ошибок, связанных с неправильными типами. Рекомендуемым значением данной нстройки является true
  • removeComments: true
  • suppressImplicitAnyIndexErrors: true
  • strict: дополнительные проверки. До тех пор, пока у вас не появится веской причины для отключения данной настройки, она должна иметь значение true
  • noEmitHelpers: при необходимости, TypeScript предоставляет утилиты и полифилы для поддержки возможностей, которых не было в ES3 и ES5. Если значение данной настройки является false, утилиты будут помещены в начало кода, в противном случае, они будут опущены (tslib можно устанавливать отдельно)

Заключение


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


Система типов TypeScript не является идеальной, но это лучшее, что мы имеет на сегодняшний день.




Облачные серверы от Маклауд отлично подходят для сайтов с JavaScript.


Зарегистрируйтесь по ссылке выше или кликнув на баннер и получите 10% скидку на первый месяц аренды сервера любой конфигурации!


Подробнее..

Перевод 5 приемов по разделению бандла и ленивой загрузке компонентов в React

25.05.2021 16:13:10 | Автор: admin

image


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


Распространенные случаи разделения сборки и ленивой или отложенной загрузки (lazy loading) включают в себя следующее:


  • Загрузка дополнительного кода при переходе пользователя к новому представлению (view слой, отвечающий за визуальное отображение)


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


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


  • Это приводит к тому, что когда пользователь захочет получить доступ к определенной функциональности, она уже будет готова



    1. Динамический импорт с помощью Webpack



Webpack позволяет загружать модули (компоненты) динамически во время выполнения кода. Рассмотрим пример:


import { useState } from 'react'function MainComponent() {const [isModalDisplayed, setModalDisplayed] = useState(false)const [ModalComponent, setModalComponent] = useState(null)const loadModalComponent = async () => {const loadResult = await import('./components/Modal.js')setModalComponent(() => loadResult.default)}return (<div>{isModalDisplayed && ModalComponent ? <ModalComponent /> : null}<buttononClick={() => {setModalDisplayed(true)loadModalComponent()}}>Load Modal Component</button></div>)}

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


Динамический импорт позволяет каждому компоненту выступать в роли микрофронтенда (microfrontend).


2. Split API для загрузки React-компонентов


Пакет fusion-react предоставляет интерфейс split, компонент-обертку для отображения различных компонентов во время загрузки сборки:


  • Резервного компонента при возникновении ошибки


  • Настоящего компонента после загрузки сборки



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


import { Link, Switch, Route } from 'react-router-dom'import { split } from 'fusion-react'const Loading = () => <div>Loading...</div>const Error = () => <div>Error</div>const Hello = split({load: () => import('./components/hello.js'),Loading,Error,})const Root = () => (<><div><ul><li><Link to='/'>Home</Link></li><li><Link to='/hello'>Hello</Link></li></ul></div><Switch><Route path='/' exact component={Home} /><Route path='/hello' component={Hello} /></Switch></>)

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


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


Интерфейс split в приведенном примере откладывает загрузку компонента Hello до того момента, когда пользователь перейдет по соответствующему маршруту. Загружаемый компонент указывается в свойстве load.


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


Прим. пер.: существуют более специализированные и популярные решения для ленивой загрузки React-компонентов, например, react-loadable или react-lazyload.


3. Создание вендорного бандла (vendor bundle)


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


Вот как можно извлечь вендорный бандл из директории node_modules:


const path = require('path')module.exports = {entry: path.resolve(__dirname, 'src/index.js'),output: {path: path.resolve(__dirname, 'dist'),filename: '[name].[contenthash].js',},}

Если после этого вы запустите сборку (yarn build или npm run build), то увидите что-то вроде этого:


 webpack: Build Finished webpack: assets by status 128 KiB [emitted]asset 935.js 124 KiB [emitted] [minimized] (id hint: vendors) 2 related assetsasset main.js 3.24 KiB [emitted] [minimized] (name: main) 1 related assetasset index.html 267 bytes [emitted]assets by status 7.9 KiB [compared for emit]asset main.css 7.72 KiB [compared for emit] (name: main) 1 related assetasset 34.js 187 bytes [compared for emit] [minimized] 1 related assetEntrypoint main 135 KiB (326 KiB) = 935.js 124 KiB main.css 7.72 KiB main.js 3.34 KiB 3 auxiliary assets...webpack 5.5.0 compiled successfully in 4856 ms

4. Создание нескольких вендорных бандлов


Обычно, все модули объединяются в один вендорный бандл.


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


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


Файл с настройками Webpack принимает свойство optimization, позволяющее разделять вендорный бандл:


module.exports = {splitChunks: {chunks: 'async',cacheGroups: {default: {minChunks: 2,reuseExistingChunk: true,},vendor_react: {test: /.*\/node_modules\/react\/index\.js/,name: 'vendor-react',chunks: 'initial',enforce: true,},},},}

После этого вендорный бандл будет разделен на client-vendor.js и clietn-vendor-react.js.


5. Ленивая загрузка компонентов с помощью React.lazy()


React.lazy() это функция, позволяющая рендерить динамически импортируемые компоненты как обычные компоненты.


Обычный импорт:


import MyComponent from './MyComponent'

Динамический импорт с помощью React.lazy():


const OtherComponent = React.lazy(() => import('./OtherComponent')

Компоненты, загружаемые с помощью React.lazy(), должны быть обернуты в компонент Suspense, который позволяет отображать резервный контент (например, индикатор загрузки) до полной загрузки импортируемого компонента:


import { lazy, Suspense } from 'react'const OtherComponent = lazy(() => import('./OtherComponent'))function MyComponent() {return (<><Suspense fallback={<div>Loading...</div>}><OtherComponent /></Suspense></>)}

Проп fallback принимает любой элемент (компонент). Компонент Suspense может быть помещен на любом родительском по отношению к ленивому компоненту уровне.


Suspense может оборачивать как отдельный компонент, так и группу компонентов:


import { lazy, Suspense } from 'react'const OtherComponent = lazy(() => import('./OtherComponent'))const AnotherComponent = lazy(() => import('./AnotherComponent'))function MyComponent() {return (<><Suspense fallback={<div>Loading...</div>}><section><OtherComponent /><AnotherComponent /></section></Suspense></>)}

Заключение


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


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


Также не стоит забывать о том, что загруженный JavaScript-код, должен быть разобран и выполнен, что также требует некоторого времени и вычислительной мощности.




Облачные серверы от Маклауд быстрые и безопасные.


Зарегистрируйтесь по ссылке выше или кликнув на баннер и получите 10% скидку на первый месяц аренды сервера любой конфигурации!


Подробнее..

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

14.09.2020 20:08:14 | Автор: admin


Источник: Dribbble

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

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


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

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



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



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

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

Slides: фреймворк для создания анимаций без кода




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

  • панели
  • слайдеры
  • диалоговые окна
  • сайдбары
  • выпадающие меню
  • контактные формы
  • навигационные элементы
  • поп-апы
  • кнопки

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

Express Animate: создание видео-анимаций




Этот инструмент позволяет генерировать анимации и специальные эффекты для видео. Затем эти видео можно встраивать на веб-страницы. Проекты можно экспортировать в формате HTML5, flash или GIF. С помощью этого инструмента можно создавать специальные элементы

Koolmoves: создание анимаций и перекодирование flash


Этот инструмент позволяет создавать HTML5-анимации для наложения эффектов на изображение, анимирования элементов навигации, создания слайд-шоу и т.п. Конечный результат можно экспортировать в HTML5, GIF, MP4/AVI. Также Koolmoves позволяет конвертировать flash-анимации в более современные форматы.

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

image

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

WAIT! Animate: создание пауз в CSS-анимациях




Этот инструмент решает конкретную задачу расчет идеальной паузы между завершением анимации и ее повторным стартом. CSS не дает простых способов сделать такую паузу, поэтому WAIT! Animate оказывается неожиданно полезным инструментом.

Granim.js: работа с градиентами в анимациях


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



iTyped: анимация текстов


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



Заключение


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

Знаете какие-то еще полезные инструменты для работы с анимациями в вебе? Оставляйте ссылки в комментариях.
Подробнее..

Перевод Как сделать интерактивную карту с помощью Python и open source библиотек

06.10.2020 18:23:53 | Автор: admin

Сегодня делимся с вами пошаговым руководством создания интерактивных карт для веб-приложения или блога. Просто сохраните эту статью в закладках. Хоть и существует, например, библиотека d3.js, которая может создавать пользовательские карты, есть несколько инструментов еще проще. В этом посте посмотрим на три простые в обращении, но мощные библиотеки Python с открытым исходным кодом и поработаем с ними.



Когда документация вводит в ступор


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

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

  1. Altair простая и быстрая реализация с легкодоступным набором функций.
  2. Plotly обладает богатой функциональностью. Включает в себя Mapbox, пользовательские настройки и стилизацию.
  3. Folium Leaflet полностью настраиваемая и интерактивная. Включает в себя подсказки, всплывающие окна и многое другое.

Предварительные требования


Хороплетная карта требует двух видов данных в фоновом режиме, один из которых это геопространственные данные, географические границы для заполнения карты (обычно это векторный файл .shp (Shapefile) или GeoJSON), и две точки данных на каждом квадрате карты для цветового кодирования карты в зависимости от самих данных.

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

Altair



Altair библиотека визуализации для Python, основанная на Vega. Хороплет реализуется минимальными усилиями и включает интерактивные элементы выделение, всплывающие подсказки и т.д.

Altair совместима с fastpages. Вы можете создавать простые записи блога в считанные минуты, просто конвертируя файлы Jupyter Notebook с помощью минимального количества кода. Ознакомьтесь с Readme на GitHub.

Фрагмент кода:

# Importing required Librariesimport geopandas as gpdimport jsonimport altair as altimport pandas as pd

Читаем Shapefile как фрейм GeoPandas:

gdf = gpd.read_file('states_india.shp')

Фрейм выглядит так:



Создаем базовый слой и слой хороплета:

# Creating configs for color,selection,hoveringmulti = alt.selection_multi(fields=['count','state'], bind='legend')color = alt.condition(multi,                  alt.Color('count', type='ordinal',                  scale=alt.Scale(scheme='yellowgreenblue')),                  alt.value('lightgray'))hover = alt.selection(type='single', on='mouseover', nearest=True,                      fields=['x', 'y'])#Creating an altair map layerchoro = alt.Chart(gdf).mark_geoshape(    stroke='black').encode(     color=color,     tooltip=['state','count']).add_selection(        multi    ).properties(     width=650,    height=800)# Legendc1 = alt.layer(choro).configure_legend(    orient = 'bottom-right',    direction = 'horizontal',    padding = 10,    rowPadding = 15)#Adding Labelslabels = alt.Chart(gdf).mark_text().encode(    longitude='x',    latitude='y',    text='count',    size=alt.value(8),    opacity=alt.value(0.6))c2 = alt.Chart(gdf).mark_geoshape(    stroke='black').encode(     color=color,     tooltip=['state','count']).add_selection(        hover    ).project(    scale=100, )(c1+labels).configure_view(strokeWidth=0)

Код выше должен визуализировать интерактивную карту с функцией отображения подсказки и подсветки при выборе (клике).

Плюсы:

  • Простая и быстрая реализация. Включает предопределенный набор функций, чтобы ускорить работу.
  • Совместимость с fastpages

Минусы:

  • Мало вариантов настройки и ограниченная интерактивность.
  • Нет возможности использовать внешние стилизованные части карты, такие как OSM, Mapbox и т.п.
  • API плохо документирован.

Реализация c помощью Plotly



Библиотека Plotly для построения графиков на Python отрисовывает готовые к публикации карты с большим количеством интерактивных и настраиваемых функций.

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

Фрагмент кода:

# Importing required librariesfrom plotly.graph_objs import Scatter, Figure, Layoutimport plotlyimport plotly.graph_objs as goimport jsonimport numpy as npimport geopandas as gpd

Импортирование Shapefile:

gdf = gpd.read_file('states_india.shp')with open('states_india_1.json') as response: india = json.load(response)

Создание базового слоя и добавление частей карты:

fig = go.Figure(go.Choroplethmapbox(geojson=india, locations=gdf['st_nm'], z=gdf['state_code'],featureidkey="properties.st_nm",colorscale="Viridis", zmin=0, zmax=25,marker_opacity=0.5, marker_line_width=1))fig.update_layout(mapbox_style="carto-positron",                  mapbox_zoom=3.5,mapbox_center = {"lat":23.537876 , "lon": 78.292142} ) fig.update_layout(margin={"r":0,"t":0,"l":0,"b":0})fig.show()

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

Плюсы:

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

Минусы:

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

Реализация с помощью Folium



Folium сочетает в себе простоту использования экосистемы Python и сильные стороны картографирования библиотеки leaflet.js. Это позволяет визуализировать настраиваемые, отзывчивые и интерактивные хороплетные карты, а также передавать богатые векторные, растровые, HTML-визуализации в виде маркеров на карте.

Библиотека имеет ряд встроенных наборов частей карт из OpenStreetMap, Mapbox и Stamen, а также поддерживает пользовательские наборы через API Mapbox или Cloudmade. Поддерживаются изображения, видео, GeoJSON и TopoJSON.

Фрагмент кода:

# Importing required Librariesimport geopandas as gpdimport pandas as pdimport foliumimport brancaimport requestsimport jsonfrom folium.features import GeoJson, GeoJsonTooltip, GeoJsonPopup

Импортирование Shapefile:

gdf = gpd.read_file('states_india.shp')with open('states_india_1.json') as response:    india = json.load(response)#Creating a custom tile (optional)import branca# Create a white image of 4 pixels, and embed it in a url.white_tile = branca.utilities.image_to_url([[1, 1], [1, 1]])

Добавление базовых слоев и слоев Choropleth:

#Base layerf = folium.Figure(width=680, height=750)m = folium.Map([23.53, 78.3], maxZoom=6,minZoom=4.8,zoom_control=True,zoom_start=5,               scrollWheelZoom=True,maxBounds=[[40, 68],[6, 97]],tiles=white_tile,attr='white tile',               dragging=True).add_to(f)#Add layers for Popup and Tooltipspopup = GeoJsonPopup(    fields=['st_nm','cartodb_id'],    aliases=['State',"Data points"],     localize=True,    labels=True,    style="background-color: yellow;",)tooltip = GeoJsonTooltip(    fields=['st_nm','cartodb_id'],    aliases=['State',"Data points"],    localize=True,    sticky=False,    labels=True,    style="""        background-color: #F0EFEF;        border: 1px solid black;        border-radius: 3px;        box-shadow: 3px;    """,    max_width=800,)# Add choropleth layerg = folium.Choropleth(    geo_data=india,    data=gdf,    columns=['st_nm', 'cartodb_id'],    key_on='properties.st_nm',    fill_color='YlGn',    fill_opacity=0.7,    line_opacity=0.4,    legend_name='Data Points',    highlight=True,    ).add_to(m)folium.GeoJson(    india,    style_function=lambda feature: {        'fillColor': '#ffff00',        'color': 'black',        'weight': 0.2,        'dashArray': '5, 5'    },    tooltip=tooltip,    popup=popup).add_to(g)f

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

Плюсы:

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

Минусы:

  • Зависит от нескольких библиотек.

Заключение


Эти три инструмента позволяют без особых хлопот реализовать индивидуально настроенные интерактивные карты для веб-сайтов.

Уважаемые читатели, а вам приходилось делать такие интерактивные карты для своих проектов?

image

Получить востребованную профессию с нуля или Level Up по навыкам и зарплате, можно пройдя онлайн-курсы SkillFactory:



Подробнее..

5 распространенных ошибок разработчиков, влияющих на время загрузки страницы

29.12.2020 12:11:44 | Автор: admin

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

Почему время загрузки страницы важно?

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

Если загрузка занимает более 3 секунд, вы потеряете половину посетителей еще до того, как они попадут на ваш сайт.

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

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

  • Удобство использования - чем меньше время загрузки, тем больше будет удовлетворен пользователь. В результате, удержание клиентов будет выше.

Давайте рассмотрим несколько примеров, основанных на исследовании, проведенном HubSpot.

  • Если Yahoo сократит время загрузки страницы на 0,4 секунды, трафик может увеличиться на 9%.

  • Замедление загрузки страницы на 1 секунду может стоить Amazon 1,6 миллиарда долларов продаж в год.

  • 2-секундная задержка поиска Bing приведет к потере дохода на 4,3% на посетителя, уменьшению числа кликов на 3,75% и сокращению количества запросов на 1,8%.

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

Факторы, влияющие на время загрузки страницы, а также советы и рекомендации по оптимизации

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

1. Большое количество HTTP-запросов

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

Браузер обычно ограничивает количество одновременных запросов от 4 до 8. Следовательно, нельзя делать много запросов параллельно.

Исследования, проведенные Yahoo, показывают, что 80% времени загрузки приложения зависит от HTTP-запросов.

Можно сделать следующее, чтобы уменьшить количество HTTP-запросов:

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

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

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

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

2. Отсутствие CDN

Если у веб-сайта нет CDN, время загрузки увеличивается, когда пользователь находится далеко от сервера. Эти задержки заметны, поскольку они влияют на все HTTP-запросы к серверу.

Использование CDN сокращает время загрузки страницы.

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

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

Однако также важно правильно настроить CDN для кэширования контента с правильными значениями TTL для эффективного использования.

Но что происходит с самым первым запросом или когда срок действия кэша CDN истечет?

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

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

Совет: делитесь повторно используемыми компонентами между проектами с помощью Bit (Github).

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

Bit поддерживает Node, TypeScript, React, Vue, Angular и др.

3. Большие размеры файлов и размер страницы

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

Уменьшение размеров файлов за счет сжатия может сократить время загрузки страницы.

Сжатие файлов - лучший способ уменьшить размер файлов и ускорить загрузку. Для этого обычно используется Gzip. Он находит похожий код в ваших файлах и временно заменяет их. Большинство веб-серверов поддерживают сжатие Gzip.

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

Сжатие файлов HTML или CSS обычно экономит около 50% или 70% размера файла, что приводит к меньшему времени загрузки страницы и меньшей пропускной способности.

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

4. Загрузка всех ресурсов одновременно

Одновременная загрузка всех файлов HTML, CSS и JS увеличивает время загрузки страницы, так как отрисовка будет заблокирована до тех пор, пока не будут загружены все эти ресурсы.

Загрузка файлов JS после других элементов улучшает время загрузки страницы.

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

Пусть у вас есть HTML, вам нужно сделать вызов внешнего JS-файла (defer.js) перед тегом </body>.

<script type="text/javascript"> function downloadJSAtOnload() {   var element = document.createElement("script");   element.src = "defer.js";   document.body.appendChild(element); } if (window.addEventListener)   window.addEventListener("load", downloadJSAtOnload, false); else if (window.attachEvent)   window.attachEvent("onload", downloadJSAtOnload); else window.onload = downloadJSAtOnload;</script>

Смысл приведенного выше кода: Подождите, пока загрузится весь документ, затем загрузите файл defer.js.

5. Большое количество переадресаций

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

Можно использовать такой инструмент, как Screaming Frog, чтобы идентифицировать все перенаправления в приложении. Анализируя результат, вы сможете убрать ненужные.

Что касается переадресации, существует два типа:

  • на стороне сервера - быстрые и кэшируемые

  • на стороне клиента - медленные и не кэшируемые

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

Заключение

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

Для оценки производительности своего веб-сайта можно использовать множество инструментов, таких как Google Pagespeed Insights, Pingdom, YSlow и т.д. Они дадут некоторое представление о том, в каком месте сайт тормозит.

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

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

Подробнее..

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, которая позволяет создавать супер-быстрые статические сайты и экономит время разработки.


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


Управление контентом осуществляется централизованно. А поскольку все технические решения скрыты на стороне сервера, система менее уязвима для атак.

Подробнее..

Из песочницы Услышал интересное мнение по поводу лэндингов. Согласны или нет?

06.10.2020 14:22:26 | Автор: admin
Само слово лэндинг это некоторый миф. Некая расплывчатая сущность, которая выдает поверхностную местечковую работу за качественный и полномасштабный проект. Еще десять лет назад лэндингом называли простую разметку текста под размещение графики и контента. Далее это понимание трансформировалось в посадочную страницу для выдачи в результате ранжирования поисковых систем. После чего эта же посадочная страница стала использоваться вообще на все случаи жизни, и в конце концов превратилась в отдельностоящий продукт, который при минимальных затратах (как финансовых, так и смысловых) подменил собой полноразмерный сайт. Причем информационная часть осталась только на 5%, все остальное заменили собой блоки не несущие практически никакой интеллектуалльной нагрузки, кроме некоторой псевдо-визуализации. Давайте разберемся почему эволюционно случилось так, что это копеечную поделку стали воспринимать как обособленную разработку.

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

Упоминать в застольной беседе ресурс с количеством страниц менее десяти, считалось признаком поволжской нищеты. Если у заказчика не хватало денег на создание сайта в двадцать страниц то на его тень начинали плевать соседи, родственники и даже тётя Сара из Заволжска. Румяный, как пицца, посетитель заходящий на сайт, просто впадал бы в ступор и воровато оглядываясь на мнение жены, норовил бы быстрее закрыть страницу с подобным убожеством. Потом, стоя у холодильника он бы долго пил пшеничную отрезвляющую валерьянку с мыслью Пресвятой Депардье угодник, что я такое сейчас увидел?.

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

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

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

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

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

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

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

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

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

26.03.2021 18:19:00 | Автор: admin

Где поместить свой сервер, чтобы обеспечить максимальную скорость? Помимо времени, необходимого серверам для ответа на запросы, требуется время просто для доставки пакета из пункта А в пункт Б.

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


По мере роста сетевых задержек могут происходить странные вещи: толстые сайты могут стать более быстрыми (особенно если они полностью обслуживаются из CDN), а тонкие сайты, использующие API-интерфейсы, могут стать более медленными. Для пользователя настольного ПК/ноутбука типичная задержка достигает 200 мс, а для мобильного пользователя 4G составляет 300400 мс. В этой статье предполагается, что пропускная способность равна 40 Мбит/с, поддерживается TLS, задержка CDN равна 40 мс и нет существующих соединений. Исходная точка здесь означает основной веб-сервер (в отличие от пограничных кэшей CDN).

Почему местоположение имеет значение

Время пересечения Интернета добавляется ко времени, затраченному на ответ на запрос. Даже если ваш API-интерфейс способен ответить на запрос за 1 мс, когда пользователь находится в Лондоне, а API-сервер в Калифорнии, пользователю всё равно придется ждать ответа около 130 мс.

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

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

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

Два вида понятия быстрый для сетей

При обсуждении сетей сбивает с толку то, что люди говорят о получении более быстрых сетей: более быстрая домашняя широкополосная связь, например, или быстрый ethernet (скорость 100 мегабит в секунду уже не впечатляет).

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

API-интерфейсы, или сети CDN

Существует сеть, в которой операции выполняются быстрее, это сеть дистрибуции содержимого (Content Distribution Network, CDN). Вместо того чтобы пройти весь путь до Калифорнии, возможно, вы сможете получить часть веб-страницы из кэша в Центральном Лондоне. Такой подход экономит время. Операция может занять всего 50 мс. Экономия достигает 60 %. Кэш отлично работает для CSS-файлов, изображений и JavaScript, т. е. для ресурсов, которые не меняются для всех пользователей. Он не так хорошо работает для ответов на API-вызовы, так как ответы различны для каждого пользователя, а порой и всегда.

Количественный подход

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

Вот что я сделал:

  1. Я взял собственные журналы доступа за две недели в сентябре сразу после того, как опубликовал что-то новое. За этот период я получил около миллиона запросов от 143 тыс. уникальных IP-адресов. Я исключил очевидных роботов (на которые пришлись около 10 % запросов).

  2. Я использовал базу данных GeoIP компании Maxmind для привязки каждого IP-адреса в этих журналах доступа к соответствующим географическим координатам.

  3. Затем я использовал опубликованные на сайте WonderNetwork данные о задержках в интернет-соединениях между примерно 240 городами мира.

  4. Я сопоставил (полуавтоматически, что было довольно мучительно) названия этих городов с идентификаторами Geonames, что позволило получить координаты городов.

  5. Затем я загрузили всё вышеперечисленное в базу данных Postgres с установленным расширением PostGIS для выполнения географических запросов.

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

Результаты

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

  • среднее (P50);

  • для трёх четвертей запросов (P75);

  • для 99 % запросов (P99).

Все числа выражены в миллисекундах.

Все результаты см. в таблице

City

p50

p75

p99

Manhattan

74

97

238

Detroit

89

115

245

Secaucus

71

96

246

Piscataway

75

98

251

Washington

82

105

253

Chicago

90

121

253

Kansas City

98

130

254

Indianapolis

96

125

254

St Louis

96

127

256

Cincinnati

92

121

257

Houston

104

134

257

Syracuse

77

102

257

Scranton

78

103

258

Quebec City

83

113

259

South Bend

92

118

259

Montreal

83

104

259

Charlotte

91

110

259

Salem

74

98

259

Buffalo

80

111

259

Albany

75

100

260

Monticello

94

123

260

Baltimore

80

105

260

Asheville

95

118

260

New York

77

103

261

Berkeley Springs

84

112

261

Minneapolis

102

133

261

Barcelona

102

148

261

Dallas

112

140

262

Des Moines

104

131

262

San Jose

139

165

263

Brunswick

77

101

264

Atlanta

88

113

264

San Francisco

136

168

264

Halifax

80

102

265

Philadelphia

77

100

266

Basel

97

146

267

Green Bay

103

131

267

Pittsburgh

88

117

267

Bern

99

147

267

Denver

112

141

267

Miami

103

129

267

Raleigh

88

111

268

Knoxville

114

135

268

Boston

77

105

268

Valencia

108

148

268

Jackson

105

132

268

Memphis

101

131

268

Jacksonville

95

122

268

Madrid

95

138

268

London

76

130

268

San Diego

138

162

269

San Antonio

112

138

269

Salt Lake City

120

151

269

Toronto

87

111

269

Cleveland

97

122

269

Austin

113

141

270

Colorado Springs

110

136

270

Orlando

103

126

270

Antwerp

93

137

271

Oklahoma City

114

147

271

Saskatoon

115

140

272

Lansing

98

127

272

Seattle

141

164

272

Columbus

92

120

273

Bristol

76

129

274

Tampa

104

130

274

Lausanne

95

139

274

Ottawa

85

111

274

Falkenstein

91

137

275

Maidstone

76

129

275

Paris

80

129

275

Toledo

102

129

275

Savannah

117

146

276

The Hague

82

138

276

Liege

87

136

277

Lincoln

100

124

277

New Orleans

115

142

278

Amsterdam

82

140

278

Las Vegas

136

163

279

Vienna

102

149

279

Coventry

80

132

279

Cromwell

80

106

280

Arezzo

109

160

280

Cheltenham

79

131

280

Sacramento

137

167

280

Alblasserdam

82

137

281

Vancouver

142

165

281

Fremont

131

157

283

Gosport

76

137

284

Frankfurt

93

136

284

Carlow

88

136

285

Phoenix

128

153

285

Portland

132

159

285

Cardiff

78

131

285

Luxembourg

87

137

285

Bruges

83

135

285

Eindhoven

85

133

285

Groningen

87

139

286

Manchester

80

137

286

Brussels

90

139

287

Brno

106

148

287

Edinburgh

84

136

287

Nuremberg

89

136

288

Albuquerque

125

159

289

Los Angeles

141

164

289

Ljubljana

110

152

289

Lugano

97

147

290

Zurich

103

146

290

Dronten

84

133

290

Newcastle

87

147

290

Rome

96

147

291

Dusseldorf

90

140

291

Munich

98

144

291

Venice

106

156

292

Edmonton

139

165

292

Copenhagen

96

145

292

St Petersburg

113

163

293

Dublin

85

143

293

Redding

142

178

293

Vilnius

110

162

293

Belfast

79

125

294

Nis

113

158

294

Douglas

87

143

294

Rotterdam

82

139

295

Bergen

107

157

295

Strasbourg

89

141

295

Roseburg

148

172

296

Graz

104

147

296

San Juan

117

141

298

Warsaw

108

161

299

Frosinone

105

153

299

Riyadh

159

206

300

Prague

103

152

301

Ktis

102

158

302

Mexico

139

164

302

Belgrade

113

160

302

Guadalajara

128

155

303

Milan

96

146

305

Bratislava

102

154

306

Osaka

181

240

307

Zagreb

103

150

308

Tallinn

108

162

308

Helsinki

105

156

308

Hamburg

127

166

309

Oslo

98

153

311

Bucharest

120

162

311

Riga

113

159

312

Panama

150

177

313

Tokyo

188

238

313

Kiev

119

168

313

Stockholm

102

153

314

Budapest

110

162

314

Kharkiv

128

169

315

Gothenburg

115

167

316

Pristina

122

167

316

Tirana

128

184

316

Geneva

96

142

316

Siauliai

113

163

317

Cairo

133

182

318

Sapporo

196

255

318

Bogota

170

188

319

Palermo

119

183

320

Gdansk

107

152

320

Caracas

149

176

320

Sofia

114

161

321

Westpoort

79

134

321

Honolulu

173

196

321

Roubaix

102

157

321

Kazan

138

190

322

Winnipeg

169

190

322

Varna

120

173

322

Tel Aviv

138

194

322

Lisbon

115

166

324

Jerusalem

145

198

324

Ankara

139

195

327

Heredia

164

188

327

Athens

128

183

329

Reykjavik

127

180

329

Paramaribo

166

194

330

Algiers

120

173

332

Chisinau

127

180

333

Bursa

135

188

334

Thessaloniki

134

187

336

Limassol

141

186

337

Lyon

95

145

340

Mumbai

204

248

340

Medellin

163

186

344

Valletta

120

176

345

Baku

160

205

346

Melbourne

227

269

346

Fez

149

198

348

Tunis

124

180

348

Koto

217

254

348

Dubai

192

243

350

Tbilisi

153

208

351

Malaysia

195

235

352

Hyderabad

214

260

354

Bangalore

212

252

355

Izmir

137

187

357

Adelaide

241

272

359

Chennai

221

248

359

Moscow

127

172

359

Lahore

217

270

361

Novosibirsk

163

206

362

Sydney

237

272

363

Karaganda

180

231

363

Vladivostok

223

264

364

Taipei

265

293

364

Lima

169

199

364

Istanbul

135

182

366

Hong Kong

199

223

366

Auckland

244

291

367

Jakarta

207

245

368

Seoul

231

277

371

Beirut

136

195

372

Accra

168

216

373

Singapore

190

246

374

Sao Paulo

193

213

375

Joao Pessoa

182

220

378

Perth

243

267

379

Ho Chi Minh City

253

287

380

Wellington

251

295

383

Brasilia

226

249

384

Manila

251

281

385

Pune

202

251

386

Dhaka

231

268

386

Phnom Penh

243

267

386

Santiago

202

230

390

Lagos

191

233

391

Quito

162

188

392

New Delhi

230

264

395

Johannesburg

237

283

398

Bangkok

222

254

401

Canberra

262

295

402

Dar es Salaam

214

267

407

Dagupan

239

268

408

Christchurch

257

309

409

Hanoi

235

264

415

Cape Town

216

262

417

Buenos Aires

232

253

417

Guatemala

217

249

418

Brisbane

261

288

422

Indore

304

352

457

Zhangjiakou

236

264

457

Nairobi

233

277

468

Kampala

244

287

480

Hangzhou

239

267

517

Shenzhen

242

275

523

Shanghai

300

367

551

Montevideo

738

775

902

Вы также можете загрузить все результаты как csv-файл, если так проще.

Результат: восточное побережье Северной Америки хорошо, прямо в Атлантике лучше

Все лучшие места находятся в Северной Америке. Это, вероятно, не стало полным сюрпризом, учитывая, что это довольно плотный кластер носителей английского языка, от которого не так далеко (с точки зрения задержки), в Великобритании/Ирландской Республике, находится другой кластер. Кроме того, в Европе много тех, для кого английский второй язык. Лучше всего находиться прямо в Атлантике: в штатах Нью-Джерси и Нью-Йорк есть много отличных мест для P99, и показатели в верхней части, между P50 и P99, варьируются не слишком сильно.

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

В настоящее время мой сервер находится в Хельсинки. Это был самый дешёвый вариант, что необычно для Финляндии. За этот сервер я плачу около трёх фунтов в месяц, всего лишь. Если бы я переместил его куда-нибудь в Нью-Джерси и потратил больше денег, в целом пользователи определённо сэкономили бы время: половина операций передачи и подтверждения была бы завершена за 75, а не за 105 мс, что позволило бы сэкономить 30 %. За несколько операций передачи и подтверждения время, вероятно, увеличилось бы примерно на шестую долю секунды по сравнению со средним показателем загрузки первой страницы, что не так уж плохо. Если вы не можете сказать, что этот веб-сайт очень обременителен для веб-браузеров с точки зрения визуализации, сокращение времени ожидания в сети сделает его значительно быстрее.

Поскольку на этом сайте ничего не генерируется динамически, в действительности мне лучше всего использовать CDN. Это действительно сэкономило бы много времени для всех: обслуживание из CDN почти в два раза лучше (~40 мс) установки сервера в самом быстром месте (71 мс).

Как это может меняться со временем

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

Город

Расстояние (км)

Реальная задержка

Теоретический максимум

Фактор замедления

Нью-Йорк

5 585

71

37

1,9

Лима

10 160

162

68

2,4

Джакарта

11 719

194

78

2,5

Каир

3 513

60

23

2,6

Санкт-Петербург

2 105

38

14

2,7

Бангалор

8 041

144

54

2,7

Богота

8 500

160

57

2,8

Буэнос-Айрес

11 103

220

74

3,0

Лагос

5 006

99

33

3,0

Москва

2 508

51

17

3,0

Сан-Паулу

9 473

193

63

3,1

Бангкок

9 543

213

64

3,3

Гонконг

9 644

221

64

3,4

Стамбул

2 504

60

17

3,6

Лахор

6 298

151

42

3,6

Токио

9 582

239

64

3,7

Ханчжоу

9 237

232

62

3,8

Шанхай

9 217

241

61

3,9

Mumbai

7 200

190

48

4,0

Тайбэй

9 800

268

65

4,1

Дакка

8 017

229

53

4,3

Сеул

8 880

269

59

4,5

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

Как видно из таблицы, задержка в Нью-Йорке находится в пределах коэффициента 2 от значения для скорости света, но маршруты в другие места, такие как Дакка и Сеул, намного медленнее: они в 4 раза превышают значения для скорости света. Вероятно, маршрут между Лондоном и Нью-Йорком так хорошо оптимизирован по вполне понятным причинам. Я сомневаюсь, что то, что между этими городами в основном лежит океан, представляет большую проблему, так как подводные кабели можно прокладывать напрямую. Добираться до Сеула или Дакки придётся более окольным путём.

Вероятно, следует упомянуть, что новые протоколы обещают уменьшить количество операций передачи и подтверждения. Версия TLS 1.3 позволяет создать зашифрованный сеанс с одной операцией передачи и подтверждения, а HTTP3 может объединить операции передачи и подтверждения протоколов HTTP и TLS. Это означает, что теперь нужны лишь три такие операции: одна для DNS, одна-единственная операция передачи и подтверждения для соединения и зашифрованного сеанса, и, наконец, третья для темы запроса.

Некоторые люди ложно надеются, что новые протоколы, такие как HTTP3, устранят необходимость в объединении (bundling) JavaScript/CSS. Это основано на недоразумении: в то время как HTTP/3 удаляет некоторые исходные операции передачи и подтверждения, эта версия не удаляет последующие операции передачи и подтверждения для дополнительных данных JavaScript или CSS. Так что объединение, к сожалению, никуда не делось.

Слабые стороны данных

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

Во-первых, база данных GeoIP неоднозначно показывает местоположение IP-адресов. Заявленная (т. е., вероятно, оптимистичная) точность колеблется до 1000 км в некоторых случаях, хотя для моего набора данных утверждается, что средняя точность составляет 132 км со стандартным отклонением 276 км. Это не так уж точно, но я думаю, всё ещё полезно.

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

У WonderNetwork много станций, но их охват не идеален. На Западе всё отлично в Великобритании представлены даже второстепенные города (вроде Ковентри). Их охват во всём мире по-прежнему хорош, но более неоднозначен. У них не так много мест в Африке или Южной Америке, а некоторые задержки в Юго-Восточной Азии кажутся странными: задержка между Гонконгом и Шэньчжэнем составляет 140 мс, тогда как эти города находятся всего в 50 км друг от друга. Этот фактор замедления более чем в тысячу раз превышает значение для скорости света. Для других городов континентального Китая проверка связи также показывает плохие результаты (что странно), хотя и не в таком масштабе. Может быть, эти коммунисты проверяют каждый ICMP-пакет вручную?

Другая проблема с данными о задержке заключается в отсутствии истинных координат центров обработки данных, в которых находятся серверы. Мне пришлось самому геокодировать данные с помощью некоторых сценариев и большого количества ручного ввода данных в Excel (я опубликовал эту таблицу Excel в github, чтобы никому не пришлось делать эту работу заново). Я очень старательно проверял данные, но ошибки всё ещё могут быть.

Однако, по моему мнению, самая большая слабость заключается в том, что все начинают отсчёт прямо от центра своего ближайшего города. На практике это не так и добавляемое из-за этого смещение может варьироваться. Здесь, в Великобритании, домашний доступ к Интернету это сложный процесс, основанный на отправке высокочастотных сигналов по медным телефонным линиям. Моя собственная задержка для других хостов в Лондоне достигает 9 мс, что плохо для такого короткого расстояния, но всё ещё на 31 мс лучше среднего значения. Многие маршрутизаторы потребительского уровня не очень хороши и добавляют значительную задержку. Печально известная проблема излишней сетевой буферизации ещё один распространённый источник задержки. Особенно это влияет на процессы, для хорошей работы которых требуется последовательный уровень задержки. В качестве примера можно привести видеоконференц-связь и многопользовательские компьютерные игры. Использование сети мобильных телефонов тоже не помогает. Сети 4G в хороших условиях добавляют около 100 мс задержки, но, конечно, всё гораздо хуже, когда уровень сигнала низок и есть много ретрансляций на уровне канала.

Я пытался предположить глобальную среднюю задержку на километр (около 0,03 мс), чтобы компенсировать расстояние до ближайшего города, но обнаружил, что это просто добавило кучу шума к моим результатам, так как для многих IP-адресов в моём наборе данных окольный путь был нереалистичным: ближайший город, который я им приписал, совсем не так близок.

Универсальность

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

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

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

Бесплатные операции передачи и подтверждения

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

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

Операции передачи и подтверждения легко добавить случайно. Особенно удивительным источником операций передачи и подтверждения служат предварительные запросы общего доступа к ресурсам независимо от источника (CORS). По соображениям безопасности, связанным с предотвращением атак на основе межсайтового скриптинга, браузеры должны проверять определённые HTTP-запросы, созданные кодом JavaScript. Для этого на тот же URL-адрес предварительно отправляется запрос с помощью специальной команды OPTIONS. На основе полученного ответа принимается решение о допустимости исходного запроса. Правила, определяющие точный момент отправки предварительных запросов, сложны, но в сети появляется удивительное количество запросов: в частности, включая POST-запросы JSON к поддоменам (например, к поддомену api.foo.com от домена foo.com) и сторонние веб-шрифты. В предварительных проверках на основе CORS-запросов используется другой набор заголовков кэширования по сравнению с остальным HTTP-кэшированием, которые редко задаются правильно и в любом случае применимы только к последующим запросам.

В наши дни многие сайты написаны как одностраничные приложения, где вы загружаете некоторый статический пакет JavaScript (надеюсь, из CDN), а затем создаёте (надеюсь, небольшое) количество API-запросов внутри своего браузера, чтобы решить, что показывать на странице. Есть надежда, что этот процесс станет быстрее после первого запроса, так как не придётся перерисовывать весь экран, когда пользователь попросит загрузить вторую страницу. Как правило, это не помогает, потому что одна HTML-страница обычно заменяется несколькими последовательными API-вызовами. Пара последовательных API-вызовов к исходному серверу почти всегда обрабатывается медленнее перерисовки всего экрана, особенно в сети мобильной связи.

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

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

Пропускная способность Интернета становится всё лучше и лучше. Сейчас можно передавать гораздо больше байт в секунду, чем даже несколько лет назад. Однако улучшения задержки довольно редки, и по мере приближения к значению для скорости света они совсем сойдут на нет.

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

Смотрите также

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

Пока я писал эту статью, произошла вспышка клубов, основанных на весе страницы, таких как Клуб 1МБ и, предположительно, более элитный Клуб 512К. Пожалуй, я одобряю это чувство (и всё это во имя веселья, я уверен). Я думаю, что они слишком подчёркивают размер передаваемых данных. Если вы в Лондоне запрашиваете динамически генерируемую страницу из Калифорнии, весь процесс всё равно займёт большую часть секунды (130 мс умножить на 5 операций передачи и подтверждения), независимо от того, насколько велик размер этой страницы.

На карту подводных кабелей всегда приятно смотреть. Если вы хотите увидеть признак разной важности разных мест: Нормандские острова (население 170 тыс. человек) имеют 8 подводных кабелей, в том числе два, которые просто соединяют Гернси и Джерси. У Мадагаскара (население 26 млн. человек) их всего четыре. Я также считаю забавным то, что, хотя Аляска и Россия довольно близки, между ними нет ни одного кабеля.

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

Узнайте, как прокачаться в других специальностях или освоить их с нуля:

Другие профессии и курсы
Подробнее..

Из песочницы ZoomX. Меняем правила шаблонизации в MODX Revolution

26.10.2020 22:08:44 | Автор: admin
Привет, друзья! Хорошая новость для модыксеров компонент, о котором мы много говорили в сообществе, вышел. Возможно не все в курсе о чём речь. Давайте я расскажу поподробнее.

Что такое ZoomX


Задача, которую позволяет решать данный компонент максимально полный отказ от встроенного шаблонизатора MODX. Таким образом, мы выбрасываем многократный парсинг контента, элементы, хранящиеся в БД, плейсхолдеры с точками и плюсами и добавляем любой современный быстрый мощный PHP шаблонизатор. Стандартные шаблоны из БД теперь используются только для привязки TV-шек к ресурсам.

Из коробки сразу доступен Smarty, так как он уже присутствует в ядре. Плюс, через заложенные в систему возможности расширения, заменены класс обработки запроса и класс подготовки ответа. Не надо ни взбалтывать, ни перемешивать, как в случае с Fenom. После установки компонента можно не заходя в админку создавать шаблоны в удобном IDE редакторе. Дальше останется только их привязать к ресурсам. А для этого используется FastRoute Никиты Попова.

Роутер


Для MODX Revolution есть несколько компонентов, позволяющих управлять роутингом. Но все они срабатывают на событие OnPageNotFound, т.е. когда MODX не нашёл документа по указанному URI. В ZoomX роутинг срабатывает перед встроенным поиском документа. И результат зависит от режима:

  • Отключен. Все указанные роуты игнорируются. MODX работает в обычном режиме.
  • Совместный (мягкий). Если для указанного URI роут не найден, то MODX продолжит обработку запроса в обычном режиме.
  • Строгий. Если для указанного URI роут не найден, то обработка запроса будет завершена с ошибкой 404.

Таким образом, вы можете работать или в привычном режиме, или использовать PHP шаблонизатор только для указанных ресурсов, или полностью отдать управление PHP шаблонизатору. It's up to you.

При использовании PHP шаблонизатора (на первом этапе это Smarty) также будет доступен и стандартный синтаксис MODX. Для этого есть специальный тег parse:

{parse}[[!snippet?foo=`bar`]]{/parse}// Или так{'[[!snippet?foo=`bar`]]'|parse:'modParser'}

Также ZoomX добавляет дополнительные плагины Smarty для работы с MODX.

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

Проект на GitHub.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru