<?xml version="1.0" encoding="utf-8"?><rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title>Веб-стандарты — Статьи</title><description>Авторские и переводные статьи по фронтенду</description><language>ru</language><link>https://web-standards.ru/articles/</link><lastBuildDate>Tue, 10 Jan 2023 00:00:00 GMT</lastBuildDate><atom:link href="https://web-standards.ru/articles/feed/" rel="self" type="application/rss+xml"/><item><title>OKLCH в CSS: почему мы ушли от RGB и HSL</title><link>https://web-standards.ru/articles/oklch-in-css-why-quit-rgb-hsl/</link><description><![CDATA[
                        <style>
    .colors {
        display: flex;
        justify-content: center;
        margin-block: -16px 16px;
        padding-block: 24px;
        column-gap: 8px;
        border: 4px solid transparent;
        border-image: linear-gradient(90deg, var(--token-color-content-block-code-gradient)) 1;
        background-image:
            repeating-conic-gradient(
                #fff 0% 25%,
                #999 0% 50%
            );
        background-size: 16px 16px;
        background-position: 50% 0;
    }

    @media (min-width: 1240px) {
        .colors {
            margin-block: -60px 60px;
            column-gap: 16px;
        }
    }

    .colors__swatch {
        width: 32px;
        height: 32px;
        border: 2px solid var(--color-grey-dark);
    }

    @media (min-width: 1240px) {
        .colors__swatch {
            width: 48px;
            height: 48px;
        }
    }
</style>
<p>В CSS мы чаще всего пишем цвета через <code>rgb()</code> или hex — но так сложилось исторически. Новая спецификация CSS Color 4 позволит нам описывать цвета через новые методы. В этой статье мы расскажем, почему нам больше всего нравится <code>oklch()</code>.</p>
<h2>Краткое объяснение</h2>
<p><code>oklch()</code> — новый способ определять цвета в CSS. В <code>oklch(L C H)</code> или <code>oklch(L C H / a)</code> компоненты из аббревиатуры расшифровываются так:</p>
<ul>
<li><code>L</code>, lightness — яркость (0%–100%), передаётся так, как её увидит глаз, в отличие от <code>L</code> в <code>hsl()</code>;</li>
<li><code>C</code>, chroma — насыщенность, варьируется от серого до наиболее интенсивного оттенка;</li>
<li><code>H</code>, hue — оттенок, угол поворота на цветовом круге (0–360);</li>
<li><code>a</code>, alpha — непрозрачность (0–1 или 0%–100%).</li>
</ul>
<pre><code tabindex="0" class="language-css">a:hover {
    /* синий */
    background: oklch(45% 0.26 264);
    /* белый */
    color: oklch(100% 0 0);
    /* чёрный с прозрачностью 50% */
    color: oklch(0% 0 0 / 50%);
}
</code></pre>
<div class="colors">
    <div class="colors__swatch" style="
        background: rgb(0.21, 50.42, 225.59)
    "></div>
    <div class="colors__swatch" style="
        background: rgb(255, 255, 255)
    "></div>
    <div class="colors__swatch" style="
        background: rgb(0 0 0 / 50%)
    "></div>
</div>
<p>Формат OKLCH обладает многими преимуществами:</p>
<ol>
<li>В отличие от <code>rgb()</code> или hex (<code>#ca0000</code>), OKLCH легко читается. По числам внутри <code>oklch()</code> мы без труда сможем определить записанный цвет. Это похоже на удобство формата HSL, но тот не передаёт яркость так, как её видит человек;</li>
<li><code>oklch()</code> лучше подходит для изменения цвета, чем <code>hsl()</code>. Он правильно передаёт яркость — она не меняется при смене оттенка (вспомните неожиданные результаты у <code>darken()</code> в Sass из-за использования HSL);</li>
<li>Из-за предсказуемой яркости у OKLCH гораздо лучше с доступностью. Это очень важно при создании палитр для дизайн-систем;</li>
<li>Многие новые устройства (например, от Apple) поддерживают больше цветов, чем старые мониторы sRGB. OKLCH позволяет использовать эти P3-цвета.</li>
</ol>
<p>Работая с OKLCH, следует помнить о важных нюансах:</p>
<ol>
<li>Для OKLCH при подборе L, C и H есть риск получить цвет, выходящий за пределы возможностей экрана. Хоть браузеры и попытаются найти ближайший поддерживаемый цвет, нам стоит проверять результат в цветовом миксере;</li>
<li>OKLCH — цветовое пространство, которое появилось совсем недавно. На момент написания этой статьи в 2022 году его экосистема ограничена. Но у нас уже есть <a href="https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-oklab-function">полифилы</a>, <a href="https://huetone.ardov.me/">генератор палитры</a>, <a href="https://oklch.com/">цветовой миксер</a> и много <a href="https://bottosson.github.io/posts/oklab/#oklab-implementations">конвертеров</a>.</li>
</ol>
<figure>
    <img src="https://web-standards.ru/articles/oklch-in-css-why-quit-rgb-hsl/images/oklch-picker.png" width="1088" height="552" loading="lazy" alt="Цветовой миксер OKLCH Злых марсиан отображает пространство OKLCH с ползунками для настройки яркости, насыщенности, альфа-канала и оттенка.">
    <figcaption>Пространство OKLCH в цветовом миксере.</figcaption>
</figure>
<h2>Оглавление</h2>
<ul>
<li><a href="https://web-standards.ru/articles/oklch-in-css-why-quit-rgb-hsl/#section-3">Развитие цвета в CSS</a></li>
<li><a href="https://web-standards.ru/articles/oklch-in-css-why-quit-rgb-hsl/#section-7">Сравнение OKLCH с другими CSS-форматами цветов</a></li>
<li><a href="https://web-standards.ru/articles/oklch-in-css-why-quit-rgb-hsl/#section-12">Принципы работы OKLCH</a></li>
<li><a href="https://web-standards.ru/articles/oklch-in-css-why-quit-rgb-hsl/#section-17">Внедрение OKLCH в проект</a></li>
<li><a href="https://web-standards.ru/articles/oklch-in-css-why-quit-rgb-hsl/#section-27">Подведение итогов</a></li>
</ul>
<h2>Развитие цвета в CSS</h2>
<h3>CSS Colors Module 4</h3>
<p>Немного истории: 5 июля 2022 года спецификация <a href="https://www.w3.org/TR/css-color-4/">CSS Color Module Level 4</a> стала кандидатом в рекомендации W3C. В ней появляется синтаксический сахар для всех функций цвета, которым мы будем пользоваться в этой статье:</p>
<pre><code tabindex="0" class="language-css">.old {
    color: rgb(51, 170, 51);
    color: rgba(51, 170, 51, 0.5);
}

.new {
    color: rgb(51 170 51);
    color: rgb(51 170 51 / 50%);
}
</code></pre>
<div class="colors">
    <div class="colors__swatch" style="
        background-color: rgb(51 170 51)
    "></div>
    <div class="colors__swatch" style="
        background-color: rgb(51 170 51 / 50%)
    "></div>
</div>
<p>Но что ещё важнее, CSS Color 4 добавляет 14 новых способов для определения цвета. И это не просто синтаксический сахар. Новые форматы записи (среди которых и <code>oklch()</code>) улучшают читаемость кода, доступность и могут принести прямую пользу для наших сайтов.</p>
<h3>P3 Colors</h3>
<p>Человеческий глаз способен различить гораздо больше цветов, чем способны отобразить многие устройства. Современные мониторы в основном показывают небольшой набор цветов, который называется sRGB.</p>
<p>Но уже сейчас все современные устройства Apple и многие OLED-экраны поддерживают на 30% больше цветов, чем есть в sRGB. Этот расширенный набор цветов называется P3. Он также известен, как широкий цветовой охват (wide-gamut colors).</p>
<p>Поддержка дополнительных 30% цветов будет полезна для дизайнеров:</p>
<ul>
<li>Новые цвета зачастую заметно насыщеннее старых. Это поможет создавать более привлекательные и красочные сайты;</li>
<li>Больше цветов — больше гибкости при создании палитр для дизайн-систем.</li>
</ul>
<figure>
    <img src="https://web-standards.ru/articles/oklch-in-css-why-quit-rgb-hsl/images/p3.png" width="1088" height="388" loading="lazy" alt="Слева: расширяющийся клин, который показывает как P3-цвета расширяют цветовое многообразие по сравнению с sRGB-пространством. Справа: два иконки, левая в sRGB-цветах, а правая в P3-цветах, гораздо ярче.">
    <figcaption>По сравнению с sRGB, в P3 гораздо больше зелёных оттенков и цвета могут быть ярче.</figcaption>
</figure>
<p>Итак, теперь у нас появились P3-цвета, но радоваться ещё рано: чтобы их использовать, нам нужно найти формат, который поддерживает охват P3. <code>rgb()</code>, <code>hsl()</code> или hex не поддерживают P3. Хотя мы и можем использовать новую запись <code>color(display-p3 1 0 0)</code>, но по числам в ней непонятно, какой указан цвет.</p>
<p>К нашему везению, OKLCH очень легко читать и менять прямо в коде. К тому же он поддерживает не только P3, но и любые цвета, которые способен увидеть человек.</p>
<h3>Производные цвета в CSS</h3>
<p><a href="https://www.w3.org/TR/css-color-4/">CSS Color 4</a> — большой шаг в мире стилей, но грядущий <a href="https://www.w3.org/TR/css-color-5/">CSS Color 5</a> будет ещё полезнее. В нём, наконец, появятся производные цвета — изменения цвета, встроенные прямо в CSS.</p>
<p>В качестве примера в следующих CSS-правилах используются <code>hsl()</code>. Не используйте формат <code>hsl()</code> в реальных проектах для трансформации цветов, это может привести к проблемам с доступностью.</p>
<pre><code tabindex="0" class="language-css">:root {
    --accent: hsl(63 61% 40%);
}

.error {
    /* Красный вариант акцентного цвета */
    background: hsl(from var(--accent) 20 s l);
}

.button:hover {
    /* Вариант на 10% светлее */
    background: hsl(
        from var(--accent) h s calc(l + 10%)
    );
}
</code></pre>
<div class="colors">
    <div class="colors__swatch" style="
        background-color: hsl(63 61% 40%)
    "></div>
    <div class="colors__swatch" style="
        background-color: hsl(20 61% 40%)
    "></div>
    <div class="colors__swatch" style="
        background-color: hsl(63 61% 50%)
    "></div>
</div>
<p>С новым синтаксисом мы можем взять один цвет — например, из кастомного свойства — и легко изменить его, переписав отдельные компоненты цвета.</p>
<p>Но, как мы упоминали, у <code>hsl()</code> есть ощутимый недостаток: этот формат может привести к проблемам с доступностью. Для него значение L зависит от оттенка, поэтому после изменения через <code>hsl()</code> может получиться плохой контраст текста и фона.</p>
<p>Здесь мы возвращаемся к старой проблеме: нам необходимо пространство, где работа с цветом даёт предсказуемый результат. И OKLCH отлично подходит для этой задачи.</p>
<pre><code tabindex="0" class="language-css">:root {
    --accent: oklch(70% 0.14 113);
}

.error {
    /* Красный вариант акцентного цвета */
    background: oklch(
        from var(--accent) l c 15
    );
}

.button:hover {
    /* Вариант на 10% светлее */
    background: oklch(
        from var(--accent) calc(l + 10%) c h
    );
}
</code></pre>
<div class="colors">
    <div class="colors__swatch" style="
        background-color: rgb(160 167 45)
    "></div>
    <div class="colors__swatch" style="
        background-color: rgb(232 119 130)
    "></div>
    <div class="colors__swatch" style="
        background-color: rgb(190 199 82)
    "></div>
</div>
<p>Отметим: <code>--accent</code> в <code>oklch(from …)</code> не обязательно должен быть тоже в формате OKLCH. Но для улучшения читаемости мы рекомендуем писать в едином формате.</p>
<h2>Сравнение OKLCH с другими форматами цветов в CSS</h2>
<p>Конечно, мы можем в разных файлах писать цвет в разных форматах. Необязательно везде использовать <code>oklch()</code>. Но единый подход к записи значительно улучшает читаемость и поддерживаемость кода.</p>
<p>Давайте попробуем выбрать единый, универсальный формат записи цвета. Наши критерии для выбора:</p>
<ol>
<li>У него должна быть встроенная поддержка в CSS;</li>
<li>Он должен уметь работать как минимум с широким цветовым охватом P3;</li>
<li>Он должен хорошо подходить для изменения цвета. Компоненты формата должны быть удобочитаемыми и при этом не зависеть друг от друга. Например, изменение яркости должно сохранять прежний уровень контраста, а изменение насыщенности не должно менять оттенок.</li>
</ol>
<h3>OKLCH против RGB и hex</h3>
<p>Все форматы <code>rgb(109 162 218)</code>, <code>#6ea3db</code> или P3-аналог <code>color(display-p3 0.48 0.63 0.84)</code> записываются тремя числами, которые определяют количество красного, зелёного и голубого соответственно. Обратите внимание: <code>1</code> в <code>color(display-p3)</code> кодирует большее значение, чем <code>255</code> в RGB.</p>
<p>Какая проблема объединяет все эти форматы? Они абсолютно нечитаемы для большинства разработчиков. Люди часто просто копируют их как магический набор символов, не пытаясь их прочитать и понять.</p>
<p>RGB, hex и <code>color(display-p3)</code> неудобны для трансформаций цвета, поскольку большинству людей интуитивно сложно задавать цвета, изменяя количество красного, голубого и зелёного. Кроме того, с помощью RGB и hex нельзя определить P3-цвета.</p>
<p>С другой стороны, OKLCH, LCH и HSL кодирует цвет так, как люди думают о цветах — через оттенок, насыщенность, яркость.</p>
<p>Сравните hex и OKLCH:</p>
<pre><code tabindex="0" class="language-css">.button {
    /* Голубой */
    background: #6ea3db;
}

.button:hover {
    /* Более яркий голубой */
    background: #7db3eb;
}

.button.is-delete {
    /* Красный с той же насыщенностью */
    background: #d68585;
}
</code></pre>
<div class="colors">
    <div class="colors__swatch" style="
        background-color: #6ea3db
    "></div>
    <div class="colors__swatch" style="
        background-color: #7db3eb
    "></div>
    <div class="colors__swatch" style="
        background-color: #d68585
    "></div>
</div>
<pre><code tabindex="0" class="language-css">.button {
    /* Голубой */
    background: oklch(70% 0.1 250);
}

.button:hover {
    /* Более яркий голубой */
    background: oklch(75% 0.1 250);
}

.button.is-delete {
    /* Красный с той же насыщенностью */
    background: oklch(70% 0.1 20);
}
</code></pre>
<div class="colors">
    <div class="colors__swatch" style="
        background-color: #6ea3db
    "></div>
    <div class="colors__swatch" style="
        background-color: #7db3eb
    "></div>
    <div class="colors__swatch" style="
        background-color: #d68585
    "></div>
</div>
<p>OKLCH-цвета можно прочитать и сравнить. Но пространство появилось совсем недавно — его экосистема только находится в стадии активного развития.</p>
<h3>OKLCH против HSL</h3>
<p>Теперь сравним OKLCH и HSL. HSL использует три компонента для кодировки оттенка, насыщенности и яркости, например <code>hsl(210 60% 64%)</code>. Главная проблема с HSL в том, что это цветовое пространство растягивается в цилиндр.</p>
<p>В цилиндре HSL каждый оттенок должен иметь одинаковое значение насыщенности (0%–100%). Но в реальности экран и глаз имеют разную максимальную насыщенность для разных оттенков. HSL не обращает внимания на это различие. Он деформирует цветовое пространство и растягивает цвета до одинакового показателя максимальной насыщенности.</p>
<figure>
    <img src="https://web-standards.ru/articles/oklch-in-css-why-quit-rgb-hsl/images/hsl-vs-oklch.png" width="1088" height="526" loading="lazy" alt="Четыре графика. Два сверху показывают пространства HSL и OKLCH с одинаковыми значениями насыщенности и интенсивности. Два снизу — аналогично для чёрного и белого цвета.">
    <figcaption>Срез яркости и оттенка в пространствах HSL и OKLCH. На чёрно-белой версии ниже видно, что у HSL реальная яркость меняется непредсказуемо.</figcaption>
</figure>
<p>HSL растягивает цветовое пространство, поэтому через него нельзя менять цвет. Компонент L в нём является непредсказуемым, зависящим от оттенка. Это приводит к неприятным проблемам с контрастом и доступностью.</p>
<p>Вот несколько реальных примеров, где проявляются эти проблемы:</p>
<ol>
<li>Добавление 10% яркости для зелёного или для фиолетового цвета приведут к разным результатам. Если вы когда-нибудь использовали функцию <code>darken()</code> в Sass, то помните, как по одной формуле нельзя было сделать одинаковый <code>:hover</code>-стиль для всех оттенков;</li>
<li>Если вы поменяете оттенок (например, чтобы из фирменного цвета сделать красный цвет ошибки), то с ним может измениться яркость, и текст на этом фоне станет нечитаемым.</li>
</ol>
<figure>
    <img src="https://web-standards.ru/articles/oklch-in-css-why-quit-rgb-hsl/images/buttons-hsl-vs-oklch.png" width="1088" height="690" loading="lazy" alt="Две колонки с двумя кнопками в каждой. В первой колонке пространство HSL, во второй OKLCH. Первый ряд кнопок выглядит одинаково, второй ряд кнопок — нет. Кнопка с HSL-цветом фона неконтрастная, кнопка с OKLCH-цветом контрастная.">
    <figcaption>Изменение оттенка в пространстве HSL может привести к проблемам доступности из-за непредсказуемого изменения контраста.</figcaption>
</figure>
<p>HSL плох для работы с цветом. В сообществе многие <a href="https://wildbit.com/blog/accessible-palette-stop-using-hsl-for-color-systems">просят избегать</a> HSL при создании палитр. Кроме того, подобно RGB или hex, формат HSL не может быть использован для определения P3-цветов.</p>
<p>OKLCH не искажает цветовое пространство; этот формат показывает нам реальную физику цвета. Это помогает добиться предсказуемого контраста при изменениях цвета.</p>
<p>С другой стороны, некоторые комбинации значений для OKLCH порождают цвета, которые обычные экраны отобразить не способны. Ряд из них можно увидеть только на P3-мониторах. Но это не критическая проблема: браузеры будут искать ближайший поддерживаемый цвет.</p>
<h3>OKLCH против Oklab; LCH против Lab</h3>
<p>В CSS есть две функции, связанные с Oklab: <code>oklab()</code> и <code>oklch()</code>. Аналогичные есть и для CIE LAB: <code>lab()</code> и <code>lch()</code>. Так в чём же разница?</p>
<p>Есть разные способы задать точку в пространстве. Oklab и LAB используют декартовые координаты с осями <em>a</em> и <em>b</em> <em>(a</em> — от красного до зелёного, <em>b</em> — от голубого до жёлтого). OKLCH и LCH используют полярные координаты, где есть угол оттенка и расстояние для насыщенности.</p>
<figure>
    <img src="https://web-standards.ru/articles/oklch-in-css-why-quit-rgb-hsl/images/oklab-vs-oklch.png" width="1088" height="527" loading="lazy" alt="Два круга. Первый иллюстрирует декартовы координаты a, b в Oklab и прямой угол между этими осями. Второй отображает полярные координаты в OKLCH в виде острого угла поворота оттенка. Угол представлен двумя лучами, один из которых представляет собой насыщенность.">
    <figcaption>Декартовы координаты для Oklab и полярные координаты для OKLCH.</figcaption>
</figure>
<p>OKLCH, и LCH удобнее для работы с цветом, так как люди думают о цвете через насыщенность и оттенок, а не количество красного и зелёного.</p>
<h3>OKLCH против LCH</h3>
<p>Формат <a href="https://lea.verou.me/2020/04/lch-colors-in-css-what-why-and-how/">LCH</a> построен на пространстве CIE LAB. Он решает все проблемы, которые существуют в HSL и RGB. Также он позволяет работать с P3-цветами, и в большинстве случаев изменение цвета даёт предсказуемый результат.</p>
<p>Однако формат LCH имеет одну неприятную проблему: неожиданный сдвиг оттенка при изменении насыщенности и яркости для голубого цвета (оттенок между 270 и 330 градусами).</p>
<figure>
    <img src="https://web-standards.ru/articles/oklch-in-css-why-quit-rgb-hsl/images/lch-vs-oklch.png" width="1088" height="430" loading="lazy" alt="Два треугольника со срезами пространств: LCH слева и OKLCH справа.">
    <figcaption>Срезы пространств LCH и OKLCH, где яркость и насыщенность изменяются, а оттенок — одинаковый. Срез LCH голубой с одной стороны и фиолетовый с другой. Оттенок в OKLCH остаётся постоянным, как и ожидается.</figcaption>
</figure>
<p>Небольшой пример из реальной жизни:</p>
<pre><code tabindex="0" class="language-css">.temperature.is-very-very-cold {
    /* Выглядит голубым */
    background: lch(35% 110 300);
}

.temperature.is-very-cold {
    /* Мы изменили только яркость,
    но голубой стал фиолетовым */
    background: lch(35% 75 300);
}

.temperature.is-cold {
    /* Глубокий фиолетовый */
    background: lch(35% 40 300);
}
</code></pre>
<div class="colors">
    <div class="colors__swatch" style="
        background-color: rgb(65 46 241)
    "></div>
    <div class="colors__swatch" style="
        background-color: rgb(86 61 189)
    "></div>
    <div class="colors__swatch" style="
        background-color: rgb(90 72 138)
    "></div>
</div>
<pre><code tabindex="0" class="language-css">.temperature.is-very-very-cold {
    /* Выглядит голубым */
    background: oklch(48% 0.27 274);
}

.temperature.is-very-cold {
    /* Всё ещё голубой */
    background: oklch(48% 0.185 274);
}

.temperature.is-cold {
    /* Всё ещё голубой */
    background: oklch(48% 0.1 274);
}
</code></pre>
<div class="colors">
    <div class="colors__swatch" style="
        background-color: rgb(64 43 241)
    "></div>
    <div class="colors__swatch" style="
        background-color: rgb(66 75 195)
    "></div>
    <div class="colors__swatch" style="
        background-color: rgb(76 88 150)
    "></div>
</div>
<p>Пространства Oklab и OKLCH <a href="https://bottosson.github.io/posts/oklab/">были созданы</a> для решения этой проблемы со сдвигом оттенка. Но OKLCH — не просто исправление. В нём появляется <a href="https://www.w3.org/TR/css-color-4/#ok-lab">много полезных возможностей</a>, связанных с математическими преобразованиями. Например, упрощается <a href="https://bottosson.github.io/posts/gamutclipping/">коррекция охвата</a> (gamut correction), поэтому CSSWG <a href="https://www.w3.org/TR/css-color-4/#css-gamut-mapping">рекомендует</a> для этого OKLCH.</p>
<h2>Принципы работы OKLCH</h2>
<h3>Создание и развитие OKLCH</h3>
<p><a href="https://twitter.com/bjornornorn">Бьёрн Оттоссон</a> создал пространства Oklab и OKLCH в 2020 году. Главная задача была в исправлении проблемы CIE LAB и LCH. Бьёрн написал <a href="https://bottosson.github.io/posts/oklab/">отличную статью</a> с описанием деталей реализации. Также в ней он рассказал о причинах, побудивших его разработать эти пространства.</p>
<p>Стоит отметить, что Oklab появилось гораздо позже других пространств. Изначально это было его главным слабым местом. Но всего два года спустя стало понятно, что сообщество отлично приняло Oklab:</p>
<ul>
<li>Оно было добавлено в <a href="https://www.w3.org/TR/css-color-4/#ok-lab">спецификацию CSS</a>;</li>
<li>Chrome, Safari и Firefox поддерживают <code>oklch()</code> и <code>oklab()</code>;</li>
<li>В Photoshop <a href="https://helpx.adobe.com/photoshop/using/gradient-interpolation.html">добавили Oklab</a> для градиентов;</li>
<li>OKlab стали использовать в <a href="https://huetone.ardov.me/">генераторах палитр</a> для улучшения доступности.</li>
</ul>
<p>Неважно, что OKLCH молодой. Всё равно грядущие изменения в CSS Colors 4 и 5 потребуют сильных перемен в экосистеме разработки. Мы верим, что раз мы начинаем сначала, то лучше взять самое современное и эффективное решение.</p>
<h3>Оси</h3>
<p>Цвета в OKLCH записываются четырьмя числами. В CSS это выглядит как <code>oklch(L C H)</code> или <code>oklch(L C H / a)</code>.</p>
<figure>
    <img src="https://web-standards.ru/articles/oklch-in-css-why-quit-rgb-hsl/images/oklch-axes.png" width="1088" height="238" loading="lazy" alt="Четыре полоски, обозначающие оси OKLCH. Верхняя слева показывает яркость: начинается как тёмно-фиолетовый, становится светло-фиолетовым при движении вправо, и в конце становится неотличимым от белого. Верхняя справа показывает насыщенность: начинается как серо-фиолетовый и переходит в светло-фиолетовый, начиная с трети столбца, и в итоге едва отличается от белого цвета. Нижняя слева показывает альфу: переход альфа-канала в виде оттенков фиолетового, начиная с едва заметных. Нижняя справа показывает оттенок: вариации ярких цветов, которые сменяются друг другом.">
    <figcaption>Оси OKLCH.</figcaption>
</figure>
<p>Объясним значение каждого компонента более детально:</p>
<ul>
<li>L — воспринимаемая яркость. Варьируется от 0% (чёрный) до 100% (белый). Указывать % необходимо даже для нулевых значений;</li>
<li>C — насыщенность, интенсивность цвета. Варьируется от 0 (серый) до бесконечности. На практике у насыщенности всё же есть максимальное значение, но оно зависит от поддерживаемого цветового охвата (например, для P3 она чуть больше, чем для sRGB). Каждый оттенок имеет разную максимальную насыщенность. Но и в P3, и в sRGB значение всегда меньше 0.37.</li>
<li>H — оттенок, угол поворота от 0 до 360. От 0 — красный, 100 — жёлтый, 150 — зелёный, 200 — голубой, 250 — синий, 300 — фиолетовый и затем вновь идут значения красного (можно использовать поговорку «каждый охотник желает знать, где сидит фазан», где каждое слово будет добавлять ещё 50). Так как это угол, то <a href="https://oklch.com/#70,0.1,0,100">значение 0</a> и <a href="https://oklch.com/#70,0.1,360,100">значение 360</a> означают одинаковый оттенок. H можно записать как с единицей измерения <code>60deg</code>, так и без неё <code>60</code>.</li>
<li>a — непрозрачность (0–1 или 0%–100%).</li>
</ul>
<p>Обратите внимание, что размеры осей неодинаковы. 1% для яркости — тот же 1%, но для насыщенности это 0.004, а для оттенка — 3.6.</p>
<p>Вот некоторые примеры цветов OKLCH:</p>
<pre><code tabindex="0" class="language-css">.bw {
    /* Чёрный */
    color: oklch(0% 0 0);
    /* Белый */
    color: oklch(100% 0 0);
    /* Тоже белый, любой оттенок
    с 100% L будет белым */
    color: oklch(100% 0.2 100);
    /* Серый */
    color: oklch(50% 0 0);
}
</code></pre>
<div class="colors">
    <div class="colors__swatch" style="
        background-color: rgb(0 0 0)
    "></div>
    <div class="colors__swatch" style="
        background-color: rgb(255 255 255)
    "></div>
    <div class="colors__swatch" style="
        background-color: rgb(255 255 255)
    "></div>
    <div class="colors__swatch" style="
        background-color: rgb(99 99 99)
    "></div>
</div>
<pre><code tabindex="0" class="language-css">.colors {
    /* Жёлтый */
    color: oklch(80% 0.12 100);
    /* Тёмно-жёлтый */
    color: oklch(60% 0.12 100);
    /* Серо-жёлтый */
    color: oklch(80% 0.05 100);
    /* Голубой, с той же
    воспринимаемой яркостью */
    color: oklch(80% 0.12 225);
}
</code></pre>
<div class="colors">
    <div class="colors__swatch" style="
        background-color: rgb(208 191 94)
    "></div>
    <div class="colors__swatch" style="
        background-color: rgb(145 129 18)
    "></div>
    <div class="colors__swatch" style="
        background-color: rgb(197 191 154)
    "></div>
    <div class="colors__swatch" style="
        background-color: rgb(88 206 248)
    "></div>
</div>
<pre><code tabindex="0" class="language-css">.opacity {
    /* Прозрачный жёлтый */
    color: oklch(80% 0.12 100 / 50%);
}
</code></pre>
<div class="colors">
    <div class="colors__swatch" style="
        background-color: rgba(208 191 94 / 50%)
    "></div>
</div>
<p>Стоит помнить, что некоторые компоненты могут содержать <a href="https://www.w3.org/TR/css-color-4/#valdef-color-none"><code>none</code></a> в значениях. Например, у белого цвета нет оттенков, и некоторые конвертеры поставят в оттенке <code>none</code>. Браузеры будут трактовать <code>none</code> как <code>0</code>.</p>
<pre><code tabindex="0" class="language-css">.white {
    /* так можно записывать */
    color: oklch(100% 0 none);
}
</code></pre>
<div class="colors">
    <div class="colors__swatch" style="
        background-color: rgb(255 255 255)
    "></div>
</div>
<h3>Производные цвета</h3>
<p>В <a href="https://www.w3.org/TR/css-color-5/">CSS Colors 5</a> появятся производные цвета, встроенный механизм изменения цветов. Это раскрывает один из наиболее весомых плюсов OKLCH: изменение цвета будет давать предсказуемый результат.</p>
<p>Синтаксис выглядит следующим образом:</p>
<pre><code tabindex="0" class="language-css">:root {
    --origin: #ff000;
}

.foo {
    color: oklch(from var(--origin) l c h);
}
</code></pre>
<div class="colors">
    <div class="colors__swatch" style="
        background-color: rgb(0 255 0)
    "></div>
    <div class="colors__swatch" style="
        background-color: rgb(0 255 0)
    "></div>
</div>
<p>Начальный цвет (<code>var(--origin)</code> в примере выше) может являться:</p>
<ul>
<li>Цветом в любом формате: <code>#ff0000</code>, <code>rgb(255, 0, 0)</code>, или <code>oklch(62.8% 0.25 30)</code>.</li>
<li>Кастомным свойством с цветом в любом формате.</li>
</ul>
<p>Любой компонент (<code>l</code>, <code>c</code>, <code>h</code>) после <code>from X</code> может быть:</p>
<ul>
<li>Буквой (<code>l</code>, <code>c</code>, <code>h</code>), которая укажет сохранить компонент таким, каким он был для начального цвета;</li>
<li>Выражением <code>calc()</code>. Вы можете использовать буквы (<code>l</code>, <code>c</code>, <code>h</code>) вместо числа, чтобы сослаться на значение в начальном цвете;</li>
<li>Новым значением, которое заменит компонент.</li>
</ul>
<p>Это может звучать сложно, но примеры ниже должны внести ясность:</p>
<pre><code tabindex="0" class="language-css">:root {
    --error: oklch(60% 0.16 30);
}

.message.is-error {
    /* Тот же цвет, но с другой прозрачностью */
    background: oklch(
        from var(--origin) l c h / 60%
    );
    /* На 10% темнее */
    border-color: oklch(
        from var(--error) calc(l - 10%) c h
    );
}

.message.is-success {
    /* Другой оттенок (зелёный)
    с той же яркостью и насыщенностью */
    background: oklch(
        from var(--error) l c 140
    );
}
</code></pre>
<div class="colors">
    <div class="colors__swatch" style="
        background-color: rgb(206 83 66)
    "></div>
    <div class="colors__swatch" style="
        background-color: rgba(206 83 66 / 60%)
    "></div>
    <div class="colors__swatch" style="
        background-color: rgb(172 50 37)
    "></div>
    <div class="colors__swatch" style="
        background-color: rgb(68 150 48)
    "></div>
</div>
<p>У OKLCH предсказуемая яркость — поэтому нам легко сделать цвет интерфейса из произвольного цвета, который задаёт пользователь (и сохранить читаемость текста). Посмотрите, как выбранный цвет влияет на цвета чекбоксов в <a href="https://oklch.com/#70,0.1,319,100">цветовом миксере</a> Злых марсиан:</p>
<pre><code tabindex="0" class="language-css">:root {
    /* Яркость и насыщенность
    заменены для хорошего контраста */
    --accent: oklch(
        from (--user-input) 87% 0.06 h
    );
}

body {
    background: var(--accent);
    color: black;
}
</code></pre>
<p>В примере выше, нам не нужно указывать цвет текста с <code>color-contrast()</code>, потому что OKLCH имеет предсказуемую яркость. Любой фон с L меньше или равно 87% имеет хороший контраст с чёрным текстом.</p>
<h3>Коррекция охвата</h3>
<p>У OKLCH есть и другая замечательная особенность: независимость от устройств. OKLCH создан не только для обычных мониторов с набором цветов sRGB.</p>
<p>Мы можем указать абсолютно любой цвет с помощью OKLCH: sRGB, P3, Rec2020 и далее. Ряд комбинаций компонентов поддерживается только на P3-мониторах; а для некоторых сочетаний ещё предстоит изобрести экраны, которые смогут их отображать.</p>
<p>Не пугайтесь выхода за пределы возможностей экрана — браузеры найдут максимально похожий цвет. Процесс поиска наиболее похожего цвета в другом охвате называется коррекцией охвата.</p>
<p>По этой причине в <a href="https://oklch.com/#60,0.16,140,100">цветовом миксере OKLCH</a> можно увидеть бреши: каждый оттенок имеет разную максимальную насыщенность. Эта проблема связана не с кодированием цвета в OKLCH, а с пределами возможностей существующих мониторов и нашего зрения.</p>
<p>Например, при некоторой яркости только у синего цвета может быть самая большая насыщенность — в зелёном или красном оттенке не будет цвета с той же насыщенностью.</p>
<figure>
    <img src="https://web-standards.ru/articles/oklch-in-css-why-quit-rgb-hsl/images/oklch-l-44.png" width="1088" height="456" loading="lazy" alt="График с двумя осями: C по горизонтали и H по вертикали. На горизонтальной шкале графика оттенки цветов сменяются слева направо. Пиковые значения на вертикальной шкале показывают, что при изменении яркости разные оттенки обладают разной максимальной насыщенностью. Пик насыщенности для синего заметно выше, чем для других цветов.">
    <figcaption>На sRGB-мониторах для 44% яркости максимальная насыщенность синего сильнее, чем у остальных цветов.</figcaption>
</figure>
<p>Есть два способа коррекции охвата:</p>
<ul>
<li>Конвертировать цвет в RGB или P3 и отсечь значения более 100% или менее 0%: <code>rgb(150% -20% 30%)</code> → <code>rgb(100% 0 30%)</code>. Это наиболее быстрый способ, но у него наихудший результат — он может заметно изменить оттенок цвета;</li>
<li>Перевести цвет в формат OKLCH и уменьшить насыщенность и яркость. Это сохранит тот же оттенок, но немного замедлит отрисовку.</li>
</ul>
<p>Крис Лилли создал <a href="https://svgees.us/Color/ok-clip-lch-explorer.html">интересное сравнение</a> между разными методами коррекции охвата.</p>
<p>Спецификация CSS Colors 4 <a href="https://www.w3.org/TR/css-color-4/#css-gamut-mapping">требует</a> от браузеров применять OKLCH-метод для коррекции охвата. Однако прямо сейчас Chrome и Safari используют быстрый, но неточный метод отсечения.</p>
<p>Именно поэтому мы рекомендуем ручную коррекцию охвата — указывать как sRGB, так и P3-цвета:</p>
<pre><code tabindex="0" class="language-css">.martian {
    background: oklch(69.73% 0.155 112.79);
}

@media (color-gamut: p3) {
    .martian {
        background: oklch(69.73% 0.176 112.79);
    }
}
</code></pre>
<div class="colors">
    <div class="colors__swatch" style="
        background-color: rgb(159 166 1)
    "></div>
    <div class="colors__swatch" style="
        background-color: color(display-p3 0.6327 0.6542 0.0677)
    "></div>
</div>
<p>Превью этого цвета вы сможете увидеть только на P3-мониторе.</p>
<p>И здесь есть хорошая новость: <a href="https://github.com/fpetrakov/stylelint-gamut">stylelint-gamut</a> может автоматически определять все P3-цвета которые нужно обернуть медиавыражением.</p>
<h2>Внедрение OKLCH в проект</h2>
<h3>Шаг 1: Добавление полифила OKLCH в CSS</h3>
<p>К марту 2023, <code>oklch()</code> <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch">поддерживают</a> Chrome и Safari. Для стабильного результата в старых версиях у нас есть полифилы для статичных значений (без кастомных свойств).</p>
<p>Есть два полифила, поддерживающих <code>oklch()</code>:</p>
<ul>
<li>Очень популярный <a href="https://preset-env.cssdb.org/">postcss-preset-env</a>;</li>
<li>И крайне быстрый <a href="https://lightningcss.dev/">Lightning CSS</a>, написанный на Rust.</li>
</ul>
<p>Скорее всего, в вашем проекте уже есть <em>postcss-preset-env.</em> Например, он есть в каждом проекте, созданном с помощью Create React App.</p>
<ol>
<li>Поищите <em>postcss-preset-env</em> в вашем лок-файле <em>(package-lock.json,</em> <em>yarn.lock</em> или <em>pnpm-lock.yaml).</em> Убедитесь, что он использует версию 7.x или выше;</li>
<li>Добавьте <code>oklch(100% 0 0)</code> в ваш CSS и посмотрите, скомпилируется ли его сборка в <code>rgb()</code>.</li>
</ol>
<p>Если у вас нет <em>postcss-preset-env,</em> но есть сборщик фронтенда (например, Webpack):</p>
<ol>
<li>Установите <em>postcss-preset-env</em> с помощью пакетного менеджера. Для npm запустите следующую команду:<pre><code tabindex="0" class="language-sh">npm install postcss-preset-env postcss
</code></pre>
</li>
<li>Проверьте <a href="https://github.com/postcss/postcss">документацию PostCSS</a>, чтобы узнать, как добавить поддержку PostCSS в ваш инструмент сборки. Например Webpack требует <a href="https://github.com/webpack-contrib/postcss-loader">postcss-loader</a>, а в <a href="https://vitejs.dev/">Vite</a> уже есть встроенная поддержка.</li>
<li>Если у вас уже есть интеграция с PostCSS, найдите файл с его конфигурацией. Многие проекты уже используют PostCSS (например, Автопрефиксер). В корневой папке проекта найдите <em>postcss.config.js</em> <em>(.postcssrc.json)</em>, или раздел <code>&quot;postcss&quot;</code> в <em>package.json</em> или конфигурации сборщика.</li>
<li>Если вы смогли найти файл конфигурации PostCSS, добавьте <em>postcss-preset-env</em> в плагины:<pre><code tabindex="0" class="language-diff">{
    &quot;plugins&quot;: [
+       &quot;postcss-preset-env&quot;,
        &quot;autoprefixer&quot;
    ]
}
</code></pre>
</li>
<li>Если вы не смогли найти файл конфигурации, создайте файл <em>.postcssrc.json</em> в корневой папке проекта:<pre><code tabindex="0" class="language-json">{
    &quot;plugins&quot;: [
        &quot;postcss-preset-env&quot;
    ]
}
</code></pre>
</li>
</ol>
<p>Если у вас ещё нет инструмента сборки, мы рекомендуем использовать Vite или компилировать CSS с помощью CLI <a href="https://github.com/parcel-bundler/lightningcss#from-the-cli">lightningcss</a>.</p>
<p>Добавьте в файл <code>.test { background: oklch(100% 0 0) }</code>, скомпилируйте стили. Если в итоговом CSS есть <code>.test{background:#fff}</code>, то всё работает.</p>
<h3>Шаг 2: Перевод цветов приложения на OKLCH</h3>
<p>После установки OKLCH-полифила вы можете заменить все цвета в форматах hex, <code>rgb()</code> или <code>hsl()</code> на <code>oklch()</code>. Они будут работать в любом браузере.</p>
<p>Найдите любой цвет в вашем CSS-коде и переведите его в <code>oklch()</code>, используя <a href="https://oklch.com/#70,0.1,191,100">цветовой миксер OKLCH</a>.</p>
<pre><code tabindex="0" class="language-diff">.header {
-   background: #f3f7fa;
+   background: oklch(97.4% 0.006 240);
}
</code></pre>
<p>Вы также можете использовать <a href="https://github.com/fpetrakov/convert-to-oklch">convert-to-oklch</a>, чтобы автоматически перевести все цвета:</p>
<pre><code tabindex="0" class="language-sh">npx convert-to-oklch ./src/**/*.css
</code></pre>
<p>Помните, что эти полифилы поддерживают только CSS, и не будут работать в HTML- или JS-файлах.</p>
<h3>Дополнительно: добавление палитры</h3>
<p>Раз уж мы рефакторим CSS, то давайте воспользуемся моментом и заодно внедрим общую палитру.</p>
<p>Что такое общая палитра?</p>
<ul>
<li>Все цвета указываются как кастомные CSS-свойства;</li>
<li>Компоненты фреймворков используют эти цвета в виде <code>var(--error)</code>;</li>
<li>Дизайнерам стоит стремиться к переиспользованию цветов;</li>
<li>Тёмная тема или тема с высоким контрастом создаются через переопределение кастомных свойств в палитре через <code>@media</code>.</li>
</ul>
<p>Вот <a href="https://github.com/evilmartians/oklch-picker/blob/main/view/base/colors.css">небольшой пример</a> такого подхода:</p>
<pre><code tabindex="0" class="language-css">:root {
    --surface-0: oklch(96% 0.005 300);
    --surface-1: oklch(100% 0 0);
    --surface-2: oklch(99% 0 0 / 85%);
    --text-primary: oklch(0% 0 0);
    --text-secondary: oklch(54% 0 0);
    --accent: oklch(57% 0.18 286);
    --danger: oklch(59% 0.23 7);
}

@media (prefers-color-scheme: dark) {
    :root {
        --surface-0: oklch(0% 0 0);
        --surface-1: oklch(29% 0.01 300);
        --surface-2: oklch(29% 0 0 / 85%);
        --text-primary: oklch(100% 0 0);
    }
}
</code></pre>
<p>После создания единой палитры, перейти к использованию <code>oklch()</code> станет проще.</p>
<h3>Шаг 3: Проверка OKLCH с помощью Stylelint</h3>
<p><a href="https://stylelint.io/">Stylelint</a> — статический анализатор стилей, который будет полезен для поиска распространённых ошибок и внедрения хороших практик. Он работает как ESLint, но для CSS, Sass, или CSS-in-JS.</p>
<p>Stylelint будет очень полезен при переходе на <code>oklch()</code>, вы сможете:</p>
<ul>
<li>Напомнить коллегам, что цвета в форматах hex, <code>rgb()</code> и <code>hsl()</code> больше не используются и что следует хранить все цвета в <code>oklch()</code> для единообразия;
— Проверить, что все P3-цвета находятся внутри <code>@media (color-gamut: p3)</code>. Это поможет избежать автоматической коррекции охвата в браузере (Chrome и Safari делают её неправильно).</li>
</ul>
<p>Давайте установим Stylelint и плагин <a href="https://www.npmjs.com/package/stylelint-gamut">stylelint-gamut</a>. Для npm:</p>
<pre><code tabindex="0" class="language-sh">npm install stylelint stylelint-gamut
</code></pre>
<p>Затем создадим конфигурацию <em>.stylelintrc</em> следующим образом:</p>
<pre><code tabindex="0" class="language-json">{
    &quot;plugins&quot;: [
        &quot;stylelint-gamut&quot;
    ],
    &quot;rules&quot;: {
        &quot;gamut/color-no-out-gamut-range&quot;: true,
        &quot;function-disallowed-list&quot;: [&quot;rgba&quot;, &quot;hsla&quot;, &quot;rgb&quot;, &quot;hsl&quot;],
        &quot;color-function-notation&quot;: &quot;modern&quot;,
        &quot;color-no-hex&quot;: true
    }
}
</code></pre>
<p>Добавим вызов Stylelint в команду <code>npm test</code>, чтобы запустить его в CI. Изменим следующую строку в <em>package.json:</em></p>
<pre><code tabindex="0" class="language-diff">&quot;scripts&quot;: {
-   &quot;test&quot;: &quot;eslint .&quot;
+   &quot;test&quot;: &quot;eslint . &amp;&amp; stylelint **/*.css&quot;
}
</code></pre>
<p>Запустим <code>npm test</code>, чтобы увидеть, какие цвета должны быть переведены в <code>oklch()</code>.</p>
<p>Мы также рекомендуем добавить <a href="https://github.com/stylelint/stylelint-config-recommended">stylelint-config-recommended</a> в <em>.stylelintrc.</em> Этот конфиг убедится, что вы используете самые популярные лучшие практики в вашем CSS.</p>
<h3>Дополнительно: P3-цвета</h3>
<p>Просто переведя цвета в <code>oklch()</code>, вы улучшите читаемость и поддерживаемость кода, но не добавите что-то заметное для пользователей. А вот если ещё и внедрить в дизайн красочные и глубокие P3-цвета, то и пользователи смогут оценить рефакторинг.</p>
<p><em>P3-цвета очень полезны при создании палитр. Но прямо сейчас мы не можем их использовать: пока небольшая часть устройств поддерживает P3.</em></p>
<p>Вот, как можно добавить P3-цвета в проект:</p>
<ol>
<li>Выберите любой насыщенный цвет в вашем CSS-коде;</li>
<li>Скопируйте его и вставьте в <a href="https://oklch.com/">цветовой миксер OKLCH</a>;</li>
<li>Измените значения <em>Chroma</em> и <em>Lightness</em> так, чтобы цвет оказался в области P3-цветов. Легче всего это сделать, смотря на график <em>Lightness:</em> выберите любой цвет выше тонкой белой линии на этом графике;</li>
<li>Скопируйте результат и оберните его в медиавыражение с <code>color-gamut: p3</code>.</li>
</ol>
<pre><code tabindex="0" class="language-css">:root {
    --accent: oklch(70% 0.2 145);
}

@media (color-gamut: p3) {
    :root {
        --accent: oklch(70% 0.29 145);
    }
}
</code></pre>
<div class="colors">
    <div class="colors__swatch" style="
        background-color: rgb(48, 189, 68)
    "></div>
    <div class="colors__swatch" style="
        background-color: color(display-p3 0.1530 0.7673 0.0793)
    "></div>
</div>
<h3>Дополнительно: OKLCH для SVG</h3>
<p>OKLCH можно использовать не только в CSS, но и в SVG или HTML. Это полезно при добавлении ярких красок в иконки.</p>
<p>Обратите внимание: полифилов для SVG нет. По этой причине мы рекомендуем использовать <code>oklch()</code> только для P3-цветов.</p>
<pre><code tabindex="0" class="language-xml">&lt;svg viewBox=&quot;0 0 120 120&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
    &lt;style&gt;
        @media (color-gamut: p3) {
            rect {
                fill: oklch(55% 0.23 146);
            }
        }
    &lt;/style&gt;
    &lt;rect
        x=&quot;10&quot; y=&quot;10&quot;
        width=&quot;100&quot; height=&quot;100&quot;
        fill=&quot;#048c2c&quot;
    /&gt;
&lt;/svg&gt;
</code></pre>
<div class="colors">
    <div class="colors__swatch" style="
        background-color: color(display-p3 0.0505 0.5557 0.0653)
    "></div>
    <div class="colors__swatch" style="
        background-color: #048c2c
    "></div>
</div>
<h3>Дополнительно: OKLCH и Oklab в градиентах</h3>
<p>Градиент — путь между двумя или более точками цветового пространства. В разных пространствах градиент для одних и тех же цветов может получиться совершенно разный.</p>
<figure>
    <img src="https://web-standards.ru/articles/oklch-in-css-why-quit-rgb-hsl/images/color-space-gradients.png" width="1088" height="337" loading="lazy" alt="Сравнение градиентов в пространствах sRGB, Oklab и OKLCH. Начальный и конечный цвета в каждом примере одинаковые, но для каждого цветового пространства градиент немного различается.">
    <figcaption>Так выглядят градиенты в разных цветовых пространствах.</figcaption>
</figure>
<p>Нет единого решения, какое пространство лучше для работы с градиентами — это зависит от конкретной задачи. Но в цветовом пространстве Oklab (родственнике OKLCH, живущем в декартовых координатах) обычно получается хороший результат:</p>
<ul>
<li>У него нет сероватой области в центре, как в sRGB (пространство, используемое по умолчанию);</li>
<li>У него нет сдвига от голубого к фиолетовому, в отличие от Lab;</li>
</ul>
<p><a href="https://drafts.csswg.org/css-images-4/#linear-gradients">В спецификации CSS Image 4</a> есть специальное выражение, изменяющее цветовое пространство для градиентов:</p>
<pre><code tabindex="0" class="language-css">.oklch {
    background: linear-gradient(
        in oklab, blue, green
    );
}
</code></pre>
<p>Код выше будет работать только в Chrome и Safari Technology Preview. Вы можете использовать <a href="https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-gradients-interpolation-method">полифил PostCSS</a>, чтобы применять Oklab-градиенты уже сейчас.</p>
<p><em>Градиенты получаются ещё более красивыми с <a href="https://larsenwork.com/easing-gradients/">easing-gradients</a>.</em></p>
<h3>Дополнительно: Изменение цветов с помощью OKLCH</h3>
<p>Уже очень скоро мы сможем увидеть истинную силу OKLCH: с <a href="https://www.w3.org/TR/css-color-5/#relative-colors">CSS Colors 5</a> браузеры начнут поддерживать производные цвета в CSS.</p>
<p>OKLCH крайне хорош для работы с цветом. В отличие от HSL, его яркость предсказуема — в нём нет сдвига оттенка при изменении насыщенности, чем не может похвастаться LCH.</p>
<p>К сожалению, здесь нам полифилы не помогут — нужно ждать поддержки браузеров. Но если мы познакомимся с OKLCH сейчас, то уже будем готовы, когда в браузерах появится поддержка производных цветов.</p>
<p>Следующий пример иллюстрирует, как можно при наведении на кнопку сделать её фон на 10% темнее:</p>
<pre><code tabindex="0" class="language-css">.button {
    background: var(--accent);
}

.button:hover {
    background: oklch(
        from var(--accent) calc(l - 10%) c h
    );
}
</code></pre>
<div class="colors">
    <div class="colors__swatch" style="
        background-color: rgb(165 145 213)
    "></div>
    <div class="colors__swatch" style="
        background-color: rgb(135 115 181)
    "></div>
</div>
<p>С кастомными свойствами CSS достаточно один раз определить логику <code>:hover</code>. В дальнейшем мы можем создавать разные варианты стилей, просто изменяя исходный цвет.</p>
<pre><code tabindex="0" class="language-css">.button {
    background: var(--button-color);
}

.button:hover {
    /* Один :hover для обычного,
    вторичного и ошибочного состояния */
    background: oklch(from var(--button-color) calc(l + 10%) c h);
}

.button {
    --button-color: var(--accent);
}

.button.is-secondary {
    --button-color: var(--dimmed);
}

.button.is-error {
    --button-color: var(--error);
}
</code></pre>
<div class="colors">
    <div class="colors__swatch" style="
        background-color: rgb(117 161 220)
    "></div>
    <div class="colors__swatch" style="
        background-color: rgb(117 161 220 / 50%)
    "></div>
    <div class="colors__swatch" style="
        background-color: rgb(214 133 131)
    "></div>
</div>
<p>Поскольку у OKLCH предсказуемый контраст, то мы можем взять случайный цвет от пользователя и сделать из него тему сайта с отличной доступностью:</p>
<pre><code tabindex="0" class="language-css">.header {
    /* JS установит --user-avatar-dominant */
    background: oklch(
        from var(--user-avatar-dominant) 80% 0.17 h
    );
    color: black;
}
</code></pre>
<p>В примере выше, OKLCH позволяет убедиться, что чёрный текст всегда будет читаемым на любом оттенке, так как мы установили L равным 80%. В будущем, с OKLCH и производными цветами мы сможем генерировать всю палитру дизайн-системы прямо в CSS.</p>
<h3>Дополнительно: OKLCH в JS</h3>
<p>Нам не нужно ждать CSS Colors 5, чтобы использовать OKLCH для изменений цвета. <a href="https://colorjs.io/">Color.js</a> и <a href="https://culorijs.org/">culori</a> позволяют менять цвета в JS со всеми преимуществами OKLCH. Как это сделать в culori, можно посмотреть в <a href="https://github.com/evilmartians/oklch-picker">исходном коде цветового миксера OKLCH</a>.</p>
<p>Пример ниже генерирует тему из пользовательского цвета с помощью Color.js:</p>
<pre><code tabindex="0" class="language-js">import Color from 'colorjs.io'

// Разбираем любой CSS цвет
let accent = new Color(userAvatarDominant)

// Устанавливаем яркость и насыщенность
accent.oklch.l = 0.8
accent.oklch.c = 0.17

// Применяем коррекцию охвата для sRGB,
// если мы вышли за пределы sRGB
if (!accent.inGamut('srgb')) {
    accent = accent.toGamut({ space: 'srgb' })
}

// Делаем цвет на 10% светлее
let hover = accent.clone()
hover.oklch.l += 0.1

document.body.style.setProperty(
    '--accent',
    accent.to('srgb').toString()
)
document.body.style.setProperty(
    '--accent-hover',
    hover.to('srgb').toString()
)
</code></pre>
<p>Вы можете использовать эти библиотеки, чтобы создавать целые палитры в цветовом пространстве OKLCH. Они будут обладать предсказуемым яркостью и хорошей доступностью. Например, <a href="https://huetone.ardov.me/">Hueone</a>, генератор доступных палитр, по умолчанию использует Oklab.</p>
<h2>Подведение итогов: почему мы перешли на OKLCH?</h2>
<p>Мы, Злые марсиане, уже используем OKLCH в наших проектах. Ответим на главный вопрос: какие же преимущества мы получили, перейдя на OKLCH?</p>
<h3>1. Лёгкое чтение цветов</h3>
<p>Благодаря OKLCH мы без труда понимаем, что за цвет написан в CSS, просто посмотрев на числа. И уже в процессе чтения кода мы можем распознать некоторые проблемы плохого контраста. Для этого нужно всего лишь сравнить яркость в CSS-правилах:</p>
<pre><code tabindex="0" class="language-css">.text {
    /* Ошибка: разницы в 20% яркости
    мало для хорошего контраста и доступности */
    background: oklch(80% 0.02 300);
    color: oklch(100% 0 0);
}

.error {
    /* Ошибка: цвета имеют немного разный оттенок */
    background: oklch(90% 0.04 30);
    color: oklch(50% 0.19 27);
}
</code></pre>
<div class="colors">
    <div class="colors__swatch" style="
        background-color: rgb(191 187 201)
    "></div>
    <div class="colors__swatch" style="
        background-color: rgb(255 255 255)
    "></div>
    <div class="colors__swatch" style="
        background-color: rgb(248 213 207)
    "></div>
    <div class="colors__swatch" style="
        background-color: rgb(183 25 28)
    "></div>
</div>
<h3>2. Лёгкая работа с цветами</h3>
<p>Мы изменяем цвета прямо в коде и получаем предсказуемые результаты:</p>
<pre><code tabindex="0" class="language-css">.button {
    background: oklch(50% 0.2 260);
}

.button:hover {
    background: oklch(60% 0.2 260);
}
</code></pre>
<div class="colors">
    <div class="colors__swatch" style="
        background-color: rgb(5 89 210)
    "></div>
    <div class="colors__swatch" style="
        background-color: rgb(46 121 245)
    "></div>
</div>
<h3>3. Использование как sRGB, так и новых P3-цветов</h3>
<p>Мы используем одинаковые CSS-функции как для sRGB, так и для P3-цветов:</p>
<pre><code tabindex="0" class="language-css">.buy-button {
    background: oklch(62% 0.19 145);
}

@media (color-gamut: p3) {
    .buy-button {
        background: oklch(62% 0.26 145);
    }
}
</code></pre>
<div class="colors">
    <div class="colors__swatch" style="
        background-color: rgb(17 162 47)
    "></div>
    <div class="colors__swatch" style="
        background-color: color(display-p3 0.1097 0.6527 0.0378)
    "></div>
</div>
<h3>4. Взаимопонимание между разработчиками и дизайнерами</h3>
<p>Математика OKLCH ближе к тому, как работают наши глаза. Поэтому, работая с <code>oklch()</code>, мы глубже понимаем, как работает цвет.</p>
<p>А понимание работы цвета позволяет разработчикам и дизайнерам стать ближе друг к другу. Современные <a href="https://huetone.ardov.me/">генераторы палитр</a> уже используют Oklab или Lab для хорошей доступности — использование <code>oklch()</code>, как в инструментах дизайнеров, так и в коде, помогает лучше понимать друг друга.</p>
<h3>5. Готовность к будущему</h3>
<p>Перейдя на OKLCH сегодня, мы подготовили себя к завтрашнему дню, когда в CSS появятся производные цвета. OKLCH — лучшее пространство для работы с цветом, и мы советуем познакомиться с ним как можно скорее.</p>
<pre><code tabindex="0" class="language-css">.button:hover {
    background: oklch(
        from var(--button-color) calc(l + 10%) c h
    );
}
</code></pre>
<p>Попробуйте и вы добавить <code>oklch()</code> в свой проект. Мы уверены, что результат вам понравится. Если у вас возникнут вопросы, пишите нам: <a href="https://twitter.com/andrey_sitnik">@andrey_sitnik</a> и <a href="https://twitter.com/ninoid_">@ninoid_</a>.</p>

                    ]]></description><pubDate>Tue, 10 Jan 2023 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/oklch-in-css-why-quit-rgb-hsl/</guid></item><item><title>Аудит доступности: основы</title><link>https://web-standards.ru/articles/a11y-audit-basics/</link><description><![CDATA[
                        <p>Доступность становится одним из показателей качества сайтов и приложений. Но с чего начать её улучшение? Как узнать, есть ли с ней проблемы? Насколько они критичны и как их исправить? На эти вопросы ответит аудит.</p>
<p>Это хороший первый шаг в начале работы над доступностью или для её поддержания, если продукт постоянно развивается и изменяется.</p>
<p>Статья пригодится разработчикам, дизайнерам, продактам и всем, кто думает об улучшении доступности, но не знает, с чего начать. Из первой части узнаете, что такое аудит, каким он бывает и как выглядит в теории процесс оценки. Во второй познакомитесь с проведением самостоятельного аудита на практике.</p>
<h2>Оглавление</h2>
<ul>
<li><a href="https://web-standards.ru/articles/a11y-audit-basics/#section-2">Определение</a></li>
<li><a href="https://web-standards.ru/articles/a11y-audit-basics/#section-3">Зачем проводить</a></li>
<li><a href="https://web-standards.ru/articles/a11y-audit-basics/#section-4">Принципы и источники критериев</a></li>
<li><a href="https://web-standards.ru/articles/a11y-audit-basics/#section-5">Когда и как часто проводить</a></li>
<li><a href="https://web-standards.ru/articles/a11y-audit-basics/#section-6">Типы аудита</a></li>
<li><a href="https://web-standards.ru/articles/a11y-audit-basics/#section-16">Методологии</a></li>
<li><a href="https://web-standards.ru/articles/a11y-audit-basics/#section-23">Публичные заявления, VPAT и ACR</a></li>
<li><a href="https://web-standards.ru/articles/a11y-audit-basics/#section-26">Оценка доступности пользователями</a></li>
<li><a href="https://web-standards.ru/articles/a11y-audit-basics/#section-29">Что делать после аудита</a></li>
<li><a href="https://web-standards.ru/articles/a11y-audit-basics/#section-30">Выводы</a></li>
<li><a href="https://web-standards.ru/articles/a11y-audit-basics/#section-31">Полезные ссылки</a></li>
</ul>
<h2>Определение</h2>
<p><em>Аудит доступности</em> — это оценка продукта на соответствие требованиям доступности, которые описаны в разных стандартах и руководствах. Он оценивает пригодность интерфейса для максимально большого числа пользователей, в том числе для людей с особыми потребностями. В результате получается отчёт об уровне доступности с рекомендациями по исправлению проблем.</p>
<p>Он может показаться похожим на тестирование доступности, но между ними есть большая разница.</p>
<ul>
<li>Аудит состоит из серии ручных и автоматических тестов, а тесты могут проводить отдельно и поодиночке.</li>
<li>Тестами проверяют ограниченное количество элементов и страниц, а во время аудита интерфейс оценивается более масштабно.</li>
<li>Аудит проводит отдельная рабочая группа или эксперты, а тесты — любые члены команды.</li>
</ul>
<p>В статье используется термин «аудит», но в других местах можете встретить <em>«оценку» (evaluation или assessment),</em> это синонимы.</p>
<h2>Зачем проводить</h2>
<p>Компании проводят аудит по разным причинам.</p>
<ul>
<li>Для расширение аудитории и улучшения интерфейса. Доступным интерфейсом сможет пользоваться больше людей, а <a href="https://devblogs.microsoft.com/startups/how-does-accessibility-fit-into-an-mvp/">71% пользователей с особыми потребностями</a> не уйдёт с недоступного сайта сразу же. К тому же доступность влияет на положение в поисковой выдаче Google.</li>
<li>Как стартовую точку для внедрения доступности, если компания решила за это взяться.</li>
<li>Для того, чтобы принять решения об изменениях в интерфейсе, когда разрабатываются новые возможности.</li>
<li>Если интерфейс изменился — чтобы проверить его доступность. Актуально для больших проектов, в которых уже поддерживается определённый уровень доступности.</li>
<li>Чтобы проверить соответствие требованиям закона. Особенно актуально в США и странах Евросоюза. Там компании могут получить штраф или потратить деньги на судебные разбирательства из-за нарушения законов.</li>
<li>Для участия в госзакупках для компаний из США. Доступность — одно из требований для выигрыша тендера.</li>
</ul>
<h2>Принципы и источники критериев</h2>
<p>Аудит основывается на нескольких принципах.</p>
<ul>
<li>Содержит измеряемые критерии, а не оценочные суждения.</li>
<li>Определяет уровень доступности в целом, например, всего сайта.</li>
<li>Основывается на чёткой методологии.</li>
<li>Включает так называемые <em><a href="https://www.boia.org/wcag-2.1-a/aa-principles-and-checkpoints">контрольные точки доступности</a>.</em></li>
</ul>
<p>Контрольные точки доступности (Accessibility Checkpoints) — это проверка выполнения конкретных требований руководств или законов. Например, во <a href="https://www.w3.org/TR/WCAG21/">WCAG 2.1</a> (Web Content Accessibility Guidelines, Руководство по обеспечению доступности веб-контента) контрольная точка — это выполнен или нет критерий успешности.</p>
<p>Критерии для аудита берутся из международных и локальных руководств, стандартов и законов. Это могут быть:</p>
<ul>
<li><a href="https://www.w3.org/TR/WCAG21/">WCAG 2.1</a>, а скоро будет <a href="https://www.w3.org/TR/WCAG22/">WCAG 2.2</a>.</li>
<li><a href="https://www.access-board.gov/ict/">Section 508</a> (Section 508 of the Rehabilitation Act of 1973, Раздел 508 Федерального Закона о реабилитации 1973 года).</li>
<li><a href="https://www.ada.gov/cguide.htm">ADA</a> (Americans with Disabilities Act, Закон об американцах-инвалидах).</li>
<li><a href="https://www.etsi.org/deliver/etsi_en/301500_301599/301549/03.02.01_60/en_301549v030201p.pdf">EN 301 549</a> (Standard EN 301 549, Европейский стандарт EN 301 549).</li>
<li>Похожие законы и стандарты других стран.</li>
</ul>
<p>При этом WCAG и законы не описывают все барьеры, с которыми сталкиваются люди. Поэтому другими источниками критериев могут быть лучшие практики, публичные результаты пользовательских исследований и знания экспертов.</p>
<figure>
    <img src="https://web-standards.ru/articles/a11y-audit-basics/images/1.png" alt="Справа ветка с вишней и листьями. Слева утверждения о том, что такое WCAG.">
    <figcaption>WCAG — это: основа, а не ответ; ориентир, а не цель; ресурс, а не инструмент; непрерывный процесс. «<a href="https://www.ab11y.com/articles/a-little-book-of-accessibility/">A Little Book of Accessibility</a>», Гарет Форд Уильямс.</figcaption>
</figure>
<h2>Когда и как часто проводить</h2>
<p>Чем раньше проводите аудит, тем лучше. Так вы избежите лишних трат на разработку и дизайн, а ещё уменьшите количество барьеров для пользователей. Например, можно провести его на первых этапах разработки, в процессе редизайна или при переходе на новые технологии.</p>
<p>Размеры продукта напрямую влияют на то, как часто проводить аудит. Для небольшого продукта без больших изменений достаточно одного раза в год. Средние проекты с умеренным количеством новых функций можно проверять раз в полгода–год. Уровень доступности активно развивающихся больших продуктов постепенно снижается из-за постоянных изменений. В их случае лучше проводить аудит не только каждый год, но и ежеквартально. Годовой будет более полным и глубоким, а квартальные — небольшими и точечными.</p>
<h2>Типы аудита</h2>
<p>Пока нет универсальной классификации типов аудита. Разные компании даже могут называть их по-разному. В этом разделе мы собрали самые распространённые типы и сгруппировали их так, чтобы было проще в них разобраться и выбрать походящий.</p>
<p>Один аудит может относиться сразу к нескольким типам. Так что подбор нужного чем-то напоминает сборку конструктора. Например, он может быть одновременно самостоятельным, подробным, смешанным и оценивать сайт. Всё зависит от ваших целей и оцениваемого продукта.</p>
<h3>Кто проводит</h3>
<ul>
<li>Внешний аудит (External Expert Audit или Expert Audit). Его проводит приглашённый эксперт или компания, которая занимается аудитом. Он дороже, но более профессиональный, полный и показывает взгляд со стороны.</li>
<li>Самостоятельный аудит (Self-audit). Проводится собственными силами. Дешевле, но не всегда найдёт все проблемы. Этот тип подходит компаниям, где есть сотрудники с нужными навыками и знаниями о доступности.</li>
</ul>
<p>Для самостоятельного аудита создаётся отдельная рабочая группа внутри компании. Она состоит из инициатора аудита, который за ним следит, и нескольких аудиторов. Их количество зависит от размера продукта и выбранных параметров аудита. Максимально объективную оценку они смогут дать, если в этот момент активно не участвуют в разработке.</p>
<h3>По цели</h3>
<ul>
<li>Подробный аудит (Detailed Audit). Проводится на основе выбранного документа. Например, WCAG 2.1 или Section 508. Также подходит для проверки на соответствие специфическим требованиям. К примеру, для видеотрансляций, банкоматов или терминалов.</li>
<li>Аудит рисков (Risk Audit). Описывает и считает количество критичных ошибок, с которыми могут столкнуться пользователи. В отчёт попадёт информация о потенциальных рисках без рекомендаций по их исправлению. Так что это, скорее, подготовительный этап, а не полноценный аудит.</li>
<li>Аудит масштаба работ (Level-of-effort Audit). Цель такого аудита — узнать стоимость и объём работ по исправлению проблем.</li>
<li>Проверочный аудит (Validation Audit). Повторный аудит после внесения изменений.</li>
</ul>
<h3>По степени автоматизации</h3>
<p>Тестирование — важный этап аудита. В этой категории типы выделены на основе того, какой вид тестирования преобладает.</p>
<h4>Автоматический аудит</h4>
<p>Также известный как <em>Software/Automated Audit.</em> Проводится с помощью автоматических инструментов и автотестов. Помогает быстро выявить основные проблемы с доступностью.</p>
<p>Однако у него есть несколько недостатков:</p>
<ul>
<li>Не всё можно проверить автоматически. Например, читаемость текстов, качество альтернативных описаний для изображений или понятность иконок. Автоматическое тестирование поможет отловить только <a href="https://alphagov.github.io/accessibility-tool-audit/">30–50% проблем с доступностью</a>. Интересно, что <a href="https://www.deque.com/axe/">axe от Deque</a> обещает найти <a href="https://www.deque.com/blog/automated-testing-study-identifies-57-percent-of-digital-accessibility-issues/">57% от всех проблем</a>.</li>
<li>Не всегда гарантируется соответствие рекомендациям и указывается их источник. Какие-то инструменты проверяют по последним версиям WCAG, какие-то нет.</li>
<li>Не все инструменты валидируют HTML и <a href="https://www.w3.org/TR/wai-aria/">ARIA</a> (Accessible Rich Internet Applications). К примеру, они могут пропустить вложенные друг в друга заголовки или явные ошибки с ARIA-атрибутами.</li>
</ul>
<p>Так что начинать оценку хорошо с автоматического аудита, но не стоит на нём останавливаться.</p>
<h4>Ручной аудит</h4>
<p>Он же <em>Manual Audit.</em> Заполняет пробелы автоматического. Можно проверить интерфейс с клавиатуры, посмотреть страницу с экранной лупой, провести юзабилити-тестирование или интервью с пользователями.</p>
<h4>Смешанный аудит</h4>
<p>Или <em>Hybrid Audit.</em> Совмещает ручные и автоматические методы. С ним легче подстроить процесс тестирования под нужды компании и избежать сложностей и пробелов только ручного или автоматического тестирования.</p>
<h3>По этапу разработки</h3>
<ul>
<li>Технический аудит. Оценка кода. В основном это автоматическая проверка с элементами ручного тестирования.</li>
<li>Аудит дизайна. Важный тип аудита, ведь примерно <a href="https://www.deque.com/blog/auditing-design-systems-for-accessibility/#design-should-consider-accessibility">67% проблем с доступностью связаны с дизайном</a>. Его можно разделить на два дополнительных подтипа:
<ul>
<li>Дизайн-аудит (Design/UI Audit). Оценка целой дизайн-системы или отдельных UI-компонентов и паттернов вручную или с помощью специальных инструментов вроде axe или <a href="https://wave.webaim.org">WAVE</a>.</li>
<li>Юзабилити-аудит (Usability/UX Audit). Этот тип помогает детальнее изучить то, как пользователи с особыми потребностями взаимодействуют с интерфейсом, и с чем у них возникают проблемы. Сочетает ручной аудит и юзабилити-тестирование. Например, оценивает читаемость текстов, информационную архитектуру или навигацию с клавиатуры.</li>
</ul>
</li>
<li>Совмещённый аудит. Оценка того, как работает весь интерфейс. Проверяются код, дизайн и контент.</li>
</ul>
<h3>По проверяемым технологиям</h3>
<ul>
<li>Аудит сайтов и десктопных приложений (Website Audit и App Audit).</li>
<li>Мобильный аудит (Mobile Audit).</li>
<li>Аудит документов (Document Audit). Можно проверять любые форматы. К примеру, PDF, DOC или PPT.</li>
<li>Приёмочное тестирование скринридеров (Screen Reader Audit). Оценка доступности для пользователей программ экранного чтения. Обычно проверяется то, как читаются заголовки, медиаконтент, интерактивные элементы, отдельные области страницы, а также порядок фокуса.</li>
<li>Аудит терминалов (Kiosk Audit). Оцениваются терминалы, в основном вручную.</li>
<li>Аудит голосовых помощников (Conversational Audit). Оценка интерфейсов для голосового управления. Например, с помощью голосовых ассистентов Alexa или Google Home.</li>
<li>И другие технологии.</li>
</ul>
<h3>Как выбрать нужный тип</h3>
<p>Чтобы подобрать подходящий тип аудита, ответьте для себя на несколько вопросов.</p>
<ul>
<li>Какая цель у аудита?
<ul>
<li>Проверить весь продукт на соответствие рекомендациям уровня AA из WCAG 2.1.</li>
<li>Узнать, насколько доступна дизайн-система.</li>
<li>Уточнить примерную стоимость аудита.</li>
<li>Или что-то другое.</li>
</ul>
</li>
<li>Что это за продукт? Может быть, это сайт, веб-приложение или сервис с голосовым управлением.</li>
<li>Насколько продукт объёмный, как часто обновляется и как много стороннего кода используется?</li>
<li>Насколько хорошо команда разбирается в доступности? Есть ли сотрудники, которые могут провести аудит?</li>
<li>Сколько можете выделить на это ресурсов?</li>
<li>Какие сроки проведения?</li>
</ul>
<p>Подробный аудит может не всем сразу подойти. Если сайт объёмный, постоянно обновляется и принципы доступности не заложены изначально, то сначала лучше провести аудит масштаба работ. Он поможет определить общее количество проблем.</p>
<p>Также перед подробным аудитом рекомендуется провести технический и исправить самые очевидные проблемы с кодом. Тогда итоговый аудит будет стоить меньше и выявит более серьёзные и фундаментальные проблемы с юзабилити.</p>
<p>Самостоятельный аудит тоже подойдёт не всем. Особенно, когда в команде нет специалистов по доступности. В этом случае лучше заказать внешний.</p>
<p>Когда недостаточно средств для проведения смешанного или ручного аудита, то в первое время поможет автоматический. Он не выявит всех проблем, зато сократит объём работы к моменту, когда вы сможете провести более подробный и глубокий аудит.</p>
<h2>Методологии</h2>
<p>Одна из базовых составляющих любого аудита — методология.</p>
<p><em>Методология</em> — это набор методов, правил и этапов, который помогает стандартизировать процесс аудита.</p>
<p>Можно найти уже готовые решения или разработать своё. Внутренние методологии обычно создают аудиторские и большие продуктовые компании, которые постоянно проводят аудит.</p>
<p>Методологии включают определённое количество этапов. Обычно их пять:</p>
<ol>
<li>Проработка требований к аудиту. На этом этапе определяются его цели, примерная стоимость и выбирается подходящий тип. Это обсуждается с аудитором или со специалистами по доступности внутри команды.</li>
<li>Изучение продукта. Здесь нужно выяснить, что это за продукт и какие технологии в нём используются. Например, это многостраничный сайт или небольшой лендинг, написанный на чистых CSS или HTML.</li>
<li>Проведение тестирования.</li>
<li>Составление отчёта.</li>
<li>Исправление ошибок, когда есть рекомендации.</li>
</ol>
<p>Этапы не обязательно проходить последовательно. В некоторых случаях это может происходить итеративно, с возвратом к предыдущим этапам. А какие-то из них можно вообще выполнять одновременно.</p>
<h3>WCAG-EM</h3>
<p><em><a href="https://www.w3.org/TR/WCAG-EM/">WCAG-EM</a> (Website Accessibility Conformance Evaluation Methodology, Методология оценки сайтов на соответствие доступности)</em> — публичная методология W3C. Она содержит рекомендации об оценке доступности на соответствие критериям успешности WCAG. Несмотря на название, подходит для любого цифрового продукта, не только для сайтов.</p>
<p>Аудит по этой методологии состоит из пяти шагов.</p>
<ol>
<li>Определение охвата аудита.</li>
<li>Изучение продукта.</li>
<li>Подбор репрезентативной выборки.</li>
<li>Проведение тестирования.</li>
<li>Составление отчёта.</li>
</ol>
<p><img src="https://web-standards.ru/articles/a11y-audit-basics/images/2.png" alt="Пять шагов проведения аудита из методологии WCAG-EM."></p>
<p>По мере прохождения шагов лучше записывать их результаты в черновик. Так будет проще потом собрать всё в итоговый отчёт.</p>
<p>Теперь давайте рассмотрим каждый шаг подробнее.</p>
<h4>1. Определение охвата аудита</h4>
<p>На первом шаге определяются границы того, что планируется оценивать. Чтобы это было проще сделать, шаг лучше разбить не несколько подзадач.</p>
<p>Для начала определяем, что оценивается и в каких объёмах. Например, сколько страниц на сайте, на каких они языках, есть ли мобильная версия и чем она отличается от десктопной, используются ли сторонние сервисы и контент. Лучше оценивать за один аудит что-то одно. Если пользователи для решения задач часто используют сразу несколько продуктов, тогда проводится один общий аудит.</p>
<p>Также следует разобраться с поддерживаемыми технологиями. Это могут быть определённые операционные системы, браузеры и вспомогательные технологии.</p>
<p>Легче всего выбрать <a href="https://webaim.org/projects/screenreadersurvey9/">популярные комбинации</a>:</p>
<ul>
<li>Windows: JAWS или NVDA и Chrome.</li>
<li>macOS: VoiceOver и Safari.</li>
<li>iOS: VoiceOver и Safari.</li>
<li>Android: TalkBack и Chrome.</li>
</ul>
<p>Важно определить желаемый уровень соответствия WCAG. Во WCAG 2.1 и 2.2 их три: A (минимальный), AA (средний) и AAA (максимальный). Чаще всего ориентируются на AA.</p>
<p>Опционально можно добавить дополнительные требования. Они зависят от целей аудита и особенностей продукта. Это могут быть:</p>
<ul>
<li>Подходящий шаблон отчёта.</li>
<li>Количество решений по исправлению проблем.</li>
<li>Дополнительные страницы не из основной выборки, которые всё равно хочется оценить.</li>
<li>Планируется ли привлекать к оценке пользователей с особыми потребностями.</li>
</ul>
<h4>2. Изучение продукта</h4>
<p>Теперь нужно подробнее изучить охват и собрать больше технических деталей. На следующем шаге они помогут сформировать выборку страниц для оценки.</p>
<p>Для начала надо выбрать популярные страницы. Обычно это главная и другие внутренние страницы, на которые есть ссылки в хедере и футере. Конечно, выбор упростит статистика по посещениям.</p>
<p>Также стоит подумать об основных функциях, которые есть в продукте. Например, выбор и покупка товаров, регистрация аккаунта или загрузка и просмотр видео.</p>
<p>Ещё важно выделить виды страниц и их состояния. Они могут выглядеть и вести себя по-разному, содержать уникальный контент и из-за этого иметь разную поддержку доступности. Например, у страниц разные макеты, структура и навигация, стили или тип контента.</p>
<p>Отдельно стоит подумать о важных пользовательских путях. К примеру, добавление товара в корзину и оформление заказа.</p>
<p>Кроме этого, нужно определить используемые технологии. Какие фреймворки и CMS используются, есть ли CSS-анимация, ARIA-роли и атрибуты и другое.</p>
<p>Опционально можно поискать значимые страницы для людей с особыми потребностями. На них обычно находится информация о специальных возможностях, инструкции и отдельные контакты для обратной связи или помощи.</p>
<h4>3. Подбор репрезентативной выборки</h4>
<p>Пришло время подобрать репрезентативные страницы, их состояния и пользовательские пути. На выбор влияют размеры сайта и его страниц, их новизна, сложность функциональности и другие похожие характеристики.</p>
<p>Если сайт небольшой, то можно не искать специально страницы для выборки. Его легко оценить целиком.</p>
<p>Когда сайт большой и страниц много, то для начала лучше сформировать структурированную выборку. В неё включают все типы страниц и пользовательские пути. Хорошо, когда есть хотя бы одна страница, которая относится ко всем типам.</p>
<p>В выборку ещё хорошо включить все страницы и их состояния, которые являются частью одного процесса. Например, форма заказа товара, которая состоит из нескольких страниц (этапов). В этом случае они — часть одного большого процесса покупки, и не работают друг без друга.</p>
<p>Дополнительно может понадобиться случайная выборка. Она полезна для объёмных сайтов, когда структурированной выборки не хватает для полной оценки. Случайных страниц должно быть 10% от числа страниц из структурированной выборки. То есть, если в структурированной выборке их 80, то в случайную попадут 8. Случайные страницы выбирают разными способами. Например, с помощью инструментов и скриптов для случайного выбора или просмотра серверных логов.</p>
<p>Случайная и структурированная выборки сначала оцениваются отдельно, а потом сравниваются. Поэтому важно в конце этого шага составить список страниц со ссылками, состояниями и пользовательскими путями с их кратким описанием. Это поможет сэкономить время и провести более точные тесты на следующем шаге.</p>
<h4>4. Проведение тестирования</h4>
<p>На этом шаге оцениваем страницы из выборок на соответствие рекомендациям из чек-листа. Проверять можно вручную, при помощи автоматических инструментов и привлекать к этому пользователей.</p>
<p>Полезно провести и автоматическое, и ручное тестирование. К ручному идеально привлекать не только экспертов, но и пользователей вспомогательных технологий. А перед этим лучше исправить критичные ошибки, которые обнаружили автоматические инструменты. Это ускорит пользовательское тестирование и поможет избежать лишних расходов.</p>
<p>Оценка проводится на основе <a href="https://www.w3.org/TR/WCAG21/#conformance-reqs">пяти требований соответствия из WCAG</a>.</p>
<ol>
<li>Уровень соответствия. Если цель — проверка на соответствие уровню AA, то в результате получится утверждение, соответствует ли ему элемент или нет.</li>
<li>Страницы целиком. Если часть страницы соответствует рекомендациям уровня A, а другая — AA, то она не соответствует уровню АА.</li>
<li>Соответствие процесса в целом. Для этого проверяются пользовательские пути. Например, если в корзину нельзя попасть из карточки товара, то это критичная ошибка.</li>
<li>Способы использования технологий, поддерживающих доступность. Контент оценивается с помощью поддерживаемых вспомогательных технологий. Если он не поддерживается напрямую, то проверяются альтернативные версии. Например, видео без субтитров или титров считается доступным, если у него есть текстовая расшифровка на этой же или отдельной странице.</li>
<li>Невмешательство. Если технологии недоступны, но не мешают пользователям или не важны для них, то это не ошибка. К примеру, рекламные баннеры.</li>
</ol>
<p>Результаты оценки лучше сразу записывать в таблицу вместе с указанием проблемных страниц, элементов и состояний. Если есть несоответствия, то отмечаются их причины. Некоторые элементы получат статус «предупреждение». Это субъективная оценка, а не ошибка.</p>
<p>Тестирование можно разделить на несколько небольших задач.</p>
<p>Вначале стоит убедиться, что выбранные страницы соответствуют всем рекомендациям. Проще это сделать, если их разбить на отдельные компоненты. Например, на заголовки разных уровней, меню в хедере и футере, строку поиска и другие. Обычно элементы повторяются и ведут себя одинаково, так что можно протестировать их один раз.</p>
<p>Также следует проверить, правильно ли работают формы, модальные окна и другие элементы, которые встречаются на пользовательских путях.</p>
<p>В самом конце сравнивают результаты проверки структурированной и случайной выборок, если их было две. Появление новых типов контента или выводов — сигнал о том, что случайная выборка получилась нерепрезентативной. В этом случае нужно вернуться к третьему и второму шагу, чтобы расширить выборку с учётом новых типов и состояний. Это нужно повторять до тех пор, пока обе выборки не будут хорошо отражать контент и структуру сайта.</p>
<h4>5. Составление отчёта</h4>
<p>На последнем шаге все выводы объединяются в один отчёт.</p>
<p>Отчёты составляются по шаблону, который состоит из обязательных и опциональных пунктов.</p>
<ul>
<li>Общая информация.
<ul>
<li>Имена аудиторов.</li>
<li>Имя ответственного за проведение аудита. Это может быть владелец сайта, разработчик и любой другой человек, который предложил провести аудит.</li>
<li>Дата проведения.</li>
</ul>
</li>
<li>Охват из первого шага.
<ul>
<li>Особенности продукта. Например, поддерживаемые языки и внешние сервисы.</li>
<li>Желаемый уровень соответствия.</li>
<li>Какие технологии планировалось поддерживать. Ими могут быть браузеры, операционные системы, скринридеры, экранные лупы и прочее.</li>
<li>Дополнительные требования (опционально). Например, какие-то особые функции или страницы, которые хотелось отдельно протестировать.</li>
</ul>
</li>
<li>Описание продукта из второго шага.
<ul>
<li>Технологический стек.</li>
<li>Важные страницы, например, главная (опционально).</li>
<li>Ключевая функциональность (опционально).</li>
<li>Типы страниц и их состояний (опционально).</li>
<li>Страницы, которые имеют значение для людей с особыми потребностями (опционально).</li>
</ul>
</li>
<li>Репрезентативная выборка из третьего шага.
<ul>
<li>Какие оценивались страницы, состояния и типы.</li>
<li>Важные пользовательские пути.</li>
<li>Список случайно выбранных страниц (опционально).</li>
</ul>
</li>
<li>Результаты тестирования из четвёртого шага.
<ul>
<li>Оценка страниц из структурированной выборки.</li>
<li>Оценка страниц из случайной выборки (опционально).</li>
<li>Сравнение результатов оценки страниц из случайной и структурированной выборок (опционально).</li>
</ul>
</li>
</ul>
<p><strong>Примечание.</strong> Для отчётов об аудите инструментов для разработки и проверки доступности используют машиночитаемый формат <a href="https://www.w3.org/WAI/standards-guidelines/earl/">EARL</a> (Evaluation and Report Language, Язык для оценки и отчёта).</p>
<p>Можно не останавливаться на рекомендациях WCAG-EM и добавить в отчёт ещё больше деталей. Включить можно всё, что кажется полезным.</p>
<ul>
<li>Какие автоматические инструменты для тестирования использовались.</li>
<li>Количество страниц и элементов, которые соответствует или не соответствуют рекомендациям.</li>
<li>Общая статистика по количеству найденных проблем и сколько обнаружено глобальных блокеров. Ещё можно указать, какой тип тестирования помог их выявить.</li>
<li>Влияние барьеров на людей. Например, какие категории пользователей они затронули и насколько серьёзно.</li>
<li>Интересные выводы из пользовательского тестирования.</li>
<li>Какой процент проблем выявило автоматическое или ручное тестирование.</li>
<li>Следующие шаги по внедрению доступности.</li>
</ul>
<p>Сделать отчёт ещё нагляднее помогут скриншоты и графики. К примеру, в панели мониторинга Deque есть круговая диаграмма с процентом выполненных и невыполненных критериев успешности WCAG, а ещё гистограммы с серьёзностью ошибок и частотой проблем.</p>
<figure>
    <img src="https://web-standards.ru/articles/a11y-audit-basics/images/3.png" alt="Три графика. На первом 60 % страниц прошли проверку, а 40 % нет. На втором — 29 проблем критичные, 62 серьёзные и 62 средние. На третьем — чаще всего встречаются проблемы с контрастностью, ARIA, alt, заголовками и с зумом страниц.">
    <figcaption>«<a href="https://www.deque.com/blog/what-to-look-for-in-an-accessibility-audit/">What to look for in an accessibility audit</a>», Гленда Симс.</figcaption>
</figure>
<p>В отдельном документе можно сохранить дополнительную информацию про проведение аудита, чтобы дополнить или лучше раскрыть выводы из отчёта. Это могут быть описание путей для поиска страниц и их состояний.</p>
<h2>Публичные заявления, VPAT и ACR</h2>
<p>Многие аудиторские компании оценивают продукты для составления публичных заявлений, шаблонов добровольной доступности (VPAT) и отчётов о соответствии ей (ACR). В этих документах нет рекомендаций, в отличие от полноценного отчёта об аудите. Они описывают только доступные фичи и соответствие критериям WCAG или требованиям законов.</p>
<h3>Публичные заявления</h3>
<p>Компании публикуют такие заявления на своих сайтах, если хотят рассказать пользователям и партнёрам о доступности сайта или приложения. Как правило, это не обязательно делать.</p>
<p>Есть несколько видов заявлений.</p>
<p><em>Заявление о доступности (Accessibility Statement)</em> — перечисление и описание типов фич и их уровня соответствия критериям успешности из WCAG или требованиям законов. Обычно описывает доступность самых важных страниц и экранов. Пример — <a href="https://www.twitch.tv/p/ru-ru/legal/accessibility/">Заявление о доступности Twitch</a>.</p>
<p>В странах Евросоюза заявление о доступности обязательно должно быть у сайтов и мобильных приложений государственных органов. Для него разработана <a href="https://eur-lex.europa.eu/eli/dec_impl/2018/1523/oj">отдельная европейская модель</a>.</p>
<p><em><a href="https://www.w3.org/TR/WCAG21/#conformance-claims">Заявление о соответствии</a> (Conformance Claims)</em> описывает уровень соответствия сайта рекомендациям из WCAG. Пример — <a href="https://www.panasonic.com/global/webaccessibility/results/2021.html">Заявление о соответствии Panasonic</a>.</p>
<p><em><a href="https://www.w3.org/TR/WCAG-EM/#step5c">Заявление об оценке</a> (Evaluation Statement)</em> — это публичные выводы о проведении аудита по методологии WCAG-EM. Состоит из нескольких пунктов:</p>
<ol>
<li>Дата проведения аудита.</li>
<li>Версия WCAG в основе аудита.</li>
<li>Желаемый уровень соответствия.</li>
<li>Краткое описание охвата: сайт, приложение, что-то другое. В общем, информация, которая была собрана на первом шаге аудита по методологии.</li>
<li>Какие технологии используются.</li>
<li>Поддерживаемые вспомогательные технологии.</li>
<li>Если продукт частично соответствует требованиям доступности, то какие части им не соответствуют.</li>
<li>Причины несоответствия.</li>
</ol>
<p>Пример — <a href="https://www.cumberlandcountync.gov/departments/no-department-pages-group/accessibility-evaluation-statement">Заявление об оценке сайта правительства округа Камберленд</a>.</p>
<h3>VPAT и ACR</h3>
<p>Оба документа описывают уровень соответствия продукта или сервиса критериям WCAG или требованиям законов и стандартов о доступности. Например, десктопной программы или сервиса по оплате штрафов.</p>
<p>Нужны американским и европейским компаниям, которые участвуют в государственных закупках в США. Без этого они не смогут сотрудничать с американскими государственными органами.</p>
<p><em><a href="https://www.itic.org/policy/accessibility/vpat">VPAT</a> (Voluntary Product Accessibility Template, Шаблон добровольной доступности продукта)</em> — зарегистрированный знак обслуживания ITI (Information Technology Industry Council, Совет индустрии информационных технологий). Изначально составлялся для того, чтобы государственным органам США было проще проводить исследования доступности американского информационного рынка.</p>
<p>Выглядит как таблица с критериями доступности, уровнем их поддержки и дополнительными примечаниями и пояснениями.</p>
<p>Существует четыре вида VPAT:</p>
<ul>
<li>Редакция WCAG про соответствие WCAG 2.0 или 2.1.</li>
<li>Редакция 508 про соответствие Section 508.</li>
<li>Редакция EU про соответствие EN 301 549.</li>
<li>Редакция INT про соответствие всем трём документам.</li>
</ul>
<p>Пример — <a href="https://government.github.com/accessibility/">GitHub VPAT</a>.</p>
<p><em>ACR (Accessibility Conformance Report, Отчёт о соответствии доступности)</em> — финальный отчёт о доступности продукта с бо́льшим количеством деталей. Он включает заполненный VPAT, информацию о продукте или услуге, поддерживаемых вспомогательных технологиях и другие детали.</p>
<p>Кроме стандартного ACR, есть ещё версия с поддержкой машинного чтения — <a href="https://accessibility.civicactions.com/posts/CivicActions-Creates-Open-Product-Accessibility-Template">OPAT</a> (Open Product Accessibility Template, Открытый шаблон добровольной доступности продукта).</p>
<h2>Оценка доступности пользователями</h2>
<p><img src="https://web-standards.ru/articles/a11y-audit-basics/images/4.jpg" alt=""></p>
<p>Автоматическое, экспертное и другие виды ручного тестирования могут пропустить некоторые важные и неочевидные проблемы. Они могут быть связаны с неправильной разметкой, плохим юзабилити или особенностями вспомогательных технологий. Поэтому в рамках аудита ещё желательно проводить оценочные пользовательские исследования. Только они смогут оставшиеся проблемы с доступностью.</p>
<p>В проведении исследований есть особенности, которые могут привести к ошибочным выводам. Поэтому лучше всего привлекать людей, которые умеют проводить интервью или юзабилити-тестирование. Если такой возможности нет, лучше найти компанию, которая этим занимается. Однако можно провести исследования и собственными силами. Это всё равно лучше, чем ничего.</p>
<h3>Методы исследований</h3>
<p>Для такой дополнительной проверки можно провести анкетирование, интервью, юзабилити-тестирование. На выбор варианта исследования и его параметры здесь влияют:</p>
<ul>
<li>Особенности продукта и этап его развития.</li>
<li>Выбранный тип аудита.</li>
<li>Количество доступных ресурсов у компании. Исследования могут быть довольно продолжительными и дорогими. Для них потребуется выделение дополнительных сотрудников и физический выезд к каждому респонденту.</li>
<li>Количество выделенных ресурсов для поиска респондентов.</li>
</ul>
<p>Если нужно провести юзабилити-тестирование, то сценарий хорошо составить таким образом, чтобы тест выявлял только проблемы с доступностью, а не общие проблемы юзабилити. Так сможете сэкономить время.</p>
<p>Кроме наблюдения или вопросов об опыте взаимодействия с продуктом, ещё помогут вопросы про используемые технологии. К примеру, об устройствах, операционных системах, браузерах, скринридерах или других вспомогательных технологиях.</p>
<p>Полезные ссылки по теме:</p>
<ul>
<li><a href="http://www.uiaccess.com/JustAsk/">Just Ask: Integrating Accessibility Throughout Design</a>, Шона Генри.</li>
<li><a href="https://medium.com/p/2e853250960f">Юзабилити-тестирование по дешёвке</a>, Влад Головач.</li>
<li><a href="https://www.ozon.ru/product/kak-sdelat-sayt-udobnym-yuzabiliti-po-metodu-stiva-kruga-28288606/">Как сделать сайт удобным. Юзабилити по методу Стива Круга</a>, Стив Круг.</li>
<li><a href="https://dmkpress.com/catalog/computer/software_development/978-5-97060-960-6/">Основы юзабилити-тестирования</a>, Кэрол М. Барум. Исчерпывающая книга про юзабилити-тестирование.</li>
</ul>
<h3>Требования к респондентам и их поиск</h3>
<p>Привлекайте к юзабилити-тестированию людей с разнообразными особыми потребностями и особенностями. К примеру, человек с когнитивными нарушениями не знает про доступность для людей с глухотой. Такое разнообразие респондентов поможет найти больше проблем с доступностью.</p>
<p>Однако не ограничивайтесь людьми с «официальной» инвалидностью и привлекайте, например, людей с особенностями восприятия информации, дислексией, синдромом дефицита внимания, тревожными расстройствами, цветовой слепотой, а также пожилых и тех, у кого есть проблемы с памятью.</p>
<p>Не любой человек с особыми потребностями подойдёт на роль респондента для проверки конкретно вашего продукта. Например, человек с глухотой не поможет выявить проблемы с доступностью трекера задач. Взаимодействие с интерфейсом такого респондента не отличается от поведения других людей. Поэтому определите требования к респондентам перед их поиском.</p>
<p>Примеры критериев для отбора респондентов:</p>
<ul>
<li>знание предметной области;</li>
<li>наличие опыта работы с похожими продуктами;</li>
<li>пользовался ли респондент проверяемым продуктом или нет;</li>
<li>уровень компьютерной грамотности;</li>
<li>навыки владения вспомогательными технологиями;</li>
<li>вид особых потребностей, физических или ментальных особенностей;</li>
<li>возраст.</li>
</ul>
<p>Можете начать поиски респондентов с этих мест:</p>
<ul>
<li>Специализированные региональные и международные организации, занимающиеся поддержкой людей с инвалидностью. Например, <a href="https://www.voi.ru/o_nas/ob_organizacii">Всероссийское общество инвалидов</a>, <a href="https://voginfo.ru">Всероссийское общество глухих</a> или <a href="https://www.vos.org.ru">Всероссийское общество слепых</a>.</li>
<li>Фонды, ассоциации и местные группы поддержки людей с особыми потребностями и с нейроотличиями. К примеру, благотворительный фонд поддержки людей с аутизмом «<a href="https://antontut.ru/about/">Антон тут рядом</a>» или <a href="https://dyslexiarf.com">Ассоциация родителей детей и взрослых с дислексией</a>.</li>
<li>Университетские программы для студентов с особенностями развития.</li>
<li>Организации, занимающиеся поддержкой пожилых людей.</li>
</ul>
<p>Можно попросить помощи у экспертов по доступности с инвалидностью. Они подскажут, как и где найти нужных респондентов. Также есть компании, которые сами подберут подходящих респондентов.</p>
<p>Полезные ссылки по теме:</p>
<ul>
<li>В книге «Just Ask: Integrating Accessibility Throughout Design» Шоны Генри есть больше информации о том, <a href="http://www.uiaccess.com/accessucd/ut_plan.html#characteristics">как отбирать участников для тестирования</a> и <a href="http://www.uiaccess.com/accessucd/interact.html">как правильно с ними взаимодействовать</a>.</li>
<li><a href="https://usabilitylab.ru/blog/poisk-respondentov-dlya-yuzabiliti-testirovaniya-chast-1-kriterii-otbora/">Поиск респондентов для юзабилити-тестирования. Часть 1: критерии отбора</a>, UsabilityLab.</li>
<li><a href="https://usabilitylab.ru/blog/respondent-search-2/">Поиск респондентов для юзабилити-тестирования. Часть 2: самостоятельный поиск</a>, UsabilityLab.</li>
<li><a href="https://medium.com/p/79d01ff38c0f">Как подбирать респондентов для UX исследования — опыт Crowdtesting</a>, Writes.</li>
</ul>
<h2>Что делать после аудита</h2>
<p>Для повышения уровня доступности продукта чаще всего недостаточно провести аудит один раз и исправить найденные проблемы. Важно на этом не останавливаться и улучшать внутренние процессы. Это поможет избежать многих проблем и не исправлять их каждый раз, когда что-то изменяется в интерфейсе.</p>
<p>После исправления ошибок бывает полезно перепроверить, точно ли их больше нет. Для этого пригодится регрессионное тестирование или проверочный аудит. Они обнаружат пропущенные ошибки в уже протестированных частях продукта или только подтвердят, что всё хорошо.</p>
<p>Также можно организовать тренинги и внутренние митапы по доступности. Так повысите уровень знаний, не потеряете их вместе с уволившимися сотрудниками и эффективнее будете онбордить новых людей.</p>
<p>Ещё полезно найти среди коллег <a href="https://pressbooks.library.ryerson.ca/dabp/chapter/characteristics-of-a-digital-accessibility-champion/">защитника доступности</a> (Accessibility Champion) и собрать команду из специалистов, которые в ней разбираются (Accessibility Champion Unit). Эти люди будут менторить других, помогать решать сложные задачи и следить за уровнем доступности продукта.</p>
<p>Если пользуетесь технологиями других разработчиков и работаете с аутсорсерами, то узнайте, как хорошо они знакомы с этой темой. Если слабо, то сделайте доступность обязательным требованием к их работе.</p>
<p>Не забывайте про регулярное автоматическое и ручное тестирование. Их можно внедрить на нескольких этапах разработки. К примеру, на этапе дизайна и написания кода. Это поможет заранее отлавливать ошибки и быстро их исправлять. Ещё полезно периодически проводить тестирование с клавиатуры. Так можно найти часть потенциальных проблем со скринридерами.</p>
<p>Наконец, важно вести документацию и чек-листы. Благодаря этому подходы к доступности станут прозрачными, знания будет проще передавать и распространять среди членов команды, а будущие аудиты станет легче и быстрее проводить.</p>
<h2>Выводы</h2>
<p>Аудит полезен в случае, если хотите повысить уровень доступности своего продукта или поддерживать его на нужном уровне.</p>
<p>У него чёткая структура и критерии в основе. Чаще всего они берутся из WCAG, но бывает и из локальных законов и стандартов о цифровой доступности.</p>
<p>Кроме этого, у любого аудита есть чёткая методология, по которой он проводится. Одна из самых популярных — WCAG-EM от W3C.</p>
<p>Он бывает разных типов. Например, самостоятельный, внешний, автоматический или подробный. Чтобы выбрать подходящий тип, нужно подумать о его целях и об особенностях оцениваемого продукта.</p>
<p>Итог любого аудита — отчёт, который состоит из списка всех найденных проблем и рекомендаций по их исправлению.</p>
<p>Один из важных этапов аудита — тестирование. Часто автоматического и ручного экспертного тестирования мало для того, чтобы сделать продукт максимально доступным. В этом случае помогает пользовательское тестирование. Оно находит неочевидные для специалистов проблемы.</p>
<p>На отчёт чем-то похожи публичные заявления, VPAT и ACR. Но, в отличие от отчёта, в них нет рекомендаций по исправлению ошибок. Это просто перечисление фич и того, что в продукте доступно, а что нет.</p>
<p>Доступность не заканчивается на аудите. Чтобы продукт всегда оставался доступным, нужно наладить внутренние процессы.</p>
<h2>Полезные ссылки</h2>
<h3>Рекомендации W3C</h3>
<ul>
<li><a href="https://www.w3.org/TR/WCAG-EM/">Website Accessibility Conformance Evaluation Methodology 1.0</a>.</li>
<li><a href="https://www.w3.org/WAI/test-evaluate/">Evaluating Web Accessibility Overview</a>.</li>
</ul>
<h3>Статьи</h3>
<ul>
<li><a href="https://digital.nhs.uk/about-nhs-digital/standards-for-web-products/accessibility-for-digital-services/accessibility-audit-process">Accessibility audit process</a>, NHS Digital.</li>
<li><a href="https://www.gov.uk/service-manual/helping-people-to-use-your-service/getting-an-accessibility-audit">Getting an accessibility audit</a>, GOV.UK.</li>
<li><a href="https://www.deque.com/blog/what-to-look-for-in-an-accessibility-audit/">What to look for in an accessibility audit</a>, Гленда Симс.</li>
<li><a href="https://www.deque.com/blog/auditing-design-systems-for-accessibility/">Auditing Design Systems for Accessibility</a>, Анна И. Кук.</li>
<li><a href="https://tetralogical.com/blog/2020/12/01/choosing-the-right-type-of-accessibility-assessment/">Choosing the right type of accessibility assessment</a>, Леони Уотсон.</li>
<li><a href="https://adactio.com/journal/18458">Accessibility testing</a>, Джереми Кит.</li>
<li><a href="https://ericwbailey.design/writing/accessibility-auditing-and-ego/">Accessibility auditing and ego</a>, Эрик Бэйли.</li>
<li><a href="https://www.boia.org/blog/whats-the-difference-between-an-accessibility-test-and-an-accessibility-audit">What's The Difference Between An Accessibility Test and an Accessibility Audit</a>, The Bureau of Internet Accessibility.</li>
<li><a href="https://www.poynter.org/tech-tools/2017/how-accessible-is-your-website-for-the-disabled-consider-doing-an-audit-to-find-out/">How accessible is your website for the disabled? Consider doing an audit to find out</a>, Мелоди Крамер.</li>
<li><a href="https://www.interactiveaccessibility.com/services/accessibility-certification">Accessibility Certification</a>, TPGi.</li>
<li><a href="https://krisrivenburgh.medium.com/acr-accessibility-conformance-report-vs-vpat-70828cd8e5bf">ACR: Accessibility Conformance Report (vs. VPAT)</a>, Крис Ривенбург.</li>
<li><a href="https://habr.com/ru/company/ispring/blog/564446/">Обеспечение доступности веб-контента: стандарты, критерии, пример реализации</a>, Павел Попов.</li>
<li><a href="https://pressbooks.library.ryerson.ca/dabp/chapter/characteristics-of-a-digital-accessibility-champion/">Characteristics of a Digital Accessibility Champion</a>, Ryerson University Pressbooks.</li>
</ul>
<h3>Книги</h3>
<ul>
<li><a href="http://www.uiaccess.com/accessucd/">Just Ask: Integrating Accessibility Throughout Design</a>, Шона Генри.</li>
<li><a href="https://dmkpress.com/catalog/computer/software_development/978-5-97060-960-6/">Основы юзабилити-тестирования</a>, Кэрол М. Барум.</li>
<li><a href="https://www.ozon.ru/product/kak-sdelat-sayt-udobnym-yuzabiliti-po-metodu-stiva-kruga-28288606/?sh=6WXreyNx">Как сделать сайт удобным. Юзабилити по методу Стива Круга</a>, Стив Круг.</li>
</ul>
<h3>Доклад</h3>
<p><a href="https://www.deque.com/axe-con/sessions/after-the-audit-integrating-accessibility-into-the-testing-process/">After the Audit: Integrating Accessibility into the Testing Process</a>, Кристал Престон-Уотсон. axe-con 2021.</p>
<h3>Отчёты о проведении аудита</h3>
<ul>
<li><a href="https://www.wpcampus.org/blog/2019/05/gutenberg-audit-results/">Отчёт по Gutenberg, редактору контента WordPress</a>.</li>
<li><a href="https://www.strathcona.ca/files/files/comc-public-website-accessibility-audit-2018.pdf">Отчёт по аудиту сайта канадского муниципалитета</a> Страткона Каунти.</li>
</ul>

                    ]]></description><pubDate>Thu, 03 Feb 2022 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/a11y-audit-basics/</guid></item><item><title>Онлайн-обучение: советы студентам</title><link>https://web-standards.ru/articles/online-education-2/</link><description><![CDATA[
                        <p><a href="https://web-standards.ru/articles/online-education-1/">В первой части серии</a> мы поговорили про основы обучения взрослых и о плюсах и минусах онлайн-обучения. Во второй части мы посмотрим на процесс с другой стороны — глазами студента.</p>
<p>Раньше, чтобы получить профессию, нужно было поступить в университет. Это было целое приключение: подать заявление, сдать несколько экзаменов, а потом дежурить возле списков зачисленных. Если везло не очень, то ещё и заплатить денег за обучение. Всё это сильно повышало ценность обучения и полученных знаний. Знакомые предприниматели и владельцы бизнеса, по их словам, смотрели на диплом как на подтверждение, что человек способен довести начатое до конца и достаточно упорен, чтобы преодолеть все трудности.</p>
<p>Сегодня образование превратилось в сервис. Ты буквально с телефона можешь нажать пару кнопок и получить доступ к знаниям. Эта простота отлично сказывается на доходах онлайн-школ, но плохо влияет на мотивацию студентов. С каждым годом я вижу всё меньше студентов, которые действительно готовы учиться. Из-за простоты доступа к курсам у них создаётся ошибочное ощущение, что и получение знаний будет таким же простым.</p>
<p>К сожалению, процесс доступа к знаниям упростился, но процесс самого получения знаний — нет. До сих пор не существует технологии репликации знаний: достать флешку из одной головы и вставить в другую. Чтобы действительно <strong>научиться</strong> чему-то нужно приложить усилия. Вы сами несёте ответственность за итог обучения.</p>
<p>В этой статье я хочу дать несколько советов будущим или настоящим студентам. Верю, что они помогут качественному обучению, освоению навыков и в конечном счёте — освоению новой профессии.</p>
<h2>Нематериальный мир</h2>
<h3>Мотивация</h3>
<p>Первое, о чём стоит сказать — это о мотивации. Если вы пошли на курсы потому что действительно хотите изменить свою жизнь, сменить работу, зарабатывать больше и так далее, то это будет помогать вам дойти до конца. Если же в основе лежит простой интерес или скидки-акции-реклама, то ваша заинтересованность быстро спадёт. Получится эффект фитнеса: вы покупаете годовой абонемент, ходите несколько раз и забиваете. Всем хорошо. Вы утешаете себя мыслью, что вы походили и ещё пойдёте — абонемент-то в кармане! Фитнесу вообще отлично, он получил деньги ни за что.</p>
<p>Поэтому очень советую вам перед покупкой любого курса провести с собой маленький психологический анализ и разобраться, что вас мотивирует на эту покупку. Ответьте для себя на несколько вопросов:</p>
<ul>
<li>Что меня толкает на покупку курса?</li>
<li>Как я использую полученные знания потом?</li>
<li>Как я верну потраченное?</li>
<li>Что изменится в моей жизни после курса?</li>
</ul>
<p>Список вопросов не полный, расширяйте и меняйте его на своё усмотрение. Главное чтобы у вас было чёткое понимание точки А и точки Б, между которыми обязательно лежит курс и никак иначе этот путь не преодолеть.</p>
<p>Работа с мотивацией тем более нужна, если вы выбрали бесплатный курс. Там вы ничем не жертвуете, кроме времени. Процесс доступнее, проще, но от этого менее ценный для вас. Для эффективного обучения на бесплатном курсе у вас должна быть тройная мотивация.</p>
<p>Из ответов на вопросы лучше всего сформулировать конкретную цель. Лучше всего будет сформулировать её в формате <a href="https://ru.wikipedia.org/wiki/SMART">SMART</a> (Specific, Measurable, Achievable, Realistic, Timely):</p>
<ul>
<li>Конкретная</li>
<li>Измеримая</li>
<li>Достижимая</li>
<li>Значимая</li>
<li>Ограниченная по времени.</li>
</ul>
<p>Например, цель «хочу стать разработчиком» не подойдёт. В ней нет критериев оценки успешности достижения этой цели, нет временных рамок. Для кого-то это может не быть значимой целью, ведь он всегда хотел быть ветеринаром.</p>
<p>Лучше так: «Хочу устроиться на позицию джуниор фронтенд-разработчика через три месяца после окончания курса». Такая цель:</p>
<ol>
<li>Конкретная,</li>
<li>Измеримая — либо устроились, либо нет,</li>
<li>Достижимая — множество примеров трудоустройства джунов после курсов,</li>
<li>Значимая — тут, конечно, только вам решать, на сколько это значимо лично для вас,</li>
<li>Ограничена по времени — это поможет достичь её в установленные сроки, а не размазать на двадцать лет.</li>
</ol>
<p>Лично со мной хорошо работает, если эта цель всегда у меня перед глазами. Это не позволяет моему мозгу сачковать и соскакивать. Я всегда помню о своей мотивации и делаю шаги к ней каждый день.</p>
<h3>Вопросы</h3>
<p><em>Ещё один грустный вздох на тему неизобретённой репликации знаний.</em> Поехали дальше.</p>
<p>В составлении и подготовке курсов обычно участвуют большие команды: методисты, преподаватели, руководители. Если это хорошие курсы, конечно. Они пытаются уместить большую область знаний в очень ограниченные временные рамки. В таком формате просто физически невозможно уместить вообще всё. Вам дадут основы и практику, но вокруг всего этого будет туман войны.</p>
<p>Поэтому очень важно уметь задавать вопросы! Почему-то с этим у многих студентов возникает проблема.</p>
<p>Практически на всех курсах, с которыми я сталкивалась, есть преподаватели, кураторы, наставники или аспиранты — люди, которым можно задавать вопросы. Пользуйтесь этим по полной. Это позволит не только лучше понять пройденный материал, закрыть белые пятна, но и узнать гораздо больше того, что даётся в программе.</p>
<p>Многие люди ищут менторов чтобы те отвечали на их вопросы и помогали двигаться вперёд. А у вас бонусом к курсам идут менторы — пользуйтесь этим!</p>
<p>Не знаю откуда, но у многих моих студентов существует страх задать вопрос, если они чего-то не поняли. Думаю, ноги растут из неприятного опыта в детстве. Но это не моя специализация =) Регулярно сталкиваюсь с ситуацией, когда студент безмолвно присылает неверно сделанную работу на проверку. В ходе обсуждения оказывается, что он не понял тему. Но не задал никаких вопросов! Вместе формулируем вопрос — вместе находим на него ответ.</p>
<p>Не бойтесь показаться навязчивым, надоедливым. Задавайте вопросы пока не получите ответы на них. У вас есть цель, помните? Вам нужно к ней добраться кровь из носу!</p>
<p>Есть другая гипотеза, что вопросы не задают потому, что человек не может их сформулировать. И правда, нас этому навыку нигде не учат специально. Примерный список вопросов к себе, который может вам помочь обозначить проблему и сформулировать вопрос к преподавателю, может выглядеть так:</p>
<ol>
<li>Тема, которую вы осваиваете.</li>
<li>Что делаете (если это код, прикладываете ссылку).</li>
<li>Что ожидаете получить.</li>
<li>Что получаете.</li>
<li>Какие способы пробовали.</li>
</ol>
<p>В зависимости от осваиваемой профессии список может расширяться, но возьмите этот за основу.</p>
<p>Навык постановки вопроса поможет вам не только в процессе обучения, но и в профессиональной деятельности, да и просто в жизни. Не секрет, что все разработчики регулярно гуглят. Чтобы получить релевантные ответы, нужно правильно задавать вопросы.</p>
<p>Из этого совета, кстати, вытекает важность посещения вебинаров онлайн, если они есть в вашем курсе. Это возможность напрямую задать свой вопрос и получить на него ответ. Возможно даже с примером от преподавателя.</p>
<h3>Формат усвоения знаний</h3>
<p>Наверняка, большинство из вас слышали, что люди условно делятся на визуалов, аудиалов и кинестетиков. В этой системе существует и четвёртый тип, о котором всегда забывают — дигиталы. Дигиталы выстраивают логическую модель мира в процессе внутреннего диалога.</p>
<p>Скорее всего ещё в школе вы выяснили про себя подходящий вам способ усвоения знаний. Кому-то важно было вести конспект, кому-то было достаточно послушать учителя, а кто-то запоминал только когда проговорит вслух. Это всё относится к вашему личному способу запоминания и понимания.</p>
<p>Как правило онлайн-курсы дают ограниченный набор типов материалов. Это могут быть вебинары, методички или презентации, практическая работа. Но в зависимости от вашего типа восприятия вам может быть этого не достаточно.</p>
<p>Ведите конспект, проговаривайте вслух, простраивайте логические цепочки, переслушивайте или пересматривайте лекции — на этом ринге нет запрещённых приёмов. Делайте всё, чтобы теоретический материал уложился в голове.</p>
<p>Со сменой профессии ваш жизненный опыт не обнуляется. Всё, что вы знали и умели до поступления на курс может вам помочь в освоении новых знаний. Проводите аналогии с уже известными вам вещами, привязывайте новые термины и понятия к объектам из понятного для вас мира. Мозгу так проще строить связи, меньше тратится энергии.</p>
<h3>Практика</h3>
<p>Теория это, конечно, прекрасно. Но мы все учимся для освоения конкретных практических навыков.</p>
<p>Большинство курсов измеряют <em>доходимость</em> курсов: количество студентов, успешно закончивших курс. Отсюда возникает необходимость подгонять программу под самых слабых. Лично мне кажется, что это наследие классической педагогики, но сейчас не об этом.</p>
<p>В случае с онлайн-обучением программа составляется так, чтобы даже самый занятой человек мог себе позволить её пройти. Отсюда появляется ограничение по объёму практики, которую дают с каждым блоком теории. Так все успеют выполнить задание и сдать его в срок.</p>
<p>Если вы действительно заинтересованы в достижении крутого результата, то я советую вам практиковаться больше. Помимо домашнего задания, придумайте себе ещё задачки в рамках освоенных тем. Если вы учитесь фронтенду и у вас нет идей, откуда брать задачки, то вам может помочь старая, но совсем не устаревшая статья «<a href="https://medium.com/p/f524d668f328">От нуля до героя фронтенда</a>».</p>
<p><img src="https://web-standards.ru/articles/online-education-2/images/1.png" alt="Рисованная девушка сидит за столом с ноутбуком, рядом стоит стаканчик кофе."></p>
<h3>Широкий кругозор</h3>
<p>Ни один курс не научит вас вообще всему, не обманывайтесь. Он может дать вам лишь ограниченный набор теоретических и практических знаний, с которых вы сможете начать свой путь. Какую бы область вы ни изучали, она гораздо шире и глубже любого курса.</p>
<p>Как можно раньше начните изучать источники информации по интересующей вас теме. Подпишитесь на новости технологий, следите за наиболее яркими представителями профессии, читайте книги, общайтесь с сообществом. Это позволит вам понять всю широту интересной вам области знаний.</p>
<p>Кроме того это поможет найти действительно интересные для вас аспекты профессии и сосредоточиться на них. Например, я разработчик, но особенно люблю делать вау-сайты для всяких конкурсов и перформансов. Мой знакомый композитор пишет любую музыку, но особенно любит писать саундтреки для кино.</p>
<p>Конкретная специализация позволит вам, пусть и не сразу, найти интересную конкретно вам нишу. Чем специфичнее ваши знания, тем меньше у вас конкурентов на рынке труда. Да, вакансий тоже будет меньше, но соревноваться за их получение вы будете с небольшим количеством людей.</p>
<p>Широкий кругозор пригодится вам на собеседованиях. Даже если вы не будете знать конкретный ответ на поставленный вопрос вы сможете порассуждать на основании имеющихся у вас знаний. Умением думать ценится работодателями гораздо выше, чем зазубренные ответы. А рассуждать и размышлять гораздо проще, оперируя широкими знаниями.</p>
<p>Проще говоря не теряйте природную любознательность. Интересуейтесь всем, что окружает вашу будущую профессию.</p>
<h3>Основы</h3>
<p>Программирование в целом и фронтенд в частности — очень стремительно развивающаяся область. Регулярно появляются какие-то новые фреймворки, технологии, меняются спецификации. В этом хаосе студенту очень легко запутаться и свернуть не туда.</p>
<p>Исходя из своего опыта советую сосредоточиться на хорошем, качественном освоении базовых, основных знаний. Всех хайповых штук не выучить, жизни не хватит. А вот качественная база позволит вам быстро и легко разбираться в любой новой технологии.</p>
<p>Не поддавайтесь на клик-бейты и пёстрые названия, углубитесь в основы. Не пожалеете.</p>
<h2>Критическое мышление</h2>
<p>Не забывайте подвергать всё сомнению, пробовать всё, о чём узнаёте. Держите в голове, что курсы составляются, пишутся и читаются живыми людьми. Например, лектор оговорился на вебинаре, вы запомнили эту информацию, а потом оказалось, что там ошибка. На перестройку уже созданной нейронной цепи потребуется время и силы. Так что сомневайтесь!</p>
<p>Зачем всё время жить в ожидании подставы? На самом деле это называется <em>критическим мышлением</em> и поможет вам не раз и не два.</p>
<blockquote>
<p>Критическое мышление — способность человека ставить под сомнение поступающую информацию, включая собственные убеждения.
<a href="https://ru.wikipedia.org/wiki/%D0%9A%D1%80%D0%B8%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5_%D0%BC%D1%8B%D1%88%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5">Википедия</a></p>
</blockquote>
<p>Представьте, что вы уже устроились разработчиком. К вам приходит менеджер и говорит «У нас есть штука, она работает так-то. К ней надо дописать такой-то кусок». Вы сразу берётесь за задачу, реализуете решение, а при интеграции оказывается, что изначальная штука работает совсем иначе. В итоге вы вынуждены переписывать свой код, потерям время. Если бы вы сразу поставили под сомнения слова менеджера и проверили точно ли та вещь, о которой он говорит, работает описанным образом, то гораздо раньше пришли бы к верному решению.</p>
<p>В рамках курса советую вам к каждому теоретическому блоку собирать простые демки в песочницах и проверять всё на живых примерах. Кроме более чёткого понимания темы этот подход позволит быстрее находить места, в которых вы перестаёте понимать материал и более точно формулировать вопросы к преподавателям или наставникам.</p>
<h2>Материальный мир</h2>
<h3>Время</h3>
<p>Предположим, что с целью вы определились и мотивация у вас прямо зашкаливает. Настало время распланировать своё обучение. Многие онлайн-курсы бравируют тем, что обучаться можно в любое время, когда захотите, хоть лёжа на диване по воскресениям.</p>
<p>Спешу огорчать вас, что это так не работает. Если вы учились в универе, то помните, что пары всегда были по расписанию. Такое же расписание вам нужно будет составить для себя.</p>
<p>Да, я понимаю, что большинство из вас работающие люди с семьями — основная аудитория курсов. Но без расписания не будет соблюдаться темп обучения, мозг будет искать лазейки чтобы ничего не делать, не напрягаться, впитывая новые знания. Он вообще довольно ленивый и хитрый орган.</p>
<p>Кроме того выделенное время позволит вам двигаться в нужном темпе, усваивать информацию порциями, не срывать сроки сдачи практических работ, готовиться к предстоящим лекциям.</p>
<p>Расписание может быть любым, в зависимости от ваших временных возможностей. Главное чтобы оно было. Можете полчаса каждый день после работы? Ок, запланируйте эти полчаса. Можете четыре часа в субботу и воскресенье? Ок, пусть будет так. Важны регулярность и постоянство.</p>
<h3>Обстановка</h3>
<p>В предыдущей части «<a href="https://web-standards.ru/articles/online-education-1/#section-18">Онлайн обучение: взгляд очевидца</a>» я упоминала, что существуют санитарные нормы, регламентирующие температуру, освещение, влажность в учебных классах. В случае с онлайн-обучением контроль всех аспектов ложится на вас. И поверьте, не стоит ими пренебрегать.</p>
<p>Понимаю, что у каждого свои условия, но постарайтесь максимально организовать своё пространство для комфортного обучения. Лучше всего учиться сидя за удобным столом на удобном стуле. Обеспечьте хорошее освещение. Если ведёте конспект, то помните, что лампа должна быть с противоположной стороны от ведущей руки, чтобы на записи не падала тень.</p>
<p>Помещение должно хорошо проветриваться. Желательно следить за уровнем влажности, этот совет полезен не только для обучения, но и для жизни в целом.</p>
<p>Идеально, если никто вас не будет беспокоить, не будет никаких отвлекающих факторов, посторонних звуков. Но тут уж как повезёт. В крайнем случае купите хорошие наушники, желательно с шумоподавлением и попросите близких вас не отвлекать.</p>
<p><img src="https://web-standards.ru/articles/online-education-2/images/2.png" alt="Рисованный мужчина сидит в тёмной комнате напротив окна за компьютером в галстуке и пиджаке, но без брюк."></p>
<h3>Физкультура</h3>
<p>Я более чем уверена, что большинство из вас в школе или в универе сачковали и не очень любили физкультуру. А очень зря! <a href="https://onlinelibrary.wiley.com/doi/10.1002/tsm2.190">Доказана</a> прямая связь между физической активностью и улучшением памяти, снижением уровня тревожности и другими классными штуками, улучшающими качество жизни. Всё это особенно важно в период активного усвоения знаний.</p>
<p>Я не призываю вас готовиться к Iron Man, бегать марафоны или ежедневно тягать железо. Обычной зарядки или прогулки будет вполне достаточно. Наши предки миллионы лет бегали по саваннам, охотились, собирали плоды с деревьев и просто много ходили. Но из-за глобализации, цифровизации и прочих визаций мы сидим часами, а то и днями. Мозг не получает достаточно кислорода и питательных веществ. Я уже молчу про другие неприятные сайдэффекты для остального организма.</p>
<p>Не пренебрегайте физической активностью хотя бы в период обучения. А там, может, и в привычку войдёт =)</p>
<p>«Руки в стороны, ноги на ширину плеч. Раз, два, три, четыре!»</p>
<h3>Портфолио</h3>
<p>Перефразируя поговорку: «Готовь сани летом, а портфолио на курсах». Если вы учитесь прямо сейчас, посмотрите сколько студентов в вашей группе. Умножьте это количество примерно на 25 потоков за год. Вычтите примерно 20% тех, кто бросит (очень оптимистично). И вы получите большое количество людей, постоянно выходящих на рынок труда с одинаковыми дипломами и с одинаковым набором навыков.</p>
<p>Чтобы повысить свою конкурентоспособность, я советую вам задуматься о портфолио с самого начала обучения и постоянно пополнять его. Причём пополнять не только учебными работами. Так сделают все ваши однокурсники. Тут мы возвращаемся к необходимости дополнительной практики.</p>
<p>Чем больше в вашем портфолио будет проектов, отличающихся от того, что работодатель видит ежедневно в резюме ваших сокурсников, тем выше ваши шансы на отклик.</p>
<h2>Заключение</h2>
<p>Помните, что кажущаяся простота и доступность знаний — обман и иллюзия. Обучение — тяжёлый и часто изнуряющий труд. Вы несёте ответственность за полученный результат.</p>
<p>Мне очень хочется верить, что мои советы помогут вам в процессе обучения и вы сможете достичь
поставленных целей.</p>

                    ]]></description><pubDate>Thu, 25 Nov 2021 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/online-education-2/</guid></item><item><title>Доступный компонент «из коробки»</title><link>https://web-standards.ru/articles/a11y-from-scratch/</link><description><![CDATA[
                        <p>Недавно коллеги попросили меня собрать небольшой чек-лист создания доступного компонента «из коробки». Постарался преобразовать свой опыт в простое руководство, следуя которому проще не переделывать компонент, когда будет нужно чинить недоступное, а сразу делать хорошо.</p>
<h2>Зачем мне делать что-то доступным?</h2>
<p>У вас идеальное зрение? У вас всегда оно будет таким?</p>
<p>Ломали ли вы когда-нибудь руку? Или, может, получали вывих? Возможно, по неосторожности порезали палец? Удобно было пользоваться интернетом одной рукой?</p>
<p>Ломалась ли у вас мышка? Или тачпад? Представьте, что у вас сломался тачпад, при этом вам срочно надо найти сервис для его ремонта, но телефон разряжен, а поисковик сделан так, что с клавиатуры вы не можете перейти по ссылке. Удачи и терпения!</p>
<p>Мы, разработчики, всё ещё не умеем считать упущенную выгоду. Как на одной из конференций сказал <a href="http://adpopko.ru/">Анатолий Попко</a> (незрячий эксперт в сфере невизуальной доступности), он готов тратить деньги в различных сервисах, чтобы вести свой бизнес. Но многие сервисы почему-то не хотят принимать его деньги, не давая пользоваться функциями полноценно.</p>
<p>Делая удобные интерфейсы для людей с особыми возможностями, вы делаете очень удобные интерфейсы для самих себя.</p>
<h2>Как люди могут пользоваться вашим сайтом?</h2>
<p>Есть много способов пользоваться сайтами помимо классического сёрфинга по интернету с монитором, мышкой и клавиатурой.</p>
<ul>
<li><em>Со скринридером —</em> сайт озвучивается. Полезно незрячим людям.</li>
<li><em>С клавиатуры —</em> когда есть возможность пользоваться навигацией без мышки, кнопками <kbd>Tab</kbd>, <kbd>Enter</kbd>, <kbd>Space</kbd> и стрелками. Полезно людям с ограничениями опорно-двигательного аппарата и тем, кто не хочет тянуться к мышке.</li>
<li><em>Мышкой —</em> когда нет возможности пользоваться клавиатурой.</li>
<li><em>С включённым зумом —</em> потому что ослабленное зрение.</li>
<li><em>С включённым контрастом —</em> потому что изначальный контраст не подходит. Полезно людям с цветовой слепотой.</li>
<li><em>С выключенной анимацией —</em> потому что мельтешение вредит. Полезно людям с эпилепсией.</li>
<li><em>Левой рукой —</em> потому что так удобнее. Полезно левшам.</li>
</ul>
<h2>Как сразу создавать доступные компоненты?</h2>
<ol>
<li>
<p><strong>Делайте по готовым инструкциям,</strong> не мудрите. До вас уже многое придумано, и придумано хорошо. На Smashing Magazine есть <a href="https://www.smashingmagazine.com/2021/03/complete-guide-accessible-front-end-components/">отличный гайд по теме</a>.</p>
</li>
<li>
<p><strong>Верстайте семантично —</strong> тогда скринридер сможет большую часть задач решить сам, без сложностей со стороны разработки.</p>
</li>
<li>
<p><strong>Используйте <a href="https://www.w3.org/WAI/standards-guidelines/aria/">ARIA</a>,</strong> если семантики не хватает. Но сначала всё же постарайтесь справиться в пункте № 2.</p>
</li>
<li>
<p><strong>Мелкие кликабельные области — плохо.</strong> Попробуйте своим пальцем попасть на тачах в мелкую кнопку, чтобы закрыть какой-нибудь попап, перекрывающий контент. Бесит? Придерживайтесь минимума в 44 пикселя для меньшей стороны интерактивного блока — этого обычно достаточно для пальца.</p>
</li>
<li>
<p><strong>Внутри ссылок <code>&lt;a&gt;</code> должен быть текст,</strong> иначе скринридер постарается прочитать <code>href</code> ссылки. Вряд ли у вас все адреса на странице красивые и понятные. Нет текста по дизайну — добавьте <code>&lt;span class=&quot;visibility-hidden&quot;&gt;</code>. А лучше пообщайтесь с дизайнером, договоритесь с ним, какой можно добавить текст.</p>
</li>
<li>
<p><strong>Не путайте кнопки и ссылки:</strong> переход в восстанавливаемое по URL состояние? Это ссылка <code>&lt;a&gt;</code>. Нет перехода? Кнопка <code>&lt;button&gt;</code>.</p>
</li>
<li>
<p><strong>Не используйте только иконки для подписи интерактивных элементов.</strong> Пусть это будет текст, не только иконка. Добавьте скрытый <code>span</code> или примените <code>aria-label</code>. По иконке и зрячие не всегда поймут, что хотел сказать автор. Текст универсален для носителей языка.</p>
</li>
<li>
<p><strong>Подписывайте картинки.</strong> Люди без зрения тоже хотят понимать, что там. Не можете подписать — поставьте пустой <code>alt=&quot;&quot;</code>, иначе скринридер постарается прочитать URL картинки. Если у вас настроена раздача статики с CDN, то адреса картинок будут не самые красивые и понятные.</p>
</li>
<li>
<p><strong>Не прячьте визуальный фокус.</strong> Если нужно спрятать «некрасивый» дефолтный фокус, оформите кастомный. Объясните дизайнеру, что прятать нельзя. Если он против, попросите его добавить <code>cursor: none</code> в его графический редактор для большей наглядности проблемы.</p>
</li>
<li>
<p><strong>Захватывайте фокус в модальных окнах.</strong> Фокус не должен проваливаться за пределы модалки.</p>
</li>
<li>
<p><strong>Переносите фокус при открытии модального окна или всплывашки внутрь.</strong> Так удобнее, можно сразу начать навигироваться по полезному контенту, а не искать его при помощи клавиши <kbd>Tab</kbd>.</p>
</li>
<li>
<p><strong>Верните фокус обратно к кнопке,</strong> которая открыла модальное окно, после того, как оно закроется.</p>
</li>
<li>
<p><strong>Цвета должны различаться</strong> сильно, если они играют роль. Лучше помимо цвета использовать ещё и иконки, подчёркивания, отличающийся текст. Зелёный цвет для обозначения успеха и красный цвет для ошибки некоторые видят как <em>одинаковый</em> для <em>непонятно-чего.</em> К тому же, если ваш сайт активно использует красный цвет как брендовый, он не будет выглядеть как состояние ошибки.</p>
</li>
<li>
<p><strong>Для видео используйте субтитры,</strong> которые не вшиты в видео. Вшитые субтитры невозможно перевести и прочитать скринридером.</p>
</li>
<li>
<p><strong>Используйте медиафичи <code>prefers-*</code>,</strong> которые подхватывают настройки операционной системы. Например, <code>prefers-reduced-motion</code> или <code>prefers-contrast</code>. Если пользователь поставил такую настройку, у него есть на это причины.</p>
</li>
<li>
<p><strong>Не используйте мелкий шрифт,</strong> он бесит даже людей с идеальным зрением. Я, например, мелкий шрифт не вижу. Дизайнер говорит, что так надо? Попросите его выставить в графическом редакторе <code>font-size: 5px !important</code>, чтобы лучше продемонстрировать проблему.</p>
</li>
<li>
<p><strong>Не переусердствуйте.</strong> Делать идеальную доступность нет смысла, пока не работают основные сценарии. Нужно делать хорошо и дать возможность пользоваться важными сценариями сайта. Когда кнопка «Купить» в интернет-магазине не работает, идеальные подписи к картинкам бессмысленны.</p>
</li>
<li>
<p><strong>Используйте правильные тексты.</strong> Не пишите на кнопке «Кнопка», роль элемента и так читается скринридером. Он умеет зачитывать роли для списков, кнопок, ссылок, навигации и других элементов.</p>
</li>
</ol>
<h2>Дополнительные материалы</h2>
<ul>
<li><a href="https://youtu.be/RQiN1Hhrxu0">Как незрячий пользуется iPhone, MacBook и Apple Watch</a>.</li>
<li><a href="https://habr.com/ru/company/linka/blog/346238/">Пандус для сайта</a>.</li>
</ul>

                    ]]></description><pubDate>Thu, 18 Nov 2021 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/a11y-from-scratch/</guid></item><item><title>Пути в кастомных свойствах. История одного бага</title><link>https://web-standards.ru/articles/url-and-custom-properties/</link><description><![CDATA[
                        <p><a href="https://web-standards.ru/podcast/302/">В недавнем выпуске «Веб-стандартов»</a> обсуждались релиз-ноуты Safari TP 133, среди которых оказался интересный фикс. О нём рассказал Никита Дубко:</p>
<blockquote>
<p>Беда была в том, что CSS-переменные работали не так: если вы в CSS-переменную задаёте URL, то он резолвился относительно HTML-документа. Оказывается, эта история связана с тем, как работают CSS-переменные. В Safari починили, или скорее поменяли это поведение, и теперь CSS-переменные точно так же резолвятся относительно URL стилевой таблицы — вполне себе ожидаемая штука.</p>
</blockquote>
<p>Поведение действительно поменяли, но если сказать точнее — скорее исправили ошибку, ведь в других браузерах всё работало по-другому, согласно спецификации. А ещё всё намного сложнее, чем кажется. Но начнём с начала.</p>
<h2>История фикса</h2>
<p>Несколько месяцев назад я узнал (не без помощи Руслана Берендеева), что в WebKit функция <code>url()</code> в кастомных свойствах резолвится не так, как ожидается, в отличие от движков Gecko (Firefox) и Blink (Chrome). Я нашёл уже давно заведённый <a href="https://bugs.webkit.org/show_bug.cgi?id=200092">баг 200092</a> — в нём была тишина, которую я и не надеялся расшевелить. Оставил этот баг на несколько месяцев, пока в него не заглянул Вадим Макеев. И, оказывается, там отвечают 😮</p>
<p>Тогда я решил посмотреть, как теперь дела обстоят с этим багом, и с удивлением обнаружил, что в заброшенной ветке сломанные пути починились, но почему-то не везде. Разница в коде небольшая, но существенная. Вдобавок оказалось, что надо учесть ещё одну особенность, шлейфом тянущуюся из уже исправленного на тот момент <a href="https://bugs.webkit.org/show_bug.cgi?id=198512">бага 198512</a>, из которого в уже знакомый мне баг пришли Вадим и разработчик WebKit Дарин Адлер. Обе эти особенности кода обозначу ниже.</p>
<p>Для прояснения ситуации я решил собрать тест, максимально совпадающий с описанием из <a href="https://drafts.csswg.org/css-variables-1/#syntax">заметки в спецификации</a>, на которую сослался Дарин.</p>
<blockquote>
<p>Например, относительные URL-адреса в CSS сопоставляются с базовым URL-адресом таблицы стилей, в которой объявляется значение. Однако, если кастомное свойство, к примеру, <code>--my-image: url(foo.jpg)</code> объявляется в таблице стилей <em>/a/style.css,</em> оно не будет немедленно преобразовано в абсолютный URL-адрес; если эта переменная позже будет использоваться, например <code>background: var(--my-image)</code>, в другой таблице стилей <em>/b/style.css,</em> то она преобразуется в путь <em>/b/foo.jpg.</em></p>
</blockquote>
<p>Я добавил к описанному сценарию все возможные варианты, на основе тех особенностей. Снабдил тест описанием и таблицей результатов. И представил его в комментаниях к багу, надеясь, что это прояснит всю ситуацию.</p>
<p>Но оказалось, что я заметку понял как описание того, как это <strong>должно</strong> работать, а Дарин — как <strong>не должно</strong>. В итоге, до выяснения правильного поведения, ни о каких правках в коде не могло быть и речи. Подход правильный, надо было разобраться. Понимая, что со своим так-себе английским могу не замечать какого-то нюанса в формулировке, я стал просить помощи известных мне гуру спецификации. И таки помог «человек-спека» — Илья Стрельцын. Он заметил, что вообще-то этот баг про <a href="https://drafts.css-houdini.org/css-properties-values-api/#the-registerproperty-function">регистрируемые кастомные свойства</a>, которые:</p>
<blockquote>
<p>Передаются не как токены, а уже как обработанные значения.</p>
</blockquote>
<p>Кажется это вывело из тупика, оживилось обсуждение, в результате которого, чтобы не менять этот, Дарин открыл новый <a href="https://bugs.webkit.org/show_bug.cgi?id=230243">баг 230243</a>, про взаимоотношения URL уже с обычными кастомными свойствами. Исправил и закрыл. Дело за отгрузкой этого исправления в Safari.</p>
<h2>Тест работы путей с кастомными свойствами</h2>
<p>То была история. А для понимания всех тонкостей текущей проблемы работы путей с кастомными свойствами легче всего разобраться с довольно простым устройством теста.</p>
<p>Обычное (не регистрируемое) кастомное свойство объявляется в стилевом файле в одной директории <em>/a/style.css:</em></p>
<pre><code tabindex="0" class="language-css">.test {
    --my-image: url('foo.svg');
}
</code></pre>
<p>А используется оно в другой <code>/b/style.css</code>:</p>
<pre><code tabindex="0" class="language-css">.test {
    background: var(--my-image);
}
</code></pre>
<p>Этот вариант я назвал <strong>inside,</strong> где <code>url()</code> находится <em>внутри</em> кастомного свойства.</p>
<p>Но Дарин с Вадимом пришли из бага, пример кода в котором можно назвать <strong>beside,</strong> где <code>url()</code> не в кастомном свойстве, а просто <em>рядом</em>, через пробел от <code>var()</code> с кастомным свойством, в котором может быть вообще любое значение, например цвет:</p>
<pre><code tabindex="0" class="language-css">.test {
    background: url('foo.svg') var(--my-color);
}
</code></pre>
<p>И раз уж возможно такое, то нельзя исключать и вариант <strong>outside,</strong> где <code>var()</code> находится от <code>url()</code> вообще <em>в соседнем</em> (через запятую) значении множественного значения.</p>
<pre><code tabindex="0" class="language-css">.test {
    background:
        url('foo.svg'),
        linear-gradient(
            var(--my-color),
            var(--my-color)
        );
}
</code></pre>
<p>Но и это ещё не всё. Упомянутое ранее небольшое, но существенное, отличие в коде, которое ломало разрешение пути — это всего лишь форма свойства, в значении которого применяется путь. Представленные выше варианты используют сокращённую форму свойства, и все их можно назвать <strong>shorthand</strong>. Для <em>beside</em> возможен только такой вариант. Но остальные сценарии возможны и с отдельными (не сокращёнными) формами свойства — <strong>longhand</strong>. В таком случае <em>inside</em> выглядит так:</p>
<pre><code tabindex="0" class="language-css">.test {
    background-image: var(--my-image);
}
</code></pre>
<p>Вариант <em>outside</em> выглядит вот так:</p>
<pre><code tabindex="0" class="language-css">.test {
    background-image:
        url('foo.svg'),
        linear-gradient(
            var(--my-color),
            var(--my-color)
        );
}
</code></pre>
<p>Во всех случаях <code>url()</code> содержит путь до файла без переходов по директориям <code>url('foo.svg')</code>. И во всех случаях он должен резолвиться в путь <em>/b/foo.svg,</em> по которому находится изображение:</p>
<img src="https://web-standards.ru/articles/url-and-custom-properties/demo/b/foo.svg" width="100" height="100" alt="Успех! :3">
<p>Для <em>inside</em> стоило предусмотреть разрешение пути по месту объявления кастомного свойства, то есть в путь <em>/a/foo.svg,</em> поэтому там я разместил изображение:</p>
<img src="https://web-standards.ru/articles/url-and-custom-properties/demo/a/foo.svg" width="100" height="100" alt="Не та папка">
<p>И остаётся последнее из возможных разрешений пути — рядом с HTML-страницей <em>/foo.svg:</em></p>
<img src="https://web-standards.ru/articles/url-and-custom-properties/demo/foo.svg" width="100" height="100" alt="Провал :(">
<p>Вся структура проекта выглядит так:</p>
<pre><code tabindex="0">├─ a
│  ├─ foo.svg
│  └─ style.css
├─ b
│  ├─ foo.svg
│  └─ style.css
├─ foo.svg
└─ index.html
</code></pre>
<p>А вот и сам тест во фрейме или, если удобно, <a href="https://web-standards.ru/articles/url-and-custom-properties/demo/">на отдельной странице</a>.</p>
<iframe title="Таблица с результатами тестов" src="https://web-standards.ru/articles/url-and-custom-properties/demo/" width="296" height="216"></iframe>
<p>До 14-й версии в Safari все пять сценариев теста фэйлились. Начиная с 14.1 — все варианты теста с <em>shorthand</em> стали проходить успешно (видимо это и был фикс <a href="https://bugs.webkit.org/show_bug.cgi?id=198512">бага 198512</a>), но оба <em>longhand</em> — фейлятся.</p>
<p>Напомню, что этот тест касается только обычных кастомных свойств. Если ещё не запутались в этом всём, то попробуйте угадать, в какое изображение должен резолвиться путь в каждом из сценариев, если обычное кастомное свойство заменить регистрируемым.</p>
<p>Думаю, если повнимательнее присмотреться, например, к коду сценария <em>longhand + outside,</em> то станет понятно, что разрешение пути до <em>/foo.svg</em> ну никак не может считаться нормальным поведением движка браузера. Это очевидная ошибка. Поэтому всё-таки «починили», а не «поменяли» поведение.</p>
<p>Остаётся лишь дождаться стабильного релиза с этой починкой и с полным <em>успехом</em> в тесте. А также надеяться, что с путями в регистрируемых кастомных свойствах будет всё хорошо. А пока…</p>
<h2>Заключение</h2>
<p>Напоследок пара советов:</p>
<p>Если вам понадобится заиспользовать <code>url()</code> хотя бы где-то рядом (внутри одного свойства) с использованием кастомного свойства, и тем более <code>url()</code> внутри кастомного свойства — используйте для этого пока, до релиза фикса, сокращённые свойства <code>background</code> и <code>mask</code>, а не отдельные <code>background-image</code> и <code>mask-image</code>.</p>
<p>Не бойтесь писать об ошибках в баг-трекеры браузеров. Написал бы я сразу, а не месяцы спустя, может уже фикс был бы в стабильном релизе. И не просто ждите, когда больной для вас баг вылечат — помогите разработчикам хоть чем-нибудь, информации подробной дайте, или тест соберите (даже ошибочность релизноутов <a href="https://bugs.webkit.org/show_bug.cgi?id=230243#c13">обнаружили</a>, прогоняя этот мой тест).</p>

                    ]]></description><pubDate>Tue, 09 Nov 2021 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/url-and-custom-properties/</guid></item><item><title>Веб и устройства: баланс между пользой, безопасностью и приватностью</title><link>https://web-standards.ru/articles/hardware-and-web/</link><description><![CDATA[
                        <p>Около года назад, <a href="https://webkit.org/tracking-prevention/">Apple опубликовала список API для веба</a>, которые они не собираются внедрять в Safari из соображений приватности и безопасности. Они беспокоились, что эти API позволят создавать цифровой след пользователей и отслеживать их. И это конечно же большое табу с точки зрения современной приватности. Звучит разумно, верно?</p>
<p>Что за API оказались в том списке? Среди прочих, аппаратные API, которые появились в браузерах Chrome и Edge за последние пару лет:</p>
<ul>
<li>WebBluetooth</li>
<li>WebHID</li>
<li>WebMidi</li>
<li>WebNFC</li>
<li>WebSerial</li>
<li>WebUSB</li>
</ul>
<p>Звучит ну прямо очень опасно, да?</p>
<p>Я понимаю некоторые сомнения по поводу появления этих возможностей в вебе. Они мощные и поэтому кажутся потенциально опасными. Никто не хочет, чтобы случайный сайт захватил контроль над устройствами в вашем доме, доставлял вредоносный код, шпионил за вами или чего хуже. Я понимаю эти сомнения.</p>
<p>Но нам следует взглянуть на фактическую опасность, рассмотреть её должным образом и не верить всяким «нутром чую» или страшилкам из того эпизода «Чёрного зеркала», что мы однажды видели…</p>
<p>Реальность такова, что для каждой возможности, что предлагает веб-платформа, существует <strong>баланс между пользой, безопасностью и приватностью.</strong> И эти API определённо полезны. И, что более важно, они также относительно безопасны. Я думаю, что добавление этих API в веб-платформу даже увеличит безопасность пользователей.</p>
<p>Любой API, потенциально влияющий на безопасность и приватность, не может использоваться на сайте без согласия пользователя. Такой сайт должен запросить разрешение у пользователя, чтобы использовать веб-камеру — аналогично и для использование Bluetooth-устройства или USB-устройства. И разрешение получает не API целиком — оно даётся отдельно взятому устройству. Сайт не знает, какие есть устройства, и не может получить их список. Сайт может только попросить разрешение для доступа к определённому типу устройств. Он его либо получит, либо нет.</p>
<p>Пользователи всегда контролируют происходящее, и их действие необходимо, чтобы разрешить доступ к определённому устройству.</p>
<h2>Проблемы с безопасностью</h2>
<p>Главная проблема здесь не техническая, проблема в социальной инженерии. Вы можете вынудить пользователя сделать что-то, что ему навредит.</p>
<p>Например, вставьте на сайт вредоносный код. Он сообщит пользователю о якобы-проблеме и подскажет как подключиться к устройству, чтобы установить драйверы, которые скомпрометируют устройство или смогут извлечь ценную информацию с устройства каким-то другим образом. Для такого вам всегда придётся обмануть пользователя — ведь мы поняли, что пользователю придётся вручную выбрать устройство, прежде чем вредоносный код сможет получить к нему доступ.</p>
<p>Я бы рад сказать, что это невозможно, но, к сожалению, это существующая проблема. Социальная инженерия — это общая проблема, которая не ограничивается лишь использованием API.</p>
<p>Но даже если это теоретически возможно, то кажется маловероятным для использования в больших масштабах.</p>
<p>Во-первых, необходимо найти уязвимое устройство, которое принимает драйверы или код, не подписанный производителем. У большинства устройств такой проблемы не будет, но не у всех. Во-вторых, у пользователей, на которых направлена атака, должно быть это устройство. У большинства, скорее всего, его не окажется. И, в-третьих, ещё нужно обмануть пользователей, чтобы они выбрали это устройство. Скорее всего, большинство засомневаются и откажутся. И наконец, запрашивать доступ к устройству — довольно подозрительное дело, которое легко заметить. Я не думаю, что такого рода атаку можно будет поддерживать длительное время.</p>
<p>Но когда речь заходит о достаточно большой группе пользователей, число устройств, которые можно скомпрометировать, не будет равно нулю.</p>
<p>Есть простые действия, которые производители браузеров уже используют для дальнейшего уменьшения такого риска:</p>
<ul>
<li>Блокировка доступа к отдельным устройствам, у которых известны уязвимости.</li>
<li>Запрет доступа к целому классу устройств с проблемами.</li>
<li>Удалённое прекращение работы API, когда его активно используют для эксплуатации уязвимости в устройстве.</li>
</ul>
<p>Несмотря на это, риск всё ещё не нулевой.</p>
<p>Насколько мне известно, удалённое выключение API использовалось лишь однажды. И не потому что API активно эксплуатировалось, а потому что исследователи безопасности сообщили об уязвимости. Тем не менее, этого хватило, чтобы применить «рубильник».</p>
<p>В 2018 году оказалось возможным <a href="https://www.yubico.com/support/security-advisories/ysa-2018-02/">извлечь ключ из устройства двухфакторной авторизации Yubico U2F</a>, используя WebUSB в обход проверки происхождения запроса, которую обычно делают браузеры. После того, как это стало известно, Google сразу отключил WebUSB и следом выпустил обновление, которое снова включало WebUSB, но добавляло все устройства Yubico в чёрный список.</p>
<p>Мне кажется, что причина, по которой мы не видели подобных атак, состоит в том, что намного проще загрузить нативное приложение и выполнить его на устройстве пользователя, чем найти тот самый вариант один-из-миллиона, в котором сойдутся все составляющее: доверчивый пользователь и уязвимое устройство. Проще говоря, <strong>игра не стоит свеч.</strong></p>
<p>Не только Google думает, что эти API достаточно безопасны, чтобы предлагать их реальным пользователям. Так же думают Microsoft и Samsung. В действительности, <strong>эти API поставляются на 70% мобильных браузеров и 78% десктопных браузеров по всему миру.</strong> И поставляются уже достаточно давно.</p>
<p>Но я думаю, что настоящий безопасный выбор лежит не между браузерами с API и браузерами без API.</p>
<p>Для специфичной задачи, которую пытается решить пользователь, обычно стоит выбор между браузером и нативным приложением. Выбор между ограниченным API, построенным вокруг пользовательского согласия в изолированной среде, и нативным приложением, которое может делать что угодно, без проверок на приватность или безопасность. Вы бы загрузили какое-то сомнительное приложение, созданное неизвестным разработчиком из магазина приложений, которое делает кто-знает-что с вашими данными в фоновом режиме?</p>
<p>Ограничивая доступ к API устройств нативными приложениями, вы заставляете людей пользоваться нативными приложениями для таких задач. И это плохо для безопасности.</p>
<p><strong>Самое опасное, что умеют браузеры —</strong> это не доступ к API устройств. Это возможность <strong>ссылаться на нативные приложения и загружать их.</strong></p>
<p>Именно эта способность активнее всего используется сейчас и с самого первого момента, когда браузер попал в руки широкой публики. И время доказало, что нет почти ничего, что могло бы снизить риски.</p>
<h2>Приватность и цифровой след</h2>
<p>Что насчёт цифрового следа? Возможно ли проследить цифровой след пользователя, используя эти API?</p>
<p>По правде говоря, в этом нет много смысла. Использование цифрового следа в качестве аргумента против этих API всего лишь демонстрирует нехватку понимания, как эти API вообще работают.</p>
<p>Цель создания цифрового следа — идентифицировать уникального пользователя. Этого можно добиться, используя уникальные опорные точки для больших групп пользователей. Каждый набор таких точек не может идентифицировать конкретного пользователя, но его можно отследить, если у вас будет достаточно опорных точек, чтобы их скомбинировать.</p>
<p>Такие точки могут быть ничем не примечательны сами по себе с точки зрения приватности. Это могут быть даже вещи, которые по иронии были предназначены для улучшения приватности. Например, HTTP-заголовок «Do Not Track». Он выключен по умолчанию, поэтому это идеальная опорная точка для отслеживания пользователей. Например, если у вас есть набор из 1 миллиона пользователей и один из сотни включил этот заголовок. Теперь вы уже можете наблюдать за 1 из 10 000 пользователей вместо одного из миллиона. Теперь попробуйте скомбинировать это с ещё хотя бы 30 типами вполне невинных опорных точек и вы можете отслеживать конкретных пользователей на разных сайтах — святой грааль отслеживания пользователя.</p>
<p>HTTP-заголовок «Do Not Track» был удалён из Safari как раз из соображений выше. Ну и правильно.</p>
<p>Вопрос вот в чём: добавляют ли эти API опорные точки для отслеживания конечных пользователей?</p>
<p>Теоретически, да. Простая поддержка таких API уже может считаться опорной точкой. Но на практике — нет. Возьмём, например, WebBluetooth. Он был доступен во всех версиях Chrome, начиная с 56-й. Поэтому единственное, о чём это говорит, так это то, старше ли Chrome 56-й версии или нет. И эту информацию можно добыть, просто заглянув в строку <code>User-Agent</code>, которая всегда есть в HTTP-запросе. Поэтому эти точки выглядят крайне бесполезными для создания цифрового следа.</p>
<p>Для цифрового следа нужны API, дающее разные результаты на разных устройствах. Например, установленные шрифты или GL-расширения, поддерживаемые графической картой. Эти параметры могут отличаться на устройствах с одинаковыми версиями браузеров. Такие опорные точки добавляют энтропии в цифровой след.</p>
<p>И что же эти API? Дают ли они какую-то дополнительную информацию для цифрового следа? Если браузер знает, какие USB-устройства подключены к вашей ОС или какие Bluetooth-устройства доступны поблизости, я бы сказал: «да, конечно». <strong>Но эти API работают не так</strong>. Их проектировали, зная о цифровом следе, поэтому они не могут использоваться для этого напрямую.</p>
<p>Возьмём для примера снова WebBluetooth как представителя аппаратных API.</p>
<p>Давайте проговорим это ещё раз. <strong>Вы не можете получить список устройств поблизости.</strong> Так нельзя сделать ни с помощью WebBluetooth, ни с помощью каких-либо других API устройства. Эта информация не доступна сайтам. Вы можете не переживать, что сайты увидят ваши устройства и смогут идентифицировать вас через них. Потому что они не смогут.</p>
<p>Что сайты смогут сделать, так это сказать браузеру, с какими устройствами они бы хотели взаимодействовать. Обычно вы даёте API набор фильтров, основанных либо на имени устройства, либо на его сервисах. И тогда вы просите у браузера разрешение подключиться к устройству.</p>
<p>Затем браузер показывает попап, спрашивающий разрешение. В нём перечислен список устройств, соответствующий фильтрам, которые вы запросили. Этот список доступен только самому пользователю, ни один скрипт на сайте его не видит. Дальше пользователь может дать доступ к определённому устройству или отказать в доступе в целом.</p>
<p>Этот попап устроен так, что он бесполезен для создания цифрового следа, поскольку это должно происходить в фоне, незаметно для пользователя. Также это делает опорные точки ненадёжными, ведь нет гарантии, что пользователь каждый раз разрешит доступ.</p>
<p><strong>API устройств просто не подходят для создания цифрового следа.</strong> Они слишком ненадёжны и очевидны, когда используются.</p>
<h2>Так что там с Safari…</h2>
<p>Вполне нормально сомневаться в безопасности аппаратных API. Лично я думаю, что риски относительно малы и с ними можно справиться. Нет ничего плохого в том, чтобы дискутировать об этом и не соглашаться. Но использование цифрового следа и отслеживания в качестве аргументов говорит о том, что у вас нет достаточной информации по вопросу.</p>
<p>Я даже не против, если Safari не будет внедрять эти возможности. Каждый производитель браузеров должен самостоятельно оценивать баланс между пользой, безопасностью, приватностью, и решать, стоит ли оно того. Apple подумала и решила, что не стоит. Я считаю иначе, но это ничего.</p>
<p>Я уверен, что они игнорируют опасность перевода пользователей на нативные приложения, но понимаю, почему для Apple это не выглядит так.</p>
<p>Но я против последствий этого выбора. Из-за того, что <a href="https://nielsleenheer.com/articles/2021/chrome-is-the-new-safari-and-so-are-edge-and-firefox/">Safari — единственный настоящий браузер на iOS</a>, пользователи не могут выбрать другой браузер, поддерживающий аппаратные API. Это большая проблема. Выбор Safari сказывается не только на самом Safari, но и на всех браузерах на этой крайне важной платформе. И это сказывается на мне, как на пользователе.</p>

                    ]]></description><pubDate>Thu, 28 Oct 2021 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/hardware-and-web/</guid></item><item><title>Подборка ссылок для знакомства с доступной разработкой</title><link>https://web-standards.ru/articles/a11y-links/</link><description><![CDATA[
                        <p>Когда изучаешь новую тему, сложно сразу понять, с чего начать. Особенно, если это активно развивающиеся технологии и подходы. Например, веб-доступность.</p>
<p>Здесь собраны ссылки на ресурсы по доступности, которые пригодятся веб-разработчикам. Они помогут сориентироваться в море информации в начале погружения в эту тему.</p>
<p>Отбирала их по нескольким критериям:</p>
<ul>
<li>о них часто упоминают другие разработчики;</li>
<li>в текстах есть важная или актуальная информация;</li>
<li>эти источники помогли мне самой в изучении доступности.</li>
</ul>
<p>Постаралась собрать здесь и ресурсы на русском языке, но пока их больше на английском. Как и с другими веб-технологиями, многие специалисты по доступности из англоговорящих стран.</p>
<h2>Навигация</h2>
<ul>
<li><a href="https://web-standards.ru/articles/a11y-links/#section-2">Основы основ</a>
<ul>
<li><a href="https://web-standards.ru/articles/a11y-links/#section-3">Про веб-доступность в общем</a></li>
<li><a href="https://web-standards.ru/articles/a11y-links/#section-9">Гайдлайны и стандарты</a></li>
<li><a href="https://web-standards.ru/articles/a11y-links/#section-13">Accessibility API</a></li>
<li><a href="https://web-standards.ru/articles/a11y-links/#section-16">Вспомогательные технологии</a></li>
</ul>
</li>
<li><a href="https://web-standards.ru/articles/a11y-links/#section-17">Пользователи</a></li>
<li><a href="https://web-standards.ru/articles/a11y-links/#section-18">Законы</a></li>
<li><a href="https://web-standards.ru/articles/a11y-links/#section-19">Чеклисты</a></li>
<li><a href="https://web-standards.ru/articles/a11y-links/#section-20">Пишем доступный код</a>
<ul>
<li><a href="https://web-standards.ru/articles/a11y-links/#section-21">Официальные руководства W3C</a></li>
<li><a href="https://web-standards.ru/articles/a11y-links/#section-22">Другие руководства</a></li>
<li><a href="https://web-standards.ru/articles/a11y-links/#section-23">Библиотеки доступных компонентов</a></li>
<li><a href="https://web-standards.ru/articles/a11y-links/#section-24">Доступные CSS и JavaScript</a></li>
</ul>
</li>
<li><a href="https://web-standards.ru/articles/a11y-links/#section-25">Дизайн</a>
<ul>
<li><a href="https://web-standards.ru/articles/a11y-links/#section-26">Инклюзивный дизайн</a></li>
<li><a href="https://web-standards.ru/articles/a11y-links/#section-27">Советы по дизайну</a></li>
</ul>
</li>
<li><a href="https://web-standards.ru/articles/a11y-links/#section-28">Тестирование</a></li>
<li><a href="https://web-standards.ru/articles/a11y-links/#section-29">Курсы</a></li>
<li><a href="https://web-standards.ru/articles/a11y-links/#section-30">Быть в курсе</a>
<ul>
<li><a href="https://web-standards.ru/articles/a11y-links/#section-31">Сообщества</a></li>
<li><a href="https://web-standards.ru/articles/a11y-links/#section-32">Подкасты и стримы</a></li>
<li><a href="https://web-standards.ru/articles/a11y-links/#section-33">Конференции и митапы</a></li>
<li><a href="https://web-standards.ru/articles/a11y-links/#section-34">Персональные блоги</a></li>
<li><a href="https://web-standards.ru/articles/a11y-links/#section-35">Рассылка</a></li>
</ul>
</li>
<li><a href="https://web-standards.ru/articles/a11y-links/#section-36">Другие подборки ссылок</a></li>
</ul>
<h2>Основы основ</h2>
<h3>Про веб-доступность в общем</h3>
<h4>Ресурсы WAI W3C</h4>
<p>Один из главных двигателей доступности — W3C WAI (W3C Web Accessibility Initiative). Это специальная рабочая группа W3C. У неё много базовых материалов по этой теме. Например, <a href="https://www.w3.org/WAI/fundamentals/">обзор основ доступности</a>. В нём разобраны основные понятия, категории пользователей, которым критически важна доступность, как они пользуются сайтами, принципы доступности и другие важные вещи.</p>
<h4>MDN</h4>
<p>На MDN есть отдельный <a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility">раздел про доступность</a> и <a href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/What_is_accessibility">вводное руководство</a>. Многие материалы переведены на русский.</p>
<h4>Google Web Fundamentals</h4>
<p>Google тоже писала <a href="https://developers.google.com/web/fundamentals/accessibility">вводные статьи про доступность</a>. Например, о важности семантической вёрстки, про доступные стили и управление фокусом.</p>
<h4>WebAIM</h4>
<p>Это некоммерческая американская организация при Университете штата Юта. Проводит аудит компаний, сертификацию и обучает другие команды доступности.</p>
<p>На её сайте тоже много <a href="https://webaim.org/articles/">базовых материалов</a>. Ещё WebAIM каждый год проводит <a href="https://webaim.org/projects/">исследования пользователей с особыми потребностями</a>. Например, из них можно узнать о распространённых проблемах в вебе или рейтинге скринридеров и операционных систем.</p>
<h4>A Little Book of Accessibility</h4>
<p><a href="https://www.ab11y.com/articles/a-little-book-of-accessibility/">Сборник советов и принципов доступности</a>, которые повлияли на стратегию BBC.</p>
<h3>Гайдлайны и стандарты</h3>
<p>У W3C WAI есть <a href="https://www.w3.org/WAI/standards-guidelines/">краткий обзор стандартов по доступности</a>. Самые важные — спецификация HTML, WCAG и WAI ARIA.</p>
<h4>HTML</h4>
<p>Во многих статьях, постах и руководствах встретите идею о том, что доступность начинается с семантики. А где самая полная информация об HTML-тегах и атрибутах? Конечно в <a href="https://html.spec.whatwg.org/multipage/">стандарте HTML</a>.</p>
<p>Не призываю читать её от начала до конца, но туда стоит заглядывать в любой непонятной ситуации с разметкой.</p>
<p>Другие полезные ссылки:</p>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/HTML">HTML: A good basis for accessibility</a> с MDN.</li>
<li><a href="https://madebymike.github.io/html5-periodic-table/">Периодическая таблица HTML-тегов</a>.</li>
<li><a href="http://html5doctor.com">HTML5 Doctor</a> — на сайте есть список всех HTML-элементов и их краткое описание, а ещё статьи о хороших практиках.</li>
<li><a href="https://www.html5accessibility.com">HTML5 Accessibility</a> — доступность фич HTML5 в популярных браузерах.</li>
<li><a href="https://www.htmhell.dev">HTMHell</a> — проект Мануэля Матузовича, который собирает примеры плохой и хорошей разметки из реальных проектов.</li>
</ul>
<h4>WCAG</h4>
<p>WCAG (Web Content Accessibility Guidelines) — руководства по обеспечению доступности веб-контента. Содержат основные рекомендации о том, как сделать любой цифровой продукт доступным.</p>
<p>Может показаться, что это слишком сложно для начала. Но, поверьте, большинство статей и других гайдлайнов ссылается на WCAG, а автоматические инструменты генерируют отчёты на основе их критериев успешности.</p>
<p>У руководств несколько версий:</p>
<ul>
<li><a href="https://www.w3.org/TR/WCAG21/">WCAG 2.1</a> — актуальна на момент выхода статьи, есть <a href="https://ifap.ru/ictdis/wcag.htm">перевод на русский</a>.</li>
<li><a href="https://www.w3.org/TR/WCAG22/">WCAG 2.2</a> — будет принята до конца 2021.</li>
</ul>
<p>Ещё несколько полезных ресурсов:</p>
<ul>
<li><a href="https://www.w3.org/WAI/WCAG21/quickref/">How to Meet WCAG</a> — справочник по критериям успешности и техникам WCAG. Поможет разобраться, что конкретно от вас хотят в руководствах.</li>
<li><a href="https://guia-wcag.com/en/">WCAG Guide</a> — наглядные карточки с критериями успешности.</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/Understanding_WCAG">Understanding the Web Content Accessibility Guidelines</a> — руководство на MDN.</li>
</ul>
<h4>WAI ARIA</h4>
<p>WAI-ARIA (WAI Accessible Rich Internet Applications Suite) — рекомендации, которые описывают вспомогательные техники создания более доступного контента для скринридеров (программ чтения с экрана). Расширяет возможности HTML с помощью специальных атрибутов и ролей. В 2021 действует <a href="https://www.w3.org/TR/wai-aria-1.1/">WAI-ARIA 1.1</a>.</p>
<p>Дополнительные ссылки:</p>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA">Обзор WAI-ARIA на MDN</a>.</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/WAI-ARIA_basics">Руководство по основам ARIA на MDN</a>.</li>
<li><a href="https://dylanb.github.io/periodic-aria11-roles.html">Периодическая таблица ролей ARIA 1.1</a>.</li>
<li><a href="https://www.deque.com/blog/aria-spec-for-the-uninitiated-part-1/">ARIA Spec for the Uninitiated</a> — серия статей в блоге Deque, <a href="https://web-standards.ru/articles/five-rules-of-aria/">есть перевод первой части</a>.</li>
</ul>
<p>Небольшая справка: Deque — американская компания, которая проводит аудиты, разрабатывает инструменты для тестирования доступности и занимается обучением.</p>
<h3>Accessibility API</h3>
<p>Для большего понимания того, что происходит под капотом у браузеров, полезно копнуть чуть глубже в сторону Accessibility API.</p>
<p>Больше всего технических подробностей содержится в рекомендациях <a href="https://www.w3.org/TR/core-aam-1.1/">W3C Core Accessibility API Mappings 1.1</a>.</p>
<h4>Дерево доступности</h4>
<p>Дерево доступности (accessibility tree) похоже на DOM-дерево, только вместо HTML-элементов в него попадают их доступные роли и имена. Именно с ним взаимодействуют скринридеры, когда им нужно прочитать контент страницы.</p>
<ul>
<li><a href="https://developers.google.com/web/fundamentals/accessibility/semantics-builtin/the-accessibility-tree">The Accessibility Tree</a> — вводная статья Google.</li>
<li><a href="https://www.smashingmagazine.com/2015/03/web-accessibility-with-accessibility-api/">Accessibility APIs: A Key To Web Accessibility</a> — одна из самых подробных и популярных статей с разбором того, как работает доступность в браузерах.</li>
<li><a href="https://medium.com/p/5a0a93931397">Accessibility API и доступность</a> — статья на русском, в которой тоже разбирается дерево доступности.</li>
<li><a href="https://russmaxdesign.github.io/html-elements-names/">Список доступных имён и ролей HTML-элементов в дереве доступности Chrome</a>.</li>
</ul>
<h4>AOM</h4>
<p>Если интересно, то дополнительно можно почитать про AOM (Accessibility Object Model, Объектная модель доступности). Она похожа на DOM, только нужна для доступа JavaScript к дереву доступности. Пока что это экспериментальный JavaScript API.</p>
<p>Информацию из первых рук найдёте в <a href="https://wicg.github.io/aom/">репозитории рабочей группы</a>, которая разрабатывает этот API.</p>
<h3>Вспомогательные технологии</h3>
<p>Это аппаратное или программное обеспечение, которое упрощает взаимодействие пользователей с контентом. Например, экранные лупы, увеличивающие размер интерфейса, выносные кнопки, специальные мышки или скринридеры.</p>
<ul>
<li><a href="https://www.nidcd.nih.gov/health/assistive-devices-people-hearing-voice-speech-or-language-disorders">Список и обзор вспомогательных устройств для людей с глухотой и особенностями речи</a> на сайте американской NIDCD (The National Institute on Deafness and Other Communication Disorders).</li>
<li><a href="https://www.afb.org/blindness-and-low-vision/using-technology/assistive-technology-products">Список вспомогательных технологий для людей со слепотой и сниженным зрением</a> на сайте AFB (The American Foundation for the Blind).</li>
</ul>
<p>В веб-доступности много внимания уделяется скринридерам. Это одна из самых популярных вспомогательных технологий, которой пользуются люди со слепотой, сниженным зрением и, на самом деле, не только они. Из статьи «<a href="https://axesslab.com/what-is-a-screen-reader/">What is a screen reader?</a>» можно больше про них узнать.</p>
<p>У скринридеров есть особенности поддержки HTM-тегов, атрибутов и ARIA. Ссылки по этой теме собраны дальше в разделе про тестирование.</p>
<h2>Пользователи</h2>
<p>Полезно не только знать о доступности в теории, но и понимать, каким пользователям она нужна больше всего. Это поможет лучше разобраться в барьерах и том, как именно они мешают реальным людям.</p>
<ul>
<li><a href="https://www.microsoft.com/design/inclusive/#inclusivethefilm">Inclusive design in action</a> — фильм из гайда Microsoft.</li>
<li><a href="https://www.sberbank.ru/common/img/uploaded/redirected/person/digital_guideline2/assets/designer.html#users">Раздел о пользователях из гайда по доступности Сбербанка</a>.</li>
<li><a href="https://www.w3.org/WAI/perspective-videos/">Web Accessibility Perspectives Videos</a> — видео с пользователями в разных ситуациях на сайте W3C.</li>
<li><a href="https://axesslab.com/tech-youtubers/">Videos of people with disabilities using tech</a> — ещё одна подборка видео про пользователей вспомогательных технологий на Axess Lab.</li>
</ul>
<h2>Законы</h2>
<p>В какой-то момент на вас начнут со всех сторон наступать странные слова и аббревиатуры. ADA, Section 508, EN 301 549. Это всё связано с законами, которые регулируют доступность.</p>
<ul>
<li><a href="https://en.wikipedia.org/wiki/Web_accessibility#Web_accessibility_legislation">Раздел про законы в статье о доступности на Википедии</a> — краткий разбор законодательства о доступности в разных странах.</li>
<li><a href="https://www.w3.org/WAI/policies/">Web Accessibility Laws &amp; Policies</a> — краткая информация W3C WAI о законах некоторых стран, которые регулируют доступность.</li>
<li><a href="https://web-standards.ru/articles/a11y-and-law/">Доступность и закон</a> — статья на «Веб-стандартах» с обзором основных европейских, американских и российских законов в области доступности. Кстати, в 2020 в России был принят новый <a href="https://docs.cntd.ru/document/1200167693">ГОСТ Р 52872-2019</a>.</li>
</ul>
<h2>Чек-листы</h2>
<p>Они пригодятся для тестирования, аудита доступности или для быстрой проверки требований. Только помните, что такие списки не заменяют WCAG. Так что не полагайтесь только на них.</p>
<p>Самые популярные чек-листы:</p>
<ul>
<li><a href="https://www.a11yproject.com/checklist/">Чек-лист The A11Y Project.</a></li>
<li><a href="https://webaim.org/standards/wcag/checklist">Чек-лист WCAG 2 WebAIM</a>.</li>
<li><a href="https://dequeuniversity.com/checklists/web/">Чек-листы Deque</a>.</li>
</ul>
<h2>Пишем доступный код</h2>
<h3>Официальные руководства W3C</h3>
<ul>
<li><a href="https://www.w3.org/WAI/roles/developers/">Обзор полезных ресурсов для разработчиков</a> — базовые советы и подсказки по доступной разработке.</li>
<li><a href="https://www.w3.org/WAI/tutorials/">Web Accessibility Tutorials</a> — больше подробностей про то, как сделать доступные меню, изображения, таблицы, формы, карусели и структуру страниц.</li>
<li><a href="https://www.w3.org/TR/wai-aria-practices-1.1/examples/">Index of ARIA Design Pattern Examples</a> — примеры правильного использования ARIA в компонентах.</li>
</ul>
<h3>Другие руководства</h3>
<ul>
<li><a href="https://www.youtube.com/playlist?list=PLNYkxOF6rcICWx0C9LVWWVqvHlYJyqw7g">A11ycasts</a> — серия коротких видео Роба Додсона из Google про основы технической доступности.</li>
<li><a href="https://www.smashingmagazine.com/2021/03/complete-guide-accessible-front-end-components/">A Complete Guide To Accessible Front-End Components</a> — серия коротких постов на Smashing Magazine. Разобраны полезные техники и инструменты для создания доступных компонентов.</li>
<li><a href="https://accessibility.digital.gov/front-end/getting-started/">Accessibility for front-end developers</a> — небольшой чек-лист в гайде американской Администрация общих служб с базовыми советами для разработчиков.</li>
<li><a href="https://web.dev/accessible/">Accessible to all</a> — советы по доступной разработке на web.dev.</li>
<li><a href="https://www.sberbank.ru/common/img/uploaded/redirected/person/digital_guideline2/assets/dev_web.html">Гайдлайн Сбербанка о доступности для веб-разработчиков</a>.</li>
<li><a href="https://weblind.ru">Веблайнд</a> — рекомендации на русском о доступной разработке для людей с нарушениями зрения.</li>
<li><a href="https://dev.to/inhuofficial/101-digital-accessibility-tips-and-tricks-4728">101 Digital Accessibility (a11y) tips and tricks</a> — много практических советов о доступной разработке.</li>
<li><a href="https://inclusive-components.design">Inclusive Components</a> — серия статей Хейдона Пикеринга. Подробный разбор того, как правильно сверстать и задизайнить карточки, переключатели тем, тултипы, слайдеры, оповещения, таблицы, выпадающие секции, вкладки, меню и списки дел. На «Веб-стандартах» есть <a href="https://web-standards.ru/articles/tags/a11y/">переводы части статей</a>.</li>
<li><a href="https://www.w3schools.com/accessibility/">Руководство по доступности на W3Schools</a> — краткие советы о доступной разметке. Например, про ориентиры, заголовки, ссылки, кнопки и формы.</li>
</ul>
<h3>Библиотеки доступных компонентов</h3>
<ul>
<li><a href="https://a11y-style-guide.com/style-guide/">A11Y Style Guide</a> — опенсорсная библиотека доступных паттернов с примерами разметки и демками доступных карточек, форм, навигации, заголовков, списков, ссылок «Читать дальше».</li>
<li><a href="https://dequeuniversity.com/library/">Библиотека кода Deque</a> — примеры доступных компонентов. Например, вкладок, слайдеров, каруселей и чекбоксов. Пока в бете.</li>
<li><a href="https://design-system.service.gov.uk/components/">Компоненты из дизайн-системы GOV.UK</a>.</li>
<li><a href="https://design-system-alpha.digital.govt.nz/components/">Компоненты из дизайн-системы правительства Новой Зеландии</a> — можно выбрать формат сниппетов, например: HTML, Handlebars, React JavaScript или TypeScript.</li>
<li><a href="https://nostyle.herokuapp.com/components">Компоненты из проекта «No Style Design System»</a> — дизайн-система из <a href="https://www.smashingmagazine.com/printed-books/form-design-patterns/">книги «Form Design Patterns»</a> Адама Сильвера. Можно посмотреть, как сделать доступно автозаполнение, поле для пароля с возможностью его показа или выбор даты.</li>
<li><a href="https://github.com/scottaohara/accessible_components">Accessible Components</a> — репозиторий Скотта О’Хары на GitHub. Есть аккордеон, вкладки, переключатели, тултип, модальное окно, хлебные крошки, формы.</li>
<li><a href="https://vuetensils.stegosource.com/introduction.html">Vuetensils</a> — библиотека доступных Vue-компонентов.</li>
<li><a href="https://chakra-ui.com/docs/getting-started">Chakra</a> — библиотека доступных React-компонентов.</li>
<li><a href="https://material.angular.io">Angular Material</a> — доступные Angular-компоненты от Google.</li>
</ul>
<h3>Доступные CSS и JavaScript</h3>
<p>В основном в руководствах, гайдах и библиотеках компонентов упор делается на HTML. Это логично, но CSS и JS тоже могут влиять на доступность.</p>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/CSS_and_JavaScript">CSS and JavaScript accessibility best practices</a> — руководство на MDN.</li>
<li><a href="https://benmyers.dev/blog/css-can-influence-screenreaders/">CSS Can Influence Screenreaders</a> — Бен Майерс про все возможные ситуации, когда CSS может повлиять на скринридеры.</li>
<li><a href="https://uselessdivs.com/blog/the-effect-of-css-on-screen-readers">The effect of CSS on screen readers</a> — дополнение к посту Бена Майерса, которое сверяет: изменилась ли в 2021 году ситуация с CSS и доступностью.</li>
<li><a href="https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/">Modern CSS Upgrades To Improve Accessibility</a> — как CSS помогает улучшить доступность.</li>
</ul>
<h2>Дизайн</h2>
<p>Всегда можно правильно сверстать компонент, но без хорошего дизайна он не будет доступным. В этом разделе найдёте несколько ссылок о доступности в дизайне.</p>
<h3>Инклюзивный дизайн</h3>
<p>Это практика проектирования продуктов или услуг таким образом, чтобы ими могло пользоваться как можно больше людей.</p>
<ul>
<li><a href="https://www.w3.org/WAI/fundamentals/accessibility-usability-inclusion/">Accessibility, Usability, and Inclusion</a> — W3C про разницу между доступностью, юзабилити и инклюзией.</li>
<li><a href="https://inclusivedesignprinciples.org">Inclusive Design Principles</a> — общие принципы инклюзивного веб-дизайна.</li>
<li><a href="https://www.microsoft.com/design/inclusive/">Гайд по инклюзивному дизайну Microsoft</a>.</li>
</ul>
<h3>Советы по дизайну</h3>
<ul>
<li><a href="https://accessibility.blog.gov.uk/2016/09/02/dos-and-donts-on-designing-for-accessibility/">Постеры про доступность</a> — проект команды по доступности сайта британского правительства GOV.UK. Проиллюстрированы советы о том, как можно и как нельзя дизайнить для людей со сниженным зрением, глухотой, расстройствами аутистического спектра, дислексией, моторными и физическими особенностями и для пользователей скринридеров.</li>
<li><a href="https://www.smashingmagazine.com/category/design-patterns/">Раздел с дизайн-паттернами на Smashing Magazine</a> — статьи про паттерны инклюзивного дизайна: кнопки, слайдеры, аккордеоны, фильтры и много чего ещё.</li>
<li><a href="https://designpatternsformentalhealth.org">The Mental Health Patterns Library</a> — библиотека доступных принципов и паттернов для людей с особенностями ментального здоровья.</li>
<li><a href="https://colorblindaccessibilitymanifesto.com">Манифест о доступности для людей с цветовой слепотой</a> — базовые правила для доступного интерфейса с точки зрения цвета.</li>
<li><a href="https://www.w3.org/TR/coga-usable/">Making Content Usable for People with Cognitive and Learning Disabilities</a> — рабочие замени специальной группы Coga TF (Cognitive and Learning Disabilities Accessibility Task Force) из W3C о том, как сделать интерфейс доступным для людей с когнитивными особенностями.</li>
<li><a href="https://teletype.in/@romanshamin/a11y-for-designers">Доступность в дизайне</a> — гайдлайн Романа Шамина, арт-директора Evil Martians, с подробностями о том, как создать доступную дизайн-систему, <a href="https://evilmartians.com/chronicles/accessible-design-from-the-get-go">есть перевод на английский</a>.</li>
</ul>
<h2>Тестирование</h2>
<p>Тестирование тоже важная часть доступной разработки, о которой полезно иметь общее представление.</p>
<ul>
<li><a href="https://sheribyrnehaber.medium.com/accessibility-best-practices-for-screenreader-testing-e35c5df9cecb">Accessibility best practices for screenreader testing</a> — Шери Бирн-Хабер с кратким описанием основных принципов тестировании доступности.</li>
<li><a href="https://www.w3.org/WAI/ER/tools/">Web Accessibility Evaluation Tools List</a> — список W3C WAI со всеми одобренными ей инструментами для оценки доступности.</li>
<li><a href="https://www.smashingmagazine.com/2021/06/complete-guide-accessibility-tooling/">A Complete Guide To Accessibility Tooling</a> — Ник Чан разбирает самые популярные инструменты для автоматического тестирования, проверки контрастности, линтеры и другие полезные тестерские штуки.</li>
<li><a href="http://a11ysupport.io">Accessibility Support</a> — аналог Can I Use в мире доступности. Можно проверить поддержку ARIA-атрибутов и ролей, а также HTML-элементов и атрибутов в разных скринридерах.</li>
<li><a href="https://www.powermapper.com/tests/screen-readers/elements/">База поддержки HTML-элементов PowerMapper Software</a>, также на сайте есть базы по атрибутам, ARIA и WCAG.</li>
<li><a href="https://bbc.github.io/accessibility-news-and-you/assistive-technology/testing.html">Гайдлайн BBC по тестированию в скринридерах</a> — поэтапное описание, как проводить тестирование скринридеров на разных платформах.</li>
<li><a href="https://dequeuniversity.com/screenreaders/">Шорткаты и жесты в скринридерах</a> — есть JAWS, NVDA, Narrator, VoiceOver и TalkBack.</li>
</ul>
<h2>Курсы</h2>
<p>Здесь собрала только пару популярных курсов, о которых часто упоминают. Больше ссылок на платные и бесплатные англоязычные курсы найдёте на <a href="https://www.digitala11y.com/digital-accessibility-courses-roundup/">Digital a11y</a>.</p>
<ul>
<li><a href="https://www.edx.org/course/web-accessibility-introduction">Introduction to Web Accessibility</a> — курс W3C, есть возможность пройти бесплатно с некоторыми ограничениями.</li>
<li><a href="https://www.udacity.com/course/web-accessibility--ud891">Бесплатный курс по доступности от Google</a>.</li>
<li><a href="https://accessibilityunity.com">Платный курс про цифровую доступность Валерии Курмак</a>.</li>
</ul>
<h2>Быть в курсе</h2>
<p>Если хотите быть в курсе событий из мира доступности и узнать об этом ещё больше, то вам могут помочь сообщества, подкасты, конференции, митапы и блоги.</p>
<h3>Сообщества</h3>
<ul>
<li><a href="https://css-tricks.com/tag/accessibility/">Статьи и посты о доступности на CSS-Tricks</a>.</li>
<li><a href="https://www.smashingmagazine.com/category/accessibility/">Категория «Accessibility» на Smashing Magazine</a>.</li>
<li><a href="https://www.a11yproject.com/posts/">Блог The A11Y Project</a> — опенсорсный проект, посвящённый всем аспектам веб-доступности.</li>
<li><a href="https://dev.to/t/a11y">Посты по тегу «a11y» на DEV Community</a>.</li>
<li><a href="https://web-standards.ru/articles/tags/a11y/">Статьи «Веб-стандартов» по тегу «a11y»</a> — авторские статьи и переводы, да вы и сами знаете.</li>
<li><a href="https://habr.com/ru/hub/accessibility/">Хаб про доступность на Хабре</a> — авторские посты и переводы статей про доступную разработку.</li>
</ul>
<h3>Подкасты и стримы</h3>
<ul>
<li><a href="https://a11yrules.com">A11y Rules Podcast</a>.</li>
<li><a href="https://www.bemyeyes.com/podcasts-show/13-letters">13 Letters</a> — подкаст об универсальном дизайне, законодательстве и лучших практиках цифровой доступности со специалистами из разных стран.</li>
<li><a href="https://web-standards.ru/podcast/">Подскаст «Веб-стандарты»</a> — не концентрируется только на доступной разработке, но новости и события из мира доступности обсуждаются почти в каждом выпуске.</li>
<li><a href="https://www.youtube.com/c/accessibilitytalks">Accessibility Talks</a> — стримы о доступности.</li>
<li><a href="https://www.twitch.tv/SomeAnticsDev">Стримы Some Antics на Twitch</a> и <a href="https://www.youtube.com/playlist?list=PLZluKlEc91YzYor_ItAax4d2iXTXbFAFF">их записи на YouTube</a> — проект Бена Майерса о доступной разработке в формате дискуссий с разработчиками, тестировщиками, дизайнерами и всеми, кто занимается доступностью.</li>
<li><a href="https://technica11y.org/">Technica11y</a> — стримы про проблемы и хитрости, связанные с технической доступностью.</li>
<li><a href="https://www.a11yproject.com/resources/#podcasts">Список других подкастов на The A11Y Project</a>.</li>
</ul>
<h3>Конференции и митапы</h3>
<p>В мире проводится много конференций и митапов про доступность. Перечислю несколько мероприятий, которые можно посмотреть бесплатно онлайн.</p>
<ul>
<li><a href="https://inclusivedesign24.org/">Inclusive Design 24</a> — 24-часовая онлайн-конференция об инклюзивном дизайне и доступности.</li>
<li><a href="https://www.deque.com/axe-con/">Axe-con</a> — конференция Deque о доступности для разработчиков, дизайнеров, менеджеров и всех неравнодушных.</li>
<li><a href="https://conf.a11yto.com">#a11yTO Conf</a> — трёхдневная онлайн-конференция, состоящая из тщательно отобранного «плейлиста» из лекций, демо и лайтнингов.</li>
<li><a href="https://a11yminsk.space">Accessibility Club Minsk</a> — русскоязычный митап о доступности, часть международной сообщества Accessibility Club. <a href="https://www.youtube.com/channel/UCfNsXZX9SOvdcKX2o6tjVhw">Записи митапов на YouTube</a>.</li>
<li><a href="https://github.com/pitercss/a11y_docs">Pitera11y_meetup</a> — митап о доступности в Санкт-Петербурге. <a href="https://www.youtube.com/playlist?list=PLTdS5E3zupkGg0FoMoWB5FD2tlBrSWUQB">Плейлист записей докладов на YouTube</a>.</li>
<li><a href="https://www.a11yproject.com/resources/#groups-and-organizations">Список других событий</a> на The A11Y Project.</li>
</ul>
<h3>Персональные блоги</h3>
<p>Много технический деталей прячется в персональных блогах.</p>
<ul>
<li><a href="http://tink.uk">Tink</a> — блог Леони Уотсон, директора TetraLogical, члена W3C Advisory Board и BIMA Inclusive Design Council, а также сопредседателя W3C Web Applications Working Group.</li>
<li><a href="https://www.scottohara.me/writing/">Блог Скотта О’Хары</a>, бывшего дизайнера, который сейчас специализируется на доступной разработке и UX.</li>
<li><a href="https://benmyers.dev">Блог Бена Майерса</a>, веб-разработчика и адвоката доступности.</li>
<li><a href="https://accessabilly.com/">Accessabilly</a> — блог Мартина Менгеле, фронтенд-разработчика и консультанта по доступности.</li>
<li><a href="https://www.sarasoueidan.com/blog/">Блог Сары Суайдан</a>, разработчицы пользовательских интерфейсов и дизайн-систем, автора статей и спикера. Блог не посвящён доступности, но она часто становится темой статей.</li>
<li><a href="https://ericwbailey.design/writing/">Блог Эрика Бэйли</a>, адвоката доступности и инклюзивного дизайна, мэйнтейнера The A11Y Project.</li>
<li><a href="https://html5accessibility.com/stuff/category/htmlaccessibility/">HTML Accessibility</a> — блог Стива Фолкнера, технического директора TPGi, ведущего разработчика Web Accessibility Toolbar, члена W3C Web Platforms Working Group и W3C ARIA Working Group.</li>
<li><a href="https://adrianroselli.com/tag/accessibility">Блог Адриана Розелли</a>, консультанта, автора статей, спикера, члена нескольких рабочих групп W3C. Например, W3C HTML Accessibility Task Force и W3C Accessible Rich Internet Applications Working Group.</li>
</ul>
<p>Конечно, не обязательно начинать с этих блогов. Можете сами попробовать найти в Твиттере интересных вам разработчиков и следить за их блогами через RSS, рассылку или другим удобным способом.</p>
<h3>Рассылка</h3>
<p>Здесь собраны рассылки, на которые я подписана. Другие можете поискать у <a href="https://www.a11yproject.com/resources/#newsletters">The A11Y Project</a>.</p>
<ul>
<li><a href="https://a11yweekly.com">Accessibility Weekly</a> — еженедельная рассылка с полезными статьями, постами и новостями за неделю. Приходит по понедельникам.</li>
<li><a href="https://webaim.org/newsletter/">The WebAIM Newsletter</a> — ежемесячная рассылка.</li>
</ul>
<h2>Другие подборки ссылок</h2>
<ul>
<li><a href="https://www.getstark.co/library/">Public Library</a> — библиотека, собранная компанией Stark, которая занимается разработкой инструментов для проверки доступности.</li>
<li><a href="https://www.digitala11y.com/web-accessibility-resources/">Digital Accessibility Resources</a> на Digital A11y.</li>
<li><a href="https://www.a11yproject.com/resources/">Список ресурсов по доступности</a> на The A11Y Project.</li>
</ul>

                    ]]></description><pubDate>Fri, 01 Oct 2021 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/a11y-links/</guid></item><item><title>Как не дать текстовым глифам превратиться в эмоджи</title><link>https://web-standards.ru/articles/prevent-emoji/</link><description><![CDATA[
                        <p>В прошлом году передо мной стояла интересная задача: сделать символ сердечка частью заголовка страницы. Мы организовывали конференцию «Я Люблю Фронтенд», а слово «Люблю» для большей эмоциональности хотели заменить символом сердечка.</p>
<p>Казалось бы, какие могут быть проблемы?</p>
<p>Если у вас есть заголовок «Я ❤ Фронтенд», он рендерится по-разному в разных браузерах в разных операционных системах. Например, на Android и iOS сердечко выглядит как эмоджи. И это огромная проблема, если вам нужно этот заголовок стилизовать.</p>
<h2>Юникод</h2>
<p>Всё дело в рендеринге. Никита Прокопов описал этот нюанс в своей <a href="https://tonsky.me/blog/emoji/">подробной статье про эмоджи</a>.</p>
<p>Каждый символ, который вы используете, имеет свою UTF-8 последовательность, свой код. Например, английская буква A имеет код <code>U+0041</code>, буква Z — <code>U+005A</code>, символ $ — <code>U+0024</code> и так далее. То же касается и эмоджи: ❤ имеет код <code>U+2764</code>.</p>
<p>Вы можете вставлять символы в свой HTML как Юникод-последовательность, а не готовыми символами.</p>
<pre><code tabindex="0" class="language-html">&lt;span&gt;Я ❤ Фронтенд&lt;/span&gt;
или
&lt;span&gt;Я &amp;#x2764; Фронтенд&lt;/span&gt;
</code></pre>
<p><code>&amp;#x</code> — префикс для указания Юникод-последовательности.</p>
<p>Но что делать, если текущий указанный шрифт не содержит внутри себя глифа, который может отрендерить ваш UTF-8 символ? Операционная система пытается найти любой шрифт, который содержит такой глиф. Для iOS это будет Apple Color Emoji, для Android — Noto Color Emoji. Но, кажется, мы можем контролировать этот процесс.</p>
<h3>Шрифты</h3>
<p>Первое решение — иметь в наличии шрифт, который содержит все необходимые нам глифы. Например, Arial. Или какой-то кастомный шрифт. Вам нужно добавить его в фолбеки внутри CSS.</p>
<pre><code tabindex="0" class="language-css">body {
    font-family: 'Text Font', 'Custom Glyphs Font', sans-serif;
}
</code></pre>
<p>Но мне не нравятся подобные решения, потому что у меня обычно нет полного контроля над всеми глифами в тексте. Особенно если разработка подразумевает какую-то админку для наполнения контентом. Больно пересобирать шрифт каждый раз, когда мне понадобится новый глиф.</p>
<h3>Селекторы варианта начертания</h3>
<p>К счастью, есть другое решение. В UTF есть специальные символы для управления рендерингом — селекторы варианта начертания (англ. variation selectors). Символ <code>U+FE0E</code> просит ОС и браузер отрендерить предыдущий глиф в виде текста, а <code>U+FE0F</code> — в виде эмоджи.</p>
<p>Давайте попробуем.</p>
<pre><code tabindex="0" class="language-html">&lt;ul&gt;
    &lt;li&gt;&amp;#x2600; может отрендериться по-разному.&lt;/li&gt;
    &lt;li&gt;&amp;#x2600;&amp;#xFE0F; должен выглядеть как эмоджи солнца.&lt;/li&gt;
    &lt;li&gt;&amp;#x2600;&amp;#xFE0E; должен выглядеть как текстовый символ солнца.&lt;/li&gt;
&lt;/ul&gt;
</code></pre>
<ul>
    <li>&#x2600; может отрендериться по-разному.</li>
    <li>&#x2600;&#xFE0F; должен выглядеть как эмоджи солнца.</li>
    <li>&#x2600;&#xFE0E; должен выглядеть как текстовый символ солнца.</li>
</ul>
<p>Получается, если вам нужно выключить эмоджи в тексте, нужно сконвертировать выбранный символ в последовательность UTF-8 или UTF-16 (например, при помощи <code>symbol.codePointAt(0).toString(16)</code> в JS), потом добавить к этому символу <code>&amp;#xFE0E</code>.</p>
<pre><code tabindex="0" class="language-html">&lt;span&gt;Я &amp;#x2764;&amp;#xFE0E; Фронтенд&lt;/span&gt;
</code></pre>
<p>И это всё не работает для эмоджи сердечка на Android. Я не знаю, почему. Когда мы тестировали это в прошлом году, всё было хорошо, но… Вы можете поиграть с различными символами <a href="https://codepen.io/dark_mefody/pen/NWgMxrd">в песочнице</a>. Но как по мне, что-то лучше, чем ничего.</p>
<h2>Доступность</h2>
<p>Помните, что все эмоджи имеют своё текстовое представление. Например, ⭐︎ — это «белая средняя звезда», ► — «чёрная стрелка, указывающая вправо». И скринридеры будут использовать эти тексты.</p>
<p>В итоге наш заголовок «Я ❤ Фронтенд» зачитывается утилитой VoiceOver как «Я Красное сердце Фронтенд». Кажется, это не то, что мы хотели сказать.</p>
<p>Чтобы починить это, <a href="https://tink.uk/accessible-emoji/">используйте глифы правильно</a>.</p>
<pre><code tabindex="0" class="language-html">&lt;span role=&quot;img&quot; aria-label=&quot;Люблю&quot;&gt;
    &amp;#x2764;
&lt;/span&gt;
</code></pre>
<p>Итоговый результат, который мы использовали на сайте конференции, ниже.</p>
<pre><code tabindex="0" class="language-html">&lt;h1&gt;
    Я &lt;span role=&quot;img&quot; aria-label=&quot;Люблю&quot;&gt;
        &amp;#x2764;&amp;#xFE0E;
    &lt;/span&gt; Фронтенд!
&lt;/h1&gt;
</code></pre>
<h2>Источники</h2>
<ul>
<li><a href="https://tonsky.me/blog/emoji/">Emoji under the hood</a></li>
<li><a href="https://mts.io/2015/04/21/unicode-symbol-render-text-emoji/">Unicode symbol as text or emoji</a></li>
<li><a href="https://codepoints.net/U+FE0E">U+FE0E VARIATION SELECTOR-15</a></li>
<li><a href="https://tink.uk/accessible-emoji/">Accessible emoji</a></li>
</ul>

                    ]]></description><pubDate>Wed, 29 Sep 2021 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/prevent-emoji/</guid></item><item><title>Пять правил использования ARIA</title><link>https://web-standards.ru/articles/five-rules-of-aria/</link><description><![CDATA[
                        <p>Эта статья — вольный перевод оригинала. Я добавляю свои примеры и мысли, пишу как чувствую и ставлю смайлики :) Автор статьи сказал, что ему понравилась моя интерпретация! Джерард занимается доступностью в Twitter и попросил поделиться с вами ссылкой на его <a href="https://www.pluralsight.com/authors/gerard-cohen">курсы по доступности</a>. Курсы лаконичные и очень понятные, я их посмотрела по 10-дневной бесплатной подписке. Итак, к делу!</p>
<p><em>Глафира Жур.</em></p>
<h2>Опасности, которые таит в себе ARIA</h2>
<p><em><a href="https://www.deque.com/blog/aria-spec-for-the-uninitiated-part-1/">В оригинальной статье</a> введение более содержательное, так что почитайте его хотя бы ради практики английского языка :) Я передам только несколько тезисов — прим. переводчицы.</em></p>
<p><strong>ARIA —</strong> (Accessible Rich Internet Applications) это набор атрибутов, который позволяет сделать наши приложения более доступными, особенно если они написаны на JS. Из моего опыта — это, например, легаси-код, где давно-давно наверстали дивами, а теперь надо как-то добавить доступность, но нет доступа к HTML.</p>
<p>Когда вы узнаёте о существовании ARIA, вы начинаете везде использовать эти атрибуты, где бы только ни захотели. Например, у вас React-компонент (типичный, на дивах), и вам нужно, чтобы скринридер начал читать его правильно, поэтому вы добавляете компоненту роль, например, <code>role=&quot;button&quot;</code> и какие-то ещё важные ARIA-атрибуты (<code>aria-required</code>, <code>aria-disabled</code> и так далее), но забываете про клавиатуру. Ошибка тут в том, что вы мало что знаете про ARIA, поэтому вероятность упустить нечто важное крайне высока. Дальше выясняется, что некоторые ARIA-атрибуты могут не иметь поддержки на определённых платформах или сочетаниях браузер + скринридер.</p>
<p>Если вы хотите познакомиться с ARIA — начинайте <a href="https://www.w3.org/TR/wai-aria/">с официальных спецификаций</a>. Не нужно сразу читать их глубоко и полностью, но парочку секций лучше изучить. Например, самое важное, с чем следует ознакомиться в спеке — это <a href="https://www.w3.org/TR/wai-aria/#roles">понятие роли</a>.</p>
<p><strong>Роль —</strong> это семантика вашего элемента, подсказка для ассистивных технологий, что можно делать с этим элементом, как его обрабатывать, как с ним взаимодействует клавиатура и так далее.</p>
<p>В ассистивные технологии входят скринридеры, брайлевские клавиатуры, голосовые помощники и другие средства доступа к информации, помимо привычных клавиатур, экранов и мышей.</p>
<p><strong>Важно:</strong> роли (и вообще любые ARIA-атрибуты) не добавляют элементам никаких стилей и поведения!</p>
<p>У ролей есть классификация. Например, бывают роли для виджетов, для структуры документа, для лайв-областей, которые как-то обновляются на фоне независимо от действий пользователя и о чём-то ему сообщают. Практически для каждой роли есть свой набор обязательных ARIA-атрибутов. Некоторые роли нельзя использовать отдельно от родительских ролей.</p>
<p>Вот и пробежались по введению в статью :) Переходим к правилам!</p>
<h2>Пять правил использования ARIA</h2>
<p>Если вы уже понимаете, что вынуждены как-то накручивать доступность на ваши интерфейсы, нужно следовать нескольким основным правилам. Это облегчит принятие решений при разработке приложений и конкретно виджетов.</p>
<h3>Правило 1</h3>
<p><strong>Не используйте ARIA</strong></p>
<p>Читая это правило, я всегда очень радуюсь — так однозначно и бескомпромиссно оно звучит :) На практике смысл его в том, что сначала вы должны полностью положиться на HTML и использовать его семантику, и только тогда, когда вам не хватило или есть какой-то сложный составной случай (например, аккордеон или вкладки) — тогда подключайте ARIA. Также приходится использовать эти волшебные атрибуты в уже упомянутом мной легаси-коде, где нет доступа к HTML и можно только с помощью JS добавлять что-то к существующим тегам.</p>
<h3>Правило 2</h3>
<p><strong>Не изменяйте семантику нативных контролов.</strong></p>
<p>Вы уже начали писать чистый, красивый и семантичный HTML, но поняли, что везде используете таблицы для вывода простых списков. У вас есть возможность явно задать элементу роль, чтобы переопределить его семантику:</p>
<ul>
<li>была <code>table</code>, и скринридер говорил, что вы попали в таблицу, такой-то столбец, такая-то строка;</li>
<li>вы не решились перевёрстывать все места и просто добавили таблице <code>role=&quot;list&quot;</code>, её детям тоже расставили нужные роли;</li>
<li>скринридер теперь читает это как список.</li>
</ul>
<p>Так вот, не переопределяйте дефолтную семантику без острой необходимости! Лучше замените тег: <code>&lt;table&gt;</code> превратится в <code>&lt;ul&gt;</code> или <code>&lt;ol&gt;</code>)!</p>
<p>Когда мне <em>приходится</em> менять семантику:</p>
<ul>
<li>если нужно спрятать таблицы для раскладки от скринридера (чтобы он не читал, что это ячейка таблицы) — задаю <code>role=&quot;presentation&quot;</code> для таблицы;</li>
<li>если <code>&lt;svg&gt;</code> со значимой картинкой — могу задать <code>role=&quot;img&quot;</code>;</li>
<li>если это <code>&lt;div&gt;</code> в составе виджета и для него нет HTML-эквивалента (например, <code>tab</code>, <code>tablist</code>, <code>tabpanel</code>);</li>
<li>иногда бывают баги, например, есть <code>&lt;ul&gt;</code>, и мы стилями сбрасываем оформление списка <code>list-style: none</code>, что в Safari может вызвать удаление роли списка для скринридера. Один из лайфхаков в таком случае — снова явно определить семантику, задав <code>role=&quot;list&quot;</code> для <code>&lt;ul&gt;</code>;</li>
<li>и другие.</li>
</ul>
<p><strong>Важно:</strong> если вы сейчас явно задаете вашему тегу <code>role</code> (особенно если это не <code>&lt;div&gt;</code>) — вы скорее всего неправильно используете ARIA.</p>
<h3>Правило 3</h3>
<p><strong>Все интерактивные роли должны быть доступны с клавиатуры.</strong></p>
<p>Нативные контролы уже доступны с клавиатуры, нам не надо ничего делать. Но если у вас <code>&lt;div role=&quot;button&quot;&gt;</code>, то вы обязаны узнать, что ожидается от роли с точки зрения взаимодействия с клавиатурой и добавить это. В нашем случае с кнопкой — это активация по <kbd>Enter</kbd> и <kbd>Space</kbd>.</p>
<p>Для виджетов тоже существует свое определенное, уже привычное пользователю взаимодействие с клавиатурой: например, фокус зациклен в модальном окне и не выходит за его пределы, пока окно открыто.</p>
<p><strong>Напоминаю,</strong> роль или любой другой ARIA-атрибут не добавляют никакого поведения и стилей элементам, они просто сообщают ассистивным технологиям необходимую информацию о назначении, взаимодействии, состоянии.</p>
<h3>Правило 4</h3>
<p><strong>Не используйте <code>role=&quot;presentation&quot;</code> и <code>aria-hidden=&quot;true&quot;</code> на видимых фокусабельных элементах.</strong></p>
<p><code>role=&quot;presentation&quot;</code> — это особенная роль. Суть в том, что она полностью убирает любую семантику с элемента и с его прямых обязательных потомков. Например, если поставить <code>role=&quot;presentation&quot;</code> на <code>&lt;ul&gt;</code> — роль также слетит с <code>&lt;li&gt;</code>. Если задать ее интерактивному контролу — ассистивные технологии перестанут его воспринимать. Если на контрол было навешено какое-то поведение — оно сохранится, но пользователь скринридера никогда не узнает, что вообще можно сделать с этим элементом, ведь скринридер разве что зачитает текст внутри, если он там есть.</p>
<p><code>aria-hidden</code> — способ спрятать что-то от ассистивных технологий. Этот атрибут не меняет вида элемента и не убирает с него никакого поведения. Элемент по-прежнему рендерится на странице, но скринридер его больше не видит.</p>
<p><code>aria-hidden</code> не стоит просто так брать и использовать, чтобы скрыть контент от скринридера. Разработчики часто вставляют его <strong>везде</strong> (на видимых элементах, на невидимых), и получается, что у разных пользователей — разная информация: зрячий видит и читает текст, который скрыт «за ненадобностью» и недоступен незрячему. Это противоречит равному доступу к контенту и функциональности.</p>
<p><strong>Важно:</strong> не следует использовать <code>aria-hidden</code> на фокусабельных элементах, а также на контейнере, который содержит фокусабельные элементы, поскольку они остаются доступными с клавиатуры. Такие элементы нужно убирать из порядка фокуса. Один из способов — добавлять интерактивным контролам <code>tabindex=&quot;-1&quot;</code>. Подробнее о проблеме можно почитать в статье «<a href="https://web.dev/aria-hidden-focus/"><code>[aria-hidden=&quot;true&quot;]</code> elements contain focusable descendants</a>», а также посмотреть примеры ошибок <a href="https://dequeuniversity.com/rules/axe/3.3/aria-hidden-focus">в описании правила <code>aria-hidden-focus</code></a> инструмента axe-core. Про другие особенности <code>aria-hidden</code> — <a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-hidden_attribute">читайте на MDN</a>.</p>
<h3>Правило 5</h3>
<p><strong>Все интерактивные элементы обязаны иметь доступное имя.</strong></p>
<p>Любой пользователь должен понимать, что за контрол он собирается активировать. Для этого контрол уже содержит роль, то есть определен его тип: кнопка, поле ввода, интерактивное меню. Но типа недостаточно, нужно еще и понятное, лаконичное, уникальное имя.</p>
<p>Имя можно задавать разными способами в зависимости от ситуации (я знаю как минимум четыре), но важно, чтобы оно было в итоге получено ассистивными технологиями и передано пользователю.</p>
<p>Нельзя забывать, что люди могут использовать ваши интерфейсы тысячей разных способов. Например, в случае с лейблами, пользователь, который взаимодействует с интерфейсами речью (говорит устройству, что нужно сделать), полагается на видимое имя элемента — например, текст кнопки. Называет его, и кнопка нажимается.</p>
<p>Как в таком случае программно нажать кнопку-иконку, у которой нет имени? Нажмется ли кнопка, если её лейбл — это просто наложенный для красивого эффекта <code>&lt;div&gt;</code> с текстом?</p>
<h2>Заключение</h2>
<p>Вот и они, самые базовые, самые основные правила использования ARIA в интерфейсах. Даже такое базовое их понимание очень сильно улучшит ваше отношение к той работе, которую вы делаете.</p>
<p>Ну и конечно же помните, что нельзя просто так взять и накрутить доступность! Думайте о пользователях и его удобстве, помогайте им понять, что и как у вас работает, как им достичь цели захода на сайт: купить товар, заказать услугу, получить информацию, отправить данные.</p>
<p>И огромное спасибо, что интересуетесь доступностью! Вместе победим!</p>

                    ]]></description><pubDate>Mon, 27 Sep 2021 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/five-rules-of-aria/</guid></item><item><title>Дополнительные уровни заголовков: удачное решение или не очень?</title><link>https://web-standards.ru/articles/extra-heading-levels/</link><description><![CDATA[
                        <p>Сегодня я хотел бы затронуть очень важную вещь в доступности веб-контента — заголовки. О важности уровней заголовков неплохо рассказывает Эрик Бейли в статье «<a href="https://web-standards.ru/articles/heading-levels/">Важность уровней заголовков для вспомогательных технологий</a>». Чтобы не повторяться, я дам небольшое введение о заголовках с точки зрения скринридера, но главное, на чём мы сфокусируемся сегодня — дополнительные уровни заголовков.</p>
<p>Причиной, побудившей написать меня об этом, стали новаторские идеи: либо отказаться от уровней заголовков вовсе, либо, если не хватает шести уровней, начать добавлять седьмой, восьмой и так далее при помощи <code>aria-level=&quot;N&quot;</code>.</p>
<p>Во многом сомнения у людей возникают из-за техники создания заголовков с помощью <code>role=&quot;heading&quot;</code>, <a href="https://www.w3.org/WAI/WCAG21/Techniques/aria/ARIA12.html">описанной в приложении к WCAG 2.1</a>, где в первом примере вовсе не указывается уровень, а во втором демонстрируется возможность создания заголовка седьмого уровня.</p>
<pre><code tabindex="0" class="language-html">&lt;div role=&quot;heading&quot; aria-level=&quot;7&quot;&gt;Jonagold&lt;/div&gt;
&lt;p&gt;
    Jonagold is a cross between the Golden
    Delicious and Jonathan varieties.
&lt;/p&gt;
</code></pre>
<p>Здесь стоит напомнить, что не все примеры из спецификаций по доступности стоит буквально применять в вашем коде. Спецификация описывает то, как должны или могут работать браузеры, чтобы транслировать подходящую информацию в скринридеры или другие вспомогательные инструменты. Но важно не забывать о совместимости и практике применения: что сейчас работает в браузерах, что показало свою эффективность, а что нет.</p>
<h2>Заголовки для скринридера</h2>
<p>Незрячий пользователь, изучая страницу, может перемещаться практически к любому типу элементов при помощи быстрой навигации. Обычная клавиатура становится панелью навигации по заголовкам, регионам, ссылкам (посещенным и непосещенным в отдельности), элементам форм (флажкам, кнопкам, полям редактора) и так далее.</p>
<figure>
    <img src="https://web-standards.ru/articles/extra-heading-levels/images/all-headings.png" alt="Окно VoicOver со списком всех заголовков страницы.">
    <figcaption>
        Скринридеры позволяют вывести все заголовки на странице, чтобы получить карту всех разделов и удобно по ней перемещаться.
    </figcaption>
</figure>
<p>Все скринридеры, к примеру, позволяют перемещаться вперед по заголовкам клавишей <kbd>H</kbd> (от header), назад — <kbd>Shift H</kbd>, но это лишь внешняя часть такой навигации. Теги от <code>&lt;h1&gt;</code> до <code>&lt;h6&gt;</code> — это разные элементы, хотя они и объединены одной ролью заголовка.</p>
<p>Навигация между разными уровнями заголовков — это то, что делает их доступными. В большом документе с серьезной иерархией частей, глав и подразделов просто необходима возможность различать заголовки разного уровня, а не пролистывать все при помощи одной горячей клавиши. Поэтому в скринридерах реализован механизм быстрого перехода к заголовкам с первого до шестого уровня, при помощи цифр от одного до шести соответственно.</p>
<p>Таким образом, используя заголовок на странице, вы не просто даёте возможность незрячему пользователю быстро переместиться к нему, но ещё и транслируете важность того или иного заголовка в структуре документа.</p>
<figure>
    <img src="https://web-standards.ru/articles/extra-heading-levels/images/level-two.png" alt="Окно VoicOver со списком только заголовков второго уровня.">
    <figcaption>
        Если нажать цифру с уровнем заголовка, в списке останутся только соответствующие заголовки. Например, только второго уровня.
    </figcaption>
</figure>
<p>Любой слепой человек не равен опытному пользователю скринридера по умолчанию. Некоторые пользователи не используют навигацию по уровням заголовков, потому что не достаточно полно владеют скринридерами. Кто-то предпочитает и вовсе перемещаться по сайту только стрелками и клавишей <kbd>Tab</kbd>, просто не используя быструю навигацию — хотя это и редкость. Пользователи бывают разные, но ваша задача сделать интерфейс доступным для всех. Грамотное использование уровней заголовков добавит доступности большинству незрячих людей, но никак не ухудшит взаимодействие для менее опытных.</p>
<h2>Пример важности уровней</h2>
<p>Покажу ситуацию на примере видеохостинга YouTube. До недавнего времени имена пользователей, оставивших комментарии под видео, не являлись заголовками. Перемещаться по аватаркам пользователей можно было при помощи буквы <kbd>G</kbd>, а первое нажатие клавиши <kbd>H</kbd> перемещало к первому ролику в группе рекомендованных, чье название было выведено в заголовок третьего уровня. После обновления все имена пользователей тоже попали в заголовки третьего уровня, из-за чего пропала возможность быстро попадать к рекомендациям.</p>
<p>Как можно было бы решить эту проблему иначе?</p>
<ul>
<li>Добавить <code>&lt;h2&gt;Рекомендации&lt;/h2&gt;</code> между группами элементов.</li>
<li>Вывести имена комментаторов через заголовки <code>&lt;h4&gt;</code>.</li>
</ul>
<p>К сожалению разработчики Google не использовали ни один из этих способов.</p>
<h2>Дополнительные уровни заголовков</h2>
<p>Как мы уже обсудили выше, навигация по заголовкам в скринридере возможна по отдельным уровням, благодаря цифрам от одного до шести. Такую быструю навигацию поддерживают все скринридеры, эти уровни соответствуют спецификации HTML.</p>
<p>Навигация по дополнительным уровням, начиная с седьмого, теоретически возможна только до девятого — на этом одиночные цифры заканчиваются. Быстрый доступ к заголовкам, начиная с десятого уровня и выше, невозможен в принципе.</p>
<p>На деле же навигация по заголовкам с седьмого по девятый уровень поддерживается только встроенным в Windows скринридером Narrator, у которого довольно небольшая аудитория незрячих пользователей. В других скринридерах эти уровни останутся недоступными.</p>
<p>Здесь стоит отметить, что скринридеры озвучивают вёрстку по-разному в разных браузерах. Не исключением являются и заголовки с дополнительными уровнями. Какие-то скринридеры отобразят хоть двадцать пятый уровень, какие-то сбросят его ко второму, чтобы к нему можно было переместиться при помощи клавиатуры. Так, например, происходит с заголовками, созданными с помощью <code>role=&quot;heading&quot;</code>, у которых уровень <code>aria-level</code> не указан.</p>
<p>Предлагаю ознакомиться со сводной таблицей поведения в разных браузерах четырех основных скринридеров для десктопа при разных уровнях заголовков, чтобы оценить все многообразие, и окончательно отказаться от мысли использовать заголовки за пределами первого-шестого уровня.</p>
<h3>Сводная таблица</h3>
<p>Поддержка заголовков больше шестого уровня в различных комбинациях браузеров и скринридеров.</p>
<div class="content__table-wrapper"><table><thead>
<tr>
<th>Уровни</th>
<th>Скринридер</th>
<th>Браузер</th>
<th>Поведение</th>
</tr>
</thead>
<tbody>
<tr>
<td>7–9</td>
<td>JAWS</td>
<td>Все</td>
<td>Сбрасываются ко второму уровню</td>
</tr>
<tr>
<td>7–9</td>
<td>NVDA</td>
<td>Все</td>
<td>Читаются с заданным уровнем</td>
</tr>
<tr>
<td>7–9</td>
<td>VoiceOver</td>
<td>Все</td>
<td>Читаются с заданным уровнем</td>
</tr>
<tr>
<td>7–9</td>
<td>Narrator</td>
<td>Все</td>
<td>Читаются с заданным уровнем</td>
</tr>
<tr>
<td>10+</td>
<td>JAWS</td>
<td>Все</td>
<td>Сбрасываются ко второму уровню</td>
</tr>
<tr>
<td>10+</td>
<td>NVDA</td>
<td>Chrome</td>
<td>Сбрасываются ко второму уровню</td>
</tr>
<tr>
<td>10+</td>
<td>NVDA</td>
<td>Firefox</td>
<td>Читаются с заданным уровнем</td>
</tr>
<tr>
<td>10+</td>
<td>VoiceOver</td>
<td>Все</td>
<td>Читаются с заданным уровнем</td>
</tr>
<tr>
<td>10+</td>
<td>Narrator</td>
<td>Chrome</td>
<td>Сбрасываются ко второму уровню и объединяются в один, если идут последовательно</td>
</tr>
<tr>
<td>10+</td>
<td>Narrator</td>
<td>Firefox</td>
<td>Игнорируются как заголовки в принципе</td>
</tr>
</tbody>
</table></div><h2>Подводя итоги</h2>
<p>Наиболее корректно ведёт себя скринридер JAWS, который во всех случаях сбрасывает нестандартные заголовки ко второму уровню. В репозитории JAWS на Гитхабе <a href="https://github.com/FreedomScientific/VFO-standards-support/issues/301">есть ишью</a>, с просьбой исправить этот баг. Был даже <a href="https://github.com/FreedomScientific/VFO-standards-support/pull/504">пул-реквест</a>, меняющий это поведение, но его так и не приняли — фактически это не баг, а фича.</p>
<p>Наименее корректно ведёт себя VoiceOver, который передает все дополнительные уровни заголовков без возможности навигации по ним. О странном поведении Narrator говорить нет смысла, он имеет крайне малую аудиторию и приведен в таблице лишь для полноты картины.</p>
<p>Получается, что если вы хотите предоставить пользователю скринридера возможность навигации по вашему документу, вам следует использовать стандартные заголовки от первого до шестого уровня из спецификации HTML. А если вам приходится имитировать заголовки с помощью ARIA-роли, убедитесь, что уровни заголовков остаются в тех же границах.</p>
<p><a href="https://www.w3.org/TR/WCAG21/#compatible">Положение 4.1 WCAG гласит</a>:</p>
<blockquote>
<p>Обеспечьте максимальную совместимость контента с существующими и разрабатываемыми пользовательскими приложениями, включая ассистивные технологии.</p>
</blockquote>
<p>Таким образом не скринридеры должны подстраиваться под спецификацию ARIA или под ваш сайт, а ваш код должен быть совместим со скринридерами, грамотно используя возможности ARIA. Поэтому так важно вести разработку не на основе спецификаций, а консультируясь и тестируя ваши интерфейсы с опытными пользователями скринридеров. Только так вы сможете сделать их не формально доступными, а по-настоящему удобными для всех.</p>

                    ]]></description><pubDate>Thu, 19 Aug 2021 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/extra-heading-levels/</guid></item><item><title>Липкая шапка таблицы на CSS</title><link>https://web-standards.ru/articles/sticky-table-header/</link><description><![CDATA[
                        <p>Практически на всех сайтах есть таблицы. А если эти таблицы имеют более дюжины строк, то вам, рано или поздно, понадобится сделать шапку таблицы «липкой». Многие до сих пор делают это с помощью JavaScript, но есть способ на чистом CSS.</p>
<h2>Position Sticky</h2>
<p>Надо добавить <code>position: sticky</code> к <code>&lt;thead&gt;</code> (или <code>&lt;tr&gt;</code>, что там у вас в роли шапки?) и не забыть указать <code>top</code>:</p>
<pre><code tabindex="0" class="language-css">thead {
    position: sticky;
    top: 0;
}
</code></pre>
<iframe src="https://codepen.io/XAHTEP26/embed/preview/ExWqodG"></iframe>
<p>Еще недавно в Chrome был баг, который не позволял делать липкими <code>&lt;thead&gt;</code> и <code>&lt;tr&gt;</code>, но в версии 91 его исправили. А всё благодаря тому, что в браузере теперь используется <a href="https://developer.chrome.com/blog/tablesng/">новый движок для рендеринга таблиц TablesNG</a>.</p>
<p>Если вам всё-таки нужна поддержка и более старых версий, то можно сделать липкими сами ячейки (<code>&lt;td&gt;</code>) в шапке таблицы. Правда при этом будет сложно указать всем правильное значение <code>top</code>, когда шапка состоит из нескольких строк.</p>
<h2>Как не выходить за рамки?</h2>
<p>К сожалению почти у всех таблиц, которые я встречаю, используется <code>border-collapse: collapse</code>. С этим свойством проще делать рамки для ячеек, но при этом сами рамки им уже не принадлежат, а как бы становятся частью самой таблицы. А это значит, что если шапка таблицы и стала липкой — рамки её ячеек всё равно прокручиваются вместе с таблицей.</p>
<iframe src="https://codepen.io/XAHTEP26/embed/preview/MWpNrKv"></iframe>
<p>Чтобы избавиться от этой проблемы, можно использовать <code>border-collapse: separate</code>. Да, с этим свойством рамки ячеек перестанут схлопываться, но нам это не помешает.</p>
<p>В некоторых дизайн-системах у ячеек есть только горизонтальные рамки, а значит достаточно просто указывать <code>border-top</code> или <code>border-bottom</code>. Но даже если вам нужно указать рамки со всех сторон, то есть много способов это сделать:</p>
<h3>Рамки для конкретных сторон</h3>
<pre><code tabindex="0" class="language-css">:root {
    --border-width: 2px;
    --border-color: #CED4DA;
    --border: var(--border-width) solid var(--border-color);
}

table {
    border-collapse: separate;
    border-spacing: 0;
}

thead {
    position: sticky;
    top: 0;
}

th, td {
    border-right: var(--border);
    border-bottom: var(--border);
}

th:first-child, td:first-child {
    border-left: var(--border);
}

thead tr:first-child th {
    border-top: var(--border);
}
</code></pre>
<iframe src="https://codepen.io/XAHTEP26/embed/preview/jOBgYGw"></iframe>
<h3>Рамки как box-shadow</h3>
<pre><code tabindex="0" class="language-css">table {
    border-collapse: separate;
    border-spacing: var(--border-width);
}

thead {
    position: sticky;
    top: var(--border-width);
}

th, td {
    box-shadow:
        0 0 0
        var(--border-width)
        var(--border-color);
}
</code></pre>
<p>В примере выше мы устанавливаем расстояние между ячейками с помощью <code>border-spacing</code> для таблицы и отступ для прилипания с помощью <code>top</code> для шапки, равный размеру рамки. А затем добавляем ячейкам тень <code>box-shadow</code>, имитирующую рамку.</p>
<iframe src="https://codepen.io/XAHTEP26/embed/preview/JjWgMLx"></iframe>
<h3>Рамки как outline</h3>
<pre><code tabindex="0" class="language-css">table {
    border-collapse: separate;
    border-spacing: var(--border-width);
}

thead {
    position: sticky;
    top: var(--border-width);
}

th, td {
    outline: var(--border);
}
</code></pre>
<p>В примере выше мы повторяем трюк из предыдущего примера, но имитирурем рамку с помощью <code>outline</code>.</p>
<iframe src="https://codepen.io/XAHTEP26/embed/preview/NWpQXLj"></iframe>

                    ]]></description><pubDate>Fri, 02 Jul 2021 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/sticky-table-header/</guid></item><item><title>Как организована веб-платформа</title><link>https://web-standards.ru/articles/web-platform/</link><description><![CDATA[
                        <p>Вы наверняка слышали про веб-платформу, спецификации, W3C, CSSWG и другие организации, которые создают технологии, которыми мы пользуемся каждый день для создания веб-интерфейсов. Но от этого всегда веяло какой-то тайной, всё казалось переусложнённым и не вызывало доверия. Давайте разберёмся, как устроено создание спецификаций и как можно принять участие в развитии веб-платформы.</p>
<h2>Веб-платформа</h2>
<p>«Веб-платформа» — это набор стандартизированных API (HTML, CSS, JavaScript, SVG…), которые разработчики используют для построения сайтов и веб-приложений. Помимо «корневых» технологий платформа включает ещё и локальные браузерные API, которые добавляют в браузер новую функциональность: DOM, Console, Fetch и другие.</p>
<h2>Стандарты</h2>
<p>Как всё работает в браузере, описано в специальных документах — спецификациях. Это подробные инструкции для разработчиков, как должна работать какая-то определённая фича. Например, как должны строиться сетки на флексах или как должен работать <code>console.log</code>.</p>
<h2>Спецификации</h2>
<p>Спецификаций в веб-платформе много. Спеки создаются в специальных организациях: W3C, WHATWG, Ecma International, OpenJS Foundation и другие — в них и создаются HTML, CSS, SVG, JS, Node.js. Дальше мы подробнее поговорим о том, как работают W3C и WHATWG.</p>
<h2>W3C</h2>
<p>World Wide Web Consortium — международная организация, в штате которой примерно 60 человек из мировых университетов (MIT, ERCIM, Keio University, Beihang University). Также в W3C есть членство для внешних компаний.</p>
<p>На апрель 2021 в W3C состоит 438 компаний-членов: производители браузеров (Mozilla, Google, Apple, Samsung), софтверные компании (Adobe, Zoom), технологические гиганты (Amazon, Facebook, Visa, Alibaba), сервисы (Airbnb, Netflix, Shopify), железо (Huawei, Intel) и <a href="https://www.w3.org/Consortium/Member/List">другие</a>.</p>
<p>Чтобы иметь членство W3C, компании нужно платить членские взносы (для бедных стран — меньше, для богатых — больше). Например, если небольшая российская компания захочет вступить в «клуб» W3C, то это в 2021 году будет стоить минимум 1950 € в год (для больших компаний в 10 раз больше).</p>
<p>Компании отправляют своих сотрудников для работы в W3C. Вместе со штатными сотрудниками W3C приглашённые делегаты формируют <a href="https://www.w3.org/groups/wg/">рабочие группы</a> (WG, Working Group).</p>
<p>Работа в FAANG <em>(аббревиатура из названий крупнейших компаний Facebook, Amazon, Apple, Netflix, Google — прим. редактора)</em> — не единственный способ попасть в рабочую группу W3C. Группа может включать и приглашённых экспертов (Invited Expert), которые никак не аффилированы с бизнесом, но являются крутанами в своей области. Все рабочие группы собираются на ежегодной сходке TPAC (Technical Plenary), которая проходит осенью.</p>
<h3>Рабочая группа CSS в W3C</h3>
<p>Как устроена рабочая группа, на примере <a href="https://www.w3.org/groups/wg/css/participants?sortaff=1">CSS Working Group</a>.</p>
<p>В ней есть председатели, штатные сотрудники W3C, делегаты из внешних компаний (FAANG и другие) и приглашённые эксперты, например: <a href="https://twitter.com/rachelandrew">Рэйчел Эндрю</a>, <a href="https://twitter.com/fantasai/">Элика Этемад</a>, <a href="https://twitter.com/LeaVerou">Лия Веру</a>, <a href="https://jonneal.dev/">Джонатан Нил</a>. Внешние эксперты приглашаются по предложению участников рабочей группы.</p>
<p>Среди всех участников выделяется три роли:</p>
<ol>
<li>Редакторы спецификаций — отвечают за модули спецификаций, следят за ишью, получают фидбек, пишут спеку и тащат прогресс по своим модулям спек (иногда у спеки только один редактор, иногда — несколько);</li>
<li>Остальные члены рабочей группы (не редакторы) — участвуют в дискуссии, брейнштормят, принимают коллективные решения;</li>
<li>Сообщество сочувствующих <a href="https://lists.w3.org/Archives/Public/www-style/">www-style</a> — это как непосредственно участники группы, так и в целом все заинтересованные в CSS люди: они комментируют и критикуют предложения, запрашивают фичи и объясняют юзкейсы, поднимают вопросы, участвуют в обсуждениях и дают фидбек редакторам спек.</li>
</ol>
<p>Контент CSS-спецификаций живёт в репозитории <a href="https://github.com/w3c/csswg-drafts">github.com/w3c/csswg-drafts</a>. Там же заводятся ишью и проводятся публичные дикуссии по контенту. Вот, например, <a href="https://github.com/w3c/csswg-drafts/issues/2701">увлекательное обсуждение CSS-нестинга</a>.</p>
<p>Также у рабочей группы есть еженедельные собрания, на которых обсуждаются и решаются ишью, ведётся лог обсуждения. Ссылки на лог публикуются <a href="https://www.w3.org/blog/CSS/">в блоге</a> и <a href="https://twitter.com/csswg">в Твиттере</a>. Вот, к примеру, о чём договорились на <a href="https://www.w3.org/blog/CSS/2021/06/16/minutes-2021-06-16/">прошлой неделе</a>.</p>
<h3>Спецификации в W3C</h3>
<p>Все спецификации W3C опубликованы в одном месте по адресу <a href="https://www.w3.org/TR/">w3.org/TR</a>.</p>
<p>Спеки проходят такие формальные стадии развития (некоторые промежуточные технические стадии упростил):</p>
<ol>
<li><strong>Working Draft (WD)</strong> — зафиксированная версия черновика спеки для ревью сообществом.</li>
<li><strong>Candidate Recommendation (CR)</strong> — сигнал для финального ревью сообществом, сбор опыта реализации спеки в браузерах.</li>
<li><strong>W3C Recommendation (REC)</strong> — все довольны, спека доделана, опубликована финальная версия.</li>
</ol>
<p>Текущая версия черновика называется <strong>Editor’s Draft (ED)</strong> и может быть достаточно сырой, но она самая живая из всех — над ней работает редактор спеки.</p>
<h3>Спецификации по CSS</h3>
<p>Если отфильтровать все спеки по тегу CSS, то получится <a href="https://www.w3.org/TR/?tag=css">больше сотни</a>. Большая часть из них — черновики WD, меньшая — рекомендации REC и CR.</p>
<p>После публикации <a href="https://www.w3.org/TR/CSS2/">единой монолитной спецификации CSS 2.1</a>, рабочая группа решила распилить её и дальше развивать отдельные независимые модули. Также появилась идея собрать из отдельных модулей общий сборник «CSS 3» и дальше развивать отдельные модули и двигать их по уровням независимо друг от друга.</p>
<p>К примеру, в CSS 2.1 был раздел Color. Поэтому в рамках CSS 3 появился отдельный модуль — CSS Color Level 3. Дальше он будет двигаться к Level 4, потом к 5 и так далее.</p>
<p>Если в спеке CSS 2.1 изначально какой-то фичи не было, например, кастомных свойств или флексов, то новые спецификации начинают нумероваться с первого уровня: например, <a href="https://www.w3.org/TR/css-variables-1/">CSS Custom Properties for Cascading Variables Module Level 1</a> или <a href="https://www.w3.org/TR/css-flexbox-1/">CSS Flexible Box Module Level 1</a>.</p>
<p>Чтобы не потеряться в обилии спек, рабочая группа CSS время от времени публикует «срез» состояния CSS — Snapshot. Последний из опубликованных — <a href="https://www.w3.org/TR/css-2020/">CSS Snapshot 2020</a>. Это список всех спек, про которые в 2020 году можно сказать, что это и есть весь современный стабильный CSS.</p>
<h3>Процесс работы над спеками CSS</h3>
<p>Как показала практика, работа над CSS-спеками не всегда укладывается в формальную линейную однонаправленную схему WD → CR → REC. Иногда развитие спеки в ответ на полученный фидбек двигается назад, а не вперёд. Поэтому есть более неформальное и близкое к жизни описание стадий работы над спеками, которое показывает <em>стабильность</em> спецификации:</p>
<ol>
<li><strong>Exploring</strong> (ранний WD): нестабильная версия, где всё ещё устаканивается и меняется. Нужен для совместной работы редактора спеки и рабочей группы.</li>
<li><strong>Revising</strong> (средний WD): модуль в основном готов, но спека ещё нуждается в нескольких циклах публикации и ревью. В конце этой стадии рабочая группа фиксирует контент спеки перед переходом на формальный CR.</li>
<li><strong>Refining</strong> (поздний WD): спека уже в основном стабильна и готова для перехода на CR, но ещё нуждается в шлифовке и полировке.</li>
<li><strong>Testing</strong> (ранний CR): на этом этапе рабочая группа уверена, что спека достаточно полна и точна, чтобы её начинать имплементировать и писать тесты, так как для дальнейшего развития спеки нужен уже опыт имплементации в браузерах и тест-кейсы.</li>
<li><strong>Stable</strong> (поздний CR): на этой стадии у рабочей группы есть достаточно полученного опыта тестирования и имплементации спеки, сама спека стабильна и надёжна, минорные ишью появляются нечасто. Такая версия спеки может быть включена в Snapshot.</li>
<li><strong>Completed</strong> (PR, REC): тесты и отчёты об имплементации готовы, изменений больше не предвидится (кроме листинга опечаток).</li>
</ol>
<p>Все CSS-спеки, сгруппированные по таким стадиям, опубликованы <a href="https://www.w3.org/Style/CSS/current-work">на странице CSS current work</a>.</p>
<h2>WHATWG</h2>
<p>Но не W3C единым жива веб-платформа. В 2004 году от W3C из-за разногласий по поводу будущего HTML откололась группа разработчиков стандартов. Эта группа назвалась WHATWG — Web Hypertext Application Technology Working Group. В W3C собирались остановить работу над HTML в угоду XHTML 2 — новой версии XHTML, обратно не совместимой с HTML 4 и XHTML 1. В WHATWG считали, что нужно продолжать развивать HTML и стандарты для создания веб-приложений. Они форкнули HTML и развили его до того самого HTML 5, тем самым выиграв у W3C.</p>
<p>До 2019 существовало две разные параллельные спецификации HTML, но потом W3C и WHATWG помирились и договорились работать вместе над <a href="https://html.spec.whatwg.org/multipage/">«вечнозелёной» спекой HTML</a>.</p>
<p>Помимо HTML в WHATWG работают над такими фичами: Compatibility, Console Object, DOM, Encoding, Fetch, Fullscreen, URL и XHR. Все спецификации опубликованы на сайте <a href="https://spec.whatwg.org/">spec.whatwg.org</a>.</p>
<p>Спеки WHATWG живут в репозитории <a href="https://github.com/whatwg/">github.com/whatwg</a>.</p>
<h3>Процесс работы WHATWG</h3>
<p>В отличие от W3C, WHATWG — открытое и бесплатное сообщество. Любой желающий может участвовать в развитии спек. Работа ведётся на <a href="https://github.com/whatwg/">Гитхабе</a>.</p>
<p>Все стандарты WHATWG — «вечнозелёные», то есть не имеют версий, а всегда «доделаны». В WHATWG сравнивают разработку спек с разработкой ПО — софт тоже постоянно разрабатывается и меняется.</p>
<p>У каждого стандарта в идеале есть:</p>
<ul>
<li>редактор или несколько;</li>
<li>активное сообщество контрибьюторов;</li>
<li>лог изменений;</li>
<li>ишью-трекер;</li>
<li>тесты.</li>
</ul>
<p>Редактор тащит развитие стандарта, разрешает разногласия между контрибьюторами, сокращает количество открытых ишью, договаривается с теми, кто внедряет стандарты в браузеры и допиливает стандарт под их возможные ограничения.</p>
<p>Всего в <a href="https://github.com/orgs/whatwg/people">организации на Гитхабе</a> в июне 2021 состоит 81 человек.</p>
<h2>Тесты веб-платформы</h2>
<p>Когда рассказывают о веб-спецификациях, мало говорят про тесты. Вот есть текст спеки, вот её внедрили в браузере, это ок. А как узнать, что ничего старого при этом не поломалось? Как оценить, что внедрение корректное и спеку поняли правильно?</p>
<p>Тесты нужны, чтобы помочь мейнтейнерам софта (браузеров) проверить корректность своей работы: найти баги, проблемы совместимости с другими браузерами. Также они помогают приоритизировать работу над определённой группой более критичных багов. У веб-платформы есть <a href="https://github.com/web-platform-tests/wpt">свой набор тестов</a>, который открыто пишется сообществом. Они удобно разделены по названиям фич и внутри структурированы по разделам спецификаций.</p>
<p>Реалии таковы, что браузеры не проходят все тесты на 100% и вряд ли будут когда-то их проходить. Новые фичи появляются в браузерах, а за ними приходят баги, спеки и внедрения допиливаются — всё это нормально. К примеру, в июне 2021 из 41761 тестов в Chrome не проходят 510 тестов, в Firefox — 1377, а в Safari — 3859.</p>
<p>Графики результатов тестов, сгруппированные по фичам, есть на сайте <a href="https://wpt.fyi/">wpt.fyi</a>. Вот такая, к примеру, <a href="https://wpt.fyi/results/css/css-grid?label=experimental&amp;label=master&amp;aligned">ситуация с поддержкой гридов в браузерах</a>.</p>
<p>Тесты, помимо пользы для разработчиков браузеров, дают и обычным разработчикам возможность оценивать фичи перед использованием. Обычно разработчики смотрят на статистику внедрения фич на сайте <a href="https://caniuse.com/">Can I Use</a>. Так можно получить ответ на вопрос: есть ли фича в определённом браузере. Но кроме формального внедрения фичи стоит учитывать:</p>
<ul>
<li>баги в её реализации: к примеру, гриды поддерживаются всеми вечнозелёными браузерами, но при этом в крайних случаях реализации браузеров разнятся;</li>
<li>уровень стабильности спеки: то, что фича реализована в браузере, не говорит о том, что она не изменится в будущем — если фича основана на нестабильной спеке, то, вероятно, при уточнении спеки изменится и сама фича.</li>
</ul>
<h2>Как контрибьютить в веб-платформу</h2>
<p>Самый простой вариант, как въехать в тему — подтянуть английский и заняться багами:</p>
<ul>
<li>сообщать о багах разработчикам браузеров: <a href="https://bugzilla.mozilla.org/">Mozilla</a>, <a href="https://bugs.webkit.org/">WebKit</a>, <a href="https://bugs.chromium.org/p/chromium/issues/list">Chromium</a>;</li>
<li>решать баги в тестах или писать новые тесты: <a href="https://github.com/web-platform-tests/wpt/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22">ишью в репозитории WPT</a>;</li>
<li>решать баги в спеках: <a href="https://github.com/whatwg/html/labels/good%20first%20issue">ишью WHATWG</a>, <a href="https://github.com/w3c/csswg-drafts/issues?q=is%3Aopen+is%3Aissue+label%3A%22Help+Wanted%22">ишью CSSWG</a>.</li>
</ul>

                    ]]></description><pubDate>Tue, 22 Jun 2021 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/web-platform/</guid></item><item><title>Ссылка для скачивания</title><link>https://web-standards.ru/articles/download-link/</link><description><![CDATA[
                        <p>Иногда передо мной стоит задача сделать ссылку, которая должна открывать системный диалог для сохранения файла. Браузеры достаточно умны, чтобы открывать такой диалог при скачивании бинарников вроде архивов или EXE-файлов. Но что делать, если я хочу скачивать картинки или какие-нибудь видео? Именно скачивать, не открывать для просмотра.</p>
<h2>Заголовок Content-Disposition</h2>
<p>Самый правильный способ попросить браузер скачать файл — добавить на стороне сервера заголовок <code>Content-Disposition</code> к потоку с файлом.</p>
<pre><code tabindex="0">Content-Disposition: attachment; filename=kitten.jpg
</code></pre>
<p>Когда браузер видит у заголовка значение <code>attachment</code>, то пытается скачать файл.</p>
<p>Но иногда у вас просто нет возможности настроить сервер под свои нужды и добавить ещё один <code>mod_rewrite</code>. Нужен какой-то более браузерный способ решить задачу.</p>
<h2>Атрибут download</h2>
<p>Самый простой способ — добавить атрибут <code>download</code> к ссылке.</p>
<p>Если вы добавите его просто так, без значения, браузер постарается получить имя файла либо из заголовка <code>Content-Disposition</code> (опять он, и у него довольно высокий приоритет), либо из пути файла.</p>
<pre><code tabindex="0" class="language-html">&lt;a href=&quot;images/kitten.jpg&quot; download&gt;
    &lt;img src=&quot;images/kitten-preview.jpg&quot; alt=&quot;Котёнок, превью.&quot;&gt;
&lt;/a&gt;
</code></pre>
<p>Попробуйте: <a href="https://web-standards.ru/articles/download-link/demo/kitten-pixel.jpg" download>ссылка</a>.</p>
<p>Но вы можете задать значение атрибуту <code>download</code>, и тогда это значение станет именем скачиваемого файла. Это может быть полезно, если у ваших файлов какие-нибудь странные автогенерируемые урлы вроде <code>https://cdn/images/a1H5-st42-Av1f-rUles</code>.</p>
<pre><code tabindex="0" class="language-html">&lt;a href=&quot;images/1h24v9lj.jpg&quot; download=&quot;kitten&quot;&gt;
    Скачать
&lt;/a&gt;
</code></pre>
<p>Попробуйте: <a href="https://web-standards.ru/articles/download-link/demo/kitten-pixel.jpg" download="i-am-tiny">ссылка</a>.</p>
<p><strong>Важно!</strong> Вся эта магия атрибутов <a href="https://www.chromestatus.com/feature/4969697975992320">не для ссылок с других доменов (cross-origin)</a>. Вы не можете управлять чужими ресурсами из соображений безопасности.</p>
<p>И помните, что IE и старые Safari не понимают атрибут <code>download</code>. <a href="https://caniuse.com/download">Проверьте в Can I use…</a>.</p>
<h2>blob: и data:</h2>
<p>Полезный лайфхак, чтобы помочь вашим пользователям сохранять картинки котиков в удобном для них формате. Если вы на своём сайте используете картинки в форматах AVIF или WebP, есть очень высокая вероятность, что ни один пользователь не сможет сохранить их к себе на компьютер или смартфон, чтобы потом пересмотреть. Точнее, сохранить-то смогут, а вот посмотреть вне браузера не смогут. Печаль.</p>
<p>Чтобы помочь пользователям, используйте <code>data:</code> или <code>blob:</code> внутри атрибута <code>href</code>.</p>
<h3>Шаг 1. Нарисуйте картинку на Canvas</h3>
<pre><code tabindex="0" class="language-js">const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
const image = new Image();
image.onload = function () {
    context.drawImage(image, 0, 0);

    // TODO: всю магию намазывать сюда
};
image.src = 'kitten-170.avif';
</code></pre>
<h3>Шаг 2а. Сохранить картинку как блоб в атрибут <code>href</code> ссылки</h3>
<pre><code tabindex="0" class="language-js">const blobLink = document.getElementById('blob-link');

canvas.toBlob(blob =&gt; {
    const blobUrl = URL.createObjectURL(blob);
    blobLink.href = blobUrl;
}, 'image/jpeg', 0.9);
</code></pre>
<p>Да-да, я могу сохранить AVIF как JPEG. Классно, правда? Пользователь скачал всего 4 КБ AVIF с сервера, а получил 13 КБ JPEG на клиенте!</p>
<h3>Шаг 2б. Сохранить картинку как data в атрибут <code>href</code> ссылки.</h3>
<p>Некоторые браузеры не умеют работать с блобами, поэтому вы можете помочь им при помощи data-урлов.</p>
<pre><code tabindex="0" class="language-js">const dataLink = document.getElementById('data-link');

dataLink.href = canvas.toDataURL('image/jpeg', 0.9);
</code></pre>
<p>Так даже проще, но такой подход хуже по производительности.</p>
<p>Можете поиграть с полным демо тут:</p>
<ul>
<li><a href="https://mefody.dev/chunks/download-link/demo/index.html">Демо</a></li>
<li><a href="https://github.com/MeFoDy/mefody.dev/blob/main/src/chunks/download-link/demo/index.html">Исходный код</a></li>
</ul>
<h2>Источники</h2>
<ul>
<li><a href="https://en.wikipedia.org/wiki/MIME#Content-Disposition">Wiki: Content-Disposition</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/A">MDN: The Anchor element</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL">MDN: <code>canvas.toDataURL</code></a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob">MDN: <code>canvas.toBlob</code></a></li>
</ul>

                    ]]></description><pubDate>Fri, 18 Jun 2021 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/download-link/</guid></item><item><title>Магия вне Хогвартса: NJS</title><link>https://web-standards.ru/articles/magic-njs/</link><description><![CDATA[
                        <p>Наверняка вы слышали про веб-сервер <a href="https://nginx.org/">Nginx</a> Игоря Сысоева. Архитектура Nginx построена таким образом, чтобы выдерживать колоссальное количество запросов от несметного количества клиентов. Словом, настоящая магия программирования на чистом Си! Если попробовать разобраться, как это работает, у многих может возникнуть синдром самозванца. И действительно, чтобы написать свой модуль для расширения функциональности Nginx, надо пройти школу чародейства и мастерства. А что делать JS-волшебникам и JS-волшебницам? Можно пройти курс защиты от тёмных сил или просто узнать больше об <a href="https://github.com/nginx/njs">NJS</a>.</p>
<h2>Алохомора: откроем замочек</h2>
<p>Nginx поддерживал внедрение скриптов в рантайм практически с самого начала, но это были скрипты на популярном тогда языке Perl. Сегодня мало кто умеет писать на Perl: сначала ему на смену пришёл PHP, а потом был период, когда практически ничего не менялось в самом Nginx. В то же время в разработке набирали популярность другие языки и всё меньше программистов могли поддерживать скрипты на сервере. Именно поэтому возникла необходимость использовать что-то другое.</p>
<p>И вот примерно с 2017 года одна из команд Nginx начала разрабатывать новое решение — использование JavaScript в качестве языка для скриптов. Было разработано два модуля для бесплатной версии Nginx, которые вместе составляют основу инструмента NJS, и много чего ещё для платной версии Nginx Plus.</p>
<p>NJS — очень интересный инструмент. У команды была возможность использовать готовые решения, например, движки V8 или SpiderMonkey, но они пошли другим путём. Эти решения нужны для решения широкого круга задач. О сложностях использования существующих движков JavaScript для работы скриптов в Nginx подробно рассказывает один из авторов NJS в докладах на <a href="https://youtu.be/GlJHbyvCMyk">русском</a> и <a href="https://youtu.be/Jc_L6UffFOs">английском</a> языке и приводит сравнение производительности.</p>
<p>Вместо этого NJS использует прекомпиляцию в байт-код при старте Nginx: виртуальная машина клонируется для каждого запроса, а ещё нет JIT-компиляции и сборщки мусора. Скрипты под NJS иногда вполне могут заменить модули Nginx на языке Си, хотя и с небольшой просадкой по производительности. При внедрении NJS было важно, чтобы Nginx не потерял своих достоинств. И команда разработчиков из Nginx сделала это!</p>
<h2>Портус: пора в новый мир</h2>
<p>Установка NJS для большинства операционных систем описана в <a href="https://nginx.org/ru/docs/njs/install.html">официальной документации</a>. Возможны как установка в качестве готового пакета, так и сборка модулей из исходных файлов. Для работы с NJS используются два основных модуля: <code>ngx_http_js_module</code> и <code>ngx_stream_js_module</code>. Первый используется для обработки данных по протоколу HTTP, второй — по протоколам TCP и UDP.</p>
<p>После установки необходимо подключить модули NJS к Nginx. Модули являются динамическими, поэтому в файле конфигурации необходимо воспользоваться <a href="https://nginx.org/ru/docs/ngx_core_module.html#load_module" title="load_module">директивой <code>load_module</code></a>:</p>
<pre><code tabindex="0">load_module modules/ngx_http_js_module.so;
# или
load_module modules/ngx_stream_js_module.so;
</code></pre>
<h2>Люмос: что-то темно</h2>
<p>Для того чтобы реализовать скрипт для Nginx, необходимо прописать определённые директивы в файле конфигурации. Здесь и дальше конфигурацию будем описывать в файле <code>nginx.conf</code>, а скрипт на языке <a href="https://nginx.org/ru/docs/njs/index.html">NJS</a> — в файле <code>script.js</code>.</p>
<p>Начнём с классического «Hello world!». Логика работы скрипта заключается в том, чтобы прослушивать заранее заданный порт, например 8080, и при обращении к корневой папке сайта отослать клиенту ответ с кодом 200 и нашим сообщением.</p>
<p>Файл <code>nginx.conf</code>:</p>
<pre><code tabindex="0">http {
    js_import script.js;

    server {
        listen 8080;

        location / {
            js_content script.hello;
        }
    }
}
</code></pre>
<p>Файл <code>script.js</code>:</p>
<pre><code tabindex="0" class="language-js">function hello(r) {
    r.return(200, 'Hello world!');
}

export default {
    hello
};
</code></pre>
<p>NJS — это подмножество JavaScript. Что можно, а что нельзя, определено <a href="https://nginx.org/ru/docs/njs/compatibility.html">дорожной картой</a>. Язык поддерживает стандарт <a href="https://www.ecma-international.org/ecma-262/">ECMA-262</a> и имеет <a href="https://nginx.org/en/docs/njs/reference.html">ряд специальных методов и свойств</a>.</p>
<p>Среди специальных методов, которые обрабатывают сущности Nginx — HTTP-запрос, Stream-сессия. Можно узнать метод и аргументы запроса, обработать его заголовки и тело HTTP-сообщения, отправить коды ответа, заголовки, тело HTTP-ответа от сервера. Возможно проксирование, авторизация, логирование, доступна обработка ошибок. Создать и организовать поток данных тоже возможно с помощью NJS. Таймеры, кодирование-декодирование строк, информация о процессе, шифрование являются частью ядра, а синтаксический разбор и форматирование URL, доступ к файловой системе через реализацию библиотеки <code>fs</code> — встроенными модулями.</p>
<p>Разработчики привели довольно много <a href="https://nginx.org/ru/docs/njs/examples.html">примеров кода и вариантов использования</a> NJS. Обязательно зайдите на эту страницу — вдруг ваш вариант уже реализован.</p>
<p>Список всех актуальных директив NJS для версии 0.4.0 и старше, которые могут быть использованы в файлах конфигурации:</p>
<ul>
<li><a href="https://nginx.org/ru/docs/http/ngx_http_js_module.html#js_body_filter"><code>js_body_filter</code></a> — устанавливает функцию из модуля в качестве фильтра тела HTTP-сообщения ответа;</li>
<li><a href="https://nginx.org/ru/docs/http/ngx_http_js_module.html#js_content" title="js_content"><code>js_content</code></a> — устанавливает функцию из модуля в качестве обработчика содержимого <code>location</code>;</li>
<li><a href="https://nginx.org/ru/docs/http/ngx_http_js_module.html#js_header_filter" title="js_header_filter"><code>js_header_filter</code></a> — устанавливает функцию из модуля в качестве фильтра заголовков HTTP-сообщения ответа;</li>
<li><a href="https://nginx.org/ru/docs/http/ngx_http_js_module.html#js_import" title="js_import"><code>js_import</code></a> — импортирует модуль с функциями;</li>
<li><a href="https://nginx.org/ru/docs/http/ngx_http_js_module.html#js_path" title="js_path"><code>js_path</code></a> — устанавливает дополнительный путь до модулей;</li>
<li><a href="https://nginx.org/ru/docs/http/ngx_http_js_module.html#js_set" title="js_set"><code>js_set</code></a> — устанавливает функцию из модуля, которая вызывается в момент первого обращения к переменной для данного запроса;</li>
<li><a href="https://nginx.org/ru/docs/http/ngx_http_js_module.html#js_var" title="js_var"><code>js_var</code></a> — объявляет перезаписываемую переменную.</li>
</ul>
<h2>Вингардиум Левиоса: полетели</h2>
<p>Теперь напишем наш собственный модуль, который будет шифровать данные 🙃. Возьмём самый простой способ шифрования и попробуем передавать и принимать данные с помощью него. Будем использовать очень простой <a href="https://ru.wikipedia.org/wiki/%D0%A8%D0%B8%D1%84%D1%80_%D0%A6%D0%B5%D0%B7%D0%B0%D1%80%D1%8F">шифр Цезаря</a>. Его заключается в том, чтобы каждый символ в сообщении заменяется на другой, сдвинутый циклично на фиксированное число позиций в алфавите. Например, слово <code>JavaScript</code> будет преобразовано в слово <code>OfafXhwnuy</code> при смещении на пять символов.</p>
<p>Напишем сначала код, используя NJS. Шифрование и дешифрование реализуем в функции <code>processMessage()</code> (для примера учтём только символы английского алфавита, для шифрования будем использовать положительное значение сдвига, а для дешифрования — отрицательное).</p>
<p>Попробуем сделать так, чтобы при приёме HTTP-запроса скрипт возвращал расшифрованное-зашифрованное сообщение в зависимости от пути на сервере <code>/encode/</code> или <code>/decode/</code>. Если мы обратимся к <code>/shift/</code>, то получим сдвиг, который заложен в нашу программу. Для обработки каждого пути напишем отдельные функции в модуле: <code>encode</code>, <code>decode</code> и <code>shift</code>. С помощью функции <code>getMessage()</code> можно будет получить сообщения из переменных, переданных GET- или POST-запросом от клиента.</p>
<p>Модули NJS перед исполнением компилируются и запускаются на сервере как отдельный процесс, а затем уничтожаются после выполнения. В платной версии Nginx Plus есть вариант хранения переменных в общем хранилище. В нашем случае мы могли бы так настраивать величину сдвига. Но у нас версия бесплатная, поэтому мы храним величину сдвига в константе <code>CAESAR_SHIFT</code>. В платной версии Nginx Plus есть даже кэширование результатов работы скриптов, нам бы вряд ли это пригодилось, но интересно.</p>
<p>Основной модуль <code>script.js</code> будет выглядеть так:</p>
<pre><code tabindex="0" class="language-js">const CAESAR_SHIFT = 5;

// Преобразуем сообщение (шифр Цезаря)
function processMessage(inputString, shift) {
    let outputString = '';
    // Бежим по всем символам строки
    for (let i = 0; i &lt; inputString.length; i++) {
        // Получаем символ строки
        let c = inputString[i];
        if (c.match(/[a-z]/i)) {
            // Получаем код символа
            const code = inputString.charCodeAt(i);
            // Обрабатываем прописные буквы
            if (code &gt;= 65 &amp;&amp; code &lt;= 90) {
                c = String.fromCharCode(((code - 65 + shift) % 26) + 65);
            }
            // Обрабатываем строчные буквы
            else if (code &gt;= 97 &amp;&amp; code &lt;= 122) {
                c = String.fromCharCode(((code - 97 + shift) % 26) + 97);
            }
        }
        // Добавляем символ в конец строки
        outputString += c;
    }
    return outputString;
}

// Получаем данные из переменной
function getMessage(r) {
    return r.args.msg;
}

// Шифруем
function encode(r) {
    r.return(200, processMessage(getMessage(r), CAESAR_SHIFT));
}

// Дешифруем
function decode(r) {
    r.return(200, processMessage(getMessage(r), -CAESAR_SHIFT));
}

// Возвращаем сдвиг
function shift(r) {
    r.return(200, CAESAR_SHIFT);
}

export default {
    encode,
    decode,
    shift
};
</code></pre>
<p>Полный список доступных в NJS конструкций языка JavaScript описан на странице «<a href="https://nginx.org/ru/docs/njs/compatibility.html">Совместимость</a>».</p>
<p>Команда <code>r.return()</code> — отправляет тело HTTP-сообщения клиенту и статус ответа веб-сервера. <code>r.variables</code> хранит переменные, переданные в запросе клиента. Перед использованием нужно экспортировать необходимые функции в интерфейс, как это делается при работе с модулями в JavaScript.</p>
<p>Первая версия кода готова, теперь настроим веб-сервер.</p>
<p>У Nginx есть официальный образ Docker. Для того чтобы проверить ваш код, вы можете выполнить команду (если у вас установлен и настроен <a href="https://www.docker.com/get-started">Docker</a>):</p>
<pre><code tabindex="0" class="language-bash">docker run -i -t nginx:mainline /usr/bin/njs
</code></pre>
<p>Так вы можете проверять корректность вашего кода, вставляя функции отдельно. Если без Docker, то вы можете сделать это напрямую, установив <a href="https://nginx.org/ru/docs/njs/cli.html">CLI-утилиту</a> на компьютер или сервер. Это по сути тот же интерпретатор, который используется при обработке скриптов в модуле Nginx.</p>
<p>Установим Nginx и NJS. Для того чтобы ставить эксперименты было удобнее, установим окружение внутри Docker-контейнера. Так можно будет работать с контейнером, как с удалённым сервером, а клиентом будет выступать наш компьютер. Вы можете сделать иначе, просто установив всё, что требуется, к себе на компьютер или на сервер. Подробнее о Docker можно почитать в цикле статей «<a href="https://web-standards.ru/articles/docker-unboxing-1/">Распаковка Docker</a>».</p>
<p>Создадим папку с проектом и перейдем в неё:</p>
<pre><code tabindex="0" class="language-bash">mkdir caesar
cd caesar
</code></pre>
<p>Создадим папку для кода и добавим наш скрипт с помощью редактора или командой <code>cat</code>:</p>
<pre><code tabindex="0" class="language-bash">mkdir src
cat &gt; ./src/script.js &lt;&lt; __EOF__
// Cодержимое файла скрипта
__EOF__
</code></pre>
<p>Добавим ещё простейший интерфейс пользователя в файле <code>index.html</code>, который поместим в папку проекта:</p>
<pre><code tabindex="0" class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;ru&quot;&gt;
&lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot;&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot;&gt;
    &lt;title&gt;NJS тест&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;form action=&quot;/encode/&quot;&gt;
        &lt;fieldset&gt;
            &lt;legend&gt;Зашифровать сообщение&lt;/legend&gt;
            &lt;label for=&quot;encode-message&quot;&gt;Сообщение&lt;/label&gt;&lt;br&gt;
            &lt;textarea id=&quot;encode-message&quot; name=&quot;message&quot; cols=&quot;30&quot; rows=&quot;10&quot;&gt;&lt;/textarea&gt;&lt;br&gt;
            &lt;button type=&quot;submit&quot;&gt;Отправить сообщение&lt;/button&gt;
        &lt;/fieldset&gt;
    &lt;/form&gt;
    &lt;form action=&quot;/decode/&quot;&gt;
        &lt;fieldset&gt;
            &lt;legend&gt;Расшифровать сообщение&lt;/legend&gt;
            &lt;label for=&quot;decode-message&quot;&gt;Сообщение&lt;/legend&gt;&lt;br&gt;
            &lt;textarea id=&quot;decode-message&quot; name=&quot;message&quot; cols=&quot;30&quot; rows=&quot;10&quot;&gt;&lt;/textarea&gt;&lt;br&gt;
            &lt;button type=&quot;submit&quot;&gt;Отправить сообщение&lt;/button&gt;
        &lt;/fieldset&gt;
    &lt;/form&gt;
    &lt;form action=&quot;/shift/&quot;&gt;
        &lt;fieldset&gt;
            &lt;legend&gt;Получить сдвиг&lt;/legend&gt;
            &lt;button type=&quot;submit&quot;&gt;Отправить запрос&lt;/button&gt;
        &lt;/fieldset&gt;
    &lt;/form&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>Создадим папку, в которой будем хранить настройки образа для Nginx (ссылка на готовый Dockerfile для сборки берём из <a href="https://github.com/nginxinc/docker-nginx/tree/master/modules">примера в официальном репозитории</a>):</p>
<pre><code tabindex="0" class="language-bash">mkdir nginx
curl -o nginx/Dockerfile https://raw.githubusercontent.com/nginxinc/docker-nginx/master/modules/Dockerfile
</code></pre>
<p>Создадим файл конфигурации <code>nginx.conf</code>:</p>
<pre><code tabindex="0" class="language-bash">touch ./nginx/nginx.conf
</code></pre>
<p>Поместим в файл конфигурацию для Nginx:</p>
<pre><code tabindex="0" class="language-nginx">load_module modules/ngx_http_js_module.so;

events {}

http {
    js_path /etc/nginx/njs/;
    js_import script.js;

    server {
        listen 8080;

        root /var/www;

        location /encode/ {
            js_content script.encode;
        }

        location /decode/ {
            js_content script.decode;
        }

        location /shift/ {
            js_content script.shift;
        }
    }
}
</code></pre>
<p>Создадим файл конфигурации <code>docker-compose.yml</code>:</p>
<pre><code tabindex="0" class="language-bash">touch docker-compose.yml
</code></pre>
<p>Наполним его:</p>
<pre><code tabindex="0" class="language-yaml">version: &quot;3.9&quot;
services:
    web:
        build:
            context: ./nginx/
            args:
                ENABLED_MODULES: njs
        image: nginx-with-njs:v1
        volumes:
            - ./nginx/nginx.conf:/etc/nginx/nginx.conf
            - ./src/:/etc/nginx/njs/
            - ./index.html:/var/www/index.html
        ports:
            - &quot;80:8080&quot;
</code></pre>
<p>Обратите внимание на пути томов <code>volumes</code>. В конфигурации используются связанные тома <code>binded volumes</code>, то есть прямая ссылка на файловую систему компьютера, на котором запущен Docker. Однако ссылки внутри контейнера очень важны. Конфигурацию и скрипт нужно поместить в <code>/etc/nginx/</code> и <code>/etc/nginx/njs/</code>, соотвественно. В конфигурации дополнительно используется директива <code>js_path</code>, чтобы точно ничего не потерялось.</p>
<p>Осталось всё запустить:</p>
<pre><code tabindex="0" class="language-bash">docker-compose up
</code></pre>
<p>Теперь можно посмотреть в браузере результат нашей работы по адресу <code>http://localhost</code>. Для того чтобы остановить веб-сервер, можно использовать сочетание клавиш <kbd>Ctrl C</kbd> или соответствующий сигнал в Unix-подобных системах.</p>
<h2>Приори Инкантатем: что это было</h2>
<p>Для написания кода можно использовать TypeScript, как это описано в <a href="https://nginx.org/ru/docs/njs/typescript.html">документации</a>. В NJS <a href="https://nginx.org/ru/docs/njs/node_modules.html">можно использовать</a> модули Node.js. Например, не составит большого труда сделать сервис для работы с протоколом <a href="https://grpc.io/">gRPC</a> с помощью библиотеки <a href="https://www.npmjs.com/package/protobufjs">protobufjs</a>. Чтобы двигаться дальше, посмотрите <a href="https://github.com/xeioex/njs-examples">примеры</a> использования скриптов NJS.</p>
<p>Разработчику не нужно писать код на Си для реализации модуля Nginx, а потом код на JavaScript для клиентского веб-приложения. Можно переиспользовать один и тот же модуль. Вы, конечно, можете писать скрипты на Perl или на PHP, но JavaScript иногда и правда является лучшим решением. А скорость исполнения в случае NJS сравнима с кодом на Си, помните об этом!</p>

                    ]]></description><pubDate>Thu, 17 Jun 2021 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/magic-njs/</guid></item><item><title>Онлайн-обучение: взгляд очевидца</title><link>https://web-standards.ru/articles/online-education-1/</link><description><![CDATA[
                        <p>В последнее время онлайн-образование очень популярно. Если не каждый день, то каждую неделю запускается новый образовательный курс. Все, от крупных компаний до инста-блогеров, создают свои курсы на разные темы.</p>
<p>Я решила собрать и структурировать весь накопившийся багаж знаний на эту тему. В основном я буду говорить о курсах по разработке, но многие тезисы вполне подходят и к другим сферам.</p>
<p>Это первая часть серии статей про онлайн-образование. Пока я планирую написать три статьи, но посмотрим как пойдёт =)</p>
<p>В этой части я расскажу основы теории обучения взрослых — андрагогики, а также порассуждаю о плюсах и минусах обучения в формате онлайн.</p>
<p>В этой статье будут встречаться комментарии других авторов. Таким образом я постараюсь дать полную картину, а не только своё личное мнение.</p>
<p>Много где я буду говорить обобщённо о среднестатистическом студенте онлайн-курсов. Если вы или ваши знакомые не подпадают под эти определения, то это скорее исключение из правил.</p>
<p>Приятного чтения!</p>
<h2>TL;DR</h2>
<ol>
<li>Существует целая наука по обучению взрослых — андрагогика.</li>
<li>Если вы хотите сделать хороший онлайн-курс учитывайте особенности обучения взрослых.</li>
<li>Если вы учитесь, то следите за обстановкой, в которой вы учитесь. Температура, влажность, удобная мебель и достаточное освещение — это очень важно.</li>
<li>Если вы преподаёте, то готовьтесь к лекции. Определите для себя несколько комфортных техник переключения внимания, подумайте над хорошими примерами для объяснения сложных вещей. Эффективный вебинар не должен длиться дольше 45 минут.</li>
</ol>
<h2>Популярность МООК</h2>
<p>Последние лет пять на российском рынке образование бум онлайна. На рынке преобладают «<a href="https://ru.wikipedia.org/wiki/%D0%9C%D0%B0%D1%81%D1%81%D0%BE%D0%B2%D1%8B%D0%B9_%D0%BE%D1%82%D0%BA%D1%80%D1%8B%D1%82%D1%8B%D0%B9_%D0%BE%D0%BD%D0%BB%D0%B0%D0%B9%D0%BD-%D0%BA%D1%83%D1%80%D1%81">массовые открытые онлайн-курсы</a>», сокращённо МООК. Это лишь одна из форм дистанционного образования, но именно она получила распространение в интернете.</p>
<p>Первые видео лекций учебных заведений начали появляться в интернете ещё в конце 1990-х. Но беспрецедентная популярность онлайн-курсов случилась в 2020–2021 годах. Причины очевидны — у людей из-за локдауна появилось больше свободного времени и пропала возможность выходить куда-либо. Многие решили посвятить это время самообразованию. Часть людей потеряла работу и решила сменить профессию на более надёжную в сегодняшнем мире.</p>
<p>Давайте поговорим об этом большом, активно растущем рынке подробнее. Начнём с самых основ.</p>
<h2>Андрагогика</h2>
<figure>
<blockquote>
<p>Средний возраст взрослого слушателя онлайн-программ — 35 лет в столице и 33 года в регионах.</p>
</blockquote>
<figcaption>Исследование EdMarket Research, 2019 год.</figcaption>
</figure>
<p>Большинство онлайн-курсов ориентировано на взрослых работающих людей, желающих сменить род деятельности или повысить квалификацию на текущей позиции. У них есть потребность в заработке, наверняка есть семья, сложившийся уклад жизни. А значит не очень много времени на изучение нового.</p>
<p>Кроме того не стоит забывать, что учиться во взрослом возрасте гораздо сложнее, чем в детстве. Мозг с меньшей охотой создаёт новые нейронные связи, тратит на это энергию.</p>
<p>Эти и некоторые другие особенности обучения взрослых учитывает наука — андрагогика. Её суть заключается в понимании психологических и физических особенностей усвоения новой информации взрослым человеком и формировании на основе этого правильного подхода к преподаванию.</p>
<p>Основные особенности, которые должны учитываться при обучении взрослых:</p>
<ul>
<li>осознанное отношение к обучению;</li>
<li>стремление к самостоятельности;</li>
<li>связь с реальностью: знания нужны для решения конкретных проблем;</li>
<li>практическая направленность: стремление применить знания и навыки;</li>
<li>существующий жизненный опыт;</li>
<li>влияние общества, особенности быта и доступное время.</li>
</ul>
<h3>Осознанное обучение</h3>
<p>Взрослый человек самостоятельно, без явного давления общества, принимает решение продолжать обучение. Нет родителей, которые заставляют ходить в школу. Общество и рынок не диктуют необходимость диплома. При наличии интернета любой человек может изучить любую дисциплину самостоятельно.</p>
<p>Поэтому если человек выбирает пойти на курсы, то он делает это исключительно по собственной инициативе, обменивая деньги на различные нематериальные преимущества.</p>
<p>Поскольку решение принято самостоятельно, то и отношение к учёбе совершенно другое. Никто не заставляет учиться из-под палки. Студент самостоятельно выбирает степень своей занятости и погружённость в процесс, вынужден самостоятельно поддерживать уровень мотивации для продолжения обучения.</p>
<h3>Самостоятельность</h3>
<p>Взрослым важно давать определённую свободу выбора методов, форм и средств обучения, сроков, времени и места обучения, оценки его результатов. Онлайн-формат даёт свободу выбора времени и места обучения. Поэтому при составлении курса нужно предусмотреть разнообразные форматы обучающих материалов и сформулировать чёткие критерии оценки выполненной работы.</p>
<p>Взрослый студент будет стараться понять материал или решить задачу самостоятельно, не обращаясь за помощью к другим участникам процесса. У этого могут быть разные причины. Страх показаться глупым в глазах других, по моим наблюдениям, сильнее всего мешает просить помощи.</p>
<h3>Связь с реальностью</h3>
<p>Поскольку обучение для взрослого человека это инструмент достижения конкретных целей, то и получаемые знания должны быть максимально прикладными. Если студент чувствует, что курс оторван от реальности и не приближает его к достижению поставленной цели, то испытывает глубокое разочарование и недовольство.</p>
<h3>Практическая направленность</h3>
<p>В детстве, в школе, нам интересно в принципе всё, что интересно преподаётся. Для ребёнка важно поглотить как можно больше информации о мире, чтобы расширить кругозор и определить сферу интересов. Взрослый же человек редко изучает что-то просто из интереса. Это скорее либо выбор новой профессии, либо углубление в текущую.</p>
<p>Поэтому пространные философские лекции вызывают больше недовольства, чем одобрения. Непонятно как это пойти и применить здесь и сейчас. Разве только козырнуть в разговоре.</p>
<h3>Жизненный опыт</h3>
<p>Обучение детей, в сравнении со взрослыми, гораздо проще. У ребёнка ещё нет собственного представлении о том, как устроен мир. Ребёнок — чистый лист. Всю информацию он будет усваивать как истину.</p>
<p>Взрослый же человек будет стараться переложить полученные знания на уже существующий опыт, провести параллели, сравнить одно с другим. При этом у всех людей опыт совершенно разный. И это существенно усложняет создание хорошего образовательного продукта, понятного абсолютно всем.</p>
<h3>Общество, быт и время</h3>
<p>Кто-то из вас в детстве наверняка слышал от родителей фразу «школа — твоя работа». И правда, в детстве мы не занимаемся практически ничем кроме обучения. Нам дано всё доступное время на усвоение новой информации. 11 лет школы, а у кого-то ещё 5 лет университета, день за днём проходят в изучении и усвоении.</p>
<p>У взрослого человека есть работа, семья, друзья, хобби, бытовые заботы. Взрослый всегда вынужден думать о деньгах. Поэтому чем быстрее он окончит курсы, тем быстрее сможет применить знания в жизни. К тому же он не может смотреть вебинары восемь часов в день, а потом ещё столько же тратить на домашние задания или дополнительные материалы. Информация должна выдаваться небольшими порциями и максимально концентрированно.</p>
<p><img src="https://web-standards.ru/articles/online-education-1/images/1.png" alt="Рисованный персонаж сидит на стуле и держит на коленях ноутбук"></p>
<h2>Зачем нужны онлайн-курсы</h2>
<p>Я часто встречаю позицию, что онлайн-курсы в принципе не нужны и в интернете есть всё для самостоятельного обучения.</p>
<p>Действительно, мы живём в уникальное время полной доступности информации. Но для самостоятельного качественного обучения одной только информации недостаточно. К тому же большое количество материалов если уж не ложные, то часто неактуальные. Не имея опыта понять это с первого взгляда практически невозможно.</p>
<h3>Доступность знаний</h3>
<p>Живя в небольшом городе, многие желающие получить хорошее высшее образование вынуждены переезжать в другой город или даже страну. Обучение онлайн даёт возможность получать качественное образование вне зависимости от места проживания.</p>
<blockquote>
<p>+ Комфортно для людей с соц тревожностью</p>
<p>+ Доступно для людей с ограниченными возможностями</p>
<p>+ Живя в мухосрани можешь получить хорошую профессию</p>
<p>+ Можно пересмотреть запись, нет необходимости спешить за лектором</p>
<p><a href="https://twitter.com/julish_k/status/1402711331753631758">Julish</a></p>
</blockquote>
<h3>Экономия времени</h3>
<p>Для большинства взрослых людей критическая нехватка времени — большая проблема. Поэтому возможность не тратить время на дорогу и посещение лекций очно играет огромную роль при выборе между офлайн- и онлайн-форматами.</p>
<p>Записи лекций на онлайн-курсах позволяют самостоятельно выбирать время для обучения и не винить себя из-за пропущенной лекции.</p>
<blockquote>
<p>+ Крутые возможности для общения. Можно познакомиться с людьми со всего мира, разных профессий, с которыми ты в обычной жизни бы и не встретился. Для этого нужны чатики и сообщества, само не случится</p>
<p>+ Не надо тратить время на дорогу и сборы, для меня это кайф</p>
<p><a href="https://twitter.com/iamlerun/status/1402714189651709952">Лера Зелёная</a></p>
</blockquote>
<h3>Готовая программа</h3>
<p>Основной ресурс человека — это время. У взрослого это время часто в дефиците. Составление плана обучения, ментальной карты знаний, поиск достоверной и актуальной информации, углубление в темы и практика потребуют ощутимого времени. Из этого вытекает первое преимущество курсов — готовая актуальная программа. Студент фактически обменивает деньги на собственное сэкономленное время. За него уже всё придумали, собрали, отсеяли, формализовали и он получает самую квинтэссенцию полезности.</p>
<h3>Актуальность</h3>
<p>Кажется, что уже существующая система высшего образования должна закрывать потребность в новых знаниях для получения новой профессии. К сожалению, как мы все знаем, эта система сильно закостенела.</p>
<p>Программы обучения тех же программистов, часто углублённые и развесистые, на деле имеют мало прикладной пользы. Я немало встречала студентов последних курсов ведущих университетов, которые параллельно с высшим образованием обучались на онлайн-курсах. Потому что понимали, что после окончания университета они не смогут конкурировать на рынке труда со своими обширными, но оторванными от реальности знаниями. Отсюда второе преимущество онлайн образования — актуальность.</p>
<p>Поскольку это целиком и полностью коммерческий продукт, в интересах создателей поддерживать его максимальную актуальность, чтобы конкурировать на рынке образования. В результате студент таких курсов получает именно те знания, которые пригодятся в работе сразу после окончания.</p>
<h3>Наставник</h3>
<p>Если человек выбирает путь самостоятельного изучения, то рано или поздно он столкнётся с необходимостью задать вопрос. Очень круто, если при этом рядом окажется человек с релевантным опытом и готовностью помогать. Но чаще всего такого ментора рядом нет. И человек либо тратит время на попытки самостоятельно понять сложный материал, либо, после серии неудач, теряет мотивацию и интерес к обучению.</p>
<p>На онлайн-курсах студента сопровождают на всём пути обучения. Преподаватели, наставники, сокурсники, аспиранты готовы объяснять непонятные моменты и подсказывать. Это существенно увеличивает шансы не потерять мотивацию и дойти до конца.</p>
<h3>Помощь с трудоустройством</h3>
<p>Как правило, одной из основных метрик образовательных платформ является количество трудоустроенных студентов. Это сильно помогает продвигать продукты и увеличивает их ценность в глазах потенциальных студентов. Поэтому при курсах часто существуют разного рода отделы по трудоустройству. Кто-то помогает составить резюме, кто-то находит подходящие вакансии. У некоторых платформ есть партнёрские программы с компаниями, готовыми брать на работу выпускников. Одним словом, все стараются вас, студентов, трудоустроить.</p>
<p>При самостоятельном обучении задача поиска работы остаётся целиком на вас. Никто не придёт и не предложит вам помощь в этом непростом деле. Ситуация усугубляется большим количеством начинающих разработчиков на рынке труда. Конкуренция огромная. Поиски могут затянуться.</p>
<p>При найме разработчика, работодатель редко интересуется вашим дипломом и понимает, что высшее образование в текущем виде не соответствует требованиям рынка. В большей степени он смотрит на ваш опыт и портфолио.</p>
<p>И пусть портфолио у всех выпускников вашего курса практически идентичное, но сертификат об окончании курсов так или иначе даёт работодателю возможность оценить уровень ваших знаний. Чаще всего сертификат — это подтверждение, что вы так или иначе прошли программу этой школы и знаете её основные пункты. Если эта программа совпадает с запросами работодателя, то это играет вам на руку.</p>
<blockquote>
<p>Самый простой способ отличить плохие курсы от хороших, на мой взгляд, это внимательно послушать их обещания про трудоустройство. Гарантия сразу после курсов — это враньё. Помощь — совсем другое дело, но не воспринимайте это как гарантию.</p>
<p>Единственная схема, при которой курсы по-настоящему заинтересованы в вашем трудоустройстве — это когда вы заключате договор, по которому потом выплачиваете проценты с будущей зарплаты. Но эта схема пока не распространена в русскоязычном образовании.</p>
<p><a href="https://twitter.com/pepelsbey">Вадим Макеев</a></p>
</blockquote>
<h2>Минусы онлайн-формата</h2>
<h3>Физические</h3>
<p>Существует большой документ из СанПиНа (санитарных правил и норм), в котором описываются конкретные требования к учебным классам. Там регламентировано всё, вплоть до температуры и влажности воздуха. Эти рекомендации призваны не просто проконтролировать всё на свете, но и создать благоприятную и безопасную обстановку для обучения.</p>
<p>В случае с онлайн-форматом студент находится в совершенно непредсказуемых для преподавателя условиях. Он может смотреть вебинар в метро, лёжа на кровати или в процессе готовки. Помещение может плохо проветриваться, свет будет слишком тусклым, а стул и стол неудобными. Все эти физические факторы не способствуют длительной концентрации и усвоению материала.</p>
<p>Длина уроков в школе и университете тоже взята не с потолка. При благоприятных внешних факторах ребёнок способен сохранять концентрацию и усваивать новый материал примерно 15 минут. У взрослого это время может достигать 25 минут. Средний вебинар на курсах длится час-полтора. Что значительно превышает время эффективности.</p>
<h3>Педагогические</h3>
<p>При подготовке офлайн-уроков хороший учитель использует большое количество разнообразных учебных инструментов: опросы, работа в командах, викторины, самостоятельная работы, эксперименты и так далее. Хороший урок построен на периодической смене активностей. Это заставляет мозг переключаться и увеличивает эффективное время обучения. Помните про 15 минут? При смене деятельности счётчик сбрасывается, начинают работать другие участки мозга и ребёнок не теряет интерес.</p>
<p>В онлайне набор этих инструментов существенно сокращается. Как правило, вебинар состоит из монотонного повествования теоретического материала. Хорошие преподаватели периодически шевелят аудиторию вопросами или приглашениями к диалогу. Но это не полноценное общение. Преподаватель говорит, а студенты пишут. Это не позволяет развернуть полноценную дискуссию с вовлечением всех участников учебного процесса. Если мы говорим об изучении программирования, то на лекции может присутствовать ещё лайв-кодинг.</p>
<p>В силу скромного набора инструментов лекции в онлайне менее эффективны с точки зрения усвоения знаний.</p>
<p>Кроме того, находясь со студентами в аудитории преподаватель может видеть реакцию. Если студенты заняты своим делом, отвлекаются, не следят за преподавателем глазами — что-то идёт не так. Хороший, заинтересованный преподаватель всегда реагирует на эти невербальные знаки и корректирует лекцию прямо на ходу.</p>
<p>Преподаватель в онлайне часто разговаривает сам с собой. Он не видит людей, не видит их глаз, а значит не может получить обратную связь и быстро подстроиться под обстоятельства.</p>
<h3>Методические</h3>
<p>Школьная или университетская программы составляются и оттачиваются на протяжении длительного времени. Преподаватель знает свою дисциплину от и до. Знает, с чем у студентов чаще всего возникают сложности, какой пример объяснения сложной концепции самый удачный.</p>
<p>Из-за постоянной актуализации материалы онлайн-курсов находятся в процессе постоянной доработки. Периодически к студентам попадает сырой материал. Преподаватели часто знакомятся с новым материалом непосредственно перед лекцией. Отсюда возникают парадоксы «чтения с листа» и неспособности быстро найти удачный пример для объяснения.</p>
<blockquote>
<p>Самый главный минус, который кажется обсуждается уже давно — это отсутствие плана развития. То есть онлайн-образование построено таким образом, что каждый курс сепарирован от полного набора инструментов и навыков специалиста.</p>
<p><a href="https://twitter.com/eessoo_es/status/1402889771211538433">Elena Saharova</a></p>
</blockquote>
<h3>Количество студентов</h3>
<p>В школьных классах как правило небольшое количество детей. Это позволяет учителю изучить характер и особенности каждого ученика и учесть их в процессе урока.</p>
<p>На онлайн-курсах часто потоки состоят из огромного количества студентов. Преподаватели сменяются от лекции к лекции. Из-за этого участники процесса не знакомы друг с другом в достаточной степени. Пропадает индивидуальность, что тоже плохо сказывается на процессе.</p>
<h3>Недостаток квалификации</h3>
<p>Ещё одним существенным минусом является отсутствие педагогического образования или минимальной подготовки у преподавателя. Чаще всего учителя на курс подбираются по степени их успешности в преподаваемой области. Но их опыт преподавания не важен. Поскольку преподавание — полноценная профессия, не все обладают этим навыком с рождения. А успехи в изучаемой области не квалифицируют человека как хорошего учителя.</p>
<p>Лектор может обладать невероятным багажом знаний, его лекции будут очень информативны, но сложно восприниматься. Что затруднит запоминание информации студентом.</p>
<h3>Качество материала</h3>
<p>Поскольку тема онлайн-образования максимально популярна сейчас и каждый стремится создать свой курс, рынок переполнен некачественными образовательными продуктами.</p>
<p>Не все курсы прорабатываются с методической, педагогической и содержательной точек зрения. Для раздувания курса до нужного размера его наполняют пустой информацией.</p>
<p>Сложно во всём этом инфошуме и разнообразии выбрать действительно качественный продукт.</p>
<blockquote>
<p>Для меня как для ученика плюс — это время, конечно, которым я могу управлять (если на курсах есть запись).</p>
<p>Минус — очень сложно выбрать из всего дикого разнообразия что-то действительно хорошее (обычно материал — ВОДА). Полагаешься только на репутацию преподов, и то…</p>
<p><a href="https://twitter.com/GlafiraZhur/status/1402872003057655812">Glafira Zhur</a></p>
</blockquote>

                    ]]></description><pubDate>Sun, 23 May 2021 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/online-education-1/</guid></item><item><title>Оптимизация метрик Web Vitals с помощью Lighthouse</title><link>https://web-standards.ru/articles/web-vitals-lighthouse/</link><description><![CDATA[
                        <p>В этой статье мы рассмотрим новые возможности инструментов Lighthouse, PageSpeed и DevTools, чтобы помочь разобраться с тем, как улучшить ваш сайт <a href="https://web.dev/vitals">по метрикам Web Vitals</a>.</p>
<p>Коротко вспомним, что это за инструменты. <a href="https://github.com/GoogleChrome/lighthouse">Lighthouse</a> — это автоматический, постоянно обновляемый инструмент с открытым кодом для улучшения качества страниц. Вы можете найти его в инструментах разработчика <a href="https://developers.google.com/web/tools/chrome-devtools">Chrome DevTools</a> и запустить в нём любые страницы: публичные или скрытые за авторизацией. Вы также можете найти Lighthouse <a href="https://developers.google.com/speed/pagespeed/insights/?url=https%3A%2F%2Fstore.google.com">в PageSpeed Insights</a>, <a href="https://github.com/GoogleChrome/lighthouse-ci">CI</a> и <a href="https://www.webpagetest.org/easy">WebPageTest</a>.</p>
<p>Lighthouse 7.x включает новые возможности, например, скриншоты элементов интерфейса, влияющих на пользовательские метрики, вроде тех, что вызывают смещение раскладки.</p>
<video width="1920" height="1080" autoplay loop muted>
    <source src="https://web-standards.ru/articles/web-vitals-lighthouse/video/1.webm" type="video/webm">
    <source src="https://web-standards.ru/articles/web-vitals-lighthouse/video/1.mp4" type="video/mp4">
</video>
<p>Мы также добавили возможность делать скриншоты элементов страницы в PageSpeed Insights, чтобы легче было найти проблемы, которые возникают при разовой загрузке страницы.</p>
<p><img src="https://web-standards.ru/articles/web-vitals-lighthouse/images/1.png" alt="Возможность делать скриншот элемента интерфейса пользователя с подсветкой узла DOM-дерева, участвующего в сдвиге раскладки на странице."></p>
<h2>Метрики Core Web Vitals</h2>
<p>Lighthouse может <a href="https://web.dev/vitals-measurement-getting-started/#measuring-web-vitals-using-lab-data">синтетически</a> измерять метрики <a href="https://web.dev/vitals/">Core Web Vitals</a>, включая <a href="https://web.dev/lcp/">Largest Contentful Paint</a>, <a href="https://web.dev/cls/">Cumulative Layout Shift</a> и <a href="https://web.dev/tbt/">Total Blocking Time</a> (аналог <a href="https://web.dev/fid/">First Input Delay</a> для измерения в лабораторных условиях). Эти метрики отражают загрузку, стабильность раскладки и готовность страницы к взаимодействию. Также есть и другие метрики, такие как <a href="https://web.dev/first-contentful-paint/">First Contentful Paint</a>, с запасом <a href="https://developer.chrome.com/devsummit/sessions/future-of-core-web-vitals/">на будущее Core Web Vitals (CWV)</a>.</p>
<p>Раздел «Metrics» в отчёте Lighthouse включает лабораторные версии этих метрик. Его можно использовать как обзор аспектов пользовательского опыта, которые требует вашего внимания.</p>
<p><img src="https://web-standards.ru/articles/web-vitals-lighthouse/images/2.png" alt="Метрики производительности Lighthouse."></p>
<p>Lighthouse фокусируется на оценке пользовательского опыта при начальной загрузке страницы в лабораторных условиях, эмулируя медленный телефон или десктоп. Если сдвиги макета или длительные JavaScript-таски возникнут на странице уже после её загрузки, лабораторные метрики этого не отразят. Для измерения показателей после загрузки страницы стоит обратиться к вкладке «Performance» инструментов разработчика, <a href="https://search.google.com/search-console/about">Search Console</a>, <a href="https://chrome.google.com/webstore/detail/web-vitals/ahfhijdlegdabablpippeagghigmibma?hl=en">расширения Web Vitals</a> или <a href="https://web.dev/vitals-measurement-getting-started/#measuring-web-vitals-using-rum-data">RUM</a>.</p>
<figure>
<p><img src="https://web-standards.ru/articles/web-vitals-lighthouse/images/3.png" alt="Трек Web Vitals на панели производительности в инструментах разработчика."></p>
<figcaption>
    На панели Web Vitals во вкладке Performance инструментов разработчика можно включить индикаторы, соответствующие метрикам. Например, индикаторы для метрики Layout Shift (LS) показаны на скриншоте выше.
</figcaption>
</figure>
<p><a href="https://web.dev/vitals-field-measurement-best-practices/">Полевые метрики</a>, которые вы можете найти <a href="https://developers.google.com/web/tools/chrome-user-experience-report">в отчёте Chrome User Experience (CrUX)</a> или <a href="https://developer.mozilla.org/en-US/docs/Web/Performance/Rum-vs-Synthetic">в RUM</a>, не имеют этих ограничений и полезно дополняют лабораторные метрики. С другой стороны, полевые данные не могут предоставить диагностическую информацию, которую вы можете получить от лабораторных метрик, вот так они и сочетаются вместе.</p>
<h2>Где можно улучшить метрики Web Vitals</h2>
<h3>Largest Contentful Paint (LCP)</h3>
<p>LCP — характеристика воспринимаемой пользователем загрузки. Она отмечает точку в процессе загрузки страницы, когда главный (или самый большой) контент загрузился и стал видим пользователю.</p>
<p>В Lighthouse есть отчёт «Largest Contentful Paint element», который позволяет выделить такой LCP-элемент. Если навести на элемент в отчёте, он подсветится на странице.</p>
<p><img src="https://web-standards.ru/articles/web-vitals-lighthouse/images/4.png" alt="Элемент, отрисовка которого занимает наибольшее время (LCP-элемент)."></p>
<p>Если этот элемент картинка, эта информация подскажет, что вам стоит оптимизировать её загрузку. В Lighthouse есть несколько отчётов по оптимизации графики, которые помогают понять, можно ли лучше сжать картинки, изменить их размеры или использовать более современный формат.</p>
<p><img src="https://web-standards.ru/articles/web-vitals-lighthouse/images/5.png" alt="Аудит размера картинок."></p>
<p>Вам может пригодиться <a href="https://gist.github.com/anniesullie/cf2982342337fd1b2be95c2d5fe5ea06">LCP-букмарклет</a> Энни Салливан, который поможет быстро найти LCP-элемент и подсветить его красной рамкой в один клик.</p>
<p><img src="https://web-standards.ru/articles/web-vitals-lighthouse/images/6.png" alt="Подсвеченный LCP-элемент."></p>
<h3>Предзагрузка картинок для улучшения LCP</h3>
<p>Чтобы улучшить метрику LCP, вы можете <a href="https://web.dev/preload-responsive-images/">предзагрузить</a> хиро-картинки <em>(например, обложки статей — прим. редактора),</em> если браузер находит их слишком поздно. Одна из причин позднего обнаружения картинок — если сначала нужно загрузить JS-бандл, чтобы узнать о картинках, которые нужно загрузить.</p>
<p><img src="https://web-standards.ru/articles/web-vitals-lighthouse/images/7.png" alt="Предварительная загрузка картинок."></p>
<p><strong>Предзагрузкой нужно пользоваться аккуратно</strong>. Пропускная способность соединения на первом этапе загрузки страницы невелика и предзагрузка картинок может повлиять на загрузку других важных ресурсов. Для эффективной предзагрузки убедитесь, что ресурсы расположены в правильном порядке, чтобы не привести к ухудшению других метрик, когда другие ресурсы тоже считаются важными (например, критический CSS, JS, шрифты). Читайте подробнее <a href="https://docs.google.com/document/d/1ZEi-XXhpajrnq8oqs5SiW-CXR3jMc20jWIzN5QRy1QA/view">о цене предзагрузки</a> (Google Docs).</p>
<p>Начиная с версии 6.5, Lighthouse предлагает возможности для применения этой оптимизации.</p>
<p>Есть несколько популярных вопросов о предварительной загрузке LCP-картинок, которые стоит рассмотреть.</p>
<p>Можно ли предварительно загрузить адаптивные картинки? <a href="https://web.dev/preload-responsive-images/#imagesrcset-and-imagesizes">Да, можно</a>. Скажем, у вас есть набор хиро-картинок для разных размеров экрана, которые описаны с помощью <code>srcset</code> и <code>sizes</code>, например:</p>
<pre><code tabindex="0" class="language-html">&lt;img src=&quot;lighthouse.jpg&quot;
    srcset=&quot;
        lighthouse_400px.jpg 400w,
        lighthouse_800px.jpg 800w,
        lighthouse_1600px.jpg 1600w&quot;
    sizes=&quot;50vw&quot; alt=&quot;Полезный Lighthouse.&quot;&gt;
</code></pre>
<p>Благодаря атрибутам <code>imagesrcset</code> и <code>imagesizes</code>, добавленным в <code>&lt;link&gt;</code>, вы можете предзагрузить адаптивные картинки, используя ту же логику из <code>srcset</code> и <code>sizes</code>:</p>
<pre><code tabindex="0" class="language-html">&lt;link rel=&quot;preload&quot; as=&quot;image&quot;
    href=&quot;lighthouse.jpg&quot;
    imagesrcset=&quot;lighthouse_400px.jpg 400w,
        lighthouse_800px.jpg 800w,
        lighthouse_1600px.jpg 1600w&quot;
    imagesizes=&quot;50vw&quot;&gt;
</code></pre>
<p>Подсветит ли отчёт возможности предзагрузки, если LCP-картинка задана с помощью CSS-фона? Да, конечно.</p>
<p>Любая картинка, помеченная как LCP (будь то <code>&lt;img&gt;</code> или фоновая картинка в CSS), это кандидат для аудита, если она обнаружена в водопаде на глубине три уровня и больше.</p>
<h3>Поиск влияний на CLS</h3>
<p>Кумулятивный сдвиг раскладки (Cumulative Layout Shift, CLS) — это оценка визуальной стабильности. Она показывает, насколько контент страницы визуально сдвигается во время загрузки. Lighthouse содержит специальный отчёт «Avoid large layout shifts» для отладки CLS.</p>
<p>Этот отчёт выделяет элементы DOM, которые вносят основной вклад в сдвиги на странице. В колонке «Element» отчёта Lighthouse вы увидите список таких элементов DOM, а справа — их вклад в CLS.</p>
<p><img src="https://web-standards.ru/articles/web-vitals-lighthouse/images/8.png" alt="Оценка Cumulative Layout Shift в Lighthouse выделяет узел DOM-дерева, который даёт существенный вклад в индикатор CLS."></p>
<p>Благодаря новой возможности Lighthouse делать скриншот элемента мы теперь можем видеть превью ключевого элемента из отчёта и увеличивать его для более детального просмотра:</p>
<p><img src="https://web-standards.ru/articles/web-vitals-lighthouse/images/9.png" alt="Клик по скриншоту элемента увеличивает его."></p>
<p>Для CLS после загрузки страницы, бывает полезно обозначить элементы, которые оказали наибольшее влияние на сдвиг раскладки. Это можно найти в сторонних инструментах, например, <a href="https://speedcurve.com/blog/web-vitals-user-experience/">в панели Core Web Vitals</a> от SpeedCurve или <a href="https://defaced.dev/tools/layout-shift-gif-generator/">в Layout Shift GIF Generator</a> от Defaced, который мне очень нравится:</p>
<p><img src="https://web-standards.ru/articles/web-vitals-lighthouse/images/10.gif" alt="Генератор сдвига раскладки страницы, с выделением процесса сдвига."></p>
<p>Для анализа сдвигов раскладки для всего сайта мне помогает <a href="https://support.google.com/webmasters/answer/9205520">отчёт Core Web Vitals</a>, который создаёт Google Search Console. Этот отчёт позволяет видеть страницы сайта с высоким значением CLS, что помогает понять, на какие файлы шаблонов мне стоит обратить внимание прежде всего:</p>
<p><img src="https://web-standards.ru/articles/web-vitals-lighthouse/images/11.png" alt="Google Search Console отображает страницы, на которых есть проблемы с CLS."></p>
<p>Чтобы улучшить показатель CLS для веб-шрифтов, обратите внимание на новый дескриптор <a href="https://groups.google.com/a/chromium.org/g/blink-dev/c/1PVr94hZHjU/m/J0xT8-rlAQAJ"><code>size-adjust</code></a> для <code>@font-face</code>. Он позволяет изменять размер базовых шрифтов для уменьшения значения CLS.</p>
<h3>Поиск картинок без размеров для улучшения CLS</h3>
<p>Чтобы <a href="https://web.dev/optimize-cls/#images-without-dimensions">уменьшить</a> сдвиг раскладки, вызванный загрузкой ресурсов без заданных размеров, задайте картинкам и видео атрибуты <code>width</code> и <code>height</code>. Это помогает браузеру выделить достаточное место на странице, пока картинки или видео грузятся.</p>
<p><img src="https://web-standards.ru/articles/web-vitals-lighthouse/images/12.png" alt="Поиск картинок без ширины и высоты."></p>
<p>Читайте статью «<a href="https://www.smashingmagazine.com/2020/03/setting-height-width-images-important-again/">Setting Height And Width On Images Is Important Again</a>» для лучшего понимания важности указания размеров и соотношения сторон для картинок.</p>
<h3>Поиск влияния на CLS от рекламы</h3>
<p>Отчёт «<a href="https://developers.google.com/publisher-ads-audits">Publisher Ads для Lighthouse</a>» позволит вам найти возможности загрузку рекламы на ваших страницах, включая роль рекламы в сдвиге раскладки и долгих операций, которые могут отложить интерактивность страницы для пользователей. Вы можете включить этот инструмент в Lighthouse с помощью Community Plugins.</p>
<p><img src="https://web-standards.ru/articles/web-vitals-lighthouse/images/13.png" alt="Аудит рекламных баннеров на странице, которые влияют на сдвиг раскладки и увеличивают время готовности к взаимодействию с пользователем."></p>
<p>Помните, что рекламные баннеры — это элементы, которые по статистике <a href="https://web.dev/optimize-cls/#ads-embeds-and-iframes-without-dimensions">вносят наибольший вклад</a> в сдвиг раскладки. Важно:</p>
<ul>
<li>быть осторожными при размещении рекламы в верхней части вьюпорта;</li>
<li>зарезервировать больше места для рекламы, чтобы избежать сдвига.</li>
</ul>
<h3>Избегайте раздельных анимаций</h3>
<p>Анимации, не объединённые в общий композитный слой рендеринга, могут дёргаться на слабых устройствах, если исполнение сложных JavaScript-тасков занимает главный поток. Такие анимации могут вызывать и сдвиги раскладки.</p>
<p>Если Chrome обнаруживает, что анимация не может быть выделена в отдельный слой, он сообщает об этом в DevTools. Это позволяет составить список всех элементов, для которых анимация не была композитной и выяснить причину. Вы можете найти эту информацию в отчёте «<a href="https://web.dev/non-composited-animations/">Avoid non-composited animations</a>».</p>
<p><img src="https://web-standards.ru/articles/web-vitals-lighthouse/images/14.png" alt="Отчёт об анимациях, не объединённых в общий слой."></p>
<h3>Отладка метрик FID, TBT, LT</h3>
<p>Метрика First Input измеряет время от первой попытки взаимодействия со страницей (например, когда они кликают по ссылке, кнопке или использую JS-контрол), до момента, когда браузер действительно начинает обрабатывать события в ответ на это взаимодействие. Долгие JavaScript-таски могут повлиять на эту метрику и на её прокси-метрику Total Blocking Time.</p>
<p><img src="https://web-standards.ru/articles/web-vitals-lighthouse/images/15.png" alt="Отчёт о долгих JS-тасках в основном потоке."></p>
<p>Lighthouse включает отчёт «<a href="https://web.dev/long-tasks-devtools/">Avoid long main-thread tasks</a>», которая перечисляет долгие таски в основном потоке. Это помогает отыскать самое большое влияние на задержку первого взаимодействия. В левой колонке вы можете увидеть адрес скрипта, ответственного за долгие таски в главном потоке.</p>
<p>Справа вы можете увидеть длительность выполнения этих тасок. Отмечу, что долгие таски — это те, что занимают более 50 миллисекунд. Такая длительность блокирует основной поток настолько, чтобы повлиять на частоту смены кадров или задержку первого взаимодействия.</p>
<p>Из сторонних сервисов для мониторинга работы основного потока мне понравился <a href="https://calibreapp.com/docs/features/main-thread-execution-timeline">Calibre</a>, который отображает на оси времени долгие таски, родительские и дочерние.</p>
<p><img src="https://web-standards.ru/articles/web-vitals-lighthouse/images/16.png" alt="JS-таски в основном потоке, отображённые с помощью Calibre."></p>
<h3>Блокировка сетевых запросов в Lighthouse</h3>
<p>Инструменты разработчика Chrome поддерживают <a href="https://developers.google.com/web/updates/2017/04/devtools-release-notes#block-requests">блокировку сетевых запросов</a>, чтобы увидеть вклад каждого из загружаемых ресурсов, если они доступны или нет. Это может оказаться важным для понимания вклада каждого отдельного скрипта, который может повлиять на такие метрики, как Total Blocking Time (TBT) и Time to Interactive (TTI).</p>
<p>Блокировка сетевых запросов также работает в Lighthouse! Давайте взглянем на отчёт Lighthouse для сайта. Например, «Performance» выдаёт 63 из 100 очков с TBT, равным 400 мс. Покопавшись, мы найдём загрузку полифила Intersection Observer в Chrome, который для этого браузера не нужен. Заблокируем его!</p>
<p><img src="https://web-standards.ru/articles/web-vitals-lighthouse/images/17.png" alt="Блокировка сетевых запросов."></p>
<p>Мы можем кликнуть правой кнопкой на скрипте в инструментах разработчика на панели «Network» и выбрать «Block request URL». Здесь мы сделали это для полифила Intersection Observer.</p>
<p><img src="https://web-standards.ru/articles/web-vitals-lighthouse/images/18.png" alt="Заблокировать запрашиваемый URL в инструментах разработчика."></p>
<p>Затем мы перезапускаем Lighthouse. На этот раз мы видим улучшение показателя Performance (70/100) и Total Blocking Time (с 400 мс до 300 мс).</p>
<p><img src="https://web-standards.ru/articles/web-vitals-lighthouse/images/19.png" alt="После блокировки дорогого сетевого запроса."></p>
<h3>Замена дорогих сторонних виджетов на заглушки</h3>
<p>Использование на странице сторонних ресурсов для видео, постов из социальных сетей или виджетов является обычной практикой. По умолчанию, большинство таких виджетов стараются загрузиться сразу же и могут существенно отразиться на пользовательском опыте. Это очень расточительно, особенно если такие виджеты некритичны — то есть пользователю ещё нужно прокрутить до них.</p>
<p>Один из способов улучшения скорости загрузки таких виджетов — <a href="https://addyosmani.com/blog/import-on-interaction/">ленивая загрузка во время взаимодействия</a>. Это может быть реализовано с помощью лёгкой заглушки виджета: полная версия загрузится только тогда, когда начнётся взаимодействие. В Lighthouse есть отчёт, который рекомендует сторонние решения для <a href="https://web.dev/third-party-facades/">ленивой загрузки с заглушкой</a>, например, для видео с YouTube.</p>
<p><img src="https://web-standards.ru/articles/web-vitals-lighthouse/images/20.png" alt="Отчёт позволяет выделить виджеты, которые могут быть заменены на заглушку с ленивой загрузкой."></p>
<p>Напомню, что Lighthouse будет <a href="https://web.dev/third-party-summary/">подсвечивать сторонний код</a>, который блокирует основной поток более, чем на 250 мс. Это поможет обнаружить все сторонние скрипты (включая собственные Google), которые стоит отложить или загрузить лениво, если результат их рендеринга прячется за прокруткой.</p>
<p><img src="https://web-standards.ru/articles/web-vitals-lighthouse/images/21.png" alt="Увеличение скорости загрузки страницы с помощью ленивой загрузки сторонних скриптов."></p>
<h3>Не только Core Web Vitals</h3>
<p>Помимо использования Core Web Vitals, последние версии Lighthouse и PageSpeed Insights также пытаются обеспечить разработчиков конкретными советами для ускорения тяжёлых JS-приложений, если у вас включены карты кода.</p>
<p>Также эти инструменты включают растущий список отчётов для уменьшения стоимости JavaScript на ваших страницах, включая зависимость от полифилов и дублирование кода, который не нужен пользователям.</p>
<p>За подробностями об инструментах Core Web Vitals следите <a href="https://twitter.com/____lighthouse">в Твиттере команды Lighthouse</a>, а также в разделе <a href="https://developers.google.com/web/updates/2020/05/devtools">What’s new in DevTools</a>.</p>

                    ]]></description><pubDate>Tue, 18 May 2021 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/web-vitals-lighthouse/</guid></item><item><title>Ищем баланс между нативным и кастомным селектом</title><link>https://web-standards.ru/articles/native-and-custom-select/</link><description><![CDATA[
                        <p>Есть план! Мы сделаем стилизованный селект. Стилизуем не просто <a href="https://css-tricks.com/styling-a-select-like-its-2019/">снаружи</a>, но и внутри. Полный контроль над стилизацией. Вдобавок к этому мы сделаем его доступным. Мы не будем пытаться <em>повторить</em> за браузером всё, что он делает по умолчанию при отрисовке нативного <code>&lt;select&gt;</code>. Мы буквально будем использовать нативный <code>&lt;select&gt;</code>, как только используется любая вспомогательная технология. Но когда будет использоваться мышь, мы отрисуем стилизованную версию и заставим ее функционировать как <code>&lt;select&gt;</code>.</p>
<p>Вот что я понимаю под «гибридным» селектом: это одновременно и нативный <code>&lt;select&gt;</code>, и его стилизованная альтернатива.</p>
<figure>
    <img src="https://web-standards.ru/articles/native-and-custom-select/images/1.png" width="1000" height="497" alt="Сравнение кастомного и нативного селектов: слева кастомный в стиле сайта, справа нативный в стиле ОС.">
    <figcaption>Кастомный селект часто используется вместо нативного ради эстетики и последовательности дизайна.</figcaption>
</figure>
<h2>Селект, выпадающий список, навигация, меню… название имеет значение</h2>
<p>Во время изучения данной темы я думала обо всех тех названиях, которыми разбрасываются, когда говорят о селектах. Наиболее общие из них — «выпадающий список» и «меню». Есть два типа ошибок при наименовании, которые мы можем допустить: дать <em>одинаковые</em> названия разным элементам или дать <em>разные</em> названия одинаковым элементам.</p>
<p>Перед тем, как мы двинемся дальше, позвольте мне внести ясность касательно использования термина «выпадающий список». Вот как я его понимаю:</p>
<blockquote>
<p><strong>Выпадающий список</strong> — интерактивный компонент, состоящий из кнопки, которая показывает и прячет список элементов, в основном по наведению мыши, клику или тапу. По умолчанию список невидим до начала взаимодействия. Список обычно показывает блок содержимого (опций) поверх другого контента.</p>
</blockquote>
<p>Множество интерфейсов могут выглядеть <em>похоже</em> на выпадающий список. Но просто назвать элемент «выпадающим списком» — всё равно что использовать слово «рыба» для описания животного. Какое семейство рыб? Рыба-клоун не то же самое, что и акула. То же касается и выпадающих списков.</p>
<figure>
    <img src="https://web-standards.ru/articles/native-and-custom-select/images/2.jpg" width="1000" height="625" alt="Кадр из мультфильма «Спасти Немо»: огромная зубастая акула за спиной двух крошечных испуганных рыбок." loading="lazy">
    <figcaption>Отличаем рыбу-клоуна от акулы.</figcaption>
</figure>
<p>Подобно тому, как в море существует много разных видов рыб, есть множество компонентов, о которых мы можем вести речь, столкнувшись со словосочетанием «выпадающий список».</p>
<ul>
<li><strong>Меню:</strong> список команд или действий, которые пользователь может исполнить на странице.</li>
<li><strong>Навигация:</strong> список ссылок, используемых для перемещения по сайту.</li>
<li><strong>Селект:</strong> контрол формы <code>&lt;select&gt;</code>, показывающий пользователю список опций, которые он может в ней выбрать.</li>
</ul>
<p>Решение, о каком типе выпадающих списков мы ведём речь, может быть туманным. Вот несколько примеров из веба, подходящих под мою классификацию вышеупомянутых элементов. Оно основано на моём исследовании, и иногда, когда я не могу найти подходящий ответ, <em>интуитивно</em> основано на моём опыте.</p>
<figure>
    <img src="https://web-standards.ru/articles/native-and-custom-select/images/3.png" width="1000" height="566" alt="Схема с примерами выпадающих списков: 1. Селект, 2. Меню, 3. Меню или селект, 4. Открывающаяся навигация, 5. Что-то другое." loading="lazy">
    <figcaption>Мир выпадающих списков: пять сценариев их использования в вебе. Более подробное описание — в таблице ниже.</figcaption>
</figure>
<div class="content__table-wrapper">
    <table>
        <colgroup>
            <col width="5%">
            <col>
            <col>
        </colgroup>
        <thead>
            <tr>
                <th>№</th>
                <th>Ожидаемое поведение</th>
                <th>Тип списка</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <th>1</th>
                <td>Ожидается, что выбранный вариант отправится внутри формы на сервер, например, возраст.</td>
                <td>Селект</td>
            </tr>
            <tr>
                <td>2</td>
                <td>Выпадающему списку не нужен выбранный вариант, например, список действий: копировать, вставить и вырезать.</td>
                <td>Меню</td>
            </tr>
            <tr>
                <td>3</td>
                <td>Выбранный вариант влияет на контент, например, сортировка списка.</td>
                <td>Меню или селект, подробности чуть позже.</td>
            </tr>
            <tr>
                <td>4</td>
                <td>Выпадающий список содержит ссылки на другие страницы, например, большая навигация со ссылками на разделы сайта.</td>
                <td><a href="https://www.w3.org/TR/wai-aria-practices/examples/disclosure/disclosure-navigation.html">Открывающаяся навигация</a></td>
            </tr>
            <tr>
                <td>5</td>
                <td>Содержимое выпадающего меню — не список, например, выбор даты.</td>
                <td>Что-то другое, что <a href="https://adrianroselli.com/2020/03/stop-using-drop-down.html">не следует называть выпадающим списком</a></td>
            </tr>
        </tbody>
    </table>
</div>
<p>Не все воспринимают интернет и взаимодействуют с ним одинаково. Именование пользовательских интерфейсов и определение дизайн-паттернов — фундаментальный процесс, хотя и с достаточным пространством для личной интерпретации.</p>
<p>Вот тип выпадающего списка, который определенно можно назвать <strong>меню.</strong> Его использование является горячей темой при обсуждении доступности. Я не буду много говорить об этом здесь, но позвольте мне просто подчеркнуть, что тег <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/menu"><code>&lt;menu&gt;</code></a> устарел и не рекомендуется к использованию. Вот подробное руководство по <a href="https://web-standards.ru/articles/menu-buttons/">инклюзивным меню и меню-кнопкам</a> <em>(в переводе на «Веб-стандартах», прим. редактора),</em> включая объяснение почему <a href="https://adrianroselli.com/2017/10/dont-use-aria-menu-roles-for-site-nav.html">ARIA-роль <code>menu</code> не следует использовать для навигации по сайту</a>.</p>
<p>Мы даже не коснулись других элементов, которые попадают в довольно серую зону, что делает классификацию выпадающих списков ещё более туманной из-за недостака практических примеров использования от WCAG.</p>
<p>Уфф… получилось много. Давайте забудем обо всём этом беспорядке с выпадающими списками и сосредоточимся исключительно на элементе <code>&lt;select&gt;</code>.</p>
<h2>Давайте поговорим про <code>&lt;select&gt;</code></h2>
<p>Стилизация элементов формы — увлекательное путешествие. Согласно MDN, есть <a href="https://developer.mozilla.org/en-US/docs/Learn/Forms/Styling_web_forms#Why_is_styling_form_widgets_challenging">хорошие, плохие и злые</a>. К хорошим относится тег <code>&lt;form&gt;</code>, который попросту является блочным элементом. К плохим — чекбоксы, стилизация которых <a href="https://css-tricks.com/custom-styling-form-inputs-with-modern-css-features/">возможна, но громоздка</a>. <code>&lt;select&gt;</code> определенно из области злых.</p>
<p>Про это написано огромное количество статей и даже в 2020 <a href="https://css-tricks.com/making-a-better-custom-select-element/">всё еще трудно создать кастомный селект</a> и <a href="https://www.24a11y.com/2019/select-your-poison-part-2/">некоторые пользователи всё ещё предпочитают простые и нативные селекты</a>.</p>
<p>Для разработчиков <code>&lt;select&gt;</code> — <a href="https://www.gwhitworth.com/blog/2019/07/form-controls-components/">самый разочаровывающий элемент форм</a>, главным образом из-за <a href="https://www.gwhitworth.com/blog/2019/10/can-we-please-style-select/">отстутствия поддержки стилизации</a>. Борьба в UX за это настолько велика, что мы ищем <a href="https://medium.com/@kollinz/dropdown-alternatives-for-better-mobile-forms-53e40d641b53">альтернативы</a>. Что ж, я думаю, что первое правило <code>&lt;select&gt;</code> такое же, <a href="https://www.w3.org/TR/using-aria/%23firstrule">как с ARIA</a>: <strong>избегайте его использования, если можете.</strong></p>
<p>Я могла бы закончить статью прямо сейчас словами <em>«Не используйте <code>&lt;select&gt;</code>, точка».</em> Но давайте посмотрим правде в глаза: селект для нас всё ещё лучшее решение в ряде случаев. Сюда можно отнести сценарии, когда мы работаем со списком, содержащим множество опций, раскладкой, ограниченной в пространстве, или же просто при нехватки времени или бюджета для разработки и реализации пользовательского интерактивного компонента с нуля.</p>
<h2>Требования к кастомному <code>&lt;select&gt;</code></h2>
<p>Приняв решение создать кастомный селект — пусть и самый простой — мы сталкиваемся с требованиями, которые мы должны учесть:</p>
<ul>
<li>Должна быть кнопка, содержащая текущий выбранный вариант.</li>
<li>Клик по блоку переключает видимость списка опций.</li>
<li>Клик по опции, расположенной в списке, обновляет выбранное значение. Текст кнопки меняется и список закрывается.</li>
<li>Клик по области вне компонента закрывает список.</li>
<li>Переключатель содержит маленький треугольник, направленный вниз, указывающий на то, что есть варианты.</li>
</ul>
<p>Что-то вроде такого:</p>
<iframe src="https://codepen.io/sandrina-p/embed/preview/dyYqmya" title="Пример кастомного и недоступного селекта" loading="lazy"></iframe>
<p>Кто-то из вас подумает: «Работает и хорошо». Но постойте… Разве это работает для <em>всех?</em> Не все используют мышку (или тачскрин). К тому же нативный <code>&lt;select&gt;</code> обладает более широким списком возможностей, которые достаются нам бесплатно и не входят в этот список требований:</p>
<ul>
<li>Выбранный вариант доступен для восприятия всеми пользователями, вне зависимости от их возможностей зрения.</li>
<li>С компонентом можно предсказуемо взаимодействовать с помощью клавиатуры во всех браузерах — например, используя клавиши стрелок для навигации, <kbd>Enter</kbd> для выбора, <kbd>Esc</kbd> для отмены и так далее.</li>
<li>Вспомогательные технологии (например, скринридеры) чётко объявляют пользователям элемент, называя его роль, имя и состояние.</li>
<li>Положение списка регулируется, то есть он не обрезается за краями экрана.</li>
<li>Элемент следует настройкам операционной системы пользователя — например, высокую контрастность, цветовую схему, ограничение движений и другие.</li>
</ul>
<p>Именно на этом этапе большинство кастомных селектов так или иначе терпят крах. Взгляните на некоторые крупные UI-библиотеки. Я не буду упоминать конкретные, потому что веб достаточно недолговечный, но сходите попробуйте. Вероятно, вы заметите разное поведение селекта в разных фреймворках.</p>
<p>Вот дополнительные характеристики, за которыми нужно следить:</p>
<ul>
<li>Выбирается ли опция списка сразу же при получения фокуса с клавиатуры?</li>
<li>Можно ли использовать <kbd>Enter</kbd> и <kbd>Space</kbd> для выбора варианта?</li>
<li>Нажатие на <kbd>Tab</kbd> переносит нас к следующему варианут списка или же к следующему элементу формы?</li>
<li>Что будет, когда вы достигнете последнего варианта в списке с помощью стрелок? Фокус замрет на последнем варианте, вернется к первому или же, что хуже всего, перейдет к следующему элементу формы?</li>
<li>Возможно ли перейти к последней опции списка с помощью клавиши <kbd>Page Down</kbd>?</li>
<li>Можно ли прокручивать элементы списка, если их больше, чем в поле видимости в данный момент?</li>
</ul>
<p>Это был небольшой пример функций нативного селекта.</p>
<blockquote>
<p>Решив создать наш собственный кастомный селект, мы обязываем людей пользоваться им определенным образом, который может отличаться от их ожиданий.</p>
</blockquote>
<p>Но всё ещё хуже. Даже нативный <code>&lt;select&gt;</code> <a href="https://www.24a11y.com/2019/select-your-poison/">ведет себя по-разному</a> в разных браузерах и скринридерах.</p>
<p>Создав наш собственный селект, мы заставим людей пользоваться им не так, как они ожидают. Это опасное решение и это именно те мелочи, в которых кроется дьявол.</p>
<h2>Создаём гибридный селект</h2>
<p>При создании простого кастомного селекта мы, того не замечая, идём на компромисс. В частности, <strong>мы жертвуем функциональностью ради эстетики.</strong>
Всё должно быть наоборот.</p>
<p>Что если вместо этого мы зададим нативный селект по умолчанию и заменим его более эстетичным, если это возможно? Вот тут и вступает в игру идея о гибридном селекте. Он гибридный, потому что состоит из двух селектов, каждый из которых показывается в нужный для него момент:</p>
<ul>
<li>Нативный селект, видимый и доступный по умолчанию.</li>
<li>Кастомный селект, скрытый до тех пор, пока не произойдёт взаимодействие посредством мыши.</li>
</ul>
<p>Начнём с разметки. Вначале, добавим нативный <code>&lt;select&gt;</code> с несколькими <code>&lt;option&gt;</code> <em>до</em> кастомного. Чуть позже я объясню почему.</p>
<p>Любой контрол формы должен содержать <a href="https://www.w3.org/WAI/tutorials/forms/labels/">лейбл</a>. Мы можем прибегнуть к <code>&lt;label&gt;</code>, но фокус будет попадать на нативный селект, когда мы будем кликать на подпись. В целях предотвращения такого поведения используем <code>&lt;span&gt;</code> и свяжем его с селектом с помощью <code>aria-labelledby</code>.</p>
<p>Наконец, с помощью <code>aria-hidden=&quot;true&quot;</code> нужно сообщить вспомогательным технологиям, чтобы те игнорировали кастомный селект. Таким образом, они видят только нативный селект, несмотря ни на что.</p>
<pre><code tabindex="0" class="language-html">&lt;span class=&quot;selectLabel&quot; id=&quot;jobLabel&quot;&gt;
    Основная рабочая роль
&lt;/span&gt;
&lt;div class=&quot;selectWrapper&quot;&gt;
    &lt;select class=&quot;selectNative js-selectNative&quot; aria-labelledby=&quot;jobLabel&quot;&gt;
        &lt;!-- Варианты --&gt;
        &lt;option&gt;&lt;/option&gt;
    &lt;/select&gt;
    &lt;div class=&quot;selectCustom js-selectCustom&quot; aria-hidden=&quot;true&quot;&gt;
        &lt;!-- Прекрасный кастомный селект --&gt;
    &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<p>Это приводит нас к стилизации, в ходе которой мы не только заставляем всё выглядеть красивее, но также и управляем переключением между селектами. Нам не хватает лишь пары строк, чтобы начать магию.</p>
<p>Для начала, оба селекта должны обладать одинаковой шириной и высотой. Это позволит пользователям не увидеть серьезного расхождения с макетом при переключении.</p>
<pre><code tabindex="0" class="language-css">.selectNative,
.selectCustom {
    position: relative;
    width: 22rem;
    height: 4rem;
}
</code></pre>
<p>Вот два селекта. Но лишь один может устанавливать пространство, которое они занимают. Второй должен быть спозиционирован абсолютно, чтобы быть вне потока документа. Давайте провернём это с кастомным селектом, так как замена производится только тогда, когда она возможна. Мы спрячем его по умолчанию, чтобы никто пока до него не добрался.</p>
<pre><code tabindex="0" class="language-css">.selectCustom {
    position: absolute;
    top: 0;
    left: 0;
    display: none;
}
</code></pre>
<p>Вот здесь-то и начинается веселье. Нам нужно определить, использует ли пользователь устройство, в котором наведение — часть основного ввода информации. Например, компьютер с мышью. Хотя мы и думаем о медиавыражениях только как о способе проверки определённых функций или же инструменте адаптивности на брейкпоинтах, их также можно использовать для обнаружения поддержки ховера с помощью <code>hover: hover</code>, который поддерживается всеми основными браузерами. Итак, давайте используем это для отображения кастомного селекта на устройствах, где можно навести курсор.</p>
<pre><code tabindex="0" class="language-css">@media (hover: hover) {
    .selectCustom {
        display: block;
    }
}
</code></pre>
<p>Отлично. Но что насчёт людей, которые используют клавиатуру для навигации даже на устройствах, поддерживающих ховер? Что делать? Мы будем прятать кастомный селект, когда нативный находится в состоянии фокуса. Мы можем поймать <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Adjacent_sibling_combinator">соседний элемент с помощью комбинирующего селектора <code>+</code></a>. Как только нативный селект в фокусе, прячем кастомный, который следует сразу за ним в DOM. Вот почему кастомный селект должен следовать за нативным.</p>
<pre><code tabindex="0" class="language-css">@media (hover: hover) {
    .selectNative:focus + .selectCustom {
        display: none;
    }
}
</code></pre>
<p>Вот и всё! Трюк переключения между двумя селектами готов. Есть другие способы сделать это через CSS, но и этот прекрасно работает.</p>
<p>Наконец, нам нужно немного JavaScript. Добавим несколько обработчиков событий:</p>
<ul>
<li>Один для события клика, по которому в игру вступает кастомный селект, раскрываясь и показывая варианты выбора.</li>
<li>Один, чтобы синхронизировать выбранные варианты. При изменении одного варианта выбора, меняется и второй.</li>
<li>И ещё один для установки навигации через клавиатуру с помощью клавиш <kbd>Up</kbd> и <kbd>Down</kbd>, выбора варианта с помощью клавиш <kbd>Enter</kbd> или <kbd>Space</kbd>, и закрытия списка через <kbd>Esc</kbd>.</li>
</ul>
<iframe src="https://codepen.io/sandrina-p/embed/preview/YzyOYRr" title="Пример гибридного селекта" loading="lazy"></iframe>
<h2>Юзабилити-тест</h2>
<p>Я провела небольшое юзабилити-тестирование, в котором я попросила нескольких людей с ограниченными возможностями воспользоваться гибридным селектом. Были протестированы следующие устройства и инструменты с использованием последних версий Chrome 81, Firefox 76, Safari 13:</p>
<ul>
<li>Компьютер только с мышью.</li>
<li>Компьютер только с клавиатурой.</li>
<li>VoiceOver на macOS с помощью клавиатуры.</li>
<li>NVDA в Windows с помощью клавиатуры.</li>
<li>VoiceOver на iPhone и iPad в Safari</li>
</ul>
<p>Все эти тесты дали желаемый результат, но я уверена, что <strong>можно было бы провести ещё больше юзабилити-тестов с более разнообразными устройствами и широким диапазоном лиц.</strong> Если у вас есть возможность протестировать на других устройствах или с другими инструментами — такими как JAWS, Dragon и подобным — пожалуйста, расскажите мне, как прошёл тест.</p>
<p>Во время теста была обнаружена проблема. В частности, проблема связана с настройкой VoiceOver «Использовать виртуальный курсор VoiceOver». Если пользователь откроет селект с помощью этого курсора, вместо нативного покажется кастомный селект.</p>
<p>Больше всего мне нравится в этом подходе то, как он совмещает всё самое лучшее из обоих миров без нанесения ущерба функциональности.</p>
<ul>
<li>Пользователи мобильных устройств и планшетов получают нативный селект, предлагающий лучший пользовательский интерфейс по сравнению с кастомным селектом, включая преимущества производительности.</li>
<li>Пользователи клавиатур получают возможность взаимодействия с нативным селектом в соответствии с их ожиданиями.</li>
<li>Вспомогательные технологии спокойно могут взаимодействовать с нативным селектом.</li>
<li>Пользователи мыши получают возможность взаимодействовать с расширенным кастомным селектом.</li>
</ul>
<p>Данный подход <strong>обеспечивает необходимую для каждого функциональность</strong> без дополнительного громоздкого кода, реализующего функции нативного селекта.</p>
<p>Не поймите меня неправильно, этот метод не является универсальным решением для всех. Он может являться рабочим для простых селектов, но, вероятно, не будет работать в случаях со сложным взаимодействием. В этих случаях нам нужно использовать ARIA и JavaScript для восполнения пробелов и создания <a href="https://24ways.org/2019/making-a-better-custom-select-element/">действительно доступного селекта</a>.</p>
<h3>Примечание касательно селекта-меню</h3>
<p>Давайте вернёмся к третьему сценарию нашего списка селектов. Если вы помните, это выпадающий список, который всегда имеет отмеченный вариант (например, сортировка). Я отнесла его к серой области как и меню или селект.</p>
<p>Идея такая: много лет назад этот тип выпадающего списка реализовывался в основном с помощью нативного <code>&lt;select&gt;</code>. В настоящее время часто можно увидеть что он реализован с нуля с помощью кастомных стилей (доступных или нет). И мы получаем селект, стилизованный под меню.</p>
<figure>
    <img src="https://web-standards.ru/articles/native-and-custom-select/images/4.png" width="683" height="270" alt="Три выпадающих селекта: кастомная сортировка товаров, кастомный выбор отображения списка, нативная сортировка товаров." loading="lazy">
    <figcaption>Примеры селектов, выступающих в качестве меню.</figcaption>
</figure>
<p><strong><code>&lt;select&gt;</code> — это вид меню.</strong> Оба имеют схожую семантику и поведение, особенно в случае, когда один вариант всегда выбран. Теперь позвольте мне упомянуть критерий из <a href="https://www.w3.org/WAI/WCAG21/Understanding/on-input.html">WCAG 3.2.2 о полях (уровень A)</a>:</p>
<blockquote>
<p>Изменение состояния любого пользовательского элемента <strong>не должно</strong> влечь за собой автоматическое изменение контекста без уведомления об этом пользователя перед самим изменением.</p>
</blockquote>
<p>Давайте применим это на практике. Представьте себе сортируемый список студентов. Может быть визуально очевидно, что сортировка происходит незамедлительно, но это не обязательно так для всех людей. Таким образом, при использовании <code>&lt;select&gt;</code>, мы рискуем нарушить правила WCAG, поскольку контент страницы изменился, а это попадает под понятие «<a href="https://www.w3.org/WAI/WCAG21/Understanding/on-input.html%23dfn-changes-of-context">изменение контекста</a>».</p>
<p>Чтобы соблюсти критерий, мы должны уведомить пользователя о действии до того, как он начнёт взаимодействовать с элементом или же поставить <code>&lt;button&gt;</code> сразу после списка, чтобы подтвердить изменения.</p>
<pre><code tabindex="0" class="language-html">&lt;label for=&quot;sortStudents&quot;&gt;
    Сортировка студентов
    &lt;!--
        Предупреждение для пользователя,
        если нет кнопки подтверждения.
    --&gt;
    &lt;span class=&quot;visually-hidden&quot;&gt;
        (Немедленный эффект после выбора)
    &lt;/span&gt;
&lt;/label&gt;
&lt;select id=&quot;sortStudents&quot;&gt;
    &lt;!-- Опции сортировки --&gt;
&lt;/select&gt;
</code></pre>
<p>Тем не менее, использование <code>&lt;select&gt;</code> наряду с созданием пользовательского меню является хорошим подходом, когда речь заходит о несложных меню, требующих изменение содержимого страницы. Просто помните, что от вашего решения зависит объём работ, необходимых для создания полностью доступного компонента. Это как раз тот случай, когда гибридный селект может выручить.</p>
<h2>Заключение</h2>
<p>Вся эта идея зарождалась как невинный CSS-трюк. Но после всех этих исследований, я вновь убедилась, что создание уникальных элементов для юзабилити с сохранением полной доступности — непростая задача.</p>
<p>Создание действительно доступных селектов (или же любого вида выпадающего списка) сложнее, чем может казаться. Руководство WCAG даёт прекрасные инструкции наряду с лучшими практиками, но без конкретных примеров использования эти инструкции носят рекомендательный характер. Не говоря уже о том, что поддержка ARIA слабая, а внешний вид и поведение браузерного <code>&lt;select&gt;</code> отличаются в разных браузерах.</p>
<p>Гибридный селект — это всего лишь ещё одна попытка создать красивый селект, сохранив при этом как можно больше изначальных функций. Не расценивайте этот эксперимент как попытку извинения за уменьшение доступности. Скорее это попытка угодить обоим мирам. При наличии ресурсов, времени и необходимых навыков <em>пожалуйста, сделайте всё как надо</em> и не забудьте протестировать всё на разных пользователях перед тем, как публиковать своё творение.</p>
<p><strong>P.S.</strong> Не забудьте <a href="https://adrianroselli.com/2020/03/stop-using-drop-down.html">выбрать правильное название</a> при создании выпадающего списка 😉</p>

                    ]]></description><pubDate>Thu, 06 May 2021 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/native-and-custom-select/</guid></item><item><title>Принцип мозаики, или Как мы сделали JavaScript по-настоящему модульным</title><link>https://web-standards.ru/articles/modular-javascript/</link><description><![CDATA[
                        <p>Идея создания модульной системы родилась и развивалась внутри нашего опенсорсного проекта <a href="https://github.com/scandipwa/scandipwa">ScandiPWA</a> — витрины для онлайн-коммерции на React. Нашей главной задачей было разработать расширяемую основу для наших проектов, в которую мы могли бы внедрять расширения, при этом имея возможность менять облик (создавать темы) как самой основы, так и любого из расширений. При этом, важно было предоставить возможность быстрого и удобного их (базы и расширений) обновления.</p>
<p>Попробовав множество подходов, мы пришли к выводу, что для решения нашей задачи, нам абсолютно не подходят решения, предусматривающие полное копирование всей кодовой базы каждым из разрабатываемых проектов. Часто применяемый вариант с применением форков заставлял раз в неделю резолвить огромное количество конфликтов, что было просто непозволительно учитывая сжатые сроки наших проектов.</p>
<p>С расширениями дела обстояли ещё хуже. Потребность в них росла, а обычные подходы к этой проблеме заставляли на каждом шагу останавливаться, и оставлять места для внедрения плагинов. В приложении таких набиралось всё больше, и нас это решительно не устраивало, код регистрации точки внедрения дублировался, а следить и документировать каждый становилось просто невозможно. Наш опыт с языками на бэкенде подсказывал, что должен был быть способ внедрять плагины на уровне кода, а не точки выхода, разбросанные по приложению. Однако JavaScript такой возможности из коробки не предоставлял.</p>
<p>Мы долго задавались вопросом: как живут все больше проекты в вебе, искали подходы, технологии… Даже тогда, два года назад, когда мы только начинали, уже были технологии типа DI <em>(dependency injection — прим. редактора).</em> И всё с ними было хорошо, да есть у нас проблема: наш продукт ScandiPWA делался для простых разработчиков. Для закоренелых PHP-разработчиков, которые и React может первый раз в жизни видят. Приходилось прямо в документации рассказывать о преимуществах над jQuery. Представьте как сложно было бы объяснить непонятные реестры.</p>
<p>В общем, нам нужно было что-то элементарно простое. Пришлось, как принято, делать самим.</p>
<h2>К чему мы пришли: плагины</h2>
<p>Как уже сказано выше, чтобы работать с плагинами, обычно приходится писать ещё и кучу кода. Расставлять точки входа, регистрировать, импортировать файлы регистрации. Это всё долго, сложно, затратно и очень плохо масштабируется. Мы решили, что всё это можно убрать. Скрыть и делать плагины сразу к самому коду. Мы очень не хотели менять прототипы, мы хотели чтобы плагины просто перехватывали вызов той или иной функции и позволяли модифицировать процесс и результат её выполнения. При этом было важно работать на уровне стека вызова оригинальной функции, чтобы программу было легко отлаживать.</p>
<p>Итак, что же мы там придумали? Теперь, вы можете делать сущности вашего языка в вашем приложении (функции, классы и методы) точкой входа для плагинов с помощью всего одного комментария! Комментарий — <code>@namespace</code> позволит изменить отмеченную сущность через плагины из любого модуля в приложении. А ещё он прекрасно подсвечивается вашим редактором! Как это в итоге выглядит:</p>
<pre><code tabindex="0" class="language-js">/** @namespace Application/getData */
const getData = () =&gt; {
    return ['Initial data'];
}

console.log(getData());
</code></pre>
<p>И что, это всё? Да, всё. Теперь вы можете расширять эту функцию, например, добавить к результату её выполнения новый элемент, сделать это, достаточно просто, смотрите:</p>
<pre><code tabindex="0" class="language-js">export default {
    // Тот самый @namespace нашей функции
    'Application/getData': {
        // Тип изменяемой сущности
        function: (args, callback) =&gt; {
            return [
                // Вызов оригинальной функции
                ...callback(...args),
                // Наш новый элемент
                'Data from the plugin'
            ];
        }
    }
}
</code></pre>
<p>А как решить проблему с импортом файлов регистрации? Определить для них одно место, и брать оттуда! Забегая немного вперёд: система ищет плагины по паттерну <code>src/plugin/*.plugin.*</code> во всех отмеченных модулях приложения.</p>
<p>Точно таким же образом это работает с классами, методами класса, их статическими методами, свойствами и даже стрелочными функциями. Просто добавь неймспейс!</p>
<h2>К чему мы пришли: темы</h2>
<p>А что с темами? Мы бы хотели надстраивать их бесконечное количество (ну или больше двух) друг на друге. Например так:</p>
<ol>
<li>Голая тема с чистой функциональностью.</li>
<li>Красивая тема № 101.</li>
<li>Кастомизации под клиента.</li>
</ol>
<p>Для нас ответ крылся в формулировке вопроса «надстраивать друг на друге». Мы решили сделать всё уже давно изведанным во многих бэкенд-фреймворках способом: при сборке предпочитать файлы детей родительским, в случае, если имена их файлов совпадают! Непонятно? Вот как это работает на практике. Спойлер, всё просто!</p>
<p>Этот файл лежит в файле <code>App.js</code> пакета <code>nice-theme</code>:</p>
<pre><code tabindex="0" class="language-jsx">import { PureComponent } from 'react';

export default class App extends PureComponent {
    render() {
        return &lt;p&gt;Это написано в «Красивой теме №101».&lt;/p&gt;;
    }
}
</code></pre>
<p>Это файл уже более новой темы «Кастомизации под клиента», но имя файла, что важно, совпадает: тоже App.js.</p>
<pre><code tabindex="0" class="language-jsx">import ParentApp from 'nice-theme/App';

export default class App extends ParentApp {
    render() {
        return (
            &lt;&gt;
                { super.render() }
                &lt;p&gt;А это в «Кастомизации под клиента».&lt;/p&gt;
            &lt;/&gt;
        );
    }
}
</code></pre>
<p>В результате получаем следующий HTML в результате рендера:</p>
<pre><code tabindex="0" class="language-html">&lt;p&gt;Это написано в «Красивой теме №101».&lt;/p&gt;
&lt;p&gt;А это в «Кастомизации под клиента».&lt;/p&gt;
</code></pre>
<p>Обратите внимание, мы использовали классы и наследование в React! Такая практика не рекомендуется. Однако в нашем случае, она решает больше проблем, чем создает:</p>
<ul>
<li>Программист может сам решать, хочет ли он остаться совместимым с оригинальной темой (и сильно упростить последующие обновления), или же он хочет переписать файл с нуля. В таком случае, он может просто создать класс с нуля (из PureComponent).</li>
<li>Мы имеем возможность наследовать методы частями. Например, нас устраивает, как рендерится название, но мы недовольны ценой. Мы всегда можем переписать лишь несколько конкретных, необходимых нам методов.</li>
<li>Наконец, мы можем на уровне метода или свойства решать, хотим ли мы унаследовать его (через обращение к <code>super</code>).</li>
</ul>
<p>Что важно, мы не даём возможности обратиться напрямую к классу родительской темы, если он переписан в новой теме. Теперь любое обращение к нему вернёт вам ваш новый, переписанный класс! Поэтому мы считаем такой подход валидным и, несмотря на популярность функциональных компонентов в React, продолжаем использовать классовые!</p>
<h2>Как это попробовать?</h2>
<p>Всё очень просто. На самом деле, механизм может работать не только с React: как плагины, так и темы можно делать для любых сущностей и файлов. Мы же пока уверенно можем заявить о поддержке самых популярных фреймворков: <a href="https://create-react-app.dev/">Create React App</a> и <a href="https://nextjs.org/">Next.js</a>. Что, плагины в Next.js? Да-да, именно так!</p>
<p>Как выглядит интеграция? Даже слишком просто. Достаточно заменить зависимость с <code>react-scripts</code> на <code>@tilework/mosaic-cra-scripts</code>, а в самих скриптах, аналогично с <code>react-scripts</code> на <code>cra-scripts</code>. И всё! Как это выглядит:</p>
<pre><code tabindex="0" class="language-diff">&quot;dependencies&quot;: {
-    &quot;react-scripts&quot;: &quot;4.0.3&quot;,
+    &quot;@tilework/mosaic-cra-scripts&quot;: &quot;0.0.2&quot;,
},
&quot;scripts&quot;: {
-    &quot;start&quot;: &quot;react-scripts start&quot;,
-    &quot;build&quot;: &quot;react-scripts build&quot;,
+    &quot;start&quot;: &quot;cra-scripts start&quot;,
+    &quot;build&quot;: &quot;cra-scripts build&quot;,
}
</code></pre>
<p>Для Next.js, всё точно также, только ставить надо <code>@tilework/mosaic-nextjs-scripts</code>, а скрипты менять нужно на <code>nextjs-scripts</code>.</p>
<p>Что делать всем остальным? Если ваш стек — это Webpack и Babel, то и для вас есть очень простое решение: ставим <code>@tilework/mosaic-config-injectors</code> и изменяем вашу Webpack-конфигурацию следующим образом:</p>
<pre><code tabindex="0" class="language-js">const webpack = require('webpack');
const ConfigInjectors = require('@tilework/mosaic-config-injectors');

module.exports = ConfigInjectors.injectWebpackConfig({
    // Конфигурация
}, {
    webpack
});
</code></pre>
<p>Готово? Ура! Поделитесь опытом использования, для нас это очень важно!</p>
<h2>Почему все-таки mosaic?</h2>
<p>Чем картины отличаются от мозаики? Мозаика состоит из отдельных элементов, а картина, хоть и окрашена в разные цвета, является одним, неразделимым целым.</p>
<p>Большинство приложений JavaScript — это картины. Наше изначальное приложение ScandiPWA — тоже. Даже разделив такое приложение на модули, нам всё равно придётся их переплетать, интегрируя один в другой. Модули — как цвета на картине, изначально выходящие из разных тюбиков, оказываются неразрывно связанными в одно целое.</p>
<p>Однако есть способ превратить приложения написанные на JavaScript в настоящую мозаику. Модули таких приложений должны содержат в себе собственную интеграцию. Они больше не будут переплетаться на холсте, как краски, а их будет достаточно выложить в определенном порядке, как плитку мозаики.</p>
<p>Плагины позволят вам сделать это! Мы начали с интеграций в уже готовые приложения, а теперь предлагаем написать всё с нуля? Да, мы сами в шоке!</p>
<p>Но как это вам поможет? Какие проблемы решит? Честно говоря, вы просто напишете приложение изначально думая о том, как его можно будет расширять. И не только сторонними расширениями, но и собственной дополнительной логикой. Это зарядка для мозга, полезная сложность, которая делает наш код немного более качественным!</p>
<p>Наверное, не стоит переписывать уже готовые приложения на этот подход, но вот писать новые… Стоит попробовать! Особенно в случае, если вы хотите в будущем легко заменять одни его части на другие. Подумайте об этом :)</p>
<h2>Ссылки</h2>
<ul>
<li><a href="https://docs.mosaic.js.org/">Документация по mosaic</a></li>
<li><a href="https://github.com/tilework/mosaic">Репозиторий проекта</a></li>
<li><a href="https://github.com/scandipwa/scandipwa">ScandiPWA, витрина для онлайн-коммерции</a> — если интересно, с чего всё начиналось.</li>
<li><a href="https://github.com/scandipwa/shopify">Пример модульного проекта на mosaic</a></li>
</ul>

                    ]]></description><pubDate>Mon, 26 Apr 2021 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/modular-javascript/</guid></item><item><title>CSS-нестинг — больше, чем сахар</title><link>https://web-standards.ru/articles/css-nesting/</link><description><![CDATA[
                        <p>В последнее время всё чаще всплывают обсуждения вокруг черновика <a href="https://drafts.csswg.org/css-nesting-1/">CSS Nesting</a>. Кажется всё началось с <a href="https://twitter.com/argyleink/status/1369292639988711425">твита Адама Аргайла</a>, где он, к слову, писал не столько специально про CSS Nesting, сколько среди прочего. В этом усмотрели некоторый шифр, что мол скоро всё будет, ведь не кто-то там твитнул, а сам автор спецификации, над которой в том числе работает маэстро CSS <a href="https://xanthir.com/contact/">Таб Аткинс</a>. В отличие от основного твита про некоторую технику в будущем, твиты со ссылками на конкретные спеки не получили значительного количества лайков. Кому нужны спеки, верно?</p>
<p>Не знаю, совпало или одно потянуло другое, но Бен Фрейн вскоре написал статью «<a href="https://benfrain.com/official-css-nesting-the-last-piece-of-the-puzzle/">CSS Nesting — the last piece of the puzzle</a>», где, в частности, сослался на твит Адама. Кстати, Бен не последний человек в мире Sass и даже написал книгу «<a href="https://sassandcompass.com/">Sass and Compass for Designers</a>» в 2013. Вероятно его авторитет и воодушевление по поводу черновика спецификации сделали своё дело и будущее модуля CSS Nesting заиграло новыми красками.</p>
<p>Вот и «Веб-стандарты» <a href="https://twitter.com/webstandards_ru/status/1372518228572930048">написали об этом</a>, а потом и <a href="https://web-standards.ru/podcast/274/">в подкасте обсудили</a>. Получилось интересно: вышли за пределы обсуждаемой статьи и подсветили интересные вопросы, хотя упустили пару моментов (но о них я ещё напишу). Чего не сказать про обсуждения вокруг, где всплывают возражения вроде того, что это всего лишь сахар, зачем из CSS пытаются сделать препроцессор, предложенный синтаксис не торт и так далее.</p>
<p>Отдельно стоит выделить цитату из <a href="https://t.me/defront/860">обзора статьи Бена Фрейна</a> на канале Defront:</p>
<blockquote>
<p>Недавно Адам Аргайл представил сообществу черновик спецификации, над которым он работает вместе с Табом Аткинсом. В этой спецификации описывается синтаксис вложенности, который похож на аналогичный синтаксис из Sass и Less. Основное отличие — нужно использовать <code>@nest</code> при размещения вкладываемого селектора в качестве потомка…</p>
</blockquote>
<p>В этой цитате есть неточности, которые могут вводить в заблуждение. Разберём их по порядку.</p>
<h2>Не такая уж новая спека</h2>
<p>Во-первых, какого-то явного «представления» спецификации от Адама не было. Он просто <a href="https://twitter.com/argyleink/status/1369318354163437568">твитнул ссылку на спеку</a>, в которой указан автором. Его имя попало в спеку всего полгода назад, <a href="https://github.com/w3c/csswg-drafts/commit/bf1a4d37d2b04ed493a740601e0ed39a30eacc41">он себя же и добавил</a>. Саму спеку, или как принято называть «модуль», очень долго вынашивал Таб Аткинс и достаточно долгое время это были, как любит говорить Вадим Макеев, «заметки на салфетке». Таб — гениальный инженер, автор большого количества CSS-спецификаций, с фантазией и идеями у него полный порядок. У него даже есть свой <a href="https://github.com/tabatkins/specs">отдельный репозиторий с потенциальными спецификациями</a>, которые уже переписаны с «салфеток» во что-то более конкретное. Но нужно понимать, что в такой непростой теме как CSS, от идеи до реализации в браузерах могут пройти годы, если не десятилетия. И Таб делился своими идеями то тут, то там — в том числе, в своих докладах и статьях о возможном будущем CSS.</p>
<p>Так вот, первый черновик CSS Nesting был <a href="https://github.com/tabatkins/specs/commit/9070913298eda901a5a68c941bd046e89f519f8c">подготовлен</a> Табом ещё в октябре 2013 года. А в мае 2018 <a href="https://github.com/jonathantneal">Джонатан Нил</a> начал <a href="https://github.com/w3c/csswg-drafts/issues/2701">масштабную дискуссию</a> по этому предложению, версии черновика от 2015 года. По сути это и было первым представлением начальной версии модуля. Если у вас есть вопросы почему всё выглядит так, как выглядит, то вам обязательно стоит прочитать эту дискуссию (я там оказывается даже вставил свои <a href="https://github.com/w3c/csswg-drafts/issues/2701#issuecomment-401486402">пять копеек</a>, о чём уже и забыл). Там, пожалуй, есть все типовые набросы и ответы к ним, технические нюансы, риски и прочее. После этого, лишь в июле 2018, спека перекочевала в <a href="https://github.com/w3c/csswg-drafts">официальные черновики CSSWG</a>.</p>
<h2>Не такой уж знакомый синтаксис</h2>
<p>Во-вторых, предложенный синтаксис всё-таки отличается от того что есть в Sass и Less. Ключевым является то, что подстановка вышестоящих селекторов вместо <code>&amp;</code> (новый потенциальный житель в синтаксисе CSS) происходит не на уровне конкатенации строк, как в препроцессорах, а на уровне селекторов, точнее, на уровне CSS-токенов. Это принципиально и, пожалуй, вызывает наибольшие нападки на предложенный в модуле синтаксис. Например, следующий код:</p>
<pre><code tabindex="0" class="language-css">.foo {
    &amp;-bar {
        /* … */
    }
}
</code></pre>
<p>Будет воспринят препроцессорами и они произведут CSS:</p>
<pre><code tabindex="0" class="language-css">.foo {
    /* … */
}

.foo-bar {
    /* … */
}
</code></pre>
<p>В тоже время CSS Nesting (пока нет имплементаций, мы можем сослаться только на текст спецификации) говорит, что это некорректно. В лучшем случае, такой код в реалиях CSS будет воспринят так, но это неточно:</p>
<pre><code tabindex="0" class="language-css">.foo {
    /* … */
}

-bar.foo {
    /* … */
}
</code></pre>
<p>Это может вызвать недоумение, если не знать как работает синтаксис CSS и что значит подстановка на уровне токенов. Так что для методологий вроде <a href="https://bem.info/">БЭМ</a>, когда механизм вложенности используют для генерации имён классов для элементов и модификаторов, CSS Nesting работать не будет, в отличие от препроцессоров.</p>
<p>В своём докладе «<a href="https://www.icloud.com/keynote/0DEQD-fUqhf0ak2AAqMjaqYMg">All about the CSS syntax</a>» летом 2018, я рассказывал про механику CSS-синтаксиса и там есть секция про CSS Nesting на 30 слайдов (слайды 83-114). Там я рассматривал новый синтаксис с точки зрения работы CSS-парсера, который работает согласно правилам <a href="https://www.w3.org/TR/css-syntax-3/">CSS Syntax Module Level 3</a>, и немного сравнивал с препроцессорами.</p>
<p>Сам Крис Эпштейн, один из авторов Sass, <a href="https://github.com/w3c/csswg-drafts/issues/2701#issuecomment-395572904">писал</a> в той самой дискуссии про CSS Nesting, что имплементация вложенности, которую они реализовали в Sass, была плохой идеей. Это одна из тех вещей, говорит он, о которых он действительно жалеет. На мой взгляд, это достаточно веский аргумент в пользу того, почему в черновике спецификации всё именно так.</p>
<p>Всё это было почти 3 года назад. Поэтому утверждения «Адам Аргайл представил сообществу черновик спецификации» и «синтаксис вложенности, который похож на аналогичный синтаксис из Sass и Less» кажутся некорректными. Последнее некорректно без уточнения, что механика другая, ведь вся схожесть ограничивается использованием <code>&amp;</code> для подстановки вышестоящих селекторов.</p>
<h2>Можно и без @nest</h2>
<p>В-третьих, использование директивы <code>@nest</code> не обязательно, если <code>&amp;</code> идёт первым у всех селекторов <a href="https://www.w3.org/TR/selectors-4/#structure">в списке селекторов</a>. Да, можно использовать директиву всегда, по какой-либо причине, это не возбраняется, но это не всегда необходимо. Использование же директивы обусловлено тем, что если у первого селектора списка в начале идёт не <code>&amp;</code>, то CSS-парсер может свалиться в так называемый <em>unbound lookahead.</em> Это значит, что разбирая блок, парсер не может быстро принять решение: перед ним вложенный блок или же это поломанная декларация <code>property: value</code>, которую он должен проигнорировать. И это ломает предсказуемость и производительность. Это если коротко. Подробнее я разбирал <a href="https://www.icloud.com/keynote/0DEQD-fUqhf0ak2AAqMjaqYMg">в докладе на примерах</a>, и об этом есть несколько объяснений в дискуссии к CSS Nesting. А для второго и далее селекторов в списке ограничение сохраняется для консистентности правила и устранения WTF при изменении порядка селекторов или удалении первого селектора, когда всё может внезапно сломаться.</p>
<p>В целом, наличие директивы <code>@nest</code> не ключевое отличие, а скорее небольшой нюанс. Субъективно, ситуации когда <code>&amp;</code> идёт не в начале, а значит может потребоваться <code>@nest</code>, случаются куда реже. Поэтому ключевое отличие всё же, что вложенность в целом работает иначе — другие правила.</p>
<p>Также из Defront:</p>
<blockquote>
<p>На данный момент черновик ещё не стабилизировался, и синтаксис описания вложенности скорее всего поменяется…</p>
</blockquote>
<p>Лично я сомневаюсь, что синтаксис как-то поменяется.</p>
<p>Во-первых, он выстоял в достаточно жаркой и жёсткой <a href="https://github.com/w3c/csswg-drafts/issues/2701">дискуссии</a>, где выбор был достаточно прочно аргументирован. На мой взгляд, синтаксис достаточно зрелый.</p>
<p>Во-вторых, за прошедшие почти три года в спеке произошло не так много изменений, но изменения значимые:</p>
<ul>
<li>Разобраны многие моменты со специфичностью. Это сложная тема, и тут на руку сыграли недавние нововведения в CSS селекторах <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:is"><code>:is()</code></a> и <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:where"><code>:where()</code></a>.</li>
<li>Добавились вложенные <code>@media</code> (видимо, с подачи Адама) — как по мне, это хорошее дополнение.</li>
<li>Появился запрет на декларации после вложенных блоков. На самом деле, это изменение <a href="https://github.com/w3c/csswg-drafts/commit/df214d179e2d8e72c4d9c8d292159e63ecd0e24d">Таб внёс совсем недавно</a>, и про это были те самые <a href="https://github.com/w3c/csswg-drafts/issues/2701#issuecomment-401486402">мои пять копеек</a>. Я не думаю, что мой комментарий как-то на это повлиял, но приятно осознавать, что мои умозаключения были в правильном ключе.</li>
<li>Добавились расширения в интерфейсы CSSOM.</li>
</ul>
<p>Также добавилось больше примеров, раскрывающие различные нюансы. Но важно, что все эти изменения не про основной синтаксис, а про тюнинг механики и интеграцию.</p>
<h2>Defront молодец</h2>
<p>Ещё небольшая, но важная ремарка. Не стоит воспринимать разбор тезисов из обзора Defront к статье Бена Фрейна как нападку на автора канала. Я бы сделал комментарий к обзору в самом канале, но, к сожалению, комментарии там не включены (о, это отдельное «шаманство» включить комментарии в Telegram канале, поверьте). Как бы то ни было, <a href="https://t.me/defront">Defront</a> отличный канал, где публикуется много ссылок на очень занятные статьи с сопутствующим обзором. Отдельно хочу выразить респект и уважуху автору канала Александру Мышову, который сделал работу над каналом своей основной деятельностью. То есть он живёт на доход от канала и <a href="https://www.patreon.com/myshov">Патреона</a>. Невероятная самоотдача своему делу, в какой-то степени авантюризм, но это очень круто! Поэтому категорически рекомендую канал.</p>
<p>Что касается темы CSS Nesting и CSS в целом, то это непростая история и в ней делают неверные выводы даже те, кто очень тесно с этим связан. Я сам не уверен, что не ошибся в своих тезисах (поправьте меня, если так). Поэтому особого криминала в неточностях обзора не вижу. Если не погружаться в глубину вопроса (а вопрос очень обширный, как можно заметить по тексту выше), то вполне понятно почему так может происходить и можно сделать скидку. У меня получилось всё это разложить лишь потому, что я давно копаю CSS со стороны спек, слежу и в какой-то степени участвую в происходящем. Но это нетипичный фронтенд. Надеюсь мой разбор поможет другим разобраться в вопросе и лучше понять положение вещей.</p>
<p>Но! Это только первая часть истории, есть ещё кое-что…</p>
<h2>Сахар и препроцессоры</h2>
<p>Если отойти от обсуждения синтаксиса <a href="https://drafts.csswg.org/css-nesting-1/">CSS Nesting</a> и остальных вопросов, рассмотренных выше, то остаются неразобранными тезисы «это всего лишь синтаксический сахар» и «из CSS пытаются сделать препроцессор». Давайте разберёмся, что не так с этими тезисами.</p>
<p>Для начала стоит понять, почему такие тезисы формируются. В черновике CSS Nesting приводятся примеры нового синтаксиса, а рядом с ними эквиваленты CSS без вложенности. Такая преемственность является желаемым эффектом, которого добиваются для новых синтаксических конструкций не только в CSS, но и, например, в JavaScript. Польза от этого, как минимум, в двух аспектах. Во-первых, так проще объяснить новое в сравнении с уже знакомым. Во-вторых, это значит, что новый синтаксис потенциально можно использовать уже сегодня и на этапе сборки адаптировать (транспилировать) его под браузеры, которые не поддерживают новый синтаксис. Если остановиться на этом, то CSS Nesting действительно выглядит как синтаксический сахар.</p>
<p>Однако если продолжить углубляться, и внимательнее почитать комментарии и высказывания Таба Аткинса, то всплывает интересная деталь. Например, он между делом <a href="https://github.com/w3c/csswg-drafts/issues/2701#issuecomment-395504004">пишет о расширении CSSOM</a>. Эти изменения уже <a href="https://drafts.csswg.org/css-nesting-1/#cssom">получили отражение в черновике</a> CSS Nesting. В частности, вводится новый интерфейс CSSNestingRule, наследуемый от CSSRule, а также расширяется интерфейс CSSStyleRule. Это означает, что браузер должен отражать CSS с использованием вложенности на объектную модель так как задал «автор». Иными словами, вложенность не разворачивается в линейную структуру (как в примерах), а сохраняется. Конечно, браузер может проводить линеаризацию в своих недрах, как, например, браузеры индексируют селекторы для более быстрого матчинга. Но это находится за рамками спецификации и зависит от имплементации.</p>
<p>Таким образом, новый синтаксис не совсем сахар и браузер не превращается в препроцессор, потому что новый синтаксис превращается в иное представление в CSSOM. Но зачем это нужно?</p>
<p>Начну с менее очевидного — комбинаторный взрыв и потенциал для оптимизаций. Рассмотрим такой пример:</p>
<pre><code tabindex="0" class="language-css">ul, ol {
    &amp; ol, &amp; ul {
        &amp; ol, &amp; ul {
            /* ... */
        }
    }
}
</code></pre>
<p>Здесь указано три блока и <a href="https://www.w3.org/TR/selectors-4/#syntax">шесть сложных селекторов</a>. Если развернуть эту конструкцию в линейную форму, то получится 2 + 4 + 8 = 14 сложных селекторов:</p>
<pre><code tabindex="0" class="language-css">ul,
ol,
ul ul,
ul ol,
ol ul,
ol ol,
ul ul ul,
ul ul ol,
ul ol ul {
    /* … */
}
</code></pre>
<p>Если использовать <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:is"><code>:is()</code></a>, то можно остаться в рамках шести сложных селекторов:</p>
<pre><code tabindex="0" class="language-css">ul,
ol,
:is(ol, ul) ul,
:is(ol, ul) ol,
:is(:is(ol, ul) ul, :is(ol, ul) ol) ul,
:is(:is(ol, ul) ul, :is(ol, ul) ol) ol {
    /* … */
}
</code></pre>
<p>Можно попробовать оптимизировать в три:</p>
<pre><code tabindex="0" class="language-css">:is(ul, ol),
:is(ul, ol) :is(ul, ol),
:is(ul, ol) :is(ul, ol) :is(ul, ol) {
    /* … */
}
</code></pre>
<p>От того, что селекторов стало меньше, работы у браузера не уменьшилось. Последний вариант не менее сложный, чем первый (из 14 селекторов). Конечно, плюс в том, что браузер может это представлять внутри себя более оптимально, чем вариант из 14 селекторов. Но чтобы прийти к оптимальному варианту, браузеру нужно делать какие-то оптимизации на этапе анализа CSS по группировке селекторов. Учитывая, что в современных сайтах и приложениях содержатся тысячи правил (селекторов), то их группировка и оптимизация выглядит очень затратной, если вообще возможной, и браузеры могут эту оптимизацию не производить (как, например, не весь JavaScript оптимизируется на этапе разбора). Поэтому, стили организованные с использованием вложенности, имеют больший потенциал для группировки селекторов и внутренних оптимизаций.</p>
<p>Также стоит заметить, что <code>:is()</code> не панацея и этот функциональный псевдокласс применим в данном примере, так как сложные селекторы были одной специфичности. Если же специфичность у селекторов разная, то для всех селекторов внутри <code>:is()</code> назначается специфичность равная максимальной специфичности среди них (сам псевдокласс <code>:is()</code> не делает вклада в специфичность). Таким образом, в случае, если специфичность у селекторов разная, то <code>:is()</code> уже нельзя использовать для уменьшения числа селекторов. Но с вложенностью у браузеров остаётся возможность оптимизации, так как они и не должны будут использовать внутри <code>:is()</code>, а скорее некоторые свои внутренние механизмы, которые будут учитывать специфичность правильно.</p>
<p>Ещё неочевидный момент, возможность подмешать стилевой блок (в том числе, с вложенностью) в другой блок. Техническая возможность динамически менять таблицы стилей и отдельные правила существует уже лет 20. Даже Internet Explorer 6 это мог (хотя у него было нестандартное API для этого, то есть не по спеке, потому что она появилась позже). Другой вопрос, что это почти никак не используется в веб-разработке, да и мало кто об этом знает. Вроде как некоторые CSS-in-JS решения используют, но это неточно.</p>
<p>Так вот теперь будет возможно подмешивать стилевые блоки в другие блоки. Тут мне видится самым сложным это найти нужный блок. Да и хороших примеров пока не знаю. Но была бы возможность, а применение найдётся. Те же кастомные свойства на этапе внедрения тоже считали бесполезными, а потом открыли много крутых техник, среди которых, например, <a href="https://www.kizu.ru/variable-order/">сортировка таблицы на CSS</a>.</p>
<h2>Инлайновые селекторы</h2>
<p>Ну и ключевое, что вносит <a href="https://drafts.csswg.org/css-nesting-1/">CSS Nesting</a> — это возможность стилизации состояний элемента, его псевдоэлементов и дочерних элементов, с использованием атрибута <code>style</code>. Да, содержимое <code>style</code> — такой же блок стилей, но без селектора (если точнее, то CSSStyleDeclaration). На данный момент, этот атрибут позволяет задать стили только для самого элемента. Если необходимо задать стили для состояний наведения <code>:hover</code> или фокуса <code>:focus</code>, то для этого нужно описывать стили отдельно. Либо, что тоже не редкость, прибегают к решениям на JavaScript, чтобы менять стили состояний по событиям. Однако JavaScript пока ещё не может стилизовать псевдоэлементы, поэтому <code>::before</code> и <code>::after</code> обычно превращаются в дополнительные вложенные элементы. А для других псевдоэлементов по-прежнему нужны стили сбоку.</p>
<p>В общем, ценность и возможности атрибута <code>style</code> весьма скромные. Это всегда было некоторым камнем преткновения, что <code>style</code> ограничен и не имеет возможности развиваться вместе с CSS. Несмотря на то, что возможно это и к лучшему (к этому ещё вернёмся), случаи бывают разные.</p>
<p>Теперь же, с CSS Nesting станет возможным следующее:</p>
<pre><code tabindex="0" class="language-html">&lt;button style=&quot;
    color: black;
    &amp;::before { content: url(icon.png) }
    &amp;:hover { color: gray }
    &amp;:focus { color: blue }
    @media (max-width: 400px) {
        &amp; { width: 100% }
    }
&quot;&gt;
        Click me!
&lt;/button&gt;
</code></pre>
<p>Оставим на потом обсуждение, насколько это хорошо или плохо. Пока речь про возможности. И выглядит так, что с CSS Nesting атрибут <code>style</code> станет наравне с остальными способами задания стилей. Конечно, этим закрываются не все возможности CSS и часть остаётся за бортом, например, директивы <code>@font-face</code>, <code>@keyframes</code>, <code>@supports</code> и подобные.</p>
<p>Уже сейчас CSS Nesting должен заиграть для вас новыми красками. Но и это ещё не финал 😉</p>
<h2>Возможны миксины</h2>
<p>Сразу предупрежу, что в этой секции будут мои домыслы. Однако они могут быть недалеки от правды, если сложить А + Б. Где «А» это CSS Nesting, а «Б» это давняя мечта многих — примеси или миксины.</p>
<p>Начну с предыстории. Летом 2015 уже знакомый нам Таб Аткинс формализовал первый черновик спецификации <a href="https://tabatkins.github.io/specs/css-apply-rule/">CSS @apply Rule</a>. Это было на фоне внедрения <a href="https://www.w3.org/TR/css-variables-1/">CSS Custom Properties</a>. Ключевая идея: раз в значение кастомных свойств можно задать что угодно, почему бы не задавать в значении блок деклараций. Тогда с помощью директивы <code>@apply</code>, такие значения (блоки) можно подмешивать в другие блоки. То есть, как несложно догадаться, получаем те самые миксины.</p>
<p>Выглядело это так:</p>
<pre><code tabindex="0" class="language-css">:root {
    --toolbar-theme: {
        background-color: hsl(120, 70%, 95%);
        border-radius: 4px;
        border: 1px solid var(--theme-color, late);
    };
    --toolbar-title-theme: {
        color: green;
    };
}

.toolbar {
    @apply --toolbar-theme;
}

.toolbar &gt; .title {
    @apply --toolbar-title-theme;
}
</code></pre>
<p>Если не вдаваться в детали, то в целом должно быть знакомо и понятно. Механика кастомных свойств, а именно переопределение значений (блоков) через каскад. Это кстати, первый прецедент, когда внутри блока деклараций появляется директива.</p>
<p>Выглядело многообещающе, но если начать вдаваться в детали, то возникают вопросы. До широкого обсуждения спецификация не дошла, в сентябре 2018 Таб Аткинс её отменил. Хотя это не помешало Angular сообществу подхватить идею и достаточно активно использовать в экосистеме фреймворка, насколько знаю.</p>
<p>Почему эту идею похоронили? Она не решала проблему. Ведь таким образом можно подмешать набор деклараций в конкретный блок, а этого недостаточно. Если, например, мы хотим подмешать стили по принципу миксина, то мы скорее хотим повлиять не только на дефолтное состояние, но и на состояния <code>:hover</code>, <code>:focus</code> и другие, а может и на псевдоэлементы или дочерние элементы. Получалось:</p>
<pre><code tabindex="0" class="language-css">:root {
    --cool-border: {
        border: 1px solid gold;
    };
    --cool-border-hover: {
        border-color: orange;
    };
    --cool-border-focus: {
        border-color: blue;
    }
}

.button {
    @apply --cool-border;
}

.button:hover {
    @apply --cool-border-hover;
}

.button:focus {
    @apply --cool-border-focus;
}
</code></pre>
<p>Очень многословно, сложно и мало похоже на настоящий миксин. То есть польза от нововведения выходила очень сомнительной. Не помню, где Таб писал об этом, но это и было основной причиной отмены спецификации. Идея оказалась неудачная. Чего-то не хватало.</p>
<p>И вот теперь на горизонте появляется модуль CSS Nesting, открывающий новые возможности и в этом вопросе. С новыми вводными, предыдущий пример можно здорово упростить:</p>
<pre><code tabindex="0" class="language-css">:root {
    --cool-border: {
        border: 1px solid gold;
        &amp;:hover {
            border-color: orange;
        }
        &amp;:focus {
            border-color: blue;
        }
    };
}

.button {
    @apply --cool-border;
}
</code></pre>
<p>Теперь миксин является целостной сущностью, его использование максимально простое и лаконичное, не нужно знать о его устройстве (в теории, естественно). Конечно, остаётся много открытых вопросов. Например, как должен миксин взаимодействовать с содержимым блока (декларациями и блоками), действительно ли миксины должны использовать механизм кастомных свойств для объявления, нужен ли каскад и так далее. С момента появления кастомных свойств в браузерах, появились новые ожидания от них, в частности добавилась новая директива <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@property"><code>@property</code></a>. Не исключено, что объявление миксинов будет осуществляться тоже через директиву, например, <code>@mixin</code>.</p>
<p>Поживём — увидим. И хотя это все лишь мои домыслы, кажется CSS Nesting как раз подводит к чему-то такому. Синтаксис же играет второстепенную роль, главное механика, возможности и решается ли проблема.</p>
<h2>Когда CSS Nesting появится в браузерах?</h2>
<p>На этот вопрос есть два ответа: «никогда» и «не менее чем через два года».</p>
<p>Ответ «никогда» может показаться странным, но это связано с отсутствием сигналов от разработчиков браузеров. Всё, что у нас есть — это черновик спецификации, пусть и официальный и прошедший <a href="https://github.com/w3c/csswg-drafts/issues/2701">публичную дискуссию</a>. А вот про мнение производителей браузеров или какие-либо обсуждения пока ничего не известно, по крайней мере мне. В трекерах есть достаточно свежие тикеты (<a href="https://bugs.chromium.org/p/chromium/issues/detail?id=1095675">Blink</a>, <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1648037">Firefox</a>, <a href="https://bugs.webkit.org/show_bug.cgi?id=223497">Safari</a>), но подробностей там нет — просто чтобы было. На «Can I Use» тоже пока <a href="https://github.com/Fyrd/caniuse/issues/4803">не добавили</a>.</p>
<p>Сегодня для новых фич такого масштаба необходим консенсус разработчиков ключевых браузеров, что функциональность должна быть в веб-платформе и это будет реализовано в браузерных движках. Разработчики — практики (а пока была только теория), и они потенциально могут найти технические проблемы, которые не позволят внедрить CSS Nesting либо вообще, либо в текущем виде. Поэтому, пока нет официального кивка со стороны разработчиков браузеров, будет сохраняться вероятность, что CSS Nesting не случится никогда.</p>
<p>Но допустим, что вендоры браузеров взяли и договорились, что CSS Nesting быть. Допустим, это случилось сегодня. Это не значит, что все тут же кинутся пилить. CSS Nesting — это мега-эпик, а эпики на этот год уже спланированы, вроде инициативы <a href="https://web.dev/compat2021/">Compat2021</a>. Так что попасть в планы CSS Nesting сможет только ближе к следующему году, а появляться в релизах начнёт не раньше второй половины 2022 года. И это оптимистичные прогнозы.</p>
<p>Для того, чтобы понять почему такие прогнозы, стоит пройтись по ключевым частям, которые предстоит реализовать.</p>
<h3>Изменение парсера CSS</h3>
<p>Технически не очень сложно. Если бы CSS Nesting был синтаксическим сахаром, то на этом пункте бы всё и закончилось. Эта часть является критичной, так как влияет на скорость загрузки страниц, поэтому внимание к этим изменениям будет повышенное. Хотя синтаксис CSS Nesting спроектирован так, чтобы иметь как можно меньшее влияние на время разбора. Поэтому тут ожидается меньше всего сложностей.</p>
<h3>Матчинг селекторов</h3>
<p>По идее, наиболее сложную часть в браузерах уже реализовали — это псевдоклассы <code>:is()</code> и <code>:where()</code>. Конечно, это явно не связанные вещи, но ведь вложенные правила можно развернуть в линейный список используя эти псевдоклассы. Конечно, ещё нужно написать эту конвертацию и могут возникнуть нюансы. Но наличие <code>:is()</code> и <code>:where()</code> явно упрощает внедрение CSS Nesting. И вероятно, как я писал выше, CSS Nesting можно будет использовать для оптимизаций. Но это неточно.</p>
<h3>Расширение CSSOM</h3>
<p>Само расширение (новые интерфейсы и расширение старых) не столь сложно, сколько сложна реализация логики по обновлению стилей при изменении структур CSSOM (то есть добавление и удаление вложенных правил). Ведь тут важно чтобы это не просто работало, но и работало быстро. А это всё за рамками спецификации.</p>
<h3>Переработка атрибута <code>style</code></h3>
<p>Именно «переработка», так как сейчас этот атрибут задаёт стили только для самого элемента, которые не влияют на стили дочерних элементов (кроме наследуемых свойств), не создают псевдоэлементы, не определяют состояний и условий (псевдоклассы), не реагируют на изменение окружения (<code>@media</code>). Этим фактом не могли не воспользоваться для оптимизаций. Очевидно, что часть оптимизаций работать перестанет. Возможно только в случае, если в значении атрибута <code>style</code> используются конструкции из CSS Nesting. Но в любом случае, придётся придумывать новую машинерию, чтобы всё заработало. Чего только стоит ситуация, что для вложенных правил (rule) в <code>style</code> корнем будет является некий виртуальный блок без селектора. Вероятно нужно будет мёржить правила из <code>style</code> с правилами из таблиц стилей, причём этот мёрж должен быть для каждого элемента и на лету. И всё это выглядит как угроза для производительности и в целом всё непросто. Пока этот пункт про <code>style</code> кажется самым сложным и рискованным.</p>
<h3>Браузерные DevTools</h3>
<p>Очевидно, что их нужно будет доработать, чтобы поддержать новую функциональность. Самое сложное придумать, как это отобразить. Вероятно придётся переосмыслить способ представления стилей в инспекторе. И ещё проверить, что нигде ничего не взорвалось.</p>
<h3>Тесты</h3>
<p>Для CSS тесты пишут как общие для спеки (пишут всем миром), так и конкретно под браузер, если нужно протестировать некоторые особенности. Тестов пока нет, и это тоже потребует немало времени и усилий. Без полного набора тестов нельзя будет сказать, корректно ли и полностью ли реализована функциональность.</p>
<p>В общем, объём работы выглядит внушительным. Поэтому на быстрое внедрение рассчитывать не приходится.</p>
<h2>Влияние CSS Nesting на разработку</h2>
<p>Начнём с того, что все инструменты, которые работают с CSS, сломаются в той или иной степени при использовании CSS Nesting:</p>
<ul>
<li>Редакторы: раскраска, рефакторинг, саджест и другие;</li>
<li>Раскраска кода;</li>
<li>Сборщики и траспайлеры CSS;</li>
<li>Минификаторы CSS;</li>
<li>Инструменты по сбору покрытия, критический CSS и прочие;</li>
<li>И многие другие.</li>
</ul>
<p>Всё это нужно будет дорабатывать. Доработки разного уровня сложности и требуют разных временных затрат. Поэтому сомнительно ожидать, что все инструменты разом поддержат CSS Nesting.</p>
<p>Также CSS Nesting может повлиять на распределение использования инструментов: слабо развивающиеся или заброшенные проекты (но популярные в прошлом), или те которые будут долго тянуть с поддержкой, могут потерять своих пользователей. CSS Nesting — первая такая функциональность, которая серьёзно влияет на обратную совместимость, а точнее её ломает. Но всё это станет актуальным, только когда производители браузеров решат внедрять CSS Nesting. А пока этого не случилось, нет смысла что-либо внедрять, кроме как ради академического интереса.</p>
<p>Есть также мнение, что CSS Nesting, с учётом потенциального расширения возможностей <code>style</code>, укрепит позиции апологетов CSS-in-JS. Чтобы не было путаницы, стоит уточнить, что речь про тех, кто использует одну из библиотек CSS-in-JS. Так вот, для пользователей таких библиотек скорее всего ничего не поменяется. А вот для их авторов потенциально будет проще реализовать некоторые механизмы. То есть CSS Nesting может повлиять только на техническое исполнение, нежели на идеологический аспект. Кстати, многие решения CSS-in-JS выносят статические части стилей в обычные файлы и используют DOM и CSSOM, наравне с атрибутом <code>style</code>. В таких случаях, используются все возможности CSS (состояния, псевдоэлементы, <code>@media</code> и другие).</p>
<p>Но есть ещё и техника, когда все стили помещают в атрибут <code>style</code> с помощью JavaScript. Такое сегодня часто можно увидеть в React-компонентах и виджетах, так как это позволяет не волноваться за поставку и внедрение в страницу сопутствующего CSS. Это тоже своего рода CSS-in-JS. И в таких случаях обычно используют события (JavaScript), чтобы менять состояние (<code>:hover</code>, <code>:focus</code> и другие) или реагировать на изменение окружения (<code>@media</code>). В таких случаях, с CSS Nesting во многих случаях получится избавиться от необходимости в JavaScript. На мой взгляд, это хорошо. И хотя я не разделяю такой подход (когда все стили в атрибуте <code>style</code>) и считаю его антипаттерном, но <em>ситуации бывают разные</em> и иногда это может быть оправдано.</p>
<p>По моему мнению, диспозицию в подходах к организации стилей CSS Nesting не меняет. Но, да, он поможет подходам CSS-in-JS улучшить техническую реализацию.</p>
<h2>Заключение</h2>
<p>В завершении, хотелось бы отметить, что помимо потенциального удобства в написании стилей, оптимизаций в браузерах и новых возможностей, CSS Nesting несёт и увеличение сложности. Сложность в изучении, валидации, обработке, сборке, интроспекции, отладке и так далее. И чтобы CSS Nesting стал явью, нужно усилие большого количества людей. Стоят ли эти усилия того, пока не ясно. Но на мой взгляд, с учётом потенциальных миксинов, внедрение CSS Nesting — вопрос времени. Вопрос только в том, насколько долго ждать.</p>
<hr>
<p><em>Скомпилировано из публикаций канала «<a href="https://t.me/gorshochekvarit/125">Горшочек варит</a>» Романа Дворнова с разрешения автора — прим. редактора.</em></p>

                    ]]></description><pubDate>Fri, 23 Apr 2021 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/css-nesting/</guid></item><item><title>Миграция с Vue CLI на Vite</title><link>https://web-standards.ru/articles/vue-cli-to-vite/</link><description><![CDATA[
                        <p>Я недавно переносил свои проекты на Vue 2 со стэка Vue CLI и Webpack на Vite. После того, как я сделал это в третий раз, у меня сложилось представление об этом процессе, которое я излагаю в этой статье.</p>
<h2>package.json</h2>
<h3>devDependencies</h3>
<p>Давайте удалим зависимость <code>@vue/cli-service</code> и заменим её на <code>vite</code> 🚀</p>
<pre><code tabindex="0" class="language-bash">npm un @vue/cli-service
npm i vite -D
</code></pre>
<pre><code tabindex="0" class="language-diff">    &quot;devDependencies&quot;: {
-       &quot;@vue/cli-service&quot;: &quot;4.3.1&quot;,
+       &quot;vite&quot;: &quot;2.1.3&quot;,
</code></pre>
<p>Вы можете также удалить все остальные зависимости, которые начинаются с <code>@vue/cli-plugin-xxx</code>, поскольку они всё равно больше не будут работать, например:</p>
<pre><code tabindex="0" class="language-bash">npm un vue/cli-plugin-babel vue/cli-plugin-eslint vue/cli-plugin-unit-jest
</code></pre>
<pre><code tabindex="0" class="language-diff">    &quot;devDependencies&quot;: {
-       &quot;@vue/cli-plugin-babel&quot;: &quot;4.3.1&quot;,
-       &quot;@vue/cli-plugin-eslint&quot;: &quot;4.3.1&quot;,
-       &quot;@vue/cli-plugin-unit-jest&quot;: &quot;4.3.1&quot;,
</code></pre>
<p>Если вы используете Vue 2, нужно будет добавить плагин <code>vite-plugin-vue2</code>, который мы будем использовать в нашем <code>vite.config.js</code>:</p>
<pre><code tabindex="0" class="language-bash">npm i vite-plugin-vue2 -D
</code></pre>
<pre><code tabindex="0" class="language-diff">    &quot;devDependencies&quot;: {
+       &quot;vite-plugin-vue2&quot;: &quot;1.4.2&quot;,
</code></pre>
<p>Также, если вы используете Git-хуки, без сомнения нужно будет установить <code>yorkie</code>, чтобы все заработало как раньше (думаю, что этот инструмент был частью Vue CLI, а в Vite его нет).</p>
<pre><code tabindex="0" class="language-bash">npm i yorkie -D
</code></pre>
<h3>scripts</h3>
<p>Мы заменим скрипт <code>serve</code> для Vue CLI на соответствующий скрипт для Vite:</p>
<pre><code tabindex="0" class="language-diff">    &quot;scripts&quot;: {
-       &quot;serve&quot;: &quot;vue-cli-service serve&quot;,
+       &quot;dev&quot;: &quot;vite&quot;,
</code></pre>
<p>Если вам ближе использование слова <code>serve</code> вместо <code>dev</code>, можете использовать и его.</p>
<p>Мы еще вернемся к скриптам <code>build</code>, <code>test</code> и <code>lint</code> в конце статьи.</p>
<h2>index.html</h2>
<p>Давайте посмотрим на наш <code>public/index.html</code>, который мы должны переместить в корневую папку проекта.</p>
<p>Мы также должны добавить скрипт, который является точкой входа приложения и без которого Vite не сможет собрать проект:</p>
<pre><code tabindex="0" class="language-html">&lt;script type=&quot;module&quot; src=&quot;/src/main.js&quot;&gt;&lt;/script&gt;
</code></pre>
<p>И наконец, мы должны заменить путь до фавиконки <code>&lt;%= BASE_URL %&gt;favicon.ico</code> на более простой <code>/favicon.ico</code> (Vite сможет отыскать его сам в папке <code>public</code>).</p>
<pre><code tabindex="0" class="language-diff">    &lt;meta charset=&quot;utf-8&quot;&gt;
    &lt;meta charset=&quot;x-ua-compatible&quot; content=&quot;ie=edge&quot;&gt;
    &lt;meta charset=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot;&gt;
-   &lt;link rel=&quot;icon&quot; href=&quot;&lt;%= BASE_URL %&gt;favicon.ico&quot;&gt;
+   &lt;link rel=&quot;icon&quot; href=&quot;/favicon.ico&quot;&gt;
    …
    &lt;div id=&quot;app&quot;&gt;&lt;/div&gt;
+   &lt;script type=&quot;module&quot; src=&quot;/src/main.js&quot;&gt;&lt;/script&gt;
</code></pre>
<h2>vite.config.js</h2>
<p>Мы должны переименовать наш файл с конфигурацией сборки с <code>vue.config.js</code> на <code>vite.config.js</code>.</p>
<p>В начале файла нужно указать следующее:</p>
<pre><code tabindex="0" class="language-js">import { defineConfig } from 'vite'
import { createVuePlugin } from 'vite-plugin-vue2'

export default defineConfig({
    plugins: [
        createVuePlugin(),
    ],
})
</code></pre>
<p>Чтобы сделать миграцию на Vite максимально прозрачной для вашей команды разработчиков, давайте оставим тот же порт <code>8080</code>, который и был:</p>
<pre><code tabindex="0" class="language-js">import { defineConfig } from 'vite'
import { createVuePlugin } from 'vite-plugin-vue2'

export default defineConfig({
    server: {
        port: 8080,
    }
})
</code></pre>
<p>Все доступные опции для конфигурации можно <a href="https://vitejs.dev/config/#config-file">посмотреть в документации</a>.</p>
<h3><code>@</code> alias</h3>
<p>Если вы используете псевдонимы Webpack для импортов в файлах вашего проекта, мы должны переопределить их:</p>
<pre><code tabindex="0" class="language-js">import { defineConfig } from 'vite'
import { createVuePlugin } from 'vite-plugin-vue2'

export default defineConfig({
    resolve: {
        alias: [
            {
                find: '@',
                replacement: path.resolve(__dirname, 'src')
            }
        ]
    },
})
</code></pre>
<p>Для пользователей WebStorm: при использовании Webpack вам достаточно было прописать в настройках IDE путь до файла с конфигурацией Webpack (для Vue CLI это был путь до базового файла Webpack: <code>node_modules/@vue/cli-servive/webpack.config.js</code>) и WebStorm магическим образом подхватывал псевдоним <code>@</code>, что позволяло очень просто обращаться к библиотекам.</p>
<p>Пока WebStorm не умеет парсить <code>vite.config.js</code>, и мы должны помочь ему, прописав настройки псевдонимов вручную в <code>webStorm.config.js</code>:</p>
<pre><code tabindex="0" class="language-js">System.config({
    paths: {
        '@/*': './src/*',
    },
})
</code></pre>
<p>Это всё, что вы должны сделать :)</p>
<h2>Расширение <code>.vue</code> и импорты</h2>
<p>Для Webpack было совершенно не обязательно указывать расширение <code>.vue</code> для файлов компонентов, но не для Vite. Нужно заменить:</p>
<pre><code tabindex="0" class="language-diff">- import DotsLoader from '@/components/DotsLoader'
+ import DotsLoader from '@/components/DotsLoader.vue'
</code></pre>
<p>Не по теме, но из моего опыта: использовать импорты с полными путями — лучшая практика.</p>
<h2>Очистка маршрутов для ленивой загрузки</h2>
<p>Вы со спокойной душой можете удалить комментарии <code>webpackChunkName</code> для генерации чанков для определенных маршрутов:</p>
<pre><code tabindex="0" class="language-js">{
    path: '/links',
    name: 'linksPage',
    component: () =&gt; import(/* webpackChunkName: &quot;links&quot; */ './views/LinksPage.vue'),
},
</code></pre>
<p>Используя Vite можно писать так:</p>
<pre><code tabindex="0" class="language-js">{
    path: '/links',
    name: 'linksPage',
    component: () =&gt; import('./views/LinksPage.vue'),
},
</code></pre>
<p>Я в этом не эксперт, но если вы действительно хотите изменить имя для вашего чанка, вероятно, вы можете это сделать принудительно с помощью Rollup <a href="https://rollupjs.org/guide/en/#outputentryfilenames"><code>output.entryFileNames</code></a>.</p>
<p>Также, можете посмотреть опции для сборщика Vite <a href="https://vitejs.dev/config/#build-rollupoptions"><code>build.rollupOptions</code></a>и перенести часть задач на него.</p>
<h2>Переменные окружения</h2>
<p>Мы должны заменить все <code>process.env</code>, которые Vite не понимает. Для Vite нужно использовать <code>import.meta.env</code>.</p>
<p>Если ваш роутер использует встроенную переменную окружения <code>BASE_URL</code>, то ее имя надо будет заменить на <code>import.meta.env.BASE_URL</code>:</p>
<pre><code tabindex="0" class="language-diff">    const router = new Router({
        mode: 'history',
-       base: process.env.BASE_URL,
+       base: import.meta.env.BASE_URL,
        routes: [...firstLevelRoutes, ...backOfficeRoutes]
    })
</code></pre>
<p>Другой встроенной переменной окружения является <code>import.meta.env.PROD</code>. Давайте будем использовать её везде, где можем:</p>
<pre><code tabindex="0" class="language-diff">- if (process.env.NODE_ENV === 'production') {
+ if (import.meta.env.PROD) {
</code></pre>
<p>Убедитесь, что настройка <code>NODE_ENV=production</code> соответствует настройкам среды в файле <code>.env</code> или в переменных окружения сервера, на котором собирается ваше приложение во время релиза. Подробнее можно посмотреть <a href="https://vitejs.dev/guide/env-and-mode.html#env-variables-and-modes">в документации по переменным окружения и режимах работы Vite</a>.</p>
<p>Что касается ваших собственных переменных окружения, которые вы использовали раньше с префиксом <code>VUE_APP</code> — вам нужно будет заменить его на <code>VITE</code>. Любая переменная окружения, которая будет начинаться с <code>VITE_xxx</code>, будет доступна во всём вашем коде.</p>
<p>Вот пример для моей переменной окружения <code>BACKEND_URL</code>:</p>
<pre><code tabindex="0" class="language-diff">    export const backendInstance = axios.create({
-       baseURL: `${process.env.VUE_APP_BACKEND_URL}/api`,
+       baseURL: `${import.meta.env.VITE_APP_BACKEND_URL}/api`,
        timeout: 10000,
    })
</code></pre>
<p>Файл <code>.env.local</code>:</p>
<pre><code tabindex="0" class="language-js">VITE_APP_BACKEND_URL=http://localhost:3001
</code></pre>
<h2>Тесты</h2>
<p>Поскольку мы больше не можем использовать <code>vue-cli-service test:unit</code>, давайте настроим наши тесты.</p>
<p>Для начала надо обновить скрипт для тестов <code>test</code>:</p>
<pre><code tabindex="0" class="language-diff">- &quot;test:unit&quot;: &quot;vue-cli-service test:init&quot;,
+ &quot;test&quot;: &quot;jest&quot;,
</code></pre>
<p>Затем пройдемся по шагам, описанным в <a href="https://vue-test-utils.vuejs.org/installation/#manual-installation">документации</a>.</p>
<p>Если вы использовали файл <code>babel.config.js</code>, то нужно будет сделать что-то вроде:</p>
<pre><code tabindex="0" class="language-diff">- presets: ['@vue/cli-plugin-babel/preset'],
+ presets: ['@babel/preset-env'],
</code></pre>
<p>У меня были ошибки для выражения <code>import.meta.env</code>.</p>
<p>К сожалению, простых настроек для модульных тестов в Vite нет, но этот <a href="https://github.com/vitejs/vite/issues/1149#issuecomment-775033930">комментарий</a> мне помог.</p>
<p>Мой файл <code>babel.config.js</code> теперь выглядит так:</p>
<pre><code tabindex="0" class="language-js">module.exports = {
    presets: ['@babel/preset-env'],
    // Чтобы Jest не нервировали 'import.meta.xxx'
    plugins: [
        function () {
            return {
                visitor: {
                    MetaProperty(path) {
                        path.replaceWithSourceString('process')
                    },
                },
            }
        },
    ],
}
</code></pre>
<p>Вы можете усилить сборку плагином <a href="https://github.com/javiertury/babel-plugin-transform-import-meta">babel-plugin-transform-import-meta</a> для Babel, который будет исправлять автоматически ошибки в ваших тестах, связанные с <code>import.meta</code>.</p>
<p>Вы можете почитать и обсудить <a href="https://github.com/vitejs/vite/issues/1955">в отдельном ишью на GitHub</a> совместную работу Vite и Jest.</p>
<h3>Ошибка <code>regeneratorRuntime</code></h3>
<pre><code tabindex="0" class="language-bash">    49 | export const actions = {
&gt;   50 |   init: async ({ commit }, routeContext) =&gt; {
ReferenceError: **regeneratorRuntime** is not defined
</code></pre>
<p>Устанавливая <code>regenerator-runtime</code> и ссылаясь на него в моем <code>setupTests.js</code>, я обнаружил, что эту ошибку уже починили.</p>
<pre><code tabindex="0" class="language-bash">npm i regenerator-runtime -D
</code></pre>
<p>Файл <code>jest.config.js</code>:</p>
<pre><code tabindex="0" class="language-js">module.exports = {
    moduleFileExtensions: [
        'js',
        'json',
        // Говорит Jest обрабатывать файлы *.vue
        'vue',
    ],
    transform: {
        // Обработай файл *.vue с помощью vue-jest
        '.*\\.(vue)$': 'vue-jest',
        // Обработай файлы *.js с помощью babel-jest
        '.*\\.(js)$': 'babel-jest',
    },
    setupFiles: ['./setupTests'],
    moduleNameMapper: {
        '^@/(.*)$': '&lt;rootDir&gt;/src/$1',
    },
    collectCoverage: false,
}
</code></pre>
<p>Первая строчка файла конфигурации <code>setupTests.js</code> будет такой:</p>
<pre><code tabindex="0" class="language-js">import 'regenerator-runtime/runtime'
</code></pre>
<h2>Линтер</h2>
<p>Я заменил два скрипта для линтинга одним:</p>
<pre><code tabindex="0" class="language-diff">- &quot;lint&quot;: &quot;vue-cli-service lint src --no-fix&quot;,
- &quot;lint:fix&quot;: &quot;vue-cli-service lint src&quot;,
+ &quot;lint&quot;: &quot;eslint src&quot;,
+ &quot;lint:fix&quot;: &quot;eslint src --fix&quot;,
</code></pre>
<h2>Развертывание приложений</h2>
<p>В этом примере мое приложение крутится в облаке S3 и CloudFront. У меня два окружения для режима продакшена: <code>preprod</code> и <code>prod</code>. Поэтому у меня и два файла для настройки окружения <code>.env</code>:</p>
<ul>
<li><code>.env.preprod</code></li>
<li><code>.env.prod</code></li>
</ul>
<p>Когда мы собираем наше приложение с помощью Rollup, мы указываем режим, и Vite меняет переменные окружения на те, которые соответствуют этому режиму.</p>
<p>Это очень похоже на Vue CLI, поэтому обновление скриптов в <code>package.json</code> очень простое:</p>
<pre><code tabindex="0" class="language-diff">- &quot;build-preprod&quot;: &quot;vue-cli-service build --mode preprod&quot;,
- &quot;build-prod&quot;: &quot;vue-cli-service build --mode prod&quot;,
+ &quot;build-preprod&quot;: &quot;vite build --mode preprod&quot;,
+ &quot;build-prod&quot;: &quot;vite build --mode prod&quot;,
</code></pre>
<p>Если вам нужно больше подробностей, они хорошо описаны <a href="https://vitejs.dev/guide/env-and-mode.html#modes">в документации</a>.</p>
<p>Небольшая ремарка: мне пришлось также поменять максимальный размер для чанка в <code>vite.config.js</code>:</p>
<pre><code tabindex="0" class="language-js">import { defineConfig } from 'vite'
import { createVuePlugin } from 'vite-plugin-vue2'

export default defineConfig({
    build: {
        // По умолчанию 500
        chunkSizeWarningLimit: 700,
    },
})
</code></pre>
<p><em>Так делать нехорошо, я знаю.</em></p>
<h2>Визуализация сборки</h2>
<p>Это последнее упоминание Vue CLI, которое было в моей кодовой базе:</p>
<pre><code tabindex="0" class="language-js">&quot;build:report&quot;: &quot;vue-cli-service build --report&quot;,
</code></pre>
<p>Давайте поищем что-то похожее в мире Rollup. Использование плагина <a href="https://github.com/btd/rollup-plugin-visualizer">rollup-plugin-visualizer</a> оказалось достаточно хорошим решением для меня.</p>
<p>Я импортировал этот плагин и прописал его в <code>vite.config.js</code>:</p>
<pre><code tabindex="0" class="language-js">import { defineConfig } from 'vite'
import { createVuePlugin } from 'vite-plugin-vue2'
import visualizer from 'rollup-plugin-visualizer'

export default defineConfig({
    plugins: [
        createVuePlugin(),
        visualizer(),
    ],
})
</code></pre>
<p>Результат теста в сгенерированном файле <code>stats.html</code>:</p>
<p><img src="https://web-standards.ru/articles/vue-cli-to-vite/images/bundle.png" alt="Обзор содержимого JS-бандла."></p>
<h2>Несколько метрик</h2>
<h3>Время старта</h3>
<ul>
<li>Загрузка Vite: около 4 секунд — предполагается, что это время не сильно изменится, даже если проект будет расти 🙌</li>
<li>Загрузка с Vue CLI и Webpack: около 30 секунд — это время постоянно растет при увеличении количества файлов в проекте 😢</li>
</ul>
<h3>Горячая перезагрузка</h3>
<p><strong>Vite:</strong></p>
<ul>
<li>Простые изменения (HTML-файлы, CSS-классы): очень быстро! 🚀</li>
<li>Серьёзные изменения (переименование функций JS, добавление компонентов): не уверен, иногда мне было удобнее перезагрузить страницу самому.</li>
</ul>
<p><strong>Vue CLI и Webpack</strong>:</p>
<ul>
<li>Простые изменения: около 4 секунд 😕</li>
<li>Серьезные изменения: никогда не жду, сам перезагружаю страницу.</li>
</ul>
<h3>Первая загрузка страницы</h3>
<p>Мы запрашиваем страницу в первый раз после запуска Vite. У меня было веб-приложение с большим количеством компонентов. Давайте взглянем на вкладку Network в Chrome DevTools:</p>
<ul>
<li><strong>Vite</strong>: загрузка около 1430 JS-файлов длится примерно 11 секунд 😟</li>
<li><strong>Vue CLI и Webpack</strong>: загрузка около 23 JS-файлов длится примерно 6 секунд 👍</li>
</ul>
<p><img src="https://web-standards.ru/articles/vue-cli-to-vite/images/compare.png" alt="Сравнение разных вариантов сборки в панели Network: слева Vite, справа Vue CLI."></p>
<p>Посмотрим, как будет развиваться проект. Пока у меня не было достаточного опыта использования Vite, но начало многообещающее. Надо будет ещё оценить все возможности этого сборщика. Например, заявлено, что в Vite круто реализована ленивая загрузка компонентов!</p>
<h2>Заключение</h2>
<p>Поработав некоторое время с Vite, могу сказать, что это был очень приятный опыт 🌟 И теперь все тяжелее и тяжелее работать с Webpack на других проектах!</p>

                    ]]></description><pubDate>Fri, 09 Apr 2021 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/vue-cli-to-vite/</guid></item><item><title>Поддержка веба во Flutter теперь стабильная</title><link>https://web-standards.ru/articles/flutter-web-support/</link><description><![CDATA[
                        <p>Мы позиционируем Flutter как UI-фреймворк для сборки красивых приложений на любой платформе. Сегодня мы анонсируем поддержку сборки веб-приложений во <a href="https://medium.com/flutter/whats-new-in-flutter-2-0-fe8e95ecc65">Flutter 2</a>.</p>
<p>Первая версия Flutter поддерживала только мобильные платформы (iOS и Android). И это позволило разработчикам загрузить в магазины App Store и Google Play больше 150 000 приложений. Появление сборки для веба означает не только то, что уже написанные приложения могут быть доступны более широкой аудитории, это означает появление новых способов взаимодействия с пользователем в вебе.</p>
<p>В этом релизе мы сфокусируемся на трех сценариях использования:</p>
<ul>
<li><strong>Прогрессивные веб-приложения (PWA) —</strong> которые можно запустить в браузере и на десктопе как обычные приложения.</li>
<li><strong>Одностраничные приложения (SPA) —</strong> веб-приложения для браузера, которые грузятся один раз, а затем передают и получают в основном только данные.</li>
<li><strong>Веб-версии существующих мобильных приложений на Flutter —</strong> с единой кодовой базой для всех платформ.</li>
</ul>
<p>Эта статья описывает то, что мы успели сделать на настоящий момент. Мы показываем на примерах, какие преимущества вы можете получить, используя поддержку веб-платформы в приложениях Flutter.</p>
<figure>
    <img src="https://web-standards.ru/articles/flutter-web-support/images/1.png" alt="Интерфейс приложения iRobot Education.">
    <figcaption>
        iRobot Education разработало приложение iRobot Coding, которое использует Flutter, доступно в вебе, чтобы каждый мог поучиться программировать
    </figcaption>
</figure>
<h2>Наше путешествие в веб</h2>
<p>Сегодня веб-платформа богата как никогда: <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API">поддержка графических ускорителей для 2D- и 3D-графики</a>, <a href="https://web.dev/progressive-web-apps/">поддержка работы приложений в офлайне</a> и <a href="https://web.dev/fugu-status/">доступ к API операционной системы и железу устройства</a>, на котором она установлена. Веб-приложения сегодня могут быть написаны на фреймворках вроде <a href="https://reactjs.org/">React</a>, <a href="https://angular.io/">Angular</a>, <a href="https://vuejs.org/">Vue</a>, <a href="https://flask.palletsprojects.com/">Flask</a>, которые используют эту платформу, чтобы дать разработчикам гибкость в создании веб-приложений.</p>
<p>Поскольку Flutter написан на языке <a href="https://dart.dev/">Dart</a>, который из коробки поддерживает трансляцию в JavaScript, следующий шаг — использование Flutter для веба — был совершенно естественным.</p>
<p>Наш подход заключается в создании набора инструментов, которые одинаково хорошо работали бы для всех платформ. Это позволяет разработчику быть уверенным, что его код запустится везде и без сюрпризов. Хотя можно было бы иметь и два фреймворка для сборки мобильных приложений и для сборки веб-приложений, незначительно отличающихся в поведении.</p>
<img src="https://web-standards.ru/articles/flutter-web-support/images/2.png" alt="Схема архитектуры для поддержки Flutter в вебе.">
<p>На уровне архитектуры, Flutter базируется на:</p>
<ul>
<li><strong>фреймворке, написанном на Dart</strong>, который использует общие абстракции, такие как виджеты, анимации и жесты;</li>
<li><strong>движке, написанном на C++</strong>, который рендерит код для целевого устройства, используя системные API.</li>
</ul>
<p>Сам Flutter написан на Dart, и около 700 000 строк кода используются для мобильной платформы, десктопа и веба. Код любого Flutter-приложения также написан на Dart: мы используем компилятор Dart для разработки мобильных приложений (<a href="https://dart.dev/tools/dartdevc">dartdevc</a>) и транслятор Dart (<a href="https://dart.dev/tools/dart2js">dart2js</a>) для перевода вашего кода в JavaScript, который, в свою очередь, может быть уже перенесен на сервер.</p>
<p>Благодаря поддежке трансляции программ на языке Dart в программы на JavaScript, наша работа сводилась лишь к замене низкоуровневого движка для рендеринга, написанного на C++, который ранее использовался только при рендеринге кода для мобильных приложений, на новый движок с поддержкой API веб-платформы. Теперь движок Flutter для веба предлагает использовать два варианта: рендеринг в оптимизированный HTML и рендеринг с помощью CanvasKit, который использует WebAssembly и WebGL для перевода команд движка Skia в команды рисования на канвасе в браузере. Flutter не умеет напрямую транспилировать виджеты в эквивалент на HTML.</p>
<p>Наша цель — предложить новый путь для веб-платформы, который улучшил бы пользовательский опыт.</p>
<h2>Путь к релизу в стабильной ветке</h2>
<p><a href="https://medium.com/flutter/web-support-for-flutter-goes-beta-35b64a1217c0">Релиз с поддержкой веба в бета-ветке</a> появился более года назад и мы получили много информации о том, как его используют разработчики. Мы работали с несколькими из них и сегодня их веб-приложения уже в продакшене.</p>
<p>С тех пор мы сделали большую часть задуманных усовершенствований в архитектуре фреймворка и добавили новые возможности, которые расширяют и оптимизируют поддержку веб-платформы. Мы сфокусировались на четырех областях: <strong>производительность</strong>, <strong>возможности, специфические для веба</strong>, <strong>поддержка десктопных экранов</strong> и <strong>плагины</strong>.</p>
<img src="https://web-standards.ru/articles/flutter-web-support/images/3.png" alt="Список возможностей Flutter для веб-платформы, доступные в стабильной ветке.">
<h3>Производительность</h3>
<p>Большая часть работы с самых первых релизов была посвящена производительности. В процессе мы лучше разобрались в характеристиках производительности и корректности применения различных технологий, доступных в вебе.</p>
<p>Первоначально мы стартовали с HTML и DOM-модели. В ней веб-движок Flutter переводил сгенерированную сцену в HTML, CSS или Canvas и рендерил каждый кадр на страницу как дерево HTML-элементов. Даже несмотря на очень хорошую браузерную поддержку и малые размеры, которые обеспечивал нам движок для рендеринга HTML, производительность для перерисовки была меньше, чем нужно для приложения с быстро менящейся графикой, наподобие <a href="https://rive.app/">Rive</a> — инструмента для совместного создания анимаций, написанный на Flutter.</p>
<figure>
    <img src="https://web-standards.ru/articles/flutter-web-support/images/4.png" alt="Интерфейс приложения Rive.">
    <figcaption>
        Rive, инструмент для создания пользовательских анимаций, перестроил свое приложение с помощью Flutter в Интернете, и теперь оно доступно в бета-версии.
    </figcaption>
</figure>
<p>Чтобы эффективно справиться с точностью, которая необходима для отрисовки сложной графики, мы начали экспериментировать с <a href="https://skia.org/user/modules/canvaskit">CanvasKit</a>, который позволял рисовать в браузере с помощью команд Skia, используя <a href="https://webassembly.org/">WebAssembly</a> и <a href="https://www.khronos.org/webgl/">WebGL</a>. Мы обнаружили, что CanvasKit может дать большую производительность, качество и точность отрисовки, используя графические ускорители. Вы можете посмотреть демо <a href="https://github.com/felixblaschke">Феликса Блашке</a>, талантливого участника Flutter-сообщества.</p>
<figure>
    <img src="https://web-standards.ru/articles/flutter-web-support/images/5.png" alt="Примеры абстрактной цветовой графики.">
    <figcaption>
        Демо-версия <a href="https://flutterplasma.dev/">Flutter Plasma</a>, созданная Феликсом Блашке и работающая в Safari, Firefox, Edge и Chrome.
    </figcaption>
</figure>
<p>Flutter поддерживает два режима рендеринга, каждый из которых обладает преимуществами или недостатками, в зависимости от сценария использования:</p>
<ul>
<li><strong>Режим HTML —</strong> использует комбинацию HTML-элементов, CSS, элементов на канвасе и SVG-элементов. Этот режим обеспечивает наименьший размер приложения.</li>
<li><strong>Режим CanvasKit —</strong> — полностью поддерживает возможности Flutter, доступные на мобильных платформах или десктопе, но добавляет 2 Мб к размеру веб-приложения.</li>
</ul>
<p>Для оптимизации характеристик вашего веб-приложения на Flutter для всех устройств, режим рендеринга выбирается автоматически. Приложение по умолчанию запускается с помощью движка HTML в мобильных браузерах и с помощью CanvasKit в браузерах на десктопе. Вы можете использовать ключ <code>--web-renderer html</code> или <code>--web-renderer canvaskit</code> для прямого выбора режима рендеринга.</p>
<h3>Возможности, специфические для веба</h3>
<p>Приложение на Flutter в браузере должно быть настолько же удобным, что и обычное веб-приложение. Поэтому мы добавили во Flutter лучшее из мира веба.</p>
<p>Веб-платформа имеет много сильных сторон, одна из них — большое количество пользователей. Одна из многих причин обеспечить поддержку веб-версии существующего Flutter-приложения — охватить пользователей за пределами магазинов мобильных приложений. Мы добавили поддержку роутинга с помощью (<a href="https://flutter.dev/docs/development/ui/navigation/url-strategies">URL-стратегий</a>), чтобы ваши пользователи могли получить доступ к вашему приложению, просто кликнув по ссылке.</p>
<figure>
    <img src="https://web-standards.ru/articles/flutter-web-support/images/6.png" alt="Демо-страница приложения Flutter Plasma.">
    <figcaption>
        <a href="https://flutterplasma.dev/">Демо-страница приложения Flutter Plasma</a> — это пример плагина <a href="https://pub.dev/packages/url_strategy">url_strategy</a>, основанного на кастомных URL-стратегиях Flutter.
    </figcaption>
</figure>
<p>Гиперссылки являются критически важным механизмом навигации в вебе. Новый <a href="https://pub.dev/documentation/url_launcher/latest/link/Link-class.html">виджет <code>link</code></a> в пакете <code>url_launcher</code> позволяет использовать возможности навигации по якорям внутри вашего приложения и переходы на внешние сайты. Вы можете использовать <code>link</code> в нужных вам виджетах, включая кнопки, встроенный текст, картинки, чтобы открывать ссылки в той же вкладке браузера или в новой.</p>
<p>Ещё одной неотъемлемой частью приложения является рендеринг текста. Разработка системы верстки текста была одной из самых больших проблем для поддержки Flutter в вебе. Поскольку в вебе отсутствует специальное API для обработки элементов с текстом, Flutter должен измерять размеры абзаца (класс <a href="https://api.flutter.dev/flutter/dart-ui/Paragraph-class.html"><code>paragraph</code></a>), обращаясь к методу <a href="https://api.flutter.dev/flutter/dart-ui/Paragraph/layout.html"><code>layout()</code></a>. Эти измерения могут серьезно повлиять на производительность, поэтому в новый <a href="https://github.com/flutter/flutter/issues/33523">механизм измерения размеров текста на канвасе</a> добавлена поддержка двух вариантов текста: простого и форматированного. Сейчас Flutter позволяет измерять размеры в вебе эффективно чтобы, например, рисовать выделение маркером в тексте.</p>
<p>Взаимодействие с текстом так же важно, как быстрый и точный рендеринг. Текст можно выделить, скопировать и вставить с помощью виджетов <code>SelectableText</code> и <code>EditableText</code>. Кроме того, текстовые поля формы поддерживают автозаполнение с помощью класса <a href="https://api.flutter.dev/flutter/widgets/AutofillGroup-class.html"><code>AutofillGroup</code></a>, который обращается к браузеру за пользовательскими данными, доступными для автозаполнения.</p>
<p>Flutter 2 частично поддерживает использование прогрессивных веб-приложений (PWA). PWA — это простой и безопасный способ преодолеть разрыв между мобильными приложениями и веб-приложениями силами <a href="https://web.dev/fugu-status/">проекта Fugu</a> команды Chrome.</p>
<figure>
    <img src="https://web-standards.ru/articles/flutter-web-support/images/7.png" alt="Интерфейс веб-приложения Invoice Ninja.">
    <figcaption>
        <a href="https://www.invoiceninja.com/">Invoice Ninja</a>, приложение для управления счетами, запустило PWA-версию, используя кодовую базу существующего мобильного приложения на Flutter.
    </figcaption>
</figure>
<p>Когда вы создаете веб-приложение на Flutter, в сборку включается файл веб-манифеста для настройки PWA и код для настройки сервис-воркера. <a href="https://developer.mozilla.org/en-US/docs/Web/Manifest">Манифест</a> содержит метаданные о том, как приложение должно запускаться, включая информацию об иконках и названии приложения. <a href="https://developers.google.com/web/ilt/pwa/introduction-to-service-worker">Сервис-воркеры</a> позволяют кэшировать ресурсы и запускать приложение офлайн. Когда Flutter-приложение будет запущено в браузере как PWA, пользователю будет доступна его установка на устройства, наряду с обычными приложениями на мобильном или десктопе.</p>
<h3>Поддержка экранов десктопов</h3>
<p>Мы хотим, чтобы веб-приложение на Flutter работало правильно, несмотря на форму и размер окна браузера. В мобильных браузерах Flutter-приложения уже имеют великолепную поддержку жестов и скроллов, независимо от того, поддерживается ли это в мобильном приложении. Браузеры на десктопе предлагают другие способы взаимодействия пользователя с элементами интерфейса, и Flutter должен поддерживать их на уровне фреймворка.</p>
<p>Например, пользователь, при просмотре контента на десктопе ожидает отображение полос прокрутки, которыми можно управлять с помощью мышки и клавиатуры. Поэтому новые <a href="http://flutter.dev/go/update-scrollbars">интерактивные полосы прокрутки</a> поддерживают темизацию <a href="https://api.flutter.dev/flutter/material/ScrollbarTheme-class.html"><code>ScrollbarTheme</code></a> и возможность прокручивать перетаскиванием ползунка. Класс контроллера <a href="https://api.flutter.dev/flutter/widgets/PrimaryScrollController-class.html"><code>PrimaryScrollController</code></a> теперь поддерживает <a href="http://flutter.dev/go/default-scroll-action">использование сочетания клавиш для навигации</a> сразу из коробки.</p>
<figure>
    <img src="https://web-standards.ru/articles/flutter-web-support/images/8.jpg" alt="Интерфейс веб-приложения Zurich Insurance.">
    <figcaption>
        Решение по управлению недвижимостью для <a href="https://www.zurich.com/">Zurich Insurance</a>, созданное компанией <a href="https://spicatech.co.uk/">Spica Technologies</a>, является отличным примером веб-поддержки бизнес-приложений Flutter, которую можно включить в десктопных браузерах.
    </figcaption>
</figure>
<p>Мы также увеличили <a href="https://github.com/flutter/flutter/issues/43350">плотность пикселей для отображаемого контента по умолчанию</a>, поскольку для указателя мыши она необходима, в отличие от тач-устройств. И мы добавили в фреймворк большой набор <a href="https://github.com/flutter/flutter/issues/60641">системных курсоров</a> для поддержки всех платформ.</p>
<p>Наконец, чтобы поддерживать <em>всех</em> пользователей, мы расширили используемую семантику для поддержки доступности на Windows, macOS и ChromeOS. В вебе дополнительное DOM-дерево, которое называется <a href="https://api.flutter.dev/flutter/semantics/SemanticsNode-class.html"><code>SemanticsNode</code></a> генерируется параллельно с DOM-деревом для объекта рендеринга <a href="https://api.flutter.dev/flutter/rendering/RenderObject-class.html"><code>RenderObject</code></a>. Дерево <code>SemanticsNode</code> переводит флаги, действия, подписи и другие семантические свойства в <a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA">ARIA-атрибуты</a>. Сейчас вы можете использовать <a href="https://support.microsoft.com/en-us/windows/complete-guide-to-narrator-e4397a0d-ef4f-b386-d8ae-c172f109bdb1">Narrator</a>, <a href="https://www.apple.com/accessibility/vision/">VoiceOver</a>, <a href="https://support.google.com/accessibility/android/answer/6007100">TalkBack</a> или <a href="https://support.google.com/chromebook/answer/7031755">ChromeVox</a> для навигации веб-приложений на Flutter.</p>
<h3>Экосистема плагинов</h3>
<p>И последнее, поддержка веб-платформы была добавлена для наиболее используемых плагинов, что обеспечило возможность поддержки веба для существующих приложений на Flutter. <a href="https://pub.dev/">Плагины Flutter</a> позволяют вашему коду взаимодействовать в нативными библиотеками для платформ, на которых запускается приложение. Когда вы запускаете Flutter-приложение в вебе, вы получаете доступ к библиотекам на JavaScript через плагины.</p>
<p>С начала бета-тестирования мы с помощью сообщества обеспечили поддержку веб-платформы в следующих плагинах:</p>
<ul>
<li><a href="https://pub.dev/packages/image_picker_for_web">image_picker</a>;</li>
<li><a href="https://pub.dev/packages/google_maps">google_maps</a>;</li>
<li><a href="https://pub.dev/packages/firebase_analytics">firebase_analytics</a>;</li>
<li><a href="https://pub.dev/packages/firebase_storage">firebase_storage</a>;</li>
<li><a href="https://pub.dev/packages/experimental_connectivity_web">connectivity</a>;</li>
<li><a href="https://pub.dev/packages/cloud_firestore">cloud_firestore</a>;</li>
<li><a href="https://pub.dev/packages/cloud_functions">cloud_functions</a>;</li>
<li><a href="https://pub.dev/packages/cross_file">cross_file</a>.</li>
</ul>
<h2>Что дальше</h2>
<p>Несколько лет назад использовать Flutter в вебе было невозможно на приемлемом уровне качества и производительности. Однако внедрение новых веб-технологий и постоянное развитие платформы позволило нам использовать намного больше низкоуровневых возможностей устройства. С поддержкой веб-платформы, Flutter обеспечивает необходимое удобство на большинстве устройств.</p>
<p>Многое в этом релизе стало возможным, благодаря предложениям и замечаниям со стороны пользователей фреймворка для веба на этапе бета-тестирования. И поэтому мы благодарим вас! Продвигаясь вперед, мы видим нашу главную цель в том, чтобы как можно быстрее разрешать все проблемы, что позволит нам оставаться сфокусированными на возможностях реализации высококачественных приложений на Flutter для <em>всех</em> целевых платформ.</p>
<figure>
    <img src="https://web-standards.ru/articles/flutter-web-support/images/9.png" alt="Интерфейс веб-приложения Moi Mobiili.">
    <figcaption>
        <a href="https://www.moi.fi/">Moi Mobiili</a>, современный оператор мобильной виртуальной сети, недавно запустил свое веб-приложение с помощью Flutter.
    </figcaption>
</figure>
<p>Вероятно, производительность будет всегда областью, в которую мы будем вкладываться. Наша цель — уменьшить размер кода и увеличить скорость отображения кадров (fps). Сегодня каждое веб-приложение на Flutter скачивает необходимый код движка для рендеринга. Мы ищем возможности закэшировать часть кода, чтобы увеличить скорость загрузки и уменьшить размер скачиваемого бандла. Недавно мы сделали демо-приложение Flutter Gallery, в котором уменьшили размер, используя отложенную загрузку библиотек, и планируем скоро рассказать о том, чему мы научились.</p>
<p>В течение следующих нескольких месяцев мы продолжим улучшать то, что было сделано:</p>
<ul>
<li>CanvasKit уже в стабильной ветке, но проблемы еще остались. Например, пока нет <a href="https://github.com/flutter/flutter/issues/74741">фолбэков для спецсимволов</a> или поддержки <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS">CORS</a> картинок.</li>
<li>PWA пока <a href="https://github.com/flutter/flutter/issues/75861">кэширует все ресурсы</a>, поэтому полноценная поддержка работы приложения без сети требует использования <a href="https://github.com/flutter/flutter/issues/70101">некоторых ручных манипуляций с CanvasKit</a>.</li>
<li>Рендеринг текста и поддержка различных функций, например, полноценной стилизации, остается очень сложной задачей, над которой мы продолжаем работать.</li>
<li>Экосистема плагинов — это то, над чем мы также продолжаем работать, чтобы пакеты, опубликованные Google, имели лучший паритет между мобильными устройствами и вебом.</li>
</ul>
<figure>
    <img src="https://web-standards.ru/articles/flutter-web-support/images/10.png" alt="Интерфейсы веб-приложений Simplebet.">
    <figcaption>
        <a href="https://simplebet.io/">Simplebet</a> использовала веб-поддержку Flutter для создания высокоинтерактивных встраиваемых ставок на НФЛ и НБА в рамках существующего набора мобильных приложений Fanduel.
    </figcaption>
</figure>
<h2>Начните использовать Flutter для веба</h2>
<p>С переносимостью Dart, мощью веб-платформы и гибкостью фреймворка Flutter теперь вы можете собрать приложения под iOS, Android и браузер с той же кодовой базой.</p>
<p>Для тех из вас, кто уже имеет готовое веб-приложение на Flutter, можно собрать его, используя <a href="https://github.com/flutter/flutter/wiki/Flutter-build-release-channels#stable">стабильную ветку</a>. Если вы только начинаете, почитайте <a href="http://flutter.dev/web">flutter.dev/web</a>, посмотрите наш <a href="https://flutter.dev/docs/get-started/codelab-web">пример</a> и <a href="https://youtu.be/HAstl_NkXl0">сессию на Flutter Engage</a>, посвященную веб-платформе.</p>
<p>С нетерпением ждем ваших успехов в применении Flutter для веба!</p>

                    ]]></description><pubDate>Mon, 22 Mar 2021 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/flutter-web-support/</guid></item><item><title>Удобный доступ к буферу обмена с Clipboard API</title><link>https://web-standards.ru/articles/clipboard-api/</link><description><![CDATA[
                        <p>Возможность скопировать что-то в буфер обмена существует в браузерах достаточно давно. Но синхронный запуск команд <code>copy</code> или <code>write</code> через функцию <code>document.execCommand()</code> всегда был не самым лучшим API, а сейчас считается устаревшим. Его неудобство сподвигло разработчиков к написанию множества библиотек, которые помогали пользоваться копированием с помощью более удобного интерфейса. К счастью, W3C задумался над разработкой нового, более удобного способа взаимодействия с Clipboard API, и уже в декабре 2016 года <a href="https://github.com/w3c/clipboard-apis/commit/3ffdbba8580e0096aa7d492d49e1309001d25162">был опубликован первый черновик</a> современного API. В 2021 году эта возможность уже есть во всех браузерах, хотя и с <a href="https://caniuse.com/async-clipboard">некоторыми отличиями в поддержке</a>.</p>
<h2>Как это было раньше</h2>
<p>Почему же всё-таки старая реализация была не самой удачной? Давайте рассмотрим на примере копирования произвольной строки. Для копирования мы должны вызвать <code>document.execCommand('copy')</code>. В случае команды <code>copy</code> функция <code>execCommand</code> принимает только один аргумент. Но что же будет помещено в буфер обмена? Правильный ответ — то, что в данный момент выделено на странице пользователем.</p>
<p>Но что делать, если нам нужно скопировать что-то произвольное, неужели просить пользователя это выделить?</p>
<p>Можно имитировать эту ситуацию с помощью трюка: поместить текст в поле ввода, убрать у него все стили, чтобы строка выглядела как текст, и поставить этому полю <code>readonly</code>. В этом случае мы можем ставить фокус на это поле, выделять в нем текст и вызывать команду копирования. Когда нам нужно скопировать текст, который не отображается на странице, все становится сложнее. Для того, чтобы наглядно увидеть, сколько всего нужно предусмотреть, я сделал небольшой пример.</p>
<pre><code tabindex="0" class="language-js">const isRTL = document.documentElement.getAttribute('dir') === 'rtl';
const button = document.querySelector('button');

button.addEventListener('click', (evt) =&gt; {
    const input = document.createElement('input');
    const text = 'https://web-standards.ru/articles/clipboard-api/';
    const yPosition = window.pageYOffset || document.documentElement.scrollTop;

    // Скрываем поле за краями экрана
    // в зависимости от направления
    // текста в текущей локали
    input.style.position = 'absolute';
    input.style[isRTL ? 'right' : 'left'] = '-9999px';

    // Предотвращаем срабатывание зума на iOS
    input.style.fontSize = '16px';

    // Предотвращаем скролл к элементу
    input.style.top = `${yPosition}px`;

    // Не допускаем появления
    // клавиатуры на мобильных девайсах
    input.setAttribute('readonly', '');

    // Вставляем элемент в DOM
    document.body.appendChild(input);
    input.value = text;

    // Помещаем поле в фокус
    input.focus();

    // Выделяем текст в поле
    input.select();

    // Копируем текст в поле обмена
    document.execCommand('copy');

    // Удаляем фейковый элемент
    document.body.removeChild(input);
});
</code></pre>
<p>Теперь кажется должно стать понятно, почему никто не будет скучать по <code>execCommand</code>.</p>
<h2>Почему работа с буфером обмена важна для сайтов</h2>
<p>Кажется, что такие знакомые сочетания клавиш как <kbd>Ctrl C</kbd> и <kbd>Ctrl V</kbd>, ну или <kbd>Cmd C</kbd> и <kbd>Cmd V</kbd> в macOS, знакомы каждому. Но пользователю иногда всё-таки легче кликнуть по кнопке и получить содержимое из буфера обмена. Например, если пользователь хочет скопировать ссылку, то сочетания клавиш не помогают и нужно совершить больше действий. Прямо как на этом сайте: вы можете скопировать ссылку на конкретное место в статье, обозначенное заголовком, если кликнете на иконку справа от заголовка. Попробуйте, получилось?</p>
<p>Если вы точно знаете, что пользователю нужно будет скопировать какие-то данные, путь к этому можно сократить с помощью простой кнопки.</p>
<figure>
    <img src="https://web-standards.ru/articles/clipboard-api/images/link.png" alt="Текст с заголовком, рядом с которым стоит кнопка с иконкой ссылки: два кольца цепи (links).">
    <figcaption>
        Применение Clipboard API для копирования ссылок.
    </figcaption>
</figure>
<figure>
    <img src="https://web-standards.ru/articles/clipboard-api/images/code.png" alt="Фрагмент кода в несколько строк, рядом с которым стоит кнопка с иконкой буфера обмена.">
    <figcaption>
        Удобное копирование сниппетов кода.
    </figcaption>
</figure>
<figure>
    <img src="https://web-standards.ru/articles/clipboard-api/images/token.png" alt="Длинный непрерывный фрагмент текста из случайных символов, рядом с которым стоит кнопка с иконкой буфера обмена.">
    <figcaption>
        Копирование важной информации, которая нужна пользователю.
    </figcaption>
</figure>
<h2>Возможности современных браузеров</h2>
<p>Современный API позволяет работать не только с текстом, но и с картинками, а также копировать смешанный контент или исключать какие-то элементы при попытке копирования или вставки. Например, если скопирововать контент из MS Word и вставить в WYSIWIG, то мы можем отфильтровать все ненужное и привести содержимое в порядок перед тем, как поместим его в форму для редактирования.</p>
<p>Есть несколько способов работы с Clipboard API, один из самых главных — это API для чтения и записи буфера обмена. Методы в <code>window.navigator.clipboard</code> дают прямой доступ к чтению или записи данных в буфер обмена. Также есть и другие возможности, которые мы рассмотрим дальше.</p>
<h2>Запись в буфер обмена</h2>
<p>Для сохранения данных в буфер можно использовать универсальный метод <code>window.navigator.clipboard.write()</code> или специальный <code>window.navigator.clipboard.writeText()</code>, если мы собираемся помещать в буфер только текст. Оба метода асинхронные и возвращают <code>Promise</code>.</p>
<p>Рассмотрим простой пример с копированием ссылки:</p>
<pre><code tabindex="0" class="language-html">&lt;span class=&quot;tooltip&quot;&gt;
    &lt;button class=&quot;tooltip__button&quot; data-href=&quot;#section-1&quot;&gt;&lt;/button&gt;
    &lt;span class=&quot;tooltip__label&quot; role=&quot;tooltip&quot; id=&quot;copy-section-1&quot;&gt;
        Скопировать ссылку
    &lt;/span&gt;
&lt;/span&gt;
</code></pre>
<pre><code tabindex="0" class="language-js">document.querySelector('.tooltip').addEventListener('click', () =&gt; {
    const tooltip = this.nextSibling;
    const hash = this.getAttribute('data-href');

    navigator.clipboard
        .writeText(`${window.location.href}${hash}`)
        .then(() =&gt; {
            // Успех!
        })
        .catch(() =&gt; {
            // Неудача :(
        });
});
</code></pre>
<p><a href="https://github.com/web-standards/web-standards.ru/blob/master/src/scripts/modules/copy-link.js">Полный пример кода на GitHub</a>.</p>
<p>Метод <code>window.navigator.clipboard.writeText</code> возвращает <code>Promise</code>, что позволяет обрабатывать исключения, если они возникнут. Одним из таких случаев может быть запрет на запись в буфер. Ниже мы рассмотрим, как запросить необходимые права для чтения и записи в буфер.</p>
<p>Если мы используем функцию <code>window.navigator.clipboard.write</code>, то к копируемым данным можно добавить, например, картинку:</p>
<pre><code tabindex="0" class="language-js">const blob = await fetch('picture.png').then((req) =&gt;
    req.blob();
);
const clipboardItem = new ClipboardItem({
    [blob.type]: blob
});

window.navigator.clipboard
    .write([clipboardItem])
    .then(() =&gt; console.log('Картинка скопирована!'))
    .catch((err) =&gt; console.error(err));
</code></pre>
<p>Также можно изменять данные перед записью в буфер обмена, когда пользователь пытается скопировать контент на странице, например, добавлять дополнительную информацию:</p>
<pre><code tabindex="0" class="language-js">document.addEventListener('copy', (evt) =&gt; {
    const source = `Источник: ${window.location.href}`;

    // Нужно заблокировать стандартное поведение
    evt.preventDefault();

    // И поместить дополнительные данные в Clipboard
    evt.clipboardData.setData(
        'text',
        `${document.getSelection()}\n\n${source}`
    );
});
</code></pre>
<h2>Чтение из буфера обмена</h2>
<p>По аналогии с записью, мы также можем читать данные из буфера обмена. Для этого есть аналогичные методы <code>read</code> и <code>readText</code>:</p>
<pre><code tabindex="0" class="language-js">window.navigator.clipboard
    .readText()
    .then((data) =&gt; console.log('Скопировано', data))
    .catch((err) =&gt; console.error('Не удалось скопировать', err));
</code></pre>
<p>Важная особенность чтения из буфера в том, что оно работает не напрямую. Например, в Google Chrome во время попытки прочитать данные из буфера пользователя уведомят о попытке чтения и предложат разрешить или запретить действие. А Safari, например, покажет контекстное меню с пунктом «Paste».</p>
<figure>
    <img src="https://web-standards.ru/articles/clipboard-api/images/chrome.png" alt="Попап под адресной строкой Google Chrome, который запрашивает доступ к копированию и предлагает: заблокировать или разрешить.">
    <figcaption>
        Попап запрашивает разрешение на чтение буфера обмена в Google Chrome при попытке вставки.
    </figcaption>
</figure>
<figure>
    <img src="https://web-standards.ru/articles/clipboard-api/images/safari.png" alt="Контекстное меню «Вставить» поверх кнопки «Вставить из буфера обмена» в Safari.">
    <figcaption>
        Контекстное меню в Safari при попытке вставки из буфера обмена.
    </figcaption>
</figure>
<p>Также можно запросить разрешение на чтение буфера заранее с помощью <a href="https://developer.mozilla.org/en-US/docs/Web/API/Permissions_API">Permissions API</a> — хотя стоит заметить, что не все браузеры его поддерживают.</p>
<pre><code tabindex="0" class="language-js">window.navigator.permissions.query({ name: 'clipboard-read' })
    .then((result) =&gt; {
        if (result.state == 'granted' || result.state == 'prompt') {
            // Можно записывать данные в буфер
        }
    });
</code></pre>
<p>Для запроса на запись в буфер используется аналогичная конструкция, но с аргументом <code>{ name: 'clipboard-write' }</code>.</p>
<p>Другой вариант чтения данных из буфера — реагировать на вставку данных на сайте. Такое событие можно слушать как на всём <code>document</code>, так и, например, в поле ввода <code>&lt;textarea&gt;</code>. С помощью этого метода можно перехватить и обработать событие.</p>
<pre><code tabindex="0" class="language-js">document.querySelector('textarea').addEventListener('paste', (evt) =&gt; {
    if (evt.clipboardData.files.lenght === 0) {
        return;
    }

    const files = Array.from(evt.clipboardData.files);

    evt.preventDefault();

    uploadFiles(files)
        .then(() =&gt; console.log('Загружено!'))
        .catch((err) =&gt; console.error('Произошла ошибка', err));
});
</code></pre>
<h2>Заключение</h2>
<p>Правильное использование современных API может значительно повысить удобство ваших интерфейсов. Clipboard API — один из таких случаев, когда вы можете упростить решение задач даже для самых неопытных пользователей.</p>

                    ]]></description><pubDate>Tue, 16 Mar 2021 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/clipboard-api/</guid></item><item><title>Я совсем забыл про стили для печати</title><link>https://web-standards.ru/articles/print-style-sheets/</link><description><![CDATA[
                        <p>Небольшая коллекция полезных CSS-техник и короткое напоминание, что стили для печати всё ещё актуальны.</p>
<p><a href="https://twitter.com/AaronGustafson">Аарон Густафсон</a> опубликовал <a href="https://twitter.com/AaronGustafson/status/788073583528538112">твит</a> про <a href="http://indiegogo.com">Indiegogo</a>, в котором он указал на то, что их страницы с деталями заказа невозможно использовать в печатном виде.</p>
<blockquote>
<p>Уважаемые <a href="https://twitter.com/Indiegogo">@Indiegogo</a>, пожалуйста, обратите внимание, как выглядит печатная версия вашей страницы с деталями заказа. Прямо сейчас она отстойная.
<a href="https://twitter.com/AaronGustafson/status/788073583528538112">Аарон Густафсон</a></p>
</blockquote>
<p><img src="https://web-standards.ru/articles/print-style-sheets/images/indiegogo.jpg" alt="Скриншот предпросмотра печати с огромными иконками."></p>
<p>Когда я увидел этот твит, он меня поразил: я осознал, что прошло уже много времени с тех пор, как я оптимизировал страницу для печати или хотя бы думал её проверить. Может быть, это из-за постоянного изменения размеров наших браузеров и уверенности в безупречной работе наших сайтов во всех формах и размерах, или, может быть, просто потому, что я сам редко печатаю веб-страницы. Как бы то ни было, я совершенно забыл о стилях для печати, и это плохо.</p>
<p>Оптимизация веб-страниц для печати важна, потому что мы хотим, чтобы наши сайты были максимально доступными, на любом носителе. Мы <a href="https://adactio.com/journal/11409">не должны делать предположения</a> о наших пользователях и их поведении. Люди по-прежнему печатают веб-страницы. Просто задумайтесь о статьях или постах в блогах, рецептах, контактной информации, картах или сайтах недвижимости. Кто-нибудь где-нибудь в конце концов попытается распечатать одну из ваших страниц.</p>
<blockquote>
<p>Я давно отказался от домашних принтеров, потому что они постоянно ломаются после десяти минут использования. Но не все такие как я, …
<a href="https://shop.smashingmagazine.com/products/inclusive-design-patterns">Хейдон Пикеринг (Инклюзивные дизайн-паттерны)</a></p>
</blockquote>
<p>Если вы такой же, как я, то надеюсь, этот пост освежит вашу память. Если вы ещё не оптимизировали страницы для печати с помощью CSS, советы ниже помогут вам начать.</p>
<h2>1. Подключение стилей для печати</h2>
<p>Лучший способ подключить стили для печати — объявить <code>@media</code>-выражение в вашем CSS.</p>
<pre><code tabindex="0" class="language-css">body {
    font-size: 18px;
}

@media print {
    /* стили для печати */

    body {
        font-size: 28px;
    }
}
</code></pre>
<p>Также вы можете подключать свои стили печати в HTML, но это создаст дополнительный запрос.</p>
<pre><code tabindex="0" class="language-html">&lt;link media=&quot;print&quot; href=&quot;print.css&quot;&gt;
</code></pre>
<h2>2. Тестирование</h2>
<p>Вам не нужно распечатывать страницу каждый раз, когда вы вносите небольшие изменения. В зависимости от вашего браузера вы можете экспортировать страницу как PDF, открыть предварительный просмотр печати или даже отлаживать прямо в браузере.</p>
<p><strong>Обновление от 6 ноября 2019:</strong> Вот <a href="https://css-tricks.com/can-you-view-print-stylesheets-applied-directly-in-the-browser/">подробная статья</a> Криса Койера о том, как эмулировать стили для печати в 2019 году.</p>
<p>Для отладки стилей для печати в Firefox откройте <em>Developer Toolbar</em> (<kbd>Shift F2</kbd> или Tools &gt; Web Developer &gt; Developer Toolbar) и введите <em>media emulate print</em> в поле внизу окна браузера и нажмите <kbd>Enter</kbd>. Активная вкладка будет вести себя так, как будто для неё задан медиа-тип <code>print</code>, пока вы не закроете её или не обновите страницу.</p>
<p><strong>Обновление от 20 октября 2018:</strong> начиная с Firefox 63+ этот способ не работает, так как Developer Toolbar был удалён.</p>
<p><img src="https://web-standards.ru/articles/print-style-sheets/images/firefox.png" alt="Эмуляция стилей для печати в Firefox."></p>
<p>В Chrome откройте инструменты разработчика (<kbd>Cmd Opt I</kbd> (macOS) или <kbd>Ctrl Shift I</kbd> (Windows) или View &gt; Developer &gt; Developer Tools, включите группу вкладок с консолью (<kbd>Esc</kbd>), откройте панель рендеринга, поставьте галочку возле <em>Emulate CSS Media</em> и выберите <em>Print</em>.</p>
<p><img src="https://res.cloudinary.com/dp3mem7or/image/upload/v1573101803/articles/print-styles/chrome.png" alt="Эмуляция стилей для печати в Chrome."></p>
<h2>3. Абсолютные единицы измерения</h2>
<p>Абсолютные единицы измерения не подходят для экранов, но отлично подходят для печати. В стилях для печати абсолютно безопасно и даже рекомендуется использовать такие абсолютные <a href="https://developer.mozilla.org/en/docs/Web/CSS/length">единицы измерения</a>, как <code>cm</code>, <code>mm</code>, <code>in</code>, <code>pt</code> или <code>pc</code>.</p>
<pre><code tabindex="0" class="language-css">section {
    margin-bottom: 2cm;
}
</code></pre>
<h2>4. Специфичные для страницы правила</h2>
<p>Можно определять свойства, характерные для страницы, такие как размеры, ориентация и отступы, с помощью правила <code>@page</code>. Это очень удобно, если вы хотите, чтобы у всех страниц были определенные поля.</p>
<pre><code tabindex="0" class="language-css">@media print {
    @page {
        margin: 1cm;
    }
}
</code></pre>
<p>Правило <code>@page</code> — часть спецификации <a href="https://drafts.csswg.org/css-page-3/">Paged Media Module</a>, которая предлагает всевозможные крутые вещи, например, выбор первой печатаемой страницы или пустых страниц, позиционирование элементов в углах страницы и <a href="https://www.smashingmagazine.com/2015/01/designing-for-print-with-css/">многое другое</a>. Вы даже можете применять его для <a href="https://alistapart.com/article/building-books-with-css3">создания книг</a>.</p>
<h2>5. Контроль разрывов страниц</h2>
<p>Поскольку печатные страницы в отличие от веб-страниц не бесконечны, контент в конечном итоге прерывается на одной странице и продолжается на следующей странице. У нас есть пять свойств для контроля над тем, что происходит в этом случае.</p>
<h3>Разрыв страницы перед элементом</h3>
<p>Если мы хотим, чтобы элемент всегда находился в начале страницы, мы можем форсировать разрыв страницы при помощи <code>page-break-before</code>.</p>
<pre><code tabindex="0" class="language-css">section {
    page-break-before: always;
}
</code></pre>
<p><a href="https://developer.mozilla.org/en/docs/Web/CSS/page-break-before">Свойство <code>page-break-before</code> на MDN</a>.</p>
<h3>Разрыв страницы после элемента</h3>
<p><code>page-break-after</code> позволяет нам форсировать или избегать разрывов страницы после элемента.</p>
<pre><code tabindex="0" class="language-css">h2 {
    page-break-after: always;
}
</code></pre>
<p><a href="https://developer.mozilla.org/en/docs/Web/CSS/page-break-after">Свойство <code>page-break-after</code> на MDN</a>.</p>
<h3>Разрыв страницы внутри элемента</h3>
<p>Это свойство отлично подходит, если вы хотите избежать ситуации, когда элемент разрывается на две страницы.</p>
<pre><code tabindex="0" class="language-css">ul {
    page-break-inside: avoid;
}
</code></pre>
<p><a href="https://developer.mozilla.org/en/docs/Web/CSS/page-break-inside">Свойство <code>page-break-inside</code> на MDN</a>.</p>
<h3>Вдовы и сироты</h3>
<p>Иногда вы не хотите принудительно разрывать страницу, но, по крайней мере, хотите контролировать количество строк, отображаемых на текущей или следующей странице. Например, если последняя строка абзаца не помещается на текущей странице, последние две строки этого абзаца будут напечатаны на следующей странице, даже если не поместилась только последняя. Так происходит потому, что свойство, которое этим управляет, <code>widows</code> <em>(англ. «вдова» — прим. редактора),</em> имеет значение по умолчанию <code>2</code>. Мы можем это изменить.</p>
<pre><code tabindex="0" class="language-css">p {
    widows: 4;
}
</code></pre>
<p>Если всё наоборот и на текущей странице помещается только одна строка, весь абзац будет напечатан на следующей странице. За такое поведение отвечает свойство <code>orphans</code> <em>(англ. «сирота» — прим. редактора),</em> и его значение по умолчанию также равно <code>2</code>.</p>
<pre><code tabindex="0" class="language-css">p {
    orphans: 3;
}
</code></pre>
<p>Код выше означает, что на текущей странице должно поместиться как минимум 3 строки, чтобы абзац не переехал на следующую страницу целиком.</p>
<p>Вот готовый <a href="http://codepen.io/matuzo/pen/oYvBjN">CodePen</a> с некоторыми примерами (<a href="http://s.codepen.io/matuzo/debug/oYvBjN">дебаг-версия</a> для более лёгкого тестирования).</p>
<p><a href="http://caniuse.com/#feat=css-page-break">Не все свойства и значения работают во всех браузерах</a>, вам нужно проверять ваши стили для печати в разных браузерах.</p>
<h2>6. Сброс стилей</h2>
<p>Имеет смысл сбрасывать некоторые стили, вроде <code>background-color</code>, <code>box-shadow</code> или <code>color</code> для печати.</p>
<p>Вот отрывок из <a href="https://github.com/h5bp/html5-boilerplate/blob/master/dist/css/style.css">стилей для печати HTML5 Boilerplate</a>:</p>
<pre><code tabindex="0" class="language-css">*,
*::before,
*::after {
    color: #000000 !important;
    text-shadow: none !important;
    background: #ffffff !important;
    box-shadow: none !important;
}
</code></pre>
<p>Стили для печати — одно из немногих исключений, где нормально использовать <code>!important</code> ;)</p>
<h2>7. Удаление ненужного контента</h2>
<p>Чтобы избегать бесполезной траты чернил, вам стоит прятать всякое нерелеватное, вроде презентанционных материалов, рекламы, навигации и прочего, при помощи <code>display: none</code>.</p>
<p>Возможно, вы даже захотите просто показать основной контент и спрятать всё остальное.</p>
<pre><code tabindex="0" class="language-css">body &gt; *:not(main) {
    display: none;
}
</code></pre>
<h2>8. Раскрытие ссылок</h2>
<p>Напечатанные ссылки абсолютно бесполезны, если вы не знаете, куда они ведут.</p>
<p>Довольно легко показывать ссылки рядом с их содержимым.</p>
<pre><code tabindex="0" class="language-css">a[href]::after {
    content: ' (' attr(href) ')';
}
</code></pre>
<p>Конечно, такой код также покажет относительные ссылки, абсолютные ссылки на ваш сайт, якоря и т. д. Что-то вроде этого подойдёт лучше:</p>
<pre><code tabindex="0" class="language-css">a[href^='http']:not([href*='mywebsite.com'])::after {
    content: ' (' attr(href) ')';
}
</code></pre>
<p>Выглядит <em>интересно,</em> я знаю. Эти строчки означают: покажи значение атрибута <code>href</code> рядом с каждой ссылкой, у которой есть атрибут <code>href</code>, который начинается с <code>http</code>, но не содержит <code>mywebsite.com</code> внутри.</p>
<h2>9. Раскрытие аббревиатур</h2>
<p>Аббревиатуры следует оборачивать в <code>&lt;abbr&gt;</code>, а их расшифровки следует добавлять в атрибут <code>title</code>. Имеет смысл показывать их на печатных страницах.</p>
<pre><code tabindex="0" class="language-css">abbr[title]::after {
    content: ' (' attr(title) ')';
}
</code></pre>
<h2>10. Принудительная печать фона</h2>
<p>Обычно браузеры не печатают фоновые цвета и фоновые изображения, если вы не попросите их это сделать, но иногда вы можете захотеть распечатать фон принудительно. Нестандартное <a href="https://developer.mozilla.org/de/docs/Web/CSS/-webkit-print-color-adjust">свойство <code>print-color-adjust</code></a> позволяет вам переопределить настройки по умолчанию для некоторых браузеров.</p>
<pre><code tabindex="0" class="language-css">header {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
}
</code></pre>
<h2>11. Медиа-выражения</h2>
<p>Если вы пишете свои медиавыражения так, как в примере ниже, имейте в виду, что CSS-правила в этом случае не будут применяться к стилям для печати.</p>
<pre><code tabindex="0" class="language-css">@media screen and (min-width: 48em) {
    /* только экран */
}
</code></pre>
<p>Вы спросите: «почему?» Потому что CSS-правила применяются только тогда, когда <code>min-width</code> равно <code>48em</code>, а тип носителя — <code>screen</code>. Без ключевого слова <code>screen</code> медиавыражение ограничивается только <code>min-width</code>.</p>
<pre><code tabindex="0" class="language-css">@media (min-width: 48em) {
    /* все типы носителей */
}
</code></pre>
<h2>12. Печать карт</h2>
<p>Текущие версии Firefox и Chrome могут печатать карты, а Safari, например, нет. Некоторые сервисы предоставляют <a href="http://staticmapmaker.com/">статические карты</a>, которые вы можете использовать вместо динамических.</p>
<pre><code tabindex="0" class="language-css">.map {
    width: 400px;
    height: 300px;
    background-image: url('http://maps.googleapis.com/maps/api/staticmap?center=Wien+Floridsdorf&amp;zoom=13&amp;scale=false&amp;size=400x300&amp;maptype=roadmap&amp;format=png&amp;visual_refresh=true');
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
}
</code></pre>
<h2>13. QR-коды</h2>
<p><a href="https://www.smashingmagazine.com/2013/03/tips-and-tricks-for-print-style-sheets/#print-qr-codes-for-easy-url-references">В этой статье на Smashing Magazine</a> есть несколько хороших советов. Один из них — показывать QR-код на печатных страницах, чтобы пользователям не приходилось вводить полный адрес страницы для перехода к веб-версии.</p>
<h2>Бонус: Печать неоптимизированных страниц</h2>
<p>Во время своего исследования я нашел отличный инструмент, который помогает печатать неоптимизированные страницы. С помощью <a href="https://css-tricks.github.io/The-Printliminator/">Printliminator</a> вы можете удалять элементы, просто кликая по ним.</p>
<p><a href="https://www.youtube.com/watch?v=Dt8hpqEIL1c">Демо на YouTube</a> и сам проект на <a href="https://github.com/CSS-Tricks/The-Printliminator">Github</a>.</p>
<h2>Бонус 2: Gutenberg</h2>
<p>Если вы фанат фреймворков, вам может понравиться <a href="https://github.com/BafS/Gutenberg">Gutenberg</a>, который немного упрощает подготовку страницы для печати.</p>
<h2>Бонус 3: Hartija</h2>
<p>Есть ещё один фреймворк со стилями для печати от <a href="http://www.vcarrer.com/">Владимира Каррера</a>, который называется <a href="https://github.com/vladocar/Hartija---CSS-Print-Framework">Hartija</a>.</p>
<p>Вот и всё! Вот ссылка на мой <a href="http://codepen.io/matuzo/pen/oYvBjN?editors=1100">CodePen</a> (<a href="http://s.codepen.io/matuzo/debug/oYvBjN">дебаг-версия</a>), если вы хотите увидеть некоторые из этих вещей в действии. Надеюсь, вам понравилась эта статья.</p>
<p>P.S. Спасибо <a href="https://twitter.com/eva_trostlos">Еве</a> за редактирование статьи и Марио за совет про Gutenberg.</p>
<h3>Источники</h3>
<ul>
<li><a href="https://www.youtube.com/watch?v=jF-OQ-BrIAM">CSS Tips and Tricks: Add External URLs to Print Stylesheets</a></li>
<li><a href="https://github.com/h5bp/html5-boilerplate/blob/master/dist/css/style.css#L195">Стили для печати в HTML5 Boilerplate</a></li>
<li><a href="https://www.smashingmagazine.com/2013/03/tips-and-tricks-for-print-style-sheets/">5 Powerful Tips And Tricks For Print Style Sheets</a></li>
<li><a href="http://stackoverflow.com/questions/9519556/faster-way-to-develop-and-test-print-stylesheets-avoid-print-preview-every-time">Faster way to develop and test print stylesheets (avoid print preview every time)?</a></li>
<li><a href="https://www.smashingmagazine.com/2015/01/designing-for-print-with-css/">Designing For Print With CSS</a></li>
<li><a href="https://github.com/BafS/Gutenberg">Gutenberg</a></li>
<li><a href="https://helloanselm.com/writings/unified-media-queries">Quick Trick: Responsive Print Media Queries</a></li>
</ul>

                    ]]></description><pubDate>Tue, 09 Mar 2021 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/print-style-sheets/</guid></item><item><title>Распаковка Docker, часть 3</title><link>https://web-standards.ru/articles/docker-unboxing-3/</link><description><![CDATA[
                        <p><a href="https://web-standards.ru/articles/docker-unboxing-2/">Во второй части</a> мы рассмотрели практические примеры упаковки приложений. В этой заключительной части мы рассмотрим способы использования Docker в процессе самой разработки.</p>
<h2>Навигация</h2>
<ul>
<li><a href="https://web-standards.ru/articles/docker-unboxing-3/#section-2">Код внутри контейнера</a></li>
<li><a href="https://web-standards.ru/articles/docker-unboxing-3/#section-3">Тестирование приложений в GUI</a></li>
<li><a href="https://web-standards.ru/articles/docker-unboxing-3/#section-4">Безопасность Docker</a>
<ul>
<li><a href="https://web-standards.ru/articles/docker-unboxing-3/#section-5">Безопасность вашей ОС</a></li>
<li><a href="https://web-standards.ru/articles/docker-unboxing-3/#section-6">Доступ при запуске приложения</a></li>
<li><a href="https://web-standards.ru/articles/docker-unboxing-3/#section-7">Безопасные образы</a></li>
<li><a href="https://web-standards.ru/articles/docker-unboxing-3/#section-8">Хранение секретной информации</a></li>
</ul>
</li>
<li><a href="https://web-standards.ru/articles/docker-unboxing-3/#section-9">Заключение</a></li>
<li><a href="https://web-standards.ru/articles/docker-unboxing-3/#section-10">Материалы по теме</a></li>
</ul>
<h2>Код внутри контейнера</h2>
<p>До сих пор, мы не говорили о написании кода внутри контейнера. Вы можете использовать классическую связку vim с плагинами + tmux. Вы можете подключится к контейнеру по SSH почти из любой популярной среды разработки. Но не использовать или хотя бы не попробовать использовать возможности Visual Studio Code (VS Code) будет преступлением.</p>
<p>Для того, чтобы работать с кодом красиво, можно и даже нужно использовать Visual Studio Code Remote, что позволяет подключиться к созданному контейнеру и работать с ним в терминальном режиме.</p>
<p>Это делается довольно просто. Достаточно установить расширение <a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack">Remote Development</a> и создать файл <code>.devcontainer.json</code> в папке из которой собирается ваш контейнер. Часто эта папка является папкой проекта. Можно собрать контейнер и на ходу с помощью мастера, но это трудно воспроизвести впоследствии. Например, содержимое <code>.devcontainer.json</code> может быть таким:</p>
<pre><code tabindex="0" class="language-json">{
    &quot;name&quot;: &quot;server&quot;,
    &quot;dockerComposeFile&quot;: [&quot;../docker-compose.yaml&quot;],
    &quot;service&quot;: &quot;test&quot;,
    &quot;workspaceFolder&quot;: &quot;/app&quot;
}
</code></pre>
<p>Это заставит VS Code собрать все контейнеры, указанные в файле для Docker Compose и открыть папку /app, которая находится внутри контейнера server.</p>
<p>В VS Code можно попытаться открыть папку проекта, и среда сама предложит открыть снова ее, но уже внутри контейнера. А можно сделать это вручную, нажав клавишу F1 или сочетание клавиш Ctrl + P (Cmd + P для macOS) и выбрав команду:</p>
<pre><code tabindex="0" class="language-bash">&gt; Remote-Containers: Open Workspace in Container.
</code></pre>
<p>Вы также можете указать те расширения, которые нужно поставить для данной рабочей сессии VS Code. После работы все эти расширения удаляться из редактора. Это очень удобно. Мне это позволяет держать в VS Code всего десять (!) расширений, которые будут востребованы в любом проекте, и никаких расширений для конкретного языка или фреймворка. Чистота и порядок!</p>
<p>В списке литературы вы сможете познакомится с <a href="https://github.com/igsekor/docker-seeds">репозиторием</a> конфигурационных файлов для разных языков и фреймворков, на которых я в основном и работаю. Этот репозиторий обновляется по мере необходимости.</p>
<h2>Тестирование приложений в GUI</h2>
<p>В Docker можно запускать не только консольные приложения, но и приложения с графическим интерфейсом (GUI). Единственное ограничение — они будут работать на сервере системы X Windows для рендеринга графических приложений и взаимодействия с ними. Воспользоваться можно интересным свойством приложений с графическим интерфейсом Linux. Приложения запускаются как клиенты, которые обращаются к серверу X Windows. При этом не важно, где этот сервер расположен. Он вполне может быть на удаленном хосте. А нам это и надо!</p>
<p>Если вы работаете на любом дистрибутиве ОС Linux, такой сервер у вас уже запущен. Чтобы поставить его на macOS, можете воспользоваться <a href="https://www.xquartz.org/">XQuartz</a>. На ОС Windows можно попробовать использовать <a href="https://sourceforge.net/projects/vcxsrv/">VcXsrv</a>. В первую очередь вы должны разрешить удаленное подключение к X Windows серверу и добавить переменные среды для контейнера. Универсального решения для всех ОС не существует, но я приведу флаги для команды <code>docker run</code>, учитывая ОС:</p>
<pre><code tabindex="0" class="language-bash">-e DISPLAY=docker.for.mac.host.internal:0 # macOS
-e DISPLAY=host.docker.internal:0 # Windows
--net=host -e DISPLAY=:0 # Linux
</code></pre>
<p>Цифра в конце строки обозначает номер монитора, на который будет выведен графический интерфейс приложения (0 используется для обозначения главного монитора). Для macOS и Windows вы можете воспользоваться графическим интерфейсом вашего сервера. Удаленное подключение для LInux можно разрешить с помощью команды:</p>
<pre><code tabindex="0" class="language-bash">xhosts +
</code></pre>
<p>Например, вы так можете запустить Gimp одной командой:</p>
<ul>
<li><strong>macOS:</strong> <code>docker run --rm -ti -e DISPLAY=docker.for.mac.host.internal:0 jamesnetherton/gimp</code></li>
<li><strong>Windows:</strong> <code>docker run --rm -ti -e DISPLAY=host.docker.internal:0 jamesnetherton/gimp</code></li>
<li><strong>Linux:</strong> <code>docker run --rm -ti --net=host -e DISPLAY=:0 jamesnetherton/gimp</code></li>
</ul>
<p>Для запуска Eclipse нужно в конце дописать <code>psharkey/eclipse</code> вместо <code>jamesnetherton/gimp</code>, а чтобы запустить Firefox — <code>jess/firefox</code>. В конце строки вы указываете имя нужного образа, который будет скачан с Docker Hub? Посмотрите, как был собран один из них, и вы будете точно знать, как запустить необходимое вам приложение с графическим интерфейсом.</p>
<h2>Безопасность Docker</h2>
<p>Предлагаю обсудить еще одну важную тему — безопасность. Рассмотрим четыре аспекта:</p>
<ol>
<li>Безопасность вашей ОС во время разработки;</li>
<li>Управление доступом на этапе запуска приложения в контейнере;</li>
<li>Безопасные образы;</li>
<li>Хранение секретной информации.</li>
</ol>
<h3>Безопасность вашей ОС</h3>
<p>Что мешает ОС быть в безопасности? Конечно то, что вы вынуждены устанавливать кучу разнообразного софта, в том числе и экспериментального. Это могут быть приложения с графическим интерфейсом или сугубо консольные приложения. Часто вам нужно устанавливать JVM или что-то подобное. Каждая новая программа несет в себе дырки, через которые можно что-нибудь, да сделать. Docker позволяет практически избавить вас от необходимости установки этого груза. Да и работать с коллегами будет намного проще.</p>
<h3>Доступ при запуске приложения</h3>
<p>Запуск контейнера происходит в отельном окружении, изолированном от ОС и других контейнеров. Но есть определенные трюки, которые позволят обеспечить вам еще большую изоляцию. В документации к Docker есть замечательное <a href="https://docs.docker.com/engine/security/userns-remap/">руководство</a>, которое точно стоит почитать. Перечислю самые важные советы, с моей точки зрения.</p>
<p>Во-первых, необходимо запуск приложений внутри контейнера осуществлять от имени какого-то пользователя. По умолчанию, приложение запускается от имени root. Осуществить это довольно просто, достаточно добавить параметр -u:</p>
<pre><code tabindex="0" class="language-bash">docker run -u user image
</code></pre>
<p>Вы также можете добавить эту настройку в Dockerfile:</p>
<pre><code tabindex="0" class="language-docker">FROM alpine:latest
RUN apk update &amp;&amp; apk add --no-cache git
USER 1000
</code></pre>
<p>Во-вторых, необходимо поддерживать только те возможности, которые вам нужны. Полный функционал для вашего приложения внутри контейнера зачастую совершенно не обязателен. Управление возможностями осуществляется через командную строку с помощью параметров <code>--cap-drop</code> и <code>--cap-add</code>. Лучшей практикой будет запуск контейнера с одним параметром <code>--cap-drop all</code> и разрешающими флагами для необходимых возможностей, обеспечивающих работу вашего приложения. Вы можете ознакомиться подробнее со всеми поддерживаемыми флагами в <a href="https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities">документации</a>. С помощью Docker Compose вы также можете управлять поддержкой той или иной возможности контейнера с помощью параметров <code>cap_drop</code> и <code>cap_add</code>. В документации к Linux есть <a href="https://man7.org/linux/man-pages/man7/capabilities.7.html">исчерпывающий список</a>.</p>
<p>В-третьих, необходимо управлять ресурсами вашей ОС, которые будут доступны для запуска контейнеров. Если вы не будете ограничивать эти ресурсы, Docker просто забьет всю оперативную память, что очень смахивает на DDOS атаку прямо у вас на компьютере. Необходимо также ограничивать и использование процессора. В Windows и macOS вы можете посмотреть потребление ресурсов системы с помощью Docker Desktop. Это можно сделать и через консоль командой, доступной уже на всех ОС:</p>
<pre><code tabindex="0" class="language-bash">docker stats
</code></pre>
<p>Об оптимальных настройках вы можете подробнее почитать в официальной <a href="https://docs.docker.com/config/containers/resource_constraints/">документации</a>.</p>
<h3>Безопасные образы</h3>
<p>Во время работы вы часто будете использовать образы из разных реестров. Будьте бдительны! Использовать образы необходимо с большим количеством скачиваний, высоким рейтингом при большом количестве отзывов, рекомендованные компанией (официальные образы) или группой, которой вы доверяете. Загружая неизвестный образ, вы получаете кота в мешке. В любом случае внимательно смотрите на конфигурационные файлы и базовые образы. Желательно также использовать подписанные образы. Для того чтобы уберечь себя от загрузки «неправильных» образов, необходимо выполнить команду в терминале:</p>
<pre><code tabindex="0" class="language-bash">export DOCKER_CONTENT_TRUST=1
</code></pre>
<p>Прочитать подробнее про эту систему можно <a href="https://docs.docker.com/engine/security/trust/">в документации</a>.</p>
<h3>Хранение секретной информации</h3>
<p>Чтобы учетные данные аккаунтов, сертификаты, секретные ключи доступа, имена ресурсов и любая другая конфиденциальная информация оставались в тайне от других, необходимо следовать двум правилам:</p>
<ol>
<li>Не помещайте секреты внутрь образа;</li>
<li>Не используйте для секретов переменные среды.</li>
</ol>
<p>В документации Docker есть <a href="https://docs.docker.com/engine/swarm/secrets/">специальный раздел</a>, посвящённый сохранности подобной чувствительной информации. Там предлагаются различные варианты. Но Джеф Хэйл (Jeff Hale) <a href="https://towardsdatascience.com/top-20-docker-security-tips-81c41dd06f57">советует</a> хранить секретные данные в томах (Docker volumes), создание и использование которых были описаны выше.</p>
<h2>Заключение</h2>
<p>Нам приходится устанавливать на свою любимую технику всякую дребедень, экспериментировать с новыми технологиями, прикручивать на этапе внедрения продукта то, что не должно быть в нем. Фраза «у меня все работает» по понятным причинам совершенно не устраивает заказчика. Эти неприятные моменты приводят к прокрастинации или даже эмоциональному выгоранию разработчика, который часто работает больше положенных восьми часов в день. Ночные посиделки на кануне сдачи этапа проекта скорее обыденность, чем исключение.</p>
<p>Все вышесказанное является не чем иным, как попыткой помочь узнать основы без чтения документации. Как и всегда, попытка сделать что-либо подобное обречена на провал. Однако надеюсь, что время, проведенное за чтением, не будет бесполезным. Для меня процесс разработки совершенно точно разделился на «До» и «После». Надеюсь, опыт и умозаключения, изложенные выше, вам пригодятся, позволят попробовать различные технологии самостоятельно, поэкспериментировать, и, может быть, обеспечат преимущества при устройстве на следующую работу!</p>
<p>Если вы поработаете с Docker, вас, скорее всего, от него уже не спасти. Есть надежда, что вы будете писать уже следующее приложение внутри контейнера. В заключение, вот вам ещё несколько ссылок:</p>
<ul>
<li><a href="https://habr.com/p/440660/">Команды Docker</a></li>
<li><a href="https://medium.com/hackernoon/docker-commands-the-ultimate-cheat-sheet-994ac78e2888">Справочник по командам</a></li>
<li><a href="https://github.com/igsekor/docker-seeds">Мой репозиторий наборов конфигурационных файлов для работы в VS Code</a></li>
<li><a href="https://blog.maddevs.io/docker-for-beginners-a2c9c73e7d3d">Docker для начинающего разработчика</a></li>
</ul>
<h2>Материалы по теме</h2>
<ol>
<li><a href="https://www.piter.com/collection/A20865/product/sovremennye-operatsionnye-sistemy-4-e-izd">Современные операционные системы</a></li>
<li><a href="https://medium.com/sysf/getting-started-with-docker-1-b4dc83e64389">Anatomy of Docker</a></li>
<li><a href="https://www.upguard.com/blog/docker-vs-lxc">LXC vs Docker: Why Docker is Better</a></li>
<li><a href="https://www.freecodecamp.org/news/a-beginner-friendly-introduction-to-containers-vms-and-docker-79a9e3e119b/">A Beginner-Friendly Introduction to Containers, VMs and Docker</a></li>
<li><a href="https://habr.com/p/438796/">Изучаем Docker, часть 1: основы</a></li>
<li><a href="https://habr.com/p/439978/">Изучаем Docker, часть 2: термины и концепции</a></li>
<li><a href="https://ru.wikipedia.org/wiki/%D0%A1%D1%82%D0%B0%D0%BD%D0%B4%D0%B0%D1%80%D1%82%D0%BD%D1%8B%D0%B5_%D0%BF%D0%BE%D1%82%D0%BE%D0%BA%D0%B8">Википедия: Стандартные потоки</a></li>
<li><a href="https://ru.wikipedia.org/wiki/TTY-%D0%B0%D0%B1%D1%81%D1%82%D1%80%D0%B0%D0%BA%D1%86%D0%B8%D1%8F">Википедия: TTY-абстракция</a></li>
<li><a href="https://docs.docker.com/storage/">Manage data in Docker</a></li>
<li><a href="https://habr.com/p/441574/">Изучаем Docker, часть 6: работа с данными</a></li>
<li><a href="https://docs.docker.com/engine/reference/builder/">Dockerfile reference</a></li>
<li><a href="https://habr.com/p/439980/">Изучаем Docker, часть 3: файлы Dockerfile</a></li>
<li><a href="https://github.com/docker-library/postgres/blob/master/12/Dockerfile">Postgres: Dockerfile</a></li>
<li><a href="https://www.postgresql.org/download/linux/redhat/">Linux downloads (Red Hat family)</a></li>
<li><a href="https://docs.docker.com/engine/reference/run/">Docker run reference</a></li>
<li><a href="https://www.cloudways.com/blog/best-php-cms/">Top 14 Best PHP CMS In Market For Developers in 2020</a></li>
<li><a href="https://docs.docker.com/compose/install/">Install Docker Compose</a></li>
<li><a href="https://docs.docker.com/compose/compose-file/">Compose file version 3 reference</a></li>
<li><a href="https://ru.vuejs.org/v2/cookbook/dockerize-vuejs-app.html">Интегрируем Docker в приложение Vue.js</a></li>
<li><a href="https://hub.docker.com/_/node">Официальный репозиторий Node.js от команды Docker</a></li>
<li><a href="https://habr.com/p/440658/">Изучаем Docker, часть 4: уменьшение размеров образов и ускорение их сборки</a></li>
<li><a href="https://docs.docker.com/develop/develop-images/multistage-build/">Use multi-stage builds</a></li>
<li><a href="https://dev.to/sujaykundu777/utilizing-the-power-of-docker-while-building-mern-apps-using-mern-docker-4olb">Utilizing the power of Docker while building MERN Apps using mern-docker</a></li>
<li><a href="https://www.digitalocean.com/community/tutorials/how-to-set-up-flask-with-mongodb-and-docker">How To Set Up Flask with MongoDB and Docker</a></li>
<li><a href="https://habr.com/p/440660/">Изучаем Docker, часть 5: команды</a></li>
<li><a href="https://medium.com/hackernoon/docker-commands-the-ultimate-cheat-sheet-994ac78e2888">Docker Commands — The Ultimate Cheat Sheet</a></li>
<li><a href="https://github.com/igsekor/docker-seeds">Docker Seeds</a></li>
<li><a href="https://blog.maddevs.io/docker-for-beginners-a2c9c73e7d3d">Docker для начинающего разработчика</a></li>
</ol>
<h2>Видео по теме</h2>
<ol>
<li><a href="https://youtu.be/wW9CAH9nSLs">The future of Linux Containers</a></li>
<li><a href="https://youtu.be/QF4ZF857m44">Основы Docker. Большой практический выпуск</a></li>
</ol>

                    ]]></description><pubDate>Wed, 10 Feb 2021 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/docker-unboxing-3/</guid></item><item><title>Распаковка Docker, часть 2</title><link>https://web-standards.ru/articles/docker-unboxing-2/</link><description><![CDATA[
                        <p><a href="https://web-standards.ru/articles/docker-unboxing-1/">В первой части</a> мы обсудили технологии виртуализации, основную терминологию Docker и рассмотрели базовые примеры использования. В этой части мы рассмотрим примеры использования Docker на практике.</p>
<h2>Навигация</h2>
<ul>
<li><a href="https://web-standards.ru/articles/docker-unboxing-2/#section-2">Использование Docker</a>
<ul>
<li><a href="https://web-standards.ru/articles/docker-unboxing-2/#section-3">Работа с базами данных</a></li>
<li><a href="https://web-standards.ru/articles/docker-unboxing-2/#section-4">Движок сайта на PHP</a></li>
<li><a href="https://web-standards.ru/articles/docker-unboxing-2/#section-5">Фронтенд-приложение</a></li>
<li><a href="https://web-standards.ru/articles/docker-unboxing-2/#section-6">Фулстек-приложение</a></li>
</ul>
</li>
</ul>
<h2>Использование Docker</h2>
<p>Начнем, пожалуй, с самого простого. Определим задачи, для которых Docker может понадобиться веб-программисту, разбив примеры типовых задачи на группы:</p>
<ol>
<li>Эксперименты и обучение:
<ul>
<li>Исследование системы команд конкретной ОС Linux (на примере CentOS 8);</li>
<li>Работа с базами данных (на примере PostgreSQL);</li>
<li>Движок сайта на PHP (на примере CMS Drupal).</li>
</ul>
</li>
<li>Разработка приложений:
<ul>
<li>Фронтенд приложение (на примере настройки базового репозитория для разработки на Node.js);</li>
<li>Фулстек-приложение (на примере MERN и связки Flask с MongoDB);</li>
<li>Работа с кодом внутри контейнера;</li>
<li>Тестирование приложений в графическом интерфейсе.</li>
</ul>
</li>
</ol>
<p>Необходимо помнить, что основной целью Docker является запуск приложения в воспроизводимом окружении. Запуск в контейнере означает гарантированную работу приложения на любой платформе. Но использовать Docker можно и другим способом. Для разработчика существует несколько таких способов. Они рассматриваются в первой и второй группе задач.</p>
<h3>Работа с базами данных</h3>
<p>Можно поступить по-старому: скачать образ нужной ОС и установить на ней интересующую вас СУБД. Но это противоречит философии Docker. Понятно, что подобрать готовый образ вы сможете и сами на Docker Hub. Однако, давайте создадим свой собственный образ, чтобы потренироваться.</p>
<p>Нам необходимо приготовить свой «слоеный пирог», как иногда метафорически называют образ в официальной документации. Мы уже знаем, что существует конфигурационный файл Dockerfile, в котором описываются все нужные нам слои с помощью специальных команд (инструкций). <a href="https://docs.docker.com/engine/reference/builder/">В официальной документации</a> подробно описаны все инструкции. Можно также обратиться <a href="https://habr.com/p/439980/">к руководству по Dockerfile</a>. Инструкция в Dockerfile формируется по следующему принципу:</p>
<pre><code tabindex="0" class="language-docker"># Comment
INSTRUCTION arguments
</code></pre>
<p>Строка начинается с имени инструкции, а дальше указываются значения ее аргументов. <a href="https://github.com/docker-library/postgres/blob/master/12/Dockerfile">Посмотрите на Dockerfile</a> из официального образа с установленной СУБД PostgreSQL от команды Docker. Вы увидите достаточно большой файл конфигурации (больше двух сотен строк!). Можно ли проще? Можно, если вы не претендуете на универсальность вашего решения. Давайте постепенно будем формировать файл конфигурации. В первую очередь нам необходимо определить базовый образ. Нам, например, понадобился образ CentOS 8, последней стабильной версии. В Dockerfile достаточно написать для этого всего лишь строчку:</p>
<pre><code tabindex="0" class="language-docker">FROM centos:latest
</code></pre>
<p>Давайте попробуем сформировать и запустить его? Используем команду <a href="https://docs.docker.com/engine/reference/commandline/build/">docker build</a>. Если запустить ее в каталоге с Dockerfile с точкой после слова build, Docker CLI должен собрать новый образ. Переходим в отдельный каталог, создаем файл Dockerfile, записываем в него строчку и запускаем сборку. Достаточно выполнить следующие две команды в терминале:</p>
<pre><code tabindex="0" class="language-bash">touch ./Dockerfile &amp;&amp; echo &quot;FROM centos:latest&quot; &gt; ./Dockerfile &amp;&amp; cat ./Dockerfile
docker build .
</code></pre>
<p>Если посмотрим список доступных образов, то увидим новую строчку списка:</p>
<pre><code tabindex="0" class="language-bash">docker image ls

&gt; REPOSITORY   TAG     IMAGE ID      CREATED        SIZE
&gt; &lt;none&gt;       &lt;none&gt;  183420543a45  3 months ago   215MB
&gt; centos       latest  0d120b6ccaa8  3 months ago   215MB
&gt; hello-world  latest  bf756fb1ae65  11 months ago  13.3kB
</code></pre>
<p>Давайте двигаться дальше. Сначала запустим контейнер в режиме доступа к консоли, указав в качестве имени контейнера Image ID:</p>
<pre><code tabindex="0" class="language-bash">docker run -it --entrypoint bash 183420543a45
</code></pre>
<p>Имя контейнера можно было указать при его создании, и мы это обязательно сделаем, но чуть позже.</p>
<p>Разумеется, для сборки все команды нужно попробовать сначала в терминале ОС, а только потом в Docker. Надо помнить также, что настройки ОС и установленные приложения не сохранятся и будут утеряны после завершения работы контейнера. Нам необходимо установить PostgreSQL в ОС, что мы и сделаем <a href="https://www.postgresql.org/download/linux/redhat/">в соответствии с официальной документацией</a>. Используем команды для CentOS 8, поскольку в последней версии образа устанавливается именно эта версия ОС:</p>
<pre><code tabindex="0" class="language-bash">rpm -Uvh https://download.postgresql.org/pub/repos/yum/reporpms/EL-8-x86_64/pgdg-redhat-repo-latest.noarch.rpm
dnf install -y postgresql12-server --disablerepo=AppStream
</code></pre>
<p>Кажется, все успешно. Эти команды можно превратить в инструкции для Dockerfile следующим образом:</p>
<pre><code tabindex="0" class="language-docker">RUN rpm -Uvh https://download.postgresql.org/pub/repos/yum/reporpms/EL-8-x86_64/pgdg-redhat-repo-latest.noarch.rpm; dnf install -y postgresql12-server --disablerepo=AppStream
</code></pre>
<p>При сборке контейнера рекомендуется использовать наименьшее количество слоев. Это нужно как для сборки контейнера, так и для быстродействия работы его файловой системы. Поэтому мы выполняем команды друг за другом на одном слое (в одной инструкции), перечисляя их через точку с запятой.</p>
<p>Теперь надо запустить установленную СУБД при старте уже собранного контейнера. Попробуем сделать через консоль контейнера. Для этого можно снова <a href="https://www.postgresql.org/download/linux/redhat/">воспользоваться инструкцией</a>:</p>
<pre><code tabindex="0" class="language-bash">systemctl start postgresql-12.service

&gt; System has not been booted with systemd as init system (PID 1). Can’t operate.
&gt; Failed to connect to bus: Host is down
</code></pre>
<p>Ошибка! Но почему? Мы имеем дело не с полноценной ОС, а с контейнером, который существует только как абстракция. В нашем случае проблема связана с двумя вещами: доступом к системным службам и журналу поведения этих служб базовой ОС.</p>
<p>Поскольку Docker является надстройкой над ядром ОС, доступ контейнера к службе systemd означает доступ к ней в базовой ОС, что не является безопасным действием и по умолчанию запрещено.</p>
<p>Можно запустить контейнер в специальном привилегированном режиме, но на свой страх и риск. Мы будем использовать указанный флаг, <a href="https://docs.docker.com/engine/reference/run/">учитывая документацию Docker</a>, поскольку мы это делаем исключительно в исследовательских целях, используя официальный образ ОС.</p>
<p>Давайте отвлечемся на минуту, и еще добавим красивые имена для образа и контейнера. Чтобы каждый раз не смотреть значение Image ID (оно будет меняться при каждой сборке) установим имя образа и тег:</p>
<pre><code tabindex="0" class="language-bash">docker image rm -f 183420543a45
docker build -t psql:centos .
</code></pre>
<p>Первая команда используется для того, чтобы удалить собранный на предыдущем шаге образ. Ключ <code>-f</code> мы используем, поскольку Docker без него не будет удалять образы, которые были запущены раньше. Попробуем запустить:</p>
<pre><code tabindex="0" class="language-bash">docker run -it psql:centos bash
</code></pre>
<p>Все работает. Вы можете посмотреть на список всех запущенных контейнеров в другом окне терминала, если выполните следующую команду:</p>
<pre><code tabindex="0" class="language-bash">docker ps

&gt; CONTAINER ID  IMAGE        COMMAND  CREATED        STATUS        PORTS  NAMES
&gt; f53b4f7223c2  psql:centos  &quot;bash&quot;   9 seconds ago  Up 8 seconds         cool_curie
</code></pre>
<p>Значение <code>CONTAINER ID</code> не очень говорящее. Каждый раз смотреть его не удобно. Давайте добавим ключ с указанием имени контейнера, чтобы можно было легче им управлять. Для этого остановим наш контейнер и запустим его снова с именем:</p>
<pre><code tabindex="0" class="language-bash">docker stop f53b4f7223c2
docker run -it --name psql-centos-1 psql:centos bash
</code></pre>
<p>Теперь намного лучше:</p>
<pre><code tabindex="0" class="language-bash">docker ps

&gt; CONTAINER ID  IMAGE        COMMAND  CREATED        STATUS        PORTS  NAMES
&gt; 39954a216f6b  psql:centos  &quot;bash&quot;   8 seconds ago  Up 8 seconds         psql-centos-1
</code></pre>
<p>Если вы уже запускали контейнер с определенным именем, то в будущем Docker CLI под тем же именем вам не даст его запустить, даже если он уже был остановлен. Предварительно имя нужно удалить из списка с помощью команды:</p>
<pre><code tabindex="0" class="language-bash">docker rm psql-centos-1

&gt; psql-centos-1
</code></pre>
<p>Посмотреть весь список, в котором будут отображены запущенные и остановленные контейнеры, можно с помощью команды:</p>
<pre><code tabindex="0" class="language-bash">docker ps -a
</code></pre>
<p>Теперь вернемся к нашей задаче. Для запуска менеджера служб systemd необходимо запустить сначала специальную службу:</p>
<pre><code tabindex="0" class="language-bash">docker run -it --privileged --name psql-centos-1 psql:centos /sbin/init
</code></pre>
<p>В другом окне терминала вы можете обратится к уже запущенному контейнеру и попасть в него с помощью команды:</p>
<pre><code tabindex="0" class="language-bash">docker exec -it psql-centos-1 /bin/bash
</code></pre>
<p>Теперь можно управлять запуском служб. Выполнения команды <code>exit</code> для остановки контейнера вам теперь будет не достаточно. Чтобы остановить контейнер, вам нужно выполнить команду:</p>
<pre><code tabindex="0" class="language-bash">docker stop psql-centos-1
</code></pre>
<p>Попробуем запустить нашу службу сейчас:</p>
<pre><code tabindex="0" class="language-bash">systemctl enable postgresql-12.service # Успешно
systemctl start postgresql-12.service

&gt; Job for postgresql-12.service failed because the control process exited with error code.
&gt; See &quot;systemctl status postgresql-12.service&quot; and &quot;journalctl -xe&quot; for details.
</code></pre>
<p>Снова неудача. В этот раз нет принципиальных ошибок, чтобы запустить нашу СУБД, осталось только настроить язык (local). Для этого нужно подправить Dockerfile следующим образом:</p>
<pre><code tabindex="0" class="language-docker">FROM centos:latest
RUN rpm -Uvh https://download.postgresql.org/pub/repos/yum/reporpms/EL-8-x86_64/pgdg-redhat-repo-latest.noarch.rpm &amp;&amp; dnf install -y postgresql12-server --disablerepo=AppStream; dnf install -y glibc-locale-source glibc-langpack-en; dnf clean all
RUN localedef -i en_US -f UTF-8 en_US.UTF-8
</code></pre>
<p>Теперь нам нужно инициализировать СУБД и запустить ее как службу. Хотя с помощью Dockerfile сделать это можно, мы для простоты будем делать это вручную в терминале. Если выполнить команду внутри контейнера, то все получится:</p>
<pre><code tabindex="0" class="language-bash">/usr/pgsql-12/bin/postgresql-12-setup initdb &amp;&amp; systemctl start postgresql-12 &amp;&amp; su postgres
</code></pre>
<p>Вы сможете теперь работать с СУБД, если наберете команду:</p>
<pre><code tabindex="0" class="language-bash">psql
</code></pre>
<p>Если добавить команду запуска сервера СУБД в качестве инструкции типа RUN в Dockerfile, то вы получите ошибку при сборке образа на этапе инициализации СУБД. Есть пути решения этой проблемы, но говорить о них в рамках данной статьи будет несколько преждевременно.</p>
<p>Не забывайте, что в рабочих задачах установка привилегированного доступа к службе <code>systemd</code> базовой ОС очень опасна! Если ваша задача является широко распространенной, то вы должны озаботиться поиском готового решения.</p>
<h3>Движок сайта на PHP</h3>
<p>Мы уже научились создавать базовые конфигурации для контейнеров и использовать контейнеры в двух режимах. Давайте теперь поработаем со стандартной задачей для веб-программиста или контент-менеджера — создание сайта на популярном PHP-движке. <a href="https://www.cloudways.com/blog/best-php-cms/">Тройка наиболее популярных движков в мире на лето 2020 года</a>: Wordpress, Magento, Drupal. Наиболее универсальным, мощным и относительно безопасным движком с хорошим тулингом (системой инструментов для разработчиков) является Drupal, он близок моему сердцу. Остановимся на нем, хотя чаще всего задача стоит в том, чтобы поставить и использовать Wordpress (Вы легко это сможете сделать в качестве упражнения по аналогии).</p>
<p>Попробуем найти подходящий образ на Docker Hub. Вы, скорее всего, наткнетесь <a href="https://hub.docker.com/_/drupal">на официальный репозиторий</a>. Отмечу только, что достаточно хорошими образами для Wordpress и Magento, на мой взгляд, являются соответственно <a href="https://hub.docker.com/_/wordpress">wordpress</a> и <a href="https://hub.docker.com/r/magento/magento-cloud-docker-php">magento-cloud-docker-php</a>.</p>
<p>В образе, который вы сможете загрузить к себе и запустить будет установлена определенная версия Drupal (ее можно выбрать с помощью тегов), но не будет установлена СУБД, без которой ничего не заработает. С точки зрения философии Docker запускать контейнер с СУБД нужно отдельно, а потом сопрягать его с контейнером, в котором установлен Drupal.</p>
<p>Давайте, в рамках поставленной задачи, познакомимся с Docker Compose. Если вы устанавливали Docker Desktop, то Docker Compose у вас уже установлен, беспокоиться не о чем. Если вы счастливый обладатель ОС Linux, то Docker Compose необходимо будет установить. Для этого вам необходимо перейти на вкладку Linux <a href="https://docs.docker.com/compose/install/">на странице документации</a>.</p>
<p>Что такое Docker Compose? Это инструмент, который позволяет управлять списком связанных контейнеров путем установки параметров запуска. Все управление осуществляется в специальном файле конфигурации в формате YAML, который в последнее время очень часто используется. Файл имеет не только заданный формат, но и имя — «docker-compose.yml».</p>
<p>Нам необходимо определенным образом сконфигурировать и запустить два образа Docker совместно: в одном будет находиться CMS Drupal, а в другом — СУБД MySQL. Если перейти на страницу образа drupal, то вы сможете в описании увидеть, какой именно репозиторий рекомендуется использовать для работы Drupal с СУБД MySQL:</p>
<pre><code tabindex="0" class="language-bash">docker run -d --name some-mysql --network some-network \
    -e MYSQL_DATABASE=drupal \
    -e MYSQL_USER=user \
    -e MYSQL_PASSWORD=password \
    -e MYSQL_ROOT_PASSWORD=password \
mysql:5.7
</code></pre>
<p>У нас нет оснований использовать что-то другое, поэтому доверимся разработчикам репозитория. Для облегчения работы с несколькими контейнерами и был придумал Docker Compose.</p>
<p>Не буду уделять много внимания описанию инструмента и системы его команд. Давайте посмотрим на готовый конфигурационный файл и попробуем разобраться с тем, как он устроен. Ниже на странице образа drupal есть готовый YAML файл.</p>
<p>Файл конфигурации устроен следующим образом. Сначала описывается требуемая для сборки контейнеров версия Docker Compose. Затем перечисляются те сервисы, которые вы планируете запускать. Под сервисами подразумеваются приложения, для каждого из которых создаётся отдельный контейнер. Для сервисов вы можете указать настройки, с которыми контейнер должен запускаться. Все тонкости можно найти <a href="https://docs.docker.com/compose/compose-file/">в документации</a>. Приведу конфигурационный файл для Drupal последней версии, работающего на базе Apache и MySQL. Благодаря формату, конфигурация довольно проста для понимания:</p>
<pre><code tabindex="0" class="language-yaml">version: '3.8'
services:
    drupal:
    image: drupal:latest
    ports:
        - 8080:80
    volumes:
        - /var/www/html/modules
        - /var/www/html/profiles
        - /var/www/html/themes
        - /var/www/html/sites
    restart: always
    mysql:
    image: mysql:5.7
    environment:
        MYSQL_DATABASE: drupal
        MYSQL_USER: user
        MYSQL_PASSWORD: passwod
        MYSQL_ROOT_PASSWORD: password
    restart: always
</code></pre>
<p>Если файл docker-compose.yml уже создан и вы находитесь в этой же папке, для запуска контейнеров достаточно выполнить команду:</p>
<pre><code tabindex="0" class="language-bash">docker-compose up -d
</code></pre>
<p>Ключ -d нужен для установки режима работы контейнеров в фоновом режиме. Если вы теперь перейдете на ссылке <code>http://localhost:8080</code> в браузере базовой ОС, вы сможете работать с сайтом. При настройке Drupal не забудьте, что СУБД работает на хосте mysql (имя сервиса в docker-compose.yaml). После остановки контейнеров все данные сайта потеряются, если предварительно не создать тома для хранения данных. Приведу также две важные команды для работы с Docker Compose:</p>
<ul>
<li>Просмотр списка запущенных контейнеров: <code>docker-compose ps</code></li>
<li>Остановка всех контейнеров, которые были подняты: <code>docker-compose down</code></li>
</ul>
<p>Вы только что запустили рабочий сайт на последней версии Drupal. Перейдем теперь к следующей группе задач.</p>
<h3>Фронтенд-приложение</h3>
<p>Как мы можем использовать Docker для работы с фреймворками Angular, React, Vue и подобными? Чтобы ответить на этот вопрос, нужно разделить этапы разработки и внедрения. На первом этапе с помощью Docker вы сможете обеспечить комфорт для себя (DX, developer experience), на втором — для администраторов или специалистов в области CI/CD.</p>
<p>Выполнить одну команду поднятия сервера гораздо проще, чем обеспечить поддержку необходимых библиотек и следить за их версиями, которые используются в коде. Сборка и развертывание проекта станет простым делом и для всех участников этого непростого процесса.</p>
<p>Давайте потренируемся в создании репозитория образов для фронтенда, заодно научимся работать с Docker Hub не только как потребители. Для желающих есть также <a href="https://ru.vuejs.org/v2/cookbook/dockerize-vuejs-app.html">хорошее руководство</a>.</p>
<p>Для этого вам необходимо <a href="https://hub.docker.com/signup">зарегистрироваться</a> на платформе Docker Hub. После регистрации вы сможете <a href="https://hub.docker.com/repository/create">создать свой репозиторий</a>.</p>
<p>Репозиторий может быть либо публичным, либо частным (в базовом бесплатном пакете услуг допускается только один частный репозиторий). Кстати, вы также можете найти и другие реестры от крупных компаний:</p>
<ul>
<li><a href="https://cloud.google.com/container-registry/">Google Container Registry</a></li>
<li><a href="https://aws.amazon.com/ru/ecr/">Amazon Elastic Container Registry</a></li>
<li><a href="https://cloud.yandex.ru/services/container-registry">Yandex Container Registry</a></li>
<li><a href="https://www.digitalocean.com/products/container-registry/">DigitalOcean Container Registry</a></li>
<li><a href="https://www.ibm.com/cloud/container-registry">IBM Cloud Container Registry</a></li>
</ul>
<p>Я подготовил <a href="https://hub.docker.com/repository/docker/igsekor/node">простой репозиторий в реестре</a> для разработки на Node.js. Я также связал этот репозиторий <a href="https://github.com/igsekor/docker-node">с репозиторием на GitHub</a>, чтобы процесс сборки новых образов происходил в автоматическом режиме после отправки очередного коммита.</p>
<p>Платформа Docker Hub позволяет использовать триггеры двух типов, которые служат основанием для начала сборки новой версии образа: коммиты с заданными тегами и коммиты в заранее определенных ветках. Я выбрал для простоты понимания второй вариант — на основе веток. Отправка коммита в определенной ветке означает автоматическую сборку образа в репозитории. Настраиваются связи между GitHub и Docker Hub на вкладке Builds репозитория на Docker Hub.</p>
<p>Чтобы загрузка и установка образов не занимали много времени, а сами образы не занимали много места на диске, в качестве базовой ОС на этот раз мы будем использовать Linux Alpine — довольно популярное решение. Опишу процесс создания четырех образов. В дальнейшем я буду развивать репозиторий, поэтому вы можете увидеть уже больше образов в нем.</p>
<p>Первый образ будет базовым, в нем будет только Node.js и пакетный менеджер npm. Второй образ будет являться базовым проектом с системой сборки Webpack, третий будет содержать vue-cli для разработки сайтов на VueJS, а четвертый позволит собирать любые проекты на Node.js и запускать их в изолированном окружении на веб-сервере nginx. В этот раз я не буду проводить вас по всей процедуре. Думаю, что достаточно будет только привести соответствующий образу Dockerfile.</p>
<p>Первый образ будет содержать только <a href="https://hub.docker.com/_/node">базовый образ Node.js</a>. В дальнейшем вы сможете его приспособить под свои задачи, например, добавить утилиты для тестирования веб-приложений, системы мониторинга и тому подобные инструменты. Итак, вот самый базовый Dockerfile:</p>
<pre><code tabindex="0" class="language-docker">FROM node:lts-alpine
</code></pre>
<p>Одной инструкции вполне достаточно для поставленной задачи. Теперь попробуем решить более сложную задачу — установить базовый проект Node.js. Создаем новую ветку в репозитории, создаем новый проект Node.js и собираем его:</p>
<pre><code tabindex="0" class="language-bash">git branch webpack
git checkout webpack
npm init
npm install
vi Dockerfile
</code></pre>
<p>После того, как мы добавили webpack, Dockerfile будет содержать дополнительные инструкции:</p>
<pre><code tabindex="0" class="language-docker">FROM node:lts-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install; npm install webpack webpack-cli --save-dev
</code></pre>
<p>Пробуем собрать образ. Если все успешно, то добавляем нужные файлы, делаем коммит и отправляем на GitHub (новый образ соберется в репозитории на Docker Hub автоматически, если вы все предварительно настроили):</p>
<pre><code tabindex="0" class="language-bash">docker build .
git add .
git commit -m 'Add basic image for webpack project'
git push origin webpack
</code></pre>
<p>Перейдем к следующей задаче:</p>
<pre><code tabindex="0" class="language-bash">git branch vue
git checkout vue
</code></pre>
<p>Чтобы создать проект на Vue, можно было бы просто добавить его в package.json. Но мы пойдем другим путем, уберем webpack и заменим его утилитой vue-cli, удалив предварительно все лишние файлы:</p>
<pre><code tabindex="0" class="language-bash">rm *.json
vue create .
rm -r node_modules
git add .
git commit -m 'Install Vue 3'
git push origin vue
</code></pre>
<p>Необходимо также поправить Dockerfile:</p>
<pre><code tabindex="0" class="language-docker">FROM node:lts-alpine
WORKDIR /app
COPY package*.json ./
COPY babel.config.js ./
COPY README.md ./
COPY src/ ./src
COPY public/ ./public
</code></pre>
<p>Мы сделали не оптимальный Dockerfile, но зато очень понятный. Для повышения оптимальности можно скопировать все содержимое папки в папку проекта внутри контейнера одной строчкой <code>COPY . .</code>, но тогда вам нужно будет создать файл.dockerignore с единственной строкой <code>node_modules</code> (по аналогии с. gitignore). Если у вас уже настроено правило на Docker Hub, то после коммита и отправки на GitHub все опять автоматически соберется.</p>
<p>Перейдем к последнему образу, который будет отвечать за сборку готового сайта на Vue и запуск на веб-сервере nginx. Предлагаю использовать веб-сервер nginx для статических файлов готовой сборки. Снова создаем соответствующую ветку в репозитории и, перейдя к ней, редактируем Dockerfile:</p>
<pre><code tabindex="0" class="language-bash">git branch vue-prod
git checkout vue-prod
vi Dockerfile
</code></pre>
<p>Я возьму <a href="https://ru.vuejs.org/v2/cookbook/dockerize-vuejs-app.html">готовый Dockerfile из руководства Vue</a>, в нем все более или менее удачно с точки зрения универсального решения:</p>
<pre><code tabindex="0" class="language-docker"># build stage
FROM node:lts-alpine as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# production-stage
FROM nginx:stable-alpine as production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html
EXPOSE 80
CMD [&quot;nginx&quot;, &quot;-g&quot;, &quot;daemon off;&quot;]
</code></pre>
<p>Здесь происходит пока что-то непонятное, поскольку используется механизм многоступенчатых сборок Docker, которые доступны с версии 17.05. Если вы хотите разобраться с этим механизмом подробнее, то можно воспользоваться <a href="https://habr.com/p/440658/">статьей</a> или <a href="https://docs.docker.com/develop/develop-images/multistage-build/">официальной документацией</a>.</p>
<p>Многоступенчатая сборка используется тогда, когда мы хотим часть файлов из одной сборки скопировать в другую сборку, что сильно уменьшает размер конечного образа. На нашем примере мы собираем сайт с помощью Node.js, а потом копируем его целиком в другую сборку, где работает nginx. Мы получаем готовое веб-приложение, которое можно смело использовать в рамках реальной разработки.</p>
<p>Пробуем собрать образ, и, если все успешно, заливаем его на GitHub:</p>
<pre><code tabindex="0" class="language-bash">git add .
git commit -m 'Add production building for VueJS app'
git push origin vue-prod
</code></pre>
<h3>Фулстек-приложение</h3>
<p>Приведу пару полезных примеров для фулстек-приложений, в которых используется несколько контейнеров, работающих совместно. Сначала посмотрим работу с MERN-приложением (MongoDB, Express, React, Node), которое у вас, предположим, уже написано — <a href="https://dev.to/sujaykundu777/utilizing-the-power-of-docker-while-building-mern-apps-using-mern-docker-4olb">подробнее об этом</a>.</p>
<p>Схема работы приложения описана и проиллюстрирована в том же репозитории. Предлагаю пройтись по стандартным конфигурационным файлам.env, Dockerfile, docker-compose.yml, которые нужно будет добавить в проект:</p>
<p>.env:</p>
<pre><code tabindex="0" class="language-bash">MONGO_HOSTNAME=mongo
MONGO_DB=myapp_db
MONGO_PORT=27017
</code></pre>
<p>Dockerfile:</p>
<pre><code tabindex="0" class="language-docker"># Dockerfile for Node Express Backend api (development)

FROM node:lts-alpine

# ARG NODE_ENV=development

# Create App Directory
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

# Install Dependencies
COPY package*.json ./

RUN npm ci

# Copy app source code
COPY . .

# Exports
EXPOSE 8080

CMD [&quot;npm&quot;, &quot;start&quot;]
</code></pre>
<p>docker-compose.yml:</p>
<pre><code tabindex="0" class="language-yaml">version: '3.7'

services:
    webapp-server:
        build:
            context: .
            dockerfile: Dockerfile
        image: myapp-server-img
        container_name: myapp-node-express
        volumes:
            - .:/usr/src/app
            - /usr/src/app/node_modules
        ports:
            - &quot;8080:8080&quot;
        depends_on:
            - mongo
        env_file: .env
        environment:
            - MONGO_HOSTNAME=$MONGO_HOSTNAME
            - MONGO_PORT=$MONGO_PORT
            - MONGO_DB=$MONGO_DB
    mongo:
        image: mongo
        container_name: myapp-mongodb
        ports:
            - &quot;27017:27017&quot;
</code></pre>
<p>В docker-compose.yml используется несколько другой подход с подключением отдельного файла для описания контейнера и загрузки переменных среды из файла.env. Такое решение, как вы наверняка знаете, довольно распространено. В репозиторий не попадают пароли, ключи и прочие секретные данные.</p>
<p>В Dockerfile описан процесс копирования файлов проекта внутрь контейнера и запуск приложения в режиме разработки на порте 8080. Режим выбран не случайно, поскольку эта сборка предназначена для разработки. При этом на ваш компьютер не нужно будет устанавливать ничего кроме Docker. Это очень полезно как для вас, так и для вашей ОС.</p>
<p>Другим распространенным примером является использование Flask в качестве бэкенда, отдельного веб-сервера для производительности и MongoDB в качестве СУБД. Такой пример <a href="https://www.digitalocean.com/community/tutorials/how-to-set-up-flask-with-mongodb-and-docker">приведен в руководстве</a>. Вот файл docker-compose.yml из него:</p>
<pre><code tabindex="0" class="language-yaml">version: '3'
services:

    flask:
        build:
            context: app
            dockerfile: Dockerfile
        container_name: flask
        image: digitalocean.com/flask-python:3.6
        restart: unless-stopped
        environment:
            APP_ENV: &quot;prod&quot;
            APP_DEBUG: &quot;False&quot;
            APP_PORT: 5000
            MONGODB_DATABASE: flaskdb
            MONGODB_USERNAME: flaskuser
            MONGODB_PASSWORD: your_mongodb_password
            MONGODB_HOSTNAME: mongodb
        volumes:
            - appdata:/var/www
        depends_on:
            - mongodb
        networks:
            - frontend
            - backend

    mongodb:
        image: mongo:4.0.8
        container_name: mongodb
        restart: unless-stopped
        command: mongod --auth
        environment:
            MONGO_INITDB_ROOT_USERNAME: mongodbuser
            MONGO_INITDB_ROOT_PASSWORD: your_mongodb_root_password
            MONGO_INITDB_DATABASE: flaskdb
            MONGODB_DATA_DIR: /data/db
            MONDODB_LOG_DIR: /dev/null
        volumes:
            - mongodbdata:/data/db
        networks:
            - backend

    webserver:
        build:
            context: nginx
            dockerfile: Dockerfile
        image: digitalocean.com/webserver:latest
        container_name: webserver
        restart: unless-stopped
        environment:
            APP_ENV: &quot;prod&quot;
            APP_NAME: &quot;webserver&quot;
            APP_DEBUG: &quot;true&quot;
            SERVICE_NAME: &quot;webserver&quot;
        ports:
            - &quot;80:80&quot;
            - &quot;443:443&quot;
        volumes:
            - nginxdata:/var/log/nginx
        depends_on:
            - flask
        networks:
            - frontend

networks:
    frontend:
        driver: bridge
    backend:
        driver: bridge

volumes:
    mongodbdata:
        driver: local
    appdata:
        driver: local
    nginxdata:
        driver: local
</code></pre>
<p>Автор предлагает создать три контейнера: для веб-сервера, для СУБД и для Flask. Обратите внимание на создание внутренней сети. Думаю, вы вполне разберетесь с конфигурацией.</p>
<hr>
<p>Мы можем использовать Docker и для организации собственных рабочих процессов, <a href="https://web-standards.ru/articles/docker-unboxing-3/">поговорим об этом в третьей части</a>.</p>

                    ]]></description><pubDate>Tue, 09 Feb 2021 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/docker-unboxing-2/</guid></item><item><title>Распаковка Docker, часть 1</title><link>https://web-standards.ru/articles/docker-unboxing-1/</link><description><![CDATA[
                        <p>Труд наш в основном творческий, интеллектуальный, и мы посвящаем довольно много времени размышлениям о том, что и как делаем. В условиях быстро развивающейся платформы веб-разработчикам приходится принимать сложные, часто компромиссные, решения. Хороший программист всегда стремится к совершенству, и такое положение вещей раздражает. Однако мы можем существенно облегчить свою участь с помощью Docker, который в худшем случае сократит время работы перед релизом, а в лучшем — добавит оснований нам быть счастливыми.</p>
<p>Есть и другая сторона. Какое впечатление создает ваш продукт у заказчика и его клиентов? В Docker вы создаете упаковку, создаете, если хотите, свой «дизайнерский язык», уникальный первичный пользовательский опыт. Отношение к вам часто формируется через него. Представьте продукт в физическом, а не в виртуальном мире. Пользователь снимает пленку, открывает крышку коробки, внимательно изучает макулатуру, достает провода и зарядку, вдыхает неповторимый аромат новой техники… Скачивая программу, пользователь ожидает нечто подобное. Восхищается ли он простотой, удобством и лаконичностью?</p>
<p>Материал, в начале которого вы находитесь, накладывает определенные требования и к упаковке самого себя. Из соображений удобства весь текст разделен на три части:</p>
<ol>
<li><strong>Первая часть</strong> описывает терминологию и основные инструменты Docker — <em>вы находитесь здесь.</em></li>
<li><a href="https://web-standards.ru/articles/docker-unboxing-2/">Вторая часть</a> посвящена практическим примерам, которые могут быть полезны веб-разработчикам.</li>
<li><a href="https://web-standards.ru/articles/docker-unboxing-3/">Третья часть</a> содержит интересности для организации процесса разработки.</li>
</ol>
<h2>Навигация</h2>
<ul>
<li><a href="https://web-standards.ru/articles/docker-unboxing-1/#section-2">Технология виртуализации</a></li>
<li><a href="https://web-standards.ru/articles/docker-unboxing-1/#section-3">Основные понятия Docker</a>
<ul>
<li><a href="https://web-standards.ru/articles/docker-unboxing-1/#section-4">Docker container (контейнер)</a></li>
<li><a href="https://web-standards.ru/articles/docker-unboxing-1/#section-5">Docker image (образ)</a></li>
<li><a href="https://web-standards.ru/articles/docker-unboxing-1/#section-6">Union File Systems</a></li>
</ul>
</li>
<li><a href="https://web-standards.ru/articles/docker-unboxing-1/#section-7">Cистема команд Linux</a></li>
</ul>
<h2>Технология виртуализации</h2>
<p>В 7 главе <a href="https://www.piter.com/collection/A20865/product/sovremennye-operatsionnye-sistemy-4-e-izd">знаменитой книги Эндрю Таненбаума</a>, ставшей хрестоматией для современного поколения инженеров в области Computer Science, понятие виртуализации рассматривается достаточно подробно. Вся концепция сводится к существованию менеджера виртуальных машин (Virtual Machine Manager), или, по-другому, гипервизора (hypervisor), который позволяет запускать ОС так, как будто она существует сама по себе и запущена на отдельном компьютере. Гипервизоры бывают двух типов: функционирующие на уровне «железа» и программные, работающие уже как слой в базовой ОС (host OS).</p>
<p>Применение виртуализации связано как с развитием самой вычислительной техники и программ, так и с построением сложных инфраструктурных решений для обеспечения работы компаний разного уровня. Выделенный сервер у хостинг-провайдера, к примеру, будет запускаться на «железном» гипервизоре, а у вас, скорее всего — на программном типа VMWare или VirtualBox.</p>
<p>Эти технологии не новые и существуют уже более 50 лет. Однако после небольшого периода забвения они получили в последнее десятилетие новую жизнь. Почему мы говорим про виртуализацию в случае Docker?</p>
<p>На ОС, которые не относятся к семейству Linux, служба Docker запускается как отдельная, хоть и очень быстрая, виртуальная машина. Потери ресурсов ОС, хоть и небольших, не избежать. Об этом лучше знать заранее. Подробнее об архитектуре Docker можно почитать в <a href="https://medium.com/sysf/getting-started-with-docker-1-b4dc83e64389">Anatomy of Docker</a>, мы же рассмотрим лишь основные моменты.</p>
<h2>Основные понятия Docker</h2>
<p>В основе Docker лежит понятие контейнера, которое появилось в Linux довольно давно. <a href="https://linuxcontainers.org/">Linux containers</a> (LXC) в основном использовались и продолжают использоваться для разделения прав пользователей и создания изолированных окружений для разработки, сборки и запуска программ. Простого разграничения прав пользователей не хватало, поскольку требовались наборы разного софта с иногда противоположными значениями в настройках для того или иного случая. Все это существенно приводило к нестабильности базовой ОС, дырам в безопасности и багам в программах.</p>
<p>Технология LXC позволяла использовать единое ядро ОС и поверх запускать изолированные экземпляры набора служб со своими настройками, пользователями, подключенными (доступными) внешними устройствами (в понимании архитектуры фон Неймана), с собственным пространством процессов и собственным сетевым стеком. Понятное дело, что все это вынесли в отдельную абстракцию, которая стала называться контейнером.</p>
<p>LXC сейчас применяются, например, у хостинг-провайдеров, позволяя клиентам арендовать часть ресурсов какого-то сервера для размещения своего сайта (услуга виртуального хостинга). При этом на одной физической или виртуальной машине могут работать одновременно очень много контейнеров.</p>
<p>Какие шаги нужно выполнить для того, чтобы создать и запустить контейнер (независимое окружение, «песочницу») с точки зрения администратора? Вот примерный список действий:</p>
<ol>
<li>Сгенерировать LXC;</li>
<li>Выделить специальную файловую систему;</li>
<li>Примонтировать новый слой ОС;</li>
<li>Создать виртуальный интерфейс для работы с сетью;</li>
<li>Настроить таблицы маршрутизации;</li>
<li>Настроить туннелирование сетевых пакетов через протокол NAT;</li>
<li>Запустить нужный процесс и перенаправить вывод на вывод родительской ОС.</li>
</ol>
<p>Реализация каждого из семи шагов занимает довольно много времени. Вы же понимаете, что с первого раза редко удаётся что-то хорошо настроить для большого количества пользователей? Словом, настройка и поддержание такой системы в рабочем состоянии — не простая задача.</p>
<p>В день рождения Docker, 21 марта 2013 года, Соломон Хайкс (Solomon Hykes) довольно изящно <a href="https://www.youtube.com/watch?v=wW9CAH9nSLs">представил</a> совершенно новое решение, достоинства которого легко продемонстрировать в терминале. Он загрузил из Интернета вариант сборки контейнера, собрал, запустил внутри контейнера приложение и получил его результаты единственной командой:</p>
<pre><code tabindex="0" class="language-bash">docker run -dp 80:80 docker/getting-started
</code></pre>
<p>Естественно, перед тем, как использовать команду docker, необходимо установить кое-какие специальные службы (пара-тройка команд, которые выполняются один раз при настройке ОС сервера). А сейчас уже существуют готовые сборки серверов с Docker, когда ничего вообще не надо настраивать. К тому же, Docker теперь отлично работает на всех популярных и на некоторых не очень популярных ОС, обеспечивая полную совместимость. Программисты могут свободно разрабатывать программы на базе своей ОС и быть уверенными, что все запустится с первого раза на любом компьютере, на любом сервере, где установлены службы Docker.</p>
<p>Таким образом, написав программу один раз и поместив ее внутрь контейнера, можно запускать ее на большинстве ОС. Я описал лишь основной сценарий использования Docker. Для программиста существует еще несколько сценариев, которые мы разберем по ходу. Однако сейчас немного остановимся на терминологии.</p>
<h3>Docker container (контейнер)</h3>
<p>В статье <a href="https://www.upguard.com/blog/docker-vs-lxc">LXC vs Docker: Why Docker is Better</a> приводится сравнение возможностей технологий Docker и LXC, которое оказывается явно не в пользу последней. В LXC под контейнером подразумевается изолированное окружение пользователя, которое формируется за счет выставления прав (ограничения доступа) для пользователя. Используется установленная на компьютере ОС, но для программ и пользователей контейнер оказывается полностью изолированным. Складывается ощущение, что он совершенно независим, но это не так. Например, LXC-контейнер не может использовать ОС, отличную от базовой. Технология LXC решает задачу изоляции окружения, для которой и была создана, но иногда нужно немного больше.</p>
<p>Под контейнером в Docker (Docker container) подразумевается эмуляция ОС на основе специальной службы Docker daemon, которая устанавливается на родительскую ОС и является сервером. Она использует не только механизм LXC, но и включает cgroups, прослойку для коммуникации со службами ОС и ядро Linux.</p>
<p>Docker daemon осуществляет управление объектами (Docker objects) и позволяет установить и запустить на Linux, macOS или Windows виртуальную копию окружения большого списка разных дистрибутивов Linux. На Linux используется ядро базовой ОС. На macOS и Windows — ядро оптимизированной виртуальной машины с ОС Linux.</p>
<p>С точки зрения пользователя контейнер становится аналогом отдельной ОС, на которой установлено нужное окружение и запускаемое приложение. С точки зрения родительской ОС, запуская контейнер, вы запускаете просто отдельное приложение.</p>
<p>Получается, что ОС работает в обычном режиме, а пользователь получает все преимущества отдельной виртуальной машины и ОС контейнера. При этом ресурсы компьютера на обеспечение механизма работы Docker практически не тратятся и идут на обеспечение работы целевого приложения.</p>
<h3>Docker engine (движок)</h3>
<p><a href="https://docs.docker.com/get-started/overview/#docker-engine">Движок Docker</a> состоит из Docker daemon, выступающего в роли сервера, прослойки REST API и клиента (Docker CLI), на котором осуществляется управление образами (Docker image) и томами (Docker volumes), управление жизненным циклом контейнеров, обеспечивается эмуляция локальной сети для связи запущенных контейнеров друг с другом.</p>
<h3>Docker image (образ)</h3>
<p>Это образ (прототип) контейнера, который в конечном итоге будет запущен с помощью Docker CLI. Образ представляет собой конфигурационный файл, который содержит набор инструкций для сборки контейнера. Каждую новую инструкцию можно сравнить с новым слоем. Например, на первом слое описывается, какой должен использоваться дистрибутив ОС, на втором слое устанавливаются какие-нибудь специфические для контейнера службы или сервисы, а на последнем слое собирается или просто устанавливается целевое приложение (система управления базами данных (СУБД) / сайт / веб-сервис).</p>
<h3>Union File Systems</h3>
<p>В контейнерах используется особая стековая файловая система (Union File Systems), в которой файлы и каталоги одного слоя представляют собой отдельную файловую систему (ветвь). Каждая новая ветвь может быть наложена на предыдущую, образуя единую файловую систему собранного контейнера. Содержимое каталогов, имеющих одинаковый путь внутри наложенных ветвей, рассматривается как единый объединенный каталог, что позволяет избежать необходимости создавать отдельные копии каждого слоя. За подробностями можно обратиться к статьям и видео:</p>
<ul>
<li><a href="https://www.freecodecamp.org/news/a-beginner-friendly-introduction-to-containers-vms-and-docker-79a9e3e119b/">A Beginner-Friendly Introduction to Containers, VMs and Docker</a></li>
<li><a href="https://habr.com/p/438796/">Изучаем Docker, часть 1: основы</a></li>
<li><a href="https://habr.com/p/439978/">Изучаем Docker, часть 2: термины и концепции</a></li>
<li><a href="https://youtu.be/wW9CAH9nSLs">The future of Linux Containers</a></li>
<li><a href="https://youtu.be/QF4ZF857m44">Основы Docker. Большой практический выпуск</a></li>
</ul>
<h3>Система команд Linux</h3>
<p>Итак, задача стоит перед нами исследовательская. Например, вы хотите попробовать разобраться с новой для вас системой команд конкретной версии ОС Linux или с настройкой какой-то определенной службы.</p>
<p>Чтобы воспользоваться Docker для этой цели, нам необходимо в первую очередь его установить. <a href="https://docs.docker.com/get-docker/">На официальном сайте</a> найдите дистрибутив Docker Desktop для macOS и Windows или инструкции для установки Docker для вашей версии ОС Linux. В результате установки вы получите Docker engine, Docker daemon и Docker CLI, то есть все самое необходимое для использования Docker.</p>
<p>Следующий этап представляется намного более творческим. Для работы контейнера с «установленной» внутри него версией ОС Linux, необходимо его из чего-то собрать. Для этого используется базовый образ (Docker image). Необходимо найти такой образ, на который вы потом будете «наслаивать» специфическое окружение. Для выполнения поставленной задачи необходимо выбрать образ ОС Linux и получить доступ к терминалу.</p>
<p>Сначала проверим, что после установки и запуска все работает корректно:</p>
<pre><code tabindex="0" class="language-bash">docker --version

&gt; Docker version 19.03.13, build 4484c46d9d
</code></pre>
<p>Командой выше можно протестировать, что Docker готов, может скачивать, устанавливать и запускать образы (Docker image). Образы должны где-то храниться, и Docker предлагает хранить их с помощью удаленной платформы <a href="https://docs.docker.com/registry/">Docker Registry</a> или в общедоступном хранилище <a href="https://hub.docker.com/">Docker Hub</a> (бесплатный общедоступный реестр образов).</p>
<p>Кроме простых образов существует понятие репозиториев <a href="https://docs.docker.com/docker-hub/repos/">Docker repository</a>, которые представляют собой набор образов с одинаковым именем, но с разными тегами (идентификаторами образов). Обычно с помощью тегов организуется хранение образов с разными версиями программного обеспечения и/или ОС.</p>
<p>Попробуем запустить образ. Для этого у команды Docker есть специальный тестовый образ, который можно скачать, установить и запустить следующим образом:</p>
<pre><code tabindex="0" class="language-bash">docker run hello-world

&gt; Unable to find image 'hello-world:latest' locally
&gt; latest: Pulling from library/hello-world
&gt; 0e03bdcc26d7: Pull complete
&gt; Digest: sha256:e7c70bb24b462baa86c102610182e3efcb12a04854e8c582838d92970a09f323
&gt; Status: Downloaded newer image for hello-world:latest
&gt;
&gt; Hello from Docker!
</code></pre>
<p>Сначала Docker CLI попытался найти указанный образ hello-world среди уже скачанных и не нашел его («Unable to find … locally»). Затем он обратился к реестру Docker Hub, который установлен по умолчанию, нашел, скачал и установил последнюю версию: «latest: Pulling from…». В итоге мы получили вывод в терминале сообщения «Hello from Docker!»</p>
<p>Если сейчас посмотреть список запущенных контейнеров с помощью команды:</p>
<pre><code tabindex="0" class="language-bash">docker ps

&gt; CONTAINER ID  IMAGE  COMMAND  CREATED  STATUS  PORTS  NAMES
</code></pre>
<p>Вы увидите, что список пуст (присутствуют только заголовки таблицы). Если запустить эту команду с определенным ключом <code>--all</code>, то мы получим список всех запущенных контейнеров и контейнеров, которые уже отработали, но не были удалены из списка (операция удаления производится вручную или при остановки службы Docker daemon):</p>
<pre><code tabindex="0">docker ps --all

&gt; CONTAINER ID  IMAGE        COMMAND   CREATED        STATUS                   PORTS  NAMES
&gt; 93cb27f68163  hello-world  &quot;/hello&quot;  3 minutes ago  Exited (0) 3 minutes ago        happy_goldberg
</code></pre>
<p>Приведу список основных команд со ссылками на оригинальную документацию, которыми вы будете пользоваться большую часть времени при использовании Docker:</p>
<ul>
<li><a href="https://docs.docker.com/engine/reference/commandline/ps/">docker ps</a></li>
<li><a href="https://docs.docker.com/engine/reference/commandline/run/">docker run</a>;</li>
<li><a href="https://docs.docker.com/engine/reference/commandline/image/">docker image</a>;</li>
<li><a href="https://docs.docker.com/engine/reference/commandline/container/">docker container</a>;</li>
<li><a href="https://docs.docker.com/storage/volumes/">docker volume</a>.</li>
</ul>
<p>Вернемся к нашей задаче. Самый простой способ воплотить ее в жизнь, найти готовый образ желаемой ОС в реестре и запустить. Для этого переходим на страницу <a href="https://hub.docker.com">Docker Hub</a> и ищем необходимый нам образ (пусть это будет <a href="https://hub.docker.com/_/centos">образ с CentOS 8</a>). Вы наверняка увидите подсказку с командой для установки данного образа, после выполнения которой вам будет выведена следующая информация:</p>
<pre><code tabindex="0" class="language-bash">docker pull centos

&gt; Using default tag: latest
&gt; latest: Pulling from library/centos
&gt; 3c72a8ed6814: Pull complete
&gt; Digest: sha256:76d24f3ba3317fa945743bb3746fbaf3a0b752f10b10376960de01da70685fbd
&gt; Status: Downloaded newer image for centos:latest
&gt; docker.io/library/centos:latest
</code></pre>
<p>Любопытно, что образ чистой ОС занимает всего около 70 Мб, гораздо меньше, чем полноценная ОС Linux. Удивляться не надо, ведь в образе нет, например, ядра ОС.</p>
<p>В интерфейсе Docker Hub есть три вкладки с полезной информацией: Description, Reviews, Tags. Наличие вкладки Tags свидетельствует о том, что перед нами не образ, а репозиторий образов. На вкладке Reviews есть полезные комментарии от пользователей, на которые стоит обратить внимание при выборе образа или версии образа. Также вы можете заметить справа над строкой, которую мы с вами скопировали, селектор с выбором платформы (ОС и архитектура процессора).</p>
<p>Надо понимать, что под архитектурой процессора подразумевается архитектура процессора, для которой собран образ ОС. По умолчанию используется amd64 (синоним x86-64). В первой строке вывода мы также видим, что по умолчанию присваивается тег latest. Это означает, что версию ОС, которая будет установлена в контейнере нужно смотреть в реестре под этим тегом.</p>
<p>Информация о том, какие слои должны присутствовать в образе (другими словами — «какое окружение»), хранится в специальном файле Dockerfile. На главной странице репозитория ссылка <a href="https://github.com/CentOS/sig-cloud-instance-images/blob/12a4f1c0d78e257ce3d33fe89092eee07e6574da/docker/Dockerfile">latest, centos8, 8</a> в списке тегов (Supported tags and respective Dockerfile links) ведет на содержимое файла Dockerfile с указанием четырех слоев контейнера, чуть позже мы рассмотрим эти конфигурационные файлы подробнее. Для используемого нами образа это всего три слоя (базовый слой, слой операционный системы, запуск терминала), которые устанавливаются командами:</p>
<pre><code tabindex="0" class="language-docker">FROM scratch
ADD centos-8-x86_64.tar.xz /
LABEL org.label-schema.schema-version=&quot;1.0&quot; org.label-schema.name=&quot;CentOS Base Image&quot; org.label-schema.vendor=&quot;CentOS&quot; org.label-schema.license=&quot;GPLv2&quot; org.label-schema.build-date=&quot;20200809&quot;
CMD [&quot;/bin/bash&quot;]
</code></pre>
<p>Используется базовая ОС для Docker. С помощью команды <code>ADD</code> добавляются файловая система образа интересующей нас ОС, затем устанавливаются именованные константы для контейнера командой <code>LABEL</code> (важный шаг для управления контейнерами), а на последнем шаге запускается терминал командой <code>CMD</code>. Кажется, пора запустить контейнер.</p>
<p>Давайте посмотрим список загруженных образов с помощью команды:</p>
<pre><code tabindex="0" class="language-bash">docker image ls

&gt; REPOSITORY   TAG     IMAGE ID      CREATED       SIZE
&gt; centos       latest  0d120b6ccaa8  3 months ago  215MB
&gt; hello-world  latest  bf756fb1ae65  11 months ago 13.3kB
</code></pre>
<p>Обратите внимание на то, что при распаковке размер образа увеличился почти в три раза (215MB против ~ 70MB). Запуск контейнера осуществляется командой:</p>
<pre><code tabindex="0" class="language-bash">docker run centos
</code></pre>
<p>Если вы выполните эту команду, то увидите, что на консоль ничего не вывелось. Почему? Вроде бы вы скачали правильный образ, вроде бы запустили… Помните, что вы запускаете какое-то приложение. После окончания работы этого приложения контейнер автоматически останавливается и выгружается из памяти. Так и получилось.</p>
<p>Как же быть, если нам нужно запустить терминал для работы с консолью контейнера? Необходимо выполнить команду со специальными ключами:</p>
<pre><code tabindex="0" class="language-bash">docker run -it --entrypoint bash centos
</code></pre>
<p>Ключ <code>--entrypoint</code> перезаписывает значение точки вхождения по умолчанию. Это приложение, которое запускается внутри контейнера после его сборки. Два ключа <code>-i</code> и <code>-t</code> служат для открытия стандартного потока ввода и использования одного терминала соответственно. С подробностями вы можете ознакомиться в <a href="https://docs.docker.com/engine/reference/commandline/run/">разделе документации</a>, относящейся к команде, <a href="https://habr.com/p/439978/">в статье на Хабре</a>, в Википедии: <a href="https://ru.wikipedia.org/wiki/%D0%A1%D1%82%D0%B0%D0%BD%D0%B4%D0%B0%D1%80%D1%82%D0%BD%D1%8B%D0%B5_%D0%BF%D0%BE%D1%82%D0%BE%D0%BA%D0%B8">Стандартные потоки</a> и <a href="https://ru.wikipedia.org/wiki/TTY-%D0%B0%D0%B1%D1%81%D1%82%D1%80%D0%B0%D0%BA%D1%86%D0%B8%D1%8F">TTY-абстракция</a>.</p>
<p>Чтобы выйти из терминала bash, как обычно, необходимо выполнить команду <code>exit</code>. Это приведет к остановке контейнера, поскольку программа терминала будет завершена.</p>
<p>Взаимодействие с внешними хранилищами — папками базовой ОС, на которой запущен Docker, или с содержимым других контейнерами — обеспечивается путем использование томов <a href="https://docs.docker.com/storage/volumes/">Docker volumes</a>. Тома монтируются в папки контейнера, которые можно задавать с помощью конфигурационных файлов или напрямую в терминале установкой определенных флагов.</p>
<p>Тома могут быть как самостоятельными ресурсами, недоступными для базовой ОС (обычный тип), так и связанными с папками базовой ОС (тип bind). Если используется независимый том, его необходимо предварительно создать и подключить к контейнеру. Для работы с томами используются следующие команды:</p>
<ul>
<li><strong>Создание тома:</strong> <code>docker volume create my_volume</code></li>
<li><strong>Список всех томов:</strong> <code>docker volume ls</code></li>
<li><strong>Информация о томе:</strong> <code>docker volume inspect my_volume</code></li>
<li><strong>Удаление тома:</strong> <code>docker volume rm my_volume</code></li>
</ul>
<p>Когда вы создадите обычный том и просмотрите информацию о нем, вы увидите, в какой именно папке будет содержаться файл с содержимым тома в базовой ОС. Чтобы подключить том при запуске контейнера и иметь доступ к содержимому этого тома в определенной папке контейнера (например, /tmp/volume), достаточно выполнить команду <code>run</code> с параметром <code>--mount</code>:</p>
<pre><code tabindex="0" class="language-bash">docker run --mount 'source=my_volume,target=/tmp/volume' -it --entrypoint bash centos
</code></pre>
<p>Если к контейнеру необходимо подключить папку из базовой ОС, необходимо выполнить команду со следующими ключами:</p>
<pre><code tabindex="0" class="language-bash">docker run --mount 'type=bind,source=/tmp,target=/tmp/volume' -it --entrypoint bash centos
</code></pre>
<p>Связывать подобным образом папки базовой ОС оказывается довольно удобно для работы приложений внутри контейнера. Вы можете разобраться с использованием томов подробнее <a href="https://docs.docker.com/storage/">в документации</a> или кратко <a href="https://habr.com/p/441574/">в руководстве по работе с данными</a>.</p>
<p>Имея доступ к терминалу ОС контейнера, вы можете изучить интересный для вас дистрибутив ОС Linux, не устанавливая его к себе на компьютер и не арендуя сервер. Например, теперь вы можете экспериментировать со специфичным пакетным менеджером или создать файлы настроек нужных вам служб. И все это после выполнения одной команды в терминале!</p>
<hr>
<p>Кажется, теперь мы прошлись по всему самому необходимому и готовы попробовать использовать технологию на практике. <a href="https://web-standards.ru/articles/docker-unboxing-2/">Поговорим об этом во второй части</a>.</p>

                    ]]></description><pubDate>Mon, 08 Feb 2021 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/docker-unboxing-1/</guid></item><item><title>Что нового в WCAG 3.0</title><link>https://web-standards.ru/articles/wcag3-changes/</link><description><![CDATA[
                        <p>Руководство по обеспечению доступности веб-контента (<a href="https://www.w3.org/TR/wcag-3.0/">Web Content Accessibility Guidelines</a>, сокращённо WCAG) — это самый важный стандарт в области веб-доступности. Он содержит рекомендации о том, как сделать контент более удобным для пользователей в целом и доступным для людей с особыми потребностями.</p>
<p>WCAG является одним из ключевых документов по веб-доступности и определяет, как она развивается. Поэтому полезно знать об изменениях, которые с ним происходят.</p>
<p>21 января 2021 Рабочая группа руководства по доступности (Accessibility Guidelines Working Group) опубликовала <a href="https://www.w3.org/TR/wcag-3.0/">первую рабочую версию черновика Руководства по доступности 3.0</a> (WCAG 3.0). Дальше буду называть его просто WCAG 3.</p>
<p>Важно отметить, что WCAG 3 — наследник предыдущих версий руководств. Он не отменяет и не заменяет их.</p>
<p>Также WCAG 3 находится пока в статусе первой рабочей версии черновика. Это значит, что W3C всё ещё рекомендует руководствоваться версией 2.1.</p>
<p>Кроме основных требований доступности, которые перекочевали в WCAG 3 из предыдущих версий, новые рекомендации частично включают и расширяют два других документа:</p>
<ul>
<li><a href="https://www.w3.org/WAI/standards-guidelines/uaag/">Руководство по доступности агентов пользователя</a> (User Agent Accessibility Guidelines 2.0 или коротко UAAG 2.0);</li>
<li><a href="https://www.w3.org/WAI/standards-guidelines/atag/">Руководство по доступности инструментов разработки</a> (Authoring Tool Accessibility Guidelines 2.0 или ATAG 2.0).</li>
</ul>
<p>WCAG 3 также их не заменяет и не отменяет.</p>
<p>Давайте теперь разберёмся с конкретными изменениями в WCAG 3.</p>
<h2>Зачем изменять WCAG</h2>
<p>Первая версия WCAG появилась в 2008 году, и с тех пор принятые подходы к доступности не сильно изменились. А технологии и нужды пользователей с особыми потребностями, напротив, эволюционировали.</p>
<p>Новая версия документа — это более комплексный и универсальный ответ на новые запросы пользователей и появление новых технологий.</p>
<h2>Цели WCAG 3</h2>
<p>Новые рекомендации, как и предыдущие, стремятся сделать цифровые продукты более доступными и удобными для людей с особыми потребностями. Это, например, интернет в целом, сайты, ePub, PDF, приложения и многие другие технологии.</p>
<p>Однако у WCAG 3 есть и новые цели.</p>
<ul>
<li>Доходчивым языком описать требования к доступности, которые будут понятны широкой аудитории.</li>
<li>Лучше отразить нужды пользователей с особыми потребностями.</li>
<li>Дать гибкую систему оценки, которая эффективно ответит на потребности большего числа организаций.</li>
<li>Мотивировать улучшать доступность продуктов с помощью новой модели оценки доступности.</li>
<li>Разработать удобную модель соответствия требованиям доступности (Conformance Model), которая поможет лучше определять доступность продуктов.</li>
<li>Дать возможность людям, создающим цифровые продукты, совершать некритичные ошибки, если они не влияют на пользователей с особыми потребностями.</li>
</ul>
<h2>Работа над черновиком WCAG 3</h2>
<p>Из рабочей группы руководства по доступности W3C были выделены две команды: <a href="https://www.w3.org/WAI/GL/task-forces/silver/">Silver Task Force</a> и <a href="https://www.w3.org/community/silver/">Silver Community Group</a>. Их задача — проведение исследований и улучшение WCAG.</p>
<p>В течение 2019 и 2020 годов группы проводили интервью с пользователями с особыми потребностями, дизайнерами, разработчиками, тестировщиками и чиновниками, занимающимися доступностью и изучали литературу и исследования по доступности.</p>
<p>В итоге они опубликовали <a href="https://docs.google.com/presentation/d/1POs7orJ4ALB0bq5_vyo4v8RxDcr-5ctwD1noVgpXuJc/view">отчёт</a> (Google Slides) о проблемах с юзабилити, структурой, требованиями и поддержкой WCAG 2. Их выводы и легли в основу черновика WCAG 3.</p>
<h2>WCAG 2 vs WCAG 3</h2>
<p>Первое, что можно заметить — название документа. Из него убрали слово «веб-контент» и оно превратилось в «W3C Accessibility Guidelines 3.0». Это связано с тем, что рекомендации охватывают больше цифровых продуктов и технологий, например, интернет вещей, виртуальную реальность, приложения и другие.</p>
<p>Ещё больше изменилась структура и модель соответствия требованиям.</p>
<h3>Новая структура WCAG</h3>
<p>Из документа убрали пять основных принципов. Теперь он имеет только три уровня содержимого:</p>
<ul>
<li>Руководства (Guidelines), как и раньше;</li>
<li>Результаты (Outcomes) вместо Критериев успешности (Success Criteria);</li>
<li>Методы (Methods) вместо Техник (Techniques).</li>
</ul>
<p><strong>Руководство</strong> — это высокоуровневая, упрощённая версия содержания. Оно нужно людям, которые пока плохо знакомы с доступностью и не готовы углубляться в технические детали. У руководств есть названия и упрощённые краткие описания (Plain Language Summary). Каждое из них касается отдельной темы: текстовых альтернатив, понятности слов, субтитров, контраста текста и так далее.</p>
<p>Пока что в WCAG 3 есть шесть руководств, но их число будет расти. Если вам интересно, что ещё планируется добавить, можете почитать про <a href="https://docs.google.com/document/d/1aCRXrtmnSSTso-6S_IO9GQ3AKTB4FYt9k92eT_1PWX4/view">планы W3C по миграции руководств из WCAG 2</a>.</p>
<p>Внутри руководств есть <strong>результаты</strong>. Это проверяемые критерии. Они состоят из критических ошибок (Critical Errors) и оценки результата (Outcome Scoring).</p>
<p><strong>Критические ошибки</strong> — это ошибки, из-за которых пользователь не может совершить нужное действие. К таким ошибкам относятся:</p>
<ul>
<li>Ошибки, которые мешают с чем-то взаимодействовать. Например, нельзя поставить звук на паузу.</li>
<li>Ошибки, из-за которых невозможно завершить процесс. Например, нельзя отправить данные, так как кнопка не в порядке табуляции.</li>
<li>Ошибки, которые приводят к затруднениям у пользователей. К примеру, непонятные и неоднозначные формулировки в тексте.</li>
</ul>
<p>Не любой результат содержит ошибки. Но если содержит, то это значит, что продукт не соответствует требованиям WCAG 3 ни на каком уровне.</p>
<p>Все результаты оцениваются по шкале от 0 до 4. Оценки результата гибкие и не зависят от технологий.</p>
<p>Результаты могут включать <strong>методы</strong>. В них содержится описание, примеры кода, тесты и их результаты.</p>
<p>Методы бывают нескольких типов:</p>
<ul>
<li>Общие (All): подходят для всех технологий.</li>
<li>Специфические (Technology Specific): подходят для одной технологии. Например, HTML, PDF или VR.</li>
<li>Фолбэк (Fallback): подходят для новых или проприетарных технологий, для которых пока нет отдельных методов.</li>
</ul>
<p><strong>Тесты</strong> нужны для оценки реализации методов. Они состоят из пошаговых инструкций по оценке метода в зависимости от технологии и информацию о том, как считать баллы. Так как результаты оформлены как проверяемые критерии, то тестирование доступности значительно упрощается.</p>
<p>Пока что все тесты разделяются на две категории:</p>
<ol>
<li>Атомарные (Atomic Tests), которые бывают ручными и автоматическими. Нужны для тестирования отдельных объектов и процессов.</li>
<li>Холистические (Holistic Tests). Необходимы для тестирования доступности в целом. Они охватывают тестирование вспомогательных технологий, юзабилити и дизайна в целом.</li>
</ol>
<p>Также в руководствах есть несколько видов дополнительных материалов:</p>
<ul>
<li>Как реализовывать принципы (How To);</li>
<li>Функциональные потребности (Functional Needs);</li>
<li>Функциональные категории (Functional Categories).</li>
</ul>
<p>В материалах «Как реализовывать принципы» рассказано простым языком о применении принципов доступности на практике. Например, <a href="https://www.w3.org/WAI/GL/WCAG3/2020/how-tos/captions/">как правильно добавить субтитры</a>. Рекомендации сгруппированы по отдельным вкладкам с общим описанием принципа, категориями пользователей, для которых мы это внедряем, что нужно делать разработчикам и дизайнерам, а также ссылками на примеры хороших реализаций и на ресурсы.</p>
<p><strong>Функциональные потребности</strong> — это описание ограничений в возможностях пользователей или несоответствие между их возможностями и средой или контекстом. Например, не все пользователи умеют читать или писать.</p>
<p>Пока функциональные потребности вынесены в <a href="https://www.w3.org/WAI/GL/WCAG3/2020/functional-needs/">отдельный черновик</a>.</p>
<p><strong>Функциональные категории</strong> — объединённые в большие группы функциональные потребности пользователей. Пока что их список предварительный и включает, например, мобильные категории потребностей, моторные, зрительные и визуальные, речевые, ментальное здоровье и так далее.</p>
<h3>Новая модель соответствия требованиям и оценка доступности</h3>
<p>Раньше соответствие критериям успешности указывало только на то, доступна страница или нет. Теперь мы определяем доступность этой страницы с помощью <strong>оценки результата</strong> (Outcome Scoring). Результаты бывают в диапазоне от 0 до 4:</p>
<ul>
<li>0 — очень плохо (Very Poor);</li>
<li>1 — плохо (Poor);</li>
<li>2 — удовлетворительно (Fair);</li>
<li>3 — хорошо (Good).</li>
<li>4 — отлично (Excellent).</li>
</ul>
<p>Эти баллы объединяются с другими, чтобы определить общий уровень доступности.</p>
<p>Общая оценка (Overall Score) складывается из средней оценки всех результатов по функциональным категориям.</p>
<p>Из общей оценки складывается уровень соответствия требованиям доступности (Conformance Level). В WCAG 3 уровни A, AA и AAA заменены на Бронзовый, Серебряный и Золотой. Пока что подробно описан только Бронзовый. Остальные уровни будут проработаны в следующих черновиках.</p>
<p><strong>Бронза (Bronze)</strong> — минимальный уровень. Он эквивалентен AA. Контент, который не соответствует требованиям этого уровня, не соответствует WCAG 3. Он присваивается, если:</p>
<ul>
<li>проведены успешные атомарные тесты, общий балл и балл в каждой функциональной категории составляют не менее 3,5;</li>
<li>отсутствуют критические ошибки.</li>
</ul>
<p><strong>Серебро (Silver)</strong> — это более высокий уровень соответствия требованиям доступности, который эквивалентен AAA. Он складывается из:</p>
<ul>
<li>наличия Бронзового уровня;</li>
<li>успешного проведения холистических тестов.</li>
</ul>
<p><strong>Золото (Gold)</strong> — самый высокий уровень соответствия. Он складывается из тех же составляющих, что у серебряного.</p>
<p>Получается, что уровня A в новом документе нет. Можно предположить, что сайты, которые соответствуют уровню A из WCAG 2.1, не будут считаться доступными с точки зрения WCAG 3.</p>
<h2>Что дальше</h2>
<p>Пока опубликована только первая рабочая версия черновика и уже ведутся обсуждения по её улучшению.</p>
<p>W3C опубликует кандидата в рекомендации WCAG 3.0 примерно в 2022 году. Затем потребуется около года на проведение всех тестов и проверок. Так что окончательную версию документа следует ждать не раньше конца 2023.</p>
<p>Так как в новом стандарте будет другая модель соответствия, то рабочая группа даст всем организациям время на переход на новый стандарт. Также она планирует подготовить вспомогательные материалы с советами по переходу.</p>
<p>Надо помнить, что принятие новых рекомендаций — это долгий и сложный процесс. Многое за это время изменится: что-то будет убрано, что-то добавлено. Поэтому пока рано внедрять что-то из предложенного в черновике в практику как в своих рабочих проектах, так и в инструментах для тестирования доступности.</p>
<h2>Полезные ссылки</h2>
<ul>
<li><a href="https://www.w3.org/TR/wcag-3.0/">Черновик WCAG 3</a>.</li>
<li><a href="https://www.w3.org/WAI/standards-guidelines/wcag/wcag3-intro/">Введение WCAG 3</a>.</li>
<li><a href="https://www.deque.com/blog/first-public-working-draft-wcag-3/">What to Expect From The First Public Working Draft of WCAG 3.0</a>, Вилко Фирс.</li>
<li><a href="https://www.deque.com/blog/public-working-draft-wcag-3-history/">First Public Working Draft of WCAG 3.0, A Brief History</a>, Вилко Фирс.</li>
<li><a href="https://myaccessible.website/accessibility-expert/ag-3.0">Accessibility Guidelines 3.0</a>, Фиона Холдер.</li>
</ul>

                    ]]></description><pubDate>Thu, 28 Jan 2021 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/wcag3-changes/</guid></item><item><title>Водитель или механик?</title><link>https://web-standards.ru/articles/driver-or-mechanic/</link><description><![CDATA[
                        <p>Спор «инструменты против знаний» обычно идёт примерно так:</p>
<p><strong>Случайный разработчик:</strong> Не нужно знать CSS. Или даже базовый JavaScript. Ну то есть, наши классные инструменты обо всём позаботятся, так ведь?</p>
<p><strong>Я:</strong> Если ты разработчик, ты должен знать что-нибудь про CSS и JavaScript. Что, если твой инструмент не решает твою задачу? Что, если он ужасен с точки зрения производительности?</p>
<p><strong>Случайный разработчик:</strong> Глупости. С моими надёжными инструментами я гораздо более продуктивен.</p>
<p><strong>Я:</strong> Но ведь иметь какие-то базовые знания — это часть того, чтобы быть профессиональным веб-разработчиком.</p>
<p><strong>Случайный разработчик:</strong> Ну вот если ты едешь на машине, ты не должен знать, как она работает, так? Ты просто рулишь.</p>
<p>И не упомнишь, сколько раз за последние десять лет я слышал эту аналогию. На первый взгляд она интересная, но я понял, что не согласен с ней.</p>
<p>Я считаю, мы не водители машины под названием «Всемирный веб», а скорее механики. И механики совершенно точно должны знать что-нибудь об устройстве, которое они создают и обслуживают, так ведь?</p>
<p>На самом деле, чем больше я думаю об этом, тем больше я вижу в этом фундаментальный вопрос.</p>
<p><strong>Кем бы вы предпочли быть: водителем машины или механиком?</strong></p>
<p>Я уверен, что такая постановка вопроса вызовет у некоторых интерес. Всё же лучше не заходить слишком далеко в таких аналогиях. Вместо того чтобы прояснить, они могут с легкостью запутать.</p>
<p>Проблема в том, что люди начинают добавлять подробности, которые не подходят к этой аналогии. Что именно мы имеем в виду под машиной? Веб, сайт или целый процесс его создания? Что значит «вести машину»? Сёрфить по вебу, создавать сайты или что-то другое? Кого именно символизирует механик? Чем являются инструменты?</p>
<p>Такая дискуссия может легко деградировать в крикливый спор о значении аналогии, когда все уже забыли о вопросе «знания против инструментов», который аналогия должна была прояснить.</p>
<p>За последние двадцать лет я заметил ещё кое-что: люди прибегают к обыденным сравнениям, только если у них кончаются реальные аргументы или если они не до конца понимают всю суть проблемы. Поэтому они создают образ чего-то, с чем все готовы согласиться, и делают вид, что это ставит точку в дискуссии.</p>
<p>Вопрос о том, действительно ли достаточно инструментов для создания сайтов, или лежащие в основе технологии также необходимы, не решается обыденным сравнением про машины. Только реальные аргументы про инструменты и знания смогут ответить на него.</p>
<p>Несмотря на все вышесказанное, мне нравится вопрос, который я задал выше, поскольку он заставляет вас принять фундаментальное решение. Так что пусть будет, даже если это <em>подбросит</em> нас до потенциально запутывающей аналогии с машинами.</p>
<p>Как разработчик, вы бы предпочли быть водителем машины или механиком?</p>

                    ]]></description><pubDate>Thu, 07 Jan 2021 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/driver-or-mechanic/</guid></item><item><title>Опрос MDN, Igalia, npm 7, React vs WordPress, уже Webpack 5, Rome — инструмент будущего</title><link>https://web-standards.ru/articles/episode-252/</link><description><![CDATA[
                        <p>Перед вами расшифровка 252 выпуска подкаста «Веб-стандарты», который вышел 19 октября 2020. Если вы хотите его именно послушать, то вы можете сделать это на <a href="https://youtu.be/v1RzZEYJmBk">Ютубе</a>, <a href="https://podcasts.google.com/feed/aHR0cHM6Ly93ZWItc3RhbmRhcmRzLnJ1L3BvZGNhc3QvZmVlZC8/episode/aHR0cHM6Ly93ZWItc3RhbmRhcmRzLnJ1L3BvZGNhc3QvZXBpc29kZXMvMjUyLm1wMw?sa=X&amp;ved=0CAUQkfYCahcKEwjA59Du-dXsAhUAAAAAHQAAAAAQAQ">Google Podcasts</a>, <a href="https://podcasts.apple.com/podcast/id1080500016?i=1000495250597">Apple Podcasts</a>, <a href="https://music.yandex.ru/album/6245956/track/72546462">Яндекс.Музыке</a> или на любой другой платформе, где можно слушать подкасты. Но если вам приятнее будет почитать — пожалуйста! А если вы вдруг найдёте опечатки или неточности, то да — <a href="https://github.com/web-standards/web-standards.ru/blob/master/src/articles/episode-252/index.md">мы будем рады пулреквесту</a>.</p>
<h2>Темы</h2>
<ul>
<li><a href="https://web-standards.ru/articles/episode-252/#section-3">События</a></li>
<li><a href="https://web-standards.ru/articles/episode-252/#section-4">Опрос MDN</a></li>
<li><a href="https://web-standards.ru/articles/episode-252/#section-5">Открытые приоритеты Igalia</a></li>
<li><a href="https://web-standards.ru/articles/episode-252/#section-6">Новинки Npm 7</a></li>
<li><a href="https://web-standards.ru/articles/episode-252/#section-7">React vs WordPress</a></li>
<li><a href="https://web-standards.ru/articles/episode-252/#section-8">Уже Webpack 5</a></li>
<li><a href="https://web-standards.ru/articles/episode-252/#section-9">Что такое Rome</a></li>
<li><a href="https://web-standards.ru/articles/episode-252/#section-10">Инструмент будущего</a></li>
</ul>
<h2>Интро</h2>
<p><strong>Никита Дубко:</strong> Привет. С вами 252-й выпуск подкаста «Веб-стандарты» и его постоянные ведущие Никита Дубко из «Яндекса»…</p>
<p><strong>Вадим Макеев:</strong> И Вадим Макеев из HTML Academy.</p>
<p><strong>Никита Дубко:</strong> В этом подкасте мы обсуждаем главные новости фронтенда за прошедшую неделю. Читайте новости в Твиттере, во ВКонтакте, в Фейсбуке или в Телеграме. Если вам нравится, что мы делаем, поддержите нас на Patreon, все ссылки в описании. Сегодня у нас в гостях Антон Кастрицкий. Антон, привет. Расскажи немножко про себя.</p>
<p><strong>Антон Кастрицкий:</strong> Всем привет. Да, меня зовут Антон, я работаю в партнерских интерфейсах Яндекс.Маркет, иногда даже выступаю с докладами, имея нездоровую любовь к инструментам разработки, бывает даже в Твиттер пишу микропакеты в npm без зависимостей, всячески топлю за веб и энтузиирую в Vim.</p>
<ul>
<li><a href="https://twitter.com/antonk52">Антон в Твиттере</a></li>
<li><a href="https://www.twitch.tv/antonk52">Антон на Твитче</a></li>
</ul>
<p><strong>Вадим Макеев:</strong> На самом деле, этот выпуск родился, и Антон с нами сегодня, потому что я как-то прочитал его тредик про Rome в Твиттере, англоязычный, причем, и не так много разработчиков по-английски Твиттер ведут, можем об этом, кстати, поговорить потом, плюс еще тема свежая и интересная. Опять же, мало разработчиков интересуются вещами, которые в практике ежедневной пока еще не вошли. Может, у тебя уже во весь рост Rome используется. Тем не менее, мы сегодня поговорим про этот новый тулчейн, как они себя называют, немножко про вебпяку… Про вебпяку, господи…</p>
<p><strong>Никита Дубко:</strong> Это мое любимое новое слово, спасибо, Вадим.</p>
<p><strong>Антон Кастрицкий:</strong> Это очень мило.</p>
<p><strong>Вадим Макеев:</strong> Немножко про Webpack, спасибо, ребят, за поддержку. Всякие новости недели и статьи. Давайте начнем с событий, как обычно.</p>
<h2>События</h2>
<ul>
<li><a href="https://nextjs.org/conf">Next.js Conf онлайн 27 октября</a></li>
<li><a href="https://developer.chrome.com/devsummit/">Chrome Dev Summit 9–10 декабря</a></li>
</ul>
<p><strong>Вадим Макеев:</strong> В прошлом выпуске мы коротко говорили про Next.js, мол, такая штука, все дела, и они неожиданно анонсировали конференцию, сегодня нам в календарь принесли, спасибо вам большое за это. Не думаю, что сами ребята, тем не менее. Next.js global user conference. Я подумал, что 27 октября размазано по нашей планете довольно-таки тонким слоем, потому что у кого-то еще 26-е, у кого-то уже 27-е, и он шагает по планете, и непонятно вообще, в какой момент эта конференция начнется. И планировать что-то… или просто в календарь добавить, к нам принесли в календарь, и нет города привязки конференции, и нет времени начала. Ты сидишь такой и думаешь — когда она будет? Вечером, утром, днем или круглые сутки будет идти?</p>
<p>Я написал об этом письмо, говорю: «Ребят, все, конечно, классно, но у вас глобальная конференция, вы не могли бы хотя бы намекнуть, когда она начинается?» Мне ответили, хохотнули, сказали: «Да, тайм-зоны, все дела». Короче, в 9 утра по PST, кажется, это Калифорния, кажется, это очень американская конференция, поэтому ожидайте, что она начнется, не знаю, вечером по российскому времени где-то.</p>
<p>Что еще? Ребята из Google анонсировали Chrome Dev Summit, он пройдет 9–10 декабря, и, к сожалению, к счастью, так вышло, опять же, в формате онлайн. Два дня конференция, которая посвящена исключительно тому, что делает Google Chrome, и это все около веба. Я на паре Chrome Dev Summit бывал, мероприятие крутейшее, там не только гуглеры, но еще и приглашают всяких ребят из компаний дружественных, чтобы они рассказали про собственные успехи, про собственные технологии, даже иногда про собственные браузеры. Поэтому я ожидаю там увидеть и Microsoft, может быть, даже Mozilla среди спикеров или, по крайней мере, где-нибудь рядышком.</p>
<p>Одна из фишек на этой конференции, что они устраивают office hours так называемые, я не знаю, есть ли по-русски адекватный перевод, офисные часы. Короче, это время, когда кто-то из команды, которая разрабатывает какие-то части Chrome, может с вами посидеть, поговорить и объяснить, что, как, ответить на ваши вопросы и все остальное.</p>
<p>В общем, ребята говорят, что конференции открыта для всех, она будет просто транслироваться на YouTube, но также можно спросить приглашение на то, что вы можете попасть на какие-то воркшопы, эти office hours и прочие всякие штуки дополнительные, в которых можно пообщаться с кем-то, а не просто молча посмотреть. Так что если вам интересно, если у вас какой-то минимальный уровень английского и возможность задавать вопросы, или интерес, или потребность задавать вопросы, регистрируйтесь, вот именно что регистрируйтесь, подавайте заявки, и радостно все посмотрим 9–10 декабря большую Google-конференцию.</p>
<p>Я не ожидаю там адских анонсов. Chrome разрабатывается сравнительно открыто, и все анонсы у них в этих шестинедельных релизах заранее известны, но какой-то набор интересных докладов и, может быть, каких-нибудь статей вокруг него обязательно будет, и мы, конечно же, будем обсуждать во время подкаста. В начале декабря, 9-10 числа.</p>
<h2>Опрос MDN</h2>
<ul>
<li><a href="https://www.surveygizmo.com/s3/5897636/Mozilla?sglocale=ru">MDN DNA Survey</a></li>
</ul>
<p><strong>Вадим Макеев:</strong> К новостям. MDN DNA survey. Какие потребности у разработчиков, решила спросить Mozilla у разработчиков, и спросила. На мой взгляд, не очень удачно. Mozilla, точнее, не Mozilla, а MDN как таковой, организация, в которую входят многие участники, не только Mozilla, спросил у разработчиков уже во второй раз, как им вообще веб, что им мешает, что им помогает, что хорошего, что плохого, и куда дальше двигаться MDN как сайту с документацией и совместимости веба и браузером и около того организациям, библиотекам, фреймворкам и всему остальному.</p>
<p>Такая попытка сделать срез актуальный на 2020 год. Они одновременно с этим, рядышком с этим, опубликовали предыдущий репорт 2019 года, в PDF, я не видел ни одного человека, который прочитал этот PDF в живую, честно говоря. Ребят, вы прочитали PDF-репорт?</p>
<p><strong>Никита Дубко:</strong> Я его открыл.</p>
<p><strong>Вадим Макеев:</strong> А я его скачал, это близко к тому, чтобы его открыть, да. Он лежит у меня близко, я на него смотрю и думаю — ну, почему, почему?</p>
<p>В общем, там много вопросов, мне удалось чуть-чуть улучшить перевод официальный этого опросника, мне Крис Милс написал по старой памяти, он помнит, что я по-русски умею, и говорит: «Слушай, у нас выходит, два дня до выпуска, помоги». Я прочитал, у меня волосы дыбом встали, я сказал: «Крис, там все очень плохо». Он говорит: «Поздно». Я говорю: «Окей». Я поправил какие-то самые очевидные ужасные вещи, но, тем не менее, у нас в чате «Веб-стандартов», в Telegram, заходите, кстати, есть дискуссия на тему того, чего получилось, и практически все говорят: «Прошел десять шагов, прошел девять шагов, не понял этого вопроса вообще». Опрос такой, очень печальный.</p>
<p>Мне повезло, ребята, мне повезло, что буквально в утро, когда мы опубликовали эту новость, вечером этого же дня я поучаствовал в звонке GDE, Google Developer Experts очередном, на котором представили результаты репорта за предыдущий год и рассказали про новый репорт. Первым вопросом после этой презентации вышел я и сказал: «Ребята, почему же PDF, почему такое плохое качество перевода, почему такое плохое качество вопросов? Даже англоязычные вопросы, они очень сложные, «по шкале от 1 до 15 оцените, насколько вам нравятся свойства, не знаю, aspect ratio». У меня нет эмоций на эту тему вообще никаких. Они бы еще это, не знаю, пятна Роршаха показывали и спрашивали: «Что вы об этом думаете?» Вопросы такого типа. К сожалению, очень сложные и очень такие нерелевантные для типичного разработчика, сквозь них нужно продираться. В общем, я задал все эти неудобные вопросы, мне немножко неловко, но все-таки ответили на них. Мне кажется, я больше никогда не попаду на этот созвон, потому что я снова прихожу и критикую всех.</p>
<p>Тем не менее, ребята объяснили, что это все все-таки делается силами Mozilla, они нанимают какое-то агентство, которое не всегда адекватно и переводит, и результаты комбинирует потом в пэдээфник, такой типичный корпоративный вариант, который всех устроит. Блин, но мы же веб, я даже не знаю, что тут еще добавить.</p>
<p>Печально, что так получилось, я сказал ребятам, трижды повторил громко, чтобы все точно поняли, что «пожалуйста, в следующем году обращайтесь к сообществам, сообщества готовы участвовать, готовы помогать».</p>
<p>Буквально тоже на днях, скоро выходит опрос The State of CSS, меня тоже позвали повычитывать русскоязычный перевод, и там тоже есть сложности, но хотя бы чуть заранее позвали. Я предложил помощь русскоязычного сообщества для следующего года и собственную помощь для того, чтобы все это сделать, более адекватно вопросы подготовить, более адекватный перевод подготовить.</p>
<p>А пока, ребята, если вы реально хотите повлиять каким-то образом на то, как развивается браузер, платформа, документация MDN и всего около того, вам придется выстрадать этот опрос. Я пока его еще и сам не прошел, но я планирую, чего его и вам желаю. Какие у вас впечатления, ребята, от этого опроса, я даже не знаю, может быть, я просто пессимист?</p>
<p><strong>Никита Дубко:</strong> Мне кажется, ты справедливо все, я начал его проходить и не закончил, потому что действительно продираешься сквозь некоторые вопросы, и уже такое впечатление сложилось в голове, что этот опрос составляли не технические люди по ТЗ от технических людей, оно прям чувствуется. Это, знаешь, как в книгах Лии Веру на русском, «забивка» которая padding, то есть что-то пошло не так. Так вот, тут такая же ситуация, эти вопросы, действительно, «оцените свои ощущения по непонятный шкале», это вообще из области психологии, не очень понятно, зачем. Действительно, есть тот же самый The State of CSS, этих ребяток обожаю, потому что они делают это красиво, эмоционально, «оцените с эмодзи это все», мне хочется в этом участвовать.</p>
<p>В общем, на самом деле, посыл хороший, что они это делают, потому что это важно, это действительно какое-то направление, когда ты не просто делаешь продукт в вакууме, и никого не спрашиваешь. Но действительно, нужно над чем-то поработать. У меня не было мотивации завершить этот опрос, там сразу пугают, что целых 25 минут это займет, и ты такой: «О, то есть мне надо где-то сейчас полчаса из жизни найти». Такие вещи уже умеют делать, посмотрите в сторону The State of JS, The State of CSS, они это делают весело, задорно опенсорсно к тому же, то есть им помогать можно это все делать хорошо. Тот же самый Web Almanac очень красиво все… У Web Almanac тоже же PDF, но у них в том числе есть и веб-версия, в которой можно удобно искать, и клевые статейки, и вообще. Моя любовь просто Web Almanac, за прошлый год это самое лучшее, по-моему, что случилось.</p>
<p>Но там не опросы, там про исследования.</p>
<p><strong>Антон Кастрицкий:</strong> Мне очень понравилась сама идея, я даже… Раз уж мы пойдем по нарастающей, то есть Вадим еще, как я понял, не открывал, Никита попробовал, но не дошел до конца, а я все-таки закончил его вчера. Но это мне далось практически болью, потому что я это делал на телефоне.</p>
<p>Никита уже может догадываться, в какую сторону я клоню. У ребят есть такая очень практическая любовь к нативным селектам.</p>
<p><strong>Вадим Макеев:</strong> Я видел, я видел, они не помещаются в длину.</p>
<p><strong>Антон Кастрицкий:</strong> При этом каждый раз, когда ты что-то выбираешь, ты поворачиваешь этот телефон, чтобы попробовать прочитать — ну, хорошо, еще на два слова больше, я попробую догадаться, что же они хотели от меня узнать! Было сложно, честно. Там были странные вопросы, были места, где я прям задавался сам вопросами о том, что же они хотели здесь спросить, потому что это был очень креативный перевод, особенно что касалось accessibility, потому что это было очевидно не с первого раза. Но кажется, что основную идею моей боли в вебе мне довелось внести.</p>
<p><strong>Вадим Макеев:</strong> В общем, главной моей проблемой с этим опросом было то, что на некоторые вопросы… Вы знаете, когда хочется отвечать на какой-то вопрос, но ты читаешь, и не понимаешь, зачем им нужен ответ, то есть ты не понимаешь, зачем они задают этот вопрос, и как им мой ответ может пригодиться. Если ты этого не понимаешь, ты не понимаешь, как на вопрос ответить, ты не понимаешь, зачем на него отвечать в принципе, и это мотивацию очень сильно понижает. Поэтому плохие формулировки, неудобный интерфейс и PDF-экспорт в итоге — это как сделать так, чтобы никто его не прошел и никто его не прочитал. В похожих выражениях я вчера ребятам все это рассказал. Я надеюсь, они сделают выводы из этого, не только исключив меня из программы GDE, но и, не знаю, пригласив в следующем году поучаствовать меня и сообщество, и кого угодно.</p>
<p>В общем, опрос важный, не проходите его с телефона, попробуйте пройти с десктопа, вдруг получится?</p>
<h2>Открытые приоритеты Igalia</h2>
<ul>
<li><a href="https://www.igalia.com/2020/10/09/Open-Prioritization-Finalists.html">Two projects remain</a></li>
<li><a href="https://blog.amp.dev/2020/10/13/open-prioritization/">AMP: $10k matching campaign</a></li>
</ul>
<p><strong>Вадим Макеев:</strong> Продолжая про сообщества. Есть такая штука, «Открытые приоритеты», Igalia запустила проект, мы уже про него говорили как-то, и есть отдельный большой выпуск подкаста The F-Word, в котором мы подробнее обсуждаем это с Брайаном Карделлом. Программа, в которой предлагает вам заплатить за то, чтобы Igalia внедрила какие-то фичи в WebKit. Я помню, как разработчики отреагировали: «Я должен платить за то, чтобы…» Нет, вы не должны платить. Если вы реально хотите, чтобы какие-то фичи появились, вы можете повлиять на это.</p>
<p>9 ноября, написала Igalia, проект подводит черту, и кажется, нужные деньги собирают две фичи: inert и focus-visible. Это очень круто, потому что это все про доступность, это все важно, это все появится в WebKitе благодаря усилиям Igalia. Но вы все еще можете успеть докинуть свои деньги и, что интересно, если вы докинете доллар, то придет AMP и скажет: «О, кто-то докинул доллар, мы тоже докинем доллар». Если вы покинете 10 долларов, придет AMP и скажет… Ну, в общем, вы поняли. У них есть бюджет 10 тысяч долларов, чтобы сматчить вашу попытку поддержать все это. Так что до 9 ноября у вас есть шанс вкинуть в два раза больше денег, чем вы сами готовы дать. Обязательно присоединяйтесь. У меня там по 100 баксов на каждой фиче есть. Я настолько их ценю и настолько их жду, что мне очень хочется, чтобы они стали кроссбраузерными, и поэтому вам тоже рекомендую.</p>
<p><strong>Никита Дубко:</strong> У меня сразу вопрос такой меркантильный. Есть фичи, которые они соберут, судя по всему, и будут те, которые собрали, но не полностью. Куда эта денежка денется? Она же переведена уже.</p>
<p><strong>Вадим Макеев:</strong> Нет, эти деньги не переведены. Написав 100 баксов, ты сказал… Я, написав 100 баксов, сказал: «Я готов заплатить». Они не заморозили деньги, они не сняли деньги, ничего, просто это было такое обещание поддержать. Еще нужно будет потом прийти и снять эти деньги. Это такая ставка, скорее, чем непосредственная оплата. Поэтому, когда какой-то проект собрал определенную ставку, их начинают снимать. Это как…</p>
<p><strong>Антон Кастрицкий:</strong> Kickstarter.</p>
<p><strong>Вадим Макеев:</strong> Да, Kickstarter, на самом деле, с тебя не снимают деньги до последнего момента.</p>
<p><strong>Никита Дубко:</strong> Я сразу с этим не разобрался, просто если бы я знал, я бы, наверное, даже… Но я и задоначу, знаешь, что уж тут? Просто ситуация какая? Когда они это начинали, ты такой: «Ага, мне сходу нужно денежку отдать, и сейчас у меня, допустим, нет такой возможности». А оказывается, что это было бы в ноябре, и я в принципе мог бы… Как здорово, что еще есть время.</p>
<p><strong>Вадим Макеев:</strong> На самом деле, это все можно было прочитать на сайте opencollective, на основе которого все это происходит, но туда еще нужно было добраться. Неважно, главное, что теперь все знают, бегите, голосуйте вашими деньгами, даже один доллар станет двумя долларами и поможет ребятам это все профинансировать. И не разориться, кстати, потому что если они недособерут, они наверняка махнут рукой и скажут: «А, окей», кому-то зарплату не заплатят, еще что-нибудь такое. Идеалисты там в Igalia работают, я пообщался с некоторыми людьми оттуда, они, конечно, прямо опенсорсеры, линуксоиды, все такие открытые и… В общем, хиппи 21-го века. Удивительный проект. У них там плоская структура, все получат одинаковое количество денег в компании. В общем, такая коммуна практически, удивительно, удивительно. Они помогают доставлять новые фичи в браузер: MathML в Chromium, accessibility фичи, WebKit и так далее. Классно.</p>
<h2>Новинки Npm 7</h2>
<ul>
<li><a href="https://github.blog/2020-10-13-presenting-v7-0-0-of-the-npm-cli/">Presenting v7.0.0 of the npm CLI</a></li>
</ul>
<p><strong>Никита Дубко:</strong> Тут это, npm 7 вышел, и он вроде как вкусный, да? Можно так говорить про инструменты, «вкусный»? Я слышал, что некоторые бесятся.</p>
<p><strong>Вадим Макеев:</strong> Я пропущу вопрос.</p>
<p><strong>Никита Дубко:</strong> Ладно. В общем, npm 7, что в нем появилось? На самом деле, кажется, у них такой анонс, он такой коротенький, но со ссылочками, они хитро сделали, они сделали такой, вроде быстренько, а когда проваливаешься в explainer, и ты такой — о-о, какая тут глубина.</p>
<p><strong>Вадим Макеев:</strong> Это вам не релиз Webpack, но об этом еще поговорим.</p>
<p><strong>Никита Дубко:</strong> Это правда. Так вот, они сделали три по факту фичи, и эти фичи, они такие, каждая заслуживает отдельного эксплейнера, что они и сделали. Первое — это то, чего, наверное, прям ждали, это клево, это workspace, это возможность тебе взять и объединить, действительно, под какое-то рабочее окружение объединить, условно, папочку или по маске как-то выделить. Ты можешь действительно какой-то уровень этого огромного развесистого дерева выделить в отдельное рабочее окружение, и там по-своему крутить чего хотеть, package lock свой настраивать и это все.</p>
<p>Почему это действительно сейчас такая штука актуальная? Потому что есть спрос на монорепозитории, и, причем, он растет, в том числе, и в больших, и в малых компаниях, и те же самые всякие open source проекты потихоньку на это переходят. Когда у тебя есть монорепозиторий, там действительно есть проблема это все разруливать, зависимости внутри, внутренние сборки, когда у тебя кусок монорепозитория является зависимостью второго куска монорепозитория. Это все — это огромная боль. Есть, конечно, специальные инструменты для этого, кто-то пишет свои костыли, кто-то лёрну, лерну, я ее «лёрна» называю почему-то, не знаю, кто-то ее эксплуатирует.</p>
<p><strong>Антон Кастрицкий:</strong> Приведем еще пару примеров, чтобы, возможно, кому-то было это более очевидно. Например, если пойти на GitHub и, например, зайти на тот же самый Babel, можно заметить, что у них есть папочка packages, внутри которого есть очень много отдельных пакетов, и это собственно и представляет собой монорепозиторий, в котором уже хранится большое количество npm-пакетов. Так исторически сложилось, что npm этой фичей не владел, и сообщество стало пилить свои инструменты для того, чтобы как-то разруливать подобные ситуации. Есть Lerna, которую изначально делал Джейми Кайл, потом он также сделал Bolt, что уже больше относят к проджект-менеджменту, но, тем не менее. Я могу ошибаться, но, по-моему, в какой-то момент даже yarn начал поддерживать workspace, этой фичей я не пользовался, поэтому тут я прокомментировать не могу.</p>
<p><strong>Вадим Макеев:</strong> Получается, что workspace делают Lerna ненужной, или дополняют, или как?</p>
<p><strong>Антон Кастрицкий:</strong> Кажется, что от Lerna можно будет отказаться. Но я еще не игрался с этой фичей, но мне очень интересно.</p>
<p><strong>Никита Дубко:</strong> Кажется, еще пока нет, потому что это только началось, и так сходу все это переписать… Плюс, у Lerna есть еще кое-какие особенности и свои… В общем, проект не просто так создавался. Но действительно, спрос есть, у yarn действительно есть работа с workspace, это мы как раз обсуждали в одном из выпусков, и это такая фича, которая тогда… «О, классно, надо на yarn переходить». А вот не надо, npm тоже теперь так умеет. Я помню, мы обсуждали, чем ответит npm, чем ответит node, это все. Ответили, пожалуйста.</p>
<p><strong>Антон Кастрицкий:</strong> Самое смешное то, что здесь еще третьим пунктом есть еще одна причина, почему можно больше не переходить на yarn.</p>
<p><strong>Никита Дубко:</strong> Именно, это поддержка yarn.lock. Если вы живете на два мира… Я просто видел даже репозитории, где для того, чтобы и тем, и тем пользоваться, люди синхронизирует yarn.lock и package-lock.json этот вот. Это может быть полезно как раз таки, когда ты хочешь фичи из обоих миров использовать. Но синхронизировать это все — это тоже отдельная боль. Теперь не надо, теперь… Если yarn изначально делался так, что он в принципе может работать с package.json, как, он с ним работает, но создает свое какое-то такое дополнительное описание, чего ему там нужно, в этом yarn.lock, теперь npm тоже умеет смотреть в yarn.lock, и тут непонятно, это такая интересная конкуренция, минивойна, не знаю, за аудиторию. Но вы теперь можете при помощи npm работать с теми репозиториями, в которых вы работали раньше yarn. Не знаю, как на это реагировать, но, скорее всего, этого позитивная новость в плане того, что действительно стала совместимость, то есть те фичи, которые были в yarn, видимо, npm теперь их понимает, он умеет парсить и у себя тоже их обрабатывать. Кажется, оба инструмента в итоге выиграли от этого.</p>
<p><strong>Вадим Макеев:</strong> Круто, а вторая фича? Я периодически сталкивался… Я не профессиональный пользователь npm, я бы так сказал, то есть я всячески, много его используют для разных задач, понятное дело, но прям чего-то сложного и тем более монорепозиториев в руках держать и поиспользовать, настраивать не приходилось. Я помню, что какое-то время для меня периодически болью становятся эти peer dependencies, которые нужно как-то отдельно ставить, управлять и вообще врубаться, что случилось с моим репозиторием, почему ничего не работает. Кажется, проблема сейчас решается, и я правильно понял эту новую фичу?</p>
<p><strong>Антон Кастрицкий:</strong> Да, про фичу заключается в том, что теперь автоматически будут стамбливаться пир-зависимости. Как многие знают, в npm в package.json есть четыре зависимости, это dependencies, dev dependencies, peer dependencies, и есть еще optional dependencies, которыми по моим ощущениям вообще никто не пользуется. Первые будут всегда устанавливаться, обычные dependencies, вторые будут устанавливаться только когда вы прогоняете npm install внутри вашего пакета, то есть если ваш пакет кто-то устанавливает, вместе с ним приедут только его основные dependencies, и peer dependencies — это пакеты, которые должны будут предоставить потребители вашего пакета. Давайте приведем пример. Я написал какую-то библиотеку для React, для потребителей, которые живут в экосистеме React, и я не хочу в своей библиотеке хардкодить версию React, потому что к потребителям тогда приедет две версии React, и вместе с ними разруливать это не очень весело. Я указываю React в peer dependencies, и люди, которые будут уже пользоваться моим пакетом, должны будут убедиться, что внутри их пакета у них в зависимостях есть React, и он будет использоваться. Но в целом так сложилось, что такие общие пакеты должны жить в пирах, но эти пиры, peer dependencies, они автоматом не устанавливаются, поэтому в большинстве таких пакетов, когда вы пытаетесь первый раз их развернуть, можно ставить такой скрипт, как npm rank bootstrap, внутри которого устанавливаются эти пиры и еще несколько полезных утилит. Если когда-то вы пытались развернуть чужой проект, и спотыкались о то, что, блин, здесь же есть еще какой-то скрипт, который нужно прогнать для того, чтобы можно было начать развернуть примеры уже в нем, покопаться и исправить это багу, скорее всего, это было именно это. Я очень рад, что эту вещь наконец-то решили.</p>
<p><strong>Вадим Макеев:</strong> Это было решение, которое не сработало, именно такой механизм работы с пирами, и теперь они его немножко меняют для того, чтобы проблем было меньше, скриптов, и не нужно было костыли писать.</p>
<p><strong>Антон Кастрицкий:</strong> Именно так, да.</p>
<p><strong>Вадим Макеев:</strong> Хороший набор изменений, когда я смогу начать их использовать?</p>
<p><strong>Антон Кастрицкий:</strong> Как я понял, эта штука уже включена в 15-ю Node, но обновиться на нее можно и находясь на 14-й или более ранних версиях.</p>
<p><strong>Вадим Макеев:</strong> Вопрос, грубо говоря, я сейчас сижу, условно, у меня 14-я Node стоит, и я захочу использовать фичи npm 7, я просто обновляю свой npm, и у меня все работает или нет? Я видел, люди постили скриншоты, потому что у них далеко не все работает.</p>
<p><strong>Антон Кастрицкий:</strong> Так происходит всегда, когда пытаешься жить на gliding edge, то есть нужно быть готовым стать первопроходцем. Мне кажется, в этом нет ничего страшного, если вдруг есть опасения, то можно немножко подождать до 16-й Node, поскольку 15-я — это не… Как сказать? Я боюсь использовать слово «стабильный релиз», это не «стабильный», есть какое-то другое слово, я надеюсь, вы поняли, что я имею в виду. Это не тот релиз, который уйдет в LTS, то есть в поддержку на долгое время, поэтому за время существования 15-й Node его полноценно обкатают, и уже к 16-й, я думаю, можно будет спокойно считать это production ready решением.</p>
<p><strong>Вадим Макеев:</strong> Я для себя это называю так. 15-я Node –это следующая версия, а 16-я Node — это следующий релиз.</p>
<p><strong>Никита Дубко:</strong> На самом деле, у них же написано в документации, что по умолчанию тебе нужно четенько указать, вместе с latest у тебя npm не приедет 7-й, у тебя будет приезжать предыдущая все еще версия, а если ты ставишь себе 15-ю, тогда приедет уже 7-й. это они, видимо, тоже понимают, что там есть какие-то нюансы, и четко про это даже предупреждают в релизе.</p>
<p><strong>Антон Кастрицкий:</strong> Нет, на самом деле, я думаю, можно ведь, грубо говоря, если какой-то команде страшно нужно жить на 14-й Node, потому что с 15-й пакеты несовместимы, например, но хочется использовать фичи 7-го npm, можно же как-то разрулить на уровне конфигов, на уровне package.json версия npm прописывается. Хотя, будет ли она автоматически переключаться? Тоже вряд ли. NVM, по-моему, не умеет переключать версию npm, он только версию Node умеет переключать, да?</p>
<p><strong>Вадим Макеев:</strong> Просто интересно, что обычно же в package.json не указывается npm как зависимость и версия npm как зависимость.</p>
<p><strong>Антон Кастрицкий:</strong> В package.json можно указать vengeance-версию Node или range этих версий.</p>
<p><strong>Вадим Макеев:</strong> Да.</p>
<p><strong>Антон Кастрицкий:</strong> А про npm, мне кажется, там тоже есть поле для npm, но я им ни разу не пользовался.</p>
<p><strong>Никита Дубко:</strong> Мне казалось, там же vengeance пишется прям, то есть там же.</p>
<p><strong>Вадим Макеев:</strong> Круто, потому что иногда бывает, что нужно Node постарее, npm поновее. Надо разобраться, действительно, если кто-то хочет с воркспейсами начать работать именно в npm, кажется, вариант.</p>
<p><strong>Антон Кастрицкий:</strong> Тут есть еще один пункт, про который я хочу сказать пару слов. Тут ребята помечают то, что они не то, чтобы помечают npx как деприкейтнутую версию, но они его полностью переписали, чтобы он работал поверх npm-exec команды, и он теперь будет кидать ворнинг, если вы пытаетесь прогнать какой-то пакет, который не был установлен. Раньше, до того, как у нас был npx, мы писали путь node_modules/.bin и команду, которую мы хотим выполнить, а теперь мы уже можем просто запускать его как npx-команда. Но у этой штуки также была дополнительная фича, то, что мы могли с ее помощью прогонять команды, которые вообще не были установлены ни в рамках нашего проекта, ни на нашей операционной системе, вообще. Если у вас не установлен JS, но вы находитесь в проекте, в котором написаны JS-тесты, вы можете сделать npx jest, и у вас начнут прогоняться ваши тесты, то есть он скачает этот пакет, локально его установит ради этого одного прогона, и затем быстренько спрячет куда-нибудь в кэше обратно. Я не очень понял пока, как они будут разруливать такие моменты, но это что-то, с чем нужно поиграться. Звучит интересно.</p>
<p><strong>Вадим Макеев:</strong> Я слышал, что там есть большая проблема с безопасностью, потому что любая фигня, ты опечатался, ты написал не jst, а jsst, и там какой-то левый пакет с npm тебе тут же скачался и исполнился. Это, конечно, так, нервно немножко. В общем, npm, дивный новый мир, что-то нам такое новое несет, если у вас есть причины прям бежать и включать себе, разберитесь, как, чтобы у вас все остальное не поломалось тоже.</p>
<h2>React vs WordPress</h2>
<ul>
<li><a href="https://hankchizljaw.com/wrote/the-(extremely)-loud-minority/">The (extremely) loud minority</a></li>
</ul>
<p><strong>Вадим Макеев:</strong> Я перестал считать Энди Белла, хотя он мне симпатичен, но перестал. Он такой немножко… Он немножко интернет-тролль, он любит шитпостить в Твиттере, факт, неоспоримый факт, я думаю, он не обидится, если вдруг ему кто-нибудь переведет. Просто удалось встретиться в прошлом году на фронтенд-конфе в Москве, пообщаться, он смешной чувак. Есть с ним отдельная запись «Веб-стандартов», так что послушайте, если интересно.</p>
<p>Он написал пост про ужасно громкое меньшинство, и ужасно громким меньшинством он называет JS-разработчиков, точнее, не совсем JS-разработчиков, а разработчиков, которые пишут на каком-нибудь single page стейке. В чем проблема? А проблема в том, что когда вы идете в Твиттер, вы слышите: каскад — это плохо, CSS уже не работает для веба, HTML — это глупый язык, давайте что-нибудь переизобретем, переделаем, еще что-то такое. И вы думаете — в Твиттере ерунды не скажут. Нет, даже по-другому, вы слышите эту реплику один раз, второй раз, третий раз, четвертый, пятый раз и думаете — блин, все так говорят, все так думают. Ты идешь с друзьями пообщаться о чем-нибудь таком, и плюс-минус фронтендерские темы всплывают, и ты понимаешь, что все мои друзья пишут на React, все мои друзья пишут на MacBook, все мои друзья, не знаю, у всех в кармане дорогой телефон, все-все-все… И ты понимаешь, что весь мир, ты экстраполируешь свой ближайший круг, естественно, на весь мир, на весь город, на всю страну, и в конечном счете на все фронтендерское сообщество, и думаешь — ну, все же так делают, все, кого я знаю, пишут на Single Page, все, кого я знаю, не любят cascade CSS и так далее.</p>
<p>А если посмотреть на статистику, получается совсем другое. У статистики, естественно, есть свои проблемы, и Энди Белл пытается нащупать вообще, а кому это не нравится, тем, кто громче кричит, тем кто пишет 1% веба, как он посчитал, тем не нравится современный веб, те хотят все переизобрести и писать на условных Single Page. Получается очень такая неловкая ситуация, в которой люди, которые громче всего говорят, убеждают нас в том, что они правы, и мы начинаем на основе этого принимать собственные решения и выставлять собственные приоритеты. Как вам такой вброс, ребят?</p>
<p><strong>Никита Дубко:</strong> Забавно, но, кстати я хочу Энди назвать как раз таки таким громким человеком, потому что у этой статистики есть один подвох — все сайты в интернете, это не значит, что это все активные сайты, которые прямо сейчас разрабатываются, это сайты, которые, черт побери, с 1993 года существуют, WordPress позже появился. Их много, и их много тех, которые работают просто… как они, летят, потому что когда-то их сделали более-менее стабильными, и они работают. Там не нужен React, все отлично работает.</p>
<p>Есть современные требования у разработчиков, есть необходимость, бизнес-необходимость делать быстро, делать хорошо, делать качественно за счет переиспользования компонентов.</p>
<p>Здесь, скорее, мне кажется, это extremely loud minority — это спикеры, которые ездят по конференциям, а не люди, которые любят рассказывать про хорошие практики, и про Vanilla, и все такое. Чего уж тут, я сам такой, не знаю, в этом чатике есть еще такие люди. Это люди, которые рассказывают best practices, но по факту, чтобы делать это широко, они рассказывают это про такие технологии как Cube CSS, который придумал Энди. Скорее всего, у меня подозрение, что его бомбит из-за того, что на этот Cube CSS точно прибежали люди, которые… «Да что вы там в своем CSS постоянно придумываете, как изолировать ваши стили? Столько лет прошло, а вы все придумываете и придумываете. Вот, смотрите, в React, и вот тебе два пакета, которые решают одну проблему, три пакета — четвертую проблему». Это все… Да, оно как-то разделяет сообщество, потому что есть люди, которые: «Я хочу быстро и в принципе неплохо», а есть люди которые: «Я хочу разбираться в вебе, и ваш React не вечен, он когда-нибудь закончится». Очень сложно объективно эту статью комментировать, потому что, да, есть статистика, но мне кажется, если разбить ее по годам, и если еще MDN этот опрос проанализировать… Статистика The State of JS как раз таки говорит о том, что React популярен.</p>
<p><strong>Вадим Макеев:</strong> Но проблема в том, что те, кто прошел опрос, они уже крутятся в сообществе, они уже трендовые, они уже интересуются технологиями, и тут, естественно, есть перекос в сторону… Как сказать? То, что какой-нибудь The State of JS — 98% мужчин и 2% женщин, это не говорит о том, что такова система, что таково соотношение, допустим, полов внутри разработки, нет. Просто прикол в том, что именно активная часть сообщества, те, кто хотят рассказать, поинтересоваться или, не знаю, высказать свое мнение погромче, там такой перекос. Я просто что в случае с Энди, что в случае со The State of JS не вижу нормальной репрезентативной картины. Но Энди дает повод усомниться, Энди дает повод посмотреть по-другому на эту картину. Мне именно этим его вброс нравится, он по-прежнему немножко тролльский, он по-прежнему некорректный во многих смыслах, но он говорит: «Ребята, посмотрите чуть-чуть шире, чем ваш ближайший круг, посмотрите на то, чем занимаются люди». Опять же, в наш чатик «Веб-стандартов» в Telegram периодически приходят люди и говорят: «Я не могу использовать эти технологии, потому что у меня такая браузерная совместимость». А другие говорят: «А у меня на проекте, я пришел на проект, новый, клевый, а там jQuery». Или «я пришел на проект, а там такая-то CMS». Какой там React, туда даже Vue не воткнуть адекватно, если уж хочется чего-то подобного. А проекты живут, работают, приносят деньги, я не знаю, все интерфейсы, все сайты «Академии» не переписаны на React, потому что мы можем. К нас только, не знаю, чаты, дээмки и какие-то еще прямо совсем интерактивные интерфейсы, которые прям совсем веб-приложения стопроцентном смысле, они написаны на React, все остальное… Да нет, работает и без этого всего развесистого. Не потому, что это новый дефолт, а потому что это решает для нас какие-то конкретные задачи, и мы живем с этим прекрасно. Для всех остальных задач они и не нужны.</p>
<p><strong>Антон Кастрицкий:</strong> Примерно года три своей карьеры потратил именно на работу с WordPress. Мне кажется, я тут могу поделиться. А именно, у меня сложилось такое впечатление, что в этом островке индустрии… Я полностью вообще согласен с автором, что это уже не minority, это очень большое количество людей, но при этом я также согласен с Никитой про то, что это сайты, которые могли сделать 10 лет назад, они все еще работают. Ну и что, что на них заходит два человека в год? Это же сайт, его может точно так же сравнить, например, с Яндекс.Маркетом, в котором я работаю, куда заходит значительно больше людей. Это совсем не громкие люди, это не те, кто будут ходить по митапам и рассказывать: «Ой, представляете, я такой плагин себе установил». Большинство, с кем я сталкивался, кто работал или до сих пор работает в этой индустрии, это люди кто может вообще даже не думать, что значит система сборки в проекте.</p>
<p>Я написал, и оно уже все, по ftp залилось, и работает. Я ни в коем случае не пытаюсь как-то принизить эту часть индустрии. Я встречал большое количество людей, кто, имея по пять лет опыта, совсем не пользуется системами контроля версий, использует какие-то внутренние тулзы этой WordPress-индустрии, который за вас просто раз в день будет делать бэкапик, и вы сами в любой момент можете откатиться.</p>
<p>Мне кажется, это две такие довольно разные парадигмы. В целом это, наверное, нормально, то, что кто-то из них громкие, потому что они тусуются в Твиттере, и Твиттер сам располагает к тому, чтобы быть более вызывающим, оттащить людей на диалог.</p>
<p><strong>Вадим Макеев:</strong> Это проклятие современных соцсетей, они, пытаясь доставить тебе интересный для тебя контент, в итоге изолируют тебя в таком ecochamber, ты слышишь то, что ты хочешь слышать.</p>
<p><strong>Антон Кастрицкий:</strong> Bubble.</p>
<p><strong>Вадим Макеев:</strong> Да, да, да.</p>
<h2>Уже Webpack 5</h2>
<ul>
<li><a href="https://webpack.js.org/blog/2020-10-10-webpack-5-release/">Webpack 5 release</a></li>
</ul>
<p><strong>Никита Дубко:</strong> Тут релизы короче за релизами. Webpack 5. Представляете? У меня так такая же реакция была, как в чатике «Веб-стандартов», типа, мы тут на 4-й переехать не успели, остановитесь, какой 5-й, что происходит, мы еще на втором сидим, как так-то? А вот, все, раньше надо было… Вебпяка, мне понравилось слово, простите. Webpack 5, короче, зарелизился, и этот тот случай, когда релиз-ноуты очень подробны, и это даже, наверное, в минус, потому что я очень долго читал, чего они сделали, и пока дочитал, забыл что они делали в начале.</p>
<p>Но если коротко, у них там есть как и breaking changes, так и по большей части оптимизация производительности, то, что я увидел. Они покопали в сторону, во-первых, tree shaking, который все развивается и развивается, и прямо уже как стандарт уже, что ли, во всех этих сборочках, без него никуда. Улучшили всякие алгоритмы, начали использовать более крутые для кодогенерации. В общем-то, да, все идет к тому, что работают про перформанс, то есть это перформанс самой сборки, они его тоже подтюнили нормально, то есть ваша сборка будет не пять минут, а четыре минуты, вы сможете заварить меньше кофе. Tree shaking — это меньше на клиент приходит ненужного, как раз они очень много расписали, каким образом это все ненужное убирается, то есть это, в том числе, поддержка того, что уже можно не поддерживать. Они выпилили No JS полифилы, где эти No JS полифилы, например, не нужны. Они сейчас взяли ориентирование на клиент. А дело в том, что, я думаю, те, кто работает с Webpack, понимают, что можно как и на клиент, так и на сервер это все, это просто инструмент, который одни файлики в другие превращает, а где вы их запускаете, это уже ваши трудности. Но у них ориентирование теперь идет на фронтенд, на веб-платформу, то есть больше поддержка реалий, я бы так сказал, есть браузеры, в браузерах уже, например, работают динамические импорты больше, чем раньше, значит, можно с этим поиграться. Можно этот самый tree shaking как-то так сделать хитро, чтобы еще меньше приходило на клиент ненужного, можно прямо на ходу какие-то вещи анализировать, что нужно прямо сейчас, что не нужно. Опять же, есть фреймворки, популярные сейчас у фронтенд фреймворкм, например, React, и можно сделать какие-то штуки, которые именно под этот React заточены, потому что есть спрос.</p>
<p>В общем, я не знаю, через весь этот список продраться — это прям сложно. Антон, я понимаю, что ты в Webpack ковырялся. Что ты можешь выделить здесь такого прям сочного?</p>
<p><strong>Антон Кастрицкий:</strong> Мои, пожалуй, три самые любимые, и они в начале этого списка, поэтому мне легко их вынести, первое — это изменение, как ребята начали работать с кэшами, это такая небольшая боль моя, в том числе, была, что очень влияло на скорость сборки, что особенно критично в момент самой работы над проектом. То, что у ребят появился long term cashing, это также ранее анонсировалось как улучшенный persistent cache, то есть что-то, ради чего вы раньше могли использовать AutoDllPlugin, HardSourcePlugin, то есть что-то, что в момент сборки кэшировало все, что у вас едет в ваши бандлы из node_modules, чтобы повторно не обрабатывать эти файлы. Теперь это уже должно работать из коробки, то есть вы сможете выкинуть эти самые плагины, оно у вас должно работать автоматом уже, гораздо быстрее.</p>
<p>Также ребята выкинули очень много всего, что было в четвертом Webpack, то есть если вдруг вы сейчас живете с 4 Webpack, и у вас есть какие-то в консоли вординги, есть деприкейтнутые штуки, скорее всего, вам нужно будет сначала пофиксить их до того, как обновляться.</p>
<p>Последняя и самая большая фича, которая прям, мне кажется, киллер-фича вообще этой версии, это мой modules federation, это что-то, что может позволить вам в рамках своего проекта начать обращаться к чужому чанку, который был сделан из чужой сборки, то есть переиспользовать какие-то виджеты между сборками с разными проектами. Такая фича, с которой нам пришлось поиграться внутри партнерских интерфейсов, они у нас не единственные из В2В-кабинетов в Яндекс.Маркете, но у нас была необходимость показывать переиспользуемые виджеты между несколькими кабинетами.</p>
<p>Для этого мы пробовали тыкаться еще в пятую альфу, нам понравилось, то есть все работает. Я ранее уже сегодня приводил пример с React, то есть если у вас две версии React на одной странице, не всегда их можно подружить между собой, можно наступить на разные костылики. Мне самому очень нравится эта фича. Мне кажется, у нее есть очень большой потенциал, который еще совсем не раскрыт. Тут же можно докинуть несколько базвордов, как микрофронтенды и подобное.</p>
<p><strong>Вадим Макеев:</strong> Слушай, а можешь еще раз объяснить про этот федерейшн?</p>
<p><strong>Антон Кастрицкий:</strong> Давай представим, что ты сидишь в одной команде, я не знаю, вообще в другом здании, работаешь над своим продуктом, я в другом офисе работаю над своим продуктом, и внезапно я понимаю, что мне нужен этот кусочек, один конкретный кусочек твоего приложения отрисовать на своей странице, чтобы он умел ходить в твои эндпойнты. Я могу только лишь сославшись на него, вообще не зная про то, как собралось, когда последний раз собиралось твое приложение, загрузить его на свою страницу, он уже будет использовать мою версию React, которая у меня используется, и он правильно отрисуется, и может быть, будет какая-то формочка, человек может ее засабмитить. Он, как сказать, окажется first class citizen внутри моей страницы.</p>
<p><strong>Вадим Макеев:</strong> А это требует каких-то дополнительных телодвижений от разработчика, подготовить все к этому, или оно все само?</p>
<p><strong>Антон Кастрицкий:</strong> Оно все не само, определенно, документацию придется почитать. Но мне кажется, это того стоит. Я почти уверен, что за ближайший год выйдет отнюдь не одна статья про то, как правильно это готовить, что значительно понизит порог входа, и это будет здорово.</p>
<p><strong>Вадим Макеев:</strong> У нас кто-то в соцсетях сказал, что главная фича — это modules federation, но никто не попытался объяснить, почему это важно и почему это хорошо. Сейчас я услышал эту версию. Она звучит интересно, но опять же, фича, кажется, узкая, в смысле, она нужна, когда у тебя что-то совсем уж развесистое, но она звучит очень ценно для такой ситуации, конечно.</p>
<p><strong>Антон Кастрицкий:</strong> В целом, пока мы находимся на этой странице, про Webpack, я хотел бы еще пару вещей отметить. Никита начал с того, что кто-то еще не успел переехать даже на 4-й. 4-й Webpack вышел уже два с половиной года назад, то етсь это отнюдь не то, что они клепают мажорные версии, первая альфа вышла еще полтора года назад, я помню, я тогда как раз читал свой доклад про Webpack, как настроить энтерпрайз-сборку так, чтобы она не тормозила. Эти полтора года мы ждали этого мажорного релиза, и наконец-то он случился, уже, мне кажется, даже 5.1, с патчами, тоже вышел. Можно начинать играться.</p>
<p>Самая последняя основная идея, в целом я не считаю, что Webpack нужен всем. Мне кажется, это довольно сложная штука, которая подходит в тех местах, когда вам действительно нужна эта гибкость сборки. Гораздо чаще и большему количеству людей может подойти тот же Parcel.</p>
<p><strong>Никита Дубко:</strong> Я думал, ты скажешь Gulp, и я тебе поаплодирую</p>
<p><strong>Вадим Макеев:</strong> А я надеялся, что это Rollup, потому что если вам просто JS собирать.</p>
<p><strong>Антон Кастрицкий:</strong> А мне кажется, Rollup все равно нужно уметь настраивать, а Parcel за тебя уже может попробовать увидеть: «О, я у тебя нашел.babelrc, дай-ка я тебе еще Babel сейчас установлю, и кажется, у тебя здесь sas-файлы, ты их импортируешь, я тебе SAS тоже притащу». Он прямо совсем beginner friendly.</p>
<p><strong>Вадим Макеев:</strong> Я не люблю слишком магические пакеты. Я просто к тому, что если нужно, допустим, как-то JS пособирать, то Rollup более чем достаточно, а если уж нужно что-то прямо совсем сложное делать… Хотя и для Rollup можно плагинов насобирать, у них в последнее время развивается хорошо. Но я к тому, что есть альтернативы. Да, это не единственный, если вам больно пользоваться Webpack, возможно, вам он не нужен.</p>
<h2>Что такое Rome</h2>
<ul>
<li><a href="https://github.com/romefrontend/rome">Репозиторий Rome</a></li>
<li><a href="https://romefrontend.dev">Сайт Rome</a></li>
<li><a href="https://twitter.com/AntonK52/status/1300142257497088007">I’ve been looking at the Rode source code…</a></li>
</ul>
<p><strong>Вадим Макеев:</strong> Антон, ты тут тредик написал, я тебя позвал по его мотивам сюда. Можешь как-то коротко запичить, что это такое, и почему это может стать для кого-то интересным, этот Rome Frontend Toolchain?</p>
<p><strong>Антон Кастрицкий:</strong> Rome собой представляет такой очень большой инструмент, в котором есть очень много всего. Бо́льшая часть этого уже работает с собственными внутренностями, наружу торчит только линтер, если мы говорим про сегодня, но в обозримом будущем у него есть очень такие большие планы, он хочет захватить весь мир, а именно, он хочет собой, одним только собой, Rome, заменить и Babel, и ESLint, и Webpack, и Prettier и Jest, много-много всего другого. Та же Lerna, про которую мы ранее говорили, у них есть и на это планы.</p>
<p>Я могу больше рассказать про то, как он работает изнутри, почему меня он так сильно заинтересовал. Мне кажется, нужно обязательно рассказать про самого автора, поскольку он был написан очень известным человеком, Себастьяном Маккензи, это автор Babel, он долгое время работал в Facebook, в инфре он также приложил руку над самим yarn, про который мы тоже уже сегодня говорили.</p>
<p>Тут можно сделать такой gist, это не кто-то, кто только что пришел, а кто-то, у кого уже есть большая экспертиза в таких инструментах, которые должны будут работать на комьюнити очень разносторонние.</p>
<p><strong>Вадим Макеев:</strong> И большая экспертиза в open source, потому что мало написать код, нужно и заставить его не просто понравится всем остальным, а еще и чтобы люди приходили, помогали, развивали и заинтересовывались. Я гляжу на проект Rome на GitHub, и там уже open governance, там уже и сайт, и логотип, и одно, другое, третье, это, по-моему, все достаточно рано появилось. Он с самого начала появился не просто как проект выходного дня, а как как проект с заявкой на что-то большое. Судя по тем целям, которые ты описал, амбиции у него не фиговые, и для этих амбиции у него соответствующий фундамент уже есть.</p>
<p><strong>Антон Кастрицкий:</strong> Я думаю, тут даже можно рассказать немножко про историю. Те, кто подписан на Себастьяна в Твиттере, последние несколько лет могли наблюдать, он любил тизить разработчиков разными скриншотами о фичах, которые будут использованы в Rome, как это выглядит, как это удобно, как это здорово и просто даже задумавшийся о контрасте, как сегодня работает ESLint, как сегодня работает Babel или какие-то схожие инструменты, уже, не знаю, чесались руки, блин, когда же это можно будет потрогать уже, очень-очень хочется.</p>
<p>Примерно полгода назад Rome вышел полностью в open source, то есть раньше он разрабатывался исключительно под нужды Facebook, потому что Себастьян-то работал в Facebook, сейчас он уже там не работает, он ушел в Discord вместе с еще несколькими очень известными людьми, и также внутри самого Rome произошло несколько интересных изменений. Он внезапно был переписан с Flow на TypeScript.</p>
<p><strong>Вадим Макеев:</strong> Видимо, если ты работаешь ли уже не в Facebook, то нет смысла писать на Flow, да?</p>
<p><strong>Антон Кастрицкий:</strong> Да. На сегодняшний день TypeScript определенно победил со своим комьюнити. Вместе с этим также появился открытый вебсайт, на котором превосходная документация, мне очень нравится правильный фундамент, который был вложен в Rome. Ребята отлично расписали, какие у них есть цели, как они хотят их добиваться, что они делают в первую очередь, чем они будут заниматься далее. Об этом мы тоже уже поговорим. В Rome уже внутри есть и свой линтер, чем они сам Rome и винтят, это что-то, чем вы уже можете пользоваться сегодня. Последняя версия, которая доступна в npm, она сейчас вам позволит это сделать только с помощью табов, но следующая версия, которую опубликуют, там уже будут пробелы.</p>
<p>В целом, Rome, его можно назвать такой, как opinionated tool, он очень хочет за вас сразу предоставить все дефолтные настройки, и в каком-то плане он очень похож на Prettier в том плане, что очень мало чего вам получится настроить, по крайней мере, сегодня. Как мне кажется, это большой плюс. Rome также уже умеет парсить, у него есть разные парсеры, не только для JavaScript, для TypeScript, Markdown, TOML, PCSS…</p>
<p><strong>Вадим Макеев:</strong> HTML даже. Я тут читаю issue, тут написано 8 августа: «Я внедрил довольно ужасный HTML Parcer, который не то, что несовместим со спекой, даже близко несовместим, но мы будем его потихонечку итерировать на основе этого».</p>
<p><strong>Антон Кастрицкий:</strong> Эти вещи еще не торчат наружу, то есть они могут использоваться внутри для генерации документации, поэтому не стоит пугаться. Также, мне кажется, можно сказать, что если вы будете слушать этот подкаст, как он выйдет, а сегодня у нас октябрь 2020 года, и если вы откроете GitHub и увидите, что последние пару месяцев очень мало чего произошло в Rome, то не пугайтесь, пожалуйста. Себастьян переезжал, и поэтому у него меньше времени хватало на Rome, но помимо GitHub, кстати, есть сервер в Discord, где тоже ребята общаются, бывает, я там могу задавать какие-то вопросы. Не знаю, мне кажется, у меня приняли на сегодняшний день, может, всего парочку pull requests туда, но у меня есть еще идеи, что там можно исправить и сделать лучше, о чем мы еще поговорим.</p>
<p>Могу рассказать про несколько вещей, которые меня, например, удивили. Rome написал свой собственный компилятор для того же TypeScript, сам Rome полностью написан на TypeScript. Давай еще сделаем шаг назад, в первую очередь, у Rome нет никаких зависимостей. Если ты откроешь GitHub, откроешь их package.json, посмотришь на dependencies, они будут пустые, а сверху будет комментарий о том, «смотри, как круто, можешь так же?»</p>
<p><strong>Вадим Макеев:</strong> Это какой-то спорт, или в этом есть ценность?</p>
<p><strong>Антон Кастрицкий:</strong> Мне кажется, в этом есть ценность, поскольку ты можешь полностью управлять всем, что ты используешь.</p>
<p><strong>Никита Дубко:</strong> Как сейчас Вадим на спортсменов-то набросил.</p>
<p><strong>Вадим Макеев:</strong> В смысле, спорт ради того, чтобы показать, что ты можешь, в этом смысле.</p>
<p><strong>Антон Кастрицкий:</strong> Мне кажется, это не странный флекс, а именно нарочно принятое решение, когда ты можешь полностью управлять всеми движущимися частями, на которых ты стоишь, ты можешь стоять с гораздо большей уверенностью.</p>
<p><strong>Вадим Макеев:</strong> Если сможешь встать, потому что тебе нужно сначала написать весь скелет, все мышцы и всю обвязку.</p>
<p><strong>Антон Кастрицкий:</strong> Да, то есть тут уже у нас, конечно, получится эта знаменитая проблема про бананы, гориллу и джунгли. Но, по моим ощущениям, у Rome это уже получилось, и бо́льшая часть джунглей уже выросла.</p>
<p><strong>Никита Дубко:</strong> Можно, я все-таки понабрасываю? Ты сказал, что большой плюс — это то, что он все делает сам из коробки. Меня корежит это все. Мы в свое время тут с Вадимом холиварили про Prettier, и в целом мне Prettier норм, но при этом я четко понимаю, что для каких-то проектов Prettier абсолютно не подходит, потому что, условно, тебе нужно в команде по-другому немножко, у вас есть какие-то особенности, проект большой, проект с legacy, еще что-то, и его ты не можешь взять его, на него натянуть это свежее, клевое. Я так понимаю, что Rome, он для старта каких-то проектов, наверное, норм. Но ведь новый проект — это не такая частая история. Не знаю, убеди меня, пожалуйста, почему это хорошо, и почему все говорят, что это клево, когда всего за тебя решают.</p>
<p><strong>Антон Кастрицкий:</strong> Хорошо, давай, тут есть два пункта. Первое, я не считаю, что Rome сегодня production ready, и тебе нужно завтра уже бежать переписывать и выкидывать весь свой…</p>
<p><strong>Вадим Макеев:</strong> Нет, подожди завтра мы перепишем все на Webpack, послезавтра мы перепишем на Rome.</p>
<p><strong>Антон Кастрицкий:</strong> Черт побери, ты прав. А второй пункт — это про Prettier, если ты помнишь, когда он впервые вышел, у него вообще не было никаких опций, и эти опции долго спорами, с кровью и потом доказывались, что «ребята, пока не появится такой опции, мы совсем никак не можем его использовать», и только после того, как там, не знаю, десятки, если не сотни людей отписали то, что «нам очень нравится этот инструмент, пожалуйста, пожалуйста, добавьте этот флажок, чтобы мы могли стащить его себе». Таким же подходом, мне кажется, будет все работать и с Rome. Сегодня и в Discord, и в issues ребята жаловались, что «как же так, у вас только пабы, и все, как мне жить? Я пробелы люблю, да еще и не 2, а 4». И было принято решение — да, хорошо, давайте поддержим такую штуку. Кстати, для такого рода настроек не нужно будет лезть в сам файл Rome, он при инициализации добавит вам EditorConfig, который уже очень распространен в индустрии, мне кажется, это стандарт, который используется не только в фронтенде, но и в других сферах.</p>
<p>А основная фича того, что Rome все делает сам, это отнюдь не про линтинг, как мне кажется, а, скорее, про то, как эти разные инструменты, то есть и компилятор, и бандлер, и линтер, и test runner могут взаимодействовать.</p>
<p>А расскажи мне, пожалуйста, какие проблемы у тебя были, Никита, с Prettier, чтобы я мог еще немножко раскрыть эту тему.</p>
<p><strong>Никита Дубко:</strong> Нет, давай ты лучше у Вадима спроси, у меня как раз таки с Prettier… Мы с ним подружились в итоге.</p>
<p><strong>Вадим Макеев:</strong> Я могу рассказать про свои проблемы с Prettier. Я хочу, чтобы вся эта связка — линтинг, тестирование, автофикс, еще и плагины мои в моем редакторе были, во-первых, адекватно интегрированы, а во-вторых, чтобы я мог себе накидать EditorConfig, чтобы я мог накидать себе какой-нибудь Stylelint какой-нибудь config, .stylelintrc, и чтобы инструмент по умолчанию работал, как он работает, со своими внутренними дефолтами, но как только он увидит в моем окружении конфиги, которые я для себя написал, то он, не задавая вопросов, начинал работать по моим настройками. Это как EditorConfig работает — ты кидаешь его в корень проекта, и редактор не говорит: «У меня здесь другие правила», нет, он просто видит config, говорит: «А, окей, все, меняю свои настройки». Какой-то подобной, во-первых, интеграции нескольких решений — HTML, JS, CSS, линтинг, автофикс и все остальное на основе моих настроек. Чего-то подобного я не видел, и мои попытки настроить в VS Code какую-то интеграцию, чтобы Prettier работал по ESLint-конфигу, они приводили к какой-то катастрофе, мне приходилось сносить все эти плагины, и выключать, и говорить: «Господи, нет, никогда больше». Чего-то подобного не хватает, и, как ты уже, наверное, понял, мне некомфортно в дефолтах Prettier. Для меня это тоже абсолютный стоппер. Если у меня нет возможности настроить, вкинуть свои конфиги, то я не могу пользоваться этим инструментом. Такой вот я старомодный, или такой вот я сам, человек с мнениями. Rome совсем идет по пути Prettier, или все-таки брезжит надежда на то, что конфиги будет уважать?</p>
<p><strong>Антон Кастрицкий:</strong> На сегодняшний день это прям совсем Prettier, то есть там есть даже некоторые вещи, которые ты можешь указать в EditorConfig, но они будут успешно проигнорированы Rome, что меня очень смутило, когда я его первый раз попробовал развернуть в одном из своих пет-проджектов. Но, тем не менее, я уверен, что в ближайшие те же полгодика там добавится разных флажков, которые он начнет поддерживать, как я уже говорил, уже есть смердженный pull request от Себастьяна, где поддержались те же самые пробелы, которые… конечно, комьюнити не может договориться о том, что же использовать.</p>
<p><strong>Вадим Макеев:</strong> Rome не поддерживал пробелы, только табы?</p>
<p><strong>Антон Кастрицкий:</strong> Да.</p>
<p><strong>Вадим Макеев:</strong> Вау, я уже люблю Rome. Я был любителем табов, пока не столкнулся с open source, и мне пришлось сломать свои привычки об колено.</p>
<p><strong>Антон Кастрицкий:</strong> Мы сейчас не будем об этом спорить, потому что нам тогда еще несколько выпусков придется выпустить.</p>
<p><strong>Никита Дубко:</strong> Тогда давай про точку с запятой, надо ее вставить в конце строчки или нет.</p>
<p><strong>Вадим Макеев:</strong> Что Rome думает? Ставит?</p>
<p><strong>Антон Кастрицкий:</strong> Да. Rome ставит точку с запятой.</p>
<p><strong>Никита Дубко:</strong> О, тогда мне тоже Rome нравится.</p>
<p><strong>Вадим Макеев:</strong> Все, переписываю все на Rome.</p>
<h2>Инструмент будущего</h2>
<p><strong>Антон Кастрицкий:</strong> Теперь, я думаю, можно даже попробовать рассказать, как все эти инструменты дружат между собой. Вадим, как ты сказал, подружить тот же Prettier с ESLint, по сути, элементарно, но почему-то этого не происходит. Prettier отвечает за formatting и совсем не отлавливает никакие ошибки, ESLint пытается сесть на, простите, два стула, и как форматировать ваш код с автофиксами, так и находить ошибки, неиспользуемые переменные и тому подобные вещи, что звучит почти идеально, но когда они оказываются вместе, ты добавляешь плагин от Prettier в свой и ESLint config, и он начинает уже использовать и его, но порой какие-то правила начинают коллизить между собой, и становится грустно. Ты начинаешь вспоминать, как правильно игнорить Prettier-правила для ESLint, как игнорить какие-то ESLint-правила, расстраиваешься, закрываешь ноутбук, и это.</p>
<p><strong>Вадим Макеев:</strong> Пишешь заявление, и уходишь к чертовой матери из этой профессии, потому что невозможно.</p>
<p><strong>Антон Кастрицкий:</strong> С Rome такого быть не может, потому что все эти… На самом деле, один минус, который я сегодня в нем вижу, я понимаю, что это будет пофикшено, но в нем нету флагельной системы, то есть если мне очень нравится линтер, но у меня в компании есть какие-то общие договоренности, которые сегодня зафиксированы парой десятков кастомных ESLint-правил, я не могу то же самое сегодня применить к Rome, что чуть-чуть обидно.</p>
<p><strong>Вадим Макеев:</strong> Если тебя послушать, получается, что у них на roadmap есть все.</p>
<p><strong>Антон Кастрицкий:</strong> Да. Как я уже говорил, ребята хотят захватить мир, и я искренне верю, что это прям инструмент будущего. Я еще даже не попробовал раскрыть эту тему.</p>
<p><strong>Вадим Макеев:</strong> Хорошо, давай теперь про будущее. Что мы выкинем из нашего нынешнего тулчейна, и что заменит Rome. У нас зависимости наши, dev-зависимости, что оттуда исчезнет, и что станет Rome, даже если не сегодня, а, допустим, через несколько лет, когда весь этот roadmap или большая его часть исполнится?</p>
<p><strong>Антон Кастрицкий:</strong> Мне искренне хочется верить, что мы сможем полностью забыть про линтинг, то есть мы просто сможем отдать это Rome, и все. Следующими шагами будут компиляторы и бандлеры, поскольку на них уже будут построены остальные части Rome, если я не ошибаюсь, следующее, что должно выйти после линтера, это бандлер, поскольку без него не получится запустить тот же test runner, потому что если сравнить с тем, как работает Jest, который может индивидуально все тесты запускать, Rome сначала их бандлит, а потом уже запускает балком, то есть это не значит, что они все будут синхронно выполняться, они все равно параллелятся, но ему все равно сначала нужно их сбандлить.</p>
<p>Почему я считаю, что это можно назвать так громко, как «инструменты будущего»? Мне очень нравится, что ты никогда не столкнешься с такой проблемой, что «ой, а у Rome это линт-правило не работает с этим». Rome очень красиво, когда ты пытаешься линтить, покажет тебе, где ты ошибся, как это починить, «если хочешь, я починю это за тебя», и это приятно.</p>
<p><strong>Вадим Макеев:</strong> Там хороший DX, получается или даже UI как таковой, этого, ошибки, он хороший.</p>
<p><strong>Вадим Макеев:</strong> Да, при этом эти все инструменты… Сегодня я могу говорить о том, как это устроено с самим Rome, потому что последние пару месяцев по воскресеньям два-три часа я мог в этом поковыряться, и мне было интересно. Поэтому я могу представить то, что вот, наверное, через годик, а может, через полтора примерно такого же DX можно будет ожидать в своем проекте, внедрив туда Rome. О чем я говорю? Если сравнить с тем же с ESLint, Jest плюс Webpack, или вставь сюда любой другой бандлер, есть одна прямо такая фундаментальная разница того, как это устроено.</p>
<p>Инструменты, которыми мы пользуемся сейчас, они живут сами по себе, и развиваются довольно-таки отдельно друг от друга. Тот же из ESLint использует свой собственный парсер для JS, если вы пользуетесь Webpack, он использует Acorn, но, скорее всего, вы туда все равно подключите Babel. Есть какие-то такие, как слои работы, которые не дружат между собой, для этих инструментов, не говоря уже о том, что какой-то из них может обновиться, а теперь он будет использовать что-то другое, что будет уже не совсем полностью совместимо с соседним инструментом, с которым… с ним придется работать. Когда я, например, у нас на работе обновлял Webpack с третьей на четвертую версию, я потратил довольно-таки большое количество времени, чтобы подружить все эти плагины между собой, и там были определенного рода дорогие сложности, связанные с этим.</p>
<p><strong>Вадим Макеев:</strong> Получается, что каждый из этих инструментов отдельно парсит, отдельно свое дерево создает, отдельно… Кроме того, что это неэффективно, в смысле, они это делают каждый раз заново, они не шарят это это дерево между собой, они еще это делают по-разному, соответственно, могут всякие артефакты вылезать.</p>
<p><strong>Антон Кастрицкий:</strong> Да. У меня есть некоторая проблема с тем, что они это делают отдельно друг от друга. Давай представим, что я в своем проекте что-то поделал, внес какие-то изменения, и теперь хочу их закомитить. У нас есть несколько прекомит-хуков, которые проверяют типы, которые проверяют то, что измененные файлы были написаны по нашему стайлгайду, которые проверяют то, что измененные файлы и тесты для них, а также тесты для других файлов, которые могли быть задеты моими измененными, проходят. Довольно-таки стандартный набор. Я нажимаю git commit, и мне нужно подождать какое-то время, скажем, 10 секунд, пока JS задетектит, а что же все-таки поменялось, потому что пока я был в своем редакторе, и изменял эти файлы, на лету я мог видеть ошибки от ESLint и от типов, я уже заранее мог применить автофикс или поправить что-то руками, если это не автофиксится, и все хорошо. А в случае с Jest я… Я очень ленивый человек, я не буду, конечно же, руками прогонять эти юнит-тесты, поэтому я подожду, пока JS сам все это за детектит, прогонит эти юнит-тесты, и скажет мне, что не так. Но эти 10 секунд, которые я буду ждать, пока Jest поймет, «изменились эти файлы, значит, на эти файлы есть здесь юнит-тесты, а также эти файлы еще затрагивают еще с десяток других файлов, на которые есть здесь юнит-тесты, а теперь нам нужно их всех поднять, нам нужно поднять наш пул воркеров для Jest, теперь мы можем распараллелить прогон этих юнит-тестов, давайте их запустим, теперь они гоняются»… Я хочу сделать git commit, и сразу увидеть: «Друг, у тебя здесь есть ошибка».</p>
<p><strong>Вадим Макеев:</strong> И как Rome здесь поможет?</p>
<p><strong>Антон Кастрицкий:</strong> А вот я хочу рассказать. У него есть фундаментальное отличие, это то, что у него есть свой собственный сервер. Тот пример, который я привел, если ты будешь прогонять юнит-тесты или ESLint, смотря в своем редакторе, или из консоли, это два несвязанных между собой процесса, то есть там равно есть какая-то цена на boot up этой программы, тот же ESLint, это все равно нод-процесс, его нужно поднять, нужно запарсить все эти javascript-файлики.</p>
<p>В случае с Rome это работает иначе. Тот же плагин для VS Code, тот же плагин… для Vim есть плагин, но мне он не понравился, поэтому, может быть, придется написать свой, или SLA API, на самом деле, любое API, которое будет работать с Rome, первый раз, когда ты его запустишь, у тебя потратится примерно 4-5 секунд на то, чтобы поднять сервер, LSP-сервер. Language Server Protocol — технология, которая изначально развивалась Microsoft для VS Code, но потом вышла как общий стандарт общения между серверами конкретных языков программирования под редакторы или IDE. У вас в самом начале поднимается этот сервер, и тут неважно, как именно вы его поднимите. Допустим, вы придете утром на работу, вы просто откроете свой редактор, и у вас автоматом он поднимется, когда вы самый первый файл откроете в своем проекте или вы запустите что-то из консоли. На всем протяжении вашей работы у вас в фоне будет работать этот сервер. Вы что-то меняете, каждый раз это общение с этим сервером, он уже знает про весь ваш проект, он уже знает, как что распарсить, он уже шарит кэши между кэшами, обычными файлами, банддером и так далее. Это прям значительно сокращает работу.</p>
<p>Я приведу пример. У меня есть мой pet project, Swagger Lint, это линьер для Swagger и Open API схем. Он довольно-таки маленький, и мне кажется, там тысяча строк кода, можно его измерить. Но если я в нем прогоню ESLint сегодня, то это занимает порядка 10 плюс секунд, на весь проект. Если я в нем попробую прогнать Rome с уже запущенным сервером, это занимает менее одной секунды.</p>
<p>Это великолепно. У меня просто, я не знаю, бабочки в животе, когда я вижу что-то подобное. Я очень люблю хороший DX, я всячески стараюсь прилагать усилия к тому, чтобы сделать жизнь разработчиков легче, будь это у нас в работе или в каком-то open source, поэтому, мне кажется, я был предрасположен к тому, чтобы полюбить Rome.</p>
<p><strong>Вадим Макеев:</strong> У меня есть вопрос, которым я хочу немножко закруглить уже нас. Представим, что у меня pet project, представим, что я страшно рисковый, и я хочу прямо сейчас взять, и начать использовать Rome. Это имеет хоть какой-либо смысл? Если я не энтузиаст, который хочет репортить баги и присылать pull requests, который хочет просто, чтобы заработало. Имеет смысл вообще использовать, кроме как в исследовательских целях?</p>
<p><strong>Антон Кастрицкий:</strong> Мне кажется, в этом году в этом еще нет большого смысла, за исключением тех случаев, как ты сказал, если ты энтузиаст, и хочешь просто поэкспериментировать с этим.</p>
<p><strong>Вадим Макеев:</strong> Можно ли себе на roadmap своего проекта на 2021 год добавить — поисследовать, вдруг Rome уже готов?</p>
<p><strong>Антон Кастрицкий:</strong> Я уверен, что в 2021 году уже линтер будет полностью готов прям к широкому использованию, для него наконец-то появятся открытые API для внешних плагинов, что было бы очень, очень и очень здорово. А про остальные части я еще не могу говорить, поскольку я пока даже не могу представить, в каком обозримом будущем планируется выпустить бандлер, после которого уже смогут приходить все остальные инструменты.</p>
<p><strong>Вадим Макеев:</strong> Мне кажется, линтер в этом плане — это отличная отправная точка, это что-то, что позволит тебе так опустить ножки в воду, а там, глядишь, уже и самому спрыгнуть хочется.</p>
<p><strong>Никита Дубко:</strong> С вами был 252-й выпуск подкаста «Веб-стандарты» и его постоянные ведущие Никита Дубко из «Яндекса»…</p>
<p><strong>Вадим Макеев:</strong> И Вадим Макеев из HTML Academy.</p>
<p><strong>Никита Дубко:</strong> Сегодня у нас в гостях был Антон Кастрицкий. Антон, спасибо, что просветил нас, что такое Rome, и с чем его едят.</p>
<p><strong>Антон Кастрицкий:</strong> Ребят, спасибо большое, что пригласили. Мне было очень интересно. Если вдруг у кого-то еще остались вопросы ко мне, меня можно найти в Твиттере, antonk52, а если вдруг вам по воскресеньям нечего делать, так бывает, что часиков в пять вечера я начинаю стримить на Twitch такой же хэндлер, поэтому заходите, будем ковыряться в Rome вместе.</p>
<p><strong>Никита Дубко:</strong> Все, теперь придется на тебя подписаться.</p>
<p>Слушайте нас в любом приложении для подкастов — на YouTube, во «ВКонтакте», и не забывайте ставить оценки и писать отзывы. Это помогает нам продолжать.</p>
<p>Если вам нравится, что мы делаем, поддержите нас на Patreon, все ссылки в описании. Услышимся на следующей неделе. Пока.</p>
<p><strong>Вадим Макеев:</strong> Пока.</p>
<p><strong>Антон Кастрицкий:</strong> Пока.</p>

                    ]]></description><pubDate>Wed, 28 Oct 2020 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/episode-252/</guid></item><item><title>Управление фокусом и атрибут inert</title><link>https://web-standards.ru/articles/focus-and-inert/</link><description><![CDATA[
                        <p>Множество вспомогательных технологий используют навигацию с клавиатуры для помощи в восприятии и взаимодействии с контентом. Один из способов подобной навигации — клавиша Tab. Возможно, вы знакомы с ним, если используете Tab для быстрого перемещения между полями формы, не дотягиваясь до мышки или трекпада.</p>
<p>Tab будет перемещаться по интерактивным элементам в том порядке, в котором они отображаются в DOM. Вот почему так важно, чтобы порядок исходного кода соответствовал визуальной иерархии вашего дизайна.</p>
<p>Список <a href="https://www.w3.org/TR/html52/dom.html%23interactive-content">интерактивных элементов</a>, по которым можно пройтись клавишей Tab:</p>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a">Ссылки</a> с заполненным атрибутом <code>href</code>;</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button"><code>&lt;button&gt;</code></a>;</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input"><code>&lt;input&gt;</code></a> и <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea"><code>&lt;textarea&gt;</code></a> с сопутствующим им <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label"><code>&lt;label&gt;</code></a>;</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select"><code>&lt;select&gt;</code></a>;</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details"><code>&lt;details&gt;</code></a>;</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/audio"><code>&lt;audio&gt;</code></a> и <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video"><code>&lt;video&gt;</code></a> при наличии контролов;</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/object"><code>&lt;object&gt;</code></a>, в зависимости от того, как он используется;</li>
<li>любой элемент с <code>overflow: scroll</code> в Firefox;</li>
<li>любой элемент с атрибутом <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/contenteditable"><code>contenteditable</code></a>;</li>
<li>любой элемент с установленным атрибутом <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex"><code>tabindex</code></a> (о нём чуть позже).</li>
</ul>
<p>Интерактивный элемент получает состояние фокуса, когда:</p>
<ul>
<li>На него переходят с помощью клавиши Tab,</li>
<li>Он является ссылкой и на него кликают,</li>
<li>Фокус программно задан с помощью <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLOrForeignElement/focus"><code>element.focus()</code></a> в JavaScript.</li>
</ul>
<p>Фокус похож на ховер, поскольку так мы определяем элемент, с которым хотим провзаимодействовать. Вот почему <a href="https://css-tricks.com/focusing-on-focus-styles/">визуально очевидные стили для фокуса</a> имеют огромное значение.</p>
<figure>
    <video src="https://web-standards.ru/articles/focus-and-inert/video/focus-navigation.mp4"
        controls loop muted playsinline>
    </video>
    <figcaption>
        Фокус следует по домашней странице. Начиная с логотипа, затем к товарам, услугам, вакансиям, блогу, контактам и останавливается на кнопке «Learn more».
    </figcaption>
</figure>
<h2>Управление фокусом</h2>
<p>Управлять фокусом — значит определять, какие элементы могут его получать, а какие нет.
Это один из самых сложных аспектов при разработке веб-интерфейсов, но он важен для доступности сайтов и приложений.</p>
<h3>Полезные практики управления фокусом</h3>
<p><strong>В 99% случаев вам стоит оставить порядок фокуса в покое.</strong> Не устану это повторять.</p>
<p>Состояние фокуса будет работать без дополнительных усилий, если вы используете <code>&lt;button&gt;</code> для кнопок, <code>&lt;a&gt;</code> для ссылок, <code>&lt;input&gt;</code> для полей форм и т. д.</p>
<p>В редких случаях вам может понадобиться добавить фокус к элементам, к которым обычно событие <code>focus</code> не применимо. Вот некоторые рекомендации относительно того, как реализовать это доступно и интуитивно понятно для пользователя.</p>
<h4>✅ Следует: узнать побольше про атрибут <code>tabindex</code></h4>
<p><code>tabindex</code> делает элементы фокусируемыми. В качестве значения он принимает число, в зависимости от которого меняется поведение фокуса.</p>
<h4>❌ Не следует: устанавливать <code>tabindex=&quot;0&quot;</code> там, где это не надо</h4>
<p>Нет необходимости устанавливать <code>tabindex</code> для интерактивных элементов, которые могут получать фокус с клавиатуры (например, <code>&lt;button&gt;</code>). Кроме того, вам не нужно прописывать <code>tabindex</code> неинтерактивным элементам, чтобы их могли прочесть вспомогательные устройства (на самом деле, отсутствие роли и доступного имени является ошибкой с точки зрения WCAG). На самом деле, это даже <a href="https://adrianroselli.com/2019/02/uncanny-a11y.html#Tabindex">усложняет навигацию</a> для тех, кто использует вспомогательные устройства. У таких пользователей уже есть другие, ожидаемые ими способы чтения контента.</p>
<h4>✅ Следует: устанавливать <code>tabindex=&quot;-1&quot;</code> для фокуса с помощью JavaScript</h4>
<p>Атрибут <code>tabindex=&quot;-1&quot;</code> используется для создания доступных интерактивных виджетов посредством JS. Прописав <code>tabindex=&quot;-1&quot;</code>, вы сделаете элемент фокусируемым для JS, а также по клику или тапу, но недоступным через клавишу Tab.</p>
<h4>❌ Не следует: использовать положительное значение <code>tabindex</code></h4>
<p>Это антипаттерн. Установив положительное значение <code>tabindex</code>, вы переопределите ожидаемый порядок элементов для фокуса через Tab и запутаете пользователя. Сделать так один раз — уже плохо, несколько — полный кошмар. Серьёзно, не надо так.</p>
<h4>❌ Не следует: создавать собственный порядок фокусировки</h4>
<p>Пройтись по интерактивным элементам можно только в том порядке, в котором они следуют друг за другом. Не следует создавать множество <code>tabindex</code> со значением, которое увеличивается для каждого последующего элемента, в соответствии с вашим представлением о том, как пользователь должен читать ваш сайт. Позвольте DOM сделать это за вас.</p>
<h2>Ловушка фокуса</h2>
<p>Иногда есть необходимость запретить состояние фокуса. Хороший пример запрета — это <a href="https://hiddedevries.nl/en/blog/2017-01-29-using-javascript-to-trap-focus-in-an-element">ловушка фокуса</a>, которая временно ограничивает событие фокуса у родительского элемента и у его дочерних элементов.</p>
<p>Ловушку фокуса не стоит путать с <a href="https://www.w3.org/TR/UNDERSTANDING-WCAG20/keyboard-operation-trapping.html">ловушкой клавиатуры</a>. Ловушки клавиатуры — это ситуации, когда невозможно закрыть виджет или перейти к другому компоненту из-за вложенного цикла плохо прописанной логики.</p>
<p>Практический пример того, как вы могли бы использовать ловушку фокуса — это модальное окно:</p>
<figure>
    <video src="https://web-standards.ru/articles/focus-and-inert/video/trapped-focus.mp4"
        controls loop muted playsinline>
    </video>
    <figcaption>
        Фокус проходит по странице и открывает модальное окно, чтобы продемонстрировать отмену фокуса. Далее фокус двигается в рамках контента модального окна, на кнопку «Play», кнопку «Cancel», кнопку «Purchase» и кнопку закрытия (всё это время фокус на странице заблокирован). После закрытия модального окна он возвращается к исходному положению на странице до его открытия.
    </figcaption>
</figure>
<h3>Почему это важно?</h3>
<p>Удержание фокуса в пределах модального окна помогает понять, что является его контентом, а что нет. Это аналогично тому, как зрячий человек может видеть, как окно всплывает поверх контента сайта или приложения. Это важная информация, если:</p>
<ul>
<li>У вас <a href="https://adrianroselli.com/2017/02/not-all-screen-reader-users-are-blind.html">плохое зрение или слепота</a> и вы полагаетесь на скринридер, чтобы узнать об изменениях после взаимодействия со страницей;</li>
<li>У вас плохое зрение и интерфейс увеличен с помощью экранной лупы, при которой фокусировка за пределами модального окна может сбить с толку и дезориентировать;</li>
<li>Вы перемещаетесь исключительно с помощью клавиатуры и, в случае закрытия модального окна, можете потеряться на странице при попытке вернуться обратно к нему.</li>
</ul>
<h3>Как это сделать?</h3>
<p>Надёжно управлять фокусом — дело сложное. Нужно прибегнуть к JavaScript, чтобы:</p>
<ol>
<li>Определить родительский блок для всех фокусируемых элементов на странице;</li>
<li>Определить границы содержимого ловушки фокуса (например, модального окна), включая первый и последний фокусируемый элемент;</li>
<li>Убрать как интерактивность, так и видимость всего, что может иметь фокус и находится вне рамок содержимого ловушки фокуса;</li>
<li>Переместить фокус на содержимое ловушки фокуса;</li>
<li>Обрабатывать события, сигнализирующие об уходе с выделенной области (сохранение, отмена, нажатие Esc и так далее);</li>
<li>Выйти из содержимого ловушки фокуса, когда сработает нужное событие;</li>
<li>Вернуть раннее отменённую интерактивность;</li>
<li>Переместить фокус обратно на интерактивный элемент, который вызвал ловушку фокуса.</li>
</ol>
<h3>Зачем нам это?</h3>
<p>Не стану врать: все эти действия отнимают много времени. Но всё же, управление фокусом и удобный порядок фокусировки являются частью <a href="https://www.w3.org/TR/UNDERSTANDING-WCAG20/navigation-mechanisms-focus-order.html">WCAG (руководства по обеспечению доступности веб-контента)</a>. Тема достаточна важна, чтобы считать её частью <a href="https://www.w3.org/WAI/policies/">международного правового стандарта о юзабилити</a>.</p>
<h3>Видимость</h3>
<p>Существует небольшой трюк, с помощью которого можно легко ограничить видимость и интерактивность элемента.</p>
<p><a href="https://tink.uk/understanding-screen-reader-interaction-modes/">У скринридеров есть режим взаимодействия</a>, который позволяет им проходить по странице или просматривать её с помощью виртуального курсора. А ещё виртуальный курсор позволяет пользователю скринридера обнаруживать неинтерактивные части страницы (заголовки, списки и т. д.). В отличие от использования Tab, виртуальный курсор доступен только пользователям скринридера.</p>
<p>При управлении фокусом может потребоваться ограничить возможность обнаружения содержимого виртуальным курсором. В нашем примере с модальным окном это значит предотвратить случайный выход пользователя за рамки окна при чтении контента.</p>
<p>Видимость может быть заблокирована с помощью правильного применения атрибута <a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-hidden_attribute"><code>aria-hidden=&quot;true&quot;</code></a>. А вот с интерактивностью есть один тонкий нюанс.</p>
<h2>Знакомство с <code>inert</code></h2>
<p><a href="https://whatpr.org/html/4288/interaction.html#the-inert-attribute">Атрибут <code>inert</code></a> — <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes">глобальный атрибут в HTML</a>, позволяющий легко убирать и восстанавливать видимость интерактивных элементов, а также их возможность получать состояние фокуса. Вот пример:</p>
<pre><code tabindex="0" class="language-html">&lt;body&gt;
    &lt;div
        aria-labelledby=&quot;modal-title&quot;
        class=&quot;c-modal&quot;
        id=&quot;modal&quot;
        role=&quot;dialog&quot;
        tabindex=&quot;-1&quot;&gt;
        &lt;div role=&quot;document&quot;&gt;
            &lt;h2 id=&quot;modal-title&quot;&gt;Save changes?&lt;/h2&gt;
            &lt;p&gt;The changes you have made will be lost if you do not save them.&lt;p&gt;
            &lt;button type=&quot;button&quot;&gt;Save&lt;/button&gt;
            &lt;button type=&quot;button&quot;&gt;Discard&lt;/button&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;main inert&gt;
        &lt;!-- ... --&gt;
    &lt;/main&gt;
&lt;/body&gt;
</code></pre>
<p>Я намеренно избегаю использования <code>&lt;dialog&gt;</code> для модального окна из-за <a href="https://www.scottohara.me/blog/2019/03/05/open-dialog.html">многочисленных проблем с поддержкой</a>.</p>
<p>Атрибут <code>inert</code> задан элементу <code>&lt;main&gt;</code>, следующему сразу после модального окна сохранения изменений. Теперь всё содержимое <code>&lt;main&gt;</code> не может попасть в фокус и по нему нельзя кликнуть.</p>
<p>Фокус ограничен рамками модального окна. Когда мы закрываем его, то убираем <code>inert</code> из <code>&lt;main&gt;</code>. Этот способ управления ловушкой фокуса проще, чем другие методы.</p>
<p><strong>Помните:</strong> событие закрытия модального окна может быть вызвано не только нажатием на кнопки внутри нашего модального окна из примера, но и при нажатии на Esc. А ещё некоторые модальные окна могут быть закрыты по клику на оверлей.</p>
<h3>Поддержка <code>inert</code></h3>
<p>Все последние версии <a href="https://caniuse.com/#feat=mdn-api_htmlelement_inert">Edge, Chrome и Opera поддерживают <code>inert</code></a>, если <a href="https://css-irl.info/how-to-enable-experimental-web-platform-features/">включены экспериментальные функции веб-платформ</a>. Также <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1655722">скоро появится</a> его поддержка в Firefox! Единственное исключение — Safari, как мобильный, так и десктопный.</p>
<p>Мне бы очень хотелось, чтобы Apple реализовала встроенную поддержку <code>inert</code>. Хоть <a href="https://github.com/WICG/inert">полифил и существует</a>, но у него <a href="https://www.scottohara.me/blog/2019/03/05/open-dialog.html#does-the-polyfill-help">серьёзные проблемы с поддержкой скринридерами</a>. Нехорошо!</p>
<p>Вдобавок хочется обратить внимание на <a href="https://github.com/WICG/inert#performance-and-gotchas">пометку в README полифила</a>:</p>
<blockquote>
<p>Полифил обойдётся вам дорого с точки зрения производительности, в отличие от нативного <code>inert</code>, т. к. он требует изрядного количества перемещений по DOM.</p>
</blockquote>
<p>Перемещение по DOM подразумевает, что JavaScript в полифиле требует высокой вычислительной мощности и, следовательно, в конечном итоге замедлит использование.</p>
<p>Для устройств с низким энергопотреблением, таких как <a href="https://www.pewresearch.org/fact-tank/2019/05/07/digital-divide-persists-even-as-lower-income-americans-make-gains-in-tech-adoption/ft_17-03-21_low-incometech_smartphone/">бюджетные Android-смартфоны и устаревшие ноутбуки</a>, а также тех, что выполняют сложные задачи (например, запуск нескольких приложений сразу), это может привести к зависанию или сбоям. Нативная браузерная поддержка делает такие процессы менее затратными в этом плане, так как у браузера есть доступ ко всем частям DOM, в отличие от JS.</p>
<h4>Safari</h4>
<p>Лично я разочарован тем, что Apple не поддерживает <code>inert</code>. Хотя понимаю, что добавление новых функций в браузер — невероятно сложная и трудная работа. <code>inert</code> кажется функцией, которую Apple стоило бы начать поддерживать гораздо раньше.</p>
<p>macOS и iOS всегда имели хорошую поддержку доступности, а поддержка вспомогательных технологий — важная часть <a href="https://www.apple.com/accessibility/">их маркетинговой кампании</a>. Поддержка <code>inert</code> представляется как естественное продолжение миссии Apple ввиду того, что сама эта функция смогла бы облегчить разработку доступных веб-интерфейсов в разы.</p>
<p>К сожалению, Apple держит в тайне, когда появится поддержка этого атрибута. Поэтому поддержка <code>inert</code> — всё ещё открытый вопрос.</p>
<h4>Igalia</h4>
<p><a href="https://www.igalia.com/">Igalia</a> — компания, работающая над функциями браузеров. Сейчас они проводят эксперимент, в котором каждый может проголосовать за те возможности браузеров, которые ему хотелось бы видеть. Конечно, моя статья совсем не про это, но вы можете <a href="https://www.smashingmagazine.com/2020/07/crowdfunding-web-platform-features-open-prioritization/">узнать больше в Smashing Magazine</a>.</p>
<p>Одно из предложений Igalia — это <a href="https://www.igalia.com/open-prioritization/#inertwebkit">добавление поддержки <code>inert</code> в WebKit</a>. Если вы искали возможность принять участие в улучшении доступности веба, но не знали с чего начать, я могу вам <a href="https://opencollective.com/html-inert-in-webkit-safari">её предоставить</a>. 5 $, 10 $, 25 $ — совсем необязательно жертвовать большие суммы, даже маленький вклад ценен.</p>
<p><a href="https://opencollective.com/html-inert-in-webkit-safari">Пожертвовать</a></p>
<h2>Итог</h2>
<p>Управление фокусом требует некоторых навыков и осторожности, но это того стоит. Атрибут <code>inert</code> значительно облегчит эту задачу.</p>
<p>Такие техники, как <code>inert</code>, являются примером одной из самых сильных сторон веба: <a href="https://www.w3.org/TR/html-design-principles/#pave-the-cowpaths">способности кодифицировать неожиданное поведение</a> в нечто простое и эффективное.</p>
<h3>Что почитать</h3>
<ul>
<li><a href="https://youtu.be/Pe0Ce1WtnUM">Controlling focus with tabindex</a> (A11ycasts, эпизод 4).</li>
<li><a href="https://developer.paciellogroup.com/blog/2014/08/using-the-tabindex-attribute/">Using the tabindex attribute</a> (The Paciello Group).</li>
<li><a href="https://hiddedevries.nl/en/blog/2017-01-29-using-javascript-to-trap-focus-in-an-element">Using JavaScript to trap focus in an element</a> (Хидде де Врис).</li>
</ul>
<p>Спасибо <a href="https://adrianroselli.com/">Адриану Розелли</a> и <a href="https://sarahmhigley.com/">Саре Хайли</a> за их отзывы.</p>

                    ]]></description><pubDate>Mon, 19 Oct 2020 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/focus-and-inert/</guid></item><item><title>Доступный для кого-то</title><link>https://web-standards.ru/articles/accessible-to-some/</link><description><![CDATA[
                        <p>Согласно <a href="https://webaim.org/projects/million/">ежегодному исследованию доступности от WebAims</a>, 98,1% сайтов из топ-1 000 000 содержат явные ошибки WCAG 2.0. У некоторых из этих сайтов лишь незначительные проблемы с контрастностью или, может, есть один пропущенный <code>id</code>, в то время как другие крайне недоступны. Тем не менее, это чертовски большое число, учитывая тот факт, что инструменты автоматического тестирования сообщают только <a href="https://accessibility.blog.gov.uk/2017/02/24/what-we-found-when-we-tested-tools-on-the-worlds-least-accessible-webpage/">о явных проблемах доступности</a>.</p>
<p>Только 1,9% протестированных страниц проходят автоматическое тестирование, что нормально, но это не значит, что на этих сайтах <a href="https://www.matuzo.at/blog/building-the-most-inaccessible-site-possible-with-a-perfect-lighthouse-score/">нет никаких барьеров</a>. Настоящая доступность выходит за рамки автоматических тестов и правил <a href="https://www.w3.org/WAI/standards-guidelines/wcag/">WCAG</a>.</p>
<p>Справедливо сказать, что большинство сайтов <strong>доступны только для кого-то.</strong></p>
<p>98,1% звучит плохо, но для большинства из нас это просто число, не так ли? Мне, как человеку, чья работа — помогать другим создавать доступные сайты, легче представить, какими могут быть реальные последствия этих ошибок. Чтобы помочь вам лучше понять, что значит этот высокий процент для пользователей и их повседневного опыта в интернете, я провел небольшой эксперимент.</p>
<p>Мой партнёр — архитектор, и она разожгла во мне интерес к современной архитектуре. Поэтому я создал страницу об архитектурном движении в Европе, и в рамках моего эксперимента я хотел бы, чтобы вы открыли эту страницу и прочитали её. Когда закончите, попробуйте обобщить то, что вы только что узнали, оцените свой опыт, а затем, пожалуйста, вернитесь к статье.</p>
<p><a href="https://cdpn.io/matuzo/debug/LYGxLLJ">Эксперимент.</a></p>
<p>Впечатляет, правда? Я надеюсь, что вы можете получить доступ к большей части информации, не используя DevTools. Я сделал всё возможное, чтобы сделать её максимально доступной. Если у вас возникли проблемы с пониманием того, о чём эта страница, обратите внимание на следующее:</p>
<ol>
<li>
<p>Наши разработчики (я) провели бесчисленные часы, оптимизируя доступность, и мы гордимся тем, что <a href="https://web.dev/measure/">Lighthouse</a>, <a href="https://wave.webaim.org/">Wave</a> и <a href="https://www.deque.com/axe/">Axe</a> не выдают никаких ошибок. Страница <strong>на 100% доступна.</strong></p>
</li>
<li>
<p><strong>Вы сами выбираете, как работать со страницей,</strong> но она оптимизирована для десктопных скринридеров. Вы получите лучший опыт, если не будете использовать мышь. Вы можете <a href="https://www.nvaccess.org/download/">бесплатно скачать NVDA</a>.</p>
</li>
<li>
<p><a href="https://twitter.com/TwitterSupport/status/1273332642113617921">Это ранняя версия страницы</a>, и мы изучаем способы сделать такие типы страниц доступными для всех.</p>
</li>
<li>
<p>К сожалению, <strong>мы не заложили бюджет,</strong> чтобы оптимизировать её не для скринридеров. Нет выгоды вкладывать деньги в эту часть рынка.</p>
</li>
<li>
<p><strong>Зачем вообще люди, пользующиеся мышью, хотят посетить сайт,</strong> посвящённый архитектуре экспрессионизма?!</p>
</li>
<li>
<p>Мы ориентируемся на нашу целевую аудиторию: пользователей VoiceOver на macOS, NVDA и JAWS. Но мы также рассматриваем <strong>виджеты доступности,</strong> которые мы могли бы добавить на сайт для других пользователей. Такое улучшение позволит вам кликать столько раз, сколько хотите, и оптимизировать сайт под ваши нужды. Это сделает сайт <a href="https://adrianroselli.com/2015/11/be-wary-of-add-on-accessibility.html">доступным на 120%</a>.</p>
</li>
<li>
<p>Мы известны первоклассными сайтами и постоянно исследуем новые технологии. Мы рассматриваем возможность использования <abbr title="Искусственный интеллект">ИИ</abbr> для рисования изображений по информации в атрибуте <code>alt</code>.</p>
</li>
</ol>
<p>На основании альтернативного текста <em>«Длинное прямоугольное здание. Het Schip не только выглядит как корабль — он ещё и напоминает причудливое произведение искусства. Его облик нетрадиционен, с какой стороны ни посмотри. Внешний вид отличает ярко-оранжевый кирпич, башни и архитектурные элементы необычной формы»</em> наши супер-алгоритмы <abbr title="Искусственный интеллект">ИИ</abbr> сгенерировали следующее изображение:</p>
<figure>
    <img src="https://web-standards.ru/articles/accessible-to-some/images/comparison.jpg" alt="Верхняя половина изображения: фотография Het Ship. Нижняя половина: сделанный мной в фотошопе реально плохой рисунок настоящего корабля с башней на нём, окнами и дверью. Пятилетний ребёнок нарисовал бы лучше.">
    <figcaption>
        Сравнение исходного здания (вверху) и сгенерированного нашим ИИ изображения (внизу). Мы уже близко.
    </figcaption>
</figure>
<p>Думаю, что большинство из вас не смогли получить доступ ко всей информации. Как ощущения? Особенно с моими дурацкими оправданиями. Довольно паршиво, правда? А теперь представьте, что вы получаете похожий опыт на большинстве посещаемых вами сайтов. Это не просто раздражает и приносит неудобства, но и заставляет злиться и грустить.</p>
<p>Пожалуйста, вспоминайте эти свои ощущения каждый раз, когда вы работаете над новым сайтом, страницей или компонентом. Делайте свои сайты <strong>доступными для всех,</strong> а не только для кого-то.</p>
<h2>За кулисами</h2>
<p>Вы, наверное, хотите знать, как я создал этот <em>эксклюзивный</em> сайт. Имейте в виду, что большинство следующих примеров кода (я выделил те, которые не являются плохими) иллюстрируют плохие и опасные практики. Я написал их просто для того, чтобы подчеркнуть: не используйте этот код на реальных проектах.</p>
<h3>Лейблы</h3>
<p>Трудно понять, о чём страница, если на ней нет <code>&lt;h1&gt;</code> или она не размечена должным образом.</p>
<pre><code tabindex="0" class="language-html">&lt;h1 aria-label=&quot;Архитектура экспрессионизма&quot;&gt;Заголовок документа&lt;/h1&gt;
</code></pre>
<p>Показано «Заголовок документа», но скринридер произносит «Архитектура экспрессионизма».</p>
<p>Примечание: <code>aria-label</code> не должен использоваться таким образом. Вам это не нужно, если уже есть текстовое содержимое.</p>
<p><strong>Читать дальше:</strong></p>
<ul>
<li><a href="https://webaim.org/techniques/semanticstructure/#headings">Semantic structure: regions, headings, and lists</a></li>
</ul>
<h3>Управляющие подсказки</h3>
<p>Скринридеры предоставляют пользователям инструкции, как взаимодействовать с такими элементами, как ссылки. Дополнительные подсказки, вроде «нажмите на эту ссылку, чтобы…» или «нажмите эту кнопку», излишни.</p>
<pre><code tabindex="0" class="language-html">&lt;a href=&quot;https://en.wikipedia.org/wiki/Expressionism&quot;&gt;
    экспрессионизм
    &lt;span aria-hidden=&quot;true&quot;&gt;(Кликните мышкой, чтобы открыть ссылку)&lt;/span&gt;
&lt;/a&gt;
</code></pre>
<p>Некоторые ссылки в моём эксперименте показывают дополнительную инструкцию <em>«(Кликните мышкой, чтобы открыть ссылку)»</em> рядом с текстом ссылки.</p>
<p><strong>Читать дальше:</strong></p>
<ul>
<li><a href="https://adrianroselli.com/2019/10/stop-giving-control-hints-to-screen-readers.html">Stop giving control hints to screen readers</a></li>
</ul>
<h3>Другой язык</h3>
<p>Атрибут <code>lang</code> — важный. Будучи применен к элементу <code>&lt;html&gt;</code>, он сообщает скринридеру естественный язык страницы. Скринридер может не определить язык автоматически; атрибут <code>lang</code> помогает ему выбрать правильный профиль голоса.</p>
<p>Если вы меняете язык в предложении, вы можете использовать атрибут <code>lang</code>, чтобы пометить слово или фразу. (Это хорошая практика).</p>
<pre><code tabindex="0" class="language-html">&lt;p&gt;
    В воздухе витает конкретное &lt;span lang=&quot;fr&quot;&gt;je ne sais quoi&lt;/span&gt;.
&lt;/p&gt;
</code></pre>
<p>Если вы так не сделаете, это может привести к тому, что русский голосовой профиль будет произносить немецкое предложение с русским акцентом. Это может быть трудным для понимания и путать, как переключение языка в середине абзаца.</p>
<pre><code tabindex="0" class="language-html">&lt;p class=&quot;visually-hidden&quot;&gt;
    Архитектура экспрессионизма — один из трёх доминирующих стилей Модерна…
&lt;/p&gt;
&lt;p aria-hidden=&quot;true&quot;&gt;
    Expressionismus in einer der drei dominanten Stile der modernen Architektur…
&lt;/p&gt;
</code></pre>
<p>Одно предложение на немецком, но оно спрятано от скринридеров.</p>
<p><strong>Читать дальше:</strong></p>
<ul>
<li><a href="https://adrianroselli.com/2015/01/on-use-of-lang-attribute.html">On use of the <code>lang</code> attribute</a></li>
</ul>
<h3>Изображения</h3>
<p>Отсутствующий <code>alt</code> — плохо, но <code>alt</code> с неверной или бесполезной информацией не намного лучше. Пожалуйста, не раздражайте пользователей скринридера длинной, скучной или нерелевантной информацией.</p>
<p>Атрибут <code>alt</code> часто используется как место для хранения ключевых слов SEO или информации об авторских правах. Здесь я перевернул это и поместил всю бесполезную информацию в изображение.</p>
<pre><code tabindex="0" class="language-html">&lt;figure&gt;
    &lt;img src=&quot;hetschip.jpg&quot; alt=&quot;Длинное прямоугольное здание. Het Schip не только выглядит как корабль — он ещё и напоминает причудливое произведение искусства. Его облик нетрадиционен, с какой стороны ни посмотри. Внешний вид отличает ярко-оранжевый кирпич, башни и архитектурные элементы необычной формы&quot;/&gt;
    &lt;figcaption&gt;Голландский экспрессионизм (Амстердамская школа), жилой дом Het Schip в Амстердаме, 1917–20 (Мишель де Клерк)&lt;/figcaption&gt;
&lt;/figure&gt;
</code></pre>
<p><strong>Читать дальше:</strong></p>
<ul>
<li><a href="https://www.smashingmagazine.com/2020/05/accessible-images/">Accessible images for when they matter most</a></li>
</ul>
<h3>Пустые ссылки</h3>
<p>Пользователи скринридеров часто сталкиваются с пустыми ссылками и кнопками. Скринридер может озвучить им, что у них есть ссылка, но если она без текста, они не знают, куда она их приведёт.</p>
<p>Используя <code>aria-label</code>, я сделал лейбл доступным только для скринридеров.</p>
<pre><code tabindex="0" class="language-html">&lt;a href=&quot;https://&quot; class=&quot;btn&quot; aria-label=&quot;Het Schip в Википедии&quot;&gt;&lt;/a&gt;
</code></pre>
<p><code>aria-label</code> часто используют, чтобы задать обозначения иконкам. Я не поклонник передачи информации только с помощью значков, потому что отсутствие текста повышает мою когнитивную нагрузку, требуя умственной обработки для выведения смысла.</p>
<p><strong>Читать дальше:</strong></p>
<ul>
<li><a href="https://www.nngroup.com/articles/icon-usability/">Icon usability</a></li>
<li><a href="https://jonyablonski.com/articles/2015/design-principles-for-reducing-cognitive-load/">Design principles for reducing cognitive load</a></li>
</ul>
<h3>Неправильный порядок</h3>
<p>Скринридеры произносят контент в порядке его появления в <abbr title="Document Object Model" lang="en">DOM</abbr>, сверху вниз. Изменение порядка при помощи CSS не влияет на порядок в документе. Трудно перемещаться и понимать отношения между элементами, если они появляются в непонятном порядке.</p>
<pre><code tabindex="0" class="language-html">&lt;p aria-hidden=&quot;true&quot;&gt;
    Кирпичный экспрессионизм — особый вариант северной Германии этого движения и в западной и Нидерландах в.
&lt;/p&gt;

&lt;p class=&quot;visually-hidden&quot;&gt;
    Кирпичный экспрессионизм — особый вариант этого движения в западной и северной Германии и в Нидерландах.
&lt;/p&gt;
</code></pre>
<p><strong>Читать дальше:</strong></p>
<ul>
<li><a href="https://tink.uk/flexbox-the-keyboard-navigation-disconnect/">Flexbox &amp; the keyboard navigation disconnect</a></li>
<li><a href="https://adrianroselli.com/2015/10/html-source-order-vs-css-display-order.html">HTML source order vs CSS display order</a></li>
</ul>
<h3>Скрытый контент</h3>
<p>Разработчики нашли хороший костыль для ситуаций, когда слишком сложно сделать части страницы доступными: они просто добавляют <code>aria-hidden=&quot;true&quot;</code> к элементу и скрывают его от скринридеров. Если его нет, его не нужно делать доступным. 🤙 *</p>
<pre><code tabindex="0" class="language-html">&lt;p style=&quot;filter: invert(1)&quot;&gt;
    Термин «Архитектура экспрессионизма…
&lt;/p&gt;
</code></pre>
<p>Примечание: я пробовал использовать <code>color: #ffffff</code> и <code>opacity: 0</code>, чтобы скрыть текст визуально, но Axe слишком умный. У меня получилось обмануть этот инструмент с помощью <code>filter: invert(1)</code>.</p>
<p>* Это был сарказм. Знаете, на всякий случай…</p>
<p><strong>Читать дальше:</strong></p>
<ul>
<li><a href="https://developer.paciellogroup.com/blog/2012/05/html5-accessibility-chops-hidden-and-aria-hidden/">HTML5 accessibility chops: hidden and aria-hidden</a></li>
</ul>
<h3>Нажмите здесь</h3>
<p>Скринридеры могут делать гораздо больше, чем просто зачитывать контент на странице. Они предоставляют различные способы навигации. Вы можете переходить от ориентира к ориентиру или от заголовка к заголовку, или вы можете перечислить все элементы формы или ссылки на странице. Если у этих элементов нет осмысленной метки, трудно, а иногда даже невозможно сказать, для чего они — как в параграфе, где каждая ссылка имеет одинаковую метку.</p>
<pre><code tabindex="0" class="language-html">&lt;p&gt;
    Среди важных событий в архитектуре экспрессионизма есть
    &lt;a href=&quot;/wiki/Werkbund_Exhibition_(1914)&quot; aria-label=&quot;Выставка в Веркбунде (1914)&quot;&gt;
        нажмите здесь
    &lt;/a&gt;
    , которая…
&lt;/p&gt;
</code></pre>
<p><strong>Читать дальше:</strong></p>
<ul>
<li><a href="https://webaim.org/techniques/hypertext/">Links and hypertext</a></li>
</ul>
<h3>Чрезмерная вложенность</h3>
<p>Структура страницы должна быть чистой и простой. Нет никакой пользы от 8 вложенных <code>&lt;article&gt;</code> или <code>&lt;ul&gt;</code>.</p>
<pre><code tabindex="0" class="language-html">&lt;ul aria-hidden=&quot;true&quot;&gt;
    &lt;li&gt;Стиль
        &lt;ul&gt;
            &lt;li&gt;характеризовался
                &lt;ul&gt;
                    &lt;li&gt;ранним модернистским принятием
                        &lt;ul&gt;
                            &lt;li&gt;новых материалов,
                                &lt;ul&gt;
                                    &lt;li&gt;формальными инновациями,
                                        &lt;ul&gt;
                                            &lt;li&gt;и очень необычным
                                                &lt;ul&gt;
                                                    &lt;li&gt;подходом
                                                        &lt;ul&gt;
                                                            &lt;li&gt;.&lt;/li&gt;
                                                        &lt;/ul&gt;
                                                    &lt;/li&gt;
                                                &lt;/ul&gt;
                                            &lt;/li&gt;
                                        &lt;/ul&gt;
                                    &lt;/li&gt;
                                &lt;/ul&gt;
                            &lt;/li&gt;
                        &lt;/ul&gt;
                    &lt;/li&gt;
                &lt;/ul&gt;
            &lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
&lt;/ul&gt;
</code></pre>
<p><strong>Читать дальше:</strong></p>
<ul>
<li><a href="https://www.htmhell.dev/10-section-is-no-replacement-for-div/">#10 <code>&lt;section&gt;</code> is no replacement for <code>&lt;div&gt;</code></a></li>
</ul>
<h3>Формы</h3>
<p>Создавать доступные и удобные для использования формы сложно, но есть несколько основ, которые просто необходимо соблюдать.</p>
<ul>
<li>Каждый элемент формы нуждается в лейбле.</li>
<li>Лейблы должны содержать текст, а не только картинки или иконки.</li>
<li>Лейблы и элементы формы должны быть сгруппированы так, чтобы и машины, и пользователи понимали связь между ними.</li>
</ul>
<p>Я сделал правильный лейбл «плохо» доступным только пользователям скринридеров, скрыв его визуально, и отключил клик по нему, добавив <code>pointer-events: none</code> лейблу.</p>
<pre><code tabindex="0" class="language-html">&lt;p&gt;
    &lt;input type=&quot;radio&quot; id=&quot;rating2&quot; name=&quot;rating&quot;&gt;
    &lt;label for=&quot;rating2&quot; style=&quot;pointer-events: none&quot;&gt;
        &lt;span class=&quot;vh&quot;&gt;плохо&lt;/span&gt;
        &lt;span aria-hidden=&quot;true&quot;&gt;Скруглённая белая звезда Скруглённая белая звезда&lt;/span&gt;
    &lt;/label&gt;
&lt;/p&gt;
</code></pre>
<p>И не только. Я могу порекомендовать <a href="https://www.smashingmagazine.com/printed-books/form-design-patterns/">Шаблоны проектирования форм</a> Адама Сильвера, если вы хотите научиться создавать инклюзивные формы.</p>
<h3>«Кнопки»</h3>
<p>Распространённая проблема доступности — наличие на странице элементов, которые выглядят как кнопки, но на самом деле они не являются <code>&lt;button&gt;</code>. Это обычно делает их менее доступными или недоступными для некоторых пользователей. Здесь я перевернул ситуацию: это настоящая кнопка, она выглядит как кнопка, но вы не можете нажать на неё.</p>
<pre><code tabindex="0" class="language-html">&lt;button style=&quot;pointer-events: none&quot; type=&quot;submit&quot; class=&quot;btn&quot; onclick=&quot;alert('Спасибо за вашу оценку')&quot;&gt;
    Отправить
&lt;/button&gt;
</code></pre>
<p><strong>Читать дальше:</strong></p>
<ul>
<li><a href="https://youtu.be/CZGqnp06DnI">Just use button — A11ycasts #05</a></li>
</ul>
<h2>Заключение</h2>
<p>Это был весёлый эксперимент, и я надеюсь, что вы получили удовольствие от чтения этой статьи. Лежащая в основе проблема, однако, совсем не весёлая. Просто это мой способ работы с серьёзными темами.</p>
<p>Ваш сайт, приложение или новая функциональность лишь наполовину хорошие, если только некоторые люди могут получить к ним доступ. Подумайте об инклюзивности и разнообразии в самом начале и тестируйте правильно. Соточка в Lighthouse или отсутствие ошибок в Axe не означают, что вы всё сделали правильно, это означает, что вы готовы приступать к ручному тестированию и тестированию с реальными пользователями, если это возможно. Прежде чем просто что-то создавать и запускать, подумайте о ваших пользователях и о том, как ваши решения могут влиять на них.</p>
<p>Вот доступная версия <a href="https://cdpn.io/matuzo/debug/qBbrMgW?editors=1100">страницы про архитектуру экспрессионизма</a>.</p>
<p>Спасибо, <a href="https://cariefisher.com/">Кэри Фишер</a>, за помощь со статьёй!</p>

                    ]]></description><pubDate>Thu, 30 Jul 2020 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/accessible-to-some/</guid></item><item><title>Объяснение ключевых слов initial, inherit, unset и revert в CSS</title><link>https://web-standards.ru/articles/inherit-initial-unset-revert/</link><description><![CDATA[
                        <p>В CSS есть несколько ключевых слов для задания значений свойств. Я собираюсь поговорить о нескольких из них: <code>initial</code>, <code>inherit</code> и об относительно новых — <code>unset</code> и <code>revert</code>.</p>
<p><em>В оригинальной статье в заголовке и во вступлении говорится только о трёх ключевых словах, но дальше в тексте идёт обсуждение четвёртого — <code>revert</code>. Мы решили добавить его во вступление для удобства читателя — прим. переводчика.)</em></p>
<p>Хотя большинство веб-разработчиков сталкивалось с ними, весьма вероятно, что многие, даже самые опытные, не до конца их понимают.</p>
<p>Долгое время я знал об этих ключевых словах только то, что они используются для сброса стилей в CSS. Но если все эти ключевые слова являются своего рода сбросом, то почему их так много? Какие именно различия между ними? Я решил глубже изучить эти ключевые слова, чтобы раз и навсегда разобраться, что отличает их друг от друга.</p>
<h2>Базовые стили для веба</h2>
<p>Прежде чем мы начнем разбираться с ключевыми словами, важно понять, откуда берутся базовые стили в браузере.</p>
<h3>Начальное значение для каждого свойства в CSS</h3>
<p>Каждое свойство в CSS имеет начальное (<code>initial</code>) значение. Оно никак не связано с типом HTML-элемента, к которому применяется.</p>
<p>Пример начального значения из MDN:</p>
<figure>
    <img src="https://web-standards.ru/articles/inherit-initial-unset-revert/images/line-height.png" alt="Пример указания значения `initial` на MDN.">
    <figcaption>Начальное значение свойства <code>line-height</code> — это <code>normal</code>.</figcaption>
</figure>
<h3>Браузерные стили</h3>
<p>После применения начальных стилей для всех CSS-свойств браузер загружает свои стили. Эти стили не имеют ничего общего с базовыми начальными значениями CSS-свойств.</p>
<p>Пример браузерных стилей:</p>
<figure>
    <img src="https://web-standards.ru/articles/inherit-initial-unset-revert/images/h1.png" alt="Пример браузерных стилей в инспекторе браузера.">
    <figcaption>Стили браузера Chrome, применяемые к элементу <code>&lt;h1&gt;</code>.</figcaption>
</figure>
<p>У HTML-элементов нет начальных значений для стилей! Базовые стили HTML-элемента, такого как <code>&lt;h1&gt;</code>, например, предоставляются стилями браузера, а не начальными значениями CSS-свойств.</p>
<p>Теперь начнем говорить о ключевых словах.</p>
<h2>Ключевое слово <code>inherit</code></h2>
<p>Ключевое слово <code>inherit</code> сообщает браузеру, что значение свойства нужно найти у ближайшего родительского элемента и унаследовать его для текущего элемента. Если у ближайшего родителя также задано значение <code>inherit</code>, то браузер продолжит подниматься по DOM, пока не найдет какое-нибудь значение. Если значения нет, то браузер применит свои стили, а если и браузерных стилей нет, то тогда будет применено значение <code>initial</code>.</p>
<iframe src="https://codepen.io/elad2412/embed/preview/hdypx" title="Пример работы значения inherit на CodePen."></iframe>
<h2>Ключевое слово <code>initial</code></h2>
<p>Чтобы понять ключевое слово <code>initial</code>, мы должны помнить важный факт: у каждого свойства в CSS есть значение по умолчанию, которое не имеет ничего общего со значениями, которые устанавливаются браузером. Браузерные стили — это стили, которые применяются браузером к конкретным HTML-элементам. Мы часто думаем, что они автоматически приходят вместе с HTML, но это не так.</p>
<p>Ключевое слово <code>initial</code> говорит браузеру использовать значение по умолчанию для заданного CSS-свойства. Например, для свойства <code>color</code> значение <code>initial</code> всегда будет <code>black</code>.</p>
<p>Такое поведение может очень запутывать, потому что, как мы и говорили ранее, значение по умолчанию для CSS-свойства не всегда совпадает со значением, которое браузер задает конкретному элементу. Например, <code>initial</code>-значение для свойства <code>display</code> равно <code>inline</code> для всех элементов. Поэтому, если для элемента <code>&lt;div&gt;</code> будет задано свойство <code>display</code> со значением <code>initial</code>, то свойство будет вычислено как <code>inline</code>, а не <code>block</code>, как в стилях браузера.</p>
<p>Пример:</p>
<pre><code tabindex="0" class="language-css">div.box {
    background-color: red;
    display: initial; /* примет значение `inline`, а не `block` */
}
</code></pre>
<p><a href="https://codepen.io/elad2412/pen/KKKqMyZ">Пример на CodePen значения <code>initial</code> для свойства <code>display</code> элемента <code>&lt;div&gt;</code></a>.</p>
<figure>
    <img src="https://web-standards.ru/articles/inherit-initial-unset-revert/images/display.png" alt="">
    <figcaption>
        <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/display">Информация об <code>initial</code>-значении свойства <code>display</code> на MDN</a>.
    </figcaption>
</figure>
<h2>Ключевое слово <code>unset</code></h2>
<p>Ключевое слово <code>unset</code> является уникальным и работает в зависимости от типа свойства. В CSS есть два типа свойств:</p>
<h3>1. Наследуемые свойства</h3>
<p>Свойства, которые затрагивают дочерние элементы. <strong>Все свойства, которые влияют на текст, имеют такое естественное поведение.</strong> Например, если мы зададим <code>font-size</code> элементу <code>&lt;html&gt;</code>, то он будет применяться ко всем дочерним элементам, пока вы не зададите другой <code>font-size</code> какому-нибудь из них.</p>
<figure>
    <img src="https://web-standards.ru/articles/inherit-initial-unset-revert/images/font-size.png" alt="">
    <figcaption>
        <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/font-size">Информация о наследуемости свойства font-size на MDN</a>.
    </figcaption>
</figure>
<h3>2. Ненаследуемые свойства</h3>
<p>Все остальные свойства, которые влияют только на элемент, для которого они заданы. <strong>Это все свойства, которые не относятся к оформлению текста</strong>. Например, если вы зададите border на родительском элементе, то он не будет задан на дочернем.</p>
<figure>
    <img src="https://web-standards.ru/articles/inherit-initial-unset-revert/images/border.png" alt="">
    <figcaption>
        <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/border">Информация о наследуемости свойства <code>border</code> на MDN</a>.
    </figcaption>
</figure>
<p>Ключевое слово <code>unset</code> работает так же, как и <code>inherit</code> для наследуемых свойств. Например, для текстового свойства <code>color</code> оно будет работать как значение <code>inherit</code>, то есть будет искать ближайший родительский элемент с заданием нужного свойства, а если он не будет найден, то применится браузерное значение, а если и браузерных стилей нет, то применится значение <code>initial</code>.</p>
<p>Для ненаследуемых свойств <code>unset</code> ведет себя как <code>initial</code>, то есть применится значение по умолчанию. Например, для свойства <code>border-color</code> оно будет работать как <code>initial</code>.</p>
<pre><code tabindex="0" class="language-css">.some-class {
    color: unset; /* будет равно `inherit` */
    display: unset; /* будет равно `initial` */
}
</code></pre>
<h3>Зачем использовать <code>unset</code>, если оно работает так же, как <code>inherit</code> и <code>initial</code>?</h3>
<p>Если <code>unset</code> ведет себя как <code>inherit</code> и <code>initial</code>, то зачем оно может понадобиться? Если мы сбрасываем только одно свойство, то <code>unset</code> не нужен. Вместо него мы можем просто использовать <code>inherit</code> или <code>initial</code>. Но сейчас у нас есть свойство <code>all</code>, которое дает нам новую возможность — сбросить наследуемые и ненаследуемые свойства одновременно.</p>
<p>Таким образом, вам не нужно сбрасывать свойства по отдельности. Применение ключевого слова <code>unset</code> к свойству <code>all</code> приведет к сбросу всех наследуемых свойств к значению <code>inherit</code> и всех ненаследуемых свойств — к значению <code>initial</code>.</p>
<p><strong>Это единственная причина существования нового ключевого слова <code>unset</code>! В противном случае мы могли бы вместо этого использовать <code>inherit</code> и <code>initial</code>.</strong></p>
<p>Вместо сброса свойств по отдельности, к примеру:</p>
<pre><code tabindex="0" class="language-css">/* Плохо */
.common-content {
    font-size: inherit;
    font-weight: inherit;
    border-width: initial;
    background-color: initial;
}
</code></pre>
<p>Мы можем использовать новое свойство <code>all</code> со значением <code>unset</code>, которое повлияет на все существующие свойства, например:</p>
<pre><code tabindex="0" class="language-css">/* Хорошо */
.common-content {
    all: unset;
}
</code></pre>
<p>Я создал небольшой пример для демонстрации того, как свойства ведут себя, когда используется свойство <code>all</code> со значением <code>unset</code>. Некоторые ведут себя так, как будто к ним применено значение <code>inherit</code>, а некоторые так, как будто к ним применено значение <code>initial</code>. <a href="https://codepen.io/elad2412/pen/QWWgKbB">Пример на Codepen использования <code>all: unset</code></a>.</p>
<h2>Ключевое слово <code>revert</code></h2>
<p>Но что, если мы хотим сбросить значение свойства до первоначально заданных браузером значений, а не до значений по умолчанию? Например, вернуть значение свойства <code>display</code> элемента <code>&lt;div&gt;</code> к значению <code>block</code> (это стили браузера), а не к значению <code>inline</code> (это базовые стили CSS).</p>
<figure>
    <img src="https://web-standards.ru/articles/inherit-initial-unset-revert/images/div.png" alt="">
    <figcaption>Браузерные стили для тега div</figcaption>
</figure>
<p>Для этих целей мы скоро получим новое ключевое слово в CSS: <code>revert</code>. Оно очень похоже на <code>unset</code>, единственное отличие состоит в том, что оно предпочитает стили браузера базовым значениям свойств CSS. Например:</p>
<pre><code tabindex="0" class="language-css">div {
    display: revert; /* = block */
}

h1 {
    font-weight: revert; /* = bold */
    font-size: revert; /* = 2em */
}
</code></pre>
<p>Таким образом, если мы хотим сбросить все стили HTML-элемента до базовых стилей браузера, мы можем сделать это так:</p>
<pre><code tabindex="0" class="language-css">/* Хорошо */
.common-content {
    all: revert;
}
</code></pre>
<p>Соответственно, <code>revert</code> дает гораздо больше возможностей, чем <code>unset</code>. Правда, на данный момент <code>revert</code> работает только в Firefox и Safari. <em>(В Chrome работает с версии 84 — прим. переводчика.)</em></p>
<h2>Заключение</h2>
<p>На этом всё. Надеюсь, вам понравилась эта статья, и вы чему-то научились из моего опыта.</p>
<h2>Видео доклада по теме</h2>
<p>Я сделал короткий доклад на эту тему, смотрите видео целиком на YouTube:</p>
<figure>
    <iframe src="https://www.youtube.com/embed/8IaKXJHRSXc" allowfullscreen></iframe>
    <figcaption>
        Мой доклад <a href="https://youtu.be/8IaKXJHRSXc">«Ключевые слова в CSS, которые никто не понимает»</a>.
    </figcaption>
</figure>

                    ]]></description><pubDate>Tue, 21 Jul 2020 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/inherit-initial-unset-revert/</guid></item><item><title>Советы по сложным CSS-иллюстрациям</title><link>https://web-standards.ru/articles/complex-css-illustrations/</link><description><![CDATA[
                        <p>Если бы вы меня спросили, какой вопрос мне чаще всего задают про фронтенд-разработку, я бы ответил: <em>«Как прокачаться в CSS?».</em> Этот вопрос обычно озвучивают после того, как я делюсь сделанными мной CSS-иллюстрациями. Это то, что <a href="https://codepen.io/jh3y/pens/">я люблю делать на CodePen</a>.</p>
<p>Для многих CSS — неукротимый мифический зверь. <a href="https://twitter.com/chriscoyier/status/1248088743002677248">Этот твит</a> Криса заставил меня улыбнуться, потому что, несмотря на всю иронию, в нём много правды. Тем не менее, что, если я скажу вам, что вам нужно всего несколько свойств и техник, чтобы создать всё, что вы хотели? Правда в том, что это действительно так.</p>
<p>Я давно хотел написать подобную статью, но это сложная тема, потому что существует так много возможностей и так много техник, что, как правило, одно и то же можно сделать несколькими способами. То же самое относится и к CSS-иллюстрациям. Нет правильного или неправильного способа. Мы все рисуем на одном и том же холсте. Просто есть много разных инструментов, чтобы поместить все эти пиксели на страницу.</p>
<p>И хотя не существует универсального подхода для CSS-иллюстраций, я могу предложить несколько техник, которые помогут вам в вашем путешествии.</p>
<iframe src="https://codepen.io/jh3y/embed/preview/mLaXRe" title="Комод на чистом CSS при помощи details и summary"></iframe>
<h2>Время и практика</h2>
<p>CSS-иллюстрация требует много времени и практики. Чем точнее вы хотите быть и чем сложнее иллюстрация, тем больше времени это займёт. Самая затратная часть — не решение, какие свойства использовать и как, а отшлифовка результата до состояния, чтобы всё выглядело правильно. <strong>Будьте готовы изучить <a href="https://developers.google.com/web/tools/chrome-devtools/css">инспектор стилей</a> в вашем браузере!</strong> Я также рекомендую попробовать <a href="https://chrome.google.com/webstore/detail/visbug/cdockenadnadldjbbgcallicgledbeoc?hl=en">VisBug</a>, если вы этого ещё не сделали.</p>
<p>Два фантастических CSS-художника — Бен Эванс и Диана Смит. Оба недавно рассказывали о затратах времени на CSS-иллюстрации.</p>
<figure>
    <img src="https://web-standards.ru/articles/complex-css-illustrations/images/painting.jpg" alt="Изображение женщины, пристально смотрящей вверх и прижимающей руки к груди, напоминающее классическую картину.">
    <figcaption>
        Создание PureCSS Gaze заняло у Дианы два долгих уикенда. Она рассказывает о некоторых своих техниках <a href="https://www.vice.com/en_us/article/9kgx7p/painting-made-with-code-html-pure-css-browser-art-diana-smith">здесь</a>. «Если у вас есть время, терпение и запал, то это, безусловно, возможно», — говорит она.
    </figcaption>
</figure>
<p>Я запостил мем про чашку, и в <a href="https://twitter.com/ivorjetski/status/1259893362129350658">ответе Бена</a> всё было прекрасно:</p>
<blockquote>
<p>Когда в первый раз увидел твит, мне захотелось сделать это на CSS, но потом подумал, что мой ответ займет около месяца.</p>
</blockquote>
<p>Это требует времени!</p>
<figure>
    <img src="https://web-standards.ru/articles/complex-css-illustrations/images/tweets/cup.jpg" alt="Фотореалистичное изображение белой чашки на белом столе.">
    <figcaption>
        <a href="https://twitter.com/jh3yy/status/1259487385554911233">CSS-иллюстрация</a>.
    </figcaption>
</figure>
<h2>Трассировка вполне приемлема</h2>
<p>Зачастую мы уже имеем представление о том, что хотим нарисовать. В конце концов, эта статья не о дизайне. Речь идет о том, чтобы взять изображение и отрендерить его с помощью DOM и CSS. Я уверен, что эта техника существует с незапамятных времен. Но это то, чем я делюсь последние несколько месяцев.</p>
<ul>
<li>Найдите или создайте изображение, которое вы хотите нарисовать.</li>
<li>Вставьте его в ваш HTML при помощи <code>&lt;img&gt;</code>.</li>
<li>Расположите его так, чтобы оно находилось прямо под вашей иллюстрацией.</li>
<li>Уменьшите прозрачность изображения, чтобы его было видно, но не слишком.</li>
<li>Трассируйте его с помощью DOM.</li>
</ul>
<p>К моему удивлению, про эту технику <a href="https://twitter.com/therealpaulcook/status/1256283143587405824">не все знают</a>. Но она бесценна для создания точных CSS-иллюстраций.</p>
<p>Следите за трюком в действии:</p>
<figure>
    <video src="https://web-standards.ru/articles/complex-css-illustrations/images/tweets/timelapse-1.mp4" controls></video>
    <figcaption>
        Вот таймлапс создания логотипа <a href="https://twitter.com/eggheadio">@eggheadio</a> на CSS 😎
        Поигрался с тенями и применил clip-path поверх 🛠️
        <a href="https://twitter.com/jh3yy/status/1256281143244136448">@jh3yy</a>
    </figcaption>
</figure>
<p>И попробуйте его здесь:</p>
<iframe src="https://codepen.io/jh3y/embed/preview/OJMNVZR" title="Переключатель CSS-трейсинга PS5"></iframe>
<h2>Обращайте внимание на отзывчивость</h2>
<p>Если вы начнёте использовать только две техники из этой статьи, пусть это будут трассировка из раздела выше и эта.</p>
<p>Есть немало фантастических примеров CSS-иллюстраций. Но одна из неприятных особенностей некоторых из них в том, что они не стилизованы — или даже не видны — на маленьких экранах. Мы живём в эпоху, когда очень важны первые впечатления от технологий. Рассмотрим на примере клавиатуры, нарисованной на CSS. Кто-то находит вашу работу, открывает её на своём смартфоне, а его встречает только половина иллюстрации или её небольшая часть. Они скорее всего упустили самые крутые части демо!</p>
<p><strong>Вот мой приём:</strong> использовать единицы вьюпорта для иллюстраций и создавать свою собственную масштабированную единицу измерения.</p>
<p>Для размеров и позиционирования вы можете использовать либо масштабированную единицу измерения, либо проценты. Это особенно полезно, когда вам нужно использовать <code>box-shadow</code>, потому что это свойство работает с единицами вьюпорта, но не работает с процентами.</p>
<p>Рассмотрим CSS-логотип <a href="https://egghead.io/">egghead.io</a>, который я создал ранее. Я нашел изображение, которое хотел использовать, и добавил его в DOM тегом <code>&lt;img&gt;</code>.</p>
<pre><code tabindex="0" class="language-html">&lt;img src=&quot;egghead.png&quot; alt=&quot;&quot;&gt;
</code></pre>
<pre><code tabindex="0" class="language-css">img {
    height: 50vmin;
    left: 50%;
    opacity: 0.25;
    position: fixed;
    top: 50%;
    transform: translate(-50%, -50%);
}
</code></pre>
<p>Высота <code>50vmin</code> — желаемый размер CSS-иллюстрации. Уменьшение прозрачности позволяет нам «трассировать» иллюстрацию по мере прогресса.</p>
<p>Теперь создадим нашу масштабированную единицу измерения.</p>
<pre><code tabindex="0" class="language-css">/**
* размеры изображения 742 x 769
* ширина — 742
* высота — 769
* желаемый размер — 50vmin
*/
:root {
    --size: 50;
    --unit: calc((var(--size) / 769) * 1vmin);
}
</code></pre>
<p>Зная размеры изображения, мы можем создать единицу измерения, которая будет масштабироваться вместе с нашим изображением. Мы знаем, что высота — это самая большая сторона, поэтому используем её как основу для создания дробной единицы.</p>
<p>Получим что-то вроде такого:</p>
<pre><code tabindex="0" class="language-css">--unit: 0.06501950585vmin;
</code></pre>
<p>Выглядит так себе, но поверьте, это нормально. Мы можем использовать её для задания размеров контейнера для иллюстации, используя <code>calc()</code>.</p>
<pre><code tabindex="0" class="language-css">.egg {
    height: calc(769 * var(--unit));
    position: relative;
    width: calc(742 * var(--unit));
    z-index: 2;
}
</code></pre>
<p>Если мы используем проценты или наше новое кастомное свойство <code>-⁠-⁠unit</code> для стилизации элементов внутри контейнера нашей CSS-иллюстрации, мы получим отзывчивую CSS-иллюстрацию… И всё это требует всего несколько строчек математики с использованием CSS-переменных!</p>
<p>Поизменяйте размеры этого демо, чтобы увидеть, что все пропорции сохраняются, ограничиваясь по размеру до <code>50vmin</code>.</p>
<iframe src="https://codepen.io/jh3y/embed/preview/rNOzYJZ" title="Отзывчивое CSS-яйцо"></iframe>
<h2>Семь раз отмерь, один отрежь</h2>
<p>Следующий совет: замеряйте. Чёрт, вы даже можете взять рулетку, если работаете с физическим объектом!</p>
<iframe src="https://codepen.io/jh3y/embed/preview/BaNGKPw" title="Домашний кинотеатр на чистом CSS"></iframe>
<p>Это может показаться немного странным, но я замерял эту сцену. Это телевизор с полками, которые стоят в моей гостиной. Все замеры сделаны в сантиметрах. Я использовал их для получения отзывчивой единицы измерения, основанной на фактической высоте телевизора. Благодаря кастомным свойствам мы можем дать имя этому числу и всем остальным. Это позволит легко запомнить, для чего оно предназначено.</p>
<pre><code tabindex="0" class="language-css">:root {
    --light-switch: 15;
    --light-switch-border: 10;
    --light-switch-top: 15;
    --light-switch-bottom: 25;
    --tv-bezel: 15;
    --tv-unit-bezel: 4;
    --desired-height: 25vmin;
    --one-cm: calc(var(--desired-height) / var(--tv-height));
    --tv-width: 158.1;
    --tv-height: 89.4;
    --unit-height: 42;
    --unit-width: 180;
    --unit-top: 78.7;
    --tv-bottom: 114.3;
    --scaled-tv-width: calc(var(--tv-width) * var(--one-cm));
    --scaled-tv-height: calc(var(--tv-height) * var(--one-cm));
    --scaled-unit-width: calc(var(--unit-width) * var(--one-cm));
    --scaled-unit-height: calc(var(--unit-height) * var(--one-cm));
}
</code></pre>
<p>Как только мы вычислили переменную, можем использовать её везде. Я знаю, что мой телевизор 158,1 см в ширину и 89,4 см в высоту — подсмотрел в инструкции. Но в моей CSS-иллюстрации он всегда будет ограничен <code>25vmin</code>.</p>
<h2>Используйте абсолютное позиционирование для всего</h2>
<p>Этот совет позволит сэкономить на нажатиях клавиш. Чаще всего вы будете пытаться позиционировать элементы абсолютно. Помогите себе и положите это правило куда-нибудь.</p>
<pre><code tabindex="0" class="language-css">/* Имена классов могут отличаться */
.css-illustration *,
.css-illustration *:after,
.css-illustration *:before,
.css-illustration:after,
.css-illustration:before {
    box-sizing: border-box;
    position: absolute;
}
</code></pre>
<p>Ваша клавиатура скажет вам спасибо!</p>
<p>Или поиграйте в этой песочнице:</p>
<iframe src="https://codepen.io/jh3y/embed/preview/pogyJbw" title="Песочница для position: absolute"></iframe>
<h2>Придерживайтесь подхода</h2>
<p>Это, безусловно, самая сложная вещь. Какой подход вы используете к CSS-иллюстрации? С чего начинаете? Должны ли вы начать с внешней части и идти внутрь картинки? Это работает не так хорошо, как хотелось бы.</p>
<p>Скорее всего, вы попробуете несколько подходов и найдёте лучший способ решить задачу. Вы, конечно, будете переделывать некоторые вещи, но чем больше вы будете практиковаться, тем чаще будете замечать шаблонные вещи и разработаете подход, который лучше всего подходит для вас.</p>
<p>Я склонен соотнести свой подход с тем, как если бы вы создавали векторное изображение, в котором иллюстрация состоит из слоёв. Разделите её и нарисуйте на бумаге, если нужно. Но начните снизу и прорабатывайте свой путь наверх. Это обычно означает более крупные фигуры сначала, а более мелкие детали — позже. Вы всегда можете повозиться с контекстом наложения, когда вам нужно подвигать элементы.</p>
<h2>Сохраняйте жёсткую структуру ваших стилей</h2>
<p>Это приводит нас к структуре. Старайтесь избегать плоской DOM-структуры для вашей иллюстрации. Сохранение вещей атомарными позволяет проще двигать части вашей иллюстрации. А ещё так гораздо проще показывать или прятать части иллюстрации или даже потом анимировать их. Рассмотрим <a href="https://codepen.io/jh3y/pen/yLYXVJa">демо CSS Snorlax</a>. Руки, ноги, голова и прочие части являются отдельными элементами. Это сделало анимирование руки гораздо проще, чем если бы я пытался держать всё вместе, так как я смог просто применить анимацию к классу <code>.snorlax__arm-left</code>.</p>
<iframe src="https://codepen.io/jh3y/embed/preview/yLYXVJa" title="Отзывчивый Снорлакс на чистом CSS"></iframe>
<p>Вот ускоренная запись того, как я делал это демо:</p>
<figure>
    <video src="https://web-standards.ru/articles/complex-css-illustrations/images/tweets/timelapse-2.mp4" controls></video>
    <figcaption>
        Попытался собрать таймлапс создания CSS Snorlax, которого мы делали вчера ночью.
        Забавно пересматривать!
        <a href="https://twitter.com/jh3yy/status/1255207715137339396">@jh3yy</a>
    </figcaption>
</figure>
<h2>Работа с неудобными фигурами</h2>
<p>Есть <a href="https://css-tricks.com/the-shapes-of-css/">довольно неплохая статья</a> на CSS-Tricks про создание фигур с помощью CSS. Но как насчёт более «неуклюжих» фигур, таких как длинная кривая или даже обводка? В таких случаях нужно мыслить нестандартно. Такие свойства, как <code>overflow</code>, <code>border-radius</code> и <code>clip-path</code> — отличные помощники.</p>
<p>Посмотрим на демо CSS Jigglypuff. Нажмите на чекбокс.</p>
<iframe src="https://codepen.io/jh3y/embed/preview/gOaVJMB" title="Джиглипафф с переключателем тела"></iframe>
<p>Вот ключ к созданию искривлённых фигур! У нас есть элемент, который гораздо больше тела с примененным <code>border-radius</code>. Тогда мы задаем <code>overflow: hidden</code> телу, чтобы обрезать этот лишний кусок.</p>
<p>Как мы можем сделать обводку? Это уже немного сложнее. Но мне нравится следующий приём: используйте прозрачный элемент с толстой рамкой. Затем примените <code>border-radius</code> и обрежьте лишнее, если нужно.</p>
<iframe src="https://codepen.io/jh3y/embed/preview/ZEbgNKO" title="Создание обводки на CSS"></iframe>
<p>Если вы нажмёте на чекбокс, покажется элемент, который мы используем для обводки этого угла. Другой приём может заключаться в том, чтобы наложить поверх круг, совпадающий по цвету с фоном. Это нормально, пока нам не понадобится изменить цвет фона. Хорошо, если у вас есть переменная или что-то вроде для этого цвета. Но такой подход слегка сложнее поддерживать.</p>
<h2><code>clip-path</code> — ваш друг</h2>
<p>В последнем демо вы могли заметить парочку интересных CSS-свойств, например, <code>clip-path</code>. Вам почти наверняка понадобится <a href="https://css-tricks.com/almanac/properties/c/clip-path/"><code>clip-path</code></a>, если вы хотите создавать сложные CSS-фигуры. Его особенно удобно использовать для обрезания краёв элементов, когда применение <code>overflow</code> на родителе не помогает.</p>
<p>Вот небольшое демо, которое я собрал некоторое время назад, чтобы показать различные возможности <code>clip-path</code>.</p>
<iframe src="https://codepen.io/jh3y/embed/preview/XqVQqa" title="Генератор clip-path на React и CSS-переменных"></iframe>
<p>Есть ещё такое демо, которое берёт идеи из статьи <a href="https://css-tricks.com/the-shapes-of-css/">Shapes of CSS</a> и воссоздаёт фигуры при помощи <code>clip-path</code>.</p>
<iframe src="https://codepen.io/jh3y/embed/preview/gOpLBEa" title="Обрезания CSS"></iframe>
<h2><code>border-radius</code> — ещё один друг</h2>
<p>Вам точно понадобится <a href="https://css-tricks.com/almanac/properties/b/border-radius/"><code>border-radius</code></a> для создания кривых. Один необычный приём заключается в использовании синтаксиса с двумя значениями. Это позволяет задавать горизонтальный и вертикальный радиус для каждого угла.</p>
<p>Поиграйте с этим демо, чтобы по-настоящему оценить мощь <code>border-radius</code>. Я призываю использовать проценты для значений, чтобы сохранять отзывчивость элементов.</p>
<iframe src="https://codepen.io/jh3y/embed/preview/XWmvwYg" title="Песочница для border-radius"></iframe>
<h2>Техники создания теней</h2>
<p>У вас уже есть все формы, всё хорошо продумано, правильные цвета везде, где нужно… Но что-то всё ещё выглядит не так. Скорее всего, это отсутствие теней.</p>
<p>Тени добавляют глубину и создают реалистичные ощущения. Посмотрим на это воссоздание иллюстрации Галь Шир. Галь великолепно использует тени и градиенты для создания красивой иллюстрации. Я подумал, что было бы интересно воссоздать её и добавить переключатель, который включает и выключает затенение, чтобы оценить разницу, которое оно создаёт.</p>
<iframe src="https://codepen.io/jh3y/embed/preview/OJMNyVg" title="CSS-котелок с переключателем затенения"></iframe>
<p>Эффекты затенения часто создаются при помощи комбинации <code>box-shadow</code> и <code>background-image</code>.</p>
<p>Ключевая особенность этих свойств в том, что мы можем складывать их в список, разделённый запятой. Например, у котла в этом демо есть список градиентов, которые используются по всему его телу.</p>
<pre><code tabindex="0" class="language-css">.cauldron {
    background:
        radial-gradient(25% 25% at 25% 55%, var(--rim-color), transparent),
        radial-gradient(100% 100% at -2% 50%, transparent, transparent 92%, var(--cauldron-color)),
        radial-gradient(100% 100% at -5% 50%, transparent, transparent 80%, var(--darkness)),
        linear-gradient(310deg, var(--inner-rim-color) 25%, transparent), var(--cauldron-color);
}
</code></pre>
<p>Обратите внимание, что здесь используются <code>radial-gradient()</code> и <code>linear-gradient()</code>, и они не всегда содержат идеально круглые числовые значения. Опять же, такие числа нормальны. На самом деле вы потратите много времени, настраивая и подкручивая разные значения в инспекторе стилей.</p>
<p>То же самое применимо и к <a href="https://css-tricks.com/snippets/css/css-box-shadow/"><code>box-shadow</code></a>. Однако с ним мы также можем использовать значение <code>inset</code>, чтобы создавать хитрые границы и дополнительную глубину.</p>
<pre><code tabindex="0" class="language-css">.cauldron__opening {
    box-shadow:
        0 0px calc(var(--size) * 0.05px) calc(var(--size) * 0.005px) var(--rim-color) inset,
        0 calc(var(--size) * 0.025px) 0 calc(var(--size) * 0.025px) var(--inner-rim-color) inset,
        0 10px 20px 0px var(--darkness), 0 10px 20px -10px var(--inner-rim-color);
}
</code></pre>
<p>Конечно, бывают случаи, когда куда разумнее использовать <a href="https://css-tricks.com/almanac/properties/f/filter/"><code>filter: drop-shadow()</code></a>, чтобы получить желаемый эффект.</p>
<p>Сайт Линн Фишер <a href="https://a.singlediv.com/">a.singlediv.com</a> — яркий пример этих свойств в действии. Потыкайте в разные элементы на этом сайте и исследуйте некоторые из иллюстраций, чтобы найти отличные способы применения <code>box-shadow</code> и <code>background-image</code> в иллюстрациях.</p>
<p>Свойство <code>box-shadow</code> настолько мощное, что вы можете создать целую иллюстрацию только с ним. <a href="https://twitter.com/jh3yy/status/1252924614281486336">Я однажды пошутил</a> о создании CSS-иллюстрации доллара.</p>
<iframe src="https://codepen.io/jh3y/embed/preview/eYpBwgN" title="Крошечная купюра 1 американского доллара"></iframe>
<p>Я использовал генератор, чтобы <a href="https://codepen.io/jh3y/details/eYpBwgN">создать иллюстрацию всего из одного <code>&lt;div&gt;</code></a>. Но Альваро Монторо <a href="https://dev.to/alvaromontoro/drawing-a-single-element-dollar-bill-with-css-and-javascript-3mj4">пошёл дальше</a> и написал генератор, который вместо этого использует <code>box-shadow</code>.</p>
<h2>Препроцессоры очень полезны</h2>
<p>Хоть это и не необходимо, использование препроцессоров может помочь содержать ваш код в чистоте и порядке. Например, Pug позволяет писать HTML быстрее, особенно когда нужно использовать циклы для работы с группой повторяющихся элементов. А дальше мы можем <a href="https://css-tricks.com/the-power-and-fun-of-scope-with-css-custom-properties/">ограничить кастомные CSS-свойства</a> таким образом, чтобы нужно было определить стили всего один раз, а затем переопределять их при необходимости.</p>
<iframe src="https://codepen.io/jh3y/embed/preview/xJXvjP" title="Стол-переключатель с кувырком на чистом CSS"></iframe>
<p>Вот другой пример, который демонстрирует структуру по принципу DRY. Цветы свёрстаны с одинаковой разметкой, но каждый имеет свой собственный класс с индексом, который используется для переопределения CSS-свойств.</p>
<iframe src="https://codepen.io/jh3y/embed/preview/eYpjXvj" title="Отзывчивый Лейф из Animal Crossing"></iframe>
<p>У первого цветка такие свойства:</p>
<pre><code tabindex="0" class="language-css">.flower--1 {
    --hue: 190;
    --x: 0;
    --y: 0;
    --size: 125;
    --r: 0;
}
</code></pre>
<p>Это первый, все остальные основаны на нём. Обратите внимание, что второй цветок немного смещён вправо и вверх. Всё, что нужно, это дать разные значения тем же кастомным свойствам:</p>
<pre><code tabindex="0" class="language-css">.flower--2 {
    --hue: 320;
    --x: 140;
    --y: -75;
    --size: 75;
    --r: 40;
}
</code></pre>
<figure>
    <video src="https://web-standards.ru/articles/complex-css-illustrations/images/tweets/timelapse-3.mp4" controls></video>
    <figcaption>
        Анимированный отзывчивый CSS Leif попал в последний CodePen Spark! ✨
        Для тех, кто не знаком с Animal Crossing, Leif — ленивец с зелеными пальцами, который посещает ваш остров 🌻
        Вот таймлапс! 📹
        <a href="https://twitter.com/jh3yy/status/1262856791873945601">@jh3yy</a>
    </figcaption>
</figure>
<h2>Вот и всё!</h2>
<p>Продолжайте, используйте эти советы, придумывайте свои собственные, делитесь ими, а также делитесь своими CSS-шедеврами! И, эй, если у вас есть свои лайфхаки, пожалуйста, делитесь ими тоже! Это определённо то, чему учатся методом проб и ошибок. То, что работает у меня, может отличаться от того, что работает у вас, и мы можем учиться на этих разных подходах.</p>

                    ]]></description><pubDate>Thu, 02 Jul 2020 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/complex-css-illustrations/</guid></item><item><title>Взгляд на JavaScript со стороны</title><link>https://web-standards.ru/articles/todays-js-from-outside/</link><description><![CDATA[
                        <p>Сегодня я пыталась помочь другу, который сам по себе замечательный компьютерный специалист, но не в сфере JavaScript, воспользоваться JavaScript-модулем, который он нашел на GitHub. С учетом того, что последние шесть лет моя работа — исследование юзабилити и преподавание в MIT, меня просто трясет от того, что это было. Куча бесполезных неверных условий, запутанные ошибки и отсутствие правильной обратной связи. И я не чувствую, что хорошо справилась с объяснением той фрустрации, через которую он проходил час, пока не сдался.</p>
<p>Всё происходило приблизительно так…</p>
<p><em>Замечание: имена пакетов и людей были изменены для защиты их конфиденциальности. Я также опустила несколько проблем, с которыми он столкнулся, так как они слишком специфичны для данного пакета. Некоторые ошибки воссозданы по памяти, так что дайте знать, если я в чём-то ошибаюсь!</em></p>
<p><strong>Джон:</strong> Привет, я хочу проверить один алгоритм, который нашёл на GitHub. Он говорит сделать <code>import functionName from packageName</code> и потом вызвать <code>functionName(arguments)</code>. Выглядит достаточно просто! Мне не нужен UI, поэтому я собираюсь использовать Node.js!</p>
<p><strong>Лия:</strong> Конечно, кажется, Node.js как раз подходит для этого!</p>
<p><em>Джон запускает <code>npm install packageName --save</code>, как написано в README пакета.</em></p>
<p><em>Джон запускает <code>node index.js</code>.</em></p>
<p><strong>Node:</strong> <code>Внимание: Чтобы загрузить ES модуль, установите &quot;type&quot;: &quot;module&quot; в package.json или используйте расширение .mjs. Синтаксическая ошибка: Нельзя использовать выражение import вне модуля.</code></p>
<p><strong>Джон:</strong> Но у меня нет package.json…</p>
<p><strong>Лия:</strong> Запусти <code>npm init</code>, он его создаст!</p>
<p><em>Джон запускает <code>npm init</code>, выполняет инструкции на экране, добавляет руками <code>&quot;type&quot;: &quot;module&quot;</code> в сгенерированный package.json.</em></p>
<p><em>Джон запускает <code>node index.js</code>.</em></p>
<p><strong>Node:</strong> <code>Синтаксическая ошибка: Нельзя использовать выражение import вне модуля</code>.</p>
<p>Странно, на этот раз ошибка была проброшена из внутренного модуля в проекте. ШТА?!</p>
<p><strong>Лия:</strong> Ладно, к чёрту, просто запусти скрипт в браузере. Это ES6-модуль, просто обычный JS-алгоритм, который не использует никакие Node.js API. Должно сработать.</p>
<p><em>Джон создаёт простую index.html с <code>&lt;script type=&quot;module&quot; src=&quot;index.js&quot;&gt;</code>.</em></p>
<p><em>Джон запускает index.html в браузере.</em></p>
<p>В консоли ничего. Тишина. Сверчки. 🦗</p>
<p><strong>Лия:</strong> Оу, тебе нужно уточнить путь до модуля, чтобы подключить <code>packageName</code>. Node.js делает всякое специальное, чтобы получить пакет из node_modules. Сейчас ты в браузере, поэтому нужно самому указать точный путь.</p>
<p><em>Джон смотрит в файловую систему, но там нет никакой папки node_modules.</em></p>
<p><strong>Лия:</strong> Оу, ты запускал <code>npm install</code> до того, как у тебя появился package.json, это скорее всего оно! Попробуй запустить ещё раз!</p>
<p><em>Джон запускает <code>npm install packageName --save</code> ещё раз.</em></p>
<p><strong>Джон:</strong> Ура, node_modules появилась!</p>
<p><em>Джон отчаянно ищет в node_modules точку входа.</em></p>
<p><em>Джон редактирует index.js, перезагружает index.html.</em></p>
<p><strong>Firefox:</strong> <code>Incorrect MIME type: text/html</code>.</p>
<p><strong>Лия:</strong> Оу, ты смотришь через <code>file://</code>! Чувак, как ты живёшь без localhost? JavaScript сейчас сильно ограничен внутри <code>file://</code>.</p>
<p><strong>Джон:</strong> Но зачем мне… ладно, я запущу localhost.</p>
<p><em>Джон запускает localhost, открывает index.html по пути <code>http://localhost:80</code>.</em></p>
<p><strong>Firefox:</strong> <code>Incorrect MIME type: text/html</code>.</p>
<p><strong>Джон:</strong> Уф. Мне нужно настроить localhost, чтобы JS-файлы раздавались с MIME-типом <code>text/javascript</code>?</p>
<p><strong>Лия:</strong> Что? Нет! Он это умеет. Хм… посмотри во вкладку Networks, я подозреваю, он не может найти твой модуль и возвращает HTML страничку для 404, поэтому оно ругается, так как MIME-тип страницы не <code>text/javascript</code>.</p>
<p><em>Смотрит в node_modules снова, исправляет путь. Оказывается, VS Code схлопывает папки, в которых только одна подпапка, поэтому мы и не заметили сразу.</em></p>
<p>Если кому интересно: я правда думаю, что это полезное улучшение VS Code, оно увеличивает эффективность. Но им действительно стоит сделать его более явным, чтобы такого не случалось.</p>
<p><strong>Firefox:</strong> <code>SyntaxError: missing ) after formal parameters</code>.</p>
<p><strong>Лия:</strong> Что? Это приходит из кода пакета, это не твоя ошибка. Я не понимаю… Можем мы посмотреть на эту строчку?</p>
<p><em>Джон кликает на строчку, которая бросает ошибку.</em></p>
<p><strong>Лия:</strong> Боже мой. Это не JavaScript, это TypeScript! С расширением .js!!</p>
<p><strong>Джон:</strong> Я просто хотел запустить одну строчку кода, чтобы протестировать алгоритм… 😭😭😭</p>
<p><em>Джон сдаётся. Решает больше никогда не прикасаться к Node.js, npm или ES6-модулям даже длинной палкой.</em></p>
<p>Конец.</p>
<p>Обратите внимание, что Джон — компьютерный специалист, который знает достаточно много про веб: у него установлены Node.js и npm, он знает, что такое MIME-типы, он может запустить localhost для своих нужд. А как быть тем, кто и правда новичок?</p>

                    ]]></description><pubDate>Fri, 29 May 2020 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/todays-js-from-outside/</guid></item><item><title>Разработчика, который писал на чистом HTML и CSS в «Блокноте», признали колдуном</title><link>https://web-standards.ru/articles/witch-developer/</link><description><![CDATA[
                        <p>Младший веб-разработчик в ООО «Масштабируемые решения для энтерпрайза» Кристофер Эбаут-Бланк привлёк к себе пристальное внимание коллег. Вскоре понадобилось вмешательство топ-менеджмента. Причина произошедшего звучит научно-фантастически: Кристофер умудрился создать сайт на чистом HTML и CSS прямо в «Блокноте».</p>
<p>Через неделю Кристофера повысили до старшего веб-разработчика. Не прошло много времени, и команда DevOps была расформирована, поскольку, ко всеобщему удивлению, продукт Кристофера не требовал столько вычислительных мощностей, как предполагалось ранее.</p>
<p>Однако впоследствии в трагической череде событий Кристофер был уволен из компании без выходного пособия, а Верховный суд без особых разбирательств официально признал его колдуном.</p>
<p>«Казалось, что законы физики не применимы к Кристоферу, — говорит один из его коллег, пожелавший остаться анонимным. — Я посмотрел на его монитор и сначала подумал — как мило! Но потом он открыл браузер, всё заработало, и я почувствовал глубокий экзистенциальный ужас, расползающийся внутри».</p>
<p>В настоящее время Кристофер проходит лечение в удалённом буткэмпе для кодеров в Колорадо.</p>

                    ]]></description><pubDate>Tue, 26 May 2020 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/witch-developer/</guid></item><item><title>«Мы можем быть не только массажистами». Как люди с нарушениями зрения учатся делать сайты доступными</title><link>https://web-standards.ru/articles/a11y-testing-in-it-academy/</link><description><![CDATA[
                        <p>На протяжении двух месяцев во львовском SoftServe училась группа студентов на новом для их IT Academy направлении. Тут готовили пятерых профессионалов по тестированию доступности (Accessibility Testing).</p>
<p>Журналисты <a href="https://dou.ua/">DOU</a> побывали на одном из занятий и узнали, какие именно знания получают будущие тестировщики, в чем особенность тестирования доступности и зачем готовить таких специалистов.</p>
<img src="https://web-standards.ru/articles/a11y-testing-in-it-academy/images/1.jpg" alt="">
<h2>Шахматист</h2>
<p>В помещении SoftServe IT Academy с минуты на минуту начнется занятие. По коридору быстро шагает парень и случайно задевает плечом двери. Это Владислав Колпаков, один из студентов курса по тестированию доступности, который стартовал в академии два месяца назад. В руках парень держит трость, а темные очки не снимает даже в помещении.</p>
<p>Влад начал терять зрение ещё в детстве. Но это не помешало ему окончить философский факультет Львовского университета им. Франко и добиться успеха в шахматах. Сегодня он — игрок и тренер сборной Украины по шахматам среди людей с нарушением зрения. А ещё в скором времени Влад станет профессиональным тестировщиком сайтов на доступность.</p>
<p>Для парня это первые IT-курсы. Ранее с компьютером он был знаком на уровне обычного пользователя. А теперь с помощью скринридера и полученных знаний сможет с лёгкостью отличить доступный сайт от недоступного, на котором человек с нарушением зрения не сможет найти нужный товар или войти в систему.</p>
<p>«Я сомневался, стоит ли идти на курсы, ведь последние несколько лет у меня неплохой прогресс в шахматах. А эти занятия требуют большой отдачи, много энергии и времени. Собственно, я понимал, что придётся чем-то пожертвовать, поэтому решение принялось нелегко. Но когда есть возможность, ею нужно воспользоваться», — говорит Владислав Колпаков.</p>
<figure>
    <img src="https://web-standards.ru/articles/a11y-testing-in-it-academy/images/2.jpg" alt="">
    <figcaption>Владислав Колпаков</figcaption>
</figure>
<h2>Рынок, закон и доступность</h2>
<p>Тестирование доступности — это тип тестирования программного обеспечения, которое применяют, чтобы сайт, приложение или что-либо ещё были удобными для использования людьми с нарушением зрения или слуха, поражением опорно-двигательного аппарата, памяти и тому подобного. Специалисты по такому виду тестирования проверяют, соответствует продукт критериям, изложенным в <a href="https://www.w3.org/WAI/standards-guidelines/wcag/">Web Content Accessibility Guidelines</a> (WCAG). Этот документ — часть из серии руководящих принципов о доступности веб-страниц, которые разработали в <a href="https://www.w3.org/">W3C</a>.</p>
<p>Контрастность цветов, размер шрифтов, заголовки разделов, аудиосопровождение страницы, анимация от взаимодействий и корректность многих параметров — всё это описано в документе. Задание тестировщика доступности — определить, где существуют проблемы.</p>
<p>Толчком для создания курсов по тестированию доступности в SoftServe стали требования рынка и законодательства.</p>
<p>Виктория Ширяева, представительница бизнес-подразделения, Senior QA и Project Manager, уже на протяжении двух лет работает с проектами SoftServe, которые внедряют доступность.</p>
<p>«В Соединённых Штатах приблизительно у каждого четвёртого есть та или иная форма инвалидности. Так что это гигантский рынок, который потенциально можно потерять. Это бизнес-драйвер. Есть и законодательные драйверы — требования, согласно которым сайты должны быть доступными. Иначе их не допустят к использованию. Особенно строго это регулируется в Америке. В последние годы к этому вопросу более внимательно относится и Европа», — объясняет специалистка.</p>
<p>По её словам, сегодня действует директива Европарламента, где прописано, что европейские страны должны принять закон, который требовал бы от всех публичных сайтов быть доступными. А к 2021 году, согласно документу, необходимо сделать такими и мобильные приложения.</p>
<p>«Так что мы поняли, что это не только благородная цель — помочь с трудоустройством людям с инвалидностью, но и большая потенциальная возможность для SoftServe предоставлять необходимые услуги уже существующим клиентам, а также новым, которые могут обратиться к нам», — подытоживает Виктория.</p>
<h2>Активист</h2>
<p>Роману Савке — 34. 15 лет назад он учился во «Львовской политехнике», где осваивал компьютерные науки. 10 лет назад занимался художественным кузнечеством. Два месяца назад впервые познакомился с командой SoftServe и узнал, что такое тестирование доступности.</p>
<figure>
    <img src="https://web-standards.ru/articles/a11y-testing-in-it-academy/images/3.jpg" alt="">
    <figcaption>Роман Савка</figcaption>
</figure>
<p>Проблемы со зрением у Романа начались во взрослом возрасте. Из-за этого даже пришлось бросить одно из любимых занятий — дзюдо. Врачи сказали, что из-за интенсивных тренировок зрение станет только хуже и есть вероятность его полностью потерять. Сейчас мужчина распознает свет и тени, так что-то, как он ориентируется в пространстве, зависит от времени суток и освещения.</p>
<p>Сегодня Роман возглавляет организацию «Равные возможности для всех», которая предоставляет юридические консультации в вопросах защиты прав человека. Со временем её участники планируют разработать рекомендации для правительства, чтобы более эффективно внедрять нормы Конвенции ООН по правам человека на просторах Украины.</p>
<p>А ещё Роман отец двоих детей и единственный кормилец в семье, так как жена находится в декретном отпуске. Поэтому решиться на курсы было непросто: вдруг не хватит навыков, времени или знаний?</p>
<p>«Я не думал, что дойду именно до обучения, тем более до финального этапа. Дело в том, что я не так давно пользуюсь скринридером и не было опыта интенсивной работы с компьютером», — говорит мужчина.</p>
<p>Теперь Роман ощущает себя более уверенным в своих умениях. Курс по тестированию доступности — это три часа занятий в аудитории IT Academy три раза в неделю и регулярные домашние задания. Время, проведённое в аудитории и ночью за домашним ПК, принесло свои результаты. Сегодня Роман разве что шутит, что постоянно ставит кучу вопросов преподавательницам. А они и рады. Говорят, если есть вопросы, значит, человеку действительно интересно, он хочет разобраться в предмете.</p>
<h2>Компьютер, английский и мотивация</h2>
<p>Найти таких, как Роман и Владислав, было непросто, хоть и требования к потенциальным участникам курса были невысоки:</p>
<ul>
<li>знать компьютер на уровне уверенного пользователя;</li>
<li>владеть английским не ниже уровня Intermediate;</li>
<li>быть мотивированным.</li>
</ul>
<p>«Больше всего людей отсеялось из-за английского. Это не наша прихоть, а реалии рынка, поскольку украинских заказчиков нет. А участникам придётся не только читать литературу, чтобы разобраться в теме, но и общаться с заказчиком или его представителями», — объясняет менторка SoftServe IT Academy Виктория Ряжская.</p>
<p>Умений или знаний в тестировании от кандидатов не требовали. Но они должны были уметь элементарные вещи: найти тот или иной сайт, зарегистрироваться на нём и так далее. Всё это — с использованием скринридера, лупы или другой программы, которая помогает людям с нарушением зрения пользоваться компьютером. Это и было одним из тестовых заданий, которое приближало на шаг к месту на курсах.</p>
<p>«У нас была девочка со зрением −13. Но мы не смогли её взять, потому что она не использовала никакой вспомогательный инструмент. Она впритык подходила к монитору, чтобы увидеть текст, изображение. Но таким образом девушка ознакамливалась с сайтом как обычный пользователь и не смогла протестировать его на предмет доступности», — уточняет Виктория Ряжская.</p>
<p>Мотивация тоже важна для того, чтобы попасть на курс, поскольку программа, как отмечают представители IT Academy, не так проста. Особенность курса в том, что он содержит в себе много информации, которую нужно освоить за короткое время. Студенты постоянно работают, каждую неделю пишут тесты, делают домашние задания, а ещё должны уделять время своим собственным делам. Поэтому, чтобы доучиться до конца и получить знания, нужно действительно этого хотеть.</p>
<p>«Мы даже нашим студентам говорим: пойдёте на работу, тогда и отдохнёте. Потому что на работе ты работаешь и выкладываешься на полную 8 часов, а во время учёбы на курсах нужно заниматься постоянно — и в аудитории, и дома. И далеко не все, кто подавал заявку, были уверены в своих силах. А некоторые не были готовы принимать что-то новое», — добавляет Виктория.</p>
<p>Всего, говорят организаторы, было около 30 заявок, но желающих — намного больше. Одна из преград — нужно было физически находиться во Львове и посещать каждое занятие, а не все могут самостоятельно передвигаться.</p>
<p>«Хоть мы и так старались максимально упростить этот момент. Волонтёры привозили людей. Мы не проводили длительные собеседования тет-а-тет с каждым потенциальным участником. Один раз собрали всех. Рассказали на общей встрече о компании, курсах и о требованиях. Потом — двухминутный разговор на английском, чтение короткого текста, его пересказ своими словами и небольшое тестовое задание», — говорит Виктория Ряжская.</p>
<h2>Филолог</h2>
<figure>
    <img src="https://web-standards.ru/articles/a11y-testing-in-it-academy/images/4.jpg" alt="">
    <figcaption>Иван Яцига</figcaption>
</figure>
<p>Одним из пяти, кого отобрали, стал 24-летний Иван Яцига. Парню даже пришлось отказаться от занятий репетиторством: он готовил учеников к школьным экзаменам по украинскому языку и литературе.</p>
<p>Не так давно Иван окончил Львовский национальный университет им. Ивана Франко. Теперь он филолог, а репетиторство — его основной источник дохода.</p>
<p>«Но я не раздумывал над тем, стоит ли идти на курсы. Репетиторство хоть и основной, но не постоянный заработок. А найти работу сегодня, тем более человеку с инвалидностью, сложно. Я хорошо учился, окончил университет, получил диплом… Впрочем, оказалось, что в реальной жизни филолог не очень кому-то нужен. Я ходил на собеседования в школы, и мне отказывали», — вспоминает Иван.</p>
<p>Иван считает, трудоустройство человека с инвалидностью — проблема на государственном уровне, она глобальна.</p>
<p>«Если посмотреть на перечень профессий, то по большей части люди с нарушением зрения могут быть или массажистами, или… массажистами!» — отмечает студент.</p>
<p>Так что курсы для него — реальный шанс трудоустроиться.</p>
<p>«Мне как филологу, конечно, вначале было смешно от таких слов, как „зашедулить“, „саппортить“. Но потихоньку привык», — с улыбкой добавляет будущий тестировщик.</p>
<p>По словам Ивана Яциги, у курса есть существенное преимущество перед классическим университетским образованием. Тут ты учишься чему-то новому за короткий срок и, если у тебя что-то не получается или что-то не нравится, всегда можно поменять направление. И не надо тратить пять лет жизни, а потом не знать, что делать с этим дипломом.</p>
<p>Правда, теперь парень шутит: пока не пришёл на курсы, пользовался сайтами нормально. А теперь куда не посмотришь — всё недоступное, ведь в голове держишь критерии оценки веб-ресурсов.</p>
<h2>Теория, практика и результаты</h2>
<p>Программа курса по тестированию доступности состоит из теории и практики и в целом похожа на ту, по которой в SoftServe обучают обычных тестировщиков.</p>
<p>«Наверно, девяносто процентов — это та информация, которую мы даём будущим ручным тестировщикам. А вторая часть — именно тестирование доступности. Естественно, её обычные тестеры не проходят», — уточняет менторка IT Academy Виктория Ряжская. Сначала студентов ознакомили с критериями доступности сайтов и приложений согласно <abbr title="Web Content Accessibility Guidelines">WCAG</abbr>, демонстрировали различные кейсы. Потом перешли к практической работе, как на проекте.</p>
<p>«Каждую неделю у нас проходит тест по теории. И интересно, что у студентов этой группы оценка выше, чем у остальных, хоть они выполняют те же задания. Единственное отличие — им дают полтора часа на выполнение, а не час, потому что наш тест не рассчитан на людей с нарушениями зрения и им сложно ориентироваться в навигации», — говорит преподавательница.</p>
<p>В конце курса студенты курса сдают финальный тест и должны презентовать собственный проект.</p>
<h2>Англоязычный</h2>
<p>Олег Шапай — один из тех, кого тестирование по иностранному языку во время отбора точно не испугало, потому что в университете он изучал английский и немецкий.</p>
<p>«Мне очень вовремя предложили этот курс, потому что я как раз искал работу и думал, что стоит найти то, что будет связано с компьютерами. Согласился на курсы не в последнюю очередь потому, что в Украине и в мире в общем существует большая проблема с доступностью», — уверен Олег.</p>
<figure>
    <img src="https://web-standards.ru/articles/a11y-testing-in-it-academy/images/5.jpg" alt="">
    <figcaption>Олег Шапай</figcaption>
</figure>
<p>Ещё до обучения парень давал рекомендации компаниям по поводу того, как сделать сайты более удобными для людей с нарушениями зрения. Однако всё это было на уровне обычного пользователя и базировалось на личном восприятии.</p>
<p>«Иногда было тяжело объяснить разработчику, в чём проблема. А теперь у меня есть фундаментальная база и я знаю, в каких именно документах находятся объяснения и требования», — добавляет он.</p>
<p>Достаточно большое количество домашних заданий на курсах Олега не останавливает. Говорит, что и так много времени проводил за монитором, а теперь может делать это с пользой. Единственная вещь, которая ему не нравится, — необходимость много писать. Парень признается: не любил этого ещё в университете, но тут уже никуда не денешься.</p>
<p>«Что-то протестировать — легко, а вот писать… Пришлось смириться», — с улыбкой отмечает он.</p>
<p>Студент говорит, что важно не просто хотеть, чтобы чем-то было удобно пользоваться, а попробовать изменить ситуацию.</p>
<p>«Например, мы видим, что сайт недоступен. А хочется, чтобы было иначе. Так давайте сделаем его доступным! Но не только для себя, а для всех. Собственно, это одна из тех вещей, которая мотивирует меня учиться тут и двигаться дальше», — говорит Олег Шапай.</p>
<h2>Выпуск и планы</h2>
<p>В конце обучения команда взялась тестировать сайт SoftServe. Работают по Agile-методологии, используют Scrum: с митингами для планирования, выполнением заданий и заполнением документации.</p>
<p>Виктория Ряжская говорит, что этот выпуск — уже настоящие эксперты в <abbr title="Web Content Accessibility Guidelines">WCAG</abbr>. Она радуется результатам команды и твердит, что у студентов удивительная память.</p>
<p>«Есть 50 критериев, по которым можно проверять сайт на доступность. В документе, который мы используем, они сгруппированы и пронумерованы. И если я попрошу кого-то из наших студентов назвать критерий, например, 1.3.2, то человек не только скажет, что это, но и даст детальное описание, нюансы, когда этот критерий может не применяться, и приведет примеры. Я эти цифры не могу запомнить, а у них всё в голове, будто по полочкам разложено», — отмечает менторка.</p>
<img src="https://web-standards.ru/articles/a11y-testing-in-it-academy/images/6.jpg" alt="">
<p>На данный момент основной план компании — трудоустроить выпускников на проекты SoftServe. Говорят, если всё пройдет успешно и будет много запросов по тестированию доступности, то второй набор точно откроют. Но пока что из-за карантина этот процесс затягивается, поскольку нет возможности организовать выпускникам онбординг и надлежаще обустроить рабочие места.</p>
<p>«Думаю, если бы мы запускали новый набор, имея такой хороший пример, было бы намного больше желающих и меньше сомнений в том, что такое возможно. Поэтому будем работать на этот результат. Тем более, что у нас уже есть хороший опыт», — говорит Виктория Ширяева.</p>
<p>Олег Шапай убеждён: если их группа получит достойную работу, то покажет, что можно и нужно ломать стереотипы.</p>
<p>«Люди должны понимать, что мы можем быть не только массажистами и преподавателями. IT открыто для всех. И было бы круто, если как можно больше людей с инвалидностью могли научиться всему, что связано с технологиями. То, что происходит сейчас, это классно и вселяет надежду», — говорит Олег.</p>

                    ]]></description><pubDate>Tue, 19 May 2020 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/a11y-testing-in-it-academy/</guid></item><item><title>Можно ли вложить тег в тег? Об инструменте Can I Include</title><link>https://web-standards.ru/articles/can-i-include/</link><description><![CDATA[
                        <p>На создание этого инструмента меня вдохновила <a href="https://htmlacademy.ru/">HTML Academy</a> и их стремление к чистоте вёрстки и строгим стандартам. В их <a href="https://htmlacademy.ru/intensive/htmlcss">курсе по вёрстке</a> вкладывание тегов объясняется примерами <a href="https://html.spec.whatwg.org/">из спецификации</a>, что не очень удобно. И мне пришла в голову идея: упростить доступ к информации о тегах и придумать простой механизм проверки возможности вкладывать теги. Далее — история создания онлайн-инструмента <strong><a href="https://caninclude.glitch.me/">Can I Include</a>.</strong></p>
<h2>Технологии</h2>
<p>Сразу встал вопрос, какие технологии использовать, а главное на каком хостинге разворачивать проект. Хотелось чего-то лёгкого и незатейливого, без использования бабелей, парселей и вебпаков, в душе зарождался дух свободы и бунтарства.</p>
<h3>Про вёрстку</h3>
<p>Подумав немного, понял, что старый добрый <a href="https://github.com/preactjs/preact">Preact</a> c его подходом к Server Side Rendering через <a href="https://github.com/developit/htm">Hyperscript Tagged Markup</a> поможет в разделении частей страниц на компоненты. При этом решил, что на первых порах стоит обойтись без клиентского JS, использовать на клиенте только CSS и HTML.</p>
<h3>Про стили</h3>
<p>Не стал использовать препроцессоры и отдал предпочтение <strong>raw</strong> CSS как есть, в одном файле.</p>
<h3>Про серверную часть</h3>
<p>Мой любимый <a href="https://expressjs.com/">Express.js</a> и <a href="https://nodejs.org/en/">Node.js</a>.</p>
<h3>Хостинг</h3>
<p>Выбрал <a href="https://glitch.com/">glitch.com</a>. Он даёт возможность кодить в браузере и открывает доступ к результатам работы любым желающим. Поддерживает Node.js и возможность доступа к контейнеру через веб-консоль прямо из редактора кода. И, что самое главное, он основан на <a href="https://www.docker.com/">Docker</a>. У меня уже был ранее отработанный простой механизм поставки изменений, и это тоже радовало. Вдобавок ко всем удобствам, <a href="https://glitch.com/">glitch.com</a> добавил <a href="https://glitch.com/pricing">тариф с возможностью бустинга 5 приложений</a>, я тут же поспешил им воспользоваться.</p>
<h3>Скрейпинг</h3>
<p>Ну куда же без скрейпинга! Без него никуда. Данные нужно извлечь из <a href="https://html.spec.whatwg.org/">спецификации HTML</a> и обработать напильником в удобоваримую структуру для доступа к данным. Для скрейпинга использовал свой любимый инструмент — <a href="https://github.com/puppeteer/puppeteer">Puppeteer</a>.</p>
<h2>Реализация</h2>
<p>Мне понадобилось пара дней на то, чтобы пройти от генерации идеи до её реализации. Я ни разу не дизайнер, так что заимствовавал идеи уже давно существующих веб-сайтов. В итоге получился простой и ёмкий, в плане отображения информации, интерфейс.</p>
<p>В ходе скрейпинга увидел дополнительную информацию с сайтов <a href="https://caniuse.com/">Сan I Use</a> и <a href="https://developer.mozilla.org/">MDN</a> по каждому тегу, расположенную на странице <a href="https://html.spec.whatwg.org/">спецификации HTML</a> в виде небольших бейджиков. В итоге решил разместить информацию о поддержке тегов разными браузерами сразу под сравниваемыми тегами. Чтобы, как говорится, всё было под рукой, в развёрнутом виде.</p>
<p>Так как не использовал JS на клиенте, то в мобильном представлении сделал переключение табов со сравниваемыми тегами с помощью CSS-селекторов.</p>
<h2>Принцип работы</h2>
<p>Так как основная цель — удобно показать информацию о двух сравниваемых тегах, то для старта я решил реализовать простой способ проверки. Информация, представленная в спецификации, больше приспособлена для понимания человеком, чем машиной — хотя и человеку нужно приложить усилия, чтобы сопоставить несколько условий и решить, можно ли вкладывать теги.</p>
<p>Для принятия решения о возможности вложения одного тега в другой необходимо сопоставить содержание секций сравниваемых тегов.</p>
<ul>
<li>У вкладываемого тега нужно проверить секцию <strong>Categories,</strong> а у родительского тега — <strong>Content model.</strong></li>
<li>В простом случае, наличие одного из пуктов из секции <strong>Categories</strong> в секции <strong>Сontent model,</strong> даёт положительный результат.</li>
<li>В случае с контентной моделью <strong>Transparent</strong> в секции <strong>Content model</strong> родительского тега, нужно обратить внимание на <strong>Content model</strong> <strong>родителя</strong> нашего родительского тега. То есть в таком случае вам нужно самостоятельно проверить следующий родительский тег из вашей разметки.</li>
<li>Есть и более сложный случай, где есть несколько «но». Порой некторые атрибуты меняют возможность вкладывания.</li>
</ul>
<p>Теперь про простой алгоритм проверки вложенности тегов:</p>
<ol>
<li>Во время скрейпинга в секциях каждого тега создаётся множество ключевых слов, в которое входят названия контентной модели, имена атрибутов и тегов.</li>
<li>Если в секции есть <strong>Nothing</strong>, то в множество ключевых тегов включается название тега.</li>
<li>Если есть модель <strong>Transparent</strong> в секции <strong>Content model</strong> родительского тега, то ситуация с включением тега не определена. В этом случае выдаётся сообщение, что нужно взять из разметки следующий внешний тег и повторить запрос. При этом следующий пункт не выполняется.</li>
<li>Далее производится проверка на пересечение множеств:
<ul>
<li>если найдены общие ключевые слова — <strong>положительный вердикт,</strong> теги можно вкладывать.</li>
<li>если нет пересекающихся ключевых слов — <strong>отрицательный вердикт,</strong> теги нельзя вкладывать.</li>
</ul>
</li>
</ol>
<h2>Интерфейс</h2>
<p>При обращении по адресу <strong>caninclude.glitch.me</strong> открывается следующая страница:</p>
<img src="https://web-standards.ru/articles/can-i-include/images/page.png" width="1824" height="1042" alt="Главная страница Can I Include.">
<p>Весёлый текстовый логотип и два поля встречают пользователя: в левом нужно ввести название тега, который вы вкладываете, в правом — тега, в который вы вкладываете. После этого, нужно нажать клавишу <kbd>Enter</kbd> или кликнуть по кнопке с вопросом.</p>
<p>В результате отправки формы по методу GET рендерится страница с информацией по двум тегам и вердиктом о вложенности.</p>
<img src="https://web-standards.ru/articles/can-i-include/images/positive.png" width="1654" height="1466" alt="Страница с успешным вердиктом.">
<p>Если определить возможность вкладывания не получилось из-за «прозрачной» контентной модели внешнего тега, то страница будет выглядеть так:</p>
<img src="https://web-standards.ru/articles/can-i-include/images/doubt.png" width="1690" height="1672" alt="Страница с неопределённым вердиктом.">
<p>В случае, если вкладывать теги нельзя, то страница с вердиктом будет выглядеть так:</p>
<img src="https://web-standards.ru/articles/can-i-include/images/negative.png" width="1692" height="1678" alt="Страница с отрицательным вердиктом.">
<h2>Итог</h2>
<p>Надеюсь, что онлайн-инструмент <a href="https://caninclude.glitch.me">Сan I Include</a> пригодится вам. Ещё много работы предстоит по улучшению интерфейса и расширению функциональности, а также по совершенствованию механизма определения вложенности. Спасибо коллективу <a href="https://htmlacademy.ru/">HTML Academy</a> за борьбу за грамотность вёрстки и что вдохновили меня на создание этого инструмента. Также в планах организовать обратную связь, чтобы получать замечания и пожелания.</p>

                    ]]></description><pubDate>Tue, 05 May 2020 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/can-i-include/</guid></item><item><title>Ускоряемся с помощью Browserslist</title><link>https://web-standards.ru/articles/speed-up-with-browserslist/</link><description><![CDATA[
                        <p>На сегодняшний день имеется большое количество разных браузеров и ещё большее количество версий каждого из них. Если раньше новые фичи добавлялись в браузеры редко, то сейчас их можно обнаружить практически с каждым новым релизом. Как итог, у разных версий браузера — разная поддержка фич, не говоря уже о разном уровне поддержки среди разных вендоров.</p>
<p>Разработчикам хочется использовать новые фичи, так как зачастую это упрощает жизнь. Благодаря инструментам разработки можно использовать фичи до их появления в браузерах путём транспиляции и использования полифилов. Также эти инструменты гарантируют работу сайта в любом браузере независимо от того, имеется ли в нём поддержка той или иной фичи. Из примеров: <a href="https://github.com/postcss/autoprefixer">Autoprefixer</a> и <a href="https://preset-env.cssdb.org">postcss-preset-env</a> для CSS, <a href="https://babeljs.io">Babel</a> для JavaScript. Но нужно понимать, что при этом размер кода увеличивается.</p>
<p>В итоге мы имеем сайт, который работает в любом браузере, но из-за этого загружается медленнее, чем мог бы. Напомню, что скорость загрузки и работы сайта напрямую влияет на количество и глубину просмотров. Что с этим можно сделать? На самом деле нам незачем транспилировать и полифилить абсолютно все фичи — достаточно это делать только с теми, которые не поддерживаются актуальными браузерами (или актуальными для аудитории вашего сайта). Например, промисы не поддерживаются только очень старыми версиями браузеров.</p>
<h2>Browserslist</h2>
<p><a href="https://github.com/browserslist/browserslist">Browserslist</a> — это тот инструмент, с помощью которого можно описать целевые браузеры веб-приложения, используя простые выражения:</p>
<pre><code tabindex="0">last 2 years
&gt; 1%
not dead
</code></pre>
<p>Этот пример файла <code>.browserslistrc</code> означает, что вам нужны: браузеры за последние два года, плюс браузеры у которых больше 1% пользователей, и все эти браузеры должны быть «живыми». Посмотреть в какие конкретные браузеры это разрезолвится можно на сайте <a href="https://browserl.ist/">browserl.ist</a>, а более подробно узнать про синтаксис выражений можно <a href="https://github.com/browserslist/browserslist">на странице проекта</a>.</p>
<p>Уже упомянутые <a href="https://github.com/postcss/autoprefixer">Autoprefixer</a>, <a href="https://preset-env.cssdb.org">postcss-preset-env</a> и <a href="https://babeljs.io/docs/en/babel-preset-env">babel-preset-env</a> под капотом используют Browserslist, и если в вашем проекте есть конфиг Browserslist, то код проекта будет собран под эти браузеры.</p>
<p>На этом этапе можно прийти к следующему выводу: чем новее браузеры, в которые мы целимся, тем меньше будет весить итоговый код сайта. При этом нужно не забывать, что в реальном мире не у всех пользователей самый новый браузер, и сайт должен быть доступен если не всем пользователям, то большинству из них. Как можно поступить, учитывая это условие?</p>
<h2>Варианты таргетирования браузеров</h2>
<h3>1. Более узкое таргетирование</h3>
<p>По умолчанию, если в проекте нет конфига, то Browserslist будет использовать <code>defaults</code> браузеры. Это выражение является сокращением для <code>&gt; 0.5%, last 2 versions, Firefox ESR, not dead</code>. В целом, можно остановиться на использовании этого выражения, и со временем браузеры, подходящие под это выражение, станут поддерживать большинство актуальных фич.</p>
<p>Но можно целиться в более узкое количество браузеров: исключить старые и непопулярные, учитывать более-менее актуальные версии браузеров. Звучит просто, но на самом деле это не так. Необходимо сбалансировать конфиг Browserslist так, чтобы он, опять же, покрывал большую часть аудитории.</p>
<h3>2. Анализ аудитории</h3>
<p>Если ваш сайт подразумевает использование только в определенных регионах, то можно попробовать использовать выражение вида <code>&gt; 5% in US</code>, которое вернёт браузеры, подходящие по статистике использования в указанной стране.</p>
<p>Семейство Browserslist полно разными дополнительными инструментами, один из них — <a href="https://github.com/browserslist/browserslist-ga">Browserslist-GA</a> (также есть <a href="https://github.com/xeroxinteractive/browserslist-adobe-analytics">browserslist-adobe-analytics</a>), который позволяет выгрузить из сервиса аналитики данные о браузерах, используемые посетителями вашего сайта. После этого становится возможным использовать эти данные в конфиге Browserslist и применять на них выражения:</p>
<pre><code tabindex="0">&gt; 0.5% in my stats
</code></pre>
<p>К примеру, если обновлять эти данные при каждом деплое, то сайт будет всегда собираться под актуальные браузеры, используемые вашей аудиторией.</p>
<h3>3. Условная загрузка ресурсов</h3>
<p>В марте 2019 <a href="https://twitter.com/mathias">Матиас Байненс</a> из Google <a href="https://github.com/whatwg/html/issues/4432">предложил</a> добавить в браузеры дифференциальную загрузку скриптов (differential script loading, далее DSL):</p>
<pre><code tabindex="0" class="language-html">&lt;script type=&quot;module&quot;
        srcset=&quot;2018.mjs 2018, 2019.mjs 2019&quot;
        src=&quot;2017.mjs&quot;&gt;&lt;/script&gt;
&lt;script nomodule src=&quot;legacy.js&quot;&gt;&lt;/script&gt;
</code></pre>
<p>До сих пор его предложение остается лишь предложением, и неизвестно, будет ли это в том или ином виде реализовано в браузерах. Но концепт понятен, и в семействе Browserslist есть инструменты, с помощью которых можно реализовать что-то подобное, один из них — <a href="https://github.com/browserslist/browserslist-useragent">browserslist-useragent</a>. Этот инструмент позволяет проверить User-Agent браузера на соответствие вашему конфигу.</p>
<h4>Browserslist-useragent</h4>
<p>В интернете уже есть несколько статей на эту тему, вот пример одной из них — <a href="https://www.smashingmagazine.com/2018/10/smart-bundling-legacy-code-browsers/">«Smart Bundling: How To Serve Legacy Code Only To Legacy Browsers»</a>. Коротко пробежимся по реализации. Для начала необходимо настроить ваш процесс сборки для вывода, например, двух версий бандлов для новых и старых браузеров. Здесь вам поможет Browserslist с его <a href="https://github.com/browserslist/browserslist#configuring-for-different-environments">возможностью объявления нескольких сред в конфигурационном файле</a>:</p>
<pre><code tabindex="0">[modern]
last 2 versions
last 1 year
not safari 12.1

[legacy]
defaults
</code></pre>
<p>Далее необходимо настроить сервер для отдачи нужного бандла под браузер пользователя:</p>
<pre><code tabindex="0" class="language-js">/* … */
import { matchesUA } from 'browserslist-useragent'
/* … */
app.get('/', (request, response) =&gt; {
    const userAgent = request.get('User-Agent')
    const isModernBrowser = matchesUA(userAgent, {
        env: 'modern',
        allowHigherVersions: true
    })
    const page = isModernBrowser
        ? renderModernPage(request)
        : renderLegacyPage(request)

    response.send(page)
})
</code></pre>
<p>Таким образом, сайт будет выдавать легковесный бандл пользователям с новыми браузерами, что приведёт к более быстрой загрузке страниц, сохраняя при этом доступность для остальных пользователей. Но, как можно было заметить, этот способ требует наличия собственного сервера со специальной логикой.</p>
<h4>Module/nomodule</h4>
<p>С появлением в браузерах поддержки ES-модулей появился другой способ реализовать DSL, но уже на стороне клиента:</p>
<pre><code tabindex="0" class="language-html">&lt;script type=&quot;module&quot; src=&quot;index.modern.js&quot;&gt;&lt;/script&gt;
&lt;script nomodule src=&quot;index.legacy.js&quot;&gt;&lt;/script&gt;
</code></pre>
<p>Этот паттерн имеет название module/nomodule, и его суть состоит в том, что старые браузеры без поддержки ES-модулей не будут обрабатывать скрипты с типом <code>module</code>, так как этот тип им неизвестен. А браузеры, которые поддерживают ES-модули, будут загружать скрипт с типом <code>module</code> и игнорировать скрипт с атрибутом <code>nomodule</code>. Браузеры с поддержкой ES-модулей, описываются следующим конфигом:</p>
<pre><code tabindex="0">[esm]
edge &gt;= 16
firefox &gt;= 60
chrome &gt;= 61
safari &gt;= 11
opera &gt;= 48
</code></pre>
<p>Главным плюсом паттерна module/nomodule является отсутствие необходимости в собственном сервере — всё работает полностью на клиентской части. Загрузку стилей в зависимости от браузера таким образом не сделать, но можно реализовать загрузку ресурсов с помощью JavaScript, используя проверку вида:</p>
<pre><code tabindex="0" class="language-js">if ('noModule' in document.createElement('script')) {
    // Новые браузеры
} else {
    // Старые браузеры
}
</code></pre>
<p>Из минусов: этот паттерн имеет кое-какие <a href="https://gist.github.com/jakub-g/5fc11af85a061ca29cc84892f1059fec">кроссбраузерные проблемы</a>. Также уже начинают появляться фичи, которые имеют разный уровень поддержки, даже среди браузеров с поддержкой ES-модулей, например, <a href="https://caniuse.com/#feat=mdn-javascript_operators_optional_chaining">optional chaining operator</a>. С появлением новых фич этот вариант DSL потеряет свою актуальность.</p>
<p>Прочитать ещё больше про паттерн module/nomodule можно в статье <a href="https://jasonformat.com/modern-script-loading/">«Modern Script Loading»</a>. Если вас заинтересовал этот вариант DSL и хочется попробовать внедрить его в свой проект, то вы можете воспользоваться плагином для Webpack: <a href="https://www.npmjs.com/package/webpack-module-nomodule-plugin">webpack-module-nomodule-plugin</a>.</p>
<h4>Browserslist-useragent-regexp</h4>
<p>Относительно недавно появился ещё один инструмент для Browserslist: <a href="https://github.com/browserslist/browserslist-useragent-regexp">browserslist-useragent-regexp</a>. Этот инструмент позволяет получить из конфига регулярное выражение для проверки User-Agent браузера. Регулярные выражения работают в любой среде исполнения JavaScript, что даёт возможность проверять User-Agent браузера не только на стороне сервера, но и на стороне клиента. Таким образом, можно реализовать работающий в браузере DSL:</p>
<pre><code tabindex="0" class="language-js">// last 2 firefox versions
var modernBrowsers = /Firefox\/(73|74)\.0\.\d+/
var script = document.createElement('script')

script.src = modernBrowsers.test(navigator.userAgent)
    ? 'index.modern.js'
    : 'index.legacy.js'

document.all[1].appendChild(script)
</code></pre>
<p>Можно добавить, что генерируемые регулярные выражения работают быстрее, чем функция <code>matchesUA</code> из browserslist-useragent, так что есть смысл использовать browserslist-useragent-regexp и на стороне сервера:</p>
<pre><code tabindex="0" class="language-js">&gt; matchesUA('Mozilla/5.0 (Windows NT 10.0; rv:54.0) Gecko/20100101 Firefox/54.0', { browsers: ['Firefox &gt; 53']})
first time: 21.604ms
&gt; matchesUA('Mozilla/5.0 (Windows NT 10.0; rv:54.0) Gecko/20100101 Firefox/54.0', { browsers: ['Firefox &gt; 53']})
warm: 1.742ms

&gt; /Firefox\/(5[4-9]|6[0-6])\.0\.\d+/.test('Mozilla/5.0 (Windows NT 10.0; rv:54.0) Gecko/20100101 Firefox/54.0')
first time: 0.328ms
&gt; /Firefox\/(5[4-9]|6[0-6])\.0\.\d+/.test('Mozilla/5.0 (Windows NT 10.0; rv:54.0) Gecko/20100101 Firefox/54.0')
warm: 0.011ms
</code></pre>
<p>Все это выглядит очень здорово, но хочется иметь удобный способ встроить это в процесс сборки проекта… И такой способ есть!</p>
<h4>Browserslist Differential Script Loading</h4>
<p><a href="https://github.com/TrigenSoftware/bdsl-webpack-plugin">Bdsl-webpack-plugin</a> это плагин для Webpack, работающий в паре с <a href="https://github.com/jantimon/html-webpack-plugin">html-webpack-plugin</a> и использующий <a href="https://github.com/browserslist/browserslist-useragent-regexp">browserslist-useragent-regexp</a>, который помогает автоматизировать добавление в бандл дифференциальной загрузки. Вот пример конфига для Webpack с применением этого плагина:</p>
<pre><code tabindex="0" class="language-js">const {
    BdslWebpackPlugin,
    getBrowserslistQueries,
    getBrowserslistEnvList
} = require('bdsl-webpack-plugin')

function createWebpackConfig(env) {
    return {
        name: env,
        /* … */
        module: {
            rules: [{
                test: /\.js$/,
                exclude: /node_modules/,
                loader: 'babel-loader',
                options: {
                    cacheDirectory: true,
                    presets: [
                        ['@babel/preset-env', {
                            /* … */
                            targets: getBrowserslistQueries({ env })
                        }]
                    ],
                    plugins: [/* … */]
                }
            }]
        },
        plugins: [
            new HtmlWebpackPlugin(/* … */),
            new BdslWebpackPlugin({ env })
        ]
    };
}

module.exports = getBrowserslistEnvList().map(createWebpackConfig)
</code></pre>
<p>В этом примере экспортируется <a href="https://webpack.js.org/configuration/configuration-types/#exporting-multiple-configurations">несколько конфигов</a> для вывода бандлов для каждой среды из конфига Browserslist. На выходе мы получим HTML-файл со встроенным DSL-скриптом:</p>
<pre><code tabindex="0" class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
    &lt;head&gt;
        &lt;title&gt;Example&lt;/title&gt;
        &lt;script&gt;function dsl(a,s,c,l,i){c=dsld.createElement('script');c.async=a[0];c.src=s;l=a.length;for(i=1;i&lt;l;i++)c.setAttribute(a[i][0],a[i][1]);dslf.appendChild(c)}var dsld=document,dslf=dsld.createDocumentFragment(),dslu=navigator.userAgent,dsla=[[]];if(/Firefox\/(73|74)\.0\.\d+/.test(dslu))dsl(dsla[0],&quot;/index.modern.js&quot;)
else dsl(dsla[0],&quot;/index.legacy.js&quot;);dsld.all[1].appendChild(dslf)&lt;/script&gt;
    &lt;/head&gt;
    &lt;body&gt;&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>Помимо загрузки скриптов есть поддержка <a href="https://github.com/TrigenSoftware/bdsl-webpack-plugin/tree/master/examples/postcss-preset-env">загрузки стилей</a>. Также имеется возможность использовать этот плагин <a href="https://github.com/TrigenSoftware/bdsl-webpack-plugin/tree/master/examples/SsrBdslWebpackPlugin">на стороне сервера</a>.</p>
<p>Но, к сожалению, есть и кое-какие нюансы, о которых нужно знать при использовании bdsl-webpack-plugin: так как загрузка скриптов и стилей инициализируется из JavaScript, то они загружаются асинхронно без блокировки рендеринга и т. д. К примеру, в случае скриптов — это означает невозможность использования атрибута <code>defer</code>, а для стилей — необходимость скрывать контент страницы до момента полной загрузки стилей. Про то, как можно обойти эти нюансы, и про другие возможности данного плагина можно прочитать в <a href="https://github.com/TrigenSoftware/bdsl-webpack-plugin/blob/master/README.md">документации</a> и в <a href="https://github.com/TrigenSoftware/bdsl-webpack-plugin#examples">примерах использования</a>.</p>
<h2>Транспиляция зависимостей</h2>
<p>Из уже прочитанной части статьи мы узнали несколько способов того, как с помощью Browserslist можно уменьшить размер <em>собственного</em> кода сайта, но остаётся другая часть итогового бандла — зависимости. В веб-приложениях размер зависимостей в итоговом бандле может занимать весомую часть.</p>
<p>По умолчанию, процесс сборки должен избегать транспиляцию зависимостей, так как тогда сборка будет занимать много времени, да и сами зависимости, если их исходники используют неподдерживаемый синтаксис, обычно распространяются уже транспилированными. На практике же можно выделить три типа пакетов:</p>
<ol>
<li>c транспилированным кодом;</li>
<li>c транспилированным и исходным кодом;</li>
<li>c кодом на актуальном синтаксисе только для новых браузеров.</li>
</ol>
<p>С первым типом, очевидно, ничего сделать не получится. Второй — нужно настроить бандлер для работы только с исходной версией кода в пакете. Третий тип — для работы кода даже в не очень актуальных браузерах всё равно необходимо транспилировать.</p>
<p>Так как общепринятого способа делать пакеты с несколькими версиями бандла нет, то опишу как я делаю такие пакеты: обычная транспилированная версия имеет расширение <code>.js</code>, и главный файл записывается в поле <code>main</code> файла <code>package.json</code>, а версия бандла без транспиляции имеет расширение <code>.babel.js</code>, и главный файл записывается в поле <code>raw</code>. Вот реальный пример — пакет <a href="https://unpkg.com/browse/canvg/">Canvg</a>. Но можно делать и по-другому, например, как это сделано <a href="https://unpkg.com/browse/preact/">в пакете Preact</a> — исходники выделены в отдельную папку, а в <code>package.json</code> есть поле <code>source</code>.</p>
<p>Для того, чтобы Webpack мог работать с такими пакетами, нужно модифицировать секцию <code>resolve</code>:</p>
<pre><code tabindex="0" class="language-js">{
    /* … */
    resolve: {
        mainFields: [
            'raw',
            'source',
            'browser',
            'module',
            'main'
        ],
        extensions: [
            '.babel.js',
            '.js',
            '.jsx',
            '.json'
        ]
    }
    /* … */
}
</code></pre>
<p>Таким образом мы указываем Webpack как он должен искать файлы в пакетах, которые он должен использовать для сборки. Дальше нам остаётся настроить <a href="https://github.com/babel/babel-loader">babel-loader</a>:</p>
<pre><code tabindex="0" class="language-js">{
    /* … */
    test: /\.js$/,
    exclude: _ =&gt; /node_modules/.test(_) &amp;&amp; !/(node_modules\/some-modern-package)|(\.babel\.js$)/.test(_),
    loader: 'babel-loader'
    /* … */
}
</code></pre>
<p>Логика простая: просим игнорировать всё из <code>node_modules</code>, за исключением конкретных пакетов и файлов с определенным расширением.</p>
<h2>Результаты</h2>
<p>Я замерил размеры бандла и скорости загрузки до и после применения дифференциальной загрузки вместе с транспиляцией зависимостей, на примере сайта <a href="https://github.com/TrigenSoftware/DevFest-Siberia">DevFest Siberia 2019</a>:</p>
<div class="content__table-wrapper">
    <table>
        <caption>Размеры бандла</caption>
        <thead>
            <tr>
                <th></th>
                <th>Без сжатия</th>
                <th>После сжатия</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>Без DSL</td>
                <td>1,08 Мб</td>
                <td>292 Кб</td>
            </tr>
            <tr>
                <td>С плагином BDSL для Webpack</td>
                <td>0,80 Мб</td>
                <td>218 Кб</td>
            </tr>
        </tbody>
    </table>
</div>
<div class="content__table-wrapper">
    <table>
        <caption>Среднее время загрузки, мс</caption>
        <thead>
            <tr>
                <th></th>
                <th>Обычный интернет</th>
                <th>Обычный 4G</th>
                <th>Хороший 3G</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>Без DSL</td>
                <td>1,511</td>
                <td>4,240</td>
                <td>8,696</td>
            </tr>
            <tr>
                <td>С плагином BDSL для Webpack</td>
                <td>1,594</td>
                <td>3,409</td>
                <td>8,561</td>
            </tr>
        </tbody>
    </table>
</div>
<div class="content__table-wrapper">
    <table>
        <caption>Лучшее время загрузки, мс</caption>
        <thead>
            <tr>
                <th></th>
                <th>Обычный интернет</th>
                <th>Обычный 4G</th>
                <th>Хороший 3G</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>Без DSL</td>
                <td>1,266</td>
                <td>3,366</td>
                <td>8,349</td>
            </tr>
            <tr>
                <td>С плагином BDSL для Webpack</td>
                <td>1,143</td>
                <td>3,142</td>
                <td>6,673</td>
            </tr>
        </tbody>
    </table>
</div>
<p>В итоге получился прирост скорости загрузки и размер бандла уменьшился на ≈20%, <a href="https://gist.github.com/dangreen/5427c5f2158c357bf0b15d38270508ac">читайте более подробный отчёт</a>. Также вы можете самостоятельно провести подобные замеры — необходимый скрипт есть <a href="https://github.com/TrigenSoftware/bdsl-webpack-plugin#metrics">в репозитории bdsl-webpack-plugin</a>.</p>
<h3>Источники</h3>
<ul>
<li><a href="https://www.smashingmagazine.com/2018/10/smart-bundling-legacy-code-browsers/">Smart Bundling: How To Serve Legacy Code Only To Legacy Browsers</a>, Шабхам Канодия</li>
<li><a href="https://jasonformat.com/modern-script-loading/">Modern Script Loading</a>, Джейсон Миллер</li>
</ul>

                    ]]></description><pubDate>Sun, 19 Apr 2020 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/speed-up-with-browserslist/</guid></item><item><title>Чему меня научил год изучения и преподавания доступности</title><link>https://web-standards.ru/articles/what-accessibility-taught-me/</link><description><![CDATA[
                        <p>Ещё несколько лет назад я не знала значения термина «доступность». Я создавала сайты, которые были доступны лишь частично, <strong>так как понятия не имела как сделать лучше.</strong> На сегодняшний день мне известно достаточно, чтобы писать, говорить о доступности и проводить мастер-классы, помогая другим создавать более доступный и инклюзивный веб. Как и многие в нашей сфере, я всё ещё нахожусь в процессе обучения. Но с тех пор, когда я только начинала, я усвоила множество ценных уроков и ключевых ценностей, которым следую в своей работе по сей день. Вот некоторые из них.</p>
<h2>Семантический HTML — основа по-настоящему доступного веба</h2>
<p>Семантический HTML — это универсальный язык, который понимают все устройства, имеющие доступ к интернету, язык, который вы используете для передачи вашего контента этим устройствам.</p>
<p>HTML семантичен или, другими словами, описателен и отвечает за передачу смысла, каждый HTML-элемент описывает тип контента, который он представляет. Таким образом, если перед вами заголовок — вы используете элемент, соответствующий заголовку. Если абзац — тег <code>&lt;p&gt;</code>. Иными словами, подразумевается использование корректных HTML-элементов по их назначению.</p>
<p>Использование корректных элементов гарантирует вашему контенту передаваемую структуру и смысл.</p>
<p>Важность структуры заключается в том, что она способствует совместимости. Совместимость — это способность разных систем, устройств, приложений или продуктов взаимодействовать согласованно, без приложения усилий пользователем. Иначе говоря, это позволяет большему количеству устройств интерпретировать и получать доступ к вашему контенту, включая те, что появятся в будущем.</p>
<p>Структура помогает приложениям для чтения и режимам чтения (подобным тому, который есть в Safari), а также таким средам, как режим высокой контрастности в Windows, интерпретировать ваш контент и стилизовать его тем способом, который приведёт к улучшению интерфейса. Это возможно только тогда, когда использованы соответствующие семантические HTML-элементы, такие как <code>&lt;article&gt;</code>, <code>&lt;h1&gt;</code>, <code>&lt;ul&gt;</code>, <code>&lt;aside&gt;</code>, <code>&lt;nav&gt;</code> и многие другие имеющиеся в HTML. Данные элементы описывают тип своего содержимого. Без них приложения будут неспособны рассказать, что оно из себя представляет и, следовательно, не смогут стилизовать его надлежащим образом. Это повысит риск того, что контент станет менее доступным, если не утратит это свойство вообще.</p>
<figure>
    <img src="https://web-standards.ru/articles/what-accessibility-taught-me/images/semantic-vs-nosemantic.png" alt="">
    <figcaption>
        Cтраница Мэнди Майкл <a href="https://medium.com/@mandy.michael/building-websites-for-safari-reader-mode-and-other-reading-apps-1562913c86c9">демонстрирует</a>, как статья, размеченная несемантично (дивами, слева) и семантично (подходящими элементами, справа) выглядит в Instapaper.
    </figcaption>
</figure>
<p>Структура также важна, поскольку позволяет пользователям более эффективно перемещаться по контенту. Пользователи скринридеров полагаются на соответствующую структуру документа, чтобы быстрее попадать на необходимые области страницы, используя различные комбинации клавиш, специальные для скринридеров. Если вы не используете соответствующие ориентиры (или «landmarks», определяемые скринридерами через семантику таких HTML-элементов, как <code>&lt;nav&gt;</code>, <code>&lt;main&gt;</code> или <code>&lt;header&gt;</code>), пользователи скринридеров могут лишиться возможности эффективно перемещаться по странице, а достижение нужных частей сайта станет более утомительным.</p>
<figure>
    <img src="https://web-standards.ru/articles/what-accessibility-taught-me/images/voiceover-menu.png" alt="">
    <figcaption>
        Меню VoiceOver показывает все ориентиры, доступные на странице, позволяя пользователю перемещаться в нужные части страницы.
    </figcaption>
</figure>
<p>Заголовки также предоставляют схему или некий каркас страницы, по которым пользователи могут перемещаться при помощи комбинаций клавиш и которые соответствуют визуальной структуре вашего контента, доступной зрячим пользователям. Вот почему нужно использовать правильные уровни заголовков, вне зависимости от того, как они выглядят на самом деле. Заметьте, что, хотя мы и можем стилизовать заголовки по-разному, там, где это необходимо, визуальная согласованность по-прежнему очень важна.</p>
<figure>
    <img src="https://web-standards.ru/articles/what-accessibility-taught-me/images/voiceover-menu-1.png" alt="">
    <figcaption>
        Меню VoiceOver показывает все заголовки, доступные на странице, позволяя пользователю ориентироваться на странице более эффективно.
    </figcaption>
</figure>
<p>Семантика также выражает назначение. HTML-элементы, как отмечает Джереми Кит в своей книге «<a href="https://resilientwebdesign.com/">Resilient Web Design</a>» — это словарь значений. Использование соответствующих HTML-элементов позволяет различным приложениям и устройствам передавать значение вашего содержимого пользователям таким образом, чтобы им было понятно, что от него ожидать и как с ним взаимодействовать.</p>
<p>Например, когда вы используете <code>&lt;button&gt;</code> для создания кнопки, скринридер отображает кнопку, как она есть, и пользователи знают, что они могут совершить с ней определённое действие (обычно обусловленное наличием у последней доступной подписи), нажав на клавиши Space или Enter. Так, они получат функциональность и доступность элемента <code>&lt;button&gt;</code>, встроенные в него по умолчанию.</p>
<p>Если же вы не используете <code>&lt;button&gt;</code> для создания кнопки, а выбираете вместо этого, скажем, <code>&lt;div&gt;</code>, вы теряете всю встроенную семантику, а также возможность доступа с клавиатуры, делая кнопку полностью недоступной для скринридеров.</p>
<blockquote>
<p>Но ведь можно использовать ARIA атрибуты, чтобы превратить <code>&lt;div&gt;</code> в <code>&lt;button&gt;</code>! Так?</p>
</blockquote>
<p>Ну, и да, и нет…</p>
<h2>ARIA — это полифилл для HTML-семантики</h2>
<p>ARIA-атрибуты — вероятно, наиболее мощный инструмент в нашем арсенале доступности. Многие ARIA-атрибуты имитируют нативную семантику HTML, в то время как некоторые предоставляют ту, что ещё не представлена в HTML. Однако, они не меняют поведение, не добавляют функциональности, состояния фокуса или возможности управления с клавиатуры. Так что, если вы решили пойти по пути создания кнопки с помощью <code>&lt;div&gt;</code>, вам придётся добавить эту функциональность при помощи JavaScript. Но зачем создавать столь хрупкую реализацию того, что уже предоставляется вам браузерами?</p>
<p><a href="https://www.w3.org/TR/using-aria/#rule1">Первое правило ARIA гласит</a>:</p>
<blockquote>
<p>Если для того, чтобы сделать элемент доступным, вместо переназначения роли элемента с помощью ARIA вы можете использовать нативный HTML-элемент или семантический атрибут с <strong>уже встроенным</strong>, необходимым поведением, <strong>то так и делайте.</strong></p>
</blockquote>
<p>Так что пока нам следует полагаться на использование нативных HTML-элементов везде, где это возможно, однако есть некоторые виджеты, которые мы можем создать только при помощи ARIA. Например, не существует нативного эквивалента для табов (например, таких элементов, которые имели бы роли <code>tab</code>, <code>tablist</code> или <code>tabpanel</code>) со всей встроенной в них по умолчанию интерактивностью, поэтому мы можем создавать табы, меняя роли другим элементам с помощью ARIA, предоставляя тем самым комбинированный UI для пользователей скринридеров.</p>
<p>Так как ARIA кардинально меняет и доступное отображение элементов, при некорректном использовании это может оказать негативное влияние на то, каким образом ваш контент будет показан людям, использующим вспомогательные технологии. <a href="https://scottohara.me/">Скотт Охара</a>, ссылается на практические рекомендации ARIA:</p>
<blockquote>
<p>Первый принцип применения ARIA гласит, что «роль — это обещание». Используя ARIA или, раз уж на то пошло, HTML, задумайтесь, что именно вы обещаете людям, которые пользуются вашими интерфейсами или потребляют ваш контент. Сдерживаете ли вы обещания, которые им даёте?
<a href="https://scottohara.me/">Скотт Охара</a></p>
</blockquote>
<p>Прежде чем использовать ARIA-атрибуты, подумайте о том, что именно вы обещаете вашим пользователям. ARIA-атрибуты прямо влияют на то, как элементы будут размещены в дереве доступности и, следовательно, каким образом с ними будут взаимодействовать пользователи. Если вы нуждаетесь в указаниях, как следует и как не следует их использовать, Документация по использованию ARIA — лучший для этого источник.</p>
<blockquote>
<p>Главный совет от <a href="https://twitter.com/LeonieWatson">@LeonieWatson</a> заключается в том, что существует 147 HTML-элементов и только два из них не имеют встроенных возможностей доступности — <code>&lt;div&gt;</code> и <code>&lt;span&gt;</code>. Используйте семантически правильные элементы по максимуму!
<a href="https://twitter.com/edinbeth/status/1176161245352927237">Бет Фрейзер</a></p>
</blockquote>
<blockquote>
<p>Простейший #a11y совет, который я когда-либо получала: <code>&lt;div&gt;</code> — последнее к чему вам следует обращаться. Имеете дело с основным контентом? Для этого есть свой тег. С заголовком? И для него тоже. <code>&lt;div&gt;</code> — это не плохо; просто он ничего не значит. И это совершенно точно означает, что не стоит использовать его для чего-то, на что можно кликнуть.
Смотрите на ваш контент, как на что-то, имеющее предназначение, и пытайтесь подобрать под это подходящий тег. В ином случае <code>&lt;div&gt;</code> (или <code>&lt;span&gt;</code>) подойдёт. Для <code>&lt;div&gt;</code> есть своё место, однако большинство учебников преувеличивают его значение.
<a href="https://twitter.com/codeability/status/1162861059822112770">И. Дж. Мейсон</a></p>
</blockquote>
<h2>JavaScript необходим для по-настоящему доступных кастомных интерактивных компонентов</h2>
<p>И хотя вы можете обойтись без него, создавая функциональные интерактивные компоненты, такие, как модальные окна на чистом CSS или раскрывающиеся виджеты с помощью печально известного чекбокс-хака, это почти всегда гарантирует, что такие компоненты не будут по-настоящему доступными.</p>
<p>Когда вы создаёте интерактивный виджет, с большей долей вероятности он будет иметь состояние. Раскрывающийся виджет имеет либо открытое состояние, либо закрытое (или развёрнутое и свёрнутое). Это состояние видно пользователям скринридеров посредством ARIA-атрибутов (например, <code>aria-expanded=&quot;true&quot;</code> или <code>false</code>) и когда пользователи взаимодействуют с виджетом, состояние меняется и эти изменения передаются пользователю. JavaScript для этого необходим. Я смирилась с этим фактом два года назад, когда мне нужно было создать доступный тултип для одного из моих клиентов.</p>
<p>JavaScript также необходим для того, чтобы добавлять кастомным компонентам возможность взаимодействовать с клавиатурой (например, табы должны переключаться с помощью клавиш со стрелками, а раскрывающийся виджет быть управляемым клавишами Space и Enter).</p>
<p><strong>Примечание:</strong> даже если JavaScript необходим для создания интерактивных кастомных компонентов, для того чтобы быть уверенным, что ваш контент инклюзивен для всех пользователей вне зависимости от их особенностей, вы можете и, вероятно, должны — везде, где это возможно — убедиться, что они могут воспринимать ваш контент, когда JavaScript недоступен. Фактически, никогда не следует рассчитывать на то, что у ваших пользователей он есть, так как существует <a href="https://kryogenix.org/code/browser/everyonehasjs.html">множество</a> причин, почему его может не быть. Вот почему прогрессивное улучшение является наиболее доступной стратегией для создания веба. Я бы даже пошла ещё дальше и сказала, что в некоторых случаях оно абсолютно необходимо для создания инклюзивных документов и компонентов.</p>
<h2>Прогрессивное улучшение — это инклюзивная стратегия для создания веба</h2>
<p>Кажется, что среди многих разработчиков сложилось неправильное представление о том, что раз JavaScript необходим для обеспечения доступности интерактивного компонента, вы не сможете создать его, используя подход прогрессивного улучшения. Причина в том, что разработчики рассматривают прогрессивное улучшение как некий анти-JavaScript-паттерн. Это заблуждение.</p>
<p>Прогрессивное улучшение можно рассматривать, как своего рода наслоение — всё начинается с гибкой основы — семантического HTML — и лишь затем сверху, при необходимости, наслаиваются стили и функциональность (JavaScript).</p>
<p>С первого дня, как я узнала о прогрессивном улучшении, оно стало для меня основной движущей силой в создании интерфейсов. Сейчас я не могу себе представить другого подхода к веб-разработке. И с тех пор, как я начала создавать интерфейсы, держа в уме доступность, я пришла к убеждению, что этот подход наиболее разумный.</p>
<p>Давайте в качестве примера снова возьмём компонент табов. В таком компоненте у вас есть серия вкладок, которые управляют блоками с содержимым. Пользователь может переключаться между блоками, активируя ту или иную вкладку. Для того чтобы работать и быть доступными, табам необходим JavaScript. Но что, если JavaScript не работает? Если бы табы были созданы без использования прогрессивного улучшения — то есть в полной уверенности разработчика, что JavaScript у пользователя всегда будет включен, они были бы не в состоянии работать и содержимое скрытых блоков осталось бы полностью недоступным.</p>
<p>Но если бы вы подошли к табам с точки зрения прогрессивного улучшения, вы бы рассмотрели вариант создания компонента с помощью одного только HTML — без использования CSS, добавляющего интерфейсу возможность взаимодействия и без JavaScript, запускающего его функциональность.</p>
<p>В ситуации, когда JavaScript недоступен, можно предусмотреть отображение вкладок, как последовательность секций с заголовком и содержимым, которую может сопровождать оглавление в начале страницы. В таком виде пользователи будут видеть компонент по умолчанию при выключенном JavaScript.</p>
<figure>
    <img src="https://web-standards.ru/articles/what-accessibility-taught-me/images/tabs-as-section.png" alt="">
    <figcaption>
        Табы могут быть прогрессивно улучшены как последовательность секций с заголовком и соответствующим ей содержимым.
    </figcaption>
</figure>
<figure>
    <img src="https://web-standards.ru/articles/what-accessibility-taught-me/images/tabs-as-section-with-contents.png" alt="">
    <figcaption>
        Табы могут быть прогрессивно улучшены как последовательность секций с заголовком и соответствующим ей содержимым, а также с оглавлением, предваряющим эти секции.
    </figcaption>
</figure>
<p>Когда JavaScript заработает, вы сможете улучшить эти секции, изменив их раскладку и отображение, а также добавив необходимую интерактивность. Я бы реализовала это, добавив в разметку флаг, по которому определяла бы наличие JavaScript и, в зависимости от этого, необходимость изменения сетки секций и активации функционала табов.</p>
<p>Когда вы применяете к доступности <a href="https://www.deque.com/shift-left/">подход «shift left»</a> <em>(привлекаете тестировщиков на ранних этапах разработки — прим. переводчика),</em> использование стратегии прогрессивного улучшения приобретает ещё больший смысл. Почти каждый кастомный интерактивный компонент, созданный мной за последний годы, представлял собой улучшение базового не интерактивного компонента. В первую очередь, начинайте с HTML. Задействуйте всё, что он может вам предложить для обеспечения базовой доступности интерфейса.</p>
<blockquote>
<p>Прогрессивное улучшение. Потому что однажды ваш JavaScript не сработает. Будьте к этому готовы.
<a href="https://www.kryogenix.org/">Стюарт Лэнгридж</a></p>
</blockquote>
<h2>Дизайн не всегда диктует реализацию</h2>
<p>Одним из самых распространённых способов, который, на мой взгляд, способен разрушить семантику является извлечение её из визуального дизайна. Потому что визуальный дизайн не всегда описывает тип контента, который он представляет.</p>
<p>Это прекрасно иллюстрирует пример с заголовками. В недавнем докладе я показывала пример из одного моего последнего проекта, где страница была спроектирована таким образом, что главного заголовка, казалось, не существует, хотя это было не так. Он просто-напросто не был стилизован как заголовок первого уровня.</p>
<figure>
    <img src="https://web-standards.ru/articles/what-accessibility-taught-me/images/main-heading-of-the-page.png" alt="">
    <figcaption>
        Главный заголовок страницы, который описывает содержимое главного раздела страницы, расположен и стилизован таким образом, что (ненамеренно) маскирует факт того, что он им является.
    </figcaption>
</figure>
<p>Размышляя над структурой документа и ожиданиями пользователей скринридеров, я узнала, что главный заголовок странице необходим. До недавних пор я должна была предусматривать заголовок для пользователей скринридеров только на тех страницах, где он визуально отсутствовал. В этом же конкретном случае заголовок уже был там, просто стилизованный таким образом, что выглядел совсем иначе.</p>
<p>Глядя на страницу через призму доступности и держа в уме HTML — семантику и структуру — поменялось моё видение страницы, а также кардинально изменился подход к работе над ней.</p>
<p>Так что если компонент или элемент выглядят как нечто определённое, это не означает, что они непременно этим являются. И наоборот, если они не выглядят как нечто определённое, это не означает, что они этим не являются.</p>
<p>То же самое справедливо и для интерактивных UI-паттернов. Один и тот же паттерн может работать по-разному, в зависимости от того в каком контексте находится. И часто именно контекст определяет поведение паттерна и то, каким должен быть пользовательский опыт, а это, в свою очередь, должно составлять основу семантики, от которой зависит сама реализация.</p>
<p>В том же самом проекте, который я упоминала, был простой на вид UI-паттерн, который оказался достаточно интересным испытанием UX и доступности. На следующем изображении скриншот этого компонента:</p>
<figure>
    <img src="https://web-standards.ru/articles/what-accessibility-taught-me/images/videoplayer-example.png" alt="">
    <figcaption>
        Компонент из моего недавнего коммерческого проекта состоит из видео слева и плейлиста справа. При клике на любое из названий справа загружается видео слева; однако, это не инициирует его запуск. (Заметьте, что содержимое на скриншоте — просто пример).
    </figcaption>
</figure>
<p>Для реализации этого видеоплееера, мне нужно было разобраться, как он работает — чтобы я могла сверстать его таким образом, чтобы передать его семантику и функциональность скринридерам должным образом.</p>
<p>И хотя плейлист справа выглядит как список ссылок, <em>в действительности</em> — это не так, потому что предполагается, что ссылка должна вас куда-то вести, чего в этом случае не происходит. При клике на название видео оно загрузится в плеере слева. Значит, это интерактивный элемент, который совершает некое действие и, следовательно, не является ссылкой. И хотя их внешний вид не демонстрирует этого, эти названия на самом деле являются <em>кнопками.</em></p>
<p>Затем возник вопрос, что случится, если кликнуть на само название? Сработает ли автозапуск? Если да, то кнопка должна отвечать также и за постановку на паузу, играя тем самым роль кнопки-переключателя. Но если клик на название запускает и ставит видео на паузу вы также захотите ассоциировать эту кнопку и с кнопками запуска и паузы внутри самого видео, что может стать проблемой, учитывая, что видео может быть с YouTube, Vimeo или со своё. И если вы не запускаете видео автоматически, должны ли вы устанавливать фокус на <code>&lt;iframe&gt;</code> после того, как нажали на кнопку?</p>
<p>После изучения предполагаемого UX и тестирования скринридерами, я, в конечном счёте, реализовала плеер с помощью табов, где каждая вкладка управляет блоком содержимого.</p>
<p>Я никогда прежде не думала об интерфейсе табов, в котором несколько вкладок управляют одним блоком. Но контекст этого компонента и его UX определили ход моих мыслей и соображения, которые привели меня к конечной реализации.</p>
<p>Первое, что я усвоила из этого компонента — UX определяет реализацию. ARIA поставляет множество атрибутов, которые позволяют нам создавать разные UI-паттерны в разных контекстах, но иногда нам всем нужна возможность немного модифицировать паттерн для конкретного контекста.</p>
<blockquote>
<p>Странно, что мы до сих пор извлекаем семантику из визуального дизайна, а не наоборот. Визуальный дизайн может отличаться в зависимости от контекста, в то время, как основная семантика — нет.
<a href="https://twitter.com/rikschennink/status/1166771758684356608">Рик Шеннинк</a></p>
</blockquote>
<h2>Если что-то технически доступно, не значит, что оно инклюзивно</h2>
<p>Вы можете создать что-то, что будет технически доступным, но при этом не будет инклюзивным. Такой элемент или компонент может иметь все необходимые кнопки, доступные с клавиатуры, а также видимые скринридерами, но действительно ли вы приняли во внимание потребности и ожидания ваших пользователей, когда принимали решения насчёт отображения того или иного элемента и взаимодействия с ним?</p>
<p>В докладе <a href="http://feather.ca/inclusion/aea2019">«Inclusive by Design»</a> Дерек Фэверстон, пропагандист доступности и дизайнер, рассказывает о том, как он и его команда создавали доступный видеоплеер для одной организации.</p>
<p>Когда пришло время и компонент понадобился для использования пользователями с разными особенностями здоровья, команда осознала, что, хотя они создали прекрасный и доступный видеоплеер, его нельзя было назвать по-настоящему инклюзивным — в нём была упущена разного рода функциональность, которая упрощала бы его использование для некоторых групп пользователей, такая как, например, перемотка вперёд или назад. Дерек и его команда также пытались предположить, как плеер мог бы использоваться всеми пользователями, и поняли, что не учли публику со старыми версиями скринридеров. Они рисковали пропустить важные объявления — те самые, что должны были помочь в управлении. Так, после нескольких итераций и тестов с разными пользователями, они в конечном счёте добавили в плеер необходимые возможности, приняв в расчёт все его недостатки и ожидания пользователей, что сделало плеер гораздо более инклюзивным и чрезвычайно улучшило его UX.</p>
<p>Доклад Дерека полон множества хороших примеров, которые подчёркивают важность <strong>своевременного вовлечения пользователей ещё на этапе проектирования</strong>, а также убеждают вас учитывать возможные различия по умолчанию.</p>
<blockquote>
<p>Ничего о нас без нас.
Майкл Масута</p>
</blockquote>
<h2>В конце концов, всё это только ради пользователя</h2>
<p>Если в процессе разработки вы будете держать в уме реальные потребности пользователей и инклюзивность, то в скором времени осознаете, что реализация спроектированного паттерна может быть достигнута разными способами. А в проектировании доступности предостаточно категоричных моментов.</p>
<p>Прекрасный пример — модальные окна. Несмотря на то, что этот UI-паттерн всех раздражает, существует множество дискуссий вокруг того, каким образом он должен быть реализован и как должен себя вести, когда модальное окно открыто: следует ли устанавливать фокус на первом доступном для этого элементе внутри модального окна. Что если таких элементов нет? Что если первый элемент, доступный для фокуса — это кнопка закрытия окна? Хотели бы вы, чтобы модальное окно побуждало вас к его скорейшему закрытию? (Очевидно, нет). Что если первый элемент, доступный для фокуса — это поле, требующее от пользователя адрес его электронной почты? Допустимо ли без какого-либо контекста требовать от пользователя личную информацию? (Очевидно, что так же нет).</p>
<p>В конце концов, не важно какое решение вы примете, оно всегда должно быть принято для пользователя. Поэтому вовлечение пользователя или более разнообразной команды в проектирование и процесс разработки — это ключевой момент в построении по-настоящему инклюзивных веб-интерфейсов.</p>
<p>Но что если у вас нет такой возможности? Что если у вас нет доступа к такому окружению или команде? Что если вы, как и я — независимый разработчик, который часто работает с командами, не имеющими доступа к пользователям или людям с особенностями здоровья.</p>
<h2>Обратитесь за советом, если сомневаетесь</h2>
<p>Если вы не в силах сделать это сами, вы можете воспользоваться опытом или советами тех, кто через это прошёл. И, что очень важно, быть готовыми и открытыми для конструктивного фидбека.</p>
<p>В нашей сфере есть уйма чудесных (иногда по понятным причинам довольно раздражительных) экспертов по доступности, которые <em>мечтают,</em> чтобы веб был более доступным. Большинство из них бесплатно дают ценные советы и делятся своими ценными знаниями в разной форме — пишут статьи и книги, создают видеокурсы, читают доклады и устраивают воркшопы и так далее. Мы многому могли бы научиться у них. И большинство из них открыты к вопросам любой степени сложности (и платно, и бесплатно).</p>
<p>Некоторые из моих авторитетных экспертов (порядок не имеет значения):</p>
<ul>
<li><a href="https://tink.uk/">Leonie Watson</a></li>
<li><a href="http://feather.ca/">Derek Featherstone</a></li>
<li><a href="http://scottohara.me/">Scott O’Hara</a></li>
<li><a href="https://marcysutton.com/">Marcy Sutton</a></li>
<li><a href="https://www.youtube.com/channel/UCJAtIv92EJqzG2EOzo92sdQ">Rob Dodson</a></li>
<li><a href="https://twitter.com/sundress">Alice Boxhall</a></li>
<li><a href="https://marcozehe.de/">Marco Zehe</a></li>
<li><a href="https://ericwbailey.design/">Eric Bailey</a></li>
<li><a href="https://twitter.com/stevefaulkner">Steve Faulkner</a></li>
<li><a href="https://www.paciellogroup.com/">The Paciello Group</a></li>
</ul>
<p>Перед тем как обратиться за помощью, вы поступите правильно, уделив время чтению спецификаций и рекомендаций по ARIA, чтобы попытаться самим узнать о доступности как можно больше. В конце концов, эти добрые люди способны помочь, но они не обязаны делать нашу работу за нас — по крайней мере, не бесплатно.</p>
<h2>Заключение</h2>
<p>Доступность — это непросто. Чаще всего это откровенно тяжело. Но это издержки профессии. Проектирование для человека — вот что, на самом деле, тяжело. А доступность, в конечном счёте, целиком <em>про</em> человека и <em>для</em> человека.</p>
<p>Мы можем делать это не до конца правильно, всегда есть место улучшениям — тем более, что всё большее количество пользователей используют наши продукты и потребляют наш контент, но одна вещь, которую я знаю точно, это то, что мы никогда не должны отбивать у них желание к этому. Так или иначе, почти всё можно улучшить. Самое главное — быть открытыми для обратной связи, быть достаточно чуткими, чтобы беспокоиться о своих пользователях и стараться изо всех сил, чтобы сделать их жизнь проще с теми (мощными) инструментами, которые есть в нашем распоряжении.</p>
<p>Используйте HTML. Совершенствуйте сделанное при помощи CSS. Добавляйте мощи JavaScript. Вовлекайте ваших пользователей в дизайн-процессы как можно раньше и всегда смотрите на вашу работу сквозь призму инклюзивности. Так, вы достигнете большего. Продолжайте учиться, повторяйте выученное и становитесь лучше. Ну, и не забывайте, что возможности нашего здоровья не всегда будут неограниченными.</p>

                    ]]></description><pubDate>Fri, 10 Jan 2020 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/what-accessibility-taught-me/</guid></item><item><title>Введение в вариативные шрифты</title><link>https://web-standards.ru/articles/introduction-to-variable-fonts/</link><description><![CDATA[
                        <p>Типографика всегда вызывала у меня особый интерес, еще задолго до того, как мы смогли использовать шрифты в вебе. И хотя с тех пор прошло уже около десяти лет, всё это время мы были ограничены в выборе шрифтов, так как увеличение их количества напрямую влияло на количество трафика, которое приходилось загружать пользователю. И хотя грамотное использование типографики может положительно отразиться на дизайне, лёгкости восприятия и удобстве использования в целом, но стоит вам подключить слишком большое количество шрифтов, как тут же это негативно отразится на производительности и следовательно, на удобстве для пользователей. Три года назад был представлен результат развития формата шрифтов OpenType, который вносит удивительные изменения в работу со шрифтами.</p>
<h2>Представляем OpenType Font Variations или «вариативные шрифты»</h2>
<p>На протяжении всего времени использования шрифтов, мне приходилось устанавливать отдельные файлы для каждой ширины, веса (жирности) или варианта начертания, который хотел использовать. Жирный (bold) в одном файле, светлый (light) в другом, курсив в третьем. Формат вариативных шрифтов является развитием OpenType (формата, используемого нами в течение многих лет), который позволяет содержать в одном файле в оптимизированном виде всё то, что ранее было разделено на отдельные файлы. Дизайнер может решить, какие оси включать, а также определить минимальные и максимальные значения.</p>
<iframe src="https://codepen.io/jpmental/embed/preview/LYYowyb"></iframe>
<p>Если говорить о вебе, это значит, что мы можем загрузить единственный файл и использовать CSS для установки параметров его осей в любое значение из всего допустимого диапазона, без каких-либо искусственных искажений силами браузера. Некоторые шрифты могут иметь только одну ось (weight является наиболее распространённой), а у некоторых можно задавать сразу несколько. Несколько осей являются «зарегистрированными», так как наиболее распространены: <strong>width</strong> (ширина), <strong>weight</strong> (вес), <strong>slant</strong> (наклон), <strong>italic</strong> (курсив) и <strong>optical size</strong> (оптический размер) — но формат является расширяемым, так что дизайнеры могут определить собственные оси и позволить вносить необходимые изменения. Давайте рассмотрим, как это работает</p>
<h2>Прямо как раньше, но по-другому</h2>
<p>Один из способов, с помощью которого новый формат сохраняет обратную совместимость с другими приложениями, которые еще не поддерживают вариативные шрифты, это так называемые «именованные экземпляры» (named instances) — которые по сути являются псевдонимами для настройки данных, которые раньше помещались в отдельные файлы. Таким образом, независимо от того, что имел в виду дизайнер шрифта, называя его «плотным жирным», эти значения просто будут отражены на соответствующих точках вариативных осей веса и ширины. Если шрифт был создан корректно, его настройки позволят установить и использовать его в последних версиях Windows и macOS, будто они были изначально встроены в систему.</p>
<p>Если приложение полностью поддерживает вариативные шрифты, появится возможность манипулировать настройками отдельных осей так, как посчитаете нужным. В настоящее время к таким программам относятся последние версии Adobe Illustrator, Photoshop, InDesign, а также Sketch.</p>
<h2>Открытие секретов шрифтов</h2>
<p>Чтобы узнать о всех возможностях работы с вариативными шрифтами, необходимо либо воспользоваться упомянутым ниже сайтом, либо загрузить Firefox (или, ещё лучше, сделать и то, и другое).</p>
<p>Если у вас есть файл шрифтов и доступ к интернету, можете воспользоваться сайтом <a href="https://wakamaifondue.com/">Wakamai Fondue</a> Роель Нискенс, что расшифровывается как «What Can My Font Do» <em>(англ. «На что способен мой шрифт» — прим. переводчика).</em> Просто перетащите файл шрифта и получите отчёт, показывающий, какие у шрифта есть особенности, размер файла, количество глифов, а также поддерживаемые языки и вариативные оси. Вы получите даже тестировщик типа и несколько ползунков, которые позволят поиграть с изменением разных осей. Обратите внимание на оси, значения и настройки по умолчанию. Эта информация понадобится, когда мы начнём писать CSS.</p>
<img src="https://web-standards.ru/articles/introduction-to-variable-fonts/images/wakamaifondue.png" alt="">
<p>Если же у вас нет доступа к файлу шрифта, всё еще можно получить необходимую информацию, просто используя инструменты разработчика браузера Firefox. По ним есть много обучающих видео, например <a href="https://www.youtube.com/watch?v=UazfLa1O94M">это</a> или <a href="https://www.youtube.com/watch?v=RYP7jKMWkVY">это</a>.</p>
<p>Благодаря Джен Симмонс и команде Firefox Devtools, у нас есть несколько потрясающих инструментов для работы с веб-шрифтами прямо в браузере. В инструментах разработчика выберите текстовый элемент, использующий нужный шрифт, а затем перейдите в закладку «Fonts», расположенную справа. Вы попадёте на панель, содержащую в одном месте всю информацию о шрифте, размере, стиле и вариативных осях. Можно изменить любое из представленных значений и сразу увидеть результат в браузере, а перейдя на вкладку «Changes», легко скопировать изменённый CSS-код для переноса к себе.</p>
<img src="https://web-standards.ru/articles/introduction-to-variable-fonts/images/devtools-fonts.png" alt="">
<p>Теперь, когда у вас есть все доступные оси, значения, настройки по умолчанию, давайте посмотрим, как это использовать на практике. Первое, на что следует обратить внимание, это то, что теги пяти зарегистрированных осей пишутся строчными буквами (<code>wght</code>, <code>wdth</code>, <code>ital</code>, <code>slnt</code>, <code>opsz</code>), тогда как кастомные — всегда заглавными. Браузеры учитывают это, и несоответствие верхнего и нижнего регистра в названии может привести к непредсказуемым результатам.</p>
<p>Существует два способа задания параметров для зарегистрированных осей: через соответствующие им CSS-свойства, а также с помощью низкоуровневого синтаксиса <code>font-variation-settings</code>. Очень важно использовать стандартные свойства везде, где это возможно, поскольку это единственный способ браузеру узнать, что делать если по какой-то причине вариативный шрифт не загрузится, или любому альтернативному устройству просмотра узнать, что нужно вывести определённый вид семантики из нашего CSS (то есть, большее значение <code>font-weight</code>, означающее более жирный текст). Хотя для кастомных осей (и на данный момент для осей <code>italics</code> и <code>slant</code>) мы должны использовать <code>font-variation-settings</code>, параметры <code>font-weight</code> (<code>wght</code>) и <code>font-stretch</code> (<code>wdth</code>) полностью поддерживаются в каждом браузере, который поддерживает вариативные шрифты. Теперь давайте рассмотрим пять зарегистрированных осей и как их использовать.</p>
<h2>Weight (вес)</h2>
<p>Вероятно, наиболее очевидной осью является <code>weight</code>, поскольку почти каждый шрифт спроектирован как минимум, под обычное и жирное начертание. С вариативным шрифтом вы можете использовать стандартное свойтво <code>font-weight</code>, но указывать не только ключевые слова или значения, например, <code>normal</code> (<code>400</code>) или <code>bold</code> (<code>700</code>), а любое число в диапазоне между минимальным и максимальным значениями, определёнными для шрифта. Согласно спецификации OpenType, значение <code>400</code> должно соответствовать ключевому слову <code>normal</code> для всех шрифтов, но на практике вы увидите, что на данный момент оно может не совпадать и довольно сильно, в зависимости от шрифта.</p>
<pre><code tabindex="0" class="language-css">p {
    font-weight: 425;
}

strong {
    font-weight: 675;
}
</code></pre>
<iframe src="https://codepen.io/jpmental/embed/preview/QWWXBoQ"></iframe>
<h3>Почему вам это понравится</h3>
<p>Позволяет использовать более широкий диапазон значений. Например, для таких ситуаций, как большие кавычки с очень тонким весом (жирностью). Определение значения «чуть тоньше, чем жирное» для отдельных слов внутри жирного текста может повысить читаемость. Чем текст жирнее, тем более закрытыми выглядят символы, но стоит сделать их немного тоньше, как они станут более открытыми, но всё ещё будут придавать акцент (попробуйте установить <code>font-weight</code> где-то между <code>500</code> и <code>600</code> вместо <code>700</code>).</p>
<h2>Width (ширина)</h2>
<p>Другим распространённым значением при разработке шрифта является ширина. Этот параметр часто характеризуют терминами «плотный», «сжатый» или «расширенный», хотя то, что значат эти слова, является полностью субъективным. Согласно спецификации, 100 должно равняться ширине <code>normal</code>, а допустимые значения могут варьироваться от 1 до 1000. Как и вес, ширина соответствует существующему (и очень неудачно названному) CSS-свойству <code>font-stretch</code> и задаётся в процентах. На ранних стадиях внедрения технологии многие дизайнеры шрифтов не придерживались стандарта с числовыми диапазонами, так что это может выглядеть немного странно в вашем CSS. Но диапазон ширины в 3% — 5% всё еще является допустимым, даже если в этом случае 5% фактически является нормальной шириной. Надеюсь, со временем этот момент станет более стандартизированным</p>
<pre><code tabindex="0" class="language-css">p {
    font-stretch: 89%;
}
</code></pre>
<iframe src="https://codepen.io/jpmental/embed/preview/mddZwKE"></iframe>
<h3>Почему вам это понравится</h3>
<p>Одна из важных задач при разработке адаптивного дизайна — не допустить ситуацию, в которой текст больших заголовков, после каждого слова переносится на новую строку, не помещаясь в ширину маленьких экранов. Помимо настройки размера шрифта <code>font-size</code>, попробуйте сделать текст заголовка немного уже. Это позволит поместить больше слов на каждой строке без уменьшения размера шрифта, которое визуально может выглядеть как снижение акцента или нарушение иерархии заголовков</p>
<h2>Italic (курсив)</h2>
<p>Ось <code>italic</code> — это более-менее то, что вы и ожидаете. В большинстве случаев это логическое значение 0 или 1: выключено (вертикальный текст) или включено — обычно значит наклонное начертание и часто замену глифа. Часто строчные буквы «a» и «g» имеют немного отличающиеся формы курсива. Хотя, конечно, и существует возможность для данного свойства вместо строгих 0 или 1 задавать диапазон значений, сценарий с состояниями «включено» или «выключено» более распространён. К сожалению, хоть это и предназначено для соответствия свойству <code>font-style: italic</code>, это одна из тех возможностей, которую браузеры реализовали не полностью, поэтому нам остаётся полагаться на синтаксис более низкого уровня <code>font-variation-settings</code>. Вы уже могли задуматься о его использовании вместе с кастомными свойствами CSS, в этом случае вам не потребуется повторно объявлять всю строку, если нужно просто изменить курсивное или вертикальное начертание.</p>
<pre><code tabindex="0" class="language-css">:root {
    --text-ital: 0;
}

body {
    font-variation-settings: 'ital' var(--text-ital);
}

em {
    --text-ital: 1;
}
</code></pre>
<iframe src="https://codepen.io/jpmental/embed/preview/yLLdqWx"></iframe>
<h3>Почему вам это понравится</h3>
<p>Наличие курсива, вертикального начертания, а также веса и других доступных для настройки осей означает, что у вас появляется возможность использовать только один файл шрифта вместо четырёх. Ведь при наличии широкого диапазона доступных осей, вам может больше ничего и не понадобиться</p>
<h2>Slant (наклон)</h2>
<p>Ось наклона похожа на курсив, но имеет два ключевых отличия. Во-первых, она принимает значение в градусах, диапазон которых, согласно спецификации OpenType, должен быть «больше −90 и меньше +90». Во-вторых, она не предусматривает замену глифа (как <code>italic</code>). Обычно используется со шрифтом без засечек и допускает любое значение в указанном ранее диапазоне. Если используемый вами шрифт имеет только ось <code>slant</code>, но не имеет <code>italic</code> (я немного расскажу об этом), можно использовать стандартное свойство <code>font-style</code> например:</p>
<pre><code tabindex="0" class="language-css">em {
    font-style: oblique 12deg;
}
</code></pre>
<p>Если доступны обе оси, потребуется использовать <code>font-variation-settings</code>, и в этом случае понадобится просто задать числовое значение без указания единиц измерения <code>deg</code>.</p>
<pre><code tabindex="0" class="language-css">:root {
    --text-slnt: 0;
}

body {
    font-variation-settings: 'slnt' var(--text-slnt);
}

em {
    --text-slnt: 12;
}
</code></pre>
<iframe src="https://codepen.io/jpmental/embed/preview/bGGXGZr"></iframe>
<h2>Почему вам это понравится</h2>
<p>Ось наклона (<code>slant</code>) позволяет задавать любое значение в пределах всего допустимого диапазона, поэтому становится возможным небольшое изменение угла наклона текста. Например, можно добавить анимацию, при которой текст после загрузки страницы постепенно становится курсивным. Это хороший способ привлечь внимание к текстовому элемент на экране таким изящным образом</p>
<h2>Optical Size (оптический размер)</h2>
<p>Это настоящая жемчужина вариативных шрифтов. Данная практика существует более 400 лет. При её использовании физически меньший шрифт вырезался с немного более толстой обводкой и немного меньшим контрастом, чтобы обеспечить качественную печать и сохранить текст разборчивым даже при мелких размерах. Другие аспекты также могут быть адаптированы. Например, апертуры (отверстия) в буквах могут быть шире, более угловатые выступы или увеличенные скругления. И наоборот, крупные точки будут вырезаны более аккуратно, что позволит добиться большей контрастности и лучше детализации. Хотя этот подход использовался во многом из-за недостаточно качественных чернил, бумаги и шрифта — он всё же позволял использовать один эскиз шрифта в широком диапазоне физических размеров символов. Однако, эта технология перестала использоваться с переходом к цифровым шрифтам.</p>
<p>Концепция заключается в том, что числовое значение для этой оси должно соответствовать визуальному размеру шрифта, и именно для этого был введёно новое свойсвто: <code>font-optical-sizing</code>. По умолчанию, оно имеет значение <code>auto</code> и такое поведение поддерживается всеми современными браузерами. Можно либо отключить его — <code>off</code>, либо задать явное значение через <code>font-variation-settings</code>.</p>
<pre><code tabindex="0" class="language-css">body {
    font-optical-sizing: auto;
}
</code></pre>
<iframe src="https://codepen.io/jpmental/embed/preview/MWWNwJa"></iframe>
<p>Или:</p>
<pre><code tabindex="0" class="language-css">:root {
    --text-opsz: 16;
}

body {
    font-variation-settings: 'opsz' var(--text-opsz);
}

h1 {
    --text-opsz: 48;
    font-size: 3em;
}
</code></pre>
<iframe src="https://codepen.io/jpmental/embed/preview/ExxqaBy"></iframe>
<h3>Почему вам это понравится</h3>
<p>Правильный оптический размер делает шрифт более разборчивым при меньших размерах. Повышенная контрастность обводки (и всего остального, что решил изменить дизайнер шрифта) может означать, что один и тот же шрифт может существенно отличаться при его использовании в заголовке или теле статьи. Убедиться в этом можно на примере шрифта Roslindale от Дэвида Джонатана Росса, который <a href="https://rwt.io/">используется на моём сайте</a>. Я использую один шрифт и для заголовков и для остального текста.</p>
<h2>Slant и Italic (наклон и курсив)</h2>
<p>Не знаю, задумывались ли над этим создатели спецификации, когда составляли её, но технически вы можете задавать обе оси: и для наклона (то есть, угла) и для курсива (то есть, замены глифа). На самом деле и Дэвид Джонатан Росс и Стивен Никсон уже применили это со шрифтами Roslindale Italic и Roslindale Recursive соответственно. В случае с Roslindale Recursive вы можете увидеть, насколько больше гибкости вы можете получить, отделив угол от глифов. Возможность задать угол наклона без замены глифов может придать блоку текста совершенно другой вид. Учитывая то, что курсив и наклон используют одно и то же свойство <code>font-variation-settings</code>, его можно использовать для определения параметров обоих атрибутов.</p>
<pre><code tabindex="0" class="language-css">:root {
    --text-ital: 0;
    --text-slnt: 0;
}

body {
    font-variation-settings: 'ital' var(--text-ital), 'slnt' var(--text-slnt);
}

em {
    --text-ital: 1;
    --text-slnt: 12;
}

.slanted {
    --text-slnt: 12;
}

.italic-forms-only {
    --text-ital: 1;
}
</code></pre>
<iframe src="https://codepen.io/jpmental/embed/preview/BaaXKBq"></iframe>
<h2>Почему вам это понравится</h2>
<p>Наличие отдельной оси под каждый тип изменения шрифта может дать больше гибкости при создании системы типографики проекта. В одном случае вы можете выбрать только наклон, в другом — и наклон и замену глифов. Может это и не самая важная функция, но всё же она добавляет дополнительное измерение, в котором можно настраивать шрифт.</p>
<h2>Кастомные оси</h2>
<p>Пока что существует только пять зарегистрированных осей, но дизайнеры могут также создавать собственные. Любая составляющая шрифта потенциально может стать осью. Есть вполне привычные, такие как форма засечек или высота строчных (x-height), так и достаточно изобретательные. Более подробный их разбор я оставлю кому-нибудь другому, но продемонстрирую один, использование которого, как я надеюсь, станет более распространённым в текстовом и UI-дизайне — это Grade.</p>
<h2>Grade</h2>
<p>Понятие «grade» в типографике впервые было введено для регулирования толщины шрифта на разных типах бумаги и печатных станках. Концепция заключается в изменении веса (жирности) шрифта без изменения межбуквенного интервала и пространства, занимаемого одним символом. Наличие его в качестве переменной оси может быть полезно в нескольких отношениях. Создание участка текста с большей контрастностью, при котором символы становятся немного более жирными без переформатирования, может сделать текст более разборчивым при слабом освещении или при разработке тёмной темы. Либо при наведении на элемент, анимировать его с помощью плавного утолщения шрифта и изменения фона. Обратите внимание, что кастомные оси должны указываться заглавными буквами.</p>
<pre><code tabindex="0" class="language-css">:root {
    --text-GRAD: 0;
}

body {
    font-variation-settings: 'GRAD' var(--text-GRAD);
}

body.dark {
    --text-GRAD: 0.5;
}
</code></pre>
<iframe src="https://codepen.io/jpamental/embed/preview/GRRVqpV"></iframe>
<h3>Почему вам это понравится</h3>
<p>Я думаю, что чаще всего ось <code>GRADE</code> будет использоваться при разработке функционала, связанного с доступностью: тёмными темами или режимом высокой контрастности. Но никто не запрещает её применения при анимировании кнопок или навигации путём утолщения текста без изменения количества пространства, которое он занимает.</p>
<h2>Поддержка</h2>
<p>К счастью, поддержка вариативных шрифтов достаточно хорошая: последние версии macOS и Windows предлагают поддержку на уровне операционной системы, делая возможной их установку в вашу систему и отображение параметров в меню настройки шрифтов в любом приложении, как если бы это были отдельные шрифты. Если вы используете последние версии приложения Adobe CC такие как Illustrator, Photoshop или InDesign — или последние версии Sketch, вы можете изменять все доступные оси. Согласно <a href="https://caniuse.com/#feat=variable-fonts">Can I use</a>, поддержка находится на уровне 87%, включая наиболее популярные мобильные платформы и браузеры.</p>
<p>Единственное исключение — это IE11, но учитывая то, что при подключении вариативных шрифтов вы можете использовать <code>@supports</code>, получается. что вполне безопасно использовать их в продакшн уже сегодня. Этот подход используется на более 40 сайтах новой веб-платформы штата Джоржия в США и успешно поддерживает статические шрифты для госслужащих, использующих IE11 и вариативные шрифты для миллионов граждан по всему штату</p>
<pre><code tabindex="0" class="language-css">p {
    font-family: YourStaticFontFamily;
}

@supports (font-variation-settings: normal) {
    p {
        font-family: YourVariableFontFamily;
    }
}
</code></pre>
<p>Поскольку CSS всегда полностью анализируется перед выполнением любого другого действия, вы можете быть уверены, что браузеры никогда не загрузят оба шрифта сразу</p>
<h2>Добавление шрифтов в проект</h2>
<p>Вероятней всего, многим из вас пока что потребуется хранить вариативные шрифты у себя на сервере, так как на данный момент только Google предлагает их через собственные API и то в бета-версии. Существует несколько ключевых отличий в составлении <code>@font-face</code> объявления, давайте рассмотрим их:</p>
<pre><code tabindex="0" class="language-css">@font-face {
    font-family: 'Family Name';
    src:
        url('YourVariableFontName.woff2') format('woff2 supports variations'),
        url('YourVariableFontName.woff2') format('woff2-variations');
    font-weight: [low] [high];
    font-stretch: [low]% [high]%;
    font-style: oblique [low]deg [high]deg;
}
</code></pre>
<p>Первое, что вы могли заметить, это немного отличающаяся строка <code>src</code>. Я добавил два варианта синтаксиса, указывающих на один и тот же файл, потому что спецификация была обновлена, но в браузерах этот момент еще не актуализировали. Так как на горизонте в дополнение к вариативным виднеются еще и разноцветные шрифты (и возможность того, что некоторые будут и вариативными и цветными), синтаксис должен быть более гибким. Таким образом, первая запись указывает <code>woff2 supports variations color</code> для шрифта, который поддерживает и то и другое. Когда браузеры начнут понимать этот синтаксис, они будут останавливать распознавание строки <code>src</code> как только дойдут до него. До тех пор они будут пропускать эту строку и переходить ко второй с форматом <code>woff2-variations</code>, который понимают все современные браузеры, поддерживающие вариативные шрифты.</p>
<p>Для веса <code>font-weight</code> и ширины <code>font-stretch</code>, если доступна соответствующая ось, укажите минимальное и максимальное значения (со знаком процента для ширины). Если соответствующей оси нет, просто используйте ключевое слово <code>normal</code>. Стоит отметить, что если также присутствует ось курсива (вместе с осью наклона или без неё), на данный момент лучше всего просто полностью пропустить строку <code>font-style</code>.</p>
<p>Задавая эти граничные значения, вы помогаете браузеру понять, что делать, если CSS запросит значение, находящееся за пределами допустимого диапазона. Таким образом, если диапазон веса 300–700, а вы случайно указали <code>font-weight: 100</code>, браузер просто установит значение 300 и не будет пытаться синтезировать меньший вес. Стоит отметить, что это работает только со стандартными CSS-свойствами, такими как <code>font-weight</code> или <code>font-stretch</code>. Если для установки значений вы используете <code>font-variation-settings</code>, браузер предполагает, что вы являетесь экспертом и попытается синтезировать результат, даже если он выходит за пределы нормального диапазона.</p>
<h2>Google Fonts тоже подходит</h2>
<p>В сентябре 2019 команда Google Fonts анонсировала бета-версию своего API, который поддерживает некоторые вариативные шрифты. Поддержка растёт и впереди еще больше шрифтов. Если вы хотите опробовать это уже сейчас, можете <a href="https://rwt.io/typography-tips/variable-fonts-new-google-fonts-api">ознакомиться с моей статьей</a> о том, как это сделать и <a href="https://codepen.io/jpamental/pen/JjPvBpm">проверить CodePen</a>, в создании которого я применил упомянутый способ.</p>
<h2>Где их найти</h2>
<p>Место, с которого следует начинать поиски вариативных шрифтов — <a href="https://v-fonts.com/">v-fonts.com</a> Ника Шермана, который является каталогом, содержащим почти все доступные на данный момент вариативные шрифты. Также загляните на GitHub, где дополнительно <a href="https://github.com/search?q=variable+fonts">сможете найти проекты</a> на разных стадиях завершённости. Ник также ведёт Твиттер, где публикует анонсы и ссылки, а я добавляю новости по веб-типографике.</p>
<p>Вы также можете посетить сайт <a href="https://axis-praxis.org/">Axis-Praxis</a> Лоуренса Пенни, необычный каталог вариативных шрифтов, который позволяет добавить шрифт на специальную тестовую площадку, которая может сообщить вам дополнительную информацию о возможностях и особенностях выбранного шрифта.</p>
<h2>Почему всё это важно</h2>
<p>Хотя всё это может быть интересно чисто с академической точки зрения, существуют некоторые значительные преимущества и возможности от использования вариативных шрифтов. С точки зрения производительности, хотя вариативные шрифты могут иметь больший размер, чем отдельные файлы каждого начертания обычных шрифтов, они всё равно намного меньше совокупного размера этих файлов. Что значит, что время загрузки страницы может значительно улучшиться. Это именно та причина, по которой <a href="https://www.nngroup.com/articles/variable-fonts-wide-screen-layout/">Nielson/Norman Group использует шрифт Source Sans Variable</a> на своём сайте в течение последнего года, а <a href="https://rwt.io/typography-tips/what-web-wants">Google тестирует шрифт Oswald Variable</a> в течение последних месяцев. В основном, просто используя их вместо нескольких отдельных файлов для получения выгоды от более быстрой загрузки страниц.</p>
<p>Но помимо этого, что меня действительно радует, так это возможности дизайна. Когда в нашем распоряжении оказываются вариативные шрифты, мы можем подходить к вопросу типографики более творчески. Пример, приведённый ниже, не должен оставаться просто примером, а становиться частью всеобщей практики.</p>
<iframe src="https://codepen.io/jpmental/embed/preview/wvwgGLK"></iframe>
<p>Надеюсь, эта статья послужила хорошим введением в тему вариативных шрифтов. <a href="https://twitter.com/jpamental">Отправляйте ссылки и вопросы</a> — я не могу дождаться, когда увижу, что у вас получилось сделать. И следите за обновлениями.</p>
<p>А пока, если хотите узнать о других способах работы с вариативными шрифтами, загляните на удивительный сайт Mandy Michael — <a href="https://variablefonts.dev/">variablefonts.dev</a></p>

                    ]]></description><pubDate>Wed, 25 Dec 2019 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/introduction-to-variable-fonts/</guid></item><item><title>Как русскоязычные пользователи с особыми потребностями пользуются сайтами</title><link>https://web-standards.ru/articles/a11y-poll-2019/</link><description><![CDATA[
                        <p><a href="https://www.sergeikriger.com/">Сергей Кригер</a> и я проводили опрос с апреля по декабрь 2019 года. В нём участвовало 143 человека. Выборка не самая большая, но хотелось бы поделиться с вами результатами.</p>
<p>Опросник состоял из 10 вопросов, связанных с тем, как люди с особыми потребностями пользуются сайтами и с какими трудностями сталкиваются.</p>
<p>Мы вдохновлялись исследованиями <a href="https://webaim.org/">WebAIM</a>. Эта организация каждый год проводит <a href="https://webaim.org/projects/">подобные исследования</a> среди англоязычных пользователей.</p>
<h2>1. Какое у вас ограничение по здоровью</h2>
<img src="https://web-standards.ru/articles/a11y-poll-2019/images/health-restrictions.png" alt="Диаграмма нарушения здоровья участников опроса.">
<p>У большей части участников опроса есть слепота или пониженное зрение — 97,9% (140 человек). Когнитивные нарушения у одного человека (0,7%), глухота или тугоухость тоже у одного (0,7%). Наконец, у одного участника опроса сразу несколько нарушений: тотальная слепота, ампутация кистей обеих рук и тугоухость (0,7%).</p>
<p>Изначально нашей целью было опросить как можно больше людей с разным спектром нарушений, но это оказалось сложной задачей.</p>
<p>Такие результаты связаны с тем, по какому каналу распространялся опросник. Например, Сергей участвовал в одном из <a href="https://tiflo.info/stream/tiflostrim-vypusk-19-veb-dostupnost-2019/">тефлостримов</a>. Тефлострим — еженедельный стрим на русском языке, в котором говорят о том, что интересует незрячих и слабовидящих людей. Кроме слушателей стрима в опросе принимали участие некоторые сотрудники и пользователи Санкт-Петербургской библиотеки для слепых и слабовидящих.</p>
<h2>2. Ваш уровень пользования интернетом</h2>
<p>Нам было интересно какой уровень владения техникой у участников опроса.</p>
<img src="https://web-standards.ru/articles/a11y-poll-2019/images/user-level.png" alt="Диаграмма с уровнем владения техникой участников опроса.">
<p>Оказалось, что больше всего пользователей со средним уровнем — 59,4% или 85 человек. За ними идут продвинутые пользователи — 38,5% (55 респондентов). Новичков оказалось всего трое — 2,1%.</p>
<h2>3. С какого устройства чаще всего выходите в интернет?</h2>
<img src="https://web-standards.ru/articles/a11y-poll-2019/images/device-usage-statistics-by-type.png" alt="Диаграмма с данными о том, какой техникой чаще всего пользуются участники опроса.">
<p>Большинство участников пользуется компьютерами или ноутбуками — 88,1% (126 человек). На втором месте по популярности смартфоны. Ими постоянно пользуются 14 человек или 9,8%. Одинаково часто пользуется и компьютером, и смартфоном два человека (0,8%). Планшетом и смартфоном всего один респондент (0,7%).</p>
<h2>4. Пользуетесь ли вспомогательными технологиями</h2>
<img src="https://web-standards.ru/articles/a11y-poll-2019/images/percent-of-usage-assistive-technology.png" alt="Диаграмма с процентным соотношением пользователей, которые пользуются вспомогательными технологиями.">
<p>Большинство участников пользуется вспомогательными технологиями. Это 140 человек, то есть 97,9%. Трое ими не пользуются — 2,1%.</p>
<h2>5. Если пользуетесь вспомогательными технологиями, то какими именно</h2>
<img src="https://web-standards.ru/articles/a11y-poll-2019/images/assistive-technology-list.png" alt="Вспомогательные технологии, которыми пользуются участники опроса.">
<p>Программами чтения с экрана (скринридеры, экранные дикторы, VoiceOver) пользуются все 100% участников опроса. На втором месте брайлевский дисплей. Его использует 7,7% (11 респондентов). За программируемую клавиатуру проголосовало 3,5% (5 человек). Экранная лупа набрала 3,2% (3).</p>
<p>Специальными мышками (джойстиками, выносными компьютерными кнопками, роллерами) не пользуется никто из опрошенных.</p>
<h2>6. Если используете вспомогательные технологии, то как научились ими пользоваться?</h2>
<p>Интересно было узнать как люди учатся пользоваться вспомогательными технологиями.</p>
<img src="https://web-standards.ru/articles/a11y-poll-2019/images/way-to-learn-assistive-technology.png" alt="Диаграмма с процентным соотношением того, как пользователи научились пользоваться вспомогательными технологиями.">
<p>Большинство респондентов научились этому самостоятельно. Их оказалось 49,3% (70). 37,1% (45) участников с этим помогли друзья, родственники или знакомые. Частично самостоятельно, частично при поддержке родственников и друзей вспомогательными технологиями научился пользоваться 1 человек — 0,7%. На специальных курсах было 17,6% (25) респондентов. В школе на уроках информатики вспомогательные технологии освоило двое человек — 1,4%.</p>
<p>Один человек (0,7%) написал, что научился этому с помощью учебных пособий и дискуссионных листов для незрячих. Ещё один участник (0,7%) указал конкретное пособие — «Пермский учебник». Вероятно, речь идёт про <a href="http://www.tiflocomp.ru/docs/audiotb_perm.php">«Звуковой учебник для начинающих незрячих пользователей компьютера»</a>.</p>
<h2>7. Как часто сталкиваетесь с недоступным контентом на сайтах</h2>
<img src="https://web-standards.ru/articles/a11y-poll-2019/images/frequency-inaccessible-sites.png" alt="Диаграмма, которая показывает как часто пользователи сталкиваются с недоступными сайтами.">
<p>Большинство респондентов иногда сталкиваются с недоступными сайтами — 75,5% (108 человек). Постоянно — 24,5% (35). За вариант «Никогда» не проголосовал ни один из участников.</p>
<p>Из этого можно сделать вывод, что многие сайты недоступны частично, а не полностью.</p>
<h2>8. Если вы встречаете недоступный контент на сайтах, то с чем вы сталкиваетесь чаще всего?</h2>
<p>Было интересно узнать какие же именно элементы в вебе респонденты считают недоступными. В этом вопросе можно было выбрать несколько ответов.</p>
<img src="https://web-standards.ru/articles/a11y-poll-2019/images/inaccessible-sites-parts.png" alt="Диаграмма с недоступными элементами на сайтах по мнению участников опроса.">
<p>На первом месте по недоступности капча. За неё проголосовало 132 респондента или 92,3%. На втором месте неработающие кнопки и ссылки. Их выбрало 73,4% (105) респондентов. Некоторые участники опроса оставляли более развёрнутые ответы о том, что конкретно не так со ссылками и кнопками.</p>
<p>Респондент 1:</p>
<blockquote>
<p>Ещё очень неудобны пункты, так называемые «onclicable», на которые можно нажать, но они не ссылки и не кнопки.</p>
</blockquote>
<p>Респондент 2:</p>
<blockquote>
<p>Вместо ссылки иногда JAWS читает ссылку не как таковую, а просто надпись, и не понятно, что это ссылка или другой элемент управления.</p>
</blockquote>
<p>Респондент 3:</p>
<blockquote>
<p>Всё больше сайтов, где элементы управления или навигации вообще не видны для скринридеров, или видны, но их нельзя активировать. У многих страницы перегружены элементами управления или навигации, они путевые, добираться до нужного долго и сложно.</p>
</blockquote>
<p>Респондент 4:</p>
<blockquote>
<p>Нет подписи к кнопкам или ссылкам. А может быть и картинка за место подписи.</p>
</blockquote>
<p>Респондент 5:</p>
<blockquote>
<p>Неподписанные ссылки (когда скринридер говорит только слово «ссылка»).</p>
</blockquote>
<p>Респондент 6:</p>
<blockquote>
<p>Это касается в основном радиостанций, где можно скачать или хотя бы послушать программы, но ссылки на них находятся вне зоны доступа экранного чтеца. Без разницы какой именно скринридер, главное, что полностью отсутствует доступ к нужному мне контенту. Наглядная ссылочка: <a href="https://radio.nv.ua/">https://radio.nv.ua/</a>. И ещё одна: <a href="https://nv.ua/">https://nv.ua/</a>.</p>
</blockquote>
<p>Отсутствие <code>alt</code> у изображений или их неправильное содержимое вызывает проблемы у 60,8% (87) опрошенных. Неожиданные изменения на странице затрагивают 45,4% (65) участников опроса.</p>
<p>С отсутствием заголовков на страницах или плохо составленными заголовками сталкивается 43,4% (62 человека). По сайту не могут перемещаться с помощью клавиатуры 39,2% (56) респондентов.</p>
<p>51,7% (74) участников сталкиваются с проблемой отсутствия подписей у полей форм. За слишком большое количество ссылок и других интерактивных элементов проголосовало 30% (43 человека).</p>
<p>Один из них отметил:</p>
<blockquote>
<p>Например, если я читаю новости на ria.ru, то после каждой новости показывается много ссылок с рекламой от Яндекса, которая меня совершенно не интересует. Как можно проскочить через эту область с рекламой и перейти сразу к следующей новости с помощью скринридера я не знаю.</p>
</blockquote>
<p>28,7% (41) респондентов сталкивается с тем, что на сайтах нет ссылок для перехода к основному содержимому. С отсутствием поля поиска и плохо работающим поиском сталкивается 26,6% (38 человек). За сложные и длинные формы проголосовал 21% (30 респондентов).</p>
<p>Один из участников опроса при этом отметил, что:</p>
<blockquote>
<p>Внутри поля форм клавиатура перестаёт слушаться.</p>
</blockquote>
<p>Для 14,7% (21 участник) проблемы вызывает отсутствие субтитров в видео. С проблемами с текстом и ошибками в нём сталкивается 7,7% (11 участников).</p>
<p>Один из респондентов отметил:</p>
<blockquote>
<p>Бесит использование схожих по написанию с русскими латинских символов в русских текстах.</p>
</blockquote>
<p>Низкий контраст текста вызывает проблемы у 2,8% (4 человека). Это может быть связано с тем, что многие из участников опроса незрячие и проблема контраста для них не такая острая. 2,1% (3) участника выделили в отдельную проблему чекбоксы и элементы для выбора дат.</p>
<p>Респондент 1:</p>
<blockquote>
<p>[Есть] трудности выбора даты в календарях. Почему-то иногда сложно вызвать его появление.</p>
</blockquote>
<p>У 1,4% (2) респондентов есть проблемы с выпадающими списками.</p>
<p>Один их участников опроса написал:</p>
<blockquote>
<p>[Есть] нечитаемые выпадающие списки, из которых кнопками невозможно выбрать нужный пункт.</p>
</blockquote>
<p>1,4% (2) участников отметил маленький размер шрифта.</p>
<p>Недоступные всплывающие элементы выделило для себя 0,7%(1) участников опроса, отсутствие версии для печати — 0,7% (1) и фреймы — 0,7% (1). Участники отмечали и проблемы, которые связаны с конкретными скринридерами.</p>
<p>Респондент 1:</p>
<blockquote>
<p>Скринридеры не все элементы фиксируют. При этом JAWS и NVDA могут не совпадать.</p>
</blockquote>
<p>Респондент 2:</p>
<blockquote>
<p>Нельзя пользоваться тачпадом, так как NVDA плохо его озвучивает.</p>
</blockquote>
<p>Респондент 3:</p>
<blockquote>
<p>Иногда огромная задержка озвучивания содержимого (машина как бы на время «зависает») в некоторых браузерах.</p>
</blockquote>
<h2>9. Назовите три сайта, которые считаете наиболее доступными</h2>
<p>Нам было интересно какие же сайты из всех, которыми участники опроса пользуются, они считают наиболее доступными. Получились интересные результаты.</p>
<img src="https://web-standards.ru/articles/a11y-poll-2019/images/accessible-sites.png" alt="Диаграмма с доступными сайтами по мнению участников опроса.">
<ol>
<li><a href="http://tiflocomp.ru/">Тифлокомп</a> — 17 голосов.</li>
<li>Яндекс.Поиск — 11 голосов.</li>
<li><a href="http://av3715.ru/">Онлайн-библиотека «Логос»</a> — 10 голосов.</li>
<li>ВКонтакте (скорее всего мобильная версия) — 10 голосов.</li>
<li>Поиск Google — 9 голосов.</li>
<li>YouTube — 7 голосов.</li>
<li>Мобильный Facebook — 5 голосов.</li>
<li>Одноклассники (скорее всего мобильная версия) — 4 голоса.</li>
<li>Рутрекер — 3 голоса.</li>
<li><a href="http://www.radiovos.ru/">Радио ВОС</a> — 3 голоса.</li>
</ol>
<p>По 2 голоса набрали:</p>
<ol>
<li>Google Drive.</li>
<li>Википедия.</li>
<li>Mail.ru.</li>
<li><a href="http://tiflo.space/">Тифло-пространство</a>.</li>
<li><a href="https://www.elitagroup.ru/">Сайт компании Элита Групп</a>.</li>
<li><a href="https://blind.games/">Blind games</a>.</li>
<li>Интернет-магазин Rozetka.</li>
<li>NNM-club.</li>
</ol>
<p>По 1 человеку проголосовало за: Gmail (новый интерфейс), Google Календарь, Яндекс.Погода, сайт Сбербанка, Сбербанк-Онлайн, сервис по отслеживанию Почты России, Slack, сайт Skype, сайт NVDA, Livejournal, Стихи.ру, ЛитРес, Консультант, Ozon, tutu.ru, погодный сайт Метеонова, портал для инвалидов Disability.ru, Тифло Хост, сайт ВОС, сайт издательства ВОС «Чтение», сайт с образовательными курсами ВОС, Реакомп, zri-sam.ru, сайт ЭлекЖест, Тифложизнь, LIOBlindSoft, Sinoptik, интернет-магазин Petshop, украинский интернет-магазин Comfy, украинский интернет-магазин Фокстрот, сайт фитомагазина «Зелена крамничка», Кинозал, Флибуста, diakov.net, emu-land.net, Регистр лекарственных средств России, remontka.pro, Brainum, сайт Неизвестный гений, LRepacks.ru, информационный портал «ТифлоГродно», мобильная версия новостного портала tyt.by, новостной портал ИноСМИ, Лента.ру, Укрпресса, информационный канал subscribe.ru, новостной сайт ORF.at, 2ip.ru, персональный сайт markmanson.net.</p>
<p>Среди ответов были и развёрнутые.</p>
<p>Респондент 1:</p>
<blockquote>
<p>Мне кажется все сайты доступны, если бы справиться с разного рода мелочами, описанными выше.</p>
</blockquote>
<p>Респондент 2:</p>
<blockquote>
<p>Их [сайтов] не так уж и мало. Мобильные версии как правило доступнее основных.</p>
</blockquote>
<p>Респондент 3:</p>
<blockquote>
<p>И вообще все службы Google и Microsoft можно брать за эталон за редкими исключениями.</p>
</blockquote>
<p>Респондент 4:</p>
<blockquote>
<p>Яндекс самый хороший, а смертельного прям нигде нет.</p>
</blockquote>
<p>Респондент 5:</p>
<blockquote>
<p>В основном я в соцсетях. Фейсбук очень даже, как на меня, неплохо устроен, правда с одной оговоркой: автоматическая прокрутка страниц в некоторых местах. И мне приходится жать страницу вверх, дабы вернуться на элемент, с которого я слетел […].
ВКонтакте тоже недурен в последнее время стал, но недоступна ссылка загрузки видео.</p>
</blockquote>
<p>Респондент 7 объясняет почему выбрал Рутрекер и nnm-club:</p>
<blockquote>
<p>rutracker.org и nnm-club.me только благодаря табличной вёрстке и некоторым элементам оформления.</p>
</blockquote>
<h2>10. Назовите три сайта, которые считаете наименее доступными</h2>
<p>Интересно было также с какими сайтами участникам опроса пользоваться сложно.</p>
<img src="https://web-standards.ru/articles/a11y-poll-2019/images/inaccessible-sites-list.png" alt="Диаграмма с недоступными сайтами.">
<ol>
<li>ВКонтакте (возможно, десктопная версия) — 11.</li>
<li>Одноклассники (тоже, возможно, десктопная версия) — 8.</li>
<li>Фейсбук (десктопная версия) — 7.</li>
<li>Яндекс.Деньги — 4.</li>
<li>Instagram — 3.</li>
<li>Яндекс.Почта — 3.</li>
<li>Яндекс.Поиск — 3.</li>
<li>Поиск Google — 3.</li>
</ol>
<p>По 2 голоса набрали:</p>
<ol>
<li>AIiExpres</li>
<li>Ozon</li>
<li>Mail.ru</li>
<li>Госуслуги</li>
<li>Сайт РЖД</li>
<li>Сбербанк-онлайн</li>
<li>ЛитРес</li>
<li>2ГИС</li>
<li>Сайт Ростелекома</li>
<li>Интернет-магазин Rozetka</li>
<li>Рутрекер</li>
</ol>
<p>По 1 участнику выбрало: Telegram, Twitter, Twitch, YouTube, RuTube, Хабр, Яндекс.Дзен, Яндекс.Диск, Яндекс.Коннект, КиноПоиск, Яндекс.Музыка, Смолкасса Ру, Rambler.ru, Сайт Первого канала, РИА Новости, электронный журнал «Школа жизни», Сайт МТС, Сайт Билайн, Сайт Love Radio, Сайт радио 101.ru, Флибуста, сайт Неизвестный гений, сайт компании Кайтен, сайт сервиса JustClick, сайт TravelPayouts, сервис рассылок Senler, портал Агенство праздник, сайт фитомагазина «Зелена крамничка», онлайн-магазин prom.ua, сайт хостинг-провайдера Reg.ru, панель управления ISP manager, сайт Инсбрукского университета имени.</p>
<p>В этом вопросе тоже были подробные ответы и комментарии от опрашиваемых.</p>
<p>Респондент 1:</p>
<blockquote>
<p>Совсем тяжёлых сайтов, к счастью, не так много. Чаще на них всё путано организовано или есть нечитаемые или неактивируемые элементы. ЛитРес становится всё хуже, Фэйсбук написан очень мутно, на сервисах Яндекс есть проблемные страницы, даже с ВКонтакте не всё гладко. Одноклассники очень мутный.</p>
</blockquote>
<p>Респондент 2:</p>
<blockquote>
<p>Так сразу и не вспомню прям совсем недоступные сайты. Но попадаются сайты с досадными ошибками, усложняющими жизнь. Например, на <a href="http://www.litres.ru">www.litres.ru</a> можно отложить книгу, а добавить в корзину с помощью клавиатуры не получается. Так же была поломана доступность интернет-магазина Ozon, в котором теперь я не могу совершать покупки.</p>
</blockquote>
<p>Респондент 3:</p>
<blockquote>
<p>Сложно сказать. В частности, сайты по заказу билетов в кино / на концерты, полная версия сайта РЖД. А в целом сложно что-то выделить. Практически везде что-то доступно нормально, а что-то не очень. С ходу трудно что-то вспомнить.</p>
</blockquote>
<p>Респондент 4:</p>
<blockquote>
<p>[Недоступны] все сайты, где много графики и нет подписанных кнопок или нет ссылок с описанием. Особенно сайты, где есть flash-контент. В этих плеерах, как правило, невозможно работать при помощи клавиатуры. Да и мышей. Для слепого очень затруднительно, потому что кнопки не подписаны.</p>
</blockquote>
<p>Респондент 5:</p>
<blockquote>
<p>На данный момент не могу таких сайтов назвать. В случае, если какую-то информацию в Google ищу, они иногда попадаются. Однако я могу маленькое неудобство описать. Например, если я хочу на сайте ria.ru подкаст прослушать, то кнопка play или воспроизведение не озвучивает screen reader. Вместо этого NVDA говорит: «Пусто». Но если на сайте youtube.com какой-нибудь фильм смотреть, то там эти кнопки озвучиваются нормально.</p>
</blockquote>
<p>Респондент 6:</p>
<blockquote>
<ol>
<li>[Недоступен] <a href="https://nv.ua/">https://nv.ua/</a>. В данном случае это страничка радио, которое в эфире рассыпается своей разнообразностью, но, по факту, лишь лжёт, ибо к сей разнообразности не дотягивается рука. Ну, разве что, кнопочку прямого эфира нажать можно. Попробуйте найти архив передач! Лишь текст, да и тот скудный. А вообще и их облако не сильно балует разнообразием ориентиров для безподглядника [человека со слепотой], большинство нужно шарить стрелами.</li>
<li><a href="https://kazky.suspilne.media/online">https://kazky.suspilne.media/online</a>. Здесь я вообще не вшариваюсь как запустить контент. Нужный находится, а слушать нельзя.</li>
<li>Радио свобода […] Методом тыка разбираешься средь неозвучиваемых элементов как воспроизвести какое-нибудь видео или аудио. А вообще тут большая проблема большая: запустить прямой эфир нельзя. В целом, такого добра в инете хватает. Я много сайтов подобных видел […].</li>
</ol>
</blockquote>
<p>Респондент 7:</p>
<blockquote>
<p>Яндекс, Сбербанк-Онлайн, Озон: не оптимально, но приемлемо доступны.</p>
</blockquote>
<p>Респондент 8:</p>
<blockquote>
<p>[Недоступны] почта Яндекса, 2gis.ru и любые картографические сервисы. И службы Яндекса в целом.</p>
</blockquote>
<p>Респондент 9:</p>
<blockquote>
<p>Плохо доступна основная версия Фэйсбука. Тяжело регистрироваться на почтовых серверах, нужно проходить картинки, которые не описаны, и нужно вставлять капчу. Очень плохо читаются соцсети Twitter, Telegram, Instagram и т. п.</p>
</blockquote>
<p>Респондент 10:</p>
<blockquote>
<p>[Плохо доступен] <a href="https://vk.com">https://vk.com</a>. Сейчас уже гораздо лучше, чем раньше, но проблемы до сих пор остаются.</p>
</blockquote>
<p>Респондент 11:</p>
<blockquote>
<p>[Недоступны] ВК, собственно, все соцсети, новостные сайты, много чего ещё.</p>
</blockquote>
<h2>Выводы</h2>
<p>Большинство участников опроса считают себя пользователями среднего уровня. Чаще всего они пользуются компьютерами и ноутбуками для выхода в интернет. Подавляющее большинство респондентов пользуется скринридерами и научились этому самостоятельно.</p>
<p>Опрошенные обращают внимание на то, что абсолютно недоступных сайтов они не встречали. Чаще всего недоступны отдельные элементы и страницы.</p>
<p>Самые проблемные элементы на сайтах — это:</p>
<ul>
<li>капчи;</li>
<li>кнопки и ссылки;</li>
<li><code>alt</code> у изображений;</li>
<li>поля и подписи к ним в формах;</li>
<li>заголовки;</li>
<li>недоступные для клавиатуры элементы;</li>
<li>слишком большое количество интерактивных элементов на страницах;</li>
<li>нет ссылки или другой возможности сразу перейти к основному контенту;</li>
<li>проблемы с поиском по сайту.</li>
</ul>
<p>Респонденты отметили, что многие мобильные версии сайтов (упрощённые) чаще всего более доступные, чем десктопные. Также среди лидеров по доступности контента специальные сайты для людей с нарушениями зрения.</p>
<p>Мы благодарны всем участникам опроса и неравнодушным разработчикам. Только объединив вместе усилия мы сможем сделать интернет местом, где будет комфортно находиться каждому из нас.</p>
<p>Надеемся, что опрос станет ежегодным и охватит максимальное количество пользователей.</p>

                    ]]></description><pubDate>Thu, 14 Nov 2019 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/a11y-poll-2019/</guid></item><item><title>Инклюзивные компоненты: слайдер</title><link>https://web-standards.ru/articles/a-content-slider/</link><description><![CDATA[
                        <p>Слайдеры похожи на мужчин. <em>Буквально не все</em> они плохие. Кто-то из них даже отзывчивый и тактичный. Однако я не доверяю тем, кто не обращает внимание на откровенно ужасный паттерн. Я, как и другие, понимаю, что многие предпочли бы просто избегать каруселей, но часто у нас нет выбора. Отсюда эта статья.</p>
<p>Слайдеры не так ужасны, но у нас сложилась культура делать их плохо. Обычно во всём виноваты конкретные реализации, а не концепция сама по себе. Как и со многими инклюзивными вещами, правильное решение заключается не в том, что именно вы делаете, а в том, чего не делаете в композиции компонента.</p>
<p>Итак, мы создадим без оглядки на предыдущие реализации то, что выполнит <em>основную задачу</em> слайдера: позволит перемещать контент вдоль горизонтальной оси.</p>
<h2>Контрол</h2>
<p>Все инклюзивные компоненты, в широком смысле, должны быть:</p>
<ul>
<li>очевидными и лёгкими в использовании;</li>
<li>совместимыми с разными типами ввода и вывода;</li>
<li>адаптивными и не зависящими от типа устройства;</li>
<li>производительными;</li>
<li><strong>управляемыми пользователем.</strong></li>
</ul>
<p>В последнее время я много размышлял над последним требованием и поэтому добавил в <a href="https://github.com/Heydon/inclusive-design-checklist">чеклист инклюзивного веб-дизайна</a> пункт <em>«Не включайте сторонние компоненты, которые нарушают конфиденциальность пользователей».</em> И пользователи так же должны быть защищены от непредвиденных или нежелательных действий, как и от незаконных. Поэтому WCAG предписывает выполнять критерий <a href="https://www.wuhcag.com/pause-stop-hide/">2.2.2. Пауза, остановка, скрытие</a>. Он запрещает использовать нежелательную анимацию. С точки зрения карусели речь идёт о возможности прекращения с помощью кнопок паузы и остановки автоматической циклической смены слайдов.</p>
<figure>
    <img src="https://web-standards.ru/articles/a-content-slider/images/carousel.png" alt="Пример карусели.">
    <figcaption>
        Карусель с кнопкой паузы в правом нижнем углу.
    </figcaption>
</figure>
<p>Так уже хорошо, но этого всё ещё недостаточно. Вы не дали настоящий <a href="http://inclusivedesignprinciples.org/#give-control">контроль над элементом</a>. Сначала отказали пользователю в этом, а позже всё-таки вернули такую возможность. Людям с вестибулярными нарушениями, у которых анимация может вызвать тошноту, к тому времени, когда появится кнопка паузы, уже будет нанесена травма.</p>
<p>Думаю, что настоящая инклюзивная карусель — это та, которая не будет двигаться без разрешения пользователя. По этой причине я предпочитаю термин «контентный слайдер» <em>(дальше будет использовать термин «слайдер» — прим. переводчика).</em> Он подразумевает, что слайдером управляет пользователь, а не скрипт. <a href="https://en.wikipedia.org/wiki/Carousel">Карусели</a> же начинают и прекращают движение тогда, когда захотят.</p>
<p>Наш слайдер не будет скользить, кроме момента, когда слайды переключаются. Но как их, собственно, переключить?</p>
<h2>Мультимодальное взаимодействие</h2>
<p>«Мультимодальность» означает <em>«возможность взаимодействовать разными способами».</em> Поддержка разных режимов взаимодействия звучит как что-то, что требует большого количества работы, но браузеры уже мультимодальны по умолчанию. Если вы не облажались, то всем интерактивным контентом можно управлять мышью, с клавиатуры и (где это поддерживается) тачем.</p>
<p>Придерживаясь стандартного поведения браузера, мы без особых усилий можем обеспечить мультимодальность в нашем слайдере.</p>
<h3>Горизонтальная прокрутка</h3>
<p>Самый простой представимый слайдер — это область, которая содержит контент без переносов. Она расположена вдоль горизонтальной оси, и имеется возможность горизонтальной прокрутки. <code>overflow-x: scroll</code> берёт тяжелую работу на себя.</p>
<pre><code tabindex="0" class="language-css">.slider {
    overflow-x: scroll;
}

.slider li {
    display: inline-block;
    white-space: nowrap;
}
</code></pre>
<p>Добавьте некоторые внешние отступы и границы, чтобы улучшить внешний вид слайдера. Это подходящий MVP <em>(минимально жизнеспособный продукт — прим. переводчика)</em> для пользователей мыши. Они могут прокручивать, потянув за видимую полосу прокрутки либо использовать жесты на трекпаде, если курсор находится поверх слайдера. В этом случае анимация будет плавной, потому что за неё отвечает браузер, а не JavaScript-функция, которая срабатывает каждые пять миллисекунд.</p>
<img src="https://web-standards.ru/articles/a-content-slider/images/horizontal-scroll.png" alt="Горизонтальный скролл слайдера.">
<p>Когда скроллбара не видно, то <a href="https://www.interaction-design.org/literature/topics/affordances">возможные действия</a> неочевидны. Не беспокойтесь: чуть позже я с этим разберусь.</p>
<h3>Поддержка клавиатуры</h3>
<p>Для пользователей мыши на большинстве платформ достаточно навести курсор на слайдер, чтобы появилась возможность его прокручивать. Пользователи мобильных устройств могут совершить этот же трюк, если просто свайпнут влево или вправо. Это не требующая особых усилий мультимодальность, которая делает веб крутым.</p>
<p>Те, кто пользуется клавиатурой, могут взаимодействовать со слайдером только тогда, когда на нём сделан фокус.</p>
<p>При нормальных обстоятельствах большинство элементов по умолчанию не получают фокус. Его могут получить только определённые интерактивные элементы, например, ссылки и контролы с тегом <code>&lt;button&gt;</code>. Пользователи не должны иметь возможности сделать фокус на неинтерактивных элементах. Если такое происходит, то это нарушение критерия <a href="https://www.w3.org/TR/UNDERSTANDING-WCAG20/navigation-mechanisms-focus-order.html">WCAG 2.4.3. Порядок фокуса</a>. Причина в том, что фокус должен предшествовать активации, и если активация невозможна, тогда зачем давать элемент в руки пользователям?</p>
<p>Чтобы дать пользователям возможность сделать фокус на слайдере, нам нужно добавить <code>tabindex=&quot;0&quot;</code>. Поскольку элемент (на котором сделан фокус) будет теперь объявлен скринридерами, мы должны присвоить ему роль и добавить подпись, которые его идентифицируют. В последующих демо мы будем использовать слайдер для показа произведений искусства, поэтому подпись «Галерея» кажется подходящей.</p>
<pre><code tabindex="0" class="language-html">&lt;div role=&quot;region&quot; aria-label=&quot;Галерея&quot; tabindex=&quot;0&quot;&gt;
    &lt;!-- Список изображений галереи --&gt;
&lt;/div&gt;
</code></pre>
<p>Роль <code>region</code> достаточно общая, но подходит для больших областей контента. Она гарантирует, что атрибут <code>aria-label</code> сработает правильно и его содержимое будет объявлено. Вы не можете просто добавить <code>aria-label</code> для любого неинтерактивного <code>&lt;div&gt;</code> или <code>&lt;span&gt;</code>.</p>
<p>Теперь, когда есть фокус, стало доступным стандартное поведение для элемента: мы можем его прокручивать клавишами с левой и правой стрелками. Нам нужен только стиль фокуса, чтобы показать зрячим пользователям, что слайдер интерактивен:</p>
<pre><code tabindex="0" class="language-css">[aria-label=&quot;gallery&quot;]:focus {
    outline: 4px solid dodgerblue;
    outline-offset: -6px; /* компенсируем 2px у border */
}
</code></pre>
<iframe src="https://codepen.io/heydon/embed/preview/XzzaKv"></iframe>
<h2>Возможность действия</h2>
<p>Уже есть пара вещей, которые сообщают пользователю, что это область слайдера: стиль фокуса и тот факт, что обычно крайнее правое изображение обрезается. Предполагается, что можно увидеть больше.</p>
<p>Вы можете решить, что этого достаточно, в зависимости от того, насколько критично для пользователей видеть скрытый контент. Так же в этом случае сохраняется лаконичность кода. Тем не менее, мы могли бы сделать всё более очевидным. Мантра инклюзивного дизайна: <em>если есть сомнения, объясните это более подробно.</em></p>
<p>Мы можем сделать это с помощью элемента с «инструкцией» после слайдера. Сообщение будет появляться в зависимости от его состояния. Например, мы могли бы показать при помощи <code>:hover</code> сообщение <em>«Прокрутите, чтобы увидеть больше».</em> Смежный селектор + добавит стиль <code>:hover</code> к элементу <code>.instructions</code>.</p>
<pre><code tabindex="0" class="language-css">#hover {
    display: none;
}

[aria-label=&quot;gallery&quot;]:hover + .instructions #hover {
    display: block;
}
</code></pre>
<img src="https://web-standards.ru/articles/a-content-slider/images/scroll-instructions.png" alt="Инструкция в поле слайдера.">
<p>Сообщение в состоянии <code>:focus</code> может быть реализовано примерно таким же образом. Однако для пользователей скринридеров нам нужно ещё связать это сообщение с областью слайдера. Не имеет значения представляет эта область интерес для каждого из них или нет. Это даёт больше контекста о том, <em>почему</em> область может быть им интересна. Также пользователи лучше понимают, чего они избегают, когда им не нужен этот элемент.</p>
<p>Для этого можем использовать атрибут <code>aria-describedby</code>. Мы указываем его для сообщения с фокусом, используя значение <code>id</code> в качестве содержимого для ARIA-атрибута:</p>
<pre><code tabindex="0" class="language-html">&lt;div role=&quot;region&quot; aria-label=&quot;gallery&quot; tabindex=&quot;0&quot; aria-describedby=&quot;focus&quot;&gt;
    &lt;!-- Список изображений галереи --&gt;
&lt;/div&gt;
&lt;div class=&quot;instructions&quot;&gt;
    &lt;p id=&quot;hover&quot;&gt;Прокрутите, чтобы увидеть больше.&lt;/p&gt;
    &lt;p id=&quot;focus&quot;&gt;Используйте клавиши со стрелками для того, чтобы увидеть больше.&lt;/p&gt;
&lt;/div&gt;
</code></pre>
<p>Теперь, когда фокус устанавливается на галерее слайдера, скринридеры объявят что-то вроде этого: <em>«Галерея изображений, область, используйте клавиши со стрелками для того, чтобы увидеть больше».</em> Как дальше описано в примечании про мультимодальность, убедитесь, что пользователям скринридеров будет просто войти в эту область и пройти через каждое изображение в определённом порядке в «режиме просмотра» (в нём они проходят через каждый элемент). Другими словами, слайдер мультимодален даже для этой группы пользователей.</p>
<figure>
    <img src="https://web-standards.ru/articles/a-content-slider/images/focus-container.png" alt="Движение фокуса по элементам скролла.">
    <figcaption>
        Путь пользователя скринридера в режиме просмотра во многом похож на путь пользователя клавиатуры при наличии связанных/интерактивных слайдов. В любом случае, браузер или скринридер сдвинет контейнер, чтобы показать элементы, на которые сделан фокус.
    </figcaption>
</figure>
<h3>Hover и focus?</h3>
<p>Иногда интересно то, что обнаруживаешь при тестировании. В моём случае я заметил, что, когда одновременно и фокусировался, <em>и</em> наводился на слайдер, появлялись оба сообщения. Конечно же.</p>
<p>Я обнаружил, что в качестве улучшения могу объединить состояния (<code>:hover:focus</code>) и показать сообщение, в котором описаны обе ситуации.</p>
<pre><code tabindex="0" class="language-css">[aria-label='gallery']:hover:focus + .instructions #hover-and-focus {
    display: block;
}
</code></pre>
<p>Используя селектор следующего элемента <code>~</code>, я могу добиться того, что два других сообщения скрыты, в противном случае увидел бы сразу три!</p>
<pre><code tabindex="0" class="language-css">[aria-label='gallery']:hover:focus + .instructions #hover-and-focus ~ * {
    display: none;
}
</code></pre>
<p>Попробуйте навестись на слайдер в демо, а затем кликнуть по нему:</p>
<iframe src="https://codepen.io/heydon/embed/preview/MOoMox"></iframe>
<h2>Обработка тача</h2>
<p>Пока что взаимодействие со слайдером при помощи тача не проработано. В инструкции это по умолчанию не предусмотрено. Так что, когда вы начинаете свайпать слайды, некоторые устройства показывают сообщение о фокусе и ссылаются на «клавиши со стрелками», которых, скорее всего, нет.</p>
<p>Поддержка тача, в первую очередь, означает отслеживание того, взаимодействует ли пользователь с устройством с помощью прикосновений.</p>
<p>Важно то, что мы не хотим отслеживать поддержку тача на уровне устройства, так как многие поддерживают его наравне с другими способами. Мы просто хотим знать, что <em>пользователь коснулся</em> экрана. Это возможно с помощью отслеживания одного события <code>touchstart</code>. Вот крошечный скрипт (все лучшие скрипты такие!):</p>
<pre><code tabindex="0" class="language-js">window.addEventListener('touchstart', function touched() {
    document.body.classList.add('touch');
    window.removeEventListener('touchstart', touched, false);
}, false);
</code></pre>
<p>Всё, что делает скрипт — это определяет событие <code>touchstart</code>, использует его для добавления <code>class</code> к элементу <code>&lt;body&gt;</code> и для удаления обработчика событий. Имея <code>class</code>, мы можем показать сообщение «Свайпните, чтобы увидеть больше»:</p>
<pre><code tabindex="0" class="language-css">.touch .instructions p {
    display: none !important;
}

.touch .instructions #touch {
    display: block !important;
}
</code></pre>
<p><strong>Примечание:</strong> <code>!important</code> здесь потому, что я упростил селекторы для удобства чтения, по ходу уменьшив их специфичность.</p>
<h2>Слайды</h2>
<p>В зависимости от вашего случая и контента, вы можете просто остановиться здесь, решив, что слайдер уже довольно хорош. У нас есть что-то совместимое и мультимодальное, и для него используется примерно 100 байт JavaScript. Это преимущество создания с нуля чего-то простого, и отсутствие зависимости от универсальной библиотеки.</p>
<p>Но пока наш слайдер на самом деле не является «слайдами», которые обычно занимают всю ширину своего контейнера. Если мы решим эту проблему адаптивно, то люди смогут любоваться каждым произведением искусства на разных размерах экрана. Также было бы неплохо иметь возможность добавлять подписи к слайдам, поэтому с этого момента будем использовать <code>&lt;figure&gt;</code> и <code>&lt;figcaption&gt;</code>.</p>
<pre><code tabindex="0" class="language-html">&lt;li&gt;
    &lt;figure&gt;
        &lt;img src=&quot;[url]&quot; alt=&quot;[Описание]&quot;&gt;
        &lt;figcaption&gt;[Название произведения искусства]&lt;/figcaption&gt;
    &lt;/figure&gt;
&lt;/li&gt;
</code></pre>
<p>Давайте переключимся на флексы.</p>
<pre><code tabindex="0" class="language-css">[aria-label=&quot;gallery&quot;] ul {
    display: flex;
}

[aria-label=&quot;gallery&quot;] li {
    list-style: none;
    flex: 0 0 100%;
}
</code></pre>
<ul>
<li><code>display: flex</code> — это всё, что нам нужно для контейнера, потому что значение <code>flex-wrap</code> по умолчанию <code>nowrap</code>.</li>
<li>Сокращённо значение <code>100%</code> для <code>flex</code> — это <code>flex-basis</code>. В результате этого каждый элемент занимает 100% от контейнера <code>&lt;ul&gt;</code>.</li>
</ul>
<p>Также я добавил флексовый контекст и для <code>&lt;figure&gt;</code>, поэтому теперь могу центрировать контент как по вертикальной, так и по горизонтальной осям.</p>
<pre><code tabindex="0" class="language-css">[aria-label=&quot;gallery&quot;] figure {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 50vh;
}
</code></pre>
<p>Значение <code>50vh</code> — это единственный <em>как бы фиксированный</em> размер, который я использую. Нужно убедиться, что слайдер имеет адекватную высоту, но вписывается во вьюпорт. Чтобы изображение и <code>&lt;figcaption&gt;</code> всегда помещались в контейнер, мы пропорционально масштабируем картинку, но компенсируем ожидаемую высоту <code>&lt;figcaption&gt;</code>. Для этого можем использовать <code>calc</code>:</p>
<pre><code tabindex="0" class="language-css">[aria-label='gallery'] figcaption {
    padding: 0.5rem;
    font-style: italic;
}

[aria-label='gallery'] img {
    max-width: 100%;
    max-height: calc(100% - 2rem);
    margin-top: 2rem;
}
</code></pre>
<img src="https://web-standards.ru/articles/a-content-slider/images/container-size.png" alt="Размеры контейнера с текстом подписи.">
<p>С внутренним отступом <code>0.5rem</code> текст подписи становится примерно <code>2rem</code> в высоту. Это вычитается из высоты адаптивного изображения. Затем <code>margin-top</code> со значением <code>2rem</code> повторно центрирует изображение.</p>
<iframe src="https://codepen.io/heydon/embed/preview/mqMvEY"></iframe>
<h2>Производительность и ленивая загрузка</h2>
<p>Одно из самых неожиданных наблюдений, отмеченных на <a href="http://shouldiuseacarousel.com/">shouldiuseacarousel.com</a>, состоит в том, что в каруселях со ссылками только «1% пользователей нажал на ссылку, из них 89% было на первом слайде». <a href="https://erikrunyon.com/2013/01/carousel-interaction-stats/">Исследования показывают</a>, что даже для автоматически вращающихся каруселей количество кликов по слайдам, расположенных после первого, резко уменьшается.</p>
<p>Вполне вероятно, что первое изображение в нашем слайдере — единственное, которое увидит большинство скринридеров. Поэтому нужно рассматривать его как одно единственное и загружать последующие картинки только в случае, <em>если</em> пользователь решит на них посмотреть.</p>
<p>Мы можем использовать <code>IntersectionObserver</code> там, где он поддерживается, чтобы загрузить каждое изображение отдельно. Это связано с тем, что каждый слайд начинает прокручиваться, когда находится в области видимости.</p>
<img src="https://web-standards.ru/articles/a-content-slider/images/loaded-slide.png" alt="Порядок загрузки изображений слайдера.">
<p>Вот скрипт, примечания следуют:</p>
<pre><code tabindex="0" class="language-js">const slides = document.querySelectorAll('[aria-label=&quot;gallery&quot;] li');

const observerSettings = {
    root: document.querySelector('[aria-label=&quot;gallery&quot;]');
}

if ('IntersectionObserver' in window) {
    const callback = (slides, observer) =&gt; {
        entries.forEach(entry =&gt; {
            if (!entry.isIntersecting) {
                return;
            }
            let img = entry.target.querySelector('img');
            img.setAttribute('src', img.dataset.src);
            observer.unobserve(entry.target);
        });
    }

    const observer = new IntersectionObserver(
        callback, observerSettings
    );

    slides.forEach(t =&gt; observer.observe(t));
} else {
    Array.prototype.forEach.call(slides, function (s) {
        let img = s.querySelector('img');
        img.setAttribute('src', img.getAttribute('data-src'));
    });
}
</code></pre>
<ul>
<li>В <code>observerSettings</code> определяем внешний элемент галереи как корневой. Мы можем совершить действие тогда, когда элементы <code>&lt;li&gt;</code> становятся видимыми.</li>
<li>Обнаруживаем функцию при помощи <code>'IntersectionObserver' in window</code> и просто загружаем изображения, если их нет. Извините, пользователи старых браузеров, но это лучшее, что мы можем предложить: по крайне мере вы получите контент.</li>
<li>Для каждого слайда, который пересекается с другим, мы устанавливаем его <code>src</code> через атрибут <code>data-src</code>, как в случае с обычной ленивой загрузкой.</li>
</ul>
<p>Позаботьтесь о том, чтобы пользователи не видели неработающие изображения при медленном соединении. Для этого случая добавим для них заглушку в качестве исходного значения <code>src</code>. Получим крошечный SVG с data URI:</p>
<pre><code tabindex="0" class="language-html">&lt;img src=&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' /%3E&quot; alt=&quot;…&quot;&gt;
</code></pre>
<h3>Без JavaScript</h3>
<p>В данный момент пользователи с отключенным JavaScript работают без изображений, так как переключение <code>data-src</code> на <code>src</code> невозможно. Кажется, самое простое решение — добавить теги <code>&lt;noscript&gt;</code>, в которых содержатся изображения с уже установленными значениями <code>src</code>.</p>
<pre><code tabindex="0" class="language-html">&lt;noscript&gt;
    &lt;img src=&quot;[url]&quot; alt=&quot;[Описание]&quot;&gt;
&lt;/noscript&gt;
</code></pre>
<iframe src="https://codepen.io/heydon/embed/preview/Ebwjyo"></iframe>
<p>Теперь наш слайдер работает и без JavaScript. У нас неплохо выходит. Однако мы обрабатываем только случай «без JavaScript», который редко встречается. Это не печально распространённая ситуация со «сломанным JavaScript». Рик Шеннинк <a href="https://twitter.com/rikschennink/status/931256220303978496">решил эту проблему</a>, поместив <code>mutationObserver</code> в <code>&lt;head&gt;</code> документа.</p>
<h2>Кнопки вперёд и назад</h2>
<p>У типичных слайдеров есть кнопки по обеим сторонам, которые перемещают слайды вперёд и назад. Это соглашение, возможно, стоит принять по двум причинам:</p>
<ul>
<li>Наличие кнопок делает слайдер больше похожим на самого себя. Так же становится более очевидной возможность взаимодействия с ним.</li>
<li>Кнопки позволяют пользователю зафиксировать слайды на месте. Больше не нужно крутить туда-сюда, чтобы отцентрировать нужный слайд.</li>
</ul>
<p>Хитрость заключается в том, чтобы использовать уже разработанную нами функциональность, а не заменять её. Наши кнопки должны заранее знать о том, что кто-то уже прокручивает или свайпает слайды, и уметь с этим работать.</p>
<p>Адаптируя наш скрипт <code>IntersectionObserver</code>, мы можем добавлять и удалять класс <code>.visible</code> у слайдов:</p>
<pre><code tabindex="0" class="language-js">slides.forEach(entry =&gt; {
    entry.target.classList.remove('visible');
    if (!entry.isIntersecting) {
        return;
    }
    let img = entry.target.querySelector('img');
    if (img.dataset.src) {
        img.setAttribute('src', img.dataset.src);
        img.removeAttribute('data-src');
    }
    entry.target.classList.add('visible');
});
</code></pre>
<p>Это означает не только то, что мы найдём <code>class=&quot;visible&quot;</code> у любого слайда, который виден на 100% (как первый слайд), но и в случае, когда пользователь прокрутил позицию между двумя слайдами. У них обоих будет этот класс.</p>
<img src="https://web-standards.ru/articles/a-content-slider/images/two-visible-slides.png" alt="Класс 'visible' на даух слайдах при прокрутке.">
<p>Чтобы полностью переместить нужный слайд в зону видимости, когда пользователь нажимает одну из кнопок, нам нужно знать только три вещи:</p>
<ol>
<li>Какой ширины контейнер.</li>
<li>Какое количество слайдов.</li>
<li>В каком направлении пользователь хочет двигаться.</li>
</ol>
<p>Если два слайда видны частично и пользователь жмёт «Вперёд», мы определяем необходимый слайд как второй <code>.visible</code>. Затем мы изменяем значение контейнера <code>scrollLeft</code> на основе следующей формулы:</p>
<p><code>нужный номер слайда × (ширина контейнера ÷ количество слайдов)</code></p>
<p>Обратите внимание на размер кнопок «Вперёд» и «Назад» из демо ниже. Они оптимизированы для лёгкого взаимодействия с тачем без потерь для десктопа.</p>
<iframe src="https://codepen.io/heydon/embed/preview/YEYqJJ"></iframe>
<h3>Точки фиксации</h3>
<p>Как заметил <a href="https://twitter.com/justmarkup">Майкл Шарнагль</a>, некоторые браузеры, включая Safari и Firefox, поддерживают простой CSS-метод фиксации слайдов при прокрутке или использовании клавиш со стрелками. Так как Safari не поддерживает <code>IntersectionObserver</code> <em>(уже поддерживается с <a href="https://caniuse.com/#feat=intersectionobserver">версии 12.1</a> — прим. переводчика),</em> то это один из способов сделать удобнее пользователям этого браузера. Мешанина из проприетарных и стандартных свойств ниже: вот что сработало в нашем случае.</p>
<pre><code tabindex="0" class="language-css">[aria-label='gallery'] {
    -webkit-overflow-scrolling: touch;
    -webkit-scroll-snap-type: mandatory;
    -ms-scroll-snap-type: mandatory;
    scroll-snap-type: mandatory;
    -webkit-scroll-snap-points-x: repeat(100%);
    -ms-scroll-snap-points-x: repeat(100%);
    scroll-snap-points-x: repeat(100%);
}
</code></pre>
<p>Фиксация прокрутки поддерживается в <a href="https://cdpn.io/heydon/debug/xPWOLp">демо со связанным контентом</a>, если хотите изучить этот способ. Совет: часть с <code>repeat(100%)</code> относится к 100% ширины каждого слайда.</p>
<h3>Группа кнопок</h3>
<p>Если разместить две кнопки внутри списка, то дальше они будут рассматриваться как сгруппированные ненумерованные элементы. После того, как в <code>&lt;ul&gt;</code> добавлена поддержка <code>aria-label</code>, мы можем добавить вспомогательную групповую подпись «Контролы галереи». Так будет легче установить цель кнопок.</p>
<pre><code tabindex="0" class="language-html">&lt;ul aria-label=&quot;Контролы галереи&quot;&gt;
    &lt;li&gt;
        &lt;button id=&quot;previous&quot; aria-label=&quot;Предыдущий&quot;&gt;
            &lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;use xlink:href=&quot;#arrow-left&quot;&gt;&lt;/use&gt;&lt;/svg&gt;
        &lt;/button&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;button id=&quot;next&quot; aria-label=&quot;Следующий&quot;&gt;
            &lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;&lt;use xlink:href=&quot;#arrow-right&quot;/&gt;&lt;/svg&gt;
        &lt;/button&gt;
    &lt;/li&gt;
&lt;/ul&gt;
</code></pre>
<p>У каждой кнопки, конечно, должна быть собственная подпись, которая, в данном случае, так же задаётся при помощи <code>aria-label</code>. Когда пользователь скринридера столкнётся с первой кнопкой, он услышит что-то вроде: <em>«Назад, кнопка, список, контролы галереи, два пункта».</em></p>
<p>Мы создали контролы для браузера, который поддерживает <code>IntesectionObserver</code>. Для других браузеров, которые его не поддерживают, слайдер всё ещё рендерится и доступен для мыши, клавиатуры и тача.</p>
<pre><code tabindex="0" class="language-js">instructions.parentNode.insertBefore(
    controls, instructions.nextElementSibling
);
</code></pre>
<h2>Индикаторы загрузки</h2>
<p>Следует отметить, что теперь можно нажать на кнопку для мгновенной загрузки слайда. При этом может возникнуть очевидный временной разрыв между появлением слайда в области видимости и загрузкой самого изображения.</p>
<p>Есть техника, которая поможет с этим разобраться: использовать для видимого плейсхолдера доминантный цвет изображения. Вы могли такое видеть на Pinterest и других подобных сайтах. Однако, это означает, что мы знаем размеры изображения, и наши картинки по своей природе адаптивны.</p>
<p>Чтобы раскладка не «подпрыгивала», когда изображения подгружаются (так как установлена высота <code>50vh</code>), мы можем вместо этого схитрить и отцентрировать индикатор загрузки для каждого слайда. Потом он будет скрыт загрузившейся картинкой.</p>
<img src="https://web-standards.ru/articles/a-content-slider/images/image-preloader.png" alt="Индикатор загрузки для слайда.">
<h2>Обработка связанного контента</h2>
<p>Порядок фокуса в нашем слайдере сейчас довольно простой: слайдер сам по себе принимает фокус (при прокрутке с помощью клавиш со стрелками), после чего можно сделать фокус на каждой кнопке по очереди.</p>
<p>Но что, если контент каждого слайда связан? После того, как вы сделали фокус на самом слайдере, первый слайд окажется в фокусе, затем каждый последующий слайд (не важно сколько их), наконец, в фокусе окажутся кнопки.</p>
<p>Помимо того, что нужно совершить много шагов, связанных с фокусом, чтобы добраться до кнопок (или вообще выйти из слайдера), у нас есть ещё одна небольшая проблема. Если пользователь прокрутит область до зоны видимости с третьим пунктом, то он будет ожидать, что это единственный элемент, который получит фокус. Вместо этого фокус будет сделан на первом пункте и слайдер вернётся в изначальное положение, показав его в области видимости.</p>
<img src="https://web-standards.ru/articles/a-content-slider/images/focus-on-initial-state.png" alt="Положение фокуса при прокрутке слайдера.">
<p>Это не катастрофа. На самом деле пункты, получающие фокус, автоматически оказываются в области видимости без JavaScript, что служит нам хорошую службу. На невидимом контенте никогда нельзя сделать фокус.</p>
<p>Там, где поддерживается <code>IntersectionObserver</code> и рендерятся кнопки, при наличии в данный момент только видимого пункта в порядке фокуса, это станет хорошим вариантом улучшения. Мы можем поправить наш скрипт так, чтобы ссылки в пунктах, которые не пересекаются, принимали <code>tabindex=&quot;-1&quot;</code>. Это сделает невозможным установление на них фокуса. Обратите внимание на строки с комментариями 1 и ниже:</p>
<pre><code tabindex="0" class="language-js">slides.forEach(entry =&gt; {
    entry.target.classList.remove('visible');
    let a = entry.target.querySelector('a');
    a.setAttribute('tabindex', '-1'); // 1
    if (!entry.isIntersecting) {
        return;
    }
    let img = entry.target.querySelector('img');
    if (img.dataset.src) {
        img.setAttribute('src', img.dataset.src);
        img.removeAttribute('data-src');
    }
    entry.target.classList.add('visible');
    a.removeAttribute('tabindex', '-1'); // 2
})
</code></pre>
<p>Просто. Теперь только один или два слайда получают фокус, в зависимости от того, сколько из них сейчас пересекаются. Так что до кнопок становится быстрее и проще добраться.</p>
<figure>
    <img src="https://web-standards.ru/articles/a-content-slider/images/disable-focus-for-out-of-viewport-slides.png" alt="Фокус на двух слайдах в поле видимости.">
    <figcaption>
        Показано два слайда с каждой стороны слайдера. Они вне области видимости. У каждого есть <code>tabindex</code> со значением минус один.
    </figcaption>
</figure>
<iframe src="https://codepen.io/heydon/embed/preview/xPWOLp"></iframe>
<p>Весь минифицированный скрипт для этого слайдера весит примерно 1,7 Кб. Первый результат поисковой выдачи по запросу «плагин карусель» в Google весит 41,9 Кб в сжатом виде и с неправильными атрибутами WAI-ARIA. В некоторых случаях от скринридеров скрывался контент, на котором сделан фокус, при помощи <code>aria-hidden</code>. Остерегайтесь <a href="https://www.w3.org/TR/using-aria/#fourth">четвёртого правила использования ARIA</a>.</p>
<p>В этом финальном демо учтены некоторые моменты для Edge и Internet Explorer:</p>
<ul>
<li>Код скомпилирован в ES5;</li>
<li>Баг с увеличением изображений на флексах устраняется при помощи <code>min-width: 1px</code> и <code>min-height: 1px</code> у картинок.</li>
<li>Изначально <code>tabindex=&quot;-1&quot;</code> установлен для каждой ссылки. В этом нет необходимости в других браузерах. Это учитывается в колбэке <code>InterSectionObserver</code> при первой загрузке.</li>
</ul>
<p>Safari пока не поддерживает <code>IntersectionObserver</code> <em>(<a href="https://caniuse.com/#feat=intersectionobserver">актуально</a> для версий, ниже 12.1 — прим. переводчика),</em> но есть <a href="https://github.com/w3c/IntersectionObserver/tree/master/polyfill">полифил</a>, который в сжатом виде весит примерно 6 Кб. Слайдер нормально работает в этом браузере, как и в других, которые не поддерживают <code>IntersectionObserver</code>.</p>
<h2>Заключение</h2>
<p>Инклюзивный дизайн не только про то, как сделать интерфейс одинаковым для всех. Он также о том, как предоставить как сделать интерфейс удобным как можно большему числу людей. У нашего слайдера не самая модная реализация, но он всё ещё достаточно хорош. Это контент должен поражать людей, а не сам интерфейс: эпохальные дадаистские фотомонтажи Ханны Хёх не должны задвигаться на второй план. Убедитесь, что слайдер адаптивный, мало весит, надёжный и совместимый. Тогда у художницы будет большая аудитория, которую она заслуживает.</p>
<p>Я предложил в своём докладе <a href="https://vimeo.com/190834530">«Пишите меньше чёртового кода»</a> концепцию <em>непрогрессивного неулучшения</em> (unprogressive non-enhancement). Идея заключается в том, что поток контента, на основе которого мы строим вкладки, карусели и тому подобное, часто не следует перестраивать. Отсутствие улучшения может быть лучше «улучшения». Однако при разумном и бережном расширении представления контента, как, например, в слайдерах, это может быть довольно привлекательным способом получения информации. Лучше всего найти хорошую и исследованную причину для такого исключения.</p>
<h2>Чек-лист</h2>
<ul>
<li>Используйте списки для объединения слайдов в группу. Тогда пользователи скринридеров в «режиме просмотра» смогут пользоваться шорткатами для перемещения по ним.</li>
<li>Предусмотрите адекватную работу слайдера в HTML благодаря CSS, а после — проверяйте поддержку в процессе улучшения с помощью JavaScript.</li>
<li>Не загружайте заранее контент, который пользователи вряд ли увидят. Отложите загрузку, пока они не выполнят какое-то действие.</li>
<li>Увеличьте область клика для пользователей мобильных устройств или устройств с маленькими экранами.</li>
<li>В случае, если есть сомнения насчёт возможности взаимодействия с контролом или виджетом, расскажите об этом в инструкции к ним.</li>
<li>Если вы мужчина и не обиделись, прочитав первый параграф, поздравляю! Вы не относитесь к мужчинам и женщинам как к конкурирующим командам.</li>
</ul>

                    ]]></description><pubDate>Thu, 14 Nov 2019 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/a-content-slider/</guid></item><item><title>Помимо автоматизированного тестирования доступности: шесть вещей, которые я проверяю на каждом своём сайте</title><link>https://web-standards.ru/articles/six-things-i-check/</link><description><![CDATA[
                        <p>Я только что закончил аудит доступности для заказчика и решил поделиться некоторыми быстрыми проверками, которые я провожу всегда, когда работаю с сайтами. Вы можете прямо сейчас применить их к своему проекту, вам не придётся разбираться со специальными инструментами или софтом.</p>
<h3>0. Автоматизированное тестирование</h3>
<p>Прежде всего я запускаю тест доступности в <a href="https://developers.google.com/web/tools/lighthouse">Lighthouse</a>, чтобы выяснить, есть ли какие-то очевидные проблемы. Автоматизированные тесты хороши, но они проверяют только часть того, что должны. <a href="https://www.matuzo.at/blog/building-the-most-inaccessible-site-possible-with-a-perfect-lighthouse-score/">Можно получить 100 баллов или 0 ошибок, но это не означает, что уже всё хорошо</a>. Вы лишь заложили основу для ручного тестирования.</p>
<h3>1. Проверьте описания изображений</h3>
<p>Первый полуавтоматический тест, который я выполняю — проверяю, есть ли у изображения описание и правильное ли оно. Для этого я использую браузерное расширение <a href="https://addons.mozilla.org/de/firefox/addon/web-developer/">Web Developer</a>. С его помощью можно выделить картинки, у которых нет атрибута <code>alt</code>, а также вывести значение <code>alt</code> рядом с изображением.</p>
<figure>
    <img src="https://web-standards.ru/articles/six-things-i-check/images/web-developer-extension.jpg" alt="">
    <figcaption>
        Раздел настроек изображений в панели инструментов расширения Web Developer.
    </figcaption>
</figure>
<h3>2. Отключите все стили</h3>
<p>Ещё одной полезной функцией расширения <a href="https://addons.mozilla.org/de/firefox/addon/web-developer/">Web Developer</a> является его способность отключения стилей на странице. Отключение CSS поможет проверить различные вещи:</p>
<ul>
<li>Работает ли сайт без стилей? (например, когда они не загрузились)</li>
<li>Правильный ли порядок элементов на странице?</li>
<li>Корректный ли размер у картинок и иконок?</li>
<li>Хорошо ли структурирован ваш документ?</li>
</ul>
<figure>
    <img src="https://web-standards.ru/articles/six-things-i-check/images/disabled-css-on-page.jpg" alt="">
    <figcaption>
        Страница расписания на сайте <a href="https://webclerks.at/">webclerks.at</a> с отключённым CSS.
    </figcaption>
</figure>
<h3>3. Валидируйте HTML</h3>
<p>Вы можете использовать <a href="https://validator.w3.org/">W3C Markup Validation Service</a> для проверки разметки. Валидатор не отлавливает все ошибки, но это в любом случае хороший способ обнаружения очевидных багов в вашем HTML, например, дублирующихся <code>id</code> или сломанных ARIA-атрибутов.</p>
<figure>
    <img src="https://web-standards.ru/articles/six-things-i-check/images/w3c-validator-result-page.jpg" alt="">
    <figcaption>
        Ошибки на странице результатов W3C-валидатора.
    </figcaption>
</figure>
<h3>4. Проверьте структуру документа</h3>
<p><a href="https://webaim.org/projects/screenreadersurvey8/#finding">Большое значение имеет хорошо продуманная структура документа</a>. Она должна начинаться с <code>&lt;h1&gt;</code>, за которым в иерархическом порядке следуют <code>&lt;h2&gt;</code>, <code>&lt;h3&gt;</code>, и т.д. Это отлично подходит для поисковых систем и пользователей скринридеров, ведь так они могут перемещаться по вашему сайту, прыгая по заголовкам.</p>
<figure>
    <img src="https://web-standards.ru/articles/six-things-i-check/images/headers-list.jpg" alt="">
    <figcaption>
        Список заголовоков на сайте <a href="https://webclerks.at/">webclerks.at</a> (скриншот из tota11y).
    </figcaption>
</figure>
<p>Для проверки структуры документа можно использовать <a href="https://validator.w3.org/">W3C Markup Validation Service</a> или инструмент <a href="https://khan.github.io/tota11y/">tota11y</a> (см. скриншот выше).</p>
<h3>5. Включите монохромный режим</h3>
<p>Для отображения сайтов в монохромном режиме я использую браузерное расширение <a href="https://chrome.google.com/webstore/detail/high-contrast/djcfdncoelnlbldjfhinnjlhdjlikmph">High Contrast</a>. Это важный тест: он покажет те элементы дизайна, которые работают только с цветом. Следует убедиться, что вы не используете исключительно цвет для передачи информации. Идеальный пример — ссылки: они должны быть подчёркнуты, чтобы можно было легко отличить их от обычного текста. <a href="https://adrianroselli.com/2019/01/underlines-are-beautiful.html">Подчёркивания — это прекрасно!</a></p>
<blockquote>
    <p>Подчёркивайте ваши чёртовы ссылки, социопаты!</p>
    <footer>
        <cite>
            Хейдон Пикеринг
        </cite>
    </footer>
</blockquote>
<figure>
    <img src="https://web-standards.ru/articles/six-things-i-check/images/smashing-magazine-monochrome.png" alt="">
    <figcaption>
        Сайт <a href="https://www.smashingmagazine.com/">The Smashing Magazine</a> в монохромном режиме.
    </figcaption>
</figure>
<p>Не обязательно устанавливать расширение, можно использовать CSS для получения такого же эффекта:</p>
<pre><code tabindex="0" class="language-css">body {
    filter: grayscale(100%);
}
</code></pre>
<h3>6. Используйте клавиатуру</h3>
<p>Уберите подальше свою мышь, пройдитесь по странице, используя только клавишу <kbd>Tab</kbd>, и проверьте, можете ли вы попасть в каждую отдельную её часть без мыши или тачпада. <kbd>Tab</kbd> — это мощный инструмент тестирования, он расскажет вам многое о вашем сайте:</p>
<ul>
<li>Хорошо ли видны стили состояния фокуса?</li>
<li>Всё ли, что должно иметь фокус, может его получить?</li>
<li>Действительно ли ваши кнопки <code>&lt;button&gt;</code>?</li>
<li>Удобно ли вообще использовать клавиатуру на вашем сайте?</li>
<li>Правильно ли вы управляете фокусом?</li>
<li>Правильно ли прячутся и отображаются элементы?</li>
<li>Соответствует ли визуальный порядок элементов реальному порядку в DOM-дереве?</li>
<li>Можете ли вы использовать кастомные JS-компоненты без мыши?</li>
</ul>
<p>Есть даже <a href="https://www.npmjs.com/package/no-mouse-days">npm-пакет для отключения курсора мыши</a>, созданный чудесной <a href="https://twitter.com/marcysutton">Марси Саттон</a>.</p>
<p>Это не конец истории. Существует ещё немало вещей, которые стоит тестировать, но сейчас хватит и этих. Я расскажу больше в другом посте :)</p>

                    ]]></description><pubDate>Tue, 05 Nov 2019 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/six-things-i-check/</guid></item><item><title>Доступность и закон</title><link>https://web-standards.ru/articles/a11y-and-law/</link><description><![CDATA[
                        <h2>Разбираем законы и стандарты по веб-доступности</h2>
<p>Часто встречаю комментарии о том, что доступность не нужна. «Зачем мне думать о слепых пользователях, их же мало!». «Зачем людям с инвалидностью интернет?». «А что мне за это будет? В чём выгода?». Тысячи их.</p>
<p>У меня есть пара контраргументов. Во-первых, <a href="http://www.who.int/news-room/fact-sheets/detail/disability-and-health">мировая статистика</a> говорит, то в мире живёт более 1 миллиарда людей с инвалидностью. Это примерно 15% от всего населения планеты. И их число с каждым годом увеличивается.</p>
<p>Во-вторых, не стоит решать за других каким сервисом они должны пользоваться, а каким нет. Кто-нибудь так же может решить за вас.</p>
<p>В-третьих, здоровье не вечно. Люди болеют и <em>иногда</em> даже стареют. Если вы не <a href="https://en.wikipedia.org/wiki/Turritopsis_dohrnii">бессмертная медуза</a>, то доступность нужна в первую очередь вам самим.</p>
<blockquote>
    <p>
        «Не иметь нарушений — это временно», — мудрые слова
        <a href="https://twitter.com/sonniesedge">@sonniesedge</a>
        на <a href="https://twitter.com/hashtag/Fronteers">#Fronteers</a>
    </p>
    <footer>
        <cite>
            <a href="https://twitter.com/charis/status/1179658993649750016">@charis</a>
        </cite>
    </footer>
</blockquote>
<p>Наконец, правовое регулирование доступности в тренде. Сейчас активно принимаются и дорабатываются уже существующие законы, которые делают её обязательной для сайтов и приложений.</p>
<p>В статье я разберу несколько законов, пару стандартов и одну директиву. Они могут коснуться вас как разработчиков, если вы работаете с европейскими и американскими компаниями или с государственным сектором в России.</p>
<p>Эта статья обзорная и в ней нет чёткого ответа <em>как именно</em> решить проблему доступности. За деталями переходите по ссылкам, которые я здесь собрала. Начнём.</p>
<h2>Евросоюз</h2>
<figure>
    <img src="https://web-standards.ru/articles/a11y-and-law/images/europe-flag.jpg" alt="Флаг Евросоюза.">
    <figcaption>Фото <a href="https://unsplash.com/@markusspiske">Markus Spiske</a>.</figcaption>
</figure>
<p>Принятие законов о доступности в Европе запустила <a href="https://www.un.org/development/desa/disabilities/convention-on-the-rights-of-persons-with-disabilities.html">Конвенция ООН о защите прав людей с инвалидностью</a> (Convention on the Rights of Persons with Disabilities — CRPD). Она принята в 2006 году и запрещает дискриминацию людей с инвалидностью.</p>
<p>В Европе есть несколько важных документов о веб-доступности:</p>
<ul>
<li>Директива о веб-доступности.</li>
<li>Европейский закон о доступности (пока не утверждён окончательно).</li>
<li>Европейский стандарт EN 301 549.</li>
</ul>
<p>На их основе принимаются все национальные законы о доступности <a href="https://ru.wikipedia.org/wiki/%D0%93%D0%BE%D1%81%D1%83%D0%B4%D0%B0%D1%80%D1%81%D1%82%D0%B2%D0%B0_%E2%80%94_%D1%87%D0%BB%D0%B5%D0%BD%D1%8B_%D0%95%D0%B2%D1%80%D0%BE%D0%BF%D0%B5%D0%B9%D1%81%D0%BA%D0%BE%D0%B3%D0%BE_%D1%81%D0%BE%D1%8E%D0%B7%D0%B0">в странах Евросоюза</a> (дальше просто ЕС).</p>
<h2>Директива о веб-доступности</h2>
<p>«Web Accessibility Directive» или «Directive (EU) 2016/2102»</p>
<p>Полное название: «Директива Европейского Парламента и Совета ЕС 2016/2102 от 26 октября 2016 года о доступности сайтов и мобильных приложений органов публичного сектора». Последние изменения сделаны в сентябре 2018.</p>
<p>Доступность с точки зрения директивы означает, что контент в вебе и в мобильных приложениях должен быть воспринимаемым, управляемым, понятным и надёжным.</p>
<h3>Кого защищает</h3>
<p>Всех граждан стран ЕС с инвалидностью.</p>
<h3>Что именно затрагивает</h3>
<p>Информационно-коммуникационные технологии, которые создают органы публичного сектора в странах ЕС:</p>
<ul>
<li>сайты;</li>
<li>мобильные приложения;</li>
<li>интранет;</li>
<li>программное обеспечение и операционные системы;</li>
<li>электронные документы.</li>
</ul>
<p>При этом требования не касаются:</p>
<ul>
<li>контента неправительственных организаций, если он не важен для пользователей с особыми потребностями;</li>
<li>сайтов и приложений общественных вещательных компаний;</li>
<li>документов Office, которые опубликованы до 23 сентября 2018, если они не используются в работе органов публичного сектора;</li>
<li>прямых трансляций;</li>
<li>онлайн-карт.</li>
</ul>
<h3>Кто обязан исполнять</h3>
<p>Органы публичного сектора в странах ЕС: городские администрации, суды, больницы, университеты, школы, библиотеки и многие другие.</p>
<p>Если коммерческая компания сотрудничает с государственной, то она тоже попадает под действие директивы. Например, когда Microsoft предоставляет государственным больницам Бельгии Windows, то она обязана выполнить требования этого закона.</p>
<h3>Содержание</h3>
<p>Закон разбит на 15 статей.</p>
<ol>
<li>Предмет и сфера применения.</li>
<li>Минимальная гармонизация (приведение директивы в соответствие с Европейским стандартом EN 301 549, о котором я расскажу чуть позже).</li>
<li>Определения.</li>
<li>Требования к доступности веб-сайтов и мобильных приложений.</li>
<li>Несоразмерное бремя.</li>
<li>Презумпция соответствия требованиям к доступности.</li>
<li>Дополнительные меры.</li>
<li>Мониторинг и отчёты.</li>
<li>Правоприменительная процедура.</li>
<li>Делегирование.</li>
<li>Процедура Комитета.</li>
<li>Преобразование в национальное право.</li>
<li>Пересмотр.</li>
<li>Вступление в силу.</li>
<li>Адресаты.</li>
</ol>
<h3>Какие требования</h3>
<p>Они основаны на принципах доступности и 38 критериях соответствия им на уровне AA из <a href="https://www.w3.org/TR/WCAG20/">Web Guidelines Access Content 2.0</a> или просто WCAG 2.0. Руководство будет упоминаться часто, так что я сделала его краткий обзор в конце статьи.</p>
<p>Некоторые эксперты считают, что в отношении мобильных приложений нужно полагаться на последнюю версию руководства — <a href="https://www.w3.org/TR/WCAG21/">WCAG 2.1</a>.</p>
<p>Ещё страны ЕС должны:</p>
<ul>
<li>Регулярно обновлять отчёты о доступности. В них описывается какие стандарты по доступности приняты в этой стране.</li>
<li>Объяснять, чем грозит нарушение директивы.</li>
<li>Описывать доступные способы обратной связи для сообщения о нарушениях требований директивы.</li>
<li>Продумывать методы и способы мониторинга и отчётности.</li>
<li>Разрабатывать технические спецификации для мобильных приложений.</li>
</ul>
<h3>Что, если я её нарушу?</h3>
<p>В директиве указаны сроки, за которые государственные органы должны привести свои сайты и приложения в порядок:</p>
<ul>
<li>Все сайты, созданные до 23 сентября 2018, должны быть доступны к сентябрю 2019.</li>
<li>Сайты, которые появились позже сентября 2018 — к 23 сентября 2020.</li>
<li>Мобильные приложения — к 23 июня 2021.</li>
</ul>
<p>Судебной практики пока нет, но в ближайшее время она начнёт появляться. За нарушения будут назначаться штрафы.</p>
<h3>Полезные ссылки</h3>
<ul>
<li><a href="https://ec.europa.eu/digital-single-market/en/web-accessibility">Веб-доступность</a>.</li>
<li><a href="https://eur-lex.europa.eu/legal-content/en/TXT/?uri=CELEX%3A32016L2102">Текст Директивы</a>.</li>
<li><a href="https://directive2102.eu/">Что такое Директива 2102?</a>.</li>
<li><a href="https://nda.ie/monitoring/eu-web-accessibility-directive">Европейская директива о веб-доступности</a>.</li>
</ul>
<h2>Европейский закон о доступности</h2>
<p>European Accessibility Act — EAA</p>
<figure>
    <img src="https://web-standards.ru/articles/a11y-and-law/images/accessibility-for-all.jpg" alt="Женщина с плакатом «Доступность для всех».">
    <figcaption>
        Фото <a href="http://www.edf-feph.org/european-accessibility-act-1">edf-feph.org</a>.
    </figcaption>
</figure>
<p>Закон утверждён Европарламентом в Страсбурге весной 2019 года. Сейчас дорабатывается.</p>
<p>У закона несколько целей:</p>
<ul>
<li>Уменьшить количество барьеров для людей с особыми потребностями, которые пользуются современным технологиям.</li>
<li>Установить чёткие требования для компаний, создающих технологии. Для начала стандартизировать их на основе WCAG, а потом расширить.</li>
<li>Сделать требования обязательными для всех компаний, не только государственных.</li>
</ul>
<h3>Кого защищает</h3>
<p>Граждан стран ЕС с инвалидностью, в том числе пожилых людей.</p>
<h3>Что именно затрагивает</h3>
<p>Все виды информационно-коммуникационных технологий:</p>
<ul>
<li>компьютеры;</li>
<li>смартфоны;</li>
<li>планшеты;</li>
<li>электронные книги;</li>
<li>телевизоры, в целом ТВ-вещание;</li>
<li>банкоматы;</li>
<li>сайты и приложения;</li>
<li>программы и операционные системы;</li>
<li>многое другое.</li>
</ul>
<h3>Кто обязан исполнять</h3>
<p>Государственные и коммерческие компании, которые создают информационно-коммуникационные технологии.</p>
<h3>Какие требования</h3>
<p>Взяты из руководств <a href="https://www.w3.org/TR/WCAG20/">WCAG 2.0</a> и <a href="https://www.w3.org/TR/WCAG21/">WCAG 2.1</a>. Потом их планируется расширить. Текста закона нет в публичном доступе, так что пока сложно сказать что-то конкретное.</p>
<h3>Что, если я его нарушу?</h3>
<p>Вас явно будут ждать штрафы. Какие — покажет окончательный текст закона и время.</p>
<h3>Полезные ссылки</h3>
<ul>
<li><a href="https://ec.europa.eu/social/main.jsp?catId=1202">Европейский закон о доступности</a>.</li>
<li><a href="https://www.3playmedia.com/2019/03/22/european-accessibility-act-eaa/">Что вам нужно знать о Европейском законе о доступности</a>.</li>
</ul>
<h2>Европейский стандарт EN 301 549</h2>
<p>Standard EN 301 549 — EN 301 549</p>
<p>Полное название: «Европейский стандарт EN 301 549 „Требования к доступности информационно-коммуникационных технологий продуктов и услуг“» (European standard EN 301 549 «Accessibility requirements for ICT products and services»).</p>
<h3>Кого защищает</h3>
<p>Пользователей с инвалидностью в странах ЕС.</p>
<h3>Что именно затрагивает</h3>
<p>Любые информационно-коммуникационные технологии:</p>
<ul>
<li>сайты (веб-контент);</li>
<li>медиа (видео и аудио);</li>
<li>электронные документы;</li>
<li>программы;</li>
<li>мобильные приложения.</li>
</ul>
<h3>Содержание</h3>
<p>Состоит из трёх частей: общей информации, положении о функциональной производительности и функциональных требований к доступности. Важный нам <a href="http://mandate376.standards.eu/standard/technical-requirements">последний раздел</a> включает 9 пунктов:</p>
<ul>
<li>Общие требования.</li>
<li>Информационно-коммуникационные технологии с голосовой связью.</li>
<li>Информационно-коммуникационные технологии с субтитрами.</li>
<li>Оборудование.</li>
<li>Веб-контент.</li>
<li>Документы.</li>
<li>Программное обеспечение (не веб-контент).</li>
<li>Документация и сервисы техподдержки.</li>
<li>Диспетчерские и аварийные службы.</li>
</ul>
<h3>Какие требования</h3>
<p>Основан на требованиях <a href="https://www.w3.org/TR/WCAG20/">WCAG 2.0</a> на уровне критерия соответствия AA.</p>
<h3>Кто обязан его исполнять</h3>
<p>Государственные органы и любая европейская и транснациональная компания, которая сотрудничает со странами ЕС.</p>
<h3>Что, если я его нарушу?</h3>
<p>Стандарт даёт только рекомендации. Он становится обязательным, когда на него ссылается документ с юридической силой. Это может быть Директива о веб-доступности или Европейский закон о доступности.</p>
<h3>Полезные ссылки</h3>
<ul>
<li><a href="http://mandate376.standards.eu/standard">Текст Стандарта</a>.</li>
<li><a href="https://www.essentialaccessibility.com/blog/en-301-549/">EN 301 549: Европейский стандарт веб-доступности</a>.</li>
</ul>
<h2>США</h2>
<figure>
    <img src="https://web-standards.ru/articles/a11y-and-law/images/usa-flag.jpg" alt="Флаг Соединённых Штатов Америки.">
    <figcaption>
        Фото <a href="https://www.pexels.com/@einfoto">Matthis Volquardsen</a>.
    </figcaption>
</figure>
<p>Если вы интересуетесь темой доступности, то скорее всего уже что-то слышали о двух американских законах: Разделе 508 и ADA. Давайте их разберём.</p>
<h2>Раздел 508</h2>
<p>Section 508, 508 Compliance</p>
<p>Раздел 508 — это один из разделов федерального Закона о реабилитации 1973 года (Rehabilitation Act of 1973 или Rehab Act). Он защищает права американцев с инвалидностью в разных сферах.</p>
<p>Раздел 508 был добавлен в текст закона в 1998. Его цель «установить требования для федеральных агентств для того, чтобы они делали свои информационно-коммуникационные технологии доступными для людей с инвалидностью».</p>
<h3>Кого защищает</h3>
<p>Американцев с различными нарушениями.</p>
<h3>Что именно затрагивает</h3>
<p>Любые информационно-коммуникационные технологии, которые создают и используют федеральные агентства:</p>
<ul>
<li>Сайты;</li>
<li>Приложения;</li>
<li>Мультимедиа: видео и аудио, компьютерные игры;</li>
<li>Электронные письма и документы;</li>
<li>Программное обеспечение и операционные системы;</li>
<li>Технику: смартфоны, компьютеры, клавиатуры, принтеры, сканеры и другую.</li>
</ul>
<h3>Кто обязан исполнять</h3>
<p>Федеральные агентства и <strong>любые компании, которые сотрудничают с ними.</strong> Ими могут быть частные фирмы из США и других стран.</p>
<p>Федеральные агентства США — это разные государственные органы, например, министерства, парламент, суды, государственные учебные заведения.</p>
<p>Ещё действие закона распространяется на общественные организации, частные вузы, колледжи, школы и компании, которые получают субсидии и гранты из федерального бюджета или любые другие деньги от государства.</p>
<h3>Содержание</h3>
<p>Раздел 508 состоит из подразделов A, B, C и D.</p>
<ul>
<li>Подраздел A общий. В нём есть цели раздела, определения, как его применять.</li>
<li>Подраздел B содержит технические стандарты, которые касаются программного обеспечения, операционных систем, мультимедиа, приложений и компьютеров.</li>
<li>В подразделе C функциональные критерии производительности.</li>
<li>В последнем подразделе, D, говорится о документации и поддержке.</li>
</ul>
<h3>Какие требования</h3>
<p>Все требования из Раздела 508 можно разделить на три большие группы:</p>
<ul>
<li><strong>Технические требования:</strong> Код сайтов и приложений, программное обеспечение и операционные системы должны быть совместимы со вспомогательными технологиями.</li>
<li><strong>Функциональные требования:</strong> Люди с инвалидностью должны иметь доступ ко всей системе в целом, а не к её отдельной части.</li>
<li><strong>Требования поддержки:</strong> Люди с особыми потребностями должны иметь доступ ко всем документам, которые описывают как взаимодействовать с системой.</li>
</ul>
<p>Требования в законе тоже основаны на принципах и критериях соответствия им на уровне AA из <a href="https://www.w3.org/TR/WCAG20/">WCAG 2.0</a>.</p>
<p>В положении «Безопасная гавань» (Safe Harbor) указано, что если продукт был создан давно и не изменялся, то он должен соответствовать старому стандарту <a href="https://www.w3.org/TR/WAI-WEBCONTENT/">WCAG 1.0</a> 1999 года. Если изменился, то тогда применяются требования из WCAG 2.0.</p>
<h3>Что, если я его нарушу?</h3>
<figure>
    <img src="https://web-standards.ru/articles/a11y-and-law/images/gavel.jpg" alt="Молоток судьи.">
    <figcaption>
        В любой статье о законах должна быть картинка с молотком судьи. Фото <a href="https://www.pexels.com/@pixabay">Pixabay</a>.
    </figcaption>
</figure>
<p>Требования «Раздела 508» жёстче, чем у других американских законов.</p>
<p>Одно из самых известных судебных разбирательств — <a href="https://www.3playmedia.com/2016/09/28/dra-v-uc-berkeley-a-win-win-with-structured-negotiation/">дело Защитников прав инвалидов (Disability Rights Advocates, DRA) против Университета Беркли</a>. Оно началось в 2011 и закончилось в 2013 тем, что университет согласился выплатить штраф и разобраться с доступностью. А причина в том, что в университетской библиотеке не было доступных электронных учебников для студентов с нарушениями зрения.</p>
<p>100 000 $ университет должен был заплатить адвокатам, 93 000 $ — за внедрение в течение трёх лет доступных электронных учебников, а 120 000 $ — за найм новых сотрудников и покупку нового оборудования для библиотеки.</p>
<p>Ещё одно дело, которое началось в феврале 2015 — <a href="https://www.3playmedia.com/2015/02/12/harvard-mit-sued-captioning-violation-ada-rehabilitation-act/">дело против Гарвардского университета и Массачусетского технологического института</a>. Национальная ассоциация глухих (National Association of the Deaf, NAD) подала иск против вузов из-за отсутствия субтитров в видео из онлайн-курсов. Разбирательства продолжаются.</p>
<h3>Полезные ссылки</h3>
<ul>
<li><a href="https://en.wikipedia.org/wiki/Rehabilitation_Act_of_1973">Закон о реабилитации 1973 года</a>.</li>
<li><a href="https://web.archive.org/web/20201112021536/https://www.access-board.gov/guidelines-and-standards/communications-and-it/about-the-section-508-standards/section-508-standards">Текст Раздела 508</a>.</li>
<li><a href="https://siteimprove.com/en-us/accessibility/section-508-compliance/">Соответствие требованиям 508: что такое Раздел 508?</a>.</li>
<li><a href="https://web.archive.org/web/20200715142027/https://www.hhs.gov/web/section-508/making-files-accessible/checklist/index.html">Чеклист доступности соответствия Разделу 508 Министерства здравоохранения и социальных служб США</a>.</li>
<li><a href="https://www.3playmedia.com/2017/12/21/the-ultimate-section-508-refresh-checklist/">Полный чеклист обновлённого Раздела 508</a>.</li>
<li><a href="https://508compliantdocumentconversion.com/section-508-lawsuits/">Судебные процессы</a>.</li>
</ul>
<h2>Закон об американцах-инвалидах</h2>
<p>Americans with Disabilities Act — ADA</p>
<p>ADA — это закон, который защищает права людей с инвалидностью. Принят в 1990 году.</p>
<p>Он запрещает дискриминацию людей с особыми потребностями во всех сферах жизни общества: на работе, в школах, транспорте и в других публичных местах.</p>
<h3>Кого защищает</h3>
<p>Американцев с инвалидностью.</p>
<h3>Что именно затрагивает</h3>
<p>Те же информационно-коммуникационные технологии, что и Раздел 508:</p>
<ul>
<li>Сайты;</li>
<li>Приложения;</li>
<li>Мультимедиа: видео и аудио, компьютерные игры;</li>
<li>Электронные письма и документы;</li>
<li>Программное обеспечение и операционные системы;</li>
<li>Технику: смартфоны, компьютеры, клавиатуры, принтеры, сканеры и так далее.</li>
</ul>
<h3>Кто обязан исполнять</h3>
<p>Любые коммерческие и некоммерческие организации, которые оказывают услуги большому числу людей. Это банки, магазины, школы, гостиницы, кинотеатры, места общественного питания.</p>
<p>Исключения — религиозные организации, частные клубы и другие компании, которые, наоборот, работают с небольшим числом людей.</p>
<h3>Содержание</h3>
<p>Закон содержит пять больших разделов:</p>
<ol>
<li>Трудоустройство.</li>
<li>Общественные организации и транспорт.</li>
<li>Общественные места и коммерческие объекты.</li>
<li>Телекоммуникации.</li>
<li>Прочие положения.</li>
</ol>
<p>Это не очевидно, но 3 раздел связан с веб-доступностью. Подразумевается, что информационно-коммуникационные технологии — это места общественного пользования. В разделе с судебной практикой я подробнее раскрою эту логику.</p>
<h3>Какие требования</h3>
<p>Все требования к информационно-коммуникационным технологиям в ADA разделены на три группы:</p>
<ul>
<li><strong>Технические:</strong> Код сайтов и приложений, программное обеспечение и в целом операционные системы должны быть совместимы со вспомогательными технологиями.</li>
<li><strong>Функциональные:</strong> Люди с инвалидностью должны иметь доступ ко всей системе в целом.</li>
<li><strong>Поддержка:</strong> Люди с особыми потребностями должны иметь доступ к документам, которые описывают как работать с системой.</li>
</ul>
<p>В самом законе нет чётких предписаний для сайтов и приложений, но суды по аналогии с «Разделом 508» ссылаются на критерии <a href="https://www.w3.org/TR/WCAG20/">WCAG 2.0</a> и соответствие им на уровне AA.</p>
<h3>Что, если я его нарушу?</h3>
<p>В 2018 году количество исков, поданных из-за нарушения третьего раздела ADA, было почти в три раза больше, чем в 2017. Их число <a href="https://www.adatitleiii.com/2019/01/number-of-federal-website-accessibility-lawsuits-nearly-triple-exceeding-2250-in-2018/">выросло с 814 до 2258</a>.</p>
<p>Одним из самых известных процессов стало <a href="http://www.adasoutheast.org/ada/publications/legal/Gil_v_Winn-Dixie.php">дело Гила против Winn-Dixie</a> в 2018.</p>
<p>Хуан Карлос Гил подал иск против продуктовой сети Winn-Dixie из-за того, что не смог воспользоваться скринридером на её сайте. Так как у Гила есть инвалидность по зрению, то суд состоялся на основании нарушения 3 раздела ADA.</p>
<p>Суд сделал вывод, что магазины этой сети — общественные места и попадают под действие ADA. Так как на сайте размещена информация о магазинах, то он — это один из сервисов мест общественного пользования.</p>
<p>В итоге Winn-Dixie была признана виновной в нарушении ADA. Ей был назначен штраф в 250 000 $. Также она обязана сделать сайт доступным и соответствующим критериям WCAG 2.0.</p>
<p>Это решение стало прецедентным и используется в качестве основания для вынесения приговоров по другим делам. Например, в <a href="https://medium.com/@sheribyrnehaber/this-week-in-accessibility-robles-v-dominos-1af8fdb4ce8f">деле Роблеса против Domino’s Pizza</a>.</p>
<p>У Гильермо Роблеса слепота. Он пытался заказать пиццу через приложение Domino’s Pizza, но не смог из-за его недоступности. Гильермо подал в суд на сеть пиццерий в 2018. Разбирательства ещё продолжаются, но всё идёт к тому, что сторона обвинения выиграет.</p>
<blockquote>
    <p>
        Глядя на неразборчивое заявление Domino’s
        (контраст, размер текста), думаю, что за ними
        могут прийти не только незрячие пользователи.
    </p>
    <footer>
        <cite>
            <a href="https://twitter.com/aardrian/status/1181713165220040706">@aardrian</a>
        </cite>
    </footer>
</blockquote>
<p>Есть важный нюанс: суды обычно выносят обвинительные приговоры тем организациям, у которых есть физическое помещение и сайт или приложение с информацией о нём. Например, у Domino’s Pizza в приложении есть доставка из конкретных пиццерий и меню. Если у организации сайт не связан с помещением, скажем, это просто кулинарный блог, то штрафа может и не быть.</p>
<h3>Полезные ссылки</h3>
<ul>
<li><a href="https://en.wikipedia.org/wiki/Americans_with_Disabilities_Act_of_1990">Статья о законе в Википедии</a>.</li>
<li><a href="https://www.ada.gov/2010_regs.htm">Текст Закона об американцах-инвалидах</a>.</li>
<li><a href="https://adata.org/learn-about-ada">Что такое Закон об американцах-инвалидах (ADA)?</a></li>
<li><a href="https://medium.com/@krisrivenburgh/the-ada-checklist-website-compliance-guidelines-for-2019-in-plain-english-123c1d58fad9">«Чеклист ADA: правила соответствия сайтов стандарту в 2019 году простым языком»</a> Криса Ривенбурга.</li>
<li><a href="https://www.adatitleiii.com/">Сайт с новостями о судах из-за нарушений ADA</a>.</li>
</ul>
<h2>Россия</h2>
<figure>
    <img src="https://web-standards.ru/articles/a11y-and-law/images/russia-flag.jpg" alt="Флаг Российской Федерации.">
    <figcaption>
        Фото <a href="https://www.pexels.com/@ajtandle">Ajinkya Tandle</a>.
    </figcaption>
</figure>
<p>Веб-доступность в России регулируется тремя законодательными актами и одним стандартом:</p>
<ul>
<li>Федеральным законом № 419-ФЗ.</li>
<li>Федеральным законом № 8-ФЗ.</li>
<li>Приказом Министерства связи и массовых коммуникаций России № 483.</li>
<li>ГОСТом Р 52872–2012. Интернет-ресурсы. Требования доступности для инвалидов по зрению.</li>
</ul>
<h2>Федеральный закон № 419-ФЗ</h2>
<p>У закона <em>лаконичное</em> название «Федеральный закон от 1 декабря 2014 года № 419-ФЗ „О внесении изменений в отдельные законодательные акты Российской Федерации по вопросам социальной защиты инвалидов в связи с ратификацией конвенции о правах инвалидов“». Вступил в силу в январе 2016.</p>
<p>Он запрещает дискриминацию людей по признаку инвалидности. Основан на <a href="https://www.un.org/development/desa/disabilities/convention-on-the-rights-of-persons-with-disabilities.html">Конвенции ООН о защите прав людей с инвалидностью</a> и <a href="http://www.consultant.ru/document/cons_doc_LAW_8559/">Федеральном законе № 181-ФЗ «О социальной защите инвалидов в Российской Федерации»</a>.</p>
<p>В нём дискриминация — это</p>
<blockquote>
<p>Любое различие, исключение или ограничение по причине инвалидности, целью либо результатом которых является умаление или отрицание признания, реализации или осуществления наравне с другими всех гарантированных в Российской Федерации прав и свобод человека и гражданина в политической, экономической, социальной, культурной, гражданской или любой иной области.</p>
</blockquote>
<h3>Кого защищает</h3>
<p>Россиян с инвалидностью.</p>
<h3>Что именно затрагивает</h3>
<p>Разные сферы от железных дорог, музеев и библиотек до государственных органов власти.</p>
<p>Веб-доступности касается только статья 22. В ней сказано, что нужно внести изменения в статью 10 Федерального закона от 9 февраля 2009 года № 8-ФЗ и установить «порядок обеспечения условий доступности для инвалидов по зрению официальных сайтов» органов государственной власти.</p>
<h3>Кто должен его исполнять</h3>
<p>Российские федеральные органы государственной власти, органы государственной власти субъектов и местного самоуправления. Если говорить конкретно, то это:</p>
<ul>
<li>Федеральное Собрание (Совет Федерации и Государственная Дума);</li>
<li>Министерства и организации, которые им подчиняются;</li>
<li>Городские администрации;</li>
<li>Суды;</li>
<li>Государственные больницы и поликлиники;</li>
<li>Государственные школы, колледжи, вузы;</li>
<li>…и другие.</li>
</ul>
<h3>Содержание</h3>
<p>Состоит из 26 статей, которые дополняют другие законы новыми требованиями.</p>
<h3>Какие требования</h3>
<p>В самом тексте 22 статьи ничего конкретно не указано. Но отсылки приведут нас к 10 статье Федерального закона № 8-ФЗ, о котором речь пойдёт ниже.</p>
<p>Спойлер: на всех государственных сайтах должна быть <em>отдельная</em> версия для слепых и слабовидящих. Требования к ней основаны на требованиях из <a href="https://www.w3.org/TR/WCAG20/">WCAG 2.0</a>.</p>
<h3>Что, если я его нарушу?</h3>
<p>Для судов отсутствие версии сайта для слепых и слабовидящих — дискриминация людей по признаку инвалидности. Можно получить штраф за нарушение <a href="http://www.consultant.ru/document/cons_doc_LAW_34661/8b36cedfef8c1b205cdbdcd85ee7a7dc250f5109/">статьи 5.62</a> Кодекса об административных правонарушениях или <a href="http://www.consultant.ru/document/cons_doc_LAW_10699/67c198fece5202f893460246a15f884f72173c28/">статьи 136</a> Уголовного Кодекса.</p>
<p>За нарушение Кодекса об административных правонарушениях штраф для физических лиц составит от 1 000 до 3 000 ₽, а для юридических — от 50 000 до 100 000 ₽.</p>
<p>Если суд сделает вывод, что нарушение связано со злоупотреблением служебным положением, то оно станет уголовным преступлением. Возможные последствия:</p>
<ul>
<li>Штраф от 100 000 до 300 000 ₽ или в размере доходов виновного за период от 1 до 2 лет;</li>
<li>Запрет на занимание государственных должностей и аналогичную работу до 5 лет;</li>
<li>Общественные работы до 480 часов, исправительные работы до 2 лет или принудительные работы до 5 лет;</li>
<li>При крайней тяжести нарушения — лишение свободы до 5 лет.</li>
</ul>
<p>Можно найти и примеры судебных разбирательств.</p>
<p>В августе 2017 сайт администрации Шенкурского муниципального района из Архангельской области не прошёл проверку на наличие версии для слепых и слабовидящих. В декабре 2017 <a href="https://sudact.ru/regular/doc/ie1hS05hLNHY/">суд обязал</a> администрацию района решить проблему с доступностью. Она <a href="http://shenradm.ru/">выполнила требования</a> и не получила штраф.</p>
<p>На <a href="https://sudact.ru/">сайте судебных и нормативных актов Российской Федерации</a> можно поискать другие решения судов.</p>
<h3>Полезные ссылки</h3>
<ul>
<li><a href="http://www.consultant.ru/document/cons_doc_LAW_171577/">Текст Федерального закона № 419-ФЗ</a>.</li>
<li><a href="https://usabilitylab.ru/blog/funkczional-dlya-slabovidyashhix-nebolshoj-obzor-i-rekomendaczii/">«Функционал для слабовидящих: небольшой обзор и рекомендации»</a> Анны Минаевой.</li>
</ul>
<h2>Федеральный закон № 8-ФЗ</h2>
<p>Полное название «Федеральный закон от 9 февраля 2009 года № 8-ФЗ „Об обеспечении доступа к информации о деятельности государственных органов и органов местного самоуправления“».</p>
<h3>Структура</h3>
<p>Закон состоит из 5 глав. В каждой есть несколько статей.</p>
<ol>
<li>Общие положения.</li>
<li>Организация доступа к информации о деятельности государственных органов и органов местного самоуправления. Основные требования при обеспечении доступа к этой информации.</li>
<li>Предоставление информации о деятельности государственных органов и органов местного самоуправления.</li>
<li>Ответственность за нарушение порядка доступа к информации о деятельности государственных органов и органов местного самоуправления.</li>
<li>Заключительные положения.</li>
</ol>
<p>Федеральный закон № 419-ФЗ вносит изменения в статью 10. Она входит во вторую главу и называется «Организация доступа к информации о деятельности государственных органов и органов местного самоуправления, размещаемой в сети „Интернет“».</p>
<h3>Кого защищает</h3>
<p>Россиян с инвалидностью.</p>
<h3>Что именно затрагивает</h3>
<p>Государственные сайты и их контент.</p>
<h3>Кто должен его исполнять</h3>
<p>Российские федеральные органы государственной власти, органы государственной власти субъектов и местного самоуправления. Это парламент, министерства, суды, местные администрации и советы.</p>
<h3>Какие требования</h3>
<p>Они не указаны напрямую. Нас опять отсылают к другому закону. В этот раз к Приказу министерства связи и массовых коммуникаций России № 483. Его я разберу дальше.</p>
<p>На самом деле мы снова встретим требования из <a href="https://www.w3.org/TR/WCAG20/">WCAG 2.0</a>, которые ограничиваются <em>отдельной</em> версией сайта для слепых и слабовидящих.</p>
<figure>
    <img src="https://web-standards.ru/articles/a11y-and-law/images/special-roscosmos-site.png" alt="Версия для слабовидящих сайта Роскосмоса: цвета инвертированы, часть текстов слилась с фоном.">
    <figcaption>
        Версия для слабовидящих <a href="https://www.roscosmos.ru/">сайта Роскосмоса</a>.
    </figcaption>
</figure>
<h3>Что, если я его нарушу?</h3>
<p>Если вы работаете в государственных организациях, то можете понести дисциплинарную, административную, гражданскую и уголовную ответственность.</p>
<p>Это <a href="http://www.consultant.ru/document/cons_doc_LAW_34661/8b36cedfef8c1b205cdbdcd85ee7a7dc250f5109/">статья 5.62</a> Кодекса об административных правонарушениях или <a href="http://www.consultant.ru/document/cons_doc_LAW_10699/67c198fece5202f893460246a15f884f72173c28/">статья 136</a> Уголовного Кодекса. О них я уже рассказала в разделе про Федеральный закон № 419-ФЗ.</p>
<h3>Полезные ссылки</h3>
<ul>
<li><a href="http://www.consultant.ru/document/cons_doc_LAW_84602/">Текст Федерального закона № 8-ФЗ</a>.</li>
</ul>
<h2>Приказ министерства связи и массовых коммуникаций России № 483</h2>
<p>У приказа <em>самое лаконичное</em> название из всех «Об установлении порядка обеспечения условий доступности для инвалидов по зрению официальных сайтов федеральных органов государственной власти, органов государственной власти субъектов Российской Федерации и органов местного самоуправления в сети „Интернет“».</p>
<p>Принят 30 ноября 2015.</p>
<h3>Кого защищает</h3>
<p>Людей со зрительными нарушениями: слепых и слабовидящих.</p>
<h3>Что именно затрагивает</h3>
<p>Сайты органов государственной власти и их контент.</p>
<h3>Кто должен его исполнять</h3>
<p>Российские федеральные органы власти, органы власти субъектов и местного самоуправления. Это могут быть разные министерства, суды, городские администрации.</p>
<h3>Структура</h3>
<p>Состоит из 4 пунктов требований.</p>
<h3>Какие требования</h3>
<p>В приказе перечислена пара конкретных требований к версиям сайтов для слепых и слабовидящих:</p>
<ul>
<li>На неё можно перейти с главной страницы сайта;</li>
<li>Изображения должны быть описаны в виде текста, если они не декоративные;</li>
<li>На специализированных сайтах для людей с инвалидностью по зрению должна быть информация о том, как им воспользоваться госуслугами.</li>
</ul>
<h3>Что, если я его нарушу?</h3>
<p>Тогда вы понесёте административную или уголовную ответственность, так как нарушите <a href="http://www.consultant.ru/document/cons_doc_LAW_34661/8b36cedfef8c1b205cdbdcd85ee7a7dc250f5109/">статью 5.62</a> Кодекса об административных правонарушениях или <a href="http://www.consultant.ru/document/cons_doc_LAW_10699/67c198fece5202f893460246a15f884f72173c28/">статью 136</a> Уголовного Кодекса.</p>
<h3>Полезные ссылки</h3>
<ul>
<li><a href="https://minsvyaz.ru/ru/documents/4985/">Текст Приказа Минкомсвязи России № 483</a>.</li>
</ul>
<h2>Государственный стандарт</h2>
<p>ГОСТ Р 52872–2012 Интернет-ресурсы. Требования доступности для инвалидов по зрению</p>
<figure>
    <img src="https://web-standards.ru/articles/a11y-and-law/images/snellen-chart.jpg" alt="Таблица для проверки остроты зрения.">
    <figcaption>
        Фото <a href="https://unsplash.com/@dtravisphd">David Travis</a>.
    </figcaption>
</figure>
<p>Введён в 2014 после ратификации Россией в мае 2012 года <a href="https://www.un.org/development/desa/disabilities/convention-on-the-rights-of-persons-with-disabilities.html">Конвенции о правах людей с инвалидностью</a>, содержит рекомендации о доступности сайтов и их контента.</p>
<p>Доступность для людей с нарушениями зрения — это</p>
<blockquote>
<p>Возможность полноценного доступа инвалидов по зрению ко всем компонентам электронных ресурсов сети Интернeт.</p>
</blockquote>
<h3>Кого защищает</h3>
<p>Людей с нарушениями зрения: слепых и слабовидящих.</p>
<h3>Что именно затрагивает</h3>
<p>Электронные интернет-ресурсы на русском языке.</p>
<blockquote>
<p>Интернет-ресурс — элемент сети Интернет, например, веб-страница, почтовый сервер или поисковая машина.</p>
</blockquote>
<p>То есть как минимум:</p>
<ul>
<li>Сайты и их контент;</li>
<li>Поисковики;</li>
<li>Почтовые сервисы.</li>
</ul>
<h3>Кто обязан исполнять</h3>
<p>Если на него не ссылаются законы и суды, то никто, так как стандарт даёт только рекомендации.</p>
<h3>Содержание</h3>
<ul>
<li>Предисловие.</li>
<li>Область применения.</li>
<li>Нормативные ссылки.</li>
<li>Термины, определения и сокращения.</li>
<li>Общие требования.</li>
<li>Требования к компонентам интернет-ресурсов.</li>
<li>Соответствие уровням доступности.</li>
<li>Приложения.</li>
<li>Библиография.</li>
</ul>
<h3>Какие требования</h3>
<p>Требования и уровни соответствия им взяты из <a href="https://www.w3.org/TR/WCAG20/">WCAG 2.0</a>. Только довольно урезанного руководства, так как они касаются <em>отдельной</em> версии сайта для слепых и слабовидящих.</p>
<h3>Полезные ссылки</h3>
<ul>
<li><a href="http://docs.cntd.ru/document/1200103663">Текст ГОСТ Р 52872–2012. Интернет-ресурсы. Требования доступности для инвалидов по зрению</a>.</li>
<li><a href="https://weblind.ru/">Как помочь слепым на вашем сайте</a>.</li>
</ul>
<h2>WCAG</h2>
<p>Все законы, о которых я рассказала, используют требования из <a href="https://www.w3.org/TR/WCAG20/">Руководства по обеспечению доступности веб-контента</a> (Web Guidelines Access Content, WCAG). Это руководство разработано группой <a href="https://www.w3.org/WAI/">Web Accessibility Initiative</a> (WAI) из <a href="https://www.w3.org/">W3C</a>.</p>
<p>Оно состоит из рекомендаций о том, как сделать контент в вебе максимально доступным для большого числа пользователей с ограниченными возможностями. Это могут быть люди с ограничениями по зрению (незрячие и слабовидящие), с нарушениями слуха (глухие и слабослышащие), с когнитивными или моторными нарушениями.</p>
<p>Критерии выполнения WCAG — это проверяемые утверждения, которые не привязаны к конкретным технологиям. В предпоследней версии WCAG 2.0 их 38. В последней WCAG 2.1 их на 17 больше — 55.</p>
<p>В основном все законы требуют соответствия предпоследней версии стандарта. Это связано с тем, что последняя вышла после их принятия — в июне 2018. Внесение правок в законы занимает много времени, поэтому они не всегда успевают за изменениями в стандартах.</p>
<p>Руководство основано на четырёх базовых принципах:</p>
<ol>
<li><strong>Воспринимаемость:</strong> Пользовательские интерфейсы и информация в них должны быть представлены только в том виде, который могут воспринимать любые пользователи.</li>
<li><strong>Управляемость:</strong> Навигация должна быть доступна для всех пользователей, включая тех, кто пользуется клавиатурой или вспомогательными технологиями.</li>
<li><strong>Понятность:</strong> Контент и дизайн пользовательского интерфейса должен быть понятным каждому пользователю.</li>
<li><strong>Надёжность:</strong> Контент должен быть надёжным и отображаться на всех устройствах и во всех программах и приложениях, которые используют пользователи, в том числе вспомогательные технологии.</li>
</ol>
<p>Сокращённо все принципы называются POUR по их первым буквам: <strong>P</strong>erceivable, <strong>O</strong>perable, <strong>U</strong>nderstandable, <strong>R</strong>obust.</p>
<p>Рекомендации имеют несколько уровней соответствия:</p>
<ul>
<li>A (низший).</li>
<li>AA (средний).</li>
<li>AAA (наивысший).</li>
</ul>
<p>Законы о доступности требуют минимум уровня AA.</p>
<h3>Полезные ссылки</h3>
<ul>
<li><a href="https://www.w3.org/TR/WCAG20/">WCAG 2.0 на английском языке</a>.</li>
<li><a href="https://www.w3.org/Translations/WCAG20-ru/WCAG20-ru-20130220/">WCAG 2.0 на русском языке</a>.</li>
<li><a href="https://www.w3.org/TR/WCAG21/">WCAG 2.1</a>.</li>
<li><a href="https://medium.com/c2-group/wcag-2-1-guidelines-explained-with-examples-5c7c5d8b69eb#7312">WCAG 2.1: руководства с поясняющими примерами</a>.</li>
<li><a href="https://webaim.org/standards/wcag/checklist">Чеклист WCAG 2 WebAIM</a>.</li>
</ul>
<h2>Выводы</h2>
<p>Обсуждать доступность хорошо, а регулировать её с помощью законов — ещё лучше. Это помогает не только людям с инвалидностью, но и компаниям, которые создают технологии. Сложно что-то делать правильно, если не знаешь, как именно это нужно делать. Поэтому стандартизация требований к доступности делает разработку предсказуемой и прозрачной.</p>
<p>В статье я разобрала важные законы о доступности в ЕС, США и России. Они есть и в Австралии, Исландии, Японии, Южной Корее и многих других странах. Можете заглянуть в обзор Гаренна Бигби <a href="https://dynomapper.com/blog/27-accessibility-testing/532-international-web-accessibility-laws-and-policies">«Международные законы и политика в отношении доступности сайтов»</a>, чтобы узнать об этом больше. Ещё есть книга о законодательстве в области доступности — <a href="https://www.amazon.com/Web-Accessibility-Standards-Regulatory-Compliance/dp/1590596382">«Веб-доступность: веб-стандарты и нормативные требования»</a>.</p>
<p>В основном требования к доступности в законах основаны на WCAG. Это руководство о том, как сделать доступными сайты и приложения. Так что для того, чтобы не получить штраф за нарушение законов о доступности, стоит держать его под рукой.</p>
<p>Европейское и американское законодательства в целом похожи. Они покрывают много информационно-коммуникационных технологий. При этом регулируются продукты и госсектора, и частных компаний.</p>
<p>В российском законодательстве о доступности много пробелов. И законы, и стандарт выделяют <em>только</em> людей с нарушениями зрения. Это странно для законов, которые борются с дискриминацией по признаку инвалидности. Почему из них тогда исключены люди с глухотой, проблемами с моторикой и ментальными нарушениями?</p>
<p>Не менее странно то, что проблему доступности для людей с нарушениями зрения решают с помощью отдельной версии сайта. Уже на уровне закона мир зрячих отделяют от мира незрячих, хотя можно сделать сайт доступным и без такого старомодного подхода. Гораздо проще учесть основные требования WCAG и сделать сайт доступным для большинства пользователей, чем делать одну версию недоступной для всех и другую доступной для конкретных пользователей.</p>
<p>Ещё больше вопросов к российским законам появляется, когда вспоминаешь о реальном мире. Кроме сайтов в нём есть мобильные приложения и настольные программы. Они никак не регулируются.</p>
<p>Также кроме государственных сайтов в законах напрямую не упомянуты негосударственные. Хорошо, когда ты можешь без проблем оформить пособие по инвалидности, но что насчёт остального веба? Как купить билет на концерт, прочитать книгу по функциональному программированию или записаться на онлайн-курсы по C++?</p>
<p>Не ограничивайтесь только требованиями законов. Конечно, важно их знать и понимать, какие требования нужно учитывать при разработке своих продуктов. Просто помните, что если вы создаёте недоступный продукт для других, то однажды можете оказаться на их месте. И это будет не самое приятное и инклюзивное место.</p>

                    ]]></description><pubDate>Mon, 04 Nov 2019 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/a11y-and-law/</guid></item><item><title>Инклюзивные компоненты: сворачиваемые секции</title><link>https://web-standards.ru/articles/collapsible-sections/</link><description><![CDATA[
                        <p>Сворачиваемые секции, возможно, самый элементарный паттерн проектирования взаимодействия в вебе. Всё, что они делают — это переключают видимость контента при клике на заголовок. Ничего особенного.</p>
<p>Хотя это простое взаимодействие, оно <a href="https://caniuse.com/#feat=details">не имеет нативной и одинаковой реализации в разных браузерах</a>. И это несмотря на то, что есть движение в сторону его стандартизации. Так что это отличный аналог «hello world» для погружения в доступность с точки зрения проектирования взаимодействия с использованием JavaScript и WAI-ARIA.</p>
<p>Почему я говорю об этом только после того, как рассказал про более сложные компоненты? Дело в том, что в этой статье я сосредоточусь на опыте автора и разработчика: мы собираемся сделать наши раскрываемые области веб-компонентами, чтобы они могли легко стать частью более крупных паттернов и быть встроенными в существующий контент.</p>
<p>Как и в случае вкладок, это помогает понять, каким будет наш компонент без улучшения при помощи JavaScript и почему это действительно поможет сделать что-то лучше. В этом случае сворачиваемая секция без JavaScript — просто раздел. То есть это подзаголовок, кратко описывающий какой-то контент: текст, медиа, да что угодно.</p>
<pre><code tabindex="0" class="language-html">&lt;h2&gt;Мой раздел&lt;/h2&gt;
&lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras efficitur laoreet massa. Nam eu porta dolor. Vestibulum pulvinar lorem et nisl tempor lacinia.&lt;/p&gt;
&lt;p&gt;Cras mi nisl, semper ut gravida sed, vulputate vel mauris. In dignissim aliquet fermentum. Donec arcu nunc, tempor sed nunc id, dapibus ornare dolor.&lt;/p&gt;
</code></pre>
<p>Одно из преимуществ _сворачивания _контента заключается в том, что заголовки становятся смежными элементами. Это даёт пользователю возможность изучить доступный контент без необходимости много скроллить и увидеть его полностью, развернув блок.</p>
<img src="https://web-standards.ru/articles/collapsible-sections/images/headers-to-collapsible.png" alt="Слева - развернутые секции с заголовками следующие друг за другом. Справа - все секции свернуты. Друг за другом следуют только заголовки.">
<p>Другое преимущество — это то, что пользователям клавиатуры не нужно проходить через все элементы страницы, на которых можно сделать фокус, чтобы попасть туда, куда они хотят. На скрытом контенте его установить нельзя.</p>
<h2>Адаптивная разметка</h2>
<p>Рискованно просто добавлять обработчик клика для заголовков, чтобы связать с ними контент. Это не то взаимодействие, которого ожидают вспомогательные технологии или которого можно добиться при помощи клавиатуры. Вместо этого нам нужно адаптировать разметку, добавив в неё стандартную кнопку.</p>
<pre><code tabindex="0" class="language-html">&lt;h2&gt;&lt;button&gt;Мой раздел&lt;/button&gt;&lt;/h2&gt;
&lt;div&gt;
    &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras efficitur laoreet massa. Nam eu porta dolor. Vestibulum pulvinar lorem et nisl tempor lacinia.&lt;/p&gt;
    &lt;p&gt;Cras mi nisl, semper ut gravida sed, vulputate vel mauris. In dignissim aliquet fermentum. Donec arcu nunc, tempor sed nunc id, dapibus ornare dolor.&lt;/p&gt;
&lt;/div&gt;
</code></pre>
<p><strong>Примечание</strong>: я обернул контент в элемент <code>&lt;div&gt;</code> для того, чтобы использовать скрипт ниже для его показа и скрытия.</p>
<p>Кнопка в данном случае — дочерний элемент заголовка. Это означает, что, когда пользователь скринридера сделает фокус на <code>&lt;button&gt;</code>, то кнопка будет определена вместе с её родительским элементом: <em>«Мой раздел, кнопка, заголовок второго уровня»</em> (или аналогичным образом в зависимости от скринридера).</p>
<img src="https://web-standards.ru/articles/collapsible-sections/images/button-in-header.png" alt="Кнопка 'Мой раздел' внутри заголовка второго уровня.">
<p>Если бы вместо этого мы _преобразовали _заголовок в кнопку, используя ARIA-атрибут <code>role=&quot;button&quot;</code>, то переопределили бы семантику заголовка. Пользователи скринридеров потеряли бы заголовок в качестве структурной и навигационной метки.</p>
<p>Также нам бы пришлось кастомизировать код для всех вариантов поведения элемента <code>&lt;button&gt;</code>, например, для поведения при фокусе (см. <code>tabindex</code> в примере ниже), и привязывать нужные клавиши, чтобы активировать наш кастомный контрол.</p>
<pre><code tabindex="0" class="language-html">&lt;!-- Не делайте так --&gt;
&lt;h2 role=&quot;button&quot; tabindex=&quot;0&quot;&gt;Мой раздел&lt;/h2&gt;
</code></pre>
<h3>Состояние</h3>
<p>Наш компонент может находится в одном из двух взаимоисключающих состояний: свёрнутом и развёрнутом. Его состояние может быть отображено визуально, но необходимо также сообщать о нём и программно. Можно сделать это, добавив для кнопки атрибут <code>aria-expanded</code> с начальным значением <code>false</code> (в свёрнутом состоянии). Соответственно, нам нужно скрыть связанный <code>&lt;div&gt;</code>. В данном случае при помощи <code>hidden</code>.</p>
<pre><code tabindex="0" class="language-html">&lt;h2&gt;&lt;button aria-expanded=&quot;false&quot;&gt;Мой раздел&lt;/button&gt;&lt;/h2&gt;
&lt;div hidden&gt;
    &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras efficitur laoreet massa. Nam eu porta dolor. Vestibulum pulvinar lorem et nisl tempor lacinia.&lt;/p&gt;
    &lt;p&gt;Cras mi nisl, semper ut gravida sed, vulputate vel mauris. In dignissim aliquet fermentum. Donec arcu nunc, tempor sed nunc id, dapibus ornare dolor.&lt;/p&gt;
&lt;/div&gt;
</code></pre>
<p>Некоторые совершают ошибку, добавляя <code>aria-expanded</code> для <em>управляемого элемента</em> вместо контрола. И это понятно, ведь фактически именно у контента переключается состояние. Если подумать, это не принесёт особой пользы: пользователю придётся найти раскрывающийся контент (это возможно только тогда, когда он на самом деле развёрнут!), а потом ещё поискать элемент, который им управляет. Поэтому информация о состоянии сообщается через контрол, который используется для переключения видимости контента.</p>
<h2>Примечание. Это всё ARIA-кнопка?</h2>
<p>Да конечно. Нам не нужно добавлять <code>role=&quot;button&quot;</code>, так как элементу <code>&lt;button&gt;</code> неявно задана эта роль (ARIA-роль всего лишь имитирует роль по умолчанию). И, в отличие от <a href="https://inclusive-components.design/menus-menu-buttons/">кнопки меню</a> <em>(есть в <a href="https://medium.com/web-standards/menu-buttons-7f3aa1ad008d">переводе на русский</a>. — прим. переводчика),</em> нам не нужно сразу же менять контекст при перемещении фокуса. Следовательно, <code>aria-haspopup</code> в данном случае не подходит.</p>
<p>Некоторые добавляют атрибут <code>aria-controls</code> и указывают <code>id</code> для контейнера, в котором хранится контент. Имейте в виду, что атрибут <a href="https://web.archive.org/web/20200924080848/https://heydonworks.com/article/aria-controls-is-poop/"><code>aria-controls</code> работает только в JAWS</a> на момент написания этой статьи <em>(на момент перевода в октябре 2019 года <a href="https://a11ysupport.io/tech/aria/aria-controls_attribute">ситуация не изменилась</a>. — прим. переводчика).</em> Пока контент в разделе следует за заголовком или кнопкой в исходном порядке (source order), это не нужно. Пользователь будет сразу же сталкиваться с раскрытым контентом, когда перемещается дальше по странице.</p>
<h3>Стилизация кнопки</h3>
<p>Мы оказались в ситуации, когда используем кнопку, но при этом она должна выглядеть как улучшенная версия заголовка, внутри которого она находится. Наиболее эффективный способ это сделать — удалить все авторские и браузерные стили для кнопок и наследовать их от их родительского заголовка.</p>
<pre><code tabindex="0" class="language-css">h2 button {
    all: inherit;
}
</code></pre>
<p>Отлично, однако сейчас кнопка интуитивно не понятна (<a href="https://www.interaction-design.org/literature/book/the-glossary-of-human-computer-interaction/affordances">affordance</a>). Она не выглядит так, будто её можно активировать. Именно здесь обычно добавляется символ плюс или минус. Плюс означает, что раздел может быть развёрнут, а минус, что его можно свернуть.</p>
<img src="https://web-standards.ru/articles/collapsible-sections/images/collapsed-and-epxpanded-section.png" alt="Две сворачиваемые секции. Сверху кнопка в заголовке свернута, снизу - развернута.">
<p>Теперь возникает такой вопрос: как нам рендерить иконку? Ответ:
максимально эффективно и доступно. Простые формы, например, прямоугольники (<code>&lt;rect&gt;</code>) — это хороший способ создания иконок в SVG, поэтому давайте это сделаем.</p>
<pre><code tabindex="0" class="language-xml">&lt;svg viewBox=&quot;0 0 10 10&quot;&gt;
    &lt;rect height=&quot;8&quot; width=&quot;2&quot; y=&quot;1&quot; x=&quot;4&quot;/&gt;
    &lt;rect height=&quot;2&quot; width=&quot;8&quot; y=&quot;4&quot; x=&quot;1&quot;/&gt;
&lt;/svg&gt;
</code></pre>
<p>Кода так мало, что он может поместиться в твит. Поскольку программно состояние передаётся через атрибут <code>aria-expanded</code>, нам не нужно, чтобы эта графика была доступна для скринридера или была интерактивной. В этом случае нам нужно добавить ещё пару атрибутов.</p>
<pre><code tabindex="0" class="language-html">&lt;button aria-expanded=&quot;false&quot;&gt;
    Мой раздел
    &lt;svg viewBox=&quot;0 0 10 10&quot; aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;
        &lt;rect class=&quot;vert&quot; height=&quot;8&quot; width=&quot;2&quot; y=&quot;1&quot; x=&quot;4&quot;/&gt;
        &lt;rect height=&quot;2&quot; width=&quot;8&quot; y=&quot;4&quot; x=&quot;1&quot;/&gt;
    &lt;/svg&gt;
&lt;/button&gt;
</code></pre>
<ul>
<li><code>aria-hidden=&quot;true&quot;</code> скрывает SVG-иконку от скринридеров и других вспомогательных технологий.</li>
<li><code>focusable=&quot;false&quot;</code> решает проблему добавления фокуса по умолчанию для SVG-файлов в Internet Explorer и Edge.</li>
</ul>
<p>Обратите внимание на класс <code>vert</code> для прямоугольника, который представляет собой вертикальную конструкцию. Мы собираемся с помощью CSS показывать и скрывать его в зависимости от состояния, превращая иконку то в плюс, то в минус.</p>
<pre><code tabindex="0" class="language-css">[aria-expanded=&quot;true&quot;] .vert {
    display: none;
}
</code></pre>
<p>Связывание состояния и его визуального представления — <em>очень хорошая практика.</em> Это гарантия того, что сообщение об изменении состояния совместимо с разными системами. Не слушайте тех, кто выступает за абсолютное разделение HTML-семантики и CSS-стилей. Форма должна следовать за функцией, и такое решение напрямую более надёжно.</p>
<p>Обратите внимание, что стиль фокуса по умолчанию был сброшен при помощи <code>inherit: all</code>. Можно объявить стиль фокуса для SVG следующим образом:</p>
<pre><code tabindex="0" class="language-css">h2 button:focus svg {
    outline: 2px solid;
}
</code></pre>
<h3>Высококонтрастные темы</h3>
<p>Ещё одна вещь: мы можем обеспечить поддержку высококонтрастных тем для элементов <code>&lt;rect&gt;</code>. Если указать в <code>fill</code> значение <code>currentColor</code> для <code>&lt;rect&gt;</code>, то такие элементы изменяют цвет вместе с текстом, когда на него влияет изменение темы.</p>
<pre><code tabindex="0" class="language-css">[aria-expanded] rect {
    fill: currentColor;
}
</code></pre>
<p>Чтобы протестировать ваш дизайн в высококонтрастной теме в Windows найдите «Высокая контрастность» и выберете нужную тему в пункте «Выбор темы». Многие высококонтрастные темы инвертируют цвета для уменьшения яркости. Это помогает людям, страдающим мигренью или светобоязнью, а также людям с нарушениями зрения.</p>
<h2>Примечание. Почему бы не использовать <code>&lt;use&gt;</code>?</h2>
<p>Если у нас много сворачиваемых областей на странице,
то повторное использование одного и того же содержимого SVG-элемента <code>&lt;pattern&gt;</code> при помощи <a href="https://wearejh.com/inline-svg-use-element/">элементов <code>&lt;use&gt;</code></a> и <code>xlink:href</code> уменьшило бы их избыточность.</p>
<pre><code tabindex="0" class="language-html">&lt;button aria-expanded=&quot;false&quot;&gt;
    Мой раздел
    &lt;svg viewBox=&quot;0 0 10 10 aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;
        &lt;use xlink:href=&quot;#plusminus&quot;/&gt;
    &lt;/svg&gt;
&lt;/button&gt;
</code></pre>
<p>К сожалению, это означало бы, что мы больше не можем управлять конкретным прямоугольником <code>.vert</code> для его показа и скрытия. В нашем случае использование небольшого количества строк кода для определения идентичного SVG не такая большая проблема.</p>
<h2>Небольшой скрипт</h2>
<p>Учитывая простоту такого взаимодействия, а также все элементы с их семантикой, нам нужно только написать очень лаконичный скрипт:</p>
<pre><code tabindex="0" class="language-js">(function() {
    const headings = document.querySelectorAll('h2');

    Array.prototype.forEach.call(headings, heading =&gt; {
        let btn = heading.querySelector('button');
        let target = heading.nextElementSibling;

        btn.onclick = () =&gt; {
            let expanded = btn.getAttribute('aria-expanded') === 'true' || false;

            btn.setAttribute('aria-expanded', !expanded);
            target.hidden = expanded;
        }
    })
})()
</code></pre>
<p><a href="https://codepen.io/heydon/pen/QqzRvQ/">Вот демо на CodePen</a>:</p>
<iframe src="https://codepen.io/heydon/embed/QqzRvQ/"></iframe>
<h2>Прогрессивное улучшение</h2>
<p>Проблема со скриптом выше заключается в том, что для него требуется вручную адаптировать HTML для работы сворачивающихся секций. Ожидается, что это реализовано инженером как компонент через шаблон или JSX. Однако для статических сайтов, например, блогов, есть две неизбежные проблемы:</p>
<ul>
<li>Если JavaScript недоступен, то в DOM остаются интерактивные элементы, которые ничего не делают, поэтому в этом случае их семантика не имеет смысла.</li>
<li>Ответственность за создание и поддержку сложного HTML лежит на авторе или редакторе.</li>
</ul>
<p>Вместо этого мы можем взять базовый ввод текста, скажем, в виде разметки или WYSIWYG <em>(от англ. «What You See Is What You Get», проще говоря — визуальный редактор. — прим. переводчика)</em> и улучшить его <em>постфактум</em> с помощью скрипта. Это довольно просто сделать с помощью jQuery, учитывая методы <code>nextUntil</code> и <code>wrapAll</code>, однако в обычном JavaScript нам нужно сделать некоторую итерацию. Вот другое демо на CodePen, в котором автоматически добавляются переключатель и группа с контентом для переключения. Он нацелен на все <code>&lt;h2&gt;</code>, найденные на странице в <code>&lt;main&gt;</code>.</p>
<iframe src="https://codepen.io/heydon/embed/gGNaoM"></iframe>
<p>Почему это написано на JavaScript? Потому что современные браузеры одинаково поддерживают методы Web API и такой небольшой интерактив не должен зависеть от большой библиотеки.</p>
<h2>Прогрессивный веб-компонент</h2>
<p>Последний пример показывает, что нам не нужно думать о раскрывающихся секциях во время редактирования. Они появляются автоматически. Но то, что мы приобрели в плане удобства, мы потеряли в контроле. Что, если вместо этого, мы найдём компромисс между лаконичной разметкой и тем, какие разделы будут раскрывающимися и в каком состоянии они будут находиться при загрузке страницы после того, что мы <em>написали</em>?</p>
<p>Веб-компоненты могут стать ответом. Рассмотрим пример ниже:</p>
<pre><code tabindex="0" class="language-html">&lt;toggle-section open=&quot;false&quot;&gt;
    &lt;h2&gt;Мой раздел&lt;/h2&gt;
    &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras efficitur laoreet massa. Nam eu porta dolor. Vestibulum pulvinar lorem et nisl tempor lacinia.&lt;/p&gt;
    &lt;p&gt;Cras mi nisl, semper ut gravida sed, vulputate vel mauris. In dignissim aliquet fermentum. Donec arcu nunc, tempor sed nunc id, dapibus ornare dolor.&lt;/p&gt;
&lt;/toggle-section&gt;
</code></pre>
<p>Имя кастомного элемента проще запомнить, а последствия взаимодействия с ним очевидны благодаря атрибуту <code>open</code>. Ещё лучше то, что, когда JavaScript недоступен, этот внешний элемент обрабатывается как просто-напросто <code>&lt;div&gt;</code>, а сворачиваемая секция остается простым разделом. Это не принесёт никакого вреда.</p>
<p>Фактически, если мы проверяем поддержку элемента с <code>&lt;template&gt;</code> и <code>attachShadow</code> при помощи скрипта, то такой же фолбэк будет отображаться в браузерах, которые эти возможности не поддерживают.</p>
<pre><code tabindex="0" class="language-js">if ('content' in document.createElement('template')) {
    // Определяем &lt;template&gt; для веб-компонента

    if (document.head.attachShadow) {
        // Определяем веб-компонент, используя синтаксис версии v1
    }
}
</code></pre>
<h2>Шаблон</h2>
<p>Мы можем добавить элемент шаблона в разметку и ссылаться на него или создать его на лету. Это я собираюсь сделать позже.</p>
<pre><code tabindex="0" class="language-js">tmpl.innerHTML = `
    &lt;h2&gt;
        &lt;button aria-expanded=&quot;false&quot;&gt;
            &lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; viewBox=&quot;0 0 10 10&quot;&gt;
                &lt;rect class=&quot;vert&quot; height=&quot;8&quot; width=&quot;2&quot; y=&quot;1&quot; x=&quot;4&quot;/&gt;
                &lt;rect height=&quot;2&quot; width=&quot;8&quot; y=&quot;4&quot; x=&quot;1&quot;/&gt;
            &lt;/svg&gt;
        &lt;/button&gt;
    &lt;/h2&gt;
    &lt;div class=&quot;content&quot; hidden&gt;
        &lt;slot&gt;&lt;/slot&gt;
    &lt;/div&gt;
    &lt;style&gt;
        h2 {
            margin: 0;
        }

        h2 button {
            all: inherit;
            box-sizing: border-box;
            display: flex;
            justify-content: space-between;
            width: 100%;
            padding: 0.5em 0;
        }

        button svg {
            height: 1em;
            margin-left: 0.5em;
        }

        [aria-expanded=&quot;true&quot;] .vert {
            display: none;
        }

        [aria-expanded] rect {
            fill: currentColor;
        }
    &lt;/style&gt;
`;
</code></pre>
<p>Этот шаблон компонента станет поддеревом Shadow DOM.</p>
<p>Когда мы стилизуем сворачиваемую секцию <em>внутри</em> её собственной Shadow DOM, то такие стили не влияют на элементы из Light DOM (стандартной, внешней DOM). Кроме того, они не применяются, если браузер не поддерживает <code>&lt;template&gt;</code> и кастомные элементы.</p>
<h2>Определение компонента</h2>
<p>Обратите внимание на элемент <code>&lt;slot&gt;</code> в HTML-шаблоне. Он — это окно в нашей Light DOM. В него гораздо проще обернуть контент, который предоставил автор, в отличие от <a href="https://codepen.io/heydon/pen/gGNaoM">предыдущего демо с прогрессивным улучшением</a>.</p>
<p>Внутри компонента <code>this.innerHTML</code> есть ссылка на этот контент в Light DOM. Мы должны создать <code>shadowRoot</code> и заполнить его контентом шаблона. Разметка Shadow DOM вместо этого находится в <code>this.shadowRoot.innerHTML</code>.</p>
<pre><code tabindex="0" class="language-js">class ToggleSection extends HTMLElement {
    constructor() {
        super();

        this.attachShadow({ mode: 'open' });
        this.shadowRoot.appendChild(tmpl.content.cloneNode(true));
    }
}
</code></pre>
<img src="https://web-standards.ru/articles/collapsible-sections/images/component-structure.png" alt="Структура компонента. Шаблон компонента связан с Shadow DOM, статический контент - с Light DOM.">
<p>С помощью этих ссылок можно переместить Light DOM в Shadow DOM. Это означает, что мы можем повторно использовать лейбл <code>&lt;h2&gt;</code> в Light DOM и убрать ставший ненужным элемент. Такие манипуляции с DOM могут выглядеть дико, особенно если вы привыкли использовать простые, декларативные (React) компоненты. Однако это то, что делает веб-компоненты прогрессивными.</p>
<pre><code tabindex="0" class="language-js">this.btn = this.shadowRoot.querySelector('h2 button');
var oldHeading = this.querySelector('h2');
var label = oldHeading.textContent;
this.btn.innerHTML = label + this.btn.innerHTML;
oldHeading.parentNode.removeChild(oldHeading);
</code></pre>
<p>Вообще-то мы можем сделать лучше и поддерживать разные начальные уровни заголовков. Вместо того, чтобы управлять заголовками, можно просто получить первый элемент из Light DOM. Убедитесь, что он — заголовок. Это необходимо для его редактирования. Однако, если это не заголовок, мы можем использовать любой элемент. Я покажу как.</p>
<pre><code tabindex="0" class="language-js">var oldHeading = this.querySelector(':first-child');
</code></pre>
<p>Сейчас нужно просто убедиться в том, что <em>уровень</em> заголовка в Shadow DOM соответствует оригиналу из Light DOM. Я могу запросить <code>tagName</code> заголовка из Light DOM и в соответствии с этим повысить его уровень в Shadow DOM при помощи <code>aria-level</code>.</p>
<pre><code tabindex="0" class="language-js">let level = parseInt(oldHeading.tagName.substr(1));
this.heading = this.shadowRoot.querySelector('h2');
if (level &amp;&amp; level !== 2) {
    this.heading.setAttribute('aria-level', level);
}
</code></pre>
<p>Второй символ из <code>tagName</code> спарсился как целое. Если это натуральное число (<code>NaN</code> — ложное) и не <code>2</code>, которое неявно присутствует в <code>&lt;h2&gt;</code>, то применится атрибут <code>aria-level</code>. Элементы, которые не являются заголовками, в качестве фоллбека всё ещё отдают содержимое <code>textContent</code> как лейбл для хранящегося в Shadow DOM <code>&lt;h2&gt;</code>. Это может сопровождаться «вежливым» (polite) предупреждением <code>console.warn</code>, которое рекомендует разработчикам использовать заголовок в качестве предпочтительного элемента.</p>
<pre><code tabindex="0" class="language-js">if (!level) {
    console.warn('У первого элемента внутри каждого &lt;toggle-section&gt; должен быть заголовок подходящего уровня.');
}
</code></pre>
<img src="https://web-standards.ru/articles/collapsible-sections/images/component-warning-example.png" alt="Предупреждение в консоли, которое рекомендует использовать заголовок в качестве предпочтительного элемента.">
<p>Одно из преимуществ использования <code>aria-level</code> заключается в том, что, в нашем случае, атрибут не используется в качестве стилевого хука (styling hook), поэтому внешний вид заголовка или кнопки остаётся неизменным.</p>
<pre><code tabindex="0" class="language-html">&lt;h2 aria-level=&quot;3&quot;&gt;
    &lt;button aria-expanded=&quot;false&quot;&gt;
    &lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; viewBox=&quot;0 0 10 10&quot;&gt;
        &lt;rect class=&quot;vert&quot; height=&quot;8&quot; width=&quot;2&quot; y=&quot;1&quot; x=&quot;4&quot;/&gt;
        &lt;rect height=&quot;2&quot; width=&quot;8&quot; y=&quot;4&quot; x=&quot;1&quot;/&gt;
    &lt;/svg&gt;
    &lt;/button&gt;
&lt;/h2&gt;
</code></pre>
<p>Если вы хотите, чтобы заголовки вашей сворачиваемой секции внешне отражали их уровень, вы должны включить что-то вроде CSS-стилей ниже:</p>
<pre><code tabindex="0" class="language-css">toggle-section [aria-level=&quot;2&quot;] {
    font-size: 2rem;
}

toggle-section [aria-level=&quot;3&quot;] {
    font-size: 1.5rem;
}

/* И так далее… */
</code></pre>
<h3>Роль <code>region</code></h3>
<p>Любой контент, который представлен заголовком, является _де-факто _(под)разделом страницы. Но, как я уже говорил в <a href="https://inclusive-components.design/a-todo-list/#headinglevel">«Списке дел»</a> <em>(есть в <a href="https://medium.com/web-standards/a-todo-list-40a324436b3e">переводе на русский</a>. — прим. переводчика),</em> вы можете использовать явные секционные элементы-контейнеры <code>&lt;section&gt;</code>. Вы добьётесь того же эффекта, если примените атрибут <code>role=&quot;region&quot;</code> к элементу вроде нашего кастомного <code>&lt;toggle-section&gt;</code> (который, в противном случае, не будет обладать такой семантикой).</p>
<pre><code tabindex="0" class="language-html">&lt;toggle-section role=&quot;region&quot;&gt;
    …
&lt;/toggle-section&gt;
</code></pre>
<p>Пользователи скринридеров <a href="https://web.archive.org/web/20201108115147/https://heydonworks.com/article/responses-to-the-screen-reader-strategy-survey/">больше предпочитают перемещаться по документу по заголовкам, чем по областям</a>, однако у многих скринридеров <em>есть</em> функция шорткатов по областям страницы (region shortcuts). Добавление атрибута <code>role=&quot;region&quot;</code> даёт довольно много:</p>
<ul>
<li>Обеспечивает фоллбек в виде навигационной подсказки для пользователей скринридеров в ситуациях, когда Light DOM не содержит заголовка.</li>
<li>Объявляется как «область» (region), когда пользователь скринридера попадает в этот раздел и ведёт себя как структурная подсказка.</li>
<li>Предоставляет стилевой хук в виде <code>toggle-button[role=&quot;region&quot;]</code>. Это позволяет нам добавлять те стили, которые мы хотим увидеть, если скрипт запущен и веб-компоненты поддерживаются.</li>
</ul>
<h2>Привязка <code>open и aria-expanded</code></h2>
<p>Когда значение атрибута <code>open</code> меняется с <code>true</code> на <code>false</code> мы хотим, чтобы было также видно переключение контента. Используя <code>observedAttributes()</code> и <code>attributeChangedCallback()</code>, мы можем сделать это <em>напрямую.</em> Разместим этот код после конструктора компонента:</p>
<pre><code tabindex="0" class="language-js">static get observedAttributes() {
    return ['open'];
}

attributeChangedCallback(name) {
    if (name === 'open') {
        this.switchState();
    }
}
</code></pre>
<ul>
<li><code>observedAttributes()</code> получает массив всех атрибутов родительского <code>&lt;toggle-section&gt;</code>, которые мы хотим увидеть.</li>
<li><code>attributeChangedCallback(name)</code> позволяет нам выполнять нашу функцию <code>switchState()</code> в случае изменения значения <code>open</code>.</li>
</ul>
<p>Преимущество такого подхода в том, что мы можем переключать состояние, используя скрипт, который просто изменяет значение <code>open </code>за пределами компонента. Чтобы <em>пользователи</em> могли изменить состояние, мы можем просто перебросить <code>open</code> в функцию клика:</p>
<pre><code tabindex="0" class="language-js">this.btn.onclick = () =&gt; {
    this.setAttribute('open',
        this.getAttribute('open') === 'true' ? 'false' : 'true'
    );
}
</code></pre>
<p>Так как функция <code>switchState()</code> увеличивает значение <code>aria-expanded</code>, то мы привязали <code>open</code> к <code>aria-expanded</code>, убедившись в доступности изменения состояния.</p>
<pre><code tabindex="0" class="language-js">this.switchState = () =&gt; {
    let expanded = this.getAttribute('open') === 'true' || false;
    this.btn.setAttribute('aria-expanded', expanded);
    this.shadowRoot.querySelector('.content').hidden = !expanded;
}
</code></pre>
<p><a href="https://codepen.io/heydon/pen/ZXdbxj/">Вот демо с CodePen с этой версией веб-компонента</a> с комментариями:</p>
<iframe src="https://codepen.io/heydon/embed/ZXdbxj/"></iframe>
<h2>Свернуть или развернуть всё</h2>
<p>Поскольку мы переключаем элемент <code>&lt;toggle-section&gt;</code> через атрибут <code>open</code>, нужно элементарно дать пользователям возможность развернуть или свернуть всё сразу. Одним из преимуществ такой меры является то, что пользователи, которые открыли несколько разделов независимо друг от друга, могут «сбросить» исходное, свёрнутое состояние для лучшей видимости содержимого. К тому же пользователи, которые считают, что возня с интерактивными элементами отвлекает или утомляет, могут вернуться к прокрутке раскрытых секций.</p>
<p>Кажется заманчивым добавить «развернуть или свернуть всё» как два отдельных переключателя. Но мы не знаем, как много разделов будет изначально в другом состоянии. Мы также не знаем, сколько их пользователь открыл или закрыл вручную.</p>
<p>Поэтому нам нужно сгруппировать два альтернативных контрола.</p>
<pre><code tabindex="0" class="language-html">&lt;ul class=&quot;controls&quot; aria-label=&quot;Контролы секции&quot;&gt;
    &lt;li&gt;&lt;button id=&quot;expand&quot;&gt;развернуть всё&lt;/button&gt;&lt;/li&gt;
    &lt;li&gt;&lt;button id=&quot;collapse&quot;&gt;свернуть всё&lt;/button&gt;&lt;/li&gt;
&lt;/ul&gt;
</code></pre>
<p>Важно объединить в группу связанные контролы, и списки — это стандартная разметка для того, чтобы это сделать. Кроме того списки ссылок рассматривались в <a href="https://inclusive-components.design/menus-menu-buttons/">«Меню и кнопки меню»</a> <em>(всё ещё есть <a href="https://medium.com/web-standards/menu-buttons-7f3aa1ad008d">перевод на русский</a>. — прим. переводчика).</em> Списки и элементы списков подсказывают пользователям скринридеров о том, что они взаимодействуют со связанными элементами, и количество таких элементов.</p>
<p>У некоторых составных ARIA-виджетов есть свои собственные механизмы группировки, например, <code>role=&quot;menu&quot;</code> группирует элементы с <code>role=&quot;menuitem&quot;</code> или <code>role=&quot;tablist&quot;</code> группирует элементы с <code>role=&quot;tab&quot;</code>. Наш вариант использования не подходит ни для одной из этих парадигм, так что хватит простого списка с заголовком.</p>
<p>Так как в тег <code>&lt;ul&gt;</code> уже встроен «групповой» элемент (group element), то многие вспомогательные технологии знают, что групповая подпись добавляется при помощи <code>aria-label</code>. Когда пользователи VoiceOver попадут в список и сделают фокус на кнопке, то услышат: <em>«Развернуть всё, кнопка, список, контролы секции, два пункта».</em></p>
<p>Обратите внимание, что при использовании <code>aria-label</code> добавляется только невидимый лейбл. Это допустимо, если назначение кнопок можно определить визуально с помощью других подсказок, по той же раскладке. В этом случае, вероятно, будет достаточно расположить кнопки рядом с разделами, но нужно это протестировать.</p>
<img src="https://web-standards.ru/articles/collapsible-sections/images/expand-and-collapse-all.png" alt="Над двумя сворачиваемыми секциями находятся контролы: свернуть все, развернуть все.">
<h2>Отслеживание URL</h2>
<p>Одно последнее улучшение.</p>
<p>Обычно, если нет улучшения с помощью JavaScript, пользователи могут переходить по ссылкам на определенные разделы страниц и делиться ими со своими <code>hash</code>. Такое поведение ожидаемо и является частью привычного UX-паттерна в вебе.</p>
<p>Большинство парсеров с этой целью добавляет атрибуты <code>id</code> для заголовков. Поскольку заголовок для целевого раздела в нашем расширенном интерфейсе может находиться внутри свёрнутого раздела или раздела, на котором не сделан фокус, нам нужно его открыть, чтобы показать контент и перенести фокус на него. Жизненный цикл <code>connectedCallback()</code> даёт нам это сделать, когда компонент готов. Это похоже на <code>DOMContentLoaded</code>, но только для веб-компонентов.</p>
<pre><code tabindex="0" class="language-js">connectedCallback() {
    if (window.location.hash.substr(1) === this.heading.id) {
        this.setAttribute('open', 'true');
        this.btn.focus();
    }
}
</code></pre>
<p>Обратите внимание на то, что мы делаем фокус на кнопке внутри заголовка компонента. Это перемещает пользователей клавиатуры к соответствующему компоненту, готовому к взаимодействию. Скринридерами будет объявлен уровень родительского заголовка вместе с названием кнопки.</p>
<p>В дополнение к этому нам нужно обновлять <code>hash</code> каждый раз, когда пользователи открывают последующий раздел. Тогда они могут поделиться определённым URL без необходимости копаться в инструментах разработчика (если они знают как!), чтобы скопировать и вставить <code>id</code> заголовка. Давайте используем <code>pushState</code> для динамичного изменения URL без перезагрузки страницы:</p>
<pre><code tabindex="0" class="language-js">this.btn.onclick = () =&gt; {
    let open = this.getAttribute('open') === 'true' || false;
    this.setAttribute('open', open ? 'false' : 'true');

    if (this.heading.id &amp;&amp; !open) {
        history.pushState(null, null, '#' + this.heading.id);
    }
}
</code></pre>
<p><a href="https://codepen.io/heydon/pen/ZXgqKG">А вот демо с добавленной возможностью отслеживания <code>hash</code></a>:</p>
<iframe src="https://codepen.io/heydon/embed/ZXgqKG"></iframe>
<h2>Заключение</h2>
<p>Ваша роль как дизайнера и разработчика (да, вы можете быть и тем, и другим одновременно) заключается в том, чтобы удовлетворять потребности людей, которые получают ваш контент и используют предложенный вами функционал. Эти потребности охватывают как потребности «конечных пользователей», так и других контрибьютеров. Продукт должен быть, конечно же, доступным и обладать хорошей производительностью, но поддержка и расширение его возможностей не должны быть завязаны на эзотерических технических знаниях.</p>
<p>Вне зависимости от того, используете ли вы веб-компоненты или нет, прогрессивное улучшение не только гарантирует, что интерфейс хорошо структурирован и надёжен. Как мы здесь увидели, это также может упростить процесс редактирования контента. Это делает разработку приложения и его контент более инклюзивным.</p>
<h2>Чеклист</h2>
<ul>
<li>Не зависьте от больших библиотек в случае небольших взаимодействий, если только рассматриваемая библиотека не будет использоваться для других улучшений взаимодействия.</li>
<li>Не переопределяйте важные роли элементов. Прочитайте <a href="https://www.w3.org/TR/using-aria/#second">второе правило использования ARIA</a>.</li>
<li>Поддерживайте высококонтрастные темы в ваших SVG-иконках при помощи <code>currentColor</code>.</li>
<li>Если контент уже статичный, то это хорошая возможность для прогрессивного улучшения вашего веб-компонента.</li>
<li>Пожалуйста, используйте информативные лейблы в секциях, а не «Раздел 1», «Раздел 2» и так далее. В таком случае это будут просто плейсхолдеры!</li>
</ul>

                    ]]></description><pubDate>Thu, 17 Oct 2019 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/collapsible-sections/</guid></item><item><title>Извлекаем критический CSS</title><link>https://web-standards.ru/articles/critical-css/</link><description><![CDATA[
                        <p>Браузер должен скачать и распарсить CSS-файл перед тем, как отрисует страницу. Это делает CSS ресурсом, блокирующим рендеринг. Если файл большой или соединение медленное, то запрос стилей может значительно увеличить время отрисовки страницы.</p>
<p><strong>Ключевой термин</strong>: критический CSS — это метод, который извлекает CSS для контента «над сгибом» <em>(above the fold — термин из газетного дизайна — прим. редактора),</em> чтобы максимально быстро показать его пользователю.</p>
<figure>
    <img src="https://web-standards.ru/articles/critical-css/images/above-the-fold.png" alt="Над сгибом и под сгибом.">
    <figcaption>
        Над сгибом и под сгибом.
    </figcaption>
</figure>
<p>Над сгибом — это то, что пользователь видит в первую очередь при загрузке страницы, до прокрутки. Не существует универсального способа определить, где заканчивается экран пользователя и что находится в первом экране, поскольку существует огромное количество устройств с разными размерами экрана.</p>
<p>Критический CSS можно заинлайнить внутри <code>&lt;head&gt;</code> прямо в HTML-документе. Это позволит избежать дополнительного запроса к серверу для получения этих стилей. Остальной CSS может быть загружен асинхронно.</p>
<img src="https://web-standards.ru/articles/critical-css/images/inline-critical-css.png" alt="">
<p>Увеличение времени рендеринга может существенно повлиять на <a href="https://developers.google.com/web/fundamentals/performance/rail#ux">воспринимаемую производительность</a>, особенно при плохом соединении. В мобильных сетях высокая задержка является проблемой независимо от пропускной способности.</p>
<img src="https://web-standards.ru/articles/critical-css/images/mobile-bandwith-delay.png" alt="">
<p>Если у вас плохой <a href="https://web.dev/first-contentful-paint/">First Contentful Paint</a> (FCP) и вы видите рекомендацию «Устранить ресурсы, блокирующие рендеринг» в отчёте Lighthouse, то стоит попробовать методику критического CSS.</p>
<img src="https://web-standards.ru/articles/critical-css/images/render-blocking-example.png" alt="">
<h3><strong>Подводные камни!</strong></h3>
<p><em>Помните, что если вы заинлайнили большой кусок CSS, то это повлияет на размер и, как следствие, время получения HTML-файла. Если важно всё, то не важно ничего. У заинлайненого CSS есть несколько недостатков: он не кэшируется браузером для повторного использования при последующих загрузках страницы, поэтому лучше использовать его осторожно.</em></p>
<p>Чтобы минимизировать запросы при первом рендеринге, старайтесь чтобы размер контента над сгибом не превышал 14 Кб (после сжатия).</p>
<p>Новые <a href="https://hpbn.co/building-blocks-of-tcp/">TCP-соединения</a> не могут сразу же использовать весь доступный поток передачи данных между клиентом и сервером. Соединение происходит с <a href="https://hpbn.co/building-blocks-of-tcp/#slow-start">медленным стартом</a>, что позволяет избежать перегрузки соединения большим количеством данных, которые оно может принять. При таком подходе сервер начинает передачу с небольшой порции данных и, если соединение с клиентом идеальное, то удваивает эту порцию при следующей отправке. Большинство серверов может передать максимум 10 пакетов или 14 Кб при первой передаче.</p>
<p>Положительный эффект на перформанс, которого вы можете добиться этим методом, зависит от типа вашего сайта. Проще говоря, чем больше CSS используется на сайте, тем заметнее будет влияние метода критического CSS.</p>
<h2>Обзор инструментов</h2>
<p>Существует несколько отличных инструментов, которые позволяют автоматически определять критический CSS для страницы. Это хорошие новости, поскольку делать это вручную довольно муторно. Требуется анализ всего DOM, чтобы определить стили, применяемые к каждому элементу в области просмотра.</p>
<h2>Critical</h2>
<p><a href="https://github.com/addyosmani/critical">Critical</a> извлекает, минифицирует и инлайнит критический CSS. Доступен в виде <a href="https://www.npmjs.com/package/critical">npm-модуля</a>. Может быть использован в Gulp (прямо внутри) или Grunt (в виде <a href="https://github.com/bezoerb/grunt-critical">плагина</a>). Есть и <a href="https://github.com/anthonygore/html-critical-webpack-plugin">плагин для Webpack</a>.</p>
<p>Это простой инструмент, который решает вашу головную боль. Для него даже не нужно указывать пути до файлов стилей, Critical автоматически определит их. Он также поддерживает извлечение критического CSS для разных разрешений экрана.</p>
<h2>criticalCSS</h2>
<p><a href="https://github.com/filamentgroup/criticalCSS">CriticalCSS</a> это ещё один <a href="https://www.npmjs.com/package/criticalcss">npm-модуль</a>, который извлекает критический CSS. Он так же доступен в виде CLI.</p>
<p>У него нет опций инлайна или минификации критического CSS, но он позволяет вам дописать правила, которые не относятся к разряду критических и дает вам более тонкий контроль над декларацией <code>@font-face</code>.</p>
<h2>Penthouse</h2>
<p><a href="https://github.com/pocketjoso/penthouse">Penthouse</a> — хороший выбор, если на вашем сайте или в приложении используется большое количество стилей или если стили динамически внедряются в DOM (как это бывает, например, в приложениях Angular). В этом инструменте под капотом скрывается <a href="https://github.com/GoogleChrome/puppeteer">Puppeteer</a> и у него даже есть <a href="https://jonassebastianohlsson.com/criticalpathcssgenerator/">онлайн-версия</a>.</p>
<p>Penthouse не умеет автоматически определять расположение файлов стилей, вы должны руками указать пути до HTML и CSS-файлов, для которых нужно сгенерировать критический CSS. Плюс этого инструмента в том, что он отлично справляется с многопоточностью.</p>

                    ]]></description><pubDate>Tue, 08 Oct 2019 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/critical-css/</guid></item><item><title>Инклюзивные компоненты: интерфейсы со вкладками</title><link>https://web-standards.ru/articles/tabbed-interfaces/</link><description><![CDATA[
                        <p>Если вы задумаетесь о том, что же именно происходит с интерфейсами, когда вы взаимодействуете с ними, то чаще всего что-то показывается или скрывается. Я уже рассказывал о <a href="https://inclusive-components.design/menus-menu-buttons/">кнопках меню</a> <em>(есть в <a href="https://web-standards.ru/articles/tabbed-interfaces/articles/menu-buttons">переводе на русский</a>. — прим. переводчика),</em> при клике по которым появляется выпадающее меню, и их родственниках — более простых и менее очевидных <a href="https://inclusive-components.design/tooltips-toggletips/">тултипах и тоглтипах</a> <em>(тоже есть в <a href="https://web-standards.ru/articles/tabbed-interfaces/articles/tooltips-toggletips">переводе</a>. — прим. переводчика).</em> Вы можете добавить к этому списку простые виджеты для раскрытия блоков, составные «аккордеоны» <strong>и интерфейсы со вкладками</strong> <em>(от англ. «tabbed interfaces», дальше я буду называть их просто «вкладками». — прим. переводчика).</em> Стоит также отметить, что одностраничные приложения (single-page applications, SPA) эмулируют отображение и скрытие целых страниц при помощи JavaScript.</p>
<p>Как мы уже знаем, разница между панелями вкладок, представлениями приложений и простым фрагментом документа не так очевидна, как может показаться. Тем не менее, нам нужно быть уверенными в том, какой именно интерфейс мы предоставляем пользователям. Иначе им будет сложно понять, как им правильно пользоваться.</p>
<p>Сторонники прогрессивного улучшения под термином интерфейс в первую очередь понимают структурированный статический контент, а уже потом то, как он может быть расширен и сделан интерактивным с помощью JavaScript. Даже если вы хотите использовать JavaScript на ранних этапах проектирования интерфейса, полезно создать надёжную основу с помощью семантического HTML, опирающегося на стандартное поведение браузера. Иногда вы даже можете обнаружить, что JavaScript — это шаг, который вообще не нужен.</p>
<p>Ставлю деньги на то, что изначально вкладки — это просто <a href="https://inclusive-components.design/menus-menu-buttons/#tablesofcontent">оглавление с якорными ссылками</a>, которые ведут к разным разделам страницы. Список вкладок, как и оглавление, позволяет пользователю выбирать между различными разделами контента.</p>
<ul>
<li>Оглавление → список вкладок.</li>
<li>Якорные ссылки → вкладки.</li>
<li>Разделы → панели вкладок.</li>
</ul>
<img src="https://web-standards.ru/articles/tabbed-interfaces/images/anchor-and-tab-comparison.png" alt="">
<h2>Улучшение с помощью CSS</h2>
<p>Что, если я использую немного CSS, чтобы визуализировать выбор раздела из моего оглавления? Конечно, это можно сделать при помощи псевдокласса <code>:target</code>.</p>
<pre><code tabindex="0" class="language-css">section:not(:target) {
    display: none;
}
</code></pre>
<iframe src="https://codepen.io/heydon/embed/VMMrgN"></iframe>
<p>Давая пользователям возможность раскрывать контент, мы превращаем наше оглавление во вкладки с помощью CSS. Поскольку <code>display: none</code> скрывает контент от вспомогательных технологий, то это улучшение затрагивает пользователей скринридеров так же, как и всех остальных.</p>
<p>Возьмите ссылки из оглавления и выровняйте их по горизонтали: тогда этот небольшой CSS-эксперимент действительно приведёт к тому, что оглавление станет похожим на вкладки. Но в этом и заключается проблема.</p>
<p>Во <a href="https://resilientwebdesign.com/chapter2/">второй главе своей книги «Resilient Web Design»</a> Джереми Кит рассказывает о <em>материальной честности</em> (material honesty) следующее: «Один материал не должен использоваться в качестве замены другому». В этом случае мы делаем оглавление всего лишь <em>похожим</em> на список вкладок. Следует этого избегать. Пользователи, которые видят вкладки, ожидают определённого поведения, которого нет у простого списка ссылок.</p>
<p>По этой же причине элементы, по дизайну похожие на вкладки, не должны использоваться для навигации по всему сайту. По крайней мере, когда пользователь выбирает «вкладку», он не ожидает перехода на новую страницу!</p>
<p>Я встречал огромное количество полноценных вкладок, написанных на JavaScript и обвешанных атрибутами ARIA, для которых простые оглавления разделов страницы хорошо работали. Так даже лучше, ведь они более надёжны и эффективны. Но ради всего святого, пусть они <em>выглядят</em> как оглавления. Пусть семантика и поведение соответствуют тем ожиданиям, которые продиктованы визуальным дизайном интерфейса.</p>
<h2>Настоящие вкладки</h2>
<p>Преимущество использования списков якорных ссылок и стандартного поведения браузера, на котором они основаны, заключается в том, что в их случае всё просто и понятно. Их поведение специфично для веба.</p>
<p>Вкладки, с другой стороны, парадигма, которая пришла из десктопных приложений. Если она <em>вообще</em> и понятна пользователям в контексте веб-страниц, то только благодаря аккуратному, хорошо проработанному визуальному дизайну и семантике ARIA.</p>
<p>Что делает вкладки вкладками, так это удобство взаимодействия с ними с клавиатуры. На самом деле единственная причина, по которой в этом случае нужна семантика ARIA — это для предупреждения пользователей скринридеров об ожидаемом поведении клавиатуры. Ниже базовая семантическая структура и примечания к ней:</p>
<pre><code tabindex="0" class="language-html">&lt;ul role=&quot;tablist&quot;&gt;
    &lt;li role=&quot;presentation&quot;&gt;
        &lt;a role=&quot;tab&quot; href=&quot;#section1&quot; id=&quot;tab1&quot; aria-selected=&quot;true&quot;&gt;
            Раздел 1
        &lt;/a&gt;
    &lt;/li&gt;
    &lt;li role=&quot;presentation&quot;&gt;
        &lt;a role=&quot;tab&quot; href=&quot;#section2&quot; id=&quot;tab2&quot;&gt;
            Раздел 2
        &lt;/a&gt;
    &lt;/li&gt;
    &lt;li role=&quot;presentation&quot;&gt;
        &lt;a role=&quot;tab&quot; href=&quot;#section3&quot; id=&quot;tab3&quot;&gt;
            Раздел 3
        &lt;/a&gt;
    &lt;/li&gt;
&lt;/ul&gt;
&lt;section role=&quot;tabpanel&quot; id=&quot;section1&quot; aria-labelledby=&quot;tab1&quot;&gt;
    …
&lt;/section&gt;
&lt;section role=&quot;tabpanel&quot; id=&quot;section2&quot; aria-labelledby=&quot;tab2&quot; hidden&gt;
    …
&lt;/section&gt;
&lt;section role=&quot;tabpanel&quot; id=&quot;section3&quot; aria-labelledby=&quot;tab3&quot; hidden&gt;
    …
&lt;/section&gt;
</code></pre>
<ul>
<li>Эти вкладки прогрессивно улучшаются за счёт оглавления и соответствующих разделов страницы. В некоторых случаях требуется добавление (<code>aria-selected</code>) или переопределение (<code>role=&quot;tab&quot;</code>) семантики. В других (<code>role=&quot;presentation&quot;</code>) — удаление семантики, которую больше нельзя применять или от которой нет никакой пользы. Вам не нужно, чтобы вкладки объявлялись так же, как простой список.</li>
<li><code>role=&quot;tablist&quot;</code> не распространяется на дочерние элементы. Эту роль нужно сочетать вместе с отдельными ролями <code>tab</code>, чтобы их могли обнаружить и перечислить вспомогательные технологии.</li>
<li>Элементы <code>tabpanel</code>, которые не связаны с выбранной вкладкой, скрываются с помощью атрибута <code>hidden</code>.</li>
<li>Пользователи, которые выбирают вкладку, должны быть уверены в её назначении. Следовательно, <code>aria-labelledby</code> используется для добавления подписи к панели вкладок при помощи названия вкладки. На практике это приводит к тому, что пользователь скринридера, попавший в панель вкладок и сделавший фокус на ссылке, услышит что-то вроде: <em>«Панель вкладок, раздел 1, [текст ссылки], ссылка».</em></li>
</ul>
<h3>Поведение клавиатуры</h3>
<p>В отличие от якорных ссылок, вкладки не перемещают пользователя к связанным с ними разделам и панелям. Они просто показывают скрытый контент. Это удобно для зрячих пользователей (включая тех из них, кто пользуется скринридерами). Такие пользователи хотят переключаться между различными разделами и не возвращаться в начало страницы каждый раз, когда хотят выбрать новый.</p>
<p>Тут есть и побочный эффект: если пользователь хочет перейти к разделу с помощью клавиатуры и взаимодействовать с его внутренним содержимым, то он вынужден пройти через все вкладки справа от текущей, которые расположены в порядке фокуса.</p>
<img src="https://web-standards.ru/articles/tabbed-interfaces/images/tab-navigation-side-effect.png" alt="">
<p>Эту проблему можно решить, если реализовать переключение между вкладками с помощью клавиш со стрелками. Пользователь может выбрать и активировать вкладки стрелками, в то время как клавиша <kbd>Tab</kbd> используется для того, чтобы установить фокус на контенте внутри панели вкладок или под активной. Другими словами, клавиша <kbd>Tab</kbd> нужна не для вкладок, что, признаю, немного сбивает с толку. Хотелось бы, чтобы у клавиши и контрола были разные имена, но увы.</p>
<img src="https://web-standards.ru/articles/tabbed-interfaces/images/arrow-key-navigation.png" alt="">
<p>Не менее важно то, что пользователя возвращает к выбранной вкладке после нажатия <kbd>Shift Tab</kbd>. Всё это можно сделать, если добавить для каждой вкладки атрибут <code>tabindex=&quot;-1&quot;</code>, кроме выбранной в данный момент. Это убирает интерактивные вкладки из порядка фокуса, но даёт возможность установить его с помощью скрипта. В примере ниже выбрана вторая вкладка, которой задано состояние <code>aria-selected</code> со значением <code>true</code>.</p>
<pre><code tabindex="0" class="language-html">&lt;ul role=&quot;tablist&quot;&gt;
    &lt;li role=&quot;presentation&quot;&gt;
        &lt;a role=&quot;tab&quot; tabindex=&quot;-1&quot; href=&quot;#section1&quot;&gt;
            Раздел 1
        &lt;/a&gt;
    &lt;/li&gt;
    &lt;li role=&quot;presentation&quot;&gt;
        &lt;a role=&quot;tab&quot; href=&quot;#section2&quot; aria-selected=&quot;true&quot;&gt;
            Раздел 2
        &lt;/a&gt;
    &lt;/li&gt;
    &lt;li role=&quot;presentation&quot;&gt;
        &lt;a role=&quot;tab&quot; tabindex=&quot;-1&quot; href=&quot;#section2&quot;&gt;
            Раздел 3
        &lt;/a&gt;
    &lt;/li&gt;
&lt;/ul&gt;
</code></pre>
<p>При помощи атрибута <code>tabindex=&quot;-1&quot;</code> я могу для оставшихся вкладок отслеживать событие <code>keydown</code> для клавиш с левой и правой стрелками, чтобы сделать активной нужную неактивную вкладку.</p>
<pre><code tabindex="0" class="language-js">tab.addEventListener('keydown', e =&gt; {
    let dir = e.which === 37 ? 'left' : 39 ? 'right' : null;
    if (dir) {
        switchTab(e.eventTarget, dir);
    }
})
</code></pre>
<p>Каждый раз, когда пользователь выбирает новую вкладку, появляется связанная с ней панель вкладок. Когда первая из четырёх вкладок выбрана, то любой скринридер объявит примерно следующее: <em>«[Название вкладки] выбрана, вкладка, 2 из 4».</em> Исчерпывающая информация.</p>
<h3>Проблема чтения панелей вкладок</h3>
<p>Теперь, когда при нажатии на клавишу <kbd>Tab</kbd> вкладки пропускаются, пользователям клавиатуры легко переместить фокус на первую ссылку или другие интерактивные элементы в открытой панели.</p>
<p>Для пользователей скринридеров такое решение не настолько оптимально. Хотя незрячие пользователи тоже могут сделать фокус на элементах внутри панели выбранной вкладки, они не смогут увидеть контент, который появляется до или после этого взаимодействия. Если в панели вкладок вообще нет интерактивного контента, то они случайно сделают фокус на первом интерактивном элементе за пределами вкладок или под ними.</p>
<p>При работе со скринридерами, такими как NVDA и JAWS, <a href="https://webaim.org/resources/shortcuts/nvda#reading">стрелка вниз перемещает пользователя к следующему элементу</a> (тому, на котором можно сделать фокус или к какому-то другому), после чего они объявят его. Если не изменять поведение, то это будет следующая вкладка в <code>tablist</code>. Вместо этого можно перехватить нажатие клавиши со стрелкой вниз и программно переместить фокус на открытую панель вкладок, убедившись, что она не пропущена. Посмотрите на <code>panels[i].focus()</code> в следующем фрагменте кода:</p>
<pre><code tabindex="0" class="language-js">tab.addEventListener('keydown', e =&gt; {
    let index = Array.prototype.indexOf.call(tabs, e.currentTarget);
    let dir =
        e.which === 37 ? index - 1 :
        e.which === 39 ? index + 1 :
        e.which === 40 ? 'down' : null;
    if (dir !== null) {
        e.preventDefault();
        dir === 'down' ? panels[i].focus() :
        tabs[dir] ? switchTab(e.currentTarget, tabs[dir]) : void 0;
    }
});
</code></pre>
<p>Часть с <code>void 0</code> означает «не делай ничего» в случае, если соседней вкладки не существует (так как вы находитесь в начале или в конце <code>tablist</code>). Можете посмотреть полное <a href="https://codepen.io/heydon/pen/veeaEa/">демо на CodePen</a>.</p>
<p>После того, как панелям со вкладками задан лейбл при помощи связанных с ними вкладок, то, когда сделан фокус на нужной вкладке и нажата клавиша со стрелкой вниз, скринридер объявит: <em>«[Название вкладки], панель вкладок».</em> Таким образом пользователи точно узнают о своём новом местоположении в интерфейсе. Оттуда они могут продолжить просматривать дочерние элементы панели вкладок или нажать сочетание <kbd>Shift Tab</kbd>, чтобы вернуться к <code>tablist</code> и выбранной вкладке.</p>
<p>Хотя зрячие пользователи клавиатуры с меньшей вероятностью будут использовать клавишу со стрелкой вниз, важно, чтобы у выбранной панели вкладок был стиль для фокуса, который указывает на изменение его положения. Всё это не помешает работе зрячих пользователей клавиатуры, которые могут делать всё, что нужно, со вкладками с помощью клавиши <kbd>Tab</kbd> и левой и правой стрелок.</p>
<h2>Фокус на неинтерактивных элементах</h2>
<p>В этой реализации мы, технически, даём пользователям возможность сделать на неинтерактивных элементах фокус при помощи нестандартных клавиш.</p>
<p>Главное правило гласит, что пользователь не должен иметь возможность сделать фокус на неинтерактивных элементах, так как тогда он будет ожидать, что каждый из них <em>что-то делает.</em> Следовательно, код вроде этого не соответствует критерию WCAG <a href="https://www.w3.org/TR/UNDERSTANDING-WCAG20/navigation-mechanisms-focus-order.html">2.4.3 Порядок фокуса</a>. В нём есть непригодный для использования элемент.</p>
<pre><code tabindex="0" class="language-html">&lt;h2 tabindex=&quot;0&quot;&gt;Раздел 3&lt;/h2&gt;
</code></pre>
<p>Однако установление фокуса на элементе с помощью скрипта допустимо, когда пользователь <em>выбрал</em> это изменение контекста. В некоторых реализациях одностраничных приложений, если пользователь выбирает новое «представление» (view), то фокус устанавливается на вновь созданном элементе представления или на его основном заголовке.</p>
<pre><code tabindex="0" class="language-html">&lt;h2 tabindex=&quot;-1&quot;&gt;Представление приложения&lt;/h2&gt;
</code></pre>
<p>При фокусе на заголовке будет объявлено его содержимое, которое дублируется как лейбл представления. Это помогает пользователям скринридеров узнать об изменениях контекста. Обратите внимание на то, как использован атрибут <code>tabindex=&quot;-1&quot;</code>. Как и в случае со вкладками, которыми мы управляем при помощи стрелок, это даёт возможность настроить фокус с помощью скрипта без участия пользователя (если не назначена кастомная клавиша). На практике это позволяет нам перемещать фокус без добавления элемента, на котором сделан фокус, в пользовательский порядок табуляции, как в случае со свойством <code>tabindex=&quot;0&quot;</code>.</p>
<p>Вот <a href="https://codepen.io/heydon/pen/veeaEa/">демо со вкладками на CodePen</a>, которое написано на JavaScript. Также там есть пояснения о поведении элементов и их семантике. Это список ссылок и элементов <code>&lt;section&gt;</code>, которые были прогрессивно улучшены и минифицированы до 1,3 Кб:</p>
<iframe src="https://codepen.io/heydon/embed/veeaEa/"></iframe>
<h2>Адаптивный дизайн</h2>
<p>Адаптивный дизайн — это инклюзивный дизайн. Он не только совместим с максимальным числом устройств, но также чувствителен к пользовательским настройкам увеличения страницы. За масштабирование всей страницы отвечает <code>@media</code> точно так же, как и за уменьшение области просмотра — вьюпорта.</p>
<p>Для вкладок нужен брейкпоинт, на котором недостаточно места для размещения всех вкладок по горизонтали. Самый быстрый способ исправить это — перестроить контент в один столбец.</p>
<img src="https://web-standards.ru/articles/tabbed-interfaces/images/tabs-to-column.png" alt="">
<p>С визуальной точки зрения это нельзя больше считать вкладками, так как они больше не выглядят как <em>вкладки.</em> Это не обязательно проблема, если выбранная вкладка (или опция) чётко обозначена. Не визуально, с точки зрения скринридеров, такой элемент выглядит и ведёт себя точно так же.</p>
<h2>Аккордеоны для небольших вьюпортов?</h2>
<p>Некоторые пытаются превратить вкладки в аккордеоны на небольших вьюпортах. Учитывая, что последние структурированы, реализованы и управляются совершенно иначе, чем вкладки, я бы не рекомендовал так делать.</p>
<p>Аккордеоны имеют свои преимущества: они объединяют заголовок и кнопку с их контентом, что, возможно, лучше подходит для одной колонки. Но адаптивный гибрид вкладок и аккордеона просто не стоит того с точки зрения производительности.</p>
<p>Там, где много вкладок или их количество заранее не известно, аккордеон безопаснее всего для <em>всех</em> размеров экранов. Одноколоночная раскладка адаптивна вне зависимости от количества контента. Проще простого.</p>
<img src="https://web-standards.ru/articles/tabbed-interfaces/images/accordeon.png" alt="">
<h2>Когда панели вкладок — представления</h2>
<p>Ранее я замечал, что группа ссылок для навигации по сайту в виде вкладок может ввести в заблуждение: пользователь ожидает от клавиатуры поведения как со вкладками, а также того, что фокус будет установлен на вкладке на текущей странице. Ссылка, ведущая на другую страницу, приведёт к загрузке этой страницы, а фокус переместится на сам документ (<code>&lt;body&gt;</code>).</p>
<p>Что касается «представлений» в одностраничных приложениях, то разные ли пути у разных экранов? Технически, они ближе к панелям вкладок, которые при этом являются полностью страницами. Но это не говорит о том, что их стоит <em>связывать</em> с панелями вкладок. Это не то, что может ожидать пользователь.</p>
<p>Представления одностраничных приложений, как правило, должны выглядеть как отдельные страницы или области на них, так что об этом стоит рассказать подробнее. Вот некоторые условия, которые нужно выполнять:</p>
<h3>1. Используйте ссылки!</h3>
<p>Убедитесь, что ссылки, которые дают пользователям возможность выбирать между представлениями, действительно являются ссылками. Даже если эти ссылки <code>return false</code> и вы используете JavaScript для переключения представлений. Поскольку благодаря этим контролам пользователи будут ориентироваться в навигации (путём изменения местоположения фокуса, см. ниже), роль ссылки больше всего подходит для такого случая. Ссылкам не нужна ARIA-роль <code>link</code>. Они объявляются скринридерами как «ссылка» по умолчанию.</p>
<p>В <a href="https://heydon.github.io/xiao/#home">Xiao</a> <em>(читается как «сяо». — прим. переводчика),</em> прогрессивно улучшенном роутере для одностраничных приложений, для обозначения представлений используются стандартные хеш-фрагменты. Ссылки на них будут объявлены как <em>«якорные ссылки»</em> большинством вспомогательных технологий. В их основе лежит стандартное поведение браузера, так что пользователь будет знать о том, что он попадёт на новую, отдельную часть страницы или приложения.</p>
<pre><code tabindex="0" class="language-html">&lt;a href=&quot;#some-route&quot;&gt;Какой-то путь&lt;/a&gt;
</code></pre>
<h3>2. Управляйте фокусом</h3>
<p>Простая замена какого-то контента на странице не приводит к тому, что пользователя автоматически переносит к этому контенту или (в случае слепых пользователей вспомогательных технологий), что его предупреждают о существовании такого контента. Как сказано выше в примечании «Фокус на неинтерактивных элементах», можно сделать фокус на основном заголовке нового представления или на внешнем элементе. Если вы делаете фокус на последнем, то рекомендуется задать ему подпись либо непосредственно при помощи <code>aria-label</code>, либо по принципу заголовка с использованием <code>aria-labelledby</code>.</p>
<pre><code tabindex="0" class="language-html">&lt;div aria-label=&quot;На главную&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;
    …
&lt;/div&gt;
</code></pre>
<p>Когда вы используете этот атрибут вместе с ролью <code>region</code> (как во фрагменте кода выше), то, если на элементе сделан фокус, скринридеры сделают объявление с контекстной информацией: <em>«На главную, область».</em></p>
<p>Если использовать Xiao, то фокус не будет сделан ни на одной области главной страницы при её загрузке. Это означает, что документ или <code>&lt;body&gt;</code> получают фокус по умолчанию и <code>&lt;title&gt;</code> объявляется скринридерами (см. ниже).</p>
<h3>3. Обновляйте <code>&lt;title&gt;</code></h3>
<p>Название приложения должно быть для конкретного представления добавлено к лейблу. Это соответствует рекомендованному паттерну для статических сайтов, где к названию сайта добавляется имя страницы.</p>
<pre><code tabindex="0" class="language-html">&lt;title&gt;[Название приложения] | [Название представления]&lt;/title&gt;
</code></pre>
<p>Вы можете подгрузить приложение с роутером Xiao для любого пути, просто добавив хеш-фрагмент пути в URL. При событии загрузки <code>&lt;title&gt;;</code> принимает значение лейбла этого пути, а скринридеры идентифицируют приложение и конкретный роутер.</p>
<h2>Заключение</h2>
<p>С помощью JavaScript можно легко показывать и скрывать или создавать и удалять контент, но у этих событий в DOM могут быть разные цели и значения в зависимости от контекста. В этой статье мы упростили показ и скрытие элементов при помощи JavaScript для создания двух совершенно разных интерфейсов: вкладок и навигации в одностраничном приложении.</p>
<p>В инклюзивном дизайне нет чего-то правильного и неправильного. Это просто попытка сделать всё возможное, чтобы как можно больше людей получило ценный для них опыт. По большому счёту всё сводится к объединению представления и поведения таким образом, чтобы пользователи, вне зависимости от характера взаимодействия с интерфейсом и особенностей чтения контента, знали, что поставленные задачи выполняются.</p>
<h2>Чеклист</h2>
<ul>
<li>Не используйте вкладки, если они не подходят для вашей ситуации и не будут поняты и оценены пользователем. То, что вы можете что-то сделать, не означает, что вы это должны делать.</li>
<li>Оглавления и якорные ссылки более простое и надёжное решение для различных вариантов использования вкладок.</li>
<li>Убедитесь, что интерфейсы, которые выглядят как вкладки, имеют ожидаемую семантику и поведение.</li>
<li>Одностраничные приложения не должны выглядеть и вести себя как вкладки несмотря на то, что в них используется JavaScript для переключения между панелями содержимого или для наполнения их содержимым.</li>
</ul>

                    ]]></description><pubDate>Mon, 30 Sep 2019 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/tabbed-interfaces/</guid></item><item><title>Инклюзивные компоненты: переключатель темы</title><link>https://web-standards.ru/articles/theme-switcher/</link><description><![CDATA[
                        <p>Моя мантра при создании веб-интерфейсов: «Если нельзя сделать это грамотно, не делай это вообще». Я учил людей <a href="https://vimeo.com/190834530">писать как можно меньше дурацкого кода</a> в Великобритании, Европе и Китае. Если фича может быть реализована только при значительном снижении производительности, то конечный результат будет отрицательным: так что от такой фичи следует отказаться. Насколько важна в вебе производительность.</p>
<p>Предлагать пользователям выбор того, как будет выглядеть интерфейс, правильно, если вы его не навязываете. Это отвечает одному из принципов инклюзивного дизайна — <a href="https://inclusivedesignprinciples.org/#offer-choice">предлагать варианты</a>. <em>Однако</em> выбор настроек, например, темы, прекрасен и должен быть реализован только тогда, когда это можно сделать грамотно.</p>
<p>Обычно альтернативные темы создаются как отдельные таблицы стилей, между которыми можно переключаться при помощи JavaScript. В некоторых случаях они приводят к проблемам с производительностью (так как перезаписывание темы требует загрузки большого количества дополнительного CSS) и, в большинстве случаев, к проблеме их дальнейшей поддержки (потому что отдельные таблицы стилей должны постоянно обновляться по мере развития сайта).</p>
<p>Один тип альтернативных тем, который имеет ценность для пользователей — это «ночной режим» с низкой яркостью. Это не только проще для глаз, когда читаешь в темноте, но также уменьшает вероятность появления мигрени и разных нарушений, связанных со светобоязнью. Для меня, как для человека, страдающего от мигреней, это важно!</p>
<p>В этой статье я расскажу о том, как грамотно сделать React-компонент, который позволит пользователям переключать светлую тему, установленную по умолчанию, на «тёмный режим», и настроить его с помощью API <code>localStorage</code>.</p>
<p>В случае со светлой темой (чаще всего это тёмный текст на светлом фоне) самым разумным будет создать не полностью новую таблицу стилей, а расширить существующие стили настолько, насколько это возможно. К счастью, в CSS есть свойство <code>filter</code>, которое позволяет инвертировать цвета. Хотя его часто связывают с изображениями, оно может быть применено к любому элементу, включая <code>&lt;html&gt;</code>:</p>
<pre><code tabindex="0" class="language-css">:root {
    filter: invert(100%);
}
</code></pre>
<p><strong>Примечание</strong>: далеко не все браузеры поддерживают сокращённую запись <code>invert()</code>. Используйте <code>100%</code> для лучшей поддержки.</p>
<p>Единственная проблема заключается в том, что <code>filter</code> может инвертировать только <em>явно заданные</em> цвета. Поэтому, если у элемента нет фонового цвета, текст инвертируется, при этом фоновый цвет по умолчанию (белый) останется прежним. Что в итоге? Светлый текст на светлом фоне.</p>
<p>Это легко исправить, присвоив для <code>background-color</code> светлый цвет.</p>
<pre><code tabindex="0" class="language-css">:root {
    background-color: #fefefe;
    filter: invert(100%);
}
</code></pre>
<p>Но у нас всё ещё остаётся проблема с дочерним элементом, у которого также нет цвета фона. И это тот случай, когда нам пригодится ключевое слово из CSS — <code>inherit</code>.</p>
<pre><code tabindex="0" class="language-css">:root {
    background-color: #fefefe;
    filter: invert(100%);
}

* {
    background-color: inherit;
}
</code></pre>
<p>На первый взгляд может показаться, что мы обладаем слишком большими возможностями, но не стоит переживать: у селектора <code>*</code> очень низкая специфичность. Он влияет только на цвет фона для элементов, для которых он ещё не задан. На практике <code>#fefefe</code> — это просто фолбэк.</p>
<h2>Сохранение растровых изображений</h2>
<p>Когда мы хотим инвертировать тему, то, скорее всего, не собираемся инвертировать растровые изображения или видео. В противном случае интерфейс будет заполнен жуткими негативами. Хитрость заключается в дважды инвертированных <code>&lt;img&gt;</code>. Селектор, который я использую, исключает SVG-изображения. Они, как правило, легко и просто инвертируются из-за того, что представлены в виде простых цветовых схем.</p>
<pre><code tabindex="0" class="language-css">:root {
    background-color: #fefefe;
    filter: invert(100%);
}

* {
    background-color: inherit;
}

img:not([src*=&quot;.svg&quot;]), video {
    filter: invert(100%);
}
</code></pre>
<p>Получилось 153 байта без сжатия: об этом позаботилась поддержка тёмных тем. Если вы всё ещё не не уверены, то вот стили, которые используются на популярных новостных сайтах:</p>
<figure>
    <img src="https://web-standards.ru/articles/theme-switcher/images/the-boston-globe-and-independent.png" alt="">
    <figcaption>
        The Boston Globe и The Independent.
    </figcaption>
</figure>
<figure>
    <img src="https://web-standards.ru/articles/theme-switcher/images/the-ny-times-and-private-eye.png" alt="">
    <figcaption>
        The New York Times и Private Eye.
    </figcaption>
</figure>
<h2>Компонент переключения темы</h2>
<p>Поскольку переключение между светлой (по умолчанию) и тёмной (инвертированной) темами — это просто включение и выключение, мы можем использовать для этого что-то простое, например, <a href="https://inclusive-components.design/toggle-button/">переключатели</a>. Мы разбирали их в предыдущей статье <em>(<a href="https://medium.com/web-standards/toggle-buttons-a41388e80974">перевод на русский</a> — прим. переводчика).</em> Однако в этот раз мы сделаем переключатель частью <a href="https://react-cn.github.io/react/index.html">React</a>-компонента. На это есть несколько причин:</p>
<ul>
<li>Возможность повторного использования в проектах на основе React, в которых вы привыкли работать.</li>
<li>Возможность использовать преимущества React: <code>props</code> и <code>defaultProps</code>.</li>
<li>Некоторые люди думают, что фреймворки вроде React и Angular мешают им писать доступный HTML. Это заблуждение должно уйти в прошлое.</li>
</ul>
<p>Также мы не забудем про прогрессивное улучшение и будем показывать компонент только тогда, когда браузер поддерживает свойство <code>filter: invert(100%)</code>.</p>
<h3>Настройка</h3>
<p>Если у вас ещё не настроено окружение для React, то можете легко это сделать, установив <code>create-react-app</code>.</p>
<pre><code tabindex="0" class="language-sh">npm i -g create-react-app
create-react-app theme-switch
cd theme-switch
npm start
</code></pre>
<p>Теперь заготовка приложения будет работать на <code>localhost:3000</code>. В новом проекте <code>theme-switch</code> наш компонент называется <strong>ThemeSwitch</strong> и будет добавлен в <code>render()</code> в <code>App.js</code> как <code>&lt;ThemeSwitch/&gt;</code>.</p>
<pre><code tabindex="0" class="language-jsx">class App extends Component {
    render() {
        return (
            &lt;div className=&quot;App&quot;&gt;
                &lt;div className=&quot;App-header&quot;&gt;
                    &lt;img src={logo} className=&quot;App-logo&quot; alt=&quot;лого&quot;/&gt;
                    &lt;h2&gt;Добро пожаловать в React&lt;/h2&gt;
                &lt;/div&gt;
                &lt;p className=&quot;App-intro&quot;&gt;
                    Чтобы начать, отредактируйте {gfm-js-extract-pre-1}
                    и сохраните для перезагрузки.
                &lt;/p&gt;
                &lt;ThemeSwitch/&gt;
            &lt;/div&gt;
        );
    }
}
</code></pre>
<p><strong>Примечание</strong>: я ленивый, поэтому оставляю заготовку. Чтобы протестировать переключатель тем, добавьте его вместе со стилизованным контентом, взятым из другого проекта. Вы можете включить CSS в <code>App.css</code>.</p>
<p>Не забудьте импортировать компонент <strong>ThemeSwitch</strong> в начале файла <code>App.js</code>:</p>
<pre><code tabindex="0" class="language-jsx">import ThemeSwitch from './components/ThemeSwitch'
</code></pre>
<h2>Файл основы компонента</h2>
<p>Как следует из пути в строке с импортом выше, мы будем работать с файлом <code>ThemeSwitch.js</code>, размещённом в новой папке «components». Так что нужно создать папку и файл. Основа для <strong>ThemeSwitch</strong> выглядит так:</p>
<pre><code tabindex="0" class="language-jsx">import React, { Component } from 'react';

class ThemeSwitch extends Component {
    render() {
    // Разметка компонента в JSX
    }
}
export default ThemeSwitch;
</code></pre>
<p>После рендеринга разметка переключателя, имеющего неактивное состояние и состояние по умолчанию, будет выглядеть таким образом (примечания после этого фрагмента кода):</p>
<pre><code tabindex="0" class="language-jsx">&lt;div&gt;
    &lt;button aria-pressed=&quot;false&quot;&gt;
        тёмная тема:
        &lt;span aria-hidden=&quot;true&quot;&gt;выключить&lt;/span&gt;
    &lt;/button&gt;
    &lt;style media=&quot;none&quot;&gt;
        html { filter: invert(100%); background: #fefefe }
        * { background-color: inherit }
        img:not([src*=&quot;.svg&quot;]), video { filter: invert(100%) }
    &lt;/style&gt;
&lt;/div&gt;
</code></pre>
<ul>
<li>Не все переключатели создаются одинаково. В этом случае мы используем <code>aria-pressed</code> для переключения доступного состояния, и явных «включена» и «выключена» для тех, кто не пользуется скринридерами. Так что часть компонента с «включена» или «выключена» не объявляется. Чтобы не было конфликта между этими состояниями, она скрыта от вспомогательных технологий при помощи <code>aria-hidden</code>. Пользователи скринридеров услышат: _«кнопка переключения тёмной темы, не нажата», <em>«кнопка переключения тёмной темы, нажата»</em> или что-то похожее на это.</li>
<li>CSS настолько лаконичен, что можно использовать его в инлайновом виде. Зададим <code>media=&quot;none&quot;</code> или <code>media=&quot;screen&quot;</code>, когда включена тёмная тема.</li>
</ul>
<p>Скоро эта разметка станет очень сложной, так как мы конвертируем её в JSX.</p>
<h2>Состояние переключения</h2>
<p>Наш компонент будет хранить текущее состояние. Это позволит пользователю переключаться между активным и неактивным состояниями тёмной темы. Сначала мы инициализируем состояние в конструкторе компонента:</p>
<pre><code tabindex="0" class="language-jsx">constructor(props) {
    super(props);

    this.state = {
        active: 'false'
    };
}
</code></pre>
<p>Чтобы оживить компонент, включим вспомогательную функцию <code>isActive()</code>, а также <code>toggle()</code>, которая фактически переключает состояние:</p>
<pre><code tabindex="0" class="language-jsx">isActive = () =&gt; this.state.active;
toggle = () =&gt; {
    this.setState({
        active: !this.isActive()
    });
}
</code></pre>
<p><strong>Примечание</strong>: стрелочные функции неявно возвращают отдельные операторы, отсюда и краткость функции <code>isActive()</code>.</p>
<p>В <code>render()</code> для компонента мы можем использовать <code>isActive()</code> для переключения значения <code>aria-pressed</code>, текста кнопки и значения CSS-атрибута <code>media</code>:</p>
<pre><code tabindex="0" class="language-jsx">return (
    &lt;div&gt;
        &lt;button aria-pressed={this.isActive()} onClick={this.toggle}&gt;
            тёмная тема:
            &lt;span aria-hidden=&quot;true&quot;&gt;
                {this.isActive() ? 'включена' : 'выключена'}
            &lt;/span&gt;
        &lt;/Button&gt;
        &lt;style media={this.isActive() ? 'screen' : 'none'}&gt;
            {this.css}
        &lt;/style&gt;
    &lt;/div&gt;
);
</code></pre>
<figure>
    <img src="https://web-standards.ru/articles/theme-switcher/images/aria-pressed-state-example.png" alt="">
    <figcaption>
        Конечно, когда выбрана тёмная тема, то сама кнопка тоже инвертирована.
    </figcaption>
</figure>
<p>Обратите внимание на часть с <code>{this.css}</code>. JSX напрямую не поддерживает встроенный CSS, поэтому мы должны сохранить его в переменной и ввести здесь динамически. В конструкторе:</p>
<pre><code tabindex="0" class="language-jsx">this.css = `
html { filter: invert(100%); background: #fefefe; }
* { background-color: inherit }
img:not([src*=&quot;.svg&quot;]), video { filter: invert(100%) }`;
</code></pre>
<h3>Боремся с проблемами браузеров</h3>
<p>К сожалению, не во всех браузерах применяются нужные стили, когда переключаешься между <code>media=&quot;none&quot;</code> и <code>media=&quot;screen&quot;</code>. Для того, чтобы вызвать перерисовку, мы должны перезаписать текстовое содержимое тега <code>&lt;style&gt;</code>. Самый простой способ, который я нашёл — это использовать метод <code>trim()</code>. Любопытно, что он нужен только для Chrome.</p>
<pre><code tabindex="0" class="language-jsx">{this.isActive() ? this.css.trim() : this.css}
</code></pre>
<h2>Сохраняем настройки темы</h2>
<p>Для того, чтобы сохранить выбор темы, сделанный пользователем, нам нужно использовать <code>localStorage</code> и методы жизненного цикла. Во-первых, я задал алиасы для <code>localStorage</code> в конструкторе. Это устраняет ошибки линтинга, которые возникают при прямом вызове <code>localStorage</code>.</p>
<pre><code tabindex="0" class="language-jsx">this.store = typeof localStorage === 'undefined' ? null : localStorage;
</code></pre>
<p>Используя метод <code>componentDidMount</code>, я могу отследить и применить сохранённые настройки после того, как компонент подключится. В выражении по умолчанию используется значение <code>false</code>, если элемент ещё не создан.</p>
<pre><code tabindex="0" class="language-jsx">componentDidMount() {
    if (this.store) {
        this.setState({
            active: this.store.getItem('ThemeSwitch') || false
        });
    }
}
</code></pre>
<p>Так как управление состоянием в React происходит асинхронно, недостаточно просто сохранять изменённое состояние после того, как оно было дополнено. Вместо этого мне нужно использовать метод <code>componentDidUpdate</code>:</p>
<pre><code tabindex="0" class="language-jsx">componentDidUpdate() {
    if (this.store) {
        this.store.setItem('ThemeSwitch', this.state.active);
    }
}
</code></pre>
<h2>Скрываем в неподдерживаемых браузерах</h2>
<p>Некоторые браузеры ещё не поддерживают <code>filter: invert(100%)</code>. Для них нужно скрыть наш переключатель темы. Лучше, если он просто недоступен, чем когда доступен и при этом не работает. Благодаря специальной функции <code>invertSupported</code> мы можем узнать о поддержке свойства, чтобы установить состояние <code>supported</code>.</p>
<p>Если вы когда-нибудь имели дело с <a href="https://modernizr.com/">Modernizr</a>, то могли бы использовать похожий тест свойства и его значения для CSS. Однако не стоит его здесь использовать, поскольку мы не хотим, чтобы наш компонент имел какие-либо зависимости, если в этом нет необходимости.</p>
<pre><code tabindex="0" class="language-jsx">invertSupported (property, value) {
    var prop = property + ':',
        el = document.createElement('test'),
        mStyle = el.style;
    el.style.cssText = prop + value;
    return mStyle[property];
}

componentDidMount() {
    if (this.store) {
        this.setState({
            supported: this.invertSupported('filter', 'invert(100%)'),
            active: this.store.getItem('ThemeSwitch') || false
        });
    }
}
</code></pre>
<p>Это можно использовать в нашем JSX для того, чтобы скрыть компонент интерфейса с помощью свойства <code>hidden</code> там, где оно не поддерживается.</p>
<pre><code tabindex="0" class="language-html">&lt;div hidden={!this.state.supported}&gt;
    &lt;!-- контент компонента --&gt;
&lt;/div&gt;
</code></pre>
<p>В современных браузерах свойство <code>hidden</code> скроет компонент от вспомогательных технологий и запретит делать на них фокус с клавиатуры. Для того, чтобы в старых браузерах оно тоже работало, используйте в стилях этот селектор:</p>
<pre><code tabindex="0" class="language-css">[hidden] {
    display: none;
}
</code></pre>
<p>В качестве альтернативы вы можете вообще отказаться от отображения содержимого компонента на странице, возвращая <code>null</code>.</p>
<pre><code tabindex="0" class="language-jsx">render() {
    if (!this.supported) {
        return null;
    }

    return (
        &lt;div&gt;
            &lt;button aria-pressed={this.state.active} onClick={this.toggle}&gt;
                inverted theme:
                &lt;span aria-hidden=&quot;true&quot;&gt;
                    {this.state.active ? 'on' : 'off'}
                &lt;/span&gt;
            &lt;/button&gt;
            &lt;style media={this.state.active ? 'screen' : 'none'}&gt;
                {this.state.active ? this.css.trim() : this.css}
            &lt;/style&gt;
        &lt;/div&gt;
    );
}
</code></pre>
<h2>Режим высокой контрастности в Windows</h2>
<p>Пользователям Windows предлагается ряд тем с высокой контрастностью уже на уровне операционной системы: несколько светлых и тёмных (свет в темноте) как наша инверсированная тема. Важно убедиться, что WHCM <em>(Windows High Contrast Mode. — прим. переводчика)</em> поддерживается настолько, насколько это возможно. Вот несколько советов:</p>
<ul>
<li>Не используйте фоновые изображения в качестве контента. Это не только инвертирует изображения в нашей инвертированной тёмной теме, но и полностью исключает их из большинства тем с высокой контрастностью Windows. Используйте в тегах <code>&lt;img/&gt;</code> изображения со смысловой нагрузкой, а не декоративные, с хорошо описывающим их <code>alt</code>.</li>
<li>Для инлайновых SVG-иконок используйте значение <code>currentColor</code> в <code>fill</code> и <code>stroke</code>. Благодаря этому цвет иконки будет меняться вместе с цветом окружающего текста при включении высококонтрастной темы.</li>
<li>Если вам нужно определить WHCM, чтобы добавить улучшения, вы можете использовать следующий медиазапрос:</li>
</ul>
<pre><code tabindex="0" class="language-css">@media (-ms-high-contrast: active) {
    /* Относящийся к WHCM код */
}
</code></pre>
<h2>Проп <code>preserveRasters</code></h2>
<p>Пропсы (свойства компонентов) — это стандартный способ настройки компонентов. Конфигурируемый компонент может использоваться в разных ситуациях и проектах, что делает его более инклюзивным.</p>
<p>В нашем случае, почему бы нам не сделать так, чтобы у Implementor был выбор: действительно ли сохранить растровые изображения или инвертировать их вместе со всем остальным контентом. Я создам проп <code>preserveRasters</code>, который принимает значения «true» или «false». Вот как это выглядит на примере нашего компонента:</p>
<pre><code tabindex="0" class="language-jsx">&lt;ThemeSwitch preserveRasters={false} /&gt;
</code></pre>
<p>Я могу запросить этот проп в виде строки CSS и повторно инвертировать изображения, если её значение равно <code>true</code>:</p>
<pre><code tabindex="0" class="language-jsx">this.css = `
    html { filter: invert(100%); background: #fefefe; }
    * { background-color: inherit }
    ${this.props.preserveRasters === 'true' ? `img:not([src*=&quot;.svg&quot;]), video { filter: invert(100%) }` : ``}`;
</code></pre>
<p><strong>Примечание</strong>: допустимо, хотя и немного некрасиво, использовать таким образом тернарные операторы для интерполяции строк.</p>
<h3>Значение по умолчанию</h3>
<p>Чтобы сделать компонент более надёжным и дать Implementor возможность пропустить атрибут, мы также можем добавить <code>defaultProp</code>. После определения класса компонента можно использовать следующее:</p>
<pre><code tabindex="0" class="language-jsx">ThemeSwitch.defaultProps = { preserveRasters: true }
</code></pre>
<h2>Устанавливаем компонент</h2>
<p>Пример <a href="https://github.com/Heydon/react-theme-switch">такого компонента</a> есть в npm:</p>
<pre><code tabindex="0" class="language-sh">npm i --save react-theme-switch
</code></pre>
<p>Помимо этого, простой пример с JavaScript, в основе которого лежит чекбокс, есть на <a href="https://codepen.io/heydon/pen/Vzyrre">СodePen</a>:</p>
<iframe src="https://codepen.io/heydon/embed/Vzyrre"></iframe>
<h2>Расположение</h2>
<p>Осталось только решить, где мы собираемся разместить компонент в документе. Как правило, утилиты вроде выбора темы следует искать в области ориентиров (landmark region), а не в <code>&lt;main&gt;</code>, поскольку пользователь скринридера ожидает, что этот контент будет меняться между страницами. Допустимы <code>&lt;header&gt;</code> (<code>role=&quot;banner&quot;</code>) или <code>&lt;footer&gt;</code> (<code>role=&quot;contentinfo&quot;</code>).</p>
<p>Переключатель должен появляться в одном и том же месте на всех страницах, чтобы пользователь мог легко найти его после того, как использовал в первый раз. Примите во внимание <a href="https://inclusivedesignprinciples.org/#be-consistent">принцип консистентности</a>, который здесь применяется.</p>
<h2>Чеклист</h2>
<ul>
<li>Внедряйте полезные функции только в том случае, если снижение производительности минимально, а конечный интерфейс не становится сильно сложнее.</li>
<li>Используйте в интерфейсах только поддерживаемые функции. Проверяйте их доступность.</li>
<li>Используйте семантическую разметку в React-компоненте: такие компоненты всегда будут работать!</li>
<li>Используйте пропсы для того, чтобы сделать ваши компоненты более конфигурируемыми и иметь возможность использовать их много раз.</li>
</ul>

                    ]]></description><pubDate>Mon, 02 Sep 2019 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/theme-switcher/</guid></item><item><title>Инклюзивные компоненты: тултипы и тоглтипы</title><link>https://web-standards.ru/articles/tooltips-toggletips/</link><description><![CDATA[
                        <p>Тултипы <em>(в этом переводе я буду транслитерировать слово «tooltip», их ещё часто называют подсказкой — прим. переводчика),</em> которые мой друг <a href="https://twitter.com/stevefaulkner">Стив</a> ласково называет «тутлипы» (tootlips), давно устоявшийся интерфейсный паттерн. Буквально это «подсказки для инструментов» в виде небольших пузырей с информацией, которые объясняют цель непонятных контролов и инструментов. Распространённый пример — контрол в виде таинственной иконки, смысл которой пользователь пока ещё не понимает.</p>
<p>Я хотел бы поговорить о том, когда и как эти пузыри должны появляться, так как столкнулся с большим количеством тултипов, которые, кажется, ведут себя немного по-разному. Я сделал вывод, что все эти реализации можно разделить на две самостоятельные группы: настоящие тултипы и паттерны, которые я с опаской называю «тоглтипы» (toggletips). Это слово придумал Стив в ходе <a href="https://developer.paciellogroup.com/blog/2016/01/simple-standalone-toggletip-widget-pattern/">пары исследований и экспериментов</a>, которые не так давно провёл.</p>
<p>Инклюзивный дизайн часто затрагивает проблему того, как обеспечить пользователя правильным инструментом для работы, и как создать правильный тултип к нему. В этой статье я рассмотрю разные ситуации, для которых могут потребоваться тултипы или тоглтипы, и покажу как сделать их инклюзивными.</p>
<h2>Атрибут <code>title</code></h2>
<p>Мы не можем говорить о тултипах без упоминания атрибута <code>title</code>: стандартный для HTML атрибут, предназначенный для пузырей с контекстной информацией. Блог Paciello Group не обходится без сарказма при описании вклада <code>title</code> в веб-интерфейсы:</p>
<blockquote>
<p>Если вы хотите скрыть контент от пользователей мобильных и планшетов, а также от пользователей вспомогательных технологий и клавиатуры, то используйте атрибут <code>title</code>.
<a href="https://developer.paciellogroup.com/blog/2013/01/using-the-html-title-attribute-updated/">Блог The Paciello Group</a>.</p>
</blockquote>
<p>Это <em>довольно плохой</em> вклад с точки зрения инклюзивности. На самом деле единственное место, где атрибут title может хорошо работать в связке со скринридерами — это элементы форм вроде <code>&lt;input&gt;</code>. Во всех других случаях пользователи мобильных устройств и клавиатуры не смогут увидеть сообщение из <code>title</code>. Вкратце: просто добавьте хорошо сформулированный, постоянно видимый лейбл.</p>
<img src="https://web-standards.ru/articles/tooltips-toggletips/images/input-labelled.png" alt="">
<p>Я большой сторонник использования стандартных HTML-элементов и атрибутов тогда, когда они доступны. Это наиболее эффективный и правильный с точки зрения производительности путь создания удобных веб-интерфейсов. Но, несмотря на то, что атрибут <code>title</code> поддерживается спецификацией, он действительно не подходит для своей цели.</p>
<p>Опять же, нам ещё предстоит определить эту цель. Для чего <em>следует</em> использовать тултипы? И даже если мы сможем сделать их такими, что с ними смогут взаимодействовать многие пользователи, нужны ли они вообще?</p>
<h2>Случаи использования тултипов</h2>
<p>Как мы уже установили, тултипы нужны для пояснений: они дают <em>недостающую</em> информацию. Но почему эта информация должна изначально отсутствовать? Как я писал в <a href="https://shop.smashingmagazine.com/products/inclusive-design-patterns">Inclusive Design Patterns</a>, иконки могут упростить понимание интерфейса и помочь в его интернационализации. Существуя отдельно, они всегда могут запутать пользователя, потому что <em>ничего не объясняют.</em> Иконки не содержат информации для пользователей, которые не узнают или не могут расшифровать их.</p>
<p>В большинстве случаев вы должны просто размещать текст рядом с иконками. Как и видимые подписи к полям, которые я только что упомянул, текстовые лейблы являются самым простым способом добавления подписей к элементам. Также они автоматически доступны для скринридеров, если это настоящий текст (а не картинки с текстом).</p>
<p>Типичное объяснение, почему лейблов нет — это «нет места». И это, вероятно, так, если вы решили не использовать их в первую очередь. Если вы считаете их важными с самого начала, то найдёте способ это сделать.</p>
<figure>
    <img src="https://web-standards.ru/articles/tooltips-toggletips/images/icons-labelled.png" alt="">
    <figcaption>Всегда есть место для текста, если вы решили его добавить. Однако в некоторых случаях для него остаётся больше места, чем в других.</figcaption>
</figure>
<p>Тултипы — последнее средство там, где пространство действительно стоит дорого — возможно, из-за огромного количества элементов управления, как на панели инструментов в редакторе WYSIWYG. Итак, как бы мы разработали их максимально инклюзивно?</p>
<h2>Инклюзивные тултипы</h2>
<p>Первое, что нужно сделать — это сделать текст в тултипах доступным для вспомогательных технологий. Есть несколько способов привязки тултипа к контролу, и мы выбираем между ними исходя из его конкретной роли: это основной лейбл или он нужен для дополнительного пояснения?</p>
<p>У контрола для управления уведомлениями с тултипом «Уведомления» — это основной лейбл. А тултип «Просмотр уведомлений и управление настройками» является дополнительным.</p>
<h2>Тултип как основной лейбл</h2>
<p>Чтобы связать один элемент с другим в качестве основного лейбла, нужно использовать <code>aria-labelledby</code>. Связь устанавливается благодаря <code>aria-labelledby</code> и <code>id</code> с одинаковыми значениями.</p>
<pre><code tabindex="0" class="language-html">&lt;button class=&quot;notifications&quot;
        aria-labelledby=&quot;notifications-label&quot;&gt;
    &lt;svg&gt;
        &lt;use xlink:href=&quot;#notifications-icon&quot;&gt;&lt;/use&gt;
    &lt;/svg&gt;
&lt;/button&gt;
&lt;div role=&quot;tooltip&quot;
        id=&quot;notifications-label&quot;&gt;
    Уведомления
&lt;/div&gt;
</code></pre>
<ul>
<li>Обратите внимание, как устанавливается роль <code>tooltip</code>. С практической точки зрения она гарантирует, что <code>aria-describedby</code> точно работает там, где поддерживается. Как указывает Леони Уотсон, <a href="https://developer.paciellogroup.com/blog/2017/07/short-note-on-aria-label-aria-labelledby-and-aria-describedby/">ARIA-лейблы и описания иногда работают не со всеми элементами</a>, если вы не используете соответствующую роль. В этом случае самой важной является роль кнопки по умолчанию у элемента <code>&lt;button&gt;</code>. Однако <code>role=&quot;tooltip&quot;</code> может расширить поддержку этого метода присвоения лейблов в некоторых программах.</li>
<li>Каким бы ни был текст в SVG, он не будет прочитан. <code>aria-labelledby</code> <em>переопределяет</em> текстовое содержимое кнопки на то, которое есть у лейбла.</li>
</ul>
<p>Сейчас пример выше для скринридеров и их пользователей с точки зрения функциональности похож на простую подпись вроде этой:</p>
<pre><code tabindex="0" class="language-html">&lt;button class=&quot;notifications&quot;&gt;
    Уведомления
&lt;/button&gt;
</code></pre>
<p>Текст тултипа доступен при фокусе так же, как если бы на него навёл курсор зрячий пользователь. На самом деле, если бы весь текст появлялся только при наведении, то для зрячих пользователей интерфейс был бы в некоторой степени аналогичен интерфейсу пользователей скринридеров.</p>
<h2>Примечание: лишние тултипы</h2>
<p>Всё то время, что я работаю консультантом по дизайну интерфейсов, я вижу людей, которые добавляют атрибут <code>title</code> для ссылок с точно таким же текстом:</p>
<pre><code tabindex="0" class="language-html">&lt;a href=&quot;/some/path&quot;
    title=&quot;Специальная страница Хейдона&quot;&gt;
    Специальная страница Хейдона
&lt;/a&gt;
</code></pre>
<p>Если текстовое содержимое элемента хорошо видно, то это избыточно.
<code>title</code> ничего не делает для скринридеров, кроме повтора в некоторых случаях.</p>
<h3>Добавляем счётчик уведомлений</h3>
<p>Что, если кнопка показывает число непрочитанных сообщений, как это часто бывает (я имею в виду, конечно, Twitter)?</p>
<img src="https://web-standards.ru/articles/tooltips-toggletips/images/icon-tooltip.png" alt="">
<p>К счастью, атрибут <code>aria-labelledby</code> может содержать несколько <code>id</code>, разделённых пробелами.</p>
<pre><code tabindex="0" class="language-html">&lt;button class=&quot;notifications&quot; aria-labelledby=&quot;notifications-count notifications-label&quot;&gt;
    &lt;svg&gt;
        &lt;use xlink:href=&quot;#notifications-icon&quot;&gt;&lt;/use&gt;
    &lt;/svg&gt;
    &lt;span id=&quot;notifications-count&quot;&gt;3&lt;/span&gt;
&lt;/button&gt;
&lt;div role=&quot;tooltip&quot; id=&quot;notifications-label&quot;&gt;
    Уведомления
&lt;/div&gt;
</code></pre>
<p>Несмотря на то, что элемент с <code>#notifications-count</code> находится внутри <code>&lt;button&gt;</code>, он не является сам по себе лейблом: он формирует первую часть лейбла в качестве первого <code>id</code>, указанного в значении <code>aria-labelledby</code>. Элемент расположен там, где он находится (в разметке), так что дизайнер может использовать относительное и абсолютное позиционирование, чтобы разместить элемент в нужном месте.</p>
<p>Сейчас для пользователей скринридеров лейбл — это «три уведомления». Такое решение лаконично и элемент работает одновременно как счётчик текущих уведомлений и как напоминание о том, что это контрол для управления уведомлениями.</p>
<h2>Тултип как дополнительное описание</h2>
<p>Давайте попробуем сейчас создать тултип в виде дополнительного описания. Это его классическая форма. Как и плейсхолдер для <code>&lt;input&gt;</code>, тултип нужен для добавления информации и пояснений.</p>
<p>Некоторые интерактивные элементы могут иметь доступные описания, но все они нуждаются в доступных лейблах. Если мы используем <code>aria-describedby</code> для связи текста тултипа, нам нужен другой метод для добавления лейбла «Уведомления». В отличие от <code>aria-labelledby</code> мы можем добавить визуально скрытый <code>&lt;span&gt;</code> для текстового содержимого кнопки рядом со счётчиком «3».</p>
<pre><code tabindex="0" class="language-html">&lt;button class=&quot;notifications&quot; aria-describedby=&quot;notifications-desc&quot;&gt;
    &lt;svg&gt;
        &lt;use xlink:href=&quot;#notifications-icon&quot;&gt;&lt;/use&gt;
    &lt;/svg&gt;
    &lt;span id=&quot;notifications-count&quot;&gt;3&lt;/span&gt;
    &lt;span class=&quot;visually-hidden&quot;&gt;Уведомления&lt;/span&gt;
&lt;/button&gt;
&lt;div role=&quot;tooltip&quot; id=&quot;notifications-desc&quot;&gt;
    Посмотреть и изменить настройки уведомлений
&lt;/div&gt;
</code></pre>
<p>Класс <code>visually-hidden</code> аналогичен специальному CSS, который мы обсуждали ранее в Inclusive Components. Он скрывает <code>&lt;span&gt;</code> визуально, при этом оставляя его доступным для объявления скринридерами.</p>
<pre><code tabindex="0" class="language-css">.visually-hidden {
    clip-path: inset(100%);
    clip: rect(1px, 1px, 1px, 1px);
    height: 1px;
    overflow: hidden;
    position: absolute;
    white-space: nowrap;
    width: 1px;
}
</code></pre>
<p>Предписанное поведение <code>aria-describedby</code> заключается в том, чтобы давать последнюю информацию об изменениях для контрола после объявления лейбла и роли. В этом случае будет объявлено следующее:</p>
<blockquote>
<p>Кнопка, уведомления… Посмотреть и изменить настройки уведомлений.</p>
</blockquote>
<p>Большинство скринридеров сделают паузу перед описанием.</p>
<h3>Взаимодействие</h3>
<p>Чтобы улучшить поведение печально известного атрибута <code>title</code>, наши тултипы должны появляться как при фокусе, так и при наведении. Добавить всплывающую подсказку в элементе рядом с кнопкой мы можем только с помощью CSS:</p>
<pre><code tabindex="0" class="language-css">[role=&quot;tooltip&quot;] {
    display: none;
}

button:hover + [role=&quot;tooltip&quot;],
button:focus + [role=&quot;tooltip&quot;] {
    display: block;
}
</code></pre>
<p>Однако, нам может понадобиться обернуть кнопку и тултип в один контейнер для позиционирования:</p>
<pre><code tabindex="0" class="language-css">.button-and-tooltip {
    position: relative;
}

[role=&quot;tooltip&quot;] {
    position: absolute;
    /* требуются значения для left, top, right, bottom */
}
</code></pre>
<h2>Взаимодействие с тач-устройствами</h2>
<p>Пока что всё это не работает хорошо для пользователей тач-экранов, потому что фокус и активное состояние существуют одновременно. На практике это означает, что вы увидите всплывающую подсказку только при нажатии на кнопку.</p>
<p>То, насколько <em>много</em> проблем возникает в этой ситуации, зависит от природы приложения в котором есть этот контрол. Насколько плохо, если пользователь нажимает на контрол без понимания того, что он сделает в первый раз? Насколько легко такие контролы могут восстанавливать первоначальное состояние?</p>
<p>Есть другая вещь, которую вы можете попробовать. Можно было бы запретить для кнопки действие по умолчанию при первом нажатии, чтобы она <em>просто</em> показывала в этот момент тултип. Вы можете развить идею с «режимом обучения» дальше и показать тултипы в виде инлайнового текста для новичков, а затем упростить интерфейс и показывать только иконки освоившимся пользователям. К этому времени они должны понять, что означают иконки.</p>
<figure>
    <img src="https://web-standards.ru/articles/tooltips-toggletips/images/icons-adaptive.png" alt="">
    <figcaption>В других случаях экран для каждого из вариантов должен иметь понятный (<code>&lt;h1&gt;</code>) заголовок с тем же самым текстом, что и у лейбла. Тогда, по крайней мере, пользователь узнает, что это за иконка, когда зайдёт на страницу.</figcaption>
</figure>
<p>Это может помочь избавиться от тултипов, что, вероятно, к лучшему. Однако, родственник тултипов — тоглтип, может работать как при взаимодействии с мышью, так и у пользователей, использующих клавиатуры и сенсорные экраны.</p>
<h2>Инклюзивные тоглтипы</h2>
<p>Тоглтипы <em>(буквально «подсказка-переключатель» — прим. переводчика)</em> похожи на тултипы в том, что они могут предоставить дополнительную информацию или что-то разъяснить. Но они отличаются тем, что их контрол сам по себе вспомогательный: тоглтипы существуют для того, чтобы показывать пузыри с информацией. Больше у них нет никакой цели.</p>
<p>Часто они имеют вид иконки с маленькой «i»:</p>
<img src="https://web-standards.ru/articles/tooltips-toggletips/images/icon-toggletip.png" alt="">
<p>Работая с тапами так же хорошо, как с мышкой и клавиатурой, тоглтипы появляются чаще при клике, чем при наведении или фокусе. Это значит, что <code>aria-describedby</code> больше не подходит. Почему? Потому что пользователь скринридера не может получить доступ к информации до того, как нажмёт на кнопку, так что это нажатие ничего не изменит. Технически, у них есть <em>доступ</em> к информации, которая делает контрол «доступным», но сам по себе он просто не имеет смысла. Другими словами, это больше проблема пользовательского опыта, чем доступности. Однако, это важная проблема.</p>
<h2>Тоглтипы с интерактивными областями</h2>
<p>Хитрость заключается в том, чтобы скринридеры объявляли информацию после события по клику. Это подходящий случай для использования интерактивных областей (<a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions">live region</a>). Мы можем добавить пустую интерактивную область и заполнить её событиями при инициализации тоглтипов. Это поможет одновременно отобразить пузырь c информацией и сделать объявление о тултипе.</p>
<p>Ниже разметка с пустой интерактивной областью. Обратите внимание на элемент <code>.tooltip-container</code>, который нужен для позиционирования. Для него должно быть задано свойство <code>position: relative</code>, которое позволяет абсолютно позиционировать рядом элемент <code>.toggletip-bubble</code>.</p>
<pre><code tabindex="0" class="language-html">&lt;span class=&quot;tooltip-container&quot;&gt;
    &lt;button type=&quot;button&quot; aria-label=&quot;больше информации&quot; data-toggletip-content=&quot;Это уточняет то, что нужно уточнить&quot;&gt;i&lt;/button&gt;
    &lt;span role=&quot;status&quot;&gt;&lt;/span&gt;
&lt;/span&gt;
</code></pre>
<p>Также обратите внимание на атрибут <code>type=&quot;button&quot;</code>, запрещающий некоторым браузерам определять элемент как кнопку отправки данных, если он расположен внутри формы. Вот разметка с интерактивной областью, в которой происходят какие-то события (после того, как на кнопку с тоглтипом кликнули):</p>
<pre><code tabindex="0" class="language-html">&lt;span class=&quot;tooltip-container&quot;&gt;
    &lt;button type=&quot;button&quot; aria-label=&quot;больше информации&quot; data-toggletip-content=&quot;Это уточняет то, что нужно уточнить&quot;&gt;i&lt;/button&gt;
    &lt;span role=&quot;status&quot;&gt;
        &lt;span class=&quot;toggletip-bubble&quot;&gt;
            Это уточняет то, что нужно уточнить
        &lt;/span&gt;
    &lt;/span&gt;
&lt;/span&gt;
</code></pre>
<p>Дополнительный скрипт с комментариями и CodePen:</p>
<pre><code tabindex="0" class="language-js">(function() {
    // Получаем все кнопки-тоглтипы
    var toggletips = document.querySelectorAll('[data-toggletip-content]');
    // Обходим их
    Array.prototype.forEach.call(toggletips, function (toggletip) {
        // Получим сообщение из элемента с data-content
        var message = toggletip.getAttribute('data-toggletip-content');
        // Получаем все элементы с интерактивными областями
        var liveRegion = toggletip.nextElementSibling;
        // Переключаем сообщение
        toggletip.addEventListener('click', function () {
                liveRegion.innerHTML = '';
                window.setTimeout(function() {
                    liveRegion.innerHTML =
                        '&lt;span class=&quot;toggletip-bubble&quot;&gt;' +
                            message +
                        '&lt;/span&gt;';
                }, 100);
        });
        // Закрываем по клику на другую область
        document.addEventListener('click', function (e) {
            if (toggletip !== e.target) {
                liveRegion.innerHTML = '';
            }
        });
        // Убираем тоглтип по нажатию на Esc
        toggletip.addEventListener('keydown', function(e) {
            if ((e.keyCode || e.which) === 27)
            liveRegion.innerHTML = '';
        });
    });
}());
</code></pre>
<iframe src="https://codepen.io/heydon/embed/zdYdQv"></iframe>
<h3>Примечания</h3>
<ul>
<li>Наша кнопка не переключатель, по крайне мере в обычном понимании. При нажатии она только показывает пузырь с информацией, а не показывает и скрывает его. Чтобы скрыть пузырь, нужно убрать фокус с кнопки, кликнуть мышью в другой области или нажать клавишу <kbd>Esc</kbd>.</li>
<li>Когда по кнопке кликнули во второй (или третий, четвёртый, пятый раз…) интерактивная область снова заполняется событиями через определённый интервал времени, после чего скринридеры повторно объявляют контент. Это проще и интуитивно понятнее в отличие от состояний при переключении (состояние «сообщение получено» не имеет особого смысла, особенно если оно уже было объявлено).</li>
<li>Сдержанный интервал (discreet interval) у последнего элемента добавлен при помощи <code>setTimeout</code>. Без этого невозможно было бы зарегистрировать повторное заполнение интерактивной области событиями и объявить контент снова.</li>
<li>Атрибут <code>role=&quot;tooltip&quot;</code> не подходит из-за того, что для интерактивной области задан <code>role=&quot;status&quot;</code>.</li>
</ul>
<h3>Прогрессивное улучшение <code>title</code></h3>
<p>Как я уже упоминал, атрибут <code>title</code> <em>действительно</em> ненадёжный. Но он, по крайней мере, добавляет доступный лейбл для некоторых вспомогательных технологий, присутствующий, когда на кнопке сделан фокус. Мы можем с помощью <code>title</code> добавить контент для пузыря и использовать его для создания атрибута <code>data-toggletip-content</code> при загрузке страницы. Первоначальный хук нашего скрипта теперь становится логическим <code>data-toggletip</code>:</p>
<pre><code tabindex="0" class="language-html">&lt;button data-toggletip aria-label=&quot;больше информации&quot; title=&quot;Это уточняет то, что нужно уточнить&quot;&gt;i&lt;/button&gt;
</code></pre>
<p>В скрипте нам нужно взять значение <code>title</code> для создания <code>data-tooltip-content</code>, а после удалить <code>title</code>, так как он нам не нужен. Если не трогать его, то его всё ещё может объявить скринридер.</p>
<pre><code tabindex="0" class="language-js">var toggletips = document.querySelectorAll('[data-toggletip][title]');

Array.prototype.forEach(toggletips, function (toggletip) {
    var message = toggletip.getAttribute('title');
    toggletip.setAttribute('data-tooltip-content', message);
    toggletip.removeAttribute('title');
});
</code></pre>
<h3>Более совершенное прогрессивное улучшение</h3>
<p>Кнопка, которая ничего не делает и имеет атрибут <code>title</code> — не самое хорошее начало. Вместо этого я бы порекомендовал инлайново отображать контент тоглтипа, а затем улучшать его, создавая кнопку динамически.</p>
<p>Вот ещё один CodePen, в котором обычный параграф прогрессивно улучшается до тоглтипа:</p>
<iframe src="https://codepen.io/heydon/embed/Vzwdpy"></iframe>
<h2>Тесты и сообщения об ошибках</h2>
<p>О чём я ещё не говорил в Inclusive Components, это написание тестов. Давайте немного обсудим это здесь. Не волнуйтесь, я не имею в виду юнит-тесты.</p>
<p>Если наш компонент, тоглтип, является частью дизайн-системы, его могут взять и использовать разные люди. Когда мы пишем тесты и включаем предупреждения, то можем убедиться, что они не используются неправильно.</p>
<p>Тоглтип-кнопка, которая не является <code>&lt;button&gt;</code>, обманывает вспомогательные технологии и на ней нельзя сделать фокус с помощью клавиатуры (если это не другой неправильный в данном случае элемент вроде ссылки). В нашем скрипте мы можем обнаружить элемент nodeName и вернуть сообщение об ошибке, если это не BUTTON. Мы используем <code>return</code>, чтобы остановить выполнение оставшейся части IIFE <em>(Immediately Invoked Function Expression — немедленно вызываемой функции — прим. переводчика).</em></p>
<pre><code tabindex="0" class="language-js">if (toggletip.nodeName !== 'BUTTON') {
    console.error('Тоглтипы должны быть элементами &lt;button&gt;.')
    return;
}
</code></pre>
<img src="https://web-standards.ru/articles/tooltips-toggletips/images/js-error.png" alt="">
<h2>CSS-тесты и сообщения об ошибках</h2>
<p>В <a href="https://shop.smashingmagazine.com/products/inclusive-design-patterns">Inclusive Design Patterns</a> я писал про использование визуальной регрессии для того, чтобы подсветить ошибки в коде и добавить сообщения об ошибке в инструменте разработчика.</p>
<p>Ошибка, которую мы ранее отловили с помощью JavaScript, может быть обнаружена с помощью CSS-селектора <code>[data-tooltip]:not(button)</code>. Мы можем подсветить элементы с ошибками с помощью красного аутлайна и добавить сообщение о них с помощью <code>ERROR</code>:</p>
<pre><code tabindex="0" class="language-css">[data-tooltip]:not(button) {
    outline: red solid 0.5em;
    ERROR: Тоглтипы должны быть элементами &lt;button&gt;.
}
</code></pre>
<p>ERROR, несмотря на невалидное значение, появится в инструментах разработчика, когда элемент будет изучен.</p>
<figure>
    <img src="https://web-standards.ru/articles/tooltips-toggletips/images/icon-toggletip-focused.png" alt="">
    <figcaption>Красный аутлайн показывает, где есть ошибка, и помогает разработчику найти нужный элемент в инспекторе DOM.</figcaption>
</figure>
<h2>Заключение</h2>
<p>Чаще всего тултипы не нужны, если вы используете понятные текстовые лейблы или знакомые пользователям иконки. Чаще всего тоглтипы — это сложный способ предоставления информации, которая может быть просто частью контента на странице. Но я всё время встречаю эти компоненты при аудите сайтов, поэтому дал некоторые рекомендации о том, как максимально правильно их использовать.</p>
<h3>Чеклист</h3>
<ul>
<li>Если у вас есть свободное пространство, не используйте тултипы или тоглтипы. Просто добавьте понятные лейблы и достаточное количество текста на страницу.</li>
<li>Если вы хотите использовать тултип, решите, следует ли указывать содержание подсказки в качестве лейбла или описания, и выберите соответствующие свойства ARIA.</li>
<li>Не полагайтесь на атрибуты <code>title</code>. Они не доступны для клавиатуры и не поддерживаются многими скринридерами.</li>
<li>Не описывайте тоглтип при помощи атрибута <code>aria-describedby</code>. Это делает кнопку с ним нефункциональной для пользователей скринридеров.</li>
<li>Не добавляйте в тултипы или тоглтипы интерактивный контент, например, кнопку закрытия или согласия, ссылки. Это работа для более сложных меню и диалоговых компонентов.</li>
</ul>
<p><em>Спасибо <a href="https://twitter.com/backwardok">@backwardok</a>, которая заметила, что я создал повторяющийся лейбл, используя текстовый узел для описания того же элемента. Упс!</em></p>

                    ]]></description><pubDate>Tue, 23 Jul 2019 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/tooltips-toggletips/</guid></item><item><title>Инклюзивные компоненты: меню и кнопки меню</title><link>https://web-standards.ru/articles/menu-buttons/</link><description><![CDATA[
                        <p>Классифицировать сложно. Например, возьмём крабов. Раки-отшельники, веерные крабы и подковообразные крабы, с точки зрения таксономии, <em>не настоящие</em> крабы, но это не мешает использовать в их названиях слово «краб». Всё становится ещё запутаннее, когда через какое-то время благодаря процессу, называемому <em>канцеризацией</em>, ненастоящие крабы эволюционируют, чтобы больше походить на настоящих. Это то, что произошло с королевскими крабами, которые в прошлом были раками-отшельниками. Представьте размеры их раковин!</p>
<p>В дизайне мы часто совершаем такую же ошибку, когда называем разные вещи одинаковыми именами. Они кажутся похожими, но внешность может быть обманчивой. Это плохо сказывается на прозрачности вашей библиотеки компонентов. С точки зрения инклюзивности это может привести к тому, что вы измените нужную семантику и поведение компонента на совершенно противоположные. Пользователи будут ожидать одного поведения, а получат другое.</p>
<p>Классический пример — термин «выпадающий список» (dropdown). В интерфейсах многие вещи «выпадают», включая <code>&lt;option&gt;</code> в <code>&lt;select&gt;</code>, и подменю навигации со списком ссылок, которое раскрывается с помощью JavaScript. Одинаковые названия — разные явления. Некоторые называют это «выпадающее меню» (pull-down menu), но давайте не вдаваться в подробности.</p>
<p>Выпадающие списки, которые состоят из нескольких пунктов, часто называют «меню». Об этом я и хочу поговорить в этой статье. Мы будем делать <em>настоящее</em> меню, но по пути обсудим их не совсем настоящие реализации.</p>
<p>Давайте начнём с вопроса: является ли блок ссылок с картинки, выпадающий вниз из панели навигации, меню?</p>
<img src="https://web-standards.ru/articles/menu-buttons/images/menu-navigation.png" alt="">
<p>Правильный ответ: нет, это не настоящее меню.</p>
<p>То, что навигационная схема состоит из списков или ссылок — давнее соглашение. Оно почти так же давно предписывает, что дополнительная навигация (sub-navigation) должна быть <em>вложенными</em> списками со ссылками. Если бы я удалил стили для компонента, показанного выше, я бы увидел что-то вроде этого списка ссылок, только с Times New Roman и синего цвета.</p>
<pre><code tabindex="0" class="language-md">• [Главная](#)
• [О нас](#)
• [Магазин](#)
    ◦ [Одежда для собак](#)
    ◦ [Вафельницы](#)
    ◦ [Магические шары](#)
• [Контакты](#)
</code></pre>
<p>С точки зрения семантики вложенные списки со ссылками в этом контексте корректны. Системы навигации на самом деле <strong>оглавления</strong>: именно так они устроены. Единственное, что действительно заставляет думать, что перед нами меню — это стилизация вложенных списков и способ их отображения при наведении курсора или при фокусе.</p>
<p>В этом месте часто ошибаются и начинают добавлять семантику из WAI-ARIA: <code>aria-haspopup=&quot;true&quot;</code>, <code>role=&quot;menu&quot;</code>, <code>role=&quot;menuitem&quot;</code> и так далее. Их можно использовать, но в другом контексте. Вот две причины для этого:</p>
<ol>
<li>ARIA-меню предназначены не для навигации, а для десктопных приложений. Представьте себе систему меню для любого из них.</li>
<li>Ссылка верхнего уровня должна быть <em>ссылкой.</em> То есть она не ведёт себя как кнопка меню.</li>
</ol>
<p>Относительно второго пункта: при перемещении по области навигации в подменю ожидаешь, что каждое подменю будет появляться при наведении или при фокусе на ссылке верхнего уровня (например, «Магазин» из примера выше). Это одновременно показывает подменю и размещает ссылки в нём в порядке получения фокуса. С помощью JavaScript можно управлять событиями фокуса (focus) и его потери (blur) и сохранить внешний вид подменю пока это необходимо. При этом те, кто использует для навигации клавиатуру, должны иметь возможность переходить по очереди от одной ссылки каждого уровня к другой.</p>
<p>Кнопки меню, которым задан атрибут <code>aria-haspopup=&quot;true&quot;</code>, так себя не ведут. Они становятся активными <em>по клику</em> и у них нет другой цели кроме показа скрытого меню.</p>
<img src="https://web-standards.ru/articles/menu-buttons/images/menu-expanded.png" alt="">
<p>Как показано на картинке, вне зависимости от того раскрывается меню или скрывается, это будет объявлено благодаря атрибуту <code>aria-expanded</code>. Вам нужно только изменить это состояние при клике, а не при фокусе. Пользователи обычно не ожидают явного изменения состояния при простом событии фокуса. На самом деле в нашей системе навигации состояние не изменяется. Это просто трюк со стилизацией. С точки зрения поведения мы можем перемещаться с помощью клавиши <kbd>Tab</kbd> так, как если бы не было никакого трюка с показом и скрытием элемента.</p>
<h2>Проблема с навигационными подменю</h2>
<p>Навигационные подменю (или для кого-то «выпадающие списки») отлично работают с мышью или с клавиатуры, но не так хороши для касаний. Когда вы в первый раз нажимаете на ссылку верхнего уровня «Магазин» из примера, то сообщаете, что надо открыть подменю и перейти по ссылке.</p>
<p>Здесь есть два возможных варианта решения проблемы:</p>
<ol>
<li>Избежать поведения по умолчанию ссылок верхнего уровня (<code>e.preventDefault()</code>) и написать скрипт для полной поддержки семантики и поведения меню WAI-ARIA.</li>
<li>Убедиться, что каждая страница, на которую ведёт ссылка верхнего уровня меню, имеет оглавление в качестве альтернативы подменю.</li>
</ol>
<p>Первое решение не самое хорошее. Я ранее замечал, что этот тип семантики и поведения нежелателен в данном контексте, где ссылки — это управляемые контролы (subject controls). Кроме того пользователи больше не смогут переходить на страницу верхнего уровня, если она есть.</p>
<h3>Какие устройства — сенсорные?</h3>
<p>Заманчиво думать: «Это не самое хорошее решение, но я применю его только для сенсорных интерфейсов». Проблема в том, как определить, есть ли у устройства сенсорный экран?</p>
<p>Вам точно не стоит относить любой «маленький экран» к разряду «экранов с сенсорным управлением». Работая в одном офисе с людьми, разрабатывающими сенсорные дисплеи для музеев, я могу заверить вас, что самые большие экраны — сенсорные. Не забывайте о ноутбуках с клавиатурой и сенсорным дисплеем.</p>
<p>К тому же многие, но далеко не все устройства небольшого размера являются сенсорными. В инклюзивном дизайне вы не можете позволить себе предполагать.</p>
<p>Второе решение более инклюзивное и надёжное. Это «фолбэк» для пользователей всех устройств. Но я специально взял в кавычки это слово, потому что думаю, что на самом деле постраничные оглавления — это <em>лучший</em> способ обеспечения навигации.</p>
<p>Похоже, получившая премию <a href="https://www.gov.uk/guidance/content-design/organising-and-grouping-content-on-gov-uk">команда Government Digital Services</a> с этим согласится. Вы также могли видеть такие оглавления в Wikipedia.</p>
<img src="https://web-standards.ru/articles/menu-buttons/images/menu-wikipedia.png" alt="">
<h2>Оглавление</h2>
<p>Оглавления — это навигация для связанных между собой страниц или их разделов, которые должны быть семантически похожи на основные области навигации сайта. Для них используются элементы с <code>&lt;nav&gt;</code>, списки и общие подписи к ним.</p>
<pre><code tabindex="0" class="language-html">&lt;nav aria-labelledby=&quot;sections-heading&quot;&gt;
    &lt;h2 id=&quot;sections-heading&quot;&gt;Продукты&lt;/h2&gt;
    &lt;ul&gt;
        &lt;li&gt;&lt;a href=&quot;/products/dog-costumes&quot;&gt;Одежда для собак&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;/products/waffle-irons&quot;&gt;Вафельницы&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;/products/magical-orbs&quot;&gt;Магические шары&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
&lt;/nav&gt;
&lt;!-- Здесь все разделы по порядку --&gt;
</code></pre>
<h3>Примечания</h3>
<ul>
<li>В этом примере мы представляем каждый раздел как отдельную страницу, как если бы это было в выпадающем подменю.</li>
<li>Важно, чтобы каждая страница из «Магазин» имела одинаковую структуру. Поэтому оглавление «Продукты» находится в одном и том же месте. Консистентность улучшает понимание.</li>
<li>Список группирует элементы, а вспомогательные технологии могут установить их количество и объявить о них, например, с помощью синтезированного голоса в скринридерах.</li>
<li>Тегу <code>&lt;nav&gt;</code> заголовок задан с помощью атрибута <code>aria-labelledby</code>. Это означает, что большинство скринридеров объявит «Продукты, навигация» при попадании в область с помощью клавиши <kbd>Tab</kbd>. Также это приведёт к тому, что такая навигация будет разбита скринридерами на отдельные элементы, через которые пользователи смогут переходить к областям страницы напрямую.</li>
</ul>
<h3>Всё на одной странице</h3>
<p>Если вы можете поместить все разделы на одной странице, при этом не сделав её слишком длинной и избежав утомительной прокрутки, то так даже лучше. Просто задайте для каждого раздела якорную ссылку. Например, <code>href=&quot;#waffle-irons&quot;</code> должна вести к <code>id=&quot;waffle-irons&quot;</code>.</p>
<pre><code tabindex="0" class="language-html">&lt;nav aria-labelledby=&quot;sections-heading&quot;&gt;
    &lt;h2 id=&quot;sections-heading&quot;&gt;Продукты&lt;/h2&gt;
    &lt;ul&gt;
        &lt;li&gt;&lt;a href=&quot;#dog-costumes&quot;&gt;Одежда для собак&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;#waffle-irons&quot;&gt;Вафельницы&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;#magical-orbs&quot;&gt;Магические шары&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
&lt;/nav&gt;
&lt;!-- Здесь раздел с одеждой для собак --&gt;
&lt;section id=&quot;waffle-irons&quot; tabindex=&quot;-1&quot;&gt;
    &lt;h2&gt;Вафельницы&lt;/h2&gt;
&lt;/section&gt;
&lt;!-- Здесь раздел с магическими шарами --&gt;
</code></pre>
<p><strong>Примечание</strong>: в некоторых браузерах фокус плохо переносится на отдельные фрагменты страницы. Добавление <code>tabindex=&quot;-1&quot;</code> к нужному фрагменту исправляет это.</p>
<p>В случае, если на сайте много контента, лучше тщательно продумывать информационную архитектуру с оглавлениями меню, чем делать ненадёжную и громоздкую систему выпадающих меню. Так будет легче сделать адаптивную версию страницы с меньшим количеством кода, а также это упростит восприятие её структуры. Там, где системы выпадающих меню скрывают структуру, оглавления обнажают её.</p>
<p>На некоторых сайтах, включая правительственный цифровой сервис <a href="https://www.gov.uk/">gov.uk</a>, есть страницы с указателями (или «темами»), которые представляют собой <em>просто</em> оглавления. Это настолько мощная концепция, что популярный генератор статических сайтов Hugo <a href="https://gohugo.io/templates/lists/">создаёт такие страницы по умолчанию</a>.</p>
<img src="https://web-standards.ru/articles/menu-buttons/images/tree.png" alt="">
<p>Информационная архитектура — это важная часть инклюзивности. Плохо организованный сайт может соответствовать нужным вам техническим требованиям, но при этом будет отталкивать многих пользователей. Особенно это касается тех, у кого есть когнитивные нарушения или у кого мало времени на то, чтобы с чем-то долго разбираться.</p>
<h2>Кнопки навигационного меню</h2>
<p>Пока мы обсуждаем тему фальшивых навигационных меню, было бы упущением с моей стороны не поговорить про кнопки меню. Вы наверняка видели состоящую из трёх линий иконку-гамбургер или «navicon».</p>
<p>Даже с упрощённой информационной архитектурой и одним уровнем навигационных ссылок пространство на маленьких экранах на вес золота. Скрытие навигации за кнопкой означает, что во вьюпорте будет больше места для основного контента.</p>
<p>Кнопка навигации больше всего из того, что мы изучали, похожа на <em>настоящую</em> кнопку меню. Так как она открывает меню по клику, то она должна:</p>
<ol>
<li>Быть кнопкой, а не ссылкой.</li>
<li>Содержать информацию о развёрнутом или свёрнутом состоянии соответствующего меню (которое, строго говоря, представляет собой просто список ссылок).</li>
</ol>
<h3>Прогрессивное улучшение</h3>
<p>Давайте не будем забегать вперёд. Мы должны помнить о прогрессивном улучшении и подумать, как это будет работать без JavaScript.</p>
<p>В изначальном HTML-файле мало что можно сделать с помощью кнопок (кроме кнопок для отправки данных, но они даже близко не связаны с тем, что нам нужно сделать). Может быть, вместо этого, нам следует начать с простой ссылки, которая приведёт нас к навигации?</p>
<pre><code tabindex="0" class="language-html">&lt;a href=&quot;#navigation&quot;&gt;Навигация&lt;/a&gt;
&lt;!-- Тут может быть какой-то контент --&gt;
&lt;nav id=&quot;navigation&quot;&gt;
    &lt;ul&gt;
        &lt;li&gt;&lt;a href=&quot;/&quot;&gt;Главная&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;/about&quot;&gt;О нас&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;/shop&quot;&gt;Магазин&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;/content&quot;&gt;Контент&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
&lt;/nav&gt;
</code></pre>
<p>Нет особой необходимости использовать ссылку, если между ней и навигацией нет большого количества контента. Так как навигация сайта в большинстве случаев должна размещаться наверху страницы, то в этом решении нет необходимости. Так что, действительно, навигационное меню без JavaScript должно быть просто… навигацией.</p>
<pre><code tabindex="0" class="language-html">&lt;nav id=&quot;navigation&quot;&gt;
    &lt;ul&gt;
        &lt;li&gt;&lt;a href=&quot;/&quot;&gt;Главная&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;/about&quot;&gt;О нас&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;/shop&quot;&gt;Магазин&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;/content&quot;&gt;Контент&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
&lt;/nav&gt;
</code></pre>
<p>Вы улучшите его, добавив кнопку, которая в исходном состоянии скрывает навигацию с помощью атрибута <code>hidden</code>:</p>
<pre><code tabindex="0" class="language-html">&lt;nav id=&quot;navigation&quot;&gt;
    &lt;button aria-expanded=&quot;false&quot;&gt;Меню&lt;/button&gt;
    &lt;ul hidden&gt;
        &lt;li&gt;&lt;a href=&quot;/&quot;&gt;Главная&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;/about&quot;&gt;О нас&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;/shop&quot;&gt;Магазин&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;/content&quot;&gt;Контент&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
&lt;/nav&gt;
</code></pre>
<p>Некоторые более старые браузеры, сами знаете какие, не поддерживают <code>hidden</code>, так что не забудьте учесть это в вашем CSS. Селектор ниже решит эту проблему, так как <code>display: none</code> делает то же самое, что и атрибут <code>hidden</code>: скрывает меню от вспомогательных технологий и удаляет ссылку из порядка получения фокуса.</p>
<pre><code tabindex="0" class="language-css">[hidden] {
    display: none;
}
</code></pre>
<p>Делать всё возможное для поддержки старого программного обеспечения — это, конечно, часть инклюзивного дизайна. Некоторый софт невозможно обновить или его разработчики не хотят этого делать.</p>
<h3>Расположение</h3>
<p>Многие неправильно размещают кнопку <em>за пределами</em> области (навигации). Это может привести к тому, что пользователи скринридеров, которые перемещаются по <code>&lt;nav&gt;</code> с помощью горячих клавиш, решат, что область пустая, что не особо им поможет. Со списком, скрытым от скринридеров, они просто столкнулись бы с этим:</p>
<pre><code tabindex="0" class="language-html">&lt;nav id=&quot;navigation&quot;&gt;
&lt;/nav&gt;
</code></pre>
<p>Вот как мы можем переключить состояние:</p>
<pre><code tabindex="0" class="language-js">var navButton = document.querySelector('nav button');
navButton.addEventListener('click', function() {
    let expanded = this.getAttribute('aria-expanded') === 'true' || false;
    this.setAttribute('aria-expanded', !expanded);
    let menu = this.nextElementSibling;
    menu.hidden = !menu.hidden;
});
</code></pre>
<h2>ARIA-controls</h2>
<p>Как я уже писал в <a href="https://web.archive.org/web/20200924080848/https://heydonworks.com/article/aria-controls-is-poop/">«Aria-controls Is Poop»</a>, атрибут <code>aria-controls</code>, который помогает пользователям скринридеров при переходе от контролирующего элемента к контролируемому, поддерживается только в JAWS. Так что на него нельзя положиться.</p>
<p>Без хорошего метода перемещения между элементами, вам нужно убедиться в том, что одно из следующего верно:</p>
<ol>
<li>Первая ссылка раскрывающегося списка — следующая в порядке фокуса после кнопки (как в предыдущем примере кода).</li>
<li>При раскрытии списка на первую ссылку сделан фокус.</li>
</ol>
<p>В нашей ситуации я рекомендовал бы первый вариант. Это намного проще, так как не нужно беспокоиться о перемещении фокуса обратно на кнопку и о том, какие события для этого нужны. Кроме того, сейчас нет ничего, что могло бы предупредить пользователей о том, что фокус будет перемещён в другое место. В настоящих меню, которые мы вскоре обсудим, за это отвечает <code>aria-haspopup=&quot;true&quot;</code>.</p>
<p>Использование <code>aria-controls</code> на самом деле не приносит большого вреда, за исключением того, что делает объявления в скринридере более подробным. Однако некоторым пользователям JAWS это может быть нужно. Вот как будет использоваться атрибут вместе с <code>id</code> для списка:</p>
<pre><code tabindex="0" class="language-html">&lt;nav id=&quot;navigation&quot;&gt;
    &lt;button aria-expanded=&quot;false&quot; aria-controls=&quot;menu-list&quot;&gt;
        Меню
    &lt;/button&gt;
    &lt;ul id=&quot;menu-list&quot; hidden&gt;
        &lt;li&gt;&lt;a href=&quot;/&quot;&gt;На главную&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;/about&quot;&gt;О нас&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;/shop&quot;&gt;Магазин&lt;/a&gt;&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;/content&quot;&gt;Товары&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
&lt;/nav&gt;
</code></pre>
<h2>Меню и роли menuitem</h2>
<p><em>Настоящее</em> меню (с точки зрения WAI-ARIA) должно себя идентифицировать с помощью роли <code>menu</code> (для контейнера) и, обычно, роли <code>menuitem</code> для дочерних элементов (или других ролей для подобных элементов). Эти роли для родителей и детей работают сообща и предоставляют вспомогательным технологиям нужную информацию. Вот как можно расширить список, добавив для него семантику меню:</p>
<pre><code tabindex="0" class="language-html">&lt;ul role=&quot;menu&quot;&gt;
    &lt;li role=&quot;menuitem&quot;&gt;Пункт 1&lt;/li&gt;
    &lt;li role=&quot;menuitem&quot;&gt;Пункт 2&lt;/li&gt;
    &lt;li role=&quot;menuitem&quot;&gt;Пункт 3&lt;/li&gt;
&lt;/ul&gt;
</code></pre>
<p>Поскольку наше навигационное меню начинает вести себя как «настоящее» меню, не должно ли тут быть ролей <code>menuitem</code>?</p>
<p>Краткий ответ — нет. Более подробный ответ: нет, потому что наши элементы списка содержат ссылки, а <a href="https://w3c.github.io/html-aria/#index-aria-menuitem">элементы <code>menuitem</code> не могут иметь интерактивных потомков</a>. То есть они <em>являются</em> контролами в меню.</p>
<p>Мы можем, конечно, удалить семантику списка у элементов <code>&lt;li&gt;</code> с помощью <a href="https://www.w3.org/TR/wai-aria/#presentation"><code>role=&quot;presentation&quot;</code></a> или <code>role=&quot;none&quot;</code> (они эквивалентны друг другу) и добавить для каждой ссылки роль menuitem. Однако, это удалит встроенную в них по умолчанию роль <code>link</code>. Другими словами, в примере ниже скринридерами будет объявлено: «Главная, пункт меню», а <em>не</em>: «Главная, ссылка» или «Главная, пункт меню, ссылка». ARIA-роли просто переопределяют HTML-роли.</p>
<pre><code tabindex="0" class="language-html">&lt;!-- Будет прочитано как «Главная, пункт меню» --&gt;
&lt;li role=&quot;presentation&quot;&gt;
    &lt;a href=&quot;/&quot; role=&quot;menuitem&quot;&gt;Главная&lt;/a&gt;
&lt;/li&gt;
</code></pre>
<p>Мы хотим, чтобы пользователи знали, что они используют ссылку и могут ожидать от неё поведения ссылки, так что пример выше не очень хороший. Как я уже сказал, настоящие меню предназначены для (написанных на JavaScript) приложений.</p>
<p>То, что у нас осталось, это своего рода гибридный компонент, который не совсем настоящее меню, но, по крайней мере, сообщает пользователям, открыт ли список ссылок, благодаря состоянию <code>aria-expanded</code>. Это подходящий паттерн для навигационных меню.</p>
<h3>Примечание. Элемент <code>&lt;select&gt;</code></h3>
<p>Если вы давно знакомы с адаптивным дизайном, то можете вспомнить паттерн, в котором навигация была свёрнута в элемент <code>&lt;select&gt;</code> в небольших вьюпортах.</p>
<img src="https://web-standards.ru/articles/menu-buttons/images/phone.png" alt="">
<p>Как и в случае с <a href="https://inclusive-components.design/toggle-button/">переключателями на основе чекбоксов, которые мы обсуждали</a> <em>(<a href="https://medium.com/web-standards/toggle-buttons-a41388e80974">см. в переводе</a> — прим. переводчика),</em> использование нативного элемента, который ведёт себя не так, как задумано, без дополнительного скрипта — хороший выбор. Особенно с точки зрения эффективности и производительности в случае мобильных устройств. Элементы <code>&lt;select&gt;</code> — это своего рода меню, с семантикой, аналогичной меню, открывающееся кнопкой, которую мы скоро будем создавать.</p>
<p>Однако, так же как и с переключателем, основанном на чекбоксе, мы используем элемент, связанный со вводом данных, а не просто делаем выбор. Это может запутать многих пользователей, тем более, что этот шаблон использует JavaScript, чтобы выбранный элемент <code>&lt;option&gt;</code> вёл себя как ссылка. Неожиданное изменение контекста, которое это вызывает, считается ошибкой согласно критерию <a href="https://www.w3.org/TR/WCAG20/#consistent-behavior-unpredictable-change">3.2.2 On Input (Level A)</a>.</p>
<h2>Настоящие меню</h2>
<p>Теперь, когда мы обсудили ненастоящие меню и квази-меню, пришло время создать <em>настоящее</em> меню, которое открывается и закрывается настоящей кнопкой меню. С этого момента я буду называть вместе кнопку и меню — «кнопкой меню».</p>
<p>Но в каком случае наша кнопка меню может считаться настоящей? Что ж, это будет компонент меню, предназначенный для выбора параметров в приложении, который предусматривает всю нужную семантику и стандартное поведение для такого инструмента.</p>
<p>Как я уже упоминал, эти соглашения пришли из дизайна десктопных приложений. ARIA-атрибуты и управление фокусом с помощью JavaScript необходимы для их полной имитации. Одна из целей ARIA — помочь веб-разработчикам создавать сложные веб-интерфейсы и не нарушать при этом устоявшие паттерны взаимодействия, пришедшие из реального мира.</p>
<p>В этом примере мы представим, что наше приложение — это какая-то игра или викторина. Наша кнопка меню даёт пользователю выбрать уровень сложности. С учётом всей семантики меню выглядит следующим образом:</p>
<pre><code tabindex="0" class="language-html">&lt;button aria-haspopup=&quot;true&quot; aria-expanded=&quot;false&quot;&gt;
    Сложность
    &lt;span aria-hidden=&quot;true&quot;&gt;&amp;#x25be;&lt;/span&gt;
&lt;/button&gt;
&lt;div role=&quot;menu&quot;&gt;
    &lt;button role=&quot;menuitem&quot;&gt;Лёгкая&lt;/button&gt;
    &lt;button role=&quot;menuitem&quot;&gt;Средняя&lt;/button&gt;
    &lt;button role=&quot;menuitem&quot;&gt;Очень высокая&lt;/button&gt;
&lt;/div&gt;
</code></pre>
<h3>Примечания</h3>
<ul>
<li><code>aria-haspopup</code> просто указывает на то, у кнопки есть скрытое меню. Это работает как предупреждение о том, что при нажатии на кнопку пользователь будет перемещён в выпадающее меню (скоро мы рассмотрим поведение фокуса). Его значение не меняется: оно всегда <code>true</code>.</li>
<li>Элемент <code>&lt;span&gt;</code> внутри кнопки содержит символ Юникод с маленьким чёрным перевёрнутым треугольником. Он визуально показывает, что нажатие на кнопку раскроет что-то под ней. Этого <code>aria-haspopup</code> не может показать. Атрибут <code>aria-hidden=&quot;true&quot;</code> не разрешает скринридерам объявлять «перевёрнутый треугольник» или что-то подобное. Благодаря <code>aria-haspopup</code> этого не нужно делать в невизуальном контексте.</li>
<li><code>aria-haspopup</code> дополняет <code>aria-expanded</code>. Атрибут сообщает пользователю, находится ли он в данный момент в открытом (развёрнутом) меню или оно закрыто (свёрнуто), переключаясь между значениями <code>true</code> и <code>false</code>.</li>
<li>Само меню имеет (точно названную) роль <code>menu</code>. Для него нужны потомки с ролями <code>menuitem</code>. Они не обязательно должны быть прямыми потомками элемента <code>menu</code>. В этом примере так сделано для простоты.</li>
</ul>
<h2>Клавиатура и поведение при фокусе</h2>
<p>Когда дело доходит до того, чтобы сделать интерактивные контролы доступными для клавиатуры, лучшее, что вы можете сделать, это использовать правильные элементы. Поскольку здесь используются элементы <code>&lt;button&gt;</code>, то мы можем быть уверены в том, что события по клику будут срабатывать при нажатии клавиш <kbd>Enter</kbd> и <kbd>Space</kbd>, как указано в <a href="https://developer.mozilla.org/en/docs/Web/API/HTMLButtonElement">HTMLButtonElement interface</a>. Также это означает, что мы можем отключить пункты меню, используя связанное с кнопкой свойство <code>disabled</code>.</p>
<p>Есть намного больше способов взаимодействовать с кнопкой с клавиатуры. Вот краткий обзор поведения при фокусе с клавиатуры, которое мы будем реализовывать на основе <a href="https://www.w3.org/TR/wai-aria-practices-1.1/#menubutton">WAI-ARIA Authoring Practices 1.1</a>:</p>
<ul>
<li><strong><kbd>Enter</kbd>, <kbd>Space</kbd> или <kbd>↓</kbd> на кнопке меню</strong> — открывает меню.</li>
<li><strong><kbd>↓</kbd> на пункте меню</strong> — перемещает фокус к следующему пункту меню или к первому, когда вы дошли до последнего пункта.</li>
<li><strong><kbd>↑</kbd> на пункте меню</strong> — перемещает фокус к предыдущему пункту меню или к последнему, если вы находитесь на первом.</li>
<li><strong><kbd>↑</kbd> на кнопке меню</strong> — закрывает меню, если оно открыто.</li>
<li><strong><kbd>Esc</kbd> на пункте меню</strong> — закрывает меню и перемещает фокус на кнопку меню.</li>
</ul>
<p>Преимущество перемещения фокуса между пунктами меню с помощью клавиш со стрелками состоит в том, что за <kbd>Tab</kbd> сохраняется функция выхода из меню. На практике это означает, что пользователям не нужно проходить через каждый пункт меню, чтобы выйти из меню. Это значительно улучшает юзабилити, особенно в меню, где много пунктов.</p>
<p>Добавление <code>tabindex=&quot;-1&quot;</code> к пунктам меню делает их недоступными для фокуса с помощью <kbd>Tab</kbd>, но сохраняет возможность фокуса на элементах при нажатии на клавиши со стрелками.</p>
<pre><code tabindex="0" class="language-html">&lt;button aria-haspopup=&quot;true&quot; aria-expanded=&quot;false&quot;&gt;
    Сложность
    &lt;span aria-hidden=&quot;true&quot;&gt;&amp;#x25be;&lt;/span&gt;
&lt;/button&gt;
&lt;div role=&quot;menu&quot;&gt;
    &lt;button role=&quot;menuitem&quot; tabindex=&quot;-1&quot;&gt;Лёгкая&lt;/button&gt;
    &lt;button role=&quot;menuitem&quot; tabindex=&quot;-1&quot;&gt;Средняя&lt;/button&gt;
    &lt;button role=&quot;menuitem&quot; tabindex=&quot;-1&quot;&gt;Очень высокая&lt;/button&gt;
&lt;/div&gt;
</code></pre>
<h2>Метод <code>open</code></h2>
<p>Мы можем создать методы для обработки различных событий как часть продуманного дизайна API.</p>
<p>Например, метод <code>open</code> нужен для переключения значения <code>aria-expanded</code> на <code>true</code>, изменить значение меню <code>hidden</code> на <code>false</code> и сделать фокус на первом элементе меню с <code>menuitem</code>, который не скрыт:</p>
<pre><code tabindex="0" class="language-js">MenuButton.prototype.open = function () {
    this.button.setAttribute('aria-expanded', true);
    this.menu.hidden = false;
    this.menu.querySelector(':not([disabled])').focus();

    return this;
}
</code></pre>
<p>Мы можем исполнить этот метод, когда пользователь нажимает клавишу вниз на кнопке меню, на которой сделан фокус:</p>
<pre><code tabindex="0" class="language-js">this.button.addEventListener('keydown', function (e) {
    if (e.keyCode === 40) {
        this.open();
    }
}.bind(this));
</code></pre>
<p>Кроме того, разработчик, использующий этот скрипт, сможет теперь программно открывать меню:</p>
<pre><code tabindex="0" class="language-js">exampleMenuButton = new MenuButton(
    document.querySelector('[aria-haspopup]')
);
exampleMenuButton.open();
</code></pre>
<h3>Примечание. Хак с чекбоксом</h3>
<p>Если вам не нужен JavaScript, то лучше не использовать его настолько, насколько это возможно. Использование третьей технологии поверх HTML и CSS всегда усложняет систему и приводит к появлению в ней слабых мест. Однако не все компоненты можно создать без использования JavaScript.</p>
<p>В случае с кнопками меню желание сделать их «работающими без JavaScript» привело к появлению так называемого хака с чекбоксом. В нём состояние <code>checked</code> (или <code>unchecked</code>) скрытого чекбокса используется для переключения видимости элемента меню с помощью CSS.</p>
<pre><code tabindex="0" class="language-css">/* Меню закрыто */
[type=&quot;checkbox&quot;] + [role=&quot;menu&quot;] {
    display: none;
}

/* Меню открыто */
[type=&quot;checkbox&quot;]:checked + [role=&quot;menu&quot;] {
    display: block;
}
</code></pre>
<p>Для пользователей скринридеров роль чекбокса и состояние <code>checked</code> бессмысленны в этом контексте. Это можно частично исправить, добавив <code>role=&quot;button&quot;</code> для чекбокса.</p>
<pre><code tabindex="0" class="language-html">&lt;input type=&quot;checkbox&quot; role=&quot;button&quot; aria-haspopup=&quot;true&quot; id=&quot;toggle&quot;&gt;
</code></pre>
<p>К сожалению, это отменяет взаимодействие со встроенным состоянием <code>checked</code>, лишая нас обратной связи о состоянии элемента без JavaScript (что плохо, хотя в этом контексте это было бы так же связано с состоянием <code>checked</code>).</p>
<p>Но это <em>возможно</em> имитировать с помощью <code>aria-expanded</code>. Нам просто нужно добавить наш лейбл к двум элементам со <code>&lt;span&gt;</code>, как показано ниже.</p>
<pre><code tabindex="0" class="language-html">&lt;input type=&quot;checkbox&quot; role=&quot;button&quot; aria-haspopup=&quot;true&quot; id=&quot;toggle&quot; class=&quot;vh&quot;&gt;
&lt;label for=&quot;toggle&quot; data-opens-menu&gt;
    Сложность
    &lt;span class=&quot;vh expanded-text&quot;&gt;развёрнуто&lt;/span&gt;
    &lt;span class=&quot;vh collapsed-text&quot;&gt;свёрнуто&lt;/span&gt;
    &lt;span aria-hidden=&quot;true&quot;&gt;&amp;#x25be;&lt;/span&gt;
&lt;/label&gt;
</code></pre>
<p>Оба этих элемента визуально скрыты благодаря <a href="http://a11yproject.com/posts/how-to-hide-content/">классу <code>visually-hidden</code></a>, но, в зависимости от того в каком состоянии мы находимся, только один из них скрыт от скринридера. То есть только одному задано свойство <code>display: none</code> и это определяется установленным (но не объявляемым) состоянием <code>checked</code>:</p>
<pre><code tabindex="0" class="language-css">/* Класс, который скрывает визуально элементы со &lt;span&gt; */
.vh {
    position: absolute !important;
    clip: rect(1px, 1px, 1px, 1px);
    padding:0 !important;
    border:0 !important;
    height: 1px !important;
    width: 1px !important;
    overflow: hidden;
}

/* Отобразить правильное состояние для скринридеров
    с помощью текущего состояния элемента */
[type=&quot;checkbox&quot;]:checked + label .expanded-text {
    display: inline;
}

[type=&quot;checkbox&quot;]:checked + label .collapsed-text {
    display: none;
}

[type=&quot;checkbox&quot;]:not(:checked) + label .expanded-text {
    display: none;
}

[type=&quot;checkbox&quot;]:not(:checked) + label .collapsed-text {
    display: inline;
}
</code></pre>
<p>Это разумное решение, но работа над нашей кнопкой меню всё ещё не закончена. Ожидаемое поведение фокуса, которое мы обсудили, просто не может быть реализовано без JavaScript.</p>
<p>Такое поведение принято и предсказуемо, оно делает кнопку более удобной. Однако, если вам действительно нужно сделать кнопку меню без JavaScript, то это самый подходящий вариант. Учитывая то, что урезанная кнопка навигационного меню, которую я рассмотрел ранее, предлагает контент меню, который <em>не</em> зависит от самого JavaScript (то есть ссылки), этот подход может быть подходящим вариантом.</p>
<p>Забавы ради, <a href="https://codepen.io/heydon/pen/afdeffcc8a349ab8138e32573ec85cd3">вот CodePen c кнопкой меню без JavaScript</a>.</p>
<iframe src="https://codepen.io/heydon/embed/vmKVJK"></iframe>
<p><strong>Примечание</strong>: меню открывает только пробел.</p>
<h2>Событие <code>choose</code></h2>
<p>Исполнение некоторых методов должно вызывать события, чтобы мы могли настроить обработчики событий. Например, мы можем транслировать событие <code>choose</code>, когда пользователь кликает по пункту меню. Мы можем установить это с помощью <code>CustomEvent</code>, которое даёт нам возможность передавать аргумент в свойство события <code>detail</code>. В этом случае аргумент (<code>choice</code>) будет узлом DOM выбранного пункта меню.</p>
<pre><code tabindex="0" class="language-js">MenuButton.prototype.choose = function (choice) {
    // Определить событие 'choose'
    var chooseEvent = new CustomEvent('choose', {
        detail: {
            choice: choice
        }
    });
    // Передать событие (Dispatch the event)
    this.button.dispatchEvent(chooseEvent);

    return this;
}
</code></pre>
<p>Есть много всего, что мы можем сделать с помощью этого механизма. Возможно, у нас есть live region с <code>id</code> со значением <code>menuFeedback</code>:</p>
<pre><code tabindex="0" class="language-html">&lt;div role=&quot;alert&quot; id=&quot;menuFeedback&quot;&gt;&lt;/div&gt;
</code></pre>
<p>Теперь мы можем настроить обработчик событий и заполнить live region информацией, скрытой внутри события:</p>
<pre><code tabindex="0" class="language-js">exampleMenuButton.addEventListener('choose', function (e) {
    // Получить текст узла (label)
    var choiceLabel = e.details.choice.textContent;

    // Получить узел live region
    var liveRegion = document.getElementById('menuFeedback');

    // Заполнить live region
    liveRegion.textContent = `Your difficulty level is ${choiceLabel}`;
}):
</code></pre>
<figure>
    <img src="https://web-standards.ru/articles/menu-buttons/images/menu-selected.png" alt="">
    <figcaption>
        Когда пользователь выбирает пункт, меню закрывается и фокус перемещается на кнопку меню. Для пользователей важно возвращаться к такому элементу после закрытия меню.
    </figcaption>
</figure>
<p>При выборе пункта меню пользователь скринридера услышит: <em>«Вы выбрали [название пункта меню]».</em> Изменения контента live region (которому задан здесь атрибут <code>role=&quot;alert&quot;</code>) объявляются скринридерами при каждом его обновлении. Live region не является обязательным, но это пример того, что может произойти в интерфейсе, когда пользователь сделал выбор в меню.</p>
<h2>Сохранение выбора</h2>
<p>Не все пункты меню нужны для выбора сохранения настроек. Многие из них ведут себя как стандартные кнопки, которые при нажатии приводят к тому, что что-то происходит в интерфейсе. Однако, в случае с нашей кнопкой меню со сложностью, мы хотели бы указать текущую настройку сложности. То есть ту, которая выбрана последней.</p>
<p>Атрибут <code>aria-checked=&quot;true&quot;</code> работает для элементов, которым вместо <code>menuitem</code> задана роль <code>menuitemradio</code>. Расширенная разметка со вторым выбранным элементом <em>(установленным)</em> выглядит так:</p>
<pre><code tabindex="0" class="language-html">&lt;button aria-haspopup=&quot;true&quot; aria-expanded=&quot;false&quot;&gt;
    Сложность
    &lt;span aria-hidden=&quot;true&quot;&gt;&amp;#x25be;&lt;/span&gt;
&lt;/button&gt;
&lt;div role=&quot;menu&quot;&gt;
    &lt;button role=&quot;menuitemradio&quot; tabindex=&quot;-1&quot;&gt;Лёгкая&lt;/button&gt;
    &lt;button role=&quot;menuitemradio&quot; aria-checked=&quot;true&quot; tabindex=&quot;-1&quot;&gt;Средняя&lt;/button&gt;
    &lt;button role=&quot;menuitemradio&quot; tabindex=&quot;-1&quot;&gt;Очень высокая&lt;/button&gt;
&lt;/div&gt;
</code></pre>
<p>В нативных меню на многих платформах выбранные элементы отмечаются с помощью галочек. Мы можем это без проблем сделать, используя чуть больше CSS:</p>
<pre><code tabindex="0" class="language-css">[role=&quot;menuitemradio&quot;][aria-checked=&quot;true&quot;]::before {
    content: '\2713\0020';
}
</code></pre>
<p>При перемещении по меню с запущенным скринридером при фокусе на этом выбранном элементе он объявит что-то вроде: <em>«Галочка, средний, пункт меню, выбрано».</em></p>
<p>Поведение при открытии меню с выбранным <code>menuitemradio</code> немного отличается. Вместо того, чтобы сделать фокус на первом (включённом) элементе в меню, фокус устанавливается на <em>выбранном</em> элементе.</p>
<img src="https://web-standards.ru/articles/menu-buttons/images/menu-focused.png" alt="">
<p>Какая польза от такого поведения? Пользователь (любой) помнит о своём предыдущем выборе. В меню с многочисленными параметрами (например, с масштабом), люди, работающие на клавиатуре, находятся в оптимальном положении для изменения настроек.</p>
<h2>Использование кнопки меню с помощью скринридера</h2>
<p>В этом видео я покажу вам как использовать кнопку меню в VoiceOver и Chrome. В примере используются элементы с ролью <code>menuitemradio</code>, атрибутом <code>aria-checked</code> и обсуждается поведение кнопки при фокусе. Таким образом ведут себя и другие популярные скринридеры.</p>
<iframe src="https://www.youtube.com/embed/Aw_HMHdId88" allowfullscreen></iframe>
<h3>Расшифровка</h3>
<p><strong>Хейдон:</strong> Это кнопка меню со сложностью и я собираюсь протестировать её на VoiceOver в Chrome. Для начала я сделаю фокус на самой кнопке.</p>
<p><strong>VoiceOver:</strong> Сложность, всплывающая кнопка.</p>
<p><strong>Хейдон:</strong> Сначала вы услышите «Сложность» — это подпись. Затем «всплывающая кнопка», что означает её состояние, что ей задано <code>area-expanded=&quot;false&quot;</code> и что она всплывающая, потому что для неё установлено значение <code>true</code> в атрибуте <code>aria-haspopup</code>.
Сейчас я открою меню с помощью клавиши со стрелкой вниз.</p>
<p><strong>VoiceOver:</strong> Галочка, очень сложно, пункт меню, выбран, меню, пять пунктов.</p>
<p><strong>Хейдон:</strong> Здесь много информации. Для начала «Галочка, очень сложная». Это подпись. Я использую псевдоэлемент для того, чтобы добавить галочку. «Пункт меню», потому что в этом случае это пункт меню с ролью <code>menuitemradio</code>. «Выбран», потому что задан атрибут <code>aria-checked</code> со значением <code>checked</code>. И также вы слышите, что в меню пять пунктов. Это потому, что этот пункт меню вместе с другими находится внутри элемента, которому задана роль <code>menu</code>.
Сейчас я нажму на кнопку со стрелкой вниз, чтобы сделать фокус на пункте «Очень высокая».</p>
<p><strong>VoiceOver:</strong> Очень высокая, пункт меню.</p>
<p><strong>Хейдон:</strong> На этот раз вы услышали только подпись и роль элемента, потому что он очевидно не выбран. Для того, чтобы выбрать этот пункт, я нажимаю кнопку <kbd>Enter</kbd>.</p>
<p><strong>VoiceOver:</strong> Сложность, всплывающая кнопка.</p>
<p><strong>Хейдон:</strong> Это закрывает меню и перемещает фокус на саму кнопку меню «Сложность». Итак, вы вернулись туда, откуда начали.</p>
<h2>Инклюзивная кнопка меню на Github</h2>
<p>Я и <a href="https://twitter.com/HugoGiraudel">Хьюго Жирадель</a> вместе работали над созданием компонента кнопки меню с фичами API, которые я описал, и ещё многим другим. Вы должны поблагодарить Хьюго за многие из этих фич, так как они основывались на работе над a11y-dialog — доступным модальным окном. Он есть на <a href="https://github.com/Heydon/inclusive-menu-button">Гитхабе</a> и в npm.</p>
<pre><code tabindex="0" class="language-sh">npm i inclusive-menu-button --save
</code></pre>
<p>Кроме того, Хьюго сделал специально для вас <a href="https://github.com/HugoGiraudel/react-menu-button">версию кнопки на React</a>.</p>
<h2>Чеклист</h2>
<ul>
<li>Не используйте семантику ARIA для меню в случае с системами навигационных меню.</li>
<li>На сайтах с большим количеством контента не скрывайте структуру в выпадающих меню.</li>
<li>Используйте <code>aria-expanded</code> у кнопок для того, чтобы сообщить о том, что навигационное меню открыто или закрыто.</li>
<li>Убедитесь, что навигационное меню следующее в порядке фокуса после кнопки, которая его открывает и закрывает.</li>
<li>Никогда не жертвуйте юзабилити ради решений без JavaScript. Это заносчиво.</li>
</ul>

                    ]]></description><pubDate>Tue, 21 May 2019 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/menu-buttons/</guid></item><item><title>Всё, что нужно знать про ARIA Live Regions</title><link>https://web-standards.ru/articles/aria-live-regions/</link><description><![CDATA[
                        <p>Как сделать изменение контента доступным.</p>
<p>Если у вас есть динамически изменяющаяся часть страницы и вы задумались о её доступности, то может возникнуть закономерный вопрос о том, как же это сделать. Это могут быть:</p>
<ul>
<li>Чаты;</li>
<li>Прогресс-бары и таймеры;</li>
<li>Виджеты с новостями и погодой;</li>
<li>Разные ошибки и оповещения: новое сообщение, лайк, подписка;</li>
<li>Тикеры (биржевая информация о котировках акций, индексов, облигаций), курсы валют;</li>
<li>Спортивная статистика и многое другое.</li>
</ul>
<p>Раньше вспомогательные технологии (в том числе скринридеры) не умели правильно их обрабатывать. Пользователи не могли узнать о том, появилась ли какая-то ошибка или новые данные до тех пор, пока не возвращались в предыдущий блок или не доходили до конца страницы. Теперь проблему доступности динамически изменяющегося контента страниц можно решить с помощью ARIA.</p>
<p>Если вы не знакомы с этой аббревиатурой, то WAI-ARIA <em>(англ. Web Accessibility Initiative — Accessible Rich Internet Applications)</em> или просто ARIA — это стандарт, состоящий из набора специальных ролей и атрибутов, которые добавляются в разметку и расширяют или дополняют функции стандартных HTML-элементов.</p>
<p>Всё, что нам нужно — это сделать часть страницы, в которой происходят изменения. В терминологии ARIA, это называется «live region» или «интерактивной областью». В стандарте можно найти <a href="https://www.w3.org/TR/wai-aria-1.2/#dfn-live-region">такое определение</a>:</p>
<blockquote>
<p><strong>Live Region</strong> — это воспринимаемые области страниц, которые обычно обновляются в результате внешнего события, когда пользователь сделал фокус где-то в другом месте. Эти области не всегда обновляются из-за взаимодействия пользователей с ними. Такая практика стала обычным делом в результате активного использования Ajax.</p>
</blockquote>
<p>Таким образом, главная цель таких областей — сообщать скринридерам как правильно обрабатывать изменения контента, которые не обязательно зависят от пользователей.</p>
<p>Чтобы сделать интерактивную область на странице, нам нужно просто добавить к любому родительскому элементу ARIA-атрибут <code>aria-live=&quot;&quot;</code> или специальную ARIA-роль. Тогда изменения всех его дочерних элементов станут доступными для скринридеров. Теперь они будут знать, как обрабатывать обновления содержимого таких элементов.</p>
<p>В ARIA есть несколько таких ролей и атрибутов. Давайте сначала поговорим о ролях.</p>
<h2>Роли интерактивных областей</h2>
<p>ARIA-ролей, которые делают часть страницы интерактивной областью, не так много. Используются они вот так: <code>role=&quot;alert&quot;</code> Вот их полный список:</p>
<ul>
<li><code>alert</code>;</li>
<li><code>status</code>;</li>
<li><code>log</code>;</li>
<li><code>timer</code>;</li>
<li><code>marquee</code>.</li>
</ul>
<p>Разберёмся с каждой ролью по порядку.</p>
<h3>Alert</h3>
<p>Тип интерактивной области, который содержит важную в определённый момент времени информацию. Это может быть сообщение об ошибке, предупреждение, которое появляется на экране после каких-то действий пользователя или без его участия (внезапная ошибка на стороне сервера). Такое сообщение может быть как текстовым, так и звуковым.</p>
<p>Рассмотрим простой пример, в котором мы предупреждаем пользователей о чём-то <em>действительно</em> важном:</p>
<pre><code tabindex="0" class="language-html">&lt;div class=&quot;warning&quot; role=&quot;alert&quot;&gt;
    Вы слишком долго смотрели в бездну,
    и теперь бездна смотрит в вас.
&lt;/div&gt;
</code></pre>
<p>Скринридер моментально объявит его в момент появления и прервёт другое объявление, если оно было.</p>
<p>👉 Для максимальной совместимости используйте <code>role=&quot;alert&quot;</code> вместе с атрибутом <code>aria-live=&quot;assertive&quot;</code>. При этом такой элемент всё ещё может быть неправильно объявлен VoiceOver на iOS. Внесём небольшие изменения в наш пример:</p>
<pre><code tabindex="0" class="language-html">&lt;div class=&quot;warning&quot; role=&quot;alert&quot; aria-live=&quot;assertive&quot;&gt;
    Вы слишком долго смотрели в бездну,
    и теперь бездна смотрит в вас.
&lt;/div&gt;
</code></pre>
<p>Получается, что мы продублировали уже встроенное в <code>role=&quot;alert&quot;</code> поведение с помощью значения <code>assertive</code> атрибута <code>aria-live</code>. Последнее как раз подсказывает скринридерам, что объявить об изменениях нужно немедленно.</p>
<h3>Status</h3>
<p>Область с такой ролью содержит дополнительную информацию, которая не особо важна и описывает состояние изменений (status bar). Это может быть информация о том, что действие пользователя успешно или наоборот, что требуется подождать завершения какого-то процесса или где-то есть ошибка. Например, такую роль можно задать сообщению об успешном автосохранении текста или использовать при валидации полей в форме регистрации.</p>
<p>Кстати, в скринридерах есть специальная команда, которая помогает узнать пользователям о статусе. В NVDA она вызывается сочетанием клавиш <kbd>Ins End</kbd>, а в JAWS — <kbd>Ins 3</kbd>.</p>
<p>В примере мы сообщаем пользователям о том, что изменения сохранены:</p>
<pre><code tabindex="0" class="language-html">&lt;div class=&quot;status-message&quot; role=&quot;status&quot;&gt;
    Мы сохранили ваши изменения автоматически,
    не благодарите.
&lt;/div&gt;
</code></pre>
<p>Скринридер объявит это с паузой, а не сразу же, как в случае с <code>role=&quot;alert&quot;</code>.</p>
<p>👉 В <code>role=&quot;status&quot;</code> встроено поведение атрибута <code>aria-live=&quot;polite&quot;</code>. Для максимальной совместимости их рекомендуется использовать вместе. Поэтому пример выше выглядит теперь так:</p>
<pre><code tabindex="0" class="language-html">&lt;div class=&quot;status-message&quot; role=&quot;status&quot; aria-live=&quot;polite&quot;&gt;
    Мы сохранили ваши изменения автоматически,
    не благодарите.
&lt;/div&gt;
</code></pre>
<p>Скринридер сообщит об успешном автосохранении с паузой и не будет прерывать другие объявления.</p>
<h3>Log</h3>
<p>Тип интерактивной области, в которой содержатся логи. Например, история сообщений из чатов, список ошибок и тому подобное. Для логов имеет важное значение последовательность, в которой появляется новая информация. Вспомните журнал событий в Windows.</p>
<p>В этом примере показано обновление контента в чате. Когда пользователь вводит сообщение в текстовое поле, то оно добавляется в конец переписки.</p>
<pre><code tabindex="0" class="language-html">&lt;div role=&quot;log&quot;&gt;
    &lt;h4&gt;История сообщений&lt;/h4&gt;
    &lt;ul&gt;
        &lt;li&gt;
            Одолжишь своего вельш-корги-кардигана
            до понедельника? Очень нужно.
        &lt;/li&gt;
    &lt;/ul&gt;
&lt;/div&gt;
</code></pre>
<p>Теперь скринридер объявляет о новых комментариях после того, как пользователь перестал набирать или отправлять сообщение.</p>
<p>👉 <code>role=&quot;log&quot;</code> на всякий случай лучше сочетать с атрибутом <code>aria-live=&quot;polite&quot;</code>:</p>
<pre><code tabindex="0" class="language-html">&lt;div role=&quot;log&quot; aria-live=&quot;polite&quot;&gt;
    &lt;h4&gt;История сообщений&lt;/h4&gt;
    &lt;ul&gt;
        &lt;li&gt;
            Одолжишь своего вельш-корги-кардигана
            до понедельника? Очень нужно.
        &lt;/li&gt;
        &lt;li&gt;Тебя снова взломали?&lt;/li&gt;
    &lt;/ul&gt;
&lt;/div&gt;
</code></pre>
<p>В этом случае все изменения будут наверняка объявляться с паузой и не прерывать другие более важные изменения.</p>
<h3>Marquee</h3>
<p>В такой области содержится информация, которая быстро изменяется. Эта роль похожа на <code>log</code>, но в этом случае последовательность обновления информации не имеет значения. Простой пример, где может пригодиться <code>role=&quot;marquee&quot;</code> — тикеры и курсы валют.</p>
<p>В этом примере мы добавляем <code>role=&quot;marquee&quot;</code> для блока с информацией о курсах валют:</p>
<pre><code tabindex="0" class="language-html">&lt;ul role=&quot;marquee&quot;&gt;
    &lt;li&gt;¥ 9999,56 ₽ за юань&lt;/li&gt;
    &lt;li&gt;F 100000000000,32 ₽ за фронтендкоин&lt;/li&gt;
&lt;/ul&gt;
</code></pre>
<p>Скринридер будет объявлять об изменениях в этом блоке тогда, когда пользователь сделает фокус на нём. Курсы валют часто изменяются, поэтому постоянные объявления об этом будут только раздражать пользователей.</p>
<p>👉 <code>role=&quot;marquee&quot;</code> стоит использовать вместе с атрибутом <code>aria-live=&quot;off&quot;</code>:</p>
<pre><code tabindex="0" class="language-html">&lt;ul role=&quot;marquee&quot; aria-live=&quot;off&quot;&gt;
    &lt;li&gt;¥ 9999,56 ₽ за юань&lt;/li&gt;
    &lt;li&gt;F 100000000000,32 ₽ за фронтендкоин&lt;/li&gt;
&lt;/ul&gt;
</code></pre>
<p>Мы просто продублировали поведение <code>role=&quot;marquee&quot;</code> по умолчанию для максимальной совместимости.</p>
<h3>Timer</h3>
<p>Эта роль нужна для тех областей, в которых содержатся счётчики, отсчитывающие время в обычном и обратном порядке. Например, таймер обратного отсчёта, часы или секундомер.</p>
<pre><code tabindex="0" class="language-html">&lt;div role=&quot;timer&quot;&gt;
    &lt;!-- Тут стремительно утекает время --&gt;
&lt;/div&gt;
</code></pre>
<p>В эту роль по умолчанию встроено поведение, при котором скринридер не будет объявлять об изменениях, произошедших с таймером, и пользователь узнает о них только при фокусе на нём.</p>
<p>👉 Элементу с <code>role=&quot;timer&quot;</code> стоит также задать атрибут <code>aria-live=&quot;off&quot;</code> для полной совместимости со всеми вспомогательными устройствами и браузерами:</p>
<pre><code tabindex="0" class="language-html">&lt;div role=&quot;timer&quot; aria-live=&quot;off&quot;&gt;
    &lt;!-- Тут стремительно утекает время --&gt;
&lt;/div&gt;
</code></pre>
<p>Если нужно, чтобы скринридер объявлял об изменениях через определённый интервал времени, то сделать это можно при помощи JavaScript. Нам нужно переключать <code>aria-live=&quot;off&quot;</code> на <code>aria-live=&quot;polite&quot;</code> через нужный промежуток времени, например, 60 минут.</p>
<h2>Атрибуты интерактивных областей</h2>
<p>Теперь поговорим об атрибутах, которые делают любую область страницы интерактивной. Их всего четыре:</p>
<ul>
<li><code>aria-live</code>;</li>
<li><code>aria-atomic</code>;</li>
<li><code>aria-relevant</code>;</li>
<li><code>aria-busy</code>.</li>
</ul>
<p>Рассмотрим каждый из них.</p>
<h3>Aria-live</h3>
<p>Этот атрибут используется для определения важности изменений, которые произошли с элементами.</p>
<p>То есть значения данного атрибута отражают то, насколько срочно и быстро вспомогательным технологиям нужно сообщить пользователям об этих изменениях. У атрибута есть три значения: <code>off</code>, <code>polite</code> и <code>assertive</code>.</p>
<ul>
<li><code>off</code> (значение по умолчанию) — указывает на низший приоритет, поэтому такие изменения не объявляются. Это поведение встроено в элементы с <code>role=&quot;marquee&quot;</code> и <code>role=&quot;timer&quot;</code>. Его можно задавать тем областям, которые не важны, или слишком быстро изменяются.</li>
<li><code>polite</code> — обозначает низкий уровень приоритета. Используется в случаях, когда в области происходят изменения, которые вспомогательным технологиям не нужно объявлять моментально. Скринридеры делают перед таким объявлением паузу, не прерывают текущие задачи и ждут, пока пользователь перестанет взаимодействовать с интерфейсом. Так себя ведут элементы с <code>role=&quot;status&quot;</code> и <code>role=&quot;log&quot;</code>. Подходит для оповещений о новых сообщениях, лайках, автосохранении и тому подобном.</li>
<li><code>assertive</code> — указывает на наивысший уровень приоритета. О таких изменениях будет объявлено сразу же, а изменения с более низким приоритетом встанут в очередь и будут объявлены позже. Так себя ведут элементы с <code>role=&quot;alert&quot;</code>, так что этот атрибут можно использовать для сообщений о важных изменениях, например, о серверной ошибке или о том, что данные не сохранились. Спецификация не рекомендует использовать это значение, когда нет необходимости сразу же сообщать пользователям об изменениях.</li>
</ul>
<p>Приведу пару простых примеров использования <code>aria-live=&quot;polite&quot;</code> и <code>aria-live=&quot;assertive&quot;</code>.</p>
<p>В этом примере с <code>aria-live=&quot;polite&quot;</code> при нажатии на кнопку с помощью скрипта меняется название блюда в параграфе.</p>
<pre><code tabindex="0" class="language-html">&lt;p aria-live=&quot;polite&quot;&gt;Моё любимое блюдо
    &lt;span id=&quot;food&quot;&gt;лютефиск&lt;/span&gt;.
&lt;/p&gt;

&lt;button type=&quot;button&quot;&gt;Следующее блюдо&lt;/button&gt;
</code></pre>
<p>Скринридер сделает перед объявлением паузу.</p>
<p>А здесь у нас есть форма с несколькими настройками и кнопкой сохранения. Если изменения не сохранились, то должно появляться сообщение об этом. Зададим ему <code>aria-live=&quot;assertive&quot;</code>:</p>
<pre><code tabindex="0" class="language-html">&lt;form&gt;
    &lt;p&gt;
        &lt;label for=&quot;devil-fruit&quot;&gt;Любимый дьявольский фрукт&lt;/label&gt;
        &lt;input type=&quot;text&quot; id=&quot;devil-fruit&quot;&gt;
    &lt;/p&gt;
    …
    &lt;button type=&quot;submit&quot;&gt;Сохранить настройки&lt;/button&gt;
&lt;/form&gt;

&lt;div class=&quot;alert-window&quot; role=&quot;alert&quot; aria-live=&quot;assertive&quot;&gt;
    Ваши настройки не сохранились,
    попробуйте ещё раз, йо-хо-хо!
&lt;/div&gt;
</code></pre>
<p>Здесь скринридер сделает объявление немедленно.</p>
<p>❗ В стандарте WAI-ARIA также указано, что в некоторых случаях вспомогательные технологии могут переопределять значения атрибута <code>aria-live</code> и объявлять о каких-то изменениях моментально.</p>
<h3>Aria-atomic</h3>
<p>Этот атрибут необязательный и влияет на то, в каком объёме вспомогательные технологии объявят об изменениях: это будет весь контент целиком или только его изменившаяся часть.</p>
<p>У атрибута есть всего два значения — <code>false</code> и <code>true</code>.</p>
<ul>
<li><code>false</code> (значение по умолчанию) — значение, при котором вспомогательные технологии сообщат только об изменениях.</li>
<li><code>true</code> — при этом значении объявляется весь контент, включая изменившуюся часть.</li>
</ul>
<p>При выборе нужного значения для <code>aria-atomic=&quot;&quot;</code> надо понять, важен ли для понимания изменений контекст. В большинстве случаев достаточно оставить значение по умолчанию.</p>
<p>В этом примере важно сохранить контекст, так что можно использовать <code>aria-atomic=&quot;true&quot;</code>:</p>
<pre><code tabindex="0" class="language-html">&lt;p aria-live=&quot;polite&quot; aria-atomic=&quot;true&quot;&gt;Моё любимое блюдо
    &lt;span id=&quot;food&quot;&gt;лютефиск&lt;/span&gt;.
&lt;/p&gt;

&lt;button type=&quot;button&quot;&gt;Следующее блюдо&lt;/button&gt;
</code></pre>
<p>Теперь скринридер будет зачитывать всё предложение целиком, а не только ту часть, которая изменяется после нажатия на кнопку.</p>
<h3>Aria-relevant</h3>
<p>Цель этого атрибута — сообщить вспомогательным технологиям о том, какие именно изменения произошли на странице и, соответственно, в дереве доступности. Это может быть удаление старого или добавление нового контента. Атрибут необязательный.</p>
<p><code>aria-relevant=&quot;&quot;</code> может содержать одно или несколько значений, разделённых пробелом.</p>
<ul>
<li><code>additions</code> — на страницу добавлена новая информация.</li>
<li><code>removals</code> — информация удалена.</li>
<li><code>text</code> — добавлен новый текст или эквивалентная ему информация, например, новое содержимое атрибута <code>alt</code>.</li>
<li><code>additions text</code> <strong>(значение по умолчанию)</strong> указывает на то, что текст был изменён и появилась новая информация на странице.</li>
<li><code>all</code> — все возможные значения. Эквивалентно значению <code>&quot;additions removals text&quot;</code>.</li>
</ul>
<p>На самом деле реальных сценариев использования этого атрибута мало. Он либо <a href="https://github.com/w3c/aria/issues/712">не работает во многих браузерах и скринридерах</a>, либо его <a href="https://medium.com/dev-channel/why-authors-should-avoid-aria-relevant-5d3164fab1e3">советуют вообще не использовать</a> и прибегать к альтернативным методам.</p>
<p>Самый реалистичный сценарий его использования — это список друзей. Когда какой-то друг вышел из сети и больше не активен, то мы можем использовать этот атрибут, чтобы сообщить пользователю об этом. Нам нужно задать для списка <code>aria-relevant=&quot;all&quot;</code>. Тогда некоторые скринридеры объявят об удалении контакта. Это работает в JAWS, когда удаляется дочерний элемент (с родительским уже не работает). На VoiceOver и NVDA этот атрибут никак не влияет.</p>
<h3>Aria-busy</h3>
<p><code>aria-busy=&quot;&quot;</code> даёт вспомогательным технологиям знать, обновляется ли сейчас содержимое элемента или нет. Атрибут имеет смысл применять тогда, когда на странице происходит автообновление контента. Что-то было удалено, что-то добавлено, какая-то его часть изменилась, и обо всё этом нам надо сообщить сразу за один раз. Это может быть полезно в случаях, когда на нашей странице есть спортивная статистика, которая обновляется в режиме реального времени, текстовый документ, который может редактировать несколько человек, какой-то новостной виджет или виджет с погодой.</p>
<p>У <code>aria-busy=&quot;&quot;</code> есть два значения — <code>false</code> и <code>true</code>.</p>
<ul>
<li><code>false</code> (значение по умолчанию) — при этом значении вспомогательные технологии не ждут, пока изменения завершаться.</li>
<li><code>true</code> — это значение говорит вспомогательным технологиям, что им нужно подождать, пока элемент не закончит изменяться, после чего они могут собрать все изменения и сделать объявление. То есть во время обновления контента пользователи скринридеров, например, не смогут читать эту обновляющуюся часть.</li>
</ul>
<p>В примере ниже у нас есть спортивные результаты, которые регулярно обновляются в ходе соревнований:</p>
<pre><code tabindex="0" class="language-html">&lt;h2&gt;Текущий счёт&lt;/h2&gt;
&lt;p role=&quot;score&quot; aria-live=&quot;polite&quot; aria-busy=&quot;true&quot;&gt;9:0&lt;/p&gt;
</code></pre>
<p>Для того, чтобы вся информация объявлялась после всех изменений, сначала добавим атрибут <code>aria-busy</code> со значением <code>true</code>, а потом, с помощью JavaScript, заменим его значение на <code>false</code> или вообще его удалим, когда все изменения завершатся.</p>
<h2>Краткие выводы</h2>
<p>Если у вас на странице есть часть, содержимое которой изменяется, то нужно сделать её интерактивной областью. Тогда скринридеры смогут держать своих пользователей в курсе всех изменений. Сделать такую часть интерактивной можно с помощью <code>role=&quot;alert&quot;</code>, <code>role=&quot;status&quot;</code>, <code>role=&quot;log&quot;</code>, <code>role=&quot;marquee&quot;</code>, <code>role=&quot;timer&quot;</code> и атрибута <code>aria-live</code>.</p>
<p>Используйте <code>role=&quot;alert&quot;</code> для важных ошибок, предупреждений. Для большей совместимости добавляйте её для нужных элементов вместе с атрибутом <code>aria-live=&quot;assertive&quot;</code> и <code>aria-atomic=&quot;true&quot;</code> (опционально).</p>
<p><code>role=&quot;status&quot;</code> подходит для сообщений о менее важных ошибках и предупреждениях. Например, сообщение об автосохранении, неправильно заполненном поле и тому подобное. Для совместимости следует сочетать эту роль с атрибутом <code>aria-live=&quot;polite&quot;</code>.</p>
<p>Если вам нужно сделать доступными историю сообщений, список ошибок и что-то, где важна последовательность обновления информации, используйте <code>role=&quot;log&quot;</code>. Для большей совместимости используйте вместе с ней атрибут <code>aria-live=&quot;polite&quot;</code>.</p>
<p>Когда у вас на странице есть тикеры, курсы валют или любой другой элемент, информация в котором быстро изменяется, то задайте для них <code>role=&quot;marquee&quot;</code>. Для совместимости дополните её <code>aria-live=&quot;off&quot;</code>.</p>
<p>Для таймера, счётчика или секундомера задавайте <code>role=&quot;timer&quot;</code>. Не забудьте о <code>aria-live=&quot;off&quot;</code> для лучшей совместимости.</p>
<p>За то, насколько срочно нужно сообщить об изменениях, отвечает <code>aria-live=&quot;&quot;</code>. Там, где об изменениях не нужно объявлять, используйте <code>aria-live=&quot;off&quot;</code>. В большинстве случаев не нужно срочно сообщать об изменениях, поэтому в них пригодится <code>aria-live=&quot;polite&quot;</code>. Иногда, когда речь идёт о важном сообщении, например, серверной ошибке, можно использовать <code>aria-live=&quot;assertive&quot;</code>.</p>
<p><code>aria-atomic=&quot;&quot;</code> — необязательный атрибут. Он влияет на то, сообщит ли скринридер о контексте или объявит только об изменениях. По умолчанию всем элементам задан <code>aria-atomic=&quot;false&quot;</code>, то есть скринридеры сообщают только об изменениях контента. Если заменить его на <code>aria-atomic=&quot;true&quot;</code>, то скринридеры будут зачитывать всё целиком, включая неизменную часть. В большинстве случаев нет необходимости изменять поведение по умолчанию.</p>
<p>Ещё один необязательный атрибут — <code>aria-relevant=&quot;&quot;</code>. Он нужен для определения типа изменений контента. У него есть несколько значений, которые можно перечислять через пробел. Реальных сценариев использования мало (удаление или добавление друга в список друзей), а многие скринридеры его игнорируют.</p>
<p>Последний необязательный атрибут — <code>aria-busy=&quot;&quot;</code>. Сообщает вспомогательным технологиям обновляется ли сейчас содержимое элемента или нет. По умолчанию элементам задан <code>aria-busy=&quot;false&quot;</code>. Скринридеры объявляют об изменениях не дожидаясь, пока они завершатся. В некоторых случаях можно использовать <code>aria-busy=&quot;true&quot;</code>, когда нужно дождаться всех обновлений. Может быть полезно для спортивной статистики или текстового документа в режиме группового редактирования.</p>
<h2>Что ещё можно почитать</h2>
<ul>
<li>Стандарт <a href="https://www.w3.org/TR/wai-aria-1.2/">WAI-ARIA 1.2</a>.</li>
<li>Заметка <a href="https://developers.google.com/web/fundamentals/accessibility/semantics-aria/hiding-and-updating-content">Hiding and Updating Content</a> на Web Fundamentals.</li>
<li>Несколько примеров использования ARIA-атрибутов в <a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions">ARIA Live Regions</a> на MDN.</li>
<li><a href="https://medium.com/@rishabhsrao/aria-live-regions-6cc96e1a8b72">ARIA Live Regions</a> Ришабха Рао.</li>
<li>Перевод статьи <a href="https://medium.com/high-technologies-center/%D0%BA%D0%B0%D0%BA-%D1%81%D0%B4%D0%B5%D0%BB%D0%B0%D1%82%D1%8C-%D1%81%D0%BE%D0%BE%D0%B1%D1%89%D0%B5%D0%BD%D0%B8%D1%8F-%D0%BE%D0%B1-%D0%BE%D1%88%D0%B8%D0%B1%D0%BA%D0%B0%D1%85-%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%D0%BD%D1%8B%D0%BC%D0%B8-1400134a59cc">Как сделать сообщения об ошибках доступными</a> Хидде де Вриса.</li>
</ul>

                    ]]></description><pubDate>Tue, 12 Mar 2019 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/aria-live-regions/</guid></item><item><title>Советы по доступности интерактивных элементов на мобильных устройствах</title><link>https://web-standards.ru/articles/mobile-a11y/</link><description><![CDATA[
                        <p><a href="https://www.w3.org/TR/WCAG21/">Web Content Accessibility Guidelines</a> (WCAG) — это набор правил для создания доступных сайтов. Вторая версия была опубликована в 2008 году, еще перед тем, как стало возможно просматривать сайты на мобильных устройствах.</p>
<p>Несколько лет назад WCAG был обновлён до версии 2.1, в которую был включен совершенно новый раздел <a href="https://www.w3.org/TR/mobile-accessibility-mapping/">с правилами доступности для мобильных устройств</a>. Среди них есть несколько правил, актуальных только для мобильных. Например, <a href="https://www.w3.org/TR/WCAG21/#orientation">правило 1.3.4</a>, которое гласит: контент не должен ограничивать свою читаемость и работоспособность только одной ориентацией экрана, такой, как книжная или альбомная, если только определённая ориентация не обоснована.</p>
<p>В этой статье я расскажу о некоторых новых рекомендациях, касающихся интерактивных элементов на странице.</p>
<h3>Располагайте интерактивные элементы там, где к ним легко получить доступ</h3>
<p>Первое, что следует учитывать при разработке сайта под мобильные устройства — это размещение интерактивных или активных элементов. По сравнению с ограниченными способами взаимодействия с десктопными устройствами, мобильными девайсами мы можем пользоваться очень по-разному — одной рукой, двумя руками, только левой или только правой и т.д.</p>
<p>Из-за подобного разнообразия способов взаимодействия рекомендуется создавать гибкие интерфейсы. Например, вы скорее всего подумаете, что расположить интерактивный элемент лучше всего в правом нижнем углу, поскольку большинство людей работают на мобильном правой рукой. И тем не менее это самое неудачное место на экране, если пользоваться мобильным левой рукой. Наилучшим решением в данной ситуации будет сделать элемент на всю ширину экрана.</p>
<img src="https://web-standards.ru/articles/mobile-a11y/images/2.jpg" alt="">
<h2>Дайте понять, что с элементом можно взаимодействовать</h2>
<p>Опять же, показать на тач-экранах что элемент интерактивен, может стать той ещё задачкой, по сравнению с десктопными устройствами. Без ховеров мы должны одним только внешним видом показать пользователю, что с элементом можно взаимодействовать.</p>
<p>Лучший способ показать, что с элементом можно взаимодействовать — придерживаться негласного соглашения о том, как должны выглядеть подобные элементы. Например, <a href="https://bitsofco.de/tips-for-making-interactive-elements-accessible-on-mobile-devices/">ссылки</a> обычно подчёркнуты или имеют цвет, отличный от цвета текста на всей странице. А кнопки соцсетей обычно квадратной формы с закруглёнными краями.</p>
<p>Мы также можем использовать расположение элементов, чтобы дать понять, что они доступны для какого-либо действия. Навигация, например, обычно располагается сверху. Для мобильных устройств также вполне допустимо расположить навигацию внизу окна.</p>
<img src="https://web-standards.ru/articles/mobile-a11y/images/3.jpg" alt="">
<h2>Дайте инструкции к доступным кастомным жестам</h2>
<p>Так же, как и в предыдущем случае, очень важно давать чёткие инструкции по кастомным жестам именно на устройствах с тач-экраном.</p>
<p>Хорошим примером для этого служит жест «потянуть чтобы обновить». Если нет никаких признаков того, что этот жест доступен, пользователь никогда им не воспользуется. В мобильной версии Твиттера есть иконка, указывающая, что можно потянуть страницу для обновления ленты. Она появляется, когда пользователь скроллит страницу вверх.</p>
<figure>
    <img src="https://web-standards.ru/articles/mobile-a11y/images/4.png" alt="Лента Твиттера в покое.">
    <img src="https://web-standards.ru/articles/mobile-a11y/images/5.png" alt="Лента Твиттера потянута вниз.">
    <img src="https://web-standards.ru/articles/mobile-a11y/images/6.png" alt="Появление спиннера вверху ленты.">
    <figcaption>При прокручивании вверх страницы профиля в мобильном Твиттере, появляется иконка «потянуть чтобы обновить»</figcaption>
</figure>
<h2>Обеспечьте разумные размер и положение интерактивной цели</h2>
<p>На устройствах с тач-экраном курсором является палец пользователя. Конечно, из-за этого точность касания гораздо ниже, нежели при работе с мышкой. Следовательно, область, на которую может тапнуть пользователь, должна быть достаточно большой для пальцев разного размера.</p>
<p>Согласно <a href="https://www.w3.org/TR/WCAG21/#target-size">правилу 2.5.5</a>, интерактивные элементы должны иметь размер не меньше чем 44×44 пикселя, за исключением случаев, когда цель встроена в блок текста.</p>
<h2>Группируйте элементы, выполняющие одно и то же действие</h2>
<p>Этот совет лучше всего проиллюстрировать следующим примером. Предположим, что у нас есть ссылка, ведущая на главную страницу сайта и, помимо этого, у нас также есть иконка домашней страницы. По различным причинам мы можем захотеть разделить эти два элемента и в итоге получим две ссылки, ведущие в одно и то же место.</p>
<pre><code tabindex="0" class="language-html">&lt;!-- Не рекомендуется --&gt;
&lt;a href=&quot;/&quot;&gt;&lt;img src=&quot;/home.png&quot; alt=&quot;Home icon&quot;&gt;&lt;/a&gt;
&lt;a href=&quot;/&quot;&gt;Главная&lt;/a&gt;
</code></pre>
<p>У этого метода два основных недостатка:</p>
<ol>
<li>Уменьшается размер области касания, поскольку промежуток между ссылками не будет активным.</li>
<li>Увеличивается количество элементов, по которым придётся проходить пользователю при навигации с клавиатуры. Если перемещаться по странице при помощи таба, то подобное дублирование может утомить.</li>
</ol>
<p>Лучшим решением будет сгруппировать оба элемента в одну ссылку:</p>
<pre><code tabindex="0" class="language-html">&lt;!-- Так гораздо лучше! --&gt;
&lt;a href=&quot;/&quot;&gt;
    &lt;img src=&quot;/home.png&quot; alt=&quot;Home icon&quot;&gt;
    Главная
&lt;/a&gt;
</code></pre>
<p>Это не только увеличит область касания для ссылки, но и уменьшит количество ссылок, по которым придётся проходить при навигации с клавиатуры.</p>
<h2>Упростите набор текста с помощью специальных раскладок клавиатуры</h2>
<p>Одна из главных проблем устройств с тач-экранами — набор текста с экранной клавиатуры. Мы можем упростить этот момент, предоставляя специальные клавиатуры в зависимости от типа вводимых данных.</p>
<p>Например, если нужно вводить цифры, то мы можем показать клавиатуру с цифрами, указав нужное значение атрибута <code>type</code> у элемента <code>&lt;input&gt;</code>.</p>
<pre><code tabindex="0" class="language-html">&lt;label&gt;
    Сколько вам лет?
    &lt;input type=&quot;number&quot;&gt;
&lt;/label&gt;
</code></pre>
<img src="https://web-standards.ru/articles/mobile-a11y/images/7.png" alt="">
<p>В некоторых случаях мы должны указать определённое значение атрибута <code>type</code> у элемента <code>&lt;input&gt;</code>, например, <code>text</code>, хотя всё ещё ожидаем от пользователя ввода цифр. В этой ситуации на помощь приходит атрибут <code>inputmode</code>, который позволяет указать, какую именно клавиатуру показать пользователю, вне зависимости от значения атрибута <code>type</code>.</p>
<pre><code tabindex="0" class="language-html">&lt;label&gt;
    Сколько вам лет?
    &lt;input type=&quot;text&quot; inputmode=&quot;numeric&quot;&gt;
&lt;/label&gt;
</code></pre>
<p>На момент написания статьи атрибут <code>inputmode</code> <a href="https://caniuse.com/#feat=input-inputmode">имеет не очень хорошую поддержку</a>.</p>
<h2>Предоставьте альтернативу набору текста</h2>
<p>Ещё один способ решить проблему с набором текста на тач-устройствах — уменьшить количество ситуаций, в которых требуется ввод текста.</p>
<p>Вообще, на мобильных устройствах проще работать с чекбоксами, радиокнопками и выпадающими списками, чем с полями для ввода текста.</p>
<p>В тех местах, где требуется ввод текста, мы можем постараться автоматически заполнять поля. Например, подставляя текущую локацию в поле адреса.</p>

                    ]]></description><pubDate>Thu, 21 Feb 2019 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/mobile-a11y/</guid></item><item><title>Инклюзивные компоненты: список дел</title><link>https://web-standards.ru/articles/a-todo-list/</link><description><![CDATA[
                        <p>Следуя традициям, каждый новый JS-фреймворк проходит через несколько этапов создания простого приложения со списком дел — приложения для добавления и удаления позиций из списка. Первый пример, написанный на Angular.js, который я видел, был как раз таким списком. Добавление и удаление пунктов демонстрировало высокую скорость работы одностраничного приложения, реализованного на основе шаблона ViewModel.</p>
<p>Инструмент <a href="http://todomvc.com/">TodoMVC</a> сравнивает и находит различия в разных реализациях приложений со списком дел в популярных MV*-фреймворках вроде Vue.js, Angular.js и Ember.js. Он помогает разработчику, который ищет технологию для нового проекта, принять правильное решение.</p>
<p>Инклюзивный дизайн интерфейса такого списка, однако, отличается от его реализации во фреймворках. Вашего пользователя не волнует, написан он на Backbone или React. Ему просто нужен конечный продукт, который будет доступен и прост в использовании. К сожалению, реализации списков дел в <a href="http://todomvc.com/">TodoMVC</a> имеют недостатки. В частности, доступ к функции удаления появляется только при наведении курсора на пункт списка, что делает её полностью недоступной для клавиатуры.</p>
<p>В этой статье я буду создавать список дел с нуля. Но то, что вы узнаете, можно использовать <em>не только</em> для подобных списков. На самом деле мы разберёмся, как сделать создание и удаление контента инклюзивным.</p>
<p>В отличие от простого переключателя из <a href="https://medium.com/web-standards/toggle-buttons-a41388e80974">предыдущей статьи</a>, списки состоят из нескольких частей. Вот что мы собираемся сделать:</p>
<img src="https://web-standards.ru/articles/a-todo-list/images/2.png" alt="Выделенный жирным заголовок «Мой список дел» описывает три задачи из списка. Они расположены на разных строках. У каждой есть чекбокс слева и иконка с мусорной корзиной справа. Под ними есть текстовое поле с плейсхолдером «Например, взять сову» и кнопка «Добавить» справа от него. В первом пункте выбран чекбокс и зачёркнут текст («Забрать детей из школы»), чтобы показать, что он выполнен.">
<h2>Заголовок</h2>
<p>Лейблы — одна из важных составляющих юзабилити. Элемент <code>&lt;label&gt;</code> добавляет подписи к полям форм. Но простые текстовые фрагменты у кнопок и ссылок — это тоже лейблы. Они сообщают, что делают эти элементы, когда вы нажимаете на них.</p>
<p>Заголовки — тоже лейблы, которые задают имена для разделов (регионов, участков, блоков), из которых состоит интерфейс. Не зависимо от того, создаёте ли вы статичный документ, такой как пост в блоге, или интерактивное одностраничное приложение, каждый крупный раздел контента этой страницы должен начинаться с заголовка. Наше название списка в этом примере — «Мой список дел». Он должен быть размечен соответственно.</p>
<pre><code tabindex="0" class="language-html">&lt;h1&gt;Мой список дел&lt;/h1&gt;
</code></pre>
<p>Это очень <em>прямолинейный</em> способ разделения интерфейса, но очевидность — это хорошо. Мы не хотим, чтобы нашим пользователям приходилось проводить целое расследование, чтобы понять, с чем они имеют дело.</p>
<h3>Уровень заголовка</h3>
<p>Выбор правильного уровня заголовков часто связан с вопросом важности, но, на самом деле, это вопрос <em>принадлежности.</em> Если список дел является единственным содержимым <code>&lt;main&gt;</code>, то для него стоит задать заголовок первого уровня, как в предыдущем примере. Его не окружают другие элементы, поэтому он на высшем уровне с точки зрения глубины.</p>
<p>В случае, если список задач — дополнительный контент, то он должен иметь уровень, который это отражает. Например, если это страница о планах на отпуск, то список «Вещи, которые нужно собрать» может быть вспомогательным инструментом.</p>
<ul>
<li>Планы на моё путешествие (<code>&lt;h1&gt;</code>)
<ul>
<li>Места, где можно выпить (<code>&lt;h2&gt;</code>)
<ul>
<li>Бары (<code>&lt;h3&gt;</code>)</li>
<li>Клубы (<code>&lt;h3&gt;</code>)</li>
</ul>
</li>
<li>Вещи, которые нужно собрать (список дел) (<code>&lt;h2&gt;</code>)</li>
</ul>
</li>
</ul>
<p>В примере выше и «Бары», и «Клубы» входят в пункт «Места, где можно выпить». Он, в свою очередь, является подпунктом «Планы на моё путешествие». Это третий уровень принадлежности, следовательно, для них заданы заголовки <code>&lt;h3&gt;</code>.</p>
<p>Даже если вам кажется, что список задач про сбор вещей менее важен, чем определение того, какие бары лучше посетить, он всё ещё находится на том же уровне с точки зрения принадлежности. Так что у него должен быть тот же уровень заголовка.</p>
<p>Страница, структура которой образована логически вложенными разделами, имеет хорошую визуальную иерархию и помогает пользователям скринридеров составить чёткое представление о ней. Также они используют заголовки в качестве инструментов навигации. Например, в JAWS клавиша <kbd>2</kbd> переместит вас к следующему разделу, который начинается с заголовка <code>&lt;h2&gt;</code>. Клавиша <kbd>H</kbd> перемещает к следующему заголовку любого уровня.</p>
<h3>Примечание: элемент <code>&lt;section&gt;</code></h3>
<p>После всего этого разговора о секциях, мы должны использовать элементы <code>&lt;section&gt;</code>, верно? Может быть. Вот о чём нужно подумать:</p>
<ol>
<li>Элементы заголовков уже описывают секции. То есть контент, который начинается с заголовка и заканчивается непосредственно перед заголовком того же уровня, <em>де-факто</em> является секцией.</li>
<li>Если вы используете элемент <code>&lt;section&gt;</code>, то для него всё ещё требуется задать заголовок, в противном случае это секция без описания.</li>
</ol>
<p>На практике значение, которое задаётся элементам <code>&lt;section&gt;</code>, ограничено, но всё же стоит отметить:</p>
<ol>
<li>Некоторые скринридеры будут объявлять начало и конец секции, когда пользователи перемещаются по странице от элемента к элементу.</li>
<li>Некоторые скринридеры поддерживают навигацию по регионам. Например, в JAWS элементы <code>&lt;section&gt;</code> помечены как «regions» и между ними можно перемещаться с помощью клавиши <kbd>R</kbd> или сочетания <kbd>Shift R</kbd>.</li>
<li>Эти теги могут улучшить организацию кода и сделать его более понятным. Они будут контейнером для секций страницы.</li>
</ol>
<p>Чтобы максимально эффективно использовать <code>&lt;section&gt;</code>, вы должны подписать их. То есть, связать их заголовки с элементами <code>&lt;section&gt;</code>, используя <code>aria-labelledby</code>:</p>
<pre><code tabindex="0" class="language-html">&lt;section aria-labelledby=&quot;todos-label&quot;&gt;
    &lt;h1 id=&quot;todos-label&quot;&gt;Мой список дел&lt;/h1&gt;
    &lt;!-- Контент --&gt;
&lt;/section&gt;
</code></pre>
<p>Заметьте, что значение <code>aria-labelledby</code> должно полностью соответствовать значению <code>id</code> у заголовка.</p>
<p>Фактически это добавляет групповую подпись к разделу. Подпись будет объявлена некоторыми скринридерами при фокусе на разделе. Если запустить NVDA, то, когда я окажусь в разделе и сделаю фокус на первом чекбоксе, то услышу: <em>«Регион мой список дел, список из трёх пунктов, забрать детей из школы, выбран».</em> Полезно давать такую информацию о контексте пользователям, которые перемещаются с помощью фокуса, а не по регионам или заголовкам.</p>
<h2>Список</h2>
<p>Я рассказал о достоинствах списков в книге <a href="https://shop.smashingmagazine.com/products/inclusive-design-patterns">«Inclusive Design Patterns»</a>. Вместе с заголовками, списки помогают придавать страницам структуру. Без заголовков или списков страницы пустые и однообразные. Это делает их сложными как для визуального восприятия, так и для любого другого.</p>
<p>Не всем спискам нужны маркеры, за отображение которых отвечает свойство <code>list-style</code>. Однако у них должны быть видимые признаки того, что их пункты похожи, эквивалентны, а также связаны друг с другом. На уровне разметки использование контейнера с <code>&lt;ul&gt;</code> или <code>&lt;ol&gt;</code> означает, что список будет определён, когда его обнаружит скринридер, а число пунктов в нём будет сосчитано. Скринридер сообщит о нашем списке из трёх пунктов как-то так: <em>«Список из трёх пунктов».</em></p>
<p>Список дел, как следует из названия — это список. В этом случае подходит неупорядоченный список, так как он не содержит никаких сведений о приоритетности пунктов. Вот разметка нашего списка задач (добавления, удаления и выбора чекбоксов пока нет):</p>
<pre><code tabindex="0" class="language-html">&lt;section aria-labelledby=&quot;todos-label&quot;&gt;
    &lt;h1 id=&quot;todos-label&quot;&gt;Мой список дел&lt;/h1&gt;
    &lt;ul&gt;
        &lt;li&gt;Забрать детей из школы&lt;/li&gt;
        &lt;li&gt;Выучить Haskell&lt;/li&gt;
        &lt;li&gt;Поспать&lt;/li&gt;
    &lt;/ul&gt;
&lt;/section&gt;
</code></pre>
<h3>Пустое состояние</h3>
<p>Пустые состояния — это один из аспектов UI-дизайна, который <a href="https://techcrunch.com/2015/11/22/the-most-overlooked-aspect-of-ux-design-could-be-the-most-important/">можно игнорировать, но на свой страх и риск</a>. Инклюзивный дизайн должен учитывать особенности стиля жизни пользователей. Новые пользователи — это одна из наиболее уязвимых групп. Им не знаком ваш интерфейс — если не вести таких пользователей осторожно за руку, эта неизвестность может их оттолкнуть.</p>
<p>Благодаря нашему заголовку и кнопке <em>«Добавить»</em> некоторым пользователям может быть сразу понятно, что нужно делать, даже без примеров или инструкций. Но интерфейс может быть менее знакомым и более сложным, чем этот простой список дел, поэтому давайте всё равно добавим пустое состояние — для тренировки.</p>
<img src="https://web-standards.ru/articles/a-todo-list/images/3.png" alt="Выделенный жирным заголовок «Мой список дел» описывает список дел, в котором выполнены все задачи. Когда вы это читаете, то уже всё сделали или ещё есть, что добавить в список. Под ним находится текстовое поле с плейсхолдером «Например, взять сову» с кнопкой «Добавить» справа от него.">
<h3>Отображение пустого состояния</h3>
<p>Конечно, можно использовать наши данные, чтобы определить, должно ли быть показано пустое состояние. Во Vue.js мы можем использовать <code>v-if</code>:</p>
<pre><code tabindex="0" class="language-html">&lt;div class=&quot;empty-state&quot; v-if=&quot;!todos.length&quot;&gt;
    &lt;p&gt;
        Либо вы уже выполнили все задачи, либо ещё есть что
        добавить в ваш список. Добавьте первый пункт &amp;#x2193;
    &lt;/p&gt;
&lt;/div&gt;
</code></pre>
<p>Но вся необходимая нам информация о состоянии уже находится в DOM. Поэтому всё, что нам нужно сделать для переключения между списком и пустым состоянием, можно сделать через CSS.</p>
<pre><code tabindex="0" class="language-css">.empty-state,
ul:empty {
    display: none;
}

ul:empty + .empty-state {
    display: block;
}
</code></pre>
<p>Это целесообразнее, поскольку нам не нужно запрашивать данные или изменять разметку. Также это доступно для скринридеров. <code>display: none</code> означает, что элемент будет скрыт как визуально, так и от скринридеров.</p>
<p>Все псевдоклассы относятся к неявным состояниям. Псевдокласс <code>:empty</code> означает, что у элемента пустое состояние. <code>:checked</code> значит, что элемент отмечен. <code>:first-child</code> означает, что он находится в начале набора. Чем больше вы их используете, тем меньше потребуется манипулировать DOM через JavaScript для добавления и изменения состояния.</p>
<h2>Добавление задачи</h2>
<p>До этого мы не обсуждали добавление задач, так что давайте сделаем это сейчас. Под списком (или пустым состоянием, если в нём ничего нет) расположены текстовое поле и кнопка «Добавить»:</p>
<img src="https://web-standards.ru/articles/a-todo-list/images/4.png" alt="Текстовое поле с плейсхолдером «Например, взять сову» и кнопка «Добавить» справа от него.">
<h3>Форма или нет?</h3>
<p>В HTML допустимо использовать <code>&lt;input&gt;</code> вне <code>&lt;form&gt;</code>. Без помощи JavaScript нельзя успешно передать из <code>&lt;input&gt;</code> данные на сервер, но это не проблема для приложений, использующих XHR <em>(<code>XMLHttpRequest</code> — прим. переводчика).</em></p>
<p>Но могут ли элементы с <code>&lt;form&gt;</code> дать что-то пользователям? Когда пользователи скринридеров JAWS или NVDA столкнутся с <code>&lt;form&gt;</code>, то автоматически включится специальный режим взаимодействия, который называют «режим форм» или «режим приложения». В этом режиме некоторые клавиши, которые могли бы использоваться в специальных сочетаниях, отключаются. Это позволяет пользователям скринридеров взаимодействовать с полями формы в полном объёме.</p>
<p>К счастью, многие типы полей, включая <code>type=&quot;text&quot;</code>, активируют режим форм самостоятельно при фокусе. Например, если я должен ввести «h» в какое-то поле, то «h» ввелась бы в него, а не переместила меня к ближайшему заголовку.</p>
<p>Настоящая причина, по которой нам необходим тег <code>&lt;form&gt;</code> заключается в том, что мы хотим дать пользователям возможность отправлять данные при нажатии на клавишу <kbd>Enter</kbd>. Это надёжно работает только тогда, когда кнопка расположена внутри <code>&lt;form&gt;</code>. Этот тег нужен не только для организации кода или семантики, но влияет и на поведение браузера.</p>
<pre><code tabindex="0" class="language-html">&lt;form&gt;
    &lt;input type=&quot;text&quot; placeholder=&quot;Например, взять сову&quot;&gt;
    &lt;button type=&quot;submit&quot;&gt;Добавить&lt;/button&gt;
&lt;/form&gt;
</code></pre>
<p><em><strong>Примечание:</strong> <a href="https://twitter.com/LeonieWatson">Леони Уотсон</a> сообщает, что <code>&lt;input&gt;</code> с типом <code>range</code> не работают в связке Firefox + JAWS, когда они не вложены в <code>&lt;form&gt;</code> или режим форм не включён пользователем вручную.</em></p>
<h3>Добавление подписи</h3>
<p>Можете ли вы найти ошибку во фрагменте кода, которую я допустил специально? Ответ: я не добавил подпись. В коде есть только атрибуты <code>placeholder</code>, а они нужны для предоставления дополнительной информации. В данном случае это предложение «взять сову».</p>
<p>Плейсхолдеры не очень хорошо подходят для скринридеров в качестве подписей, так что здесь нужен другой подход. Вопрос в том, должна ли эта подпись быть видимой или её стоит сделать доступной только для скринридеров.</p>
<p><em>Почти</em> во всех случаях видимая подпись должна располагаться над полем или слева от него. Это связано с тем, что плейсхолдер исчезает при фокусе и может быть заменён текстом автозаполнения. Это означает, что зрячие пользователи потеряют подписи. Заполнение поля информацией или исправление текста автозаполнения превратятся в гадание на кофейной гуще.</p>
<img src="https://web-standards.ru/articles/a-todo-list/images/5.png" alt="Текстовое поле без плейсхолдера. Это вводит пользователя в заблуждение, о чём свидетельствует введёное им «Эм».">
<p>Однако, наш случай особый, потому что подписи «Добавить» для кнопки, расположенной рядом, вполне достаточно. Те, кто смотрит на форму, знают, для чего нужно поле благодаря одной кнопке.</p>
<p><strong>У всех полей должны быть подписи</strong>, потому что пользователи скринридеров не могут заглянуть вперёд и увидеть, даёт ли кнопка отправки, до которой они ещё не дошли, подсказки о целях формы. В связке простые поле-кнопка, как в этом случае, и регионы поиска не нуждаются в <em>видимой</em> подписи. Это работает тогда, когда текст кнопки достаточно информативный.</p>
<figure>
    <img src="https://web-standards.ru/articles/a-todo-list/images/2.png" alt="Три примера описанного паттерна. В первом примере списка дел кнопка отправки данных подписана как «Добавить», так что всё в порядке. Во втором для поисковой формы использована кнопка с текстом «Искать» и это тоже приемлемо. В третьем примере кнопка подписана просто как «Отправить», а так делать не рекомендуется.">
    <figcaption>Кроме того, убедитесь, что у форм с несколькими полями есть видимые подписи для каждого из них. Без этого пользователи не будут знать какое поле за что отвечает.</figcaption>
</figure>
<p>Есть несколько способов добавить невидимую подпись к полю для пользователей скринридеров. Один из самых простых и более лаконичных — <code>aria-label</code>. В примере ниже «Добавить новую задачу» — это значение. Оно более наглядно, чем просто «Добавить», и помогает отличить его от текста кнопки без путаницы при переключении между двумя элементами.</p>
<pre><code tabindex="0" class="language-html">&lt;form&gt;
    &lt;input type=&quot;text&quot;
            aria-label=&quot;Добавить новую задачу&quot;
            placeholder=&quot;Например, взять сову&quot;&gt;
    &lt;button type=&quot;submit&quot;&gt;Добавить&lt;/button&gt;
&lt;/form&gt;
</code></pre>
<h3>Примечание: стилизация плейсхолдера</h3>
<p>Обратите внимание на то, что в некоторых браузерах серый текст плейсхолдера очень бледный и не соответствует минимальным требованиям контраста <a href="https://www.w3.org/TR/WCAG20/#visual-audio-contrast">WCAG 1.4.3 Contrast (Minimum)</a>.</p>
<p>Я рекомендую делать текст плейсхолдеров с кроссбраузерной поддержкой более контрастным. Также стоит использовать дополнительные стили, например, курсив как в примере ниже, чтобы отличать текст плейсхолдера от введённого пользователем текста.</p>
<pre><code tabindex="0" class="language-css">::-webkit-input-placeholder {
    color: #444;
    font-style: italic;
}

::-moz-placeholder {
    color: #444;
    font-style: italic;
}

:-ms-input-placeholder {
    color: #444;
    font-style: italic;
}

:-moz-placeholder {
    color: #444;
    font-style: italic;
}
</code></pre>
<p>К сожалению, для этого нужны отдельные блоки правил, так как у каждого браузера есть проблемы с парсингом таких селекторов для других браузеров.</p>
<h2>Представление поведения</h2>
<p>Одно из преимуществ использования <code>&lt;form&gt;</code> с кнопкой типа <code>submit</code> заключается в том, что пользователи могут отправить данные, нажав на кнопку напрямую или с помощью клавиши <kbd>Enter</kbd>. Даже пользователи, которые не всегда используют клавиатуру для управления интерфейсом, могут нажать на <kbd>Enter</kbd>, потому что это быстрее. То, что делает взаимодействие возможным для некоторых, делает его лучше для других. Это инклюзивно.</p>
<p>Если пользователь пытается отправить неверные данные, то нам нужно остановить его. Отключив кнопку пока поле заполнено неправильно, отправка по клику или нажатию на <kbd>Enter</kbd> будет запрещена. На самом деле на кнопке с <code>type=&quot;submit&quot;</code> в этом случае нельзя сделать фокус с клавиатуры. Также мы добавляем к полю <code>aria-invalid=&quot;true&quot;</code>. Скринридеры объявят пользователям, что поле заполнено неправильно и нужно изменить введённые данные.</p>
<pre><code tabindex="0" class="language-html">&lt;form&gt;
    &lt;input type=&quot;text&quot; aria-invalid=&quot;true&quot;
            aria-label=&quot;Создать новую задачу&quot;
            placeholder=&quot;Например, взять сову&quot;&gt;
    &lt;button type=&quot;submit&quot; disabled&gt;Добавить&lt;/button&gt;
&lt;/form&gt;
</code></pre>
<h2>Обратная связь</h2>
<p>Суть взаимодействия человека с компьютером состоит в том, что когда одна сторона что-то делает, то другая должна откликнуться в ответ. Это просто вежливо. Для большинства пользователей ответ компьютера при добавлении элемента не очевиден: они просто видят новый элемент, который появился на странице. Если есть возможность <em>анимировать</em> появление нового элемента, то лучше это сделать. Какое-то движение на странице при его возникновении с большей вероятностью заметят.</p>
<p>В этом случае для незрячих пользователей или тех, кто не пользуется обычным интерфейсом, ничего не происходит. У них по-прежнему установлен фокус на поле, которое не содержит ничего нового, о чём может объявить скринридер. Тишина.</p>
<p>Перемещая фокус на другую часть страницы, скажем, на новый пункт списка, этот элемент будет объявлен. Но мы не хотим перемещать фокус на другой элемент. Может пользователям нужно добавить больше задач. Вместо этого мы можем использовать интерактивную область (live region).</p>
<h3>Обратная связь с интерактивной областью</h3>
<p>Интерактивные области — это элементы, о содержимом которых скринридеры сообщат <em>при каждом его изменении.</em> С интерактивной областью мы можем сделать так, чтобы скринридеры общались с пользователями, не заставляя их выполнять какие-либо действия (например, перемещать фокус).</p>
<p>Простые интерактивные области обозначаются с помощью атрибута <code>role=&quot;status&quot;</code> или эквивалентного ему <code>aria-live=&quot;polite&quot;</code>. Для максимальной совместимости с разными скринридерами стоит использовать оба атрибута. Это может показаться излишним, но благодаря этому у вас будет больше пользователей.</p>
<pre><code tabindex="0" class="language-html">&lt;div role=&quot;status&quot; aria-live=&quot;polite&quot;&gt;
    &lt;!-- Добавьте контент, чтобы его услышать --&gt;
&lt;/div&gt;
</code></pre>
<p>Что касается события отправки формы, я могу просто добавить обратную связь в интерактивную область, о чём объявит скринридер.</p>
<pre><code tabindex="0" class="language-js">var todoName = document.querySelector('[type=&quot;text&quot;]').value;

function addedFeedback(todoName) {
    let liveRegion = document.querySelector('[role=&quot;status&quot;]');
    liveRegion.textContent = `${todoName} added.`;
}

// Пример использования
addedFeedback(todoName);
</code></pre>
<p>Самый простой способ сделать ваше веб-приложение более доступным — разместить сообщения о состоянии в интерактивной области. Тогда, когда оно обновится визуально, это также будут объявлено скринридерами.</p>
<figure>
    <img src="https://web-standards.ru/articles/a-todo-list/images/7.png" alt="Сообщение о состоянии «Взять сову добавлено». Перед белым текстом на зелёном фоне расположена иконка с галочкой.">
    <figcaption>Это общепринятый цвет для сообщения о состоянии. Например, сообщение об успешности действия зелёное. Но важно полагаться не только на цвет, чтобы не исключить дальтоников. Поэтому в примере есть дополнительная галочка.</figcaption>
</figure>
<p>Инклюзивность заключается в том, чтобы у всех пользователей был <strong>равноценный опыт</strong>, не обязательно одинаковый. Иногда то, что работает для одного пользователя, бессмысленно, избыточно или мешает другому.</p>
<p>В этом случае сообщение о состоянии не нужно делать видимым, так как пункт виден при добавлении. На самом деле одновременное добавление пункта в список и показ сообщения о состоянии могут отвлечь внимание пользователей. Другими словами, видимое добавление элемента и объявление «[имя элемента] добавлено» уже эквивалентны.</p>
<p>Так что мы можем скрыть такое сообщение с помощью класса <code>vh</code>.</p>
<pre><code tabindex="0" class="language-html">&lt;div role=&quot;status&quot; aria-live=&quot;polite&quot; class=&quot;vh&quot;&gt;
    &lt;!-- Добавьте контент, чтобы его услышать --&gt;
&lt;/div&gt;
</code></pre>
<p>Этот служебный класс использует магию и определяет, скрыты ли элементы. При этом они обнаруживаются и объявляются скринридерами. Вот как это выглядит:</p>
<pre><code tabindex="0" class="language-css">.vh {
    position: absolute !important;
    clip: rect(1px, 1px, 1px, 1px);
    padding: 0 !important;
    border: 0 !important;
    height: 1px !important;
    width: 1px !important;
    overflow: hidden;
}
</code></pre>
<h2>Отчекиваем задачи</h2>
<p>В отличие от переключателя из <a href="https://medium.com/web-standards/toggle-buttons-a41388e80974">предыдущей статьи</a>, в этот раз чекбокс, с точки зрения семантики, правильный способ что-то активировать и наоборот. Вам не нужно <em>включать</em> или <em>отключать</em> задачи, нужно их <em>отметить.</em></p>
<p>К счастью, чекбоксы позволяют легко это сделать прямо из коробки. Нам просто нужно помнить, что <code>&lt;label&gt;</code> нужен для каждого <code>&lt;input&gt;</code>. При переборе данных мы можем записать уникальные значения для каждой пары <code>for</code>-<code>id</code>, используя для связки текущий индекс и интерполяцию строк. Вот как это можно сделать с помощью Vue.js:</p>
<pre><code tabindex="0" class="language-html">&lt;ul&gt;
    &lt;li v-for=&quot;(todo, index) in todos&quot;&gt;
        &lt;input type=&quot;checkbox&quot; :id=&quot;`todo-${index}`&quot; v-model=&quot;todo.done&quot;&gt;
        &lt;label :for=&quot;`todo-${index}`&quot;&gt;{{todo.name}}&lt;/label&gt;
    &lt;/li&gt;
&lt;/ul&gt;
</code></pre>
<p><em><strong>Примечание</strong>: в этом примере каждый пункт списка имеет значение <code>done</code>, следовательно, <code>v-model=&quot;todo.done&quot;</code> автоматически отмечает чекбокс, когда он определяется как <code>true</code>.</em></p>
<h3>Стиль для зачёркивания</h3>
<p>Создание надёжных и доступных компонентов просто, когда вы используете семантические элементы по назначению. В моём варианте я просто добавил незначительное улучшение — свойство <code>line-through</code> для выбранных пунктов. Оно применяется к <code>&lt;label&gt;</code> с помощью <code>:checked</code> при использовании смежного селектора.</p>
<pre><code tabindex="0" class="language-css">:checked + label {
    text-decoration: line-through;
}
</code></pre>
<p>Ещё раз, я использую неявное состояние, чтобы повлиять на стиль. Не нужно добавлять и убирать <code>class=&quot;crossed-out&quot;</code> или делать что-то подобное.</p>
<p><em><strong>Примечание</strong>: если вы хотите стилизовать чекбоксы сами, то посмотрите <a href="http://wtfforms.com/">WTF Forms</a>. Там есть советы о том, как это сделать без кастомных элементов. В конце этой статьи вы найдёте <a href="https://codepen.io/heydon/pen/VpVNKW">демо</a>, в котором я использую класс <code>.tick</code> для <code>&lt;span&gt;</code>, чтобы сделать что-то похожее.</em></p>
<h2>Удаление задач</h2>
<p>Отчекивание и удаление задач — это разные действия. Иногда вам нужно видеть, какие из них вы сделали, иногда вы добавляете не тот пункт в список или он вообще становится ненужным.</p>
<p>Удаление задачи можно осуществить с помощью простой кнопки. Для этого не требуется какой-то информации о состоянии: подпись описывает всё, что нам нужно знать. Конечно, если кнопка содержит иконку или глиф вместо текста, то нужно использовать дополнительную подпись.</p>
<pre><code tabindex="0" class="language-html">&lt;button aria-label=&quot;Удалить&quot;&gt;
    &amp;times;
&lt;/button&gt;
</code></pre>
<p>На самом деле, давайте будем более конкретными и включим имя элемента с текстом задачи. Всегда лучше добавлять подписи, которые имеют смысл при отдельном рассмотрении элементов. Эта уникальная подпись поможет отличить его от других. В вашем JS-фреймворке должны быть шаблонные решения для этого. В моём случае во Vue.js это выглядит так:</p>
<pre><code tabindex="0" class="language-html">&lt;button :aria-label=&quot;`Удалить ${todo.name}`&quot;&gt;
    &amp;times;
&lt;/button&gt;
</code></pre>
<p>В этом примере специальный символ <code>&amp;times;</code> используется для добавления символа с крестиком. Если бы не <code>aria-label</code>, которая переопределяет значение, подпись была бы объявлена как «умножить».</p>
<p>Всегда следите за тем, как скринридеры интерпретируют специальные символы и символы Юникода. А в примере ниже символ будет объявлен как «стрелка вниз» или что-то вроде этого. Так как в нашем случае текстовое поле всегда находится под списком как визуально, так и в HTML-коде, стрелка приводит пользователя к нему.</p>
<img src="https://web-standards.ru/articles/a-todo-list/images/8.png" alt="Выделенный жирным заголовок «Мой список дел» описывает список дел, в котором выполнены все задачи. Когда вы это читаете, то уже всё сделали или ещё есть, что добавить в список. Под ним находится текстовое поле с плейсхолдером «Например, взять сову» с кнопкой «Добавить» справа от него.">
<p>Здесь значок с мусорной корзиной добавлен с помощью SVG. Это отличный формат графики, потому что он масштабируется без потери качества. Многим пользователям часто нужно изменять масштаб интерфейсов, в том числе тем, у кого близорукость, проблемы с двигательным аппаратом и кому нужна большая область касания или клика.</p>
<pre><code tabindex="0" class="language-html">&lt;button aria-label=&quot;удалить&quot;&gt;
    &lt;svg&gt;
        &lt;use xlink:href=&quot;#bin-icon&quot;&gt;&lt;/use&gt;
    &lt;/svg&gt;
&lt;/button&gt;
</code></pre>
<img src="https://web-standards.ru/articles/a-todo-list/images/9.png" alt="Список из трёх задач. Они расположены на разных строках. У каждой есть чекбокс слева и иконка с мусорной корзиной справа. В первом пункте выбран чекбокс и зачёркнут текст («Забрать детей из школы»), чтобы показать, что он выполнен.">
<p>Чтобы уменьшить количество кода при повторном использовании одного и того же SVG, мы используем элемент <code>&lt;use&gt;</code>. Он ссылается на экземпляр SVG, определённой как <code>&lt;symbol&gt;</code> в <code>&lt;body&gt;</code>:</p>
<pre><code tabindex="0" class="language-html">&lt;body&gt;
    &lt;svg style=&quot;display: none&quot;&gt;
        &lt;symbol id=&quot;bin-icon&quot; viewBox=&quot;0 0 20 20&quot;&gt;
            &lt;path d=&quot;[здесь координаты]&quot;&gt;
        &lt;/symbol&gt;
    &lt;/svg&gt;
&lt;/body&gt;
</code></pre>
<p>Раздутый DOM может ухудшить взаимодействие с интерфейсом для многих пользователей, так как действия займут больше времени. Пользователи вспомогательных технологий, в частности, могут столкнуться с тем, что их программное обеспечение не отвечает.</p>
<h3>Примечание: инклюзивные иконки</h3>
<p>Повышение <a href="http://uxmyths.com/post/715009009/myth-icons-enhance-usability">удобства использования интерфейсов благодаря иконкам — спорный вопрос</a>. Конечно, в сочетании с текстом, они могут помочь лучше что-то понять. Особенно это касается тех пользователей, у которых низкий уровень грамотности или тех, кто знакомится с интерфейсом на другом языке. Однако любые иконки без дополнительного текста могут быть неправильно интерпретированы.</p>
<img src="https://web-standards.ru/articles/a-todo-list/images/10.png" alt="Для сравнения иконка с мусорной корзиной расположена рядом с крестиком.">
<p>Иконка с мусорной корзиной на самом деле нужна для <em>удаления</em>? Могу я сделать иконку получше, которая будет выглядеть больше похожей на мусорное ведро? Я могу попытаться сделать что-то другое, например, использовать иконку с крестиком (как это сделано в реализациях на TodoMVC), но не выглядит ли она больше похожей на иконку для <em>завершения</em> действия? Тут нет простых ответов, поэтому лучшее, что вы можете сделать — тестировать это на реальных пользователях.</p>
<p>К счастью, случайное удаление пункта списка не такая критичная ошибка. Пользователи могут без проблем понять, что означает иконка методом проб и ошибок. Там, где удаление <em>критично</em>, поможет диалоговое окно подтверждения, которое всё объясняет и при этом в нём можно завершить действие.</p>
<img src="https://web-standards.ru/articles/a-todo-list/images/11.png" alt="Текст диалогового окна «Вы уверены, что хотите удалить эти задачи?» с кнопками «Да» и «Нет».">
<h3>Управление фокусом</h3>
<p>Когда пользователь кликает по кнопке удаления пункта списка, задача, включая чекбокс, подпись и <strong>сама кнопка</strong>, будут удалены из DOM. Это приводит к возникновению интересной проблемы: что произойдёт с фокусом, когда вы удаляете элемент, на котором сейчас сделан фокус?</p>
<img src="https://web-standards.ru/articles/a-todo-list/images/12.png" alt="Схема из двух списков. В первом списке сделан фокус в виде голубой рамки на иконке в виде мусорной корзины (кнопкой удаления) справа от пункта «Выучить Haskell». Во втором списке показано, что задача удалена из списка и фокуса нигде не видно.">
<p>Если вы не будете соблюдать осторожность, то решение этой проблемы будет <em>сильно раздражать</em> пользователей клавиатуры, в том числе пользователей скринридеров.</p>
<p>На самом деле браузеры не знают, где сделать фокус, когда он был удалён таким образом. Некоторые добавляют «призрачный» фокус там, где раньше был удалённый элемент, а другие делают фокус на следующем элементе. Некоторые по умолчанию делают фокус на внешнем документе. Это означает, что пользователям клавиатуры придётся снова проходить через весь DOM, чтобы попасть обратно в место удалённого элемента.</p>
<p>Для того, чтобы взаимодействие разных пользователей с интерфейсом было консистентным, мы должны всё тщательно обдумать и использовать <code>focus()</code> для соответствующего элемента, но какого именно?</p>
<p>Один из вариантов — сделать фокус на первом чекбоксе списка. При этом будут объявлены не только его имя и состояние, но и общее количество оставшихся элементов списка. Их количество будет на один пункт меньше, чем раньше. Это всё полезный контекст.</p>
<pre><code tabindex="0" class="language-js">document.querySelector('ul input').focus();
</code></pre>
<p>Примечание: <code>querySelector</code> возвращает <em>первый</em> элемент, соответствующий селектору. В нашем случае это первый чекбокс в списке задач.</p>
<p>Но что, если мы просто удалили последний пункт нашего списка и вернулись к пустому состоянию? Здесь нет чекбокса, на котором можно сделать фокус. Давайте попробуем что-нибудь другое. Вместо всего этого я хочу сделать две вещи:</p>
<ol>
<li>Сделать фокус на заголовке области «Мой список дел».</li>
<li>Использовать уже добавленную интерактивную область для обратной связи.</li>
</ol>
<p>Вы никогда не должны делать неинтерактивные элементы вроде заголовков фокусируемыми, потому что в этом случае предполагается, что они должны что-то <em>делать.</em> Когда я тестирую интерфейс и в нём есть подобные элементы, то я не смогу привести его в соответствие с требованием <a href="https://www.w3.org/TR/WCAG20/#navigation-mechanisms">WCAG 2.4.3 Focus Order</a>.</p>
<p>Однако иногда вам нужно привести пользователя к определённой части страницы с помощью скрипта. Чтобы переместить пользователя к заголовку и объявить его, нужно сделать две вещи:</p>
<ol>
<li>Добавить к заголовку атрибут <code>tabindex=&quot;-1&quot;</code>.</li>
<li>Сделать фокус при помощи метода <code>focus()</code>.</li>
</ol>
<pre><code tabindex="0" class="language-html">&lt;h1 tabindex=&quot;-1&quot;&gt;Мой список дел&lt;/h1&gt;
</code></pre>
<p>Значение <code>-1</code> выполняет сразу несколько задач: оно делает элементы не сфокусированными для пользователей (включая те элементы, на которых можно сделать фокус). При этом оно позволяет устанавливать фокус на элементах с помощью JavaScript. Таким образом мы можем переместить пользователя к неинтерактивному элементу и не сделать его «остановкой табуляции» (элементом, к которому можно перемещаться с помощью <kbd>Tab</kbd>).</p>
<p>Кроме того, при установлении фокуса на заголовке будут объявлены его текст, роль, уровень и информация о контексте, такая как «region» (некоторыми скринридерами). По крайней мере, вы должны услышать: <em>«Мой список дел, заголовок второго уровня».</em> Поскольку он в фокусе, нажатие на клавишу <kbd>Tab</kbd> вернёт пользователя обратно в список к первому чекбоксу. По сути, мы сообщаем: <em>«Теперь, когда вы удалили этот элемент из списка, вот снова весь список».</em></p>
<p>Обычно я не добавляю стили фокуса для элементов, на которых можно сфокусироваться таким образом. В этом случае я использую этот метод из-за того, что целевой элемент не является интерактивным и не должен таким быть.</p>
<pre><code tabindex="0" class="language-css">[tabindex=&quot;-1&quot;] { outline: none }
</code></pre>
<p>После того, как элемент с фокусом был удалён (и вместе с ним его стиль фокуса), фокус перемещается на заголовок. Пользователь клавиатуры может затем нажать на <kbd>Tab</kbd>, чтобы найти первый чекбокс или, если не осталось элементов, перейти к текстовому полю в нижней части этого компонента.</p>
<h3>Обратная связь</h3>
<p>Возможно, мы дали достаточно информации пользователю и он в идеальном положении для продолжения взаимодействия с интерфейсом. Но всегда лучше быть максимально точным. Поскольку у нас уже есть интерактивная область, почему бы не использовать её для сообщения о том, элемент был успешно удалён?</p>
<pre><code tabindex="0" class="language-js">function deletedFeedback(todoName) {
    let liveRegion = document.querySelector('[role=&quot;status&quot;]');
    liveRegion.textContent = `${todoName} удалено.`;
}

// Пример использования
deletedFeedback(todoName);
</code></pre>
<p>Я понимаю, что вы, вероятно, не написали бы это на ванильном JavaScript. Это просто примерный вариант того, как это будет работать.</p>
<p>Сейчас, потому что мы используем <code>role=&quot;status&quot;</code> (<code>aria-live=&quot;polite&quot;</code>), со скринридерами происходит кое-что интересное. Сначала объявляется <em>«Мой список дел, заголовок второго уровня»</em>, а потом <em>«[название задачи] удалено».</em></p>
<p>Это происходит из-за того, что при значении <code>polite</code> интерактивные области ждут, пока пользователь не остановится, прежде чем сделать объявление. Если бы я использовал <code>role=&quot;alert&quot;</code> (<code>aria-live=&quot;assertive&quot;</code>), то сообщение о состоянии полностью или частично переопределило бы объявление заголовка с фокусом. В нашем случае пользователь знает и где он находится, и что его действие оказалось успешным.</p>
<h2>Рабочее демо</h2>
<p>Я создал <a href="https://codepen.io/heydon/pen/VpVNKW/">страницу на СodePen</a>, чтобы наглядно продемонстрировать технику из этой статьи. В демке используется Vue.js, но это можно реализовать с помощью любого JS-фреймворка. Её можно протестировать в разных браузерах и скринридерах.</p>
<iframe src="https://codepen.io/heydon/embed/VpVNKW/"></iframe>
<h2>Заключение</h2>
<p>При создании инклюзивного компонента со списком дел нужно учесть довольно много моментов: семантическую структуру, добавление подписей, иконки, работу с фокусом и обратную связь. Если это делает инклюзивный дизайн сложным для вас, то учтите следующее:</p>
<ol>
<li>Это новая информация. Не волнуйтесь, скоро это войдёт в привычку.</li>
<li>Всё, что вы узнали здесь, можно применить ко многим компонентам и его контенту.</li>
<li>Вам нужно только один раз создать такой компонент. Тогда он будет длительное время находиться в вашей библиотеке компонентов и его можно постоянно использовать.</li>
</ol>
<h2>Чеклист</h2>
<ul>
<li>Задайте для каждого крупного компонента, такого как список, понятный заголовок.</li>
<li>Добавляйте подписи к полям «только для скринридеров» только тогда, когда есть видимые подписи. Плейсхолдеры не в счёт.</li>
<li>Когда вы исключаете элемент с фокусом из DOM, сделайте фокус на соответствующем соседнем элементе с помощью метода <code>focus()</code>.</li>
<li>Внимательно отнеситесь к описаниям в пустых состояниях. Они знакомят новых пользователей с вашим интерфейсом.</li>
</ul>

                    ]]></description><pubDate>Mon, 18 Feb 2019 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/a-todo-list/</guid></item><item><title>Новые логические свойства в CSS!</title><link>https://web-standards.ru/articles/logical-css-props/</link><description><![CDATA[
                        <p>Следующий шаг в эволюции CSS</p>
<h2>Интро</h2>
<p>Большинство из нас, разработчиков, мыслят терминами право-лево и верх-низ. Всё потому что изначально интернет предназначался главным образом для загрузки документов, а не для сайтов со сложной структурой, которые мы создаём теперь. Потому никто в тот момент не учитывал потребностей многоязычных сайтов.</p>
<img src="https://web-standards.ru/articles/logical-css-props/images/1.png" alt="">
<p>До недавнего времени переменные в Sass были лучшим способом поддержки сайтов с языками, пишущимися в разные стороны (RTL и LTR). Если вы хотите узнать больше, то почитайте мою статью «<a href="https://medium.com/@elad/the-best-way-to-rtl-your-website-with-sass-105e34a4298a">The Best Way to RTL Websites with Sass!</a>»</p>
<p>Новые логические свойства дают нам гораздо больше возможностей управлять нашими сайтами, независимо от того, на каком они языке (английский, арабский, японский или другие), с минимальными изменениями стилей.</p>
<p>Самое время начать!</p>
<h2>Мыслить логическими CSS-свойствами</h2>
<p>Мы привыкли видеть что-то подобное, когда обсуждаем блочную модель:</p>
<img src="https://web-standards.ru/articles/logical-css-props/images/2.png" alt="">
<p>Раньше так было правильно, и есть до сих пор, но подходят последние деньки классических физических свойств типа <code>margin-left</code>, <code>padding-right</code>, <code>border-top</code> и других.</p>
<p>Прежде чем начать использовать новые логические свойства, вам нужно перестать думать терминами право-лево, верх-низ, и заменить из на <code>inline-start</code>, <code>inline-end</code> и <code>block-start</code>, <code>block-end</code>.</p>
<img src="https://web-standards.ru/articles/logical-css-props/images/3.png" alt="">
<h3>Строчная ось</h3>
<p>Давайте для примера возьмём английский язык. Направление чтения начинается слева и идёт направо. Это строчный аспект свойств. Это можно легко запомнить, рассмотрев ряд элементов с <code>display: inline</code>. Каждый элемент отображается в строку.</p>
<p>Например, <code>padding-inline-start</code> задаст отступ с той стороны, где начинается контент на текущем языке:</p>
<ul>
<li>Английский: <code>padding-inline-start</code> = <code>padding-left</code></li>
<li>Арабский: <code>padding-inline-start</code> = <code>padding-right</code></li>
<li>Японский: <code>padding-inline-start</code> = <code>padding-top</code></li>
</ul>
<h3>Блочная ось</h3>
<p>При замене верхних и нижних связанных свойств легко вспоминать, что верх находится в начале нашего сайта, а низ — в конце. Просто представьте несколько элементов с <code>display: block</code>, которые расположены друг над другом.</p>
<p>Возможно, вы всё ещё спрашиваете себя, а разве это не всегда так?!</p>
<p>Ответ чуточку сложнее. В настоящее время все сайты на любых языках работают именно таким образом. Просто потому что до сих пор не было других доступных методов.</p>
<p>Сайты на японском языке и некоторых других восточных языках идут справа налево, а не сверху вниз! Чтобы понять, каково это, представьте, что вы повернули экран на 90° вправо. Сайт приходится листать не по вертикали, а по горизонтали!</p>
<p>Пример блочных свойства:</p>
<ul>
<li>Английский и арабский: <code>padding-block-start</code> = <code>padding-top</code></li>
<li>Японский: <code>padding-block-start</code> = <code>padding-right</code></li>
</ul>
<img src="https://web-standards.ru/articles/logical-css-props/images/4.png" alt="">
<h2>Новые свойства блочной модели</h2>
<p><code>margin</code>, <code>padding</code> и <code>border</code></p>
<p>После того, как вы разобрались со строчной и блочной осями, вы можете использовать их по прямому назначению.</p>
<p>Пример для английского:</p>
<h3>margin</h3>
<ul>
<li><code>margin-block-start</code> = <code>margin-top</code></li>
<li><code>margin-block-end</code> = <code>margin-bottom</code></li>
<li><code>margin-inline-start</code> = <code>margin-left</code></li>
<li><code>margin-inline-end</code> = <code>margin-right</code></li>
</ul>
<h3>padding</h3>
<ul>
<li><code>padding-block-start</code> = <code>padding-top</code></li>
<li><code>padding-block-end</code> = <code>padding-bottom</code></li>
<li><code>padding-inline-start</code> = <code>padding-left</code></li>
<li><code>padding-inline-end</code> = <code>padding-right</code></li>
</ul>
<h3>border</h3>
<ul>
<li><code>border-block-start</code> = <code>border-top</code></li>
<li><code>border-block-end</code> = <code>border-bottom</code></li>
<li><code>border-inline-start</code> = <code>border-left</code></li>
<li><code>border-inline-end</code> = <code>border-right</code></li>
</ul>
<h2>Логическая величина</h2>
<p><code>width</code> и <code>height</code> заменяются на <code>inline-size</code> и <code>block-size</code></p>
<p>Свойства <code>height</code> и <code>width</code> также должны соответствовать этой новой методологии. Как только мы свыкнемся с методологией строка-блок, станет легче разобраться с размерами. Для английского языка свойство <code>width</code> следует заменить на <code>inline-size</code>, а свойство <code>height</code> — на <code>block-size</code>.</p>
<p>Пример строчного и блочного размеров:
Для английского и арабского (LTR и RTL)</p>
<ul>
<li><code>width</code> = <code>inline-size</code></li>
<li><code>height</code> = <code>block-size</code></li>
</ul>
<p>В языках, идущих сверху вниз, например, японском, мы столкнёмся с противоположным:</p>
<ul>
<li><code>inline-size</code> = <code>height</code></li>
<li><code>block-size</code> = <code>width</code></li>
</ul>
<p>Для минимальных и максимальных свойств просто добавьте <code>min</code> или <code>max</code> в начале. К примеру:</p>
<ul>
<li><code>min-inline-size: 300px</code></li>
<li><code>max-block-size: 100px</code></li>
</ul>
<img src="https://web-standards.ru/articles/logical-css-props/images/5.png" alt="">
<h3>Позиционирование в CSS</h3>
<p>Свойства, которые мы раньше использовали для позиционирования, <code>top</code>, <code>bottom</code>, <code>left</code>, <code>right</code>, превратились в новые свойства с префиксом <code>inset: inset-block-start</code>, <code>inset-block-end</code>, <code>inset-inline-start</code>, <code>inset-inline-end</code>.</p>
<p>Для английского (LTR):</p>
<ul>
<li><code>top</code> = <code>inset-block-start</code></li>
<li><code>bottom</code> = <code>inset-block-end</code></li>
<li><code>left</code> = <code>inset-inline-start</code></li>
<li><code>right</code> = <code>inset-inline-end</code></li>
</ul>
<pre><code tabindex="0" class="language-css">/* Старая техника */
.popup {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
}

/* Новая техника */
.popup {
    position: fixed;
    inset-block-start: 0;  /* top — для английского */
    inset-block-end: 0;    /* bottom — для английского */
    inset-inline-start: 0; /* left — для английского */
    inset-inline-end: 0;   /* right — для английского */
}
</code></pre>
<p>Бросив беглый взгляд, вы можете задаться вопросом, какого чёрта я должен использовать такие сложные имена?! Но на то есть веская причина. Новые имена свойств можно комбинировать в шорткаты, подобно текущим <code>padding</code>, <code>margin</code>, <code>border</code>.</p>
<p>Пример:</p>
<pre><code tabindex="0" class="language-css">.popup {
    position: fixed;
    inset: 0 0 0 0; /* top, right, bottom, left — для английского */
}
</code></pre>
<img src="https://web-standards.ru/articles/logical-css-props/images/6.png" alt="">
<img src="https://web-standards.ru/articles/logical-css-props/images/7.png" alt="">
<h3>Обтекание в CSS</h3>
<p>Обтекание довольно простое, есть всего два значения, <code>inline-start</code> и <code>inline-end</code>, которые заменяют собой <code>left</code> и <code>right</code>.</p>
<p>Для английского (LTR):</p>
<ul>
<li><code>float: left</code> = <code>float: inline-start</code></li>
<li><code>float: right</code> = <code>float: inline-end</code></li>
</ul>
<h3>Text-align</h3>
<p>Это свойство даже проще, чем обтекание, значения <code>left</code> и <code>right</code> заменяются на <code>start</code> и <code>end</code>.</p>
<p>Для английского (LTR):</p>
<ul>
<li><code>text-align: left</code> = <code>text-align: start</code></li>
<li><code>text-align: right</code> = <code>text-align: end</code></li>
</ul>
<h3>Ещё</h3>
<p>Свойство resize: используется в основном для <code>&lt;textarea&gt;</code>. Его значения изменятся с <code>horizontal</code> и <code>vertical</code> на <code>inline</code> и <code>block</code>.</p>
<p>Для английского (LTR):</p>
<ul>
<li><code>resize: horizontal</code> = <code>resize: inline</code></li>
<li><code>resize: vertical</code> = <code>resize: block</code></li>
</ul>
<p>У свойства <code>background-position</code> пока нет реализации ни в одном из браузеров, но если копнуть поглубже, то можно найти отсылки к <code>background-position-inline</code> и <code>background-position-block</code> на <a href="https://developer.mozilla.org/en-US/">MDN</a>. Ещё нет нормальной документации, но они работают над этим! :)</p>
<p>Прочее: уже сейчас можно предположить, что свойства типа <code>transform-origin</code> тоже будут обновлены, как и любые другие свойства, имеющие отношение к направлению.</p>
<h3>Гриды и флексбоксы</h3>
<p>Хорошая новость в том, что гриды и флексбоксы уже построены на новых логических свойствах и нет нужды их обновлять.</p>
<h3>Рабочий процесс с учётом логических свойств</h3>
<p>На первый взгляд это выглядит очень сложно. Но в работе всё просто. При написании стилей не нужно беспокоится о поддержке нескольких языков. Вы просто используете логические свойства взамен старых физических свойств.</p>
<h2>Применение выравнивания в зависимости от языка</h2>
<p>После того, как мы рассмотрели все обновлённые логические свойства, вот вам ещё два, которые позволят определить <strong>выравнивание блочной оси</strong> (поток сайта) и <strong>выравнивание строчной оси</strong> (направление чтения текста).</p>
<h3>Свойство <code>writing-mode</code> (блочная ось)</h3>
<p>Определились с потоком сайта. В большинстве случаев он будет идти сверху вниз, но, как уже упоминалось, для некоторых языков он может идти справа налево (японский) или даже слева направо (монгольский). В обоих случаях у нас будет горизонтальная прокрутка, а не вертикальная, как мы привыкли.</p>
<p>Примечание: на данный момент есть три основных значения для <code>writing-mode</code>. Их имена могут сбить с толку. Всё потому, что в них есть направление блочной оси плюс выравнивание текста (строчная ось). Это вгоняет в тоску, выравнивание текста тут явно избыточно и только вызывает путаницу.</p>
<p>Чтобы избежать этой путаницы, я рекомендую игнорировать часть значения со строчной осью и обращать внимание только на часть значения для блочной оси.</p>
<h3>Примеры</h3>
<p>Значения:</p>
<ul>
<li><code>writing-mode: horizontal-tb</code> — сверху вниз, как для английского (значение по умолчанию).</li>
<li><code>writing-mode: vertical-rl</code> — справа налево, для японского.</li>
<li><code>writing-mode: vertical-lr</code> — слева направо, для монгольского.</li>
</ul>
<p>Моё личное мнение — в значениях нужно оставить только <code>tb</code>, <code>rl</code>, <code>lr</code> (часть для блочной оси), чтобы устранить потенциальную путаницу.</p>
<p>Пример для японского:</p>
<pre><code tabindex="0" class="language-css">html {
    writing-mode: vertical-rl;
}
</code></pre>
<img src="https://web-standards.ru/articles/logical-css-props/images/8.png" alt="">
<h3>Свойство <code>direction</code> (строчная ось)</h3>
<p>Определяет, откуда должен начинаться текст: слева направо или справа налево, но только в случае, если задано значение по умолчанию для свойства <code>writing-mode</code>. Если мы поменяем значение <code>writing-mode</code> на один из вертикальных режимов, то фактическое направление написания изменится с положения слева направо и будет идти сверху вниз. Или наоборот, при значении с <code>rtl</code> (справа налево), оно изменится на сверху вниз.</p>
<p>Пример направления для арабского:</p>
<pre><code tabindex="0" class="language-css">html {
    direction: rtl;
}
</code></pre>
<p>Даже удивительно, на сколько просто сайт, идущий сверху вниз, можно преобразовать в идущий справа налево с горизонтальной прокруткой.</p>
<p>Я сделал небольшое демо. Его лучше смотреть в Firefox, в текущий момент именно в нём поддерживается наибольшее количество свойств.</p>
<p><a href="https://codepen.io/elad2412/pen/oQJmYQ/">Демо</a> (попробуйте поменять язык):</p>
<iframe src="https://codepen.io/elad2412/embed/oQJmYQ/"></iframe>
<h2>Браузерная поддержка</h2>
<ul>
<li>Все свойства блочной модели <code>margin</code>, <code>padding</code>, <code>border</code> и новые свойства ширины и высоты (<code>inline-size</code>, <code>block-size</code>) уже работают во всех основных браузерах, кроме Edge.</li>
<li>Новые значения для <code>text-align</code> также работают везде, кроме Edge.</li>
<li>Свойства и значения для <code>float</code>, <code>position</code>, <code>resize</code> работают пока только в Firefox.</li>
</ul>
<h2>Проблемы с логическими свойствами</h2>
<p>Со всеми этими новыми доработками мы столкнёмся с новыми для нас проблемами. Например, что если мы захотим записать все значения свойства в сокращённом виде: <code>margin: 10px 20px 8px 5px</code>? В таком случае мы не сможем предсказать, как это будет проинтерпретировано браузером. Если сайт построен на физических свойствах, то значения будут расшифрованы следующим образом: <code>margin-top</code>, <code>margin-right</code>, <code>margin-bottom</code>, <code>margin-left</code>. Но если сайт построен на новых логических свойствах, то значения будут расшифрованы так: <code>margin-block-start</code>, <code>margin-inline-end</code>, <code>margin-block-end</code>, <code>margin-inline-start</code>.</p>
<p>На сайтах, написанных под английский (или русский) языки, физические и логические свойства будут работать одинаково. Для сайтов на других языках значения сокращений, как в примере с <code>margin</code>, должны работать в соответствии со значением свойства <code>direction</code> или <code>writing-mode</code>.</p>
<p>Это вопрос всё ещё открыт. Я внёс предложение, которое <a href="https://github.com/w3c/csswg-drafts/issues/1282#issuecomment-443253091">может решить эту проблему</a>. Если у вас есть решение получше, то вы можете оставить комментарий в этой ветке!</p>
<p>В данный момент, если вы хотите использовать логические единицы, вам следует отказаться от шорткатов в пользу полных названий свойств.</p>
<p>Предложенное мною решение:</p>
<pre><code tabindex="0" class="language-css">html {
    flow-mode: physical;
    /* или */
    flow-mode: logical;
}

.box {
    /* будет интерпретироваться согласно значению flow-mode** ***/
    margin: 10px 5px 6px 3px;
    padding: 5px 10px 2px 7px;
}
</code></pre>
<h3>Проблемы с адаптивным дизайном</h3>
<p>Пока пытался создать рабочее демо, я попробовал использовать новое свойство максимальной ширины <code>max-inline-size</code> внутри медиавыражения, предполагая, что для языков «справа налево» и «слева направо» оно будет вести себя как <code>max-width</code>, а для языков вроде японского — как <code>max-height</code>. К сожалению, браузеры пока отказываются понимать это свойство внутри медиавыражений.</p>
<pre><code tabindex="0" class="language-css">/* Не работает */
@media (max-inline-size: 1000px) {
    .main-content {
        background: red;
        grid-template-columns: auto;
    }
}
</code></pre>
<h3>Изменения, которые нужно учесть</h3>
<p>Во время написания этого поста, уже после глубокого изучения и понимания концепции логических свойств, я заметил несколько упущенных моментов, которые следует поправить в будущем:</p>
<ul>
<li><code>line-height</code> заменить на <code>line-size</code></li>
<li><code>border-width</code> заменить на <code>border-size</code></li>
</ul>
<p>Но, похоже, пока не стоит этого ждать, по крайней мере в отношении <code>border-width</code>. Это свойство обновили буквально только что и в его названии по прежнему присутствует <code>width</code>. Пример: <code>border-block-start-width</code>.</p>
<p>Но кто знает, может этот пост попадётся на глаза правильным людям из W3C :)</p>
<h2>Резюмируя</h2>
<p>Вот и всё. Я надеюсь, что вам понравилась эта статья и вы узнали что-то новое. Я буду признателен, если вы поаплодируете или поделитесь этим постом :)</p>
<h3>Больше постов по типографике</h3>
<ul>
<li><a href="https://24ways.org/2016/css-writing-modes/">CSS Writing Modes</a> — крайне рекомендую!</li>
<li><a href="https://tympanus.net/codrops/css_reference/text-orientation/">Text Orientations</a></li>
</ul>
<h3>Другие мои посты о CSS</h3>
<ul>
<li><a href="https://medium.com/@elad/the-best-way-to-rtl-your-website-with-sass-105e34a4298a">The Best Way to RTL Websites with Sass!</a></li>
<li><a href="https://medium.com/@elad/css-architecture-for-multiple-websites-with-sass-7e923fc53f7a">CSS Architecture for Multiple Websites With Sass</a></li>
<li><a href="https://medium.com/@elad/becoming-a-css-grid-ninja-f4c6db018cc1">Becoming a CSS Grid Ninja!</a></li>
<li><a href="https://medium.com/@elad/the-new-responsive-design-evolution-2bfb9b504a4e">The New Responsive Design Evolution</a></li>
<li><a href="https://medium.com/web-standards/sticky-bc7ff7088693">Как на самом деле работает position: sticky в CSS</a></li>
</ul>

                    ]]></description><pubDate>Wed, 06 Feb 2019 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/logical-css-props/</guid></item><item><title>Как быстро создать десктопное приложение на HTML, CSS и JavaScript</title><link>https://web-standards.ru/articles/desktop-app/</link><description><![CDATA[
                        <figure>
    <img src="https://web-standards.ru/articles/desktop-app/images/1.jpg" alt="">
    <figcaption>
        Фотография <a href="https://unsplash.com/@robinpierre">Робина Пьерра</a>
    </figcaption>
</figure>
<p>Можно ли использовать HTML, CSS и JavaScript для создания десктопных приложений?</p>
<p>Ответ — да 😄</p>
<p>В этой статье мы сосредоточимся в основном на том, как Electron можно использовать для создания десктопных приложений с использованием веб-технологий.</p>
<h2>Electron</h2>
<p><a href="https://electronjs.org/">Electron</a> может быть использован для создания десктопных приложений, также эти приложения будут мультиплатформенными — Windows, Mac, Linux и другие.</p>
<p>Electron объединяет Chromium и Node.js в одну среду исполнения. Это позволяет нам запускать код HTML, CSS и JavaScript в виде десктопного приложения.</p>
<h2>Electron Forge</h2>
<p>Если Electron используется напрямую, то перед сборкой приложения потребуется определённая ручная настройка. Также, если вы хотите использовать Angular, React, Vue или любой другой фреймворк или библиотеку, вам нужна будет ручная настройка.</p>
<p><a href="https://electronforge.io/">Electron Forge</a> значительно упрощает все вышеперечисленное.</p>
<p>Он предоставляет шаблонные приложения с Angular, React, Vue и другими фреймворками, что позволяет избежать дополнительных танцев с бубном.</p>
<p>Также он обеспечивает простоту сборки и упаковки приложения. В нём есть множество других функций, которые можно найти в <a href="https://docs.electronforge.io/">документации</a>.</p>
<h2>Предварительная подготовка</h2>
<p>Убедитесь, что у вас установлен Node.js. Если нет, то его можно скачать <a href="https://nodejs.org/en/">отсюда</a>. Установите Electron Forge глобально, используя следующую команду:</p>
<pre><code tabindex="0" class="language-sh">npm install -g electron-forge
</code></pre>
<h2>Начнём с приложения</h2>
<p>Используйте следующую команду для создания вашего приложения:</p>
<pre><code tabindex="0">electron-forge init simple-desktop-app-electronjs
</code></pre>
<p>Где <em>simple-desktop-app-electronicjs</em> — это название приложения.</p>
<p>Потребуется некоторое время, чтобы команда, указанная выше, отработала. После завершения предыдущего процесса запустите приложение с помощью следующих команд:</p>
<pre><code tabindex="0" class="language-sh">cd simple-desktop-app-electronjs
npm i
npm start
</code></pre>
<p>Это должно открыть окно как на скрине ниже:</p>
<img src="https://web-standards.ru/articles/desktop-app/images/2.jpg" alt="">
<h2>Разберёмся в структуре и коде</h2>
<p>Приложение имеет определенную структуру папок. Здесь я перечислю некоторые важные моменты в этой структуре папок.</p>
<h3>package.json</h3>
<p>Содержит информацию о приложении, которое вы создаете, все зависимости, необходимые для приложения, и несколько скриптов. Некоторые из скриптов уже предварительно настроены, но вы также можете добавлять новые.</p>
<p>Путь config.forge содержит все конфигурации конкретно для Electron. Например, make-target используется для указания целевых файлов для различных платформ, таких как Windows, Mac или Linux.</p>
<p>Также в <code>package.json</code> есть <code>&quot;main&quot;: &quot;src/index.js&quot;</code>, который указывает, что <code>src/index.js</code> является входной точкой приложения.</p>
<h3>src/index.js</h3>
<p>Согласно <code>package.json</code>, <code>index.js</code> является основным скриптом. Процесс, который запускает основной скрипт, называется <strong>главным процессом.</strong> Таким образом, основной процесс запускает скрипт <code>index.js</code>.</p>
<p>Основной процесс нужен для отображения элементов интерфейса. Это делается путем создания страниц. Каждая созданная страница выполняется в процессе, называемом <strong>процессом отрисовки.</strong></p>
<h3>Главный процесс и процесс отрисовки</h3>
<p>Основное предназначение главного процесса — создание страниц с помощью экземпляра <code>BrowserWindow</code>. Экземпляр <code>BrowserWindow</code> использует процесс отрисовки для запуска каждой страницы.</p>
<p><strong>Любое приложение может иметь только один главный процесс, но много процессов визуализации.</strong></p>
<p>Также возможно взаимодействие между главным процессом и процессом отрисовки. Однако, я не буду останавливаться на этом в текущей статье.</p>
<figure>
    <img src="https://web-standards.ru/articles/desktop-app/images/3.png" alt="Архитектура Electron, показывающая главный процесс и процессы отрисовки. Названия файлов могут быть другими.">
    <figcaption>
        Архитектура Electron, показывающая главный процесс и процессы отрисовки. Названия файлов могут быть другими.
    </figcaption>
</figure>
<p><strong>abcd.html</strong> показан в качестве второй веб-страницы в приведенной выше архитектуре. Но в нашем коде у нас не будет второй веб-страницы.</p>
<h3>src/index.html</h3>
<p><code>index.js</code> загружает файл <code>index.html</code> в новый экземпляр <code>BrowserWindow</code>.</p>
<p>Это означает, что <code>index.js</code> создает новое окно GUI и загружает его со страницей <code>index.html</code>. Страница <code>index.html</code> запускается в своем собственном процессе отрисовки.</p>
<h3>Код в index.js с пояснениями</h3>
<p>Большая часть кода, созданного в <code>index.js</code>, содержит хорошие комментарии, объясняющие, что происходит. Здесь я упомяну несколько ключевых моментов, которые следует отметить в <code>index.js</code>:</p>
<pre><code tabindex="0" class="language-js">mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
});

// и загрузи index.html из app.
mainWindow.loadURL(`file://${__dirname}/index.html`);
</code></pre>
<p>Приведенный выше фрагмент кода просто создает экземпляр <code>BrowserWindow</code> и загружает <code>index.html</code> в <code>BrowserWindow</code>. Вы увидите, что <code>app</code> часто используется в коде. Например, возьмите приведенный ниже фрагмент:</p>
<pre><code tabindex="0" class="language-js">app.on('ready', createWindow);
</code></pre>
<p><code>app</code> используется для управления жизненным циклом событий приложения. Приведенный выше фрагмент кода говорит, что, когда приложение будет готово, нужно загрузить первое окно.</p>
<p>Точно так же <code>app</code> может использоваться для выполнения других действий с различными событиями. Например, его можно использовать для выполнения некоторых действий непосредственно перед закрытием приложения и так далее.</p>
<h2>Создадим десктопное приложение конвертера температур</h2>
<p>Давайте воспользуемся тем же приложением, которое мы создали ранее, и немного изменим его, чтобы создать приложение конвертера температуры.</p>
<p>Сначала давайте установим Bootstrap с помощью следующей команды:</p>
<pre><code tabindex="0" class="language-sh">npm install bootstrap --save
</code></pre>
<p>Скопируйте следующий код в src/index.html:</p>
<pre><code tabindex="0" class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
    &lt;head&gt;
        &lt;meta charset=&quot;utf-8&quot;&gt;
        &lt;title&gt;Конвертер температур&lt;/title&gt;
        &lt;link rel=&quot;stylesheet&quot; href=&quot;../node_modules/bootstrap/dist/css/bootstrap.min.css&quot;&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;h1&gt;Конвертер температур&lt;/h1&gt;
        &lt;div class=&quot;form-group col-md-3&quot;&gt;
            &lt;label for=&quot;celcius&quot;&gt;По Цельсию:&lt;/label&gt;
            &lt;input type=&quot;text&quot; class=&quot;form-control&quot; id=&quot;celcius&quot; onkeyup=&quot;celciusToFahrenheit()&quot;&gt;
        &lt;/div&gt;
        &lt;div class=&quot;form-group col-md-3&quot;&gt;
            &lt;label for=&quot;fahrenheit&quot;&gt;По Фаренгейту:&lt;/label&gt;
            &lt;input type=&quot;text&quot; class=&quot;form-control&quot; id=&quot;fahrenheit&quot; onkeyup=&quot;fahrenheitToCelcius()&quot;&gt;
        &lt;/div&gt;
        &lt;script src=&quot;renderer.js&quot;&gt;&lt;/script&gt;
    &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>Приведенный выше код выполняет следующие действия:</p>
<ol>
<li>Создаёт поле ввода текста с <code>id=&quot;celcius&quot;</code>. Всякий раз, когда в это поле что-то вводится, вызывается функция <code>celciusToFahrenheit()</code>.</li>
<li>Создаёт поле ввода текста с <code>id=&quot;fahrenheit&quot;</code>. Всякий раз, когда в это поле что-то вводится, вызывается функция <code>fahrenheitToCelcius()</code>.</li>
<li>Когда в поле ввода «По Цельсию» вводится значение, в поле «По Фаренгейту» показывается та же температура, но в Фаренгейтах.</li>
<li>Когда в поле ввода «По Фаренгейту» вводится значение, в поле «По Цельсию» показывается та же температура, но в Цельсиях.</li>
</ol>
<p>2 функции, которые выполняют преобразование температуры, будут храниться в <code>renderer.js</code>.</p>
<p>Создайте файл с именем <code>renderer.js</code> внутри <code>src</code>. Скопируйте в него следующий код:</p>
<pre><code tabindex="0" class="language-js">function celciusToFahrenheit(){
    let celcius = document.getElementById('celcius').value;
    let fahrenheit = (celcius * 9 / 5) + 32;
    document.getElementById('fahrenheit').value = fahrenheit;
}

function fahrenheitToCelcius(){
    let fahrenheit = document.getElementById('fahrenheit').value;
    let celcius = (fahrenheit - 32) * 5 / 9;
    document.getElementById('celcius').value = celcius;
}
</code></pre>
<p>Функция <code>celciusToFahrenheit()</code> считывает значение в текстовом поле «По Цельсию», преобразует его в градусы Фаренгейта и записывает новую температуру в текстовое поле «По Фаренгейту».</p>
<p>Функция <code>fahrenheitToCelcius()</code> делает ровно наоборот.</p>
<h2>Запускаем приложение</h2>
<p>Запустите приложение, используя следующую команду:</p>
<pre><code tabindex="0" class="language-sh">npm start
</code></pre>
<p>Должно открыться следующее окно. Попробуйте ввести разные значения в инпуты.</p>
<img src="https://web-standards.ru/articles/desktop-app/images/4.png" alt="">
<h2>Упаковываем приложение</h2>
<p>Команда для упаковки приложения:</p>
<pre><code tabindex="0" class="language-sh">npm run package
</code></pre>
<p>Выполнение этой команды потребует некоторого времени. Как только выполнение закончится, проверьте папку out в папке проекта.</p>
<p>Я проверил это на машине c Windows. Была создана папка с именем <code>simple-desktop-app-Electronjs-win32-x64</code> внутри папки <code>out</code>.</p>
<p>Таким образом, в папке <code>out/simple-desktop-app-Electronjs-win32-x64</code> команда создала файл <code>.exe</code> для этого приложения. Нажатие на исполняемый файл автоматически запускает десктопное приложение.</p>
<p><em>При создании приложения на macOS, папка внутри <code>out</code> называется <code>simple-desktop-app-Electronjs-darwin-x64</code> и создаётся файл <code>.app</code>, который работает точно так же — прим. переводчика.</em></p>
<p>Имя папки <code>simple-desktop-app-electronicjs-win32-x64</code> может быть разделено на <code>имя-платформа-архитектура</code>, где:</p>
<ul>
<li>имя — <code>simple-desktop-app-electronjs</code></li>
<li>платформа — <code>win32</code> <em>(darwin на macOS — прим. переводчика)</em></li>
<li>архитектура — <code>x64</code></li>
</ul>
<p>Когда вы запускаете команду без каких-либо параметров, по умолчанию она упаковывает пакеты для платформы, на которой вы разрабатываете.</p>
<p>Допустим, мы хотим пакет для другой платформы и архитектуры. Тогда вы можете использовать следующий синтаксис:</p>
<pre><code tabindex="0" class="language-sh">npm run package -- --platform=&lt;платформа&gt; arch=&lt;архитектура&gt;
</code></pre>
<p>Например, чтобы упаковать приложение для Linux, вы можете использовать следующую команду:</p>
<pre><code tabindex="0" class="language-sh">npm run package -- --platform=linux --arch=x64
</code></pre>
<p>Это создаст папку с именем <code>simple-desktop-app-electronicjs-linux-x64</code> внутри папки <code>out</code>.</p>
<h2>Создание файла make</h2>
<p>Чтобы создать файл <code>make</code> или установщик для приложения, используйте следующую команду:</p>
<pre><code tabindex="0" class="language-sh">npm run make
</code></pre>
<p>Потребуется некоторое время на выполнение этой команды. Как только процесс закончится, проверьте папку <code>out</code> в папке проекта.</p>
<p>В папке <code>out/make</code> будет создан установщик Windows для десктопного приложения <em>(или ZIP-архив на macOS — прим. переводчика).</em></p>
<p>Когда вы запускаете эту команду без каких-либо параметров, по умолчанию она создает установщик для платформы, которую вы используете для разработки.</p>
<h2>Код</h2>
<p>Код для этого приложения доступен <a href="https://github.com/aditya-sridhar/simple-desktop-app-electronjs">в моем репозитории GitHub</a>. <em>См. форк репозитория на русском: <a href="https://github.com/solarrust/simple-desktop-app-electronjs">solarrust/simple-desktop-app-electronjs</a> — прим. переводчика.</em></p>
<h2>Поздравляю 😄</h2>
<p>Теперь вы знаете, как создавать десктопные приложения с использованием HTML, CSS и JavaScript. В этой статье были рассмотрены основные понятия об Electron и Electron Forge. Почитайте <a href="https://docs.electronforge.io/">документацию</a> чтобы узнать больше.</p>
<h2>Об авторе</h2>
<p>Я люблю технологии и слежу за новинками. Мне также нравится помогать другим, делясь своими технологическими знаниями.</p>
<p>Не стесняйтесь связаться со мной через <a href="https://www.linkedin.com/in/aditya1811/">LinkedIn</a>, вы также можете подписаться на меня <a href="https://twitter.com/adityasridhar18">в Твитере</a> или зайти <a href="https://adityasridhar.com/">на мой сайт</a>.</p>

                    ]]></description><pubDate>Wed, 06 Feb 2019 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/desktop-app/</guid></item><item><title>CSS и JS в состоянии войны: как это остановить</title><link>https://web-standards.ru/articles/css-vs-js/</link><description><![CDATA[
                        <p><strong>Резюмируя</strong>: множество людей любят и JS и UX, CSS и т.д. Если мы перестанем раздавать ярлыки типа «JS-разработчик» или «UX-разработчик», то сможем добиться перемирия в текущей войне «JS против CSS».</p>
<h2>Война реальна</h2>
<p>Некоторые называют её «<a href="https://css-tricks.com/the-great-divide/">Великим расколом</a>»: линия фронта реальна; с приверженцами JavaScript с одной стороны и людьми из лагеря UX и CSS, которые пропагандируют подход «без-JS» к интерфейсам, с другой стороны.</p>
<p>Фронтенд-разработчики боятся, что потеряют работу если не будут следовать за хайпом вокруг JS. И это вполне логично: проводится значительно меньше конференций и митапов по CSS по сравнению с JS, React и иже с ними. Например, в Нью-Йорке существует больше шести JS-митапов и ноль регулярных CSS-митапов.</p>
<p>С другой стороны мы видим, что простые статические сайты слишком перегружены только из-за <a href="https://ru.wikipedia.org/wiki/%D0%A1%D0%B8%D0%BD%D0%B4%D1%80%D0%BE%D0%BC_%D1%83%D0%BF%D1%83%D1%89%D0%B5%D0%BD%D0%BD%D0%BE%D0%B9_%D0%B2%D1%8B%D0%B3%D0%BE%D0%B4%D1%8B">синдрома упущенной выгоды</a>.</p>
<p>Мы каждый день наблюдаем, как видные деятели фронтенд сообщества обвиняют друг друга. И это, мягко говоря, расстраивает.</p>
<h2>Выйти за рамки</h2>
<p>Воюющих часто разделяют на коалиции:</p>
<ol>
<li><strong>JS-JS-JS:</strong> разработчики, создающие SPA с клиентской частью на каком-нибудь JavaScript-фреймворке, типа React, Vue.js и Angular. Они являются активными пользователями всяческих инструментов для сборки (Babel, Webpack и т.д.) и JS-библиотек.</li>
<li><strong>UX-разработчики, CSS-разработчики, HTML-JS-CSS-разработчики:</strong> разработчики, создающие статичные сайты на ванильном JavaScript и чистом CSS. Доступность и быстродействие — главные темы обсуждений внутри комьюнити.</li>
</ol>
<p>Но <strong>существует</strong> ли этот раскол? Может этот дуализм основан только лишь на наших предрассудках?</p>
<p>На мой взгляд, эта предвзятость во многом обусловлена двумя вещами.</p>
<p>Во-первых, существует тренд разделять конференции по CSS и JS. Думаю, что это началось с очень популярного семейства ивентов JSConf и CSSConf и тенденции организации митапов Впиши-Свой-Город-Сюда.js. Западные платформы публикации контента поддерживают этот разлад: некоторые публикуют в основном статьи о React и JS в то время, как другие сфокусированы на CSS и UX.</p>
<p>Во-вторых, соцсети хороши в <a href="http://www.pewresearch.org/fact-tank/2016/01/27/the-demographic-trends-shaping-american-politics-in-2016-and-beyond/">поляризации сообщества</a>. Мы помещаем себя в пузырь единомышленников путём подписки на их фиды и ухудшаем ситуацию, делая перепосты только самых агрессивных мнений, приходящих <em>с той стороны.</em></p>
<p>Современный веб невероятно сложен. Крайне сложно освоить все необходимые для работы веба технологии и никто по-настоящему не может назвать себя на 100% фулстэк-разработчиком. Но из-за искусственно увеличенного разрыва между ветками обсуждения JS и CSS с UX люди с разными, но не обязательно противоположными увлечениями, сталкиваются с чёрно-белым взглядом на мир «JS против CSS». Разработчики на React, которые увлекаются CSS-анимацией и <a href="https://en.wikipedia.org/wiki/Computer_accessibility">a11y</a>, получают ярлык «фанат JS». И CSS-разработчик, который любит Babel и CSS-in-JS с без рантайма, всё ещё будет носить звание CSS-парня или девчонки.</p>
<h2>Люди, любящие обоих</h2>
<p>Как создатель <a href="https://postcss.org/">PostCSS</a>, я никогда не мог выбрать сторону даже если очень хотел этого. С одной стороны PostCSS это инструмент для CSS (что отражено в названии). С другой стороны PostCSS это инструмент сборки, написанный на <strong>JavaScript</strong>, а сборщики не особо в чести в современном сообществе CSS.</p>
<p>И я не одинок, есть ещё куча таких же людей: к примеру, создатель великолепных <a href="https://github.com/aholachek/react-flip-toolkit">инструментов для анимации в React</a> или создатель <a href="https://github.com/YozhikM/stylelint-a11y">CSS-линтера доступности</a>.</p>
<p>По правде говоря, каждый из нас знает лишь небольшое подмножество технологий. И чьи-то увлечения не обязаны лежать в одной области. Это нормально — любить и React и CSS одновременно. Или использовать сложную систему сборки чтобы быть уверенным, что твой продукт является доступным. Или может погрузиться в распределённые системы, чтобы сделать действительно <a href="https://github.com/logux">классный UX в условиях отсутствия интернета</a>.</p>
<p>Даже сами технологии не могут быть чёрно-белыми.</p>
<p>Сторонники «лагеря CSS» часто упоминают <a href="http://getbem.com/">БЭМ</a> как решение тех проблем, для которых создавался CSS-in-JS. Но не многие знают, что он, БЭМ, был спроектирован в <a href="https://yandex.com/company/">Яндексе</a> не как чисто CSS-технология. В него также входят JavaScript-фреймворк и изначально строился на ряде принципов, которые были позже использованы в React (например, вложенность маленьких изолированных компонентов).</p>
<p>Конфиги для ESLint, популярные в React-сообществе (по типу <a href="https://www.npmjs.com/package/eslint-config-airbnb">конфига AirBnB</a>), содержат <a href="https://github.com/evcohen/eslint-plugin-jsx-a11y#supported-rules">множество правил обеспечения доступности</a>.</p>
<h2>Решение</h2>
<p>Я считаю, что война реальна. Я думаю, что мы можем остановить эту войну, перестав разделять разработчиков на категории чёрного и белого.</p>
<ol>
<li>Если ты любишь технологии с обеих «сторон»: рассказывай об этом! Вытащи это на свет, дай людям возможность начать цивилизованное обсуждение. Тебе нравятся современные JS-фреймворки, но также тебе нравится создавать статические сайты, которые рендерятся на сервере? Скажи об этом миру. Независимые разработчики будут создавать больше фреймворков для статических сайтов, если будут видеть необходимость в них.</li>
<li>Давайте устроим публичный форум для обсуждений между мирами JS и CSS. Если вы организуете JavaScript-митап, то пусть в программе будет один доклад о CSS или UX. Пусть будут фронтенд-конференции вместо JS-конференций и CSS-конференций, где люди из разных лагерей смогут рассказать своим оппонентам о своих ежедневных проблемах и предпочитаемых решениях.</li>
<li>Давайте пробовать технологии, пришедшие с другой стороны:</li>
</ol>
<ul>
<li>Если вы CSS- или UX-разработчик, то начните с линтеров. <a href="https://stylelint.io/">Stylelint</a> отличный CSS-линтер для начала знакомства.Он будет предупреждать вам об ошибках и позволит делиться лучшими практиками внутри команды. И вы можете запустить его как плагин в вашем любимом текстовом редакторе, поэтому вам даже не нужен какой-либо сборщик.</li>
<li>Если вы React-разработчик — попробуйте ванильный JS на следующем лендинге или блоге. Это поможет лучше понять внутренности вашего фреймворка. А ваши юзеры скажут вам спасибо за более быструю загрузку за счёт более лёгкого бандла.</li>
</ul>
<h2>Что ещё почитать</h2>
<p>Моя статья <a href="https://evilmartians.com/chronicles/five-years-of-postcss-state-of-the-union">о будущем PostCSS, линтеров и CSS-in-JS</a> из <a href="https://evilmartians.com/chronicles/">Марсианских хроник</a>.</p>

                    ]]></description><pubDate>Mon, 04 Feb 2019 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/css-vs-js/</guid></item><item><title>HTML, CSS и исчезающие точки входа в индустрию</title><link>https://web-standards.ru/articles/vanishing-entry-points/</link><description><![CDATA[
                        <p>Все опять обозлились на CSS. Я даже не стану перечислять причины. В общей сложности всё сводится к тому, что CSS достаточно прост, но одновременно с этим и достаточно сложен. До такой степени, что, во избежание волнений общественности, проще завернуть его в JavaScript. Вы можете прочитать более осмысленный взгляд Криса Койера <a href="https://css-tricks.com/the-great-divide/">«The Great Divide»</a>.</p>
<p>Подобные споры об инструментах, фреймворках и технологиях возникают повсеместно. Я постоянно наблюдаю их те 20 лет, что работаю в вебе. Стандартные технологии де-факто имеют свои ограничения, мы сталкиваемся с проблемами и хотим их решить. Частенько мы решаем эти проблемы просто выбрасывая всё. Старые вещи ужасны и были изобретены когда не было лучших альтернатив! Теперь, имея все свои знания, мы можем сделать лучше. Давайте переизобретём колесо!</p>
<p>Мы можем наблюдать подобное в мире хранилищ данных, где люди всячески избегают реляционных баз данных, хотя чаще всего <a href="https://twitter.com/simonw/status/1089554577723056128?s=20">именно они им и нужны</a>.</p>
<p>Мы видим это в стремлении к статическим сайтам. Всё начинается под предлогом экономии скорости отрисовки при отсутствии базы данных и заканчивается воссозданием баз данных в файловой системе или использованием сторонних сервисов в попытках заткнуть дыры, с которыми отлично справилась бы традиционная CMS.</p>
<p>В обоих описанных сценариях есть исключения, когда альтернатива в виде РСУБД (<a href="https://ru.wikipedia.org/wiki/%D0%A0%D0%B5%D0%BB%D1%8F%D1%86%D0%B8%D0%BE%D0%BD%D0%BD%D0%B0%D1%8F_%D0%A1%D0%A3%D0%91%D0%94">Реляционная СУБД</a>) является правильным решением. Статические сайты отлично подходят для некоторых типов контента. Это хорошее решение определённой части проблем. Однако я видела много ситуаций, в которых попытка использовать новейшие технологии приводила проект в упадок и в конечном итоге требовался дорогостоящий рефакторинг или полная переделка.</p>
<p>Это постоянное переосмысление колеса — наша Ахиллесова пята. Мы всегда оптимистичны и надеемся, что из этого выйдет что-то хорошее, но чаще всего это просто приводит к беспорядку. Команды раз за разом сталкиваются с проектами, которые никто не в силах исправить, потому что они, эти проекты, основаны на цепочке малоизвестных технологий. Компании часто получают сайты, разработанные подрядчиками на технологиях, которые быстро канули в лету. И когда бизнес хочет что-то обновить, то новый подрядчик предлагает ему переделать всё заново.</p>
<p>Однако, когда дело доходит до фреймворков и подходов, которые усложняют написание HTML и CSS, проблема становится чуть более существенной, чем бизнес, теряющий пару лет работы и попадающий на полную переделку по причине того, что не может поддерживать опрометчиво выбранный фреймворк.</p>
<p>Все обсуждения HTML и CSS напрямую сказываются на точке входа в профессию разработчика. Не важно, фронтендер ты или бэкендер, большинство из нас с вами, не имеющих за плечами высшего <em>технического</em> образования, начали своё знакомство с профессией с простых в изучении HTML и CSS. Волшебное чувство видеть, как твой код работает на реальной странице! Мы уже потеряли большинство точек входе, которые у нас были. Исчезли родительские форумы, где люди учат друг друга HTML и CSS в попытках создать семейный альбом. Эти люди теперь использую Facebook или, возможно, завели блог со стандартной темой оформления на Wordpress.com или SquareSpace. Пропали люди, кастомизирующие свои странички на MySpace или изучающие HTML на <a href="https://www.reddit.com/r/neopets/comments/25nmni/how_many_people_started_coding_because_of_neopets/">Neopets</a>. Больше нет людей, в основном женщин, попадавших в индустрию потому что были вынуждены изучить HTML, ведь ведение сайта организации являлось частью рабочих обязанностей администратора.</p>
<p>Как подчёркивает <a href="https://twitter.com/betsythemuffin/status/1090342513054007296">этот классный тред</a>, входной точкой для большинства людей без традиционного образования стали буткемпы. Как правило там преподаётся стиль разработки, основанный на фреймворке, который позволит учащимся как можно быстрее получить работу. Однако, на основании вопросов от людей, прошедших подобное обучение, я вижу, что основы часто пропускаются и откладываются до лучших времён. Мы оказываем этим людям «медвежью услугу», если они впоследствии попадают в среду, где до основ дело никогда не дойдёт или, что ещё хуже, где HTML и CSS полностью обесценены. Из-за своих знаний HTML и CSS я спокойно реагирую на любые изменения в вебе. Я по опыту знаю, что эти знания позволят мне легко переключится и быстро освоить любые, построенные вокруг этих технологий, новинки.</p>
<p>Есть что-то удивительное в том, что, несмотря на всё, что мы насоздавали за последние 20 лет, я могу за день научить любого новичка создавать страничку на HTML и CSS. Нам не потребуется говорить об инструментах и фреймворках, изучать как создавать пулреквест или как перед стартом перетащить на компьютер кучу кода с помощью npm. Нам понадобится только текстовый редактор и пара часов. <em>И на нашей страничке уже что-то появится.</em></p>
<p>Это реальная точка входа и да, если работа является их целью, то в 2019 году им придётся быстро переключиться на инструменты и технологии, которые востребованы у работодателей. Но так или иначе, все эти инструменты в конце выдают HTML и CSS. Это основа всего, что мы делаем. И это делает маловероятным обесценивание тех, кто обладает действительно глубокими знаниями.</p>
<p>Если у вас есть практические знания проблем, с которыми вы сталкивались при работе с CSS, то вы сможете создать действительно классное решение этих проблем на JavaScript. Многие из нас, кто работает с CSS, хотят услышать ваши идеи. Однако, если вы начнёте наш разговор с признания, что на самом деле не изучали CSS до того, как предложить ему замену или если я увижу, что вы унижаете меня (или других людей) за незнание выбранного вами фреймворка, то я буду более скептически относится к вашим предложениям. Я не буду помогать вам строить мир, в который такой человек как я никогда не смог бы попасть.</p>
<blockquote>
<p>Когда вы делаете навыки программирования ключевым компонентом роли каждого веб-разработчика, вы превращаете веб в песочницу для программистов. Это когда вы начинаете мыслить фабриками объектов и, жаловаться «почему пользователь не может обновиться» и обесценивать такие «мягкие и женственные» навыки, как дизайн.</p>
<p>Задаёте потрясающий вопрос <strong>«а как ещё мы должны строить сложные приложения»</strong>, когда вообще никто не говорит об этом. По сути, спорите о том, как повысить базовый уровень создания <strong>всего</strong> до такого, который полностью исключается человек.
<a href="https://twitter.com/sonniesedge/status/1089801399788101632">Чарли Оуэн @sonniesedge</a></p>
</blockquote>
<p>Возможно я из «старой гвардии», но если вы думаете, что я не способна изучить React и потому так яростно отстаиваю <em>свой путь</em>, то, прошу вас, остановитесь. Посмотри я на всё это, будучи 22 лет отроду, я бы развернулась и убежала прочь. Если мы сделаем так, что для старта потребуется понимание программирования, то мы передадим всю власть тем, кто уже находится в привилегированной группе. У меня ещё много сил для борьбы с этим.</p>

                    ]]></description><pubDate>Fri, 01 Feb 2019 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/vanishing-entry-points/</guid></item><item><title>Как на самом деле работает position: sticky в CSS</title><link>https://web-standards.ru/articles/position-sticky/</link><description><![CDATA[
                        <p>У <code>position: sticky</code> уже очень неплохая браузерная поддержка, но большинство разработчиков так и не используют это свойство.</p>
<p>У этого есть две основные причины: во-первых, браузерам потребовалось много времени на реализацию адекватной поддержки этого свойства. И все просто успели забыть о нём.</p>
<p>Во-вторых, многие разработчики до конца не понимают логику, по которой это свойство работает. И тут появляюсь я!</p>
<img src="https://web-standards.ru/articles/position-sticky/images/1.png" alt="">
<p>Я полагаю, что вы хорошо знакомы с позиционированием в CSS, но давайте кратко повторим основные моменты:</p>
<p>Ещё три года назад существовало четыре типа позиционирования: <code>static</code>, <code>relative</code>, <code>absolute</code> и <code>fixed</code>.</p>
<p>Основное различие между <code>static</code> и <code>relative</code>, <code>absolute</code> и <code>fixed</code> в том, какое место они занимают в потоке документа (DOM). Элементы с позицией <code>static</code> и <code>relative</code> сохраняют своё естественное положение в потоке документа, в то время как <code>absolute</code> и <code>fixed</code> «вырываются» из потока документа и становятся плавающими.</p>
<p>Новое значение <code>sticky</code> похоже на все предыдущие значения сразу. Я проиллюстрирую это чуть позже.</p>
<h3>Моё первое знакомство с «липким» позиционированием</h3>
<p>Думаю, что большинство из вас игрались с «липким» позиционированием. Так было и у меня, пока в один момент я не осознал, что совсем не понимаю это свойство.</p>
<p>При первом знакомстве с <code>position: sticky</code> все быстро понимают, что элемент <em>залипает</em>, когда область просмотра достигает определённой позиции.</p>
<p>Пример:</p>
<pre><code tabindex="0" class="language-css">.some-component {
    position: sticky;
    top: 0;
}
</code></pre>
<p>Проблема в том, что иногда это работает, а иногда нет. Когда всё работает, то элемент и правда залипает. Но когда не работает, то при прокрутке элемент перестаёт залипать. Как человеку, который живёт одним только CSS, мне было важно разобраться в сути проблемы. Именно поэтому я решил тщательно изучить <strong>«липкое» позиционирование.</strong></p>
<h3>«Липкая» разведка</h3>
<p>Во время своих экспериментов я заметил, что если элемент с <code>position: sticky</code> является единственным ребёнком своего родителя-обёртки, то этот «липкий» элемент не залипает.</p>
<pre><code tabindex="0" class="language-html">&lt;!-- НЕ РАБОТАЕТ!!! --&gt;
&lt;style&gt;
    .sticky {
        position: sticky;
        top: 0;
    }
&lt;/style&gt;

&lt;div class=&quot;wrapper&quot;&gt;
    &lt;div class=&quot;sticky&quot;&gt;
        Некий контент
    &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<p>Когда я добавлял больше элементов внутрь родителя-обёртки всё начинало работать как ожидалось.</p>
<p><strong>Почему так происходит?</strong></p>
<p>Причина кроется в том, что элемент с <code>position: sticky</code> может перемещаться только в пределах контейнера, в котором находится. А поскольку в моём случае он был единственным ребёнком, у него не было элементов-братьев, поверх которых он мог перемещаться.</p>
<h3>Как на самом деле работает position: sticky в CSS</h3>
<p>«Липкое» позиционирование состоит из двух основных частей: <strong>«липкого» элемента</strong> и <strong>«липкого» контейнера.</strong></p>
<p><strong>«Липкий» элемент</strong> — это элемент, которому мы задали <code>position: sticky</code>. Элемент будет становиться плавающим, как только область видимости достигнет определённой позиции, например <code>top: 0px</code>.</p>
<p>Пример:</p>
<pre><code tabindex="0" class="language-css">.some-component {
    position: sticky;
    top: 0px;
}
</code></pre>
<p><strong>«Липкий» контейнер</strong> — это HTML-элемент, который оборачивает «липкий» элемент. Это максимальная область, в которой может перемещаться наш элемент.</p>
<p><strong>Когда вы задаёте элементу <code>position: sticky</code>, его родитель автоматически становится «липким» контейнером!</strong>
Очень важно это запомнить! Контейнер будет являться областью видимости для элемента. «Липкий» элемент не может выйти за пределы своего «липкого» контейнера.</p>
<p>В этом причина, почему в предыдущем примере «липкий» элемент не залипал: он был единственным дочерним элементом контейнера.</p>
<p>Наглядный пример:</p>
<img src="https://web-standards.ru/articles/position-sticky/images/2.png" alt="">
<figure>
    <iframe src="https://codepen.io/elad2412/embed/preview/MZZVjw"></iframe>
    <figcaption>
        <a href="https://codepen.io/elad2412/pen/QYLEdK">Пример на CodePen</a>.
    </figcaption>
</figure>
<h3>Понимание «липкого» поведения</h3>
<p>Как я и говорил, <code>position: sticky</code> ведёт себя не так, как другие типы позиционирования. Но, с другой стороны, у них есть определённые сходства. Позвольте мне пояснить:</p>
<p>Относительное (или статичное) — «липкий» элемент похож на элемент со статическим или относительным позиционированием поскольку сохраняет свою естественную позицию в DOM (остаётся в потоке).
Фиксированное — когда элемент залипает, то ведёт себя как будто у него заданы стили <code>position: fixed</code>, остаётся на той же позиции в области видимости и вырывается из потока документа.
Абсолютное — в конце доступной для перемещений области элемент останавливается и остаётся поверх другого элемента. Точно также, как ведёт себя абсолютно спозиционированный элемент в контейнере с <code>position: relative</code>.</p>
<h2>Залипает внизу?!</h2>
<p>В большинстве случаев вы будете использовать <code>position: sticky</code> чтобы прикрепить элемент к верхнему краю страницы. Что-то вроде этого:</p>
<pre><code tabindex="0" class="language-css">.component {
    position: sticky;
    top: 0;
}
</code></pre>
<p>Именно для таких сценариев и был создан этот тип позиционирования. До его появления такой трюк приходилось проворачивать с помощью JavaScript.</p>
<p>Но вы с тем же успехом можете использовать это свойство для того, чтобы прилепить элемент к нижней границе. Это значит, что футеру можно задать «липкое» позиционирование и при скролле он всегда будет залипать у нижнего края. И когда мы дойдём до конца «липкого» контейнера наш элемент остановится на своей естественной позиции. Лучше использовать эту особенность для элементов, находящихся в самом конце контейнера.</p>
<p>Полный пример:</p>
<p>HTML</p>
<pre><code tabindex="0" class="language-html">&lt;main class=&quot;main-container&quot;&gt;
    &lt;header class=&quot;main-header&quot;&gt;HEADER&lt;/header&gt;
    &lt;div class=&quot;main-content&quot;&gt;MAIN CONTENT&lt;/div&gt;
    &lt;footer class=&quot;main-footer&quot;&gt;FOOTER&lt;/footer&gt;
&lt;/main&gt;
</code></pre>
<p>CSS</p>
<pre><code tabindex="0" class="language-css">.main-footer {
    position: sticky;
    bottom: 0;
}
</code></pre>
<figure>
    <iframe src="https://codepen.io/elad2412/embed/preview/MZZVjw"></iframe>
    <figcaption>
        <a href="https://codepen.io/elad2412/pen/MZZVjw">Пример на CodePen</a>.
    </figcaption>
</figure>
<p>В реальной жизни я использую такое поведение для сводных таблиц. И, я думаю, с помощью этого приёма можно реализовать «липкую» навигацию в футере.</p>
<h2>Браузерная поддержка</h2>
<ul>
<li>«Липкое» позиционирование поддерживается всеми основными современными браузерами. Исключение: старый-добрый IE.</li>
<li>Для Safari потребуется префикс <code>-webkit</code></li>
</ul>
<pre><code tabindex="0">position: -webkit-sticky; /* Safari */
position: sticky;
</code></pre>
<figure>
    <img src="https://web-standards.ru/articles/position-sticky/images/3.png" alt="">
    <figcaption>
        Более 86% браузеров поддерживает sticky по данным <a href="https://caniuse.com/#search=sticky">Can I Use</a>.
    </figcaption>
</figure>
<h2>В заключение</h2>
<p>Вот и всё. Я надеюсь, что вам понравилась эта статья и мне удалось поделиться своим опытом. Я буду признателен, если вы поделитесь этим постом и поаплодируйте.</p>
<h3>Другие мои посты о CSS</h3>
<ul>
<li><a href="https://medium.com/@elad/new-css-logical-properties-bc6945311ce7">New CSS Logical Properties!</a></li>
<li><a href="https://medium.com/@elad/becoming-a-css-grid-ninja-f4c6db018cc1">Becoming a CSS Grid Ninja!</a></li>
<li><a href="https://medium.com/@elad/the-new-responsive-design-evolution-2bfb9b504a4e">The New Responsive Design Evolution</a></li>
<li><a href="https://medium.com/@elad/the-best-way-to-rtl-your-website-with-sass-105e34a4298a">The Best Way to RTL Websites with Sass!</a></li>
<li><a href="https://medium.com/@elad/css-architecture-for-multiple-websites-with-sass-7e923fc53f7a">CSS Architecture for Multiple Websites With Sass</a></li>
</ul>

                    ]]></description><pubDate>Thu, 31 Jan 2019 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/position-sticky/</guid></item><item><title>Что нового в JavaScript 2019</title><link>https://web-standards.ru/articles/new-in-js2019/</link><description><![CDATA[
                        <p>На протяжении последних лет JavaScript развивался и получал новые фичи. Если вам любопытно, чего ждать от новой версии JavaScript, то этот пост для вас!</p>
<p>Перед тем, как мы поговорим о новых возможностях, важно понять как новые идеи становятся частью языка JavaScript.</p>
<h2>Процесс для новых возможностей JavaScript</h2>
<p>Если коротко, то спецификация языка, которая управляет JavaScript, называется <a href="https://www.ecma-international.org/publications/standards/Ecma-262.htm">ECMAScript</a>. Группа Ecma International, которая рассматривает и принимает изменения в спецификацию языка, называется Технический комитет 39 или <a href="https://www.ecma-international.org/memento/tc39.htm">TC39</a>. Изменения в спецификацию ECMAScript проходят через <a href="https://tc39.github.io/process-document/">стандартизированный процесс</a>, включая стадии становления.</p>
<ul>
<li>Этап 0: идеи</li>
<li>Этап 1: официальное предложение</li>
<li>Этап 2: черновики</li>
<li>Этап 3: кандидаты</li>
<li>Этап 4: одобренные</li>
</ul>
<p>До тех пор, пока нововведение не достигнет 4 этапа, нет никаких гарантий, что оно станет частью спецификации ECMAScript. Тем не менее, некоторые JavaScript-движки, такие, как <a href="https://v8.dev/">V8</a> (используется в Сhrome and Node.js) и <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey">SpiderMonkey</a> от Firefox, могут добавить поддержку предложенных возможностей <em>до того</em>, как они перешли на этап 4. Так разработчики могут протестировать их и дать фидбэк.</p>
<h2>Текущие кандидаты в ECMAScript2019</h2>
<p>В момент написания этой статьи уже существует список <a href="https://github.com/tc39/proposals">предложений TC39</a>, которые находятся на 4 этапе. И тем не менее есть несколько кандидатов на 3 этапе.</p>
<p><em><strong>Дисклеймер:</strong> поскольку есть несколько кандидатов на этапе 3, финальный вариант ECMAScript2019 может не включать в себя все перечисленные предложения. По факту некоторые из этих предложений находятся на рассмотрении годами. Кроме того, реализации новых возможностей могут выглядеть или вести себя не так, как описано в текущих кандидатах.</em></p>
<h2>Изменения в классах JavaScript</h2>
<p>Есть несколько предложений по изменениям, которые следует внести в классы, включая <a href="https://github.com/tc39/proposal-class-fields">строковую декларацию</a>, <a href="https://github.com/tc39/proposal-private-methods">приватные методы и поля</a> и <a href="https://github.com/tc39/proposal-static-class-features/">статические методы и поля</a>.</p>
<pre><code tabindex="0" class="language-js">class Truck extends Automobile {
    model = 'Очень мощный'; // Объявление публичного поля
    #numberOfSeats = 5; // Объявление приватного поля
    #isCrewCab = true;
    static #name = 'Грузовик'; // Статическое приватное поле

    // Статичный метод
    static formattedName() {
        // Обратите внимание, что имя класса Truck используется
        // вместо this чтобы получить доступ к статическому полю
        return `Это автомобиль ${ Truck.#name }.`;
    }

    constructor( model, seats = 2 ) {
        super();
        this.seats = seats;
    }

    // Приватный метод
    #getBodyType() {
        return this.#isCrewCab ?
            'Двойная кабина' : 'Стандартная кабина';
    }

    bodyType() {
        return `${ this.#numberOfSeats }-passenger ${ this.model } ${ this.#getBodyType() }`;
    }

    get seats() { return this.#numberOfSeats; }
    set seats( value ) {
        if ( value &gt;= 1 &amp;&amp; value &lt; 7 ) {
            this.#numberOfSeats = value;
            this.#isCrewCab = value &gt; 3;
        }
    }
}
</code></pre>
<p>Лично мне не очень нравится синтаксис <code>#</code> для приватных полей и методов. Я бы предпочёл чтобы в спецификации JavaScript использовалось ключевое слово <code>private</code> для этих целей по аналогии с другими языками.</p>
<h2>Строковые методы <code>trimStart()</code> и <code>trimEnd()</code></h2>
<p>У типа данных <code>String</code> существует метод <code>trim()</code>, с помощью которого можно удалить пробелы с обоих концов строки. <a href="https://github.com/tc39/proposal-string-left-right-trim">Предложено</a> ввести методы <code>trimStart()</code> и <code>trimEnd()</code>, которые дадут больше контроля над удалением пробелов.</p>
<pre><code tabindex="0" class="language-js">const one = '      Привет и позвольте ';
const two = 'нам начать.        ';
console.log( one.trimStart() + two.trimEnd() )
// &quot;Привет и позвольте нам начать.&quot;
</code></pre>
<p>Интересный факт: эта возможность языка <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimEnd#Browser_compatibility">уже реализована</a> во многих JavaScript-движках. Один из случаев когда браузеры помогают развивать язык.</p>
<h2>Большие числа с <code>BigInt</code></h2>
<p>Мы видим примитив <a href="https://github.com/tc39/proposal-bigint">BigInt</a> для целых чисел, превышающих текущее максимальное значение 253. <code>BigInt</code> может быть объявлен несколькими различными способами.</p>
<pre><code tabindex="0" class="language-js">// Референс
const theBiggestIntegerToday = Number.MAX_SAFE_INTEGER;
// 9007199254740991

// Используем синтаксис 'n' для объявления BigInt
const ABiggerInteger = 9100000000000001n;

// Используем конструктор BigInt()
const EvenBigger = BigInt( 9100000000000002 );
// 9100000000000002n

// Используем конструктор BigInt() со строкой
const SuchBigWow = BigInt( '9100000000000003' );
// 9100000000000003n
</code></pre>
<p><a href="https://developers.google.com/web/updates/2018/05/bigint">Узнайте больше</a> о вариантах использования и фишках <code>BigInt</code>.</p>
<h2>Одномерные массивы с <code>flat()</code> и <code>flatMap()</code></h2>
<p>Если вы изучали функциональное программирование, то <a href="https://github.com/tc39/proposal-flatMap">вы точно узнаете</a> <code>flat()</code> и <code>flatMap()</code>. <code>flat()</code> принимает массив значений, который может состоять в том числе из других массивов, и возвращает новый одномерный массив.</p>
<pre><code tabindex="0" class="language-js">const nestedArraysOhMy = ['a', ['b', 'c'], ['d', ['e', 'f']]];
// .flat() принимает необязательный аргумент глубины
const ahhThatsBetter = nestedArraysOhMy.flat(2);
console.log( ahhThatsBetter );
// ['a', 'b', 'c', 'd', 'e', 'f']
</code></pre>
<p><code>flatMap()</code> похож на <code>map()</code>, но колбэк может вернуть массив и в результате получится плоский одномерный массив вместо вложенных массивов.</p>
<pre><code tabindex="0" class="language-js">const scattered = ['мой любимый', 'бутерброд', 'это', 'сэндвич с курицей'];

// Обычный map() вернёт вложенные массивы
const huh = scattered.map(chunk =&gt; chunk.split(' '));
console.log(huh);
// [['мой', 'любимый'], ['бутерброд'], ['это'], ['сэндвич', 'с', 'курицей']]

// flatMap() объединяет возвращаемые массивы
const better = scattered.flatMap( chunk =&gt; chunk.split( ' ' ) );
console.log(better);
// ['мой', 'любимый', 'бутерброд', 'это', 'сэндвич', 'с', 'курицей']
</code></pre>
<h2>Другие предложения-кандидаты в ES2019</h2>
<p>Ниже представлен список дополнительных кандидатов, которые находятся на 3 этапе в момент написания статьи.</p>
<ul>
<li>Стандартизированный объект <a href="https://github.com/tc39/proposal-global"><code>globalThis</code></a></li>
<li>Динамический <a href="https://github.com/tc39/proposal-dynamic-import"><code>import()</code></a></li>
<li>Нововведения в <a href="https://github.com/tc39/proposal-regexp-legacy-features">устаревший RegExp</a></li>
<li><a href="https://github.com/tc39/proposal-import-meta"><code>import.meta</code></a></li>
<li>Строковый метод <a href="https://github.com/tc39/proposal-string-matchall"><code>matchAll()</code></a></li>
<li><a href="https://github.com/tc39/proposal-object-from-entries"><code>Object.fromEntries()</code></a></li>
<li>Правильный <a href="https://github.com/tc39/proposal-well-formed-stringify"><code>JSON.stringify</code></a></li>
<li>Стандартизированный <a href="https://github.com/tc39/proposal-hashbang">Hashbang</a> для приложений с интерфейсом командной строки (CLI)</li>
</ul>
<h2>Когда ожидать ES2019</h2>
<p>В последние годы TC39 выпускает обновлённую спецификацию ECMA-262 языка ECMAScript в июне. Всё говорит о том, что и ES2019 мы увидим в июне.</p>
<h2>Попробуйте новинки ES2019 уже сегодня</h2>
<p>Некоторые из описанных выше новинок языка уже реализованы в JavaScript-движках и утилитах. Они могут быть отключены по умолчанию, но легко включаются в настройках.</p>
<h2>Потестируйте в последней версии Node.js</h2>
<p><a href="https://nodejs.org/">Node.js</a> использует движок Chrome V8. Некоторые кандидаты могут быть использованы в Node.js потому что V8 уже поддерживает их (например, <code>Array.prototype.flat</code> и <code>String.prototype.trimEnd</code>).</p>
<p>Вы можете включить другие фичи языка при помощи <code>--harmony-{feature-flag}</code> в параметрах командной строки. Чтобы проверить, какие флаги поддерживает ваша версия Node.js, используйте опцию <code>--v8-options</code> для получения списка. Некоторые кандидаты будут помечены «in progress» <em>(прим. переводчика: в процессе).</em></p>
<p>MacOS или Linux:</p>
<pre><code tabindex="0" class="language-sh">node --v8-options | grep &quot;in progress&quot;
</code></pre>
<p>Windows:</p>
<pre><code tabindex="0" class="language-sh">node --v8-options | find &quot;in progress&quot;
</code></pre>
<p>Например, чтобы запустить приложение Node.js с поддержкой строковой декларации и статических методов, вы можете использовать следующие опции CLI.</p>
<pre><code tabindex="0" class="language-sh">node --harmony-class-fields --harmony-static-fields index.js
</code></pre>
<h2>Тестируйте с Babel 7.0+</h2>
<p><a href="https://babeljs.io/">Babel</a> это утилита JavaScript, которая позволяет вам использовать последние возможности языка, даже если они ещё не поддерживаются всеми браузерами и окружающими средами. Вы пишите «современный» JavaScript, а Babel переводит ваш код в синтаксис, который поддерживается старыми движками.</p>
<p>Babel поддерживает экспериментальные возможности языка при помощи <a href="https://babeljs.io/docs/en/plugins">плагинов</a>. Babel публикует список поддерживаемых возможностей ECMAScript в своём официальном репозитории.</p>
<h2>Что ещё почитать о JavaScript и ES Next</h2>
<p>Хотите знать больше о JavaScript? Посетите некоторые из этих полезных ресурсов.</p>
<ul>
<li><a href="https://developer.okta.com/blog/2018/12/19/learn-javascript-in-2019">Learn JavaScript in 2019!</a></li>
<li><a href="https://developer.okta.com/blog/2019/01/16/history-and-future-of-async-javascript">The History (and Future) of Asynchronous JavaScript</a></li>
<li><a href="https://scotch.io/tutorials/build-a-secure-nodejs-application-with-javascript-async-await-using-hapi">Build a Secure Node.js Application with JavaScript Async Await Using Hapi</a></li>
<li><a href="https://developer.okta.com/blog/2018/11/15/node-express-typescript">Use TypeScript to Build a Node API with Express</a></li>
</ul>
<p>Если вам будет интересно, то вы можете почитать <a href="https://www.ecma-international.org/publications/standards/Ecma-262-arch.htm">предыдущие версии</a> ECMAScript, например, ES2015, ES2016 и ES2017.</p>

                    ]]></description><pubDate>Wed, 30 Jan 2019 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/new-in-js2019/</guid></item><item><title>Text-transform и копирование</title><link>https://web-standards.ru/articles/text-transform-and-copy/</link><description><![CDATA[
                        <p>Что происходит при копировании текста с <code>text-transform</code>.</p>
<figure>
    <img src="https://web-standards.ru/articles/text-transform-and-copy/images/1.jpg" alt="">
    <figcaption>
        Alphabet, Le Bistronome (CC BY-NC 2.0)
    </figcaption>
</figure>
<p>В CSS есть свойство <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/text-transform"><code>text-transform</code></a>. С помощью него можно менять регистр текста: можно преобразовать текст в ПРОПИСНЫЕ, строчные или Капитализировать Только Первые Буквы Слов. Это делается значениями <code>uppercase</code>, <code>lowercase</code> и <code>capitalize</code>, соответственно. Помимо них есть значения <code>full-width</code> и <code>full-size-kana</code>, про которые мы в этот раз говорить не будем.</p>
<p style="text-transform: uppercase">Пример текста с <code>text-transform: uppercase</code></p>
<p style="text-transform: lowercase">Пример текста с <code>text-transform: lowercase</code></p>
<p style="text-transform: capitalize">Пример текста с <code>text-transform: capitalize</code></p>
<p>Выше приведен пример использования свойства <code>text-transform</code>. Попробуйте скопировать любой из них и вставить куда-нибудь. Результат будет отличаться в зависимости от вашего браузера:</p>
<ul>
<li>Если у вас браузер на WebKit или Blink (Chrome, Safari, Opera 15+, Яндекс.Браузер и вскоре даже Edge), то вставится измененный текст — тот, который вы видите в браузере.</li>
<li>В остальных браузерах (Firefox, IE и пока Edge) вставится исходный текст — тот, который написан в HTML.</li>
</ul>
<p>Стандарт до недавнего времени не определял, какое поведение является верным, поэтому оно стало предметом споров в баг-трекерах. <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=35148">Баг для Firefox</a> завели еще в 2000 году, <a href="https://bugs.webkit.org/show_bug.cgi?id=43202">баг для WebKit</a> с противоположным смыслом — в 2010. У сторонников обоих вариантов есть аргументы.</p>
<p>Главный аргумент за копирование измененного текста, как в WebKit — соответствие ожиданиям пользователя. Человек не может понять, в результате чего текст отображается заглавными буквами. Может быть, он прописан таким образом в исходном коде, а может быть, к нему применено <code>text-trasform: uppercase</code>. Если пользователь будет копировать текст в верхнем регистре, а потом вставлять его и иногда видеть что-то другое, ему будет казаться, что веб сломался и глючит.</p>
<p>Аргумент другой стороны в том, что такое поведение нарушает принцип разделения содержимого и оформления. CSS не должен влиять на контент страницы. А раз создатели сайта решили менять регистр через CSS, то они относятся к этому как к стилизации, и у пользователя должен быть доступ к изначальному варианту текста.</p>
<p><a href="https://github.com/w3c/csswg-drafts/issues/627">В 2016 году</a> за этот вопрос взялась Рабочая группа CSS (CSSWG). При обсуждении <a href="https://github.com/w3c/csswg-drafts/issues/627#issuecomment-255279958">выделили</a> основные сценарии использования <code>text-transform</code>:</p>
<ul>
<li><code>p::first-line { text-transform: uppercase }</code> — первую строку набирают прописными буквами, как это иногда делают в англоязычной художественной литературе. В этом случае лучше копировать исходный текст, иначе он будет выглядеть странно.</li>
<li><code>abbr { text-transform: lowercase; font-variant: small-caps }</code> — аббревиатуры в тексте набирают капителью. Свойство <code>font-variant: small-caps</code> берёт специальное начертание для капители из шрифта или отображает строчные буквы как прописные, только уменьшенного размера, если их нет. В этом случае тоже лучше копировать исходный текст, иначе аббревиатуры будут набраны строчными буквами.</li>
<li><code>h1 { text-transform: uppercase }</code> — стилизуют заголовки с помощью прописных букв. Здесь применимы аргументы за оба варианта поведения.</li>
<li><code>em { font-style: normal; text-transform: uppercase }</code> — используют прописные буквы для выделения важных фрагментов. Здесь тоже применимы оба аргумента.</li>
</ul>
<p>В результате обсуждений в рабочей группе, в 2018 году было принято решение: <a href="https://github.com/w3c/csswg-drafts/commit/f736e8e1b9812a9e854d03328a0acca827310801">дополнить стандарт CSS</a> указанием, что свойство <code>text-transform</code> не должно влиять на копируемый текст.</p>
<figure>
    <img src="https://web-standards.ru/articles/text-transform-and-copy/images/2.png" alt="">
    <figcaption>
        Коммит, который вносит изменения в стандарт.
    </figcaption>
</figure>
<p>Сейчас баг в Firefox закрыт как «wontfix», баг в WebKit по-прежнему открыт. Но уже можно надеяться, что скоро его поправят, и копирование текста с <code>text-transform</code> начнет работать во всех браузерах одинаково.</p>

                    ]]></description><pubDate>Thu, 24 Jan 2019 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/text-transform-and-copy/</guid></item><item><title>«Исправление» списков</title><link>https://web-standards.ru/articles/fixing-lists/</link><description><![CDATA[
                        <p><a href="https://twitter.com/gerardkcohen">Джерард К. Коэн</a> в сентябре 2017 опубликовал на Unfettered Thoughts <a href="https://unfetteredthoughts.net/2017/09/26/voiceover-and-list-style-type-none/">статью о том</a>, как пропадает семантика у списков в VoiceOver и Safari (WebKit; macOS и iOS), когда используется свойство <code>list-style: none</code>. И это происходит не только из-за <code>list-style: none</code>, но и при использования любых CSS-свойств, которые убирают маркеры или нумерацию у элементов списка.</p>
<p>Это не единственный случай, когда изменения в CSS влияют на то, как элементы обрабатываются вспомогательными технологиями вроде скринридеров. Изменяя значения свойств <code>display</code> и <code>visibility</code> на <code>none</code> или <code>hidden</code> соответственно, мы не только визуально скроем такой контент, но и сделаем его недоступным для скринридеров. Однако эта проблема с изменением стиля списка уникальна только для Safari. В других браузерах и для других вспомогательных технологий эти свойства будут всегда скрывать и показывать контент.</p>
<p>Почему WebKit — это единственный браузерный движок, который ведёт себя таким образом?</p>
<h2>Когда «баг — это фича» на самом деле баг?</h2>
<p>В марте 2017 <a href="https://bugs.webkit.org/show_bug.cgi?id=170179">был обнаружен баг</a>, связанный с влиянием <code>list-style: none</code> на семантику списка. В итоге был дан такой ответ:</p>
<blockquote>
<p>Это было осознанное решение из-за повсеместного использования списков веб-разработчиками. […] По сути, если вы удаляете все видимые по умолчанию признаки списка, то у зрячего пользователя или пользователя скринридера не будет никаких оснований считать, что контент им является. Если вы хотите изменить эту эвристику для доступности, то нужно всегда задавать явную ARIA-роль <code>role=&quot;list&quot;</code>.</p>
</blockquote>
<p>Я могу это понять. Семантика сложная, и люди часто неправильно используют HTML. Тем не менее, эта проблема может возникнуть при использовании распространённого метода, когда нужно удалить стиль списка по умолчанию, но сохранить при этом его семантику. Особенно если список оформляется так, что он всё ещё похож на список.</p>
<p>Кроме того, идея о том, что стоит рассчитывать на повторную установку семантики для списка при использовании ARIA, противоречит <a href="https://www.w3.org/TR/using-aria/#rule1">первому правилу использования ARIA</a>. Оно гласит:</p>
<blockquote>
<p>Если вы можете использовать стандартные HTML-элементы или атрибуты с уже <strong>встроенной в них</strong> необходимой семантикой и поведением, <strong>сделайте это</strong> вместо того, чтобы повторно использовать элемент и добавлять роль ARIA, состояние или значение, чтобы сделать его доступным.</p>
</blockquote>
<p>Другая <a href="https://www.w3.org/TR/using-aria/#aria-does-nothing">цитата оттуда же</a>:</p>
<blockquote>
<p>Ни один элемент в HTML4 не нуждается в добавлении ролей ARIA для того, чтобы раскрыть их семантику, уже встроенную по умолчанию. […] В большинстве случаев ARIA-роль и ARIA-атрибуты, семантика которых соответствует семантике стандартных элементов, не нужны. Их не рекомендуются использовать, так как эти свойства уже установлены браузером.</p>
</blockquote>
<p>В процессе обучения разработчиков, плохо знакомых с доступностью, это вечная проблема. Им часто говорят, что «не нужно использовать ARIA, если вы используете правильные HTML-элементы». После чего со вздохом объясняют:</p>
<blockquote>
<p>Вам нужно использовать ARIA для того, чтобы вернуть семантику списку в случае именно этого сочетания браузера и скринридера.
Нет, вам не нужно это делать для других браузеров и скринридеров.
Нет, этого не происходит с Chrome (Blink) и VoiceOver.
Нет, вам нужно просто игнорировать предупреждения валидаторов «не дублируйте при помощи ролей ARIA роли стандартных элементов».
Нет, это не нужно для Internet Explorer…</p>
</blockquote>
<h2>Когда вы даёте разработчику ARIA-роль</h2>
<p>Немного отойду от темы, но считаю важным акцентировать внимание на этом. Когда вы советуете использовать ARIA, то это застревает в голове. Люди, которые плохо разбираются в тонкостях ARIA, «усвоят», что это «делает контент более доступным» и будут это использовать даже <a href="https://css-tricks.com/aria-spackle-not-rebar/">тогда, когда в этом нет необходимости</a>.</p>
<p>Я ранее отмечал в <a href="https://www.scottohara.me/blog/2018/05/26/aria-lists.html">«ARIA Lists»</a>, что не нужно проходить через огонь, воду и медные трубы, чтобы создать неупорядоченный или упорядоченный список с помощью ARIA и CSS-счётчиков. Марси Саттон в 2016 написала статью <a href="https://marcysutton.com/links-vs-buttons-in-modern-web-applications/">«Links vs Buttons»</a>, которая, вероятно, не потеряет свою актуальность в ближайшие годы. Люди, чтобы избежать странного поведения элементов, которое они не понимают, будут делать лишнюю работу, чтобы создать элементы с помощью ARIA. Пожалуйста, давайте не увязать в этом.</p>
<p>Другая интересная деталь, связанная с ARIA: если вы посмотрите на мою статью про использование ARIA для списков в режиме чтения в Firefox, то мой пример списка с ARIA исчезнет.</p>
<p>Почему? Это же просто список, разве нет? Хм-м-м…</p>
<p>Я обнаружил разное поведение и у другой разметки с ARIA в некоторых <a href="https://medium.com/@mandy.michael/building-websites-for-safari-reader-mode-and-other-reading-apps-1562913c86c9">режимах для чтения браузера</a>. Иногда такая разметка пропадает, иногда всё работает нормально, но этих странностей не бывает, когда используешь стандартные HTML-элементы без ARIA. Это то, о чём нужно думать при тестировании своего контента.</p>
<p>Ладно, давайте вернёмся к разговору о WebKit и VoiceOver…</p>
<h2>Исправление ради исправления</h2>
<p>К счастью, ARIA не единственный способ вернуть списку его семантику. В статье на Unfettered Thoughts рассмотрен случай добавления пробела нулевой ширины через псевдоэлемент <code>::before</code> для элементов списка. Это восстанавливает семантику списка.</p>
<p>Пример CSS-кода:</p>
<pre><code tabindex="0" class="language-css">.list li {
    list-style-type: none; /* убираем маркеры списка */
}

.list li::before {
    content: &quot;\200B&quot;; /* добавляем пробел нулевой ширины */
    position: absolute; /* и ещё кое-что */
}
</code></pre>
<p>Используя этот код на практике, я обнаружил, что добавление <code>position: absolute</code> для псевдоэлемента может быть полезным. В проектах, над которыми я работал, бывало, что пробел с нулевой шириной не вёл себя так, как нужно. Из-за хака появлялись странные отступы или ненужные разрывы строк. Эти визуальные странности были в значительной степени связаны с другими стилями, которые наследовались <code>::before</code> от других нужных классов. С помощью <code>position: absolute</code> мы удаляем псевдоэлемент из потока элементов, что уменьшает вероятность возникновения такой проблемы.</p>
<p>Ещё нужно быть осторожным, если псевдоэлемент <code>::before</code> уже использован для других целей, и добавление неразрывного пробела может навредить другим стилям. Изменение стилей и разметки могли бы помочь это исправить, но усилия должны быть сопоставимы с необходимостью поддержки Safari.</p>
<p><a href="https://twitter.com/aardrian/status/1083889915417559040">Адриан Роселл заметил</a>, что отсутствие семантики у списка <em>«может не иметь большого значения, если тестирование пользователей не показывает, что список вам действительно необходим».</em> Хотя такое поведение может быть в некоторых ситуациях нежелательным, давайте не будем прилагать слишком много усилий для исправления того, что уже слишком активно пытались исправить, используя ненужную семантику. Проводите тестирование с реальными людьми!</p>
<h2>Новая альтернатива</h2>
<p><a href="https://twitter.com/jon_neal/status/1083829750072836096">Джонатан Нил</a> предложил на Unfettered Thoughts альтернативное решение на CSS. В документации к <a href="https://github.com/jonathantneal/postcss-list-style-safari-fix">PostCSS List Style Safari Fix</a> Джонатана сказано, что он обнаружил, что использование следующего CSS устранит необходимость в псевдоэлементе и снова вернёт семантику списку в Safari и VoiceOver:</p>
<pre><code tabindex="0" class="language-css">/* Сокращённая версия селекторов Джонатана */
ol,
ul {
    list-style: none;
}

ol,
ul {
    list-style: url(&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'/%3E&quot;);
}
</code></pre>
<p>Я обнаружил, что <code>url(#!)</code> тоже будет возвращать необходимую семантику, но нужно будет дополнительно всё протестировать и убедиться, что это не приводит ни к каким негативным последствиям в других браузерах.</p>
<p>Это выглядит многообещающе. При тестировании список был корректно объявлен VoiceOver на WebKit, не помешал другим скринридерам (тестировался на NVDA 2018.4 и на последнем JAWS 2019) и небольшие тестовые примеры корректно отображаются в большинстве современных браузеров.</p>
<p>Я обнаружил единственную проблему у этой техники, когда попытался использовать её для своей навигации по сайту (в данный момент не использую ни один из хаков; пусть браузерные эвристики делают с моей навигацией то, что делают).</p>
<p>После добавления <code>list-style: url(…)</code> для существующего селектора списка навигации, в верхней части каждого пункта в Internet Explorer 11 и Edge 42 появился отступ:</p>
<img src="https://web-standards.ru/articles/fixing-lists/images/2.jpg" alt="">
<p>При отключении <code>list-style</code> отступ исчезает и навигация отображается так, как нужно.</p>
<img src="https://web-standards.ru/articles/fixing-lists/images/3.jpg" alt="">
<p>Я предполагаю, что в моём CSS есть что-то другое, что приводит к появлению этих отступов. При <a href="https://codepen.io/scottohara/pen/JweKEp">использовании этой техники</a> в более простых, но похожих наборах правил, он не появляется.</p>
<h2>Подводя итоги</h2>
<p>Я слегка в замешательстве. Решение об отмене семантики у списка, если он больше не выглядит как список, имеет для меня смысл. Обычно я думаю, что внешний вид элементов должен отражать их содержание. Но такое поверхностное решение категорично. Оно также не учитывается тот факт, что существуют другие способы оформления списков, кроме использования маркеров по умолчанию. Следовательно, хотя я понимаю смысл этого решения, не думаю, что поведение Safari правильное в этой ситуации.</p>
<p>Такое решение движется в другом направлении и нарушает форму того, как представляют другие браузеры стилизованные списки, а скринридеры их интерпретируют. Обычно разработчики не ожидают, что CSS влияет на семантику HTML. Это был своего рода способ устранения таких элементов, как: <code>&lt;center&gt;</code>, <code>&lt;big&gt;</code>, <code>&lt;font&gt;</code> и т. д. HTML предназначен для семантики, а CSS — для стиля.</p>
<p>Но, как я писал в своей статье <a href="https://www.scottohara.me/blog/2018/10/03/unbutton-buttons.html">«Unbuttoning Buttons»</a>, а также Адриан Розелли в <a href="https://adrianroselli.com/2018/05/display-contents-is-not-a-css-reset.html">«Display: Contents Is Not a CSS Reset»</a> и <a href="https://adrianroselli.com/2018/02/tables-css-display-properties-and-aria.html">«Tables, CSS Display Properties, and ARIA»</a>, CSS может оказать негативный эффект на семантику, и разработчики могут не поступить мудрее.</p>
<p>Просто посмотрите на количество ответов, ретвитов и лайков к твиту Сары Суайдан:</p>
<blockquote>
<p><code>#TIL</code> that removing list bullets with <code>list-style: none</code> removes <code>&lt;ul/ol&gt;</code> semantics in VoiceOver. Adding zero-width space fixes this; so this is yet another thing to add to our base <code>#CSS</code> files.
<a href="https://twitter.com/SaraSoueidan/status/1083734801952182272">Sara Soueidan @SaraSoueidan</a></p>
</blockquote>
<blockquote>
<p>Сегодня я узнала, что, если убрать маркеры списка с помощью <code>list-style: none</code>, то это отменит семантику <code>&lt;ul/ol&gt;</code> в VoiceOver.
Добавление пробела нулевой ширины исправит это; так что это ещё одна вещь, которую надо добавить в наш базовый файл.
<a href="https://unfetteredthoughts.net/2017/09/26/voiceover-and-list-style-type-none/">https://unfetteredthoughts.net/2017/09/26/voiceover-and-list-style-type-none/</a></p>
</blockquote>
<p>Было много фронтенд-разработчиков и тех, кто специализируется на доступности, но не знает об этой особенности в Safari.</p>
<p>Эрик Эггерт воскликнул:</p>
<blockquote>
<p>We really need an open conversation on what CSS is allowed to overwrite semantics. Apart from <code>display: none</code>/<code>visibility: hidden</code>, I personally think no CSS should alter the semantics of the page.
<a href="https://twitter.com/yatil/status/1083738608593448963">Eric Eggert @yatil</a></p>
</blockquote>
<blockquote>
<p>Нам действительно необходимо открытое обсуждение того, что CSS позволяет переопределять семантику. Лично я считаю, что CSS не должен изменять семантику страницы, кроме <code>display: none</code> или <code>visibility: hidden</code>.</p>
</blockquote>
<p>А так отвечает Джеймс Крейг:</p>
<blockquote>
<p>I realize this decision upsets some web authors, but remember the first rule of the Web: Consider the needs of:</p>
<ul>
<li>Users before,</li>
<li>Web Authors/Devs before,</li>
<li>Browser Implementers before,</li>
<li>Spec authors.
<a href="https://twitter.com/cookiecrook/status/1084141791580905472">James Craig @cookiecrook</a></li>
</ul>
</blockquote>
<blockquote>
<p>This decision was all about the users’ experience on the majority of pages where web developers are not paying attention to the screen reader experience. Definitely open to change suggestions (including updating the heuristic) that make it better for authors w/o penalizing users.
<a href="https://twitter.com/cookiecrook/status/1084142936583892992">James Craig @cookiecrook</a></p>
</blockquote>
<blockquote>
<p>Я понимаю, что это решение расстраивает некоторых авторов, но помните про первое правило интернета. Учитывайте потребности:</p>
<ul>
<li>пользователей</li>
<li>до авторов и разработчиков,</li>
<li>до авторов и разработчиков,</li>
<li>до разработчиков браузеров,</li>
<li>до авторов спецификаций.
Это всё нужно для удобства пользователей, ведь сегодня на большинстве сайтов разработчики не уделяют внимание удобству скринридеров. Разумеется, я открыт для предложений по внесению изменений (включая обновление эвристики), которые сделают жизнь авторов лучше без ухудшения положения пользователей.</li>
</ul>
</blockquote>
<p>Так что замечания и комментарии приветствуются :)</p>

                    ]]></description><pubDate>Mon, 21 Jan 2019 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/fixing-lists/</guid></item><item><title>Puppeteer для краулинга страниц сайта и сохранения их в Markdown</title><link>https://web-standards.ru/articles/puppeteer-crawl-to-markdown/</link><description><![CDATA[
                        <p>Одна из моих целей на 2019: закончить редизайн <a href="https://justmarkup.com/">моего сайта</a>, и, что гораздо важнее, превратить его из сайта на WordPress в обычный статический сайт. Чтобы это сделать, мне надо сохранить все мои посты в формате Markdown. Попытки найти для этой задачи подходящий плагин для WordPress не увенчались успехом. С другой стороны, я бы мог использовать WordPress REST API или даже RSS-фид, чтобы получить данные, но и это не показалось мне вполне правильным решением. Потом я прочёл <a href="https://24ways.org/2018/dynamic-social-sharing-images/">статью</a>, написанную <a href="https://twitter.com/drewm">Дрю МакЛиланом</a>, в которой он использует Puppeteer для динамического создания скриншотов страниц, которые потом использует в превью постов для социальных сетей. И тогда у меня созрел план.</p>
<p>В этой статье я опишу, как с помощью Puppeteer найти все статьи на странице, открыть их одну за другой, извлечь контент, сконвертировать в Markdown и сохранить в отдельных файлах.</p>
<h2>Запуск Puppeteer</h2>
<p><a href="https://github.com/GoogleChrome/puppeteer">Puppeteer</a> — это «безголовый» Google Chrome на Node.js, который предоставляет понятный API для работы. Это означает, что вы можете запустить Chrome из командной строки, даже не открывая окна браузера. Вы можете открывать страницы, на которых будет рендериться CSS, выполняться JavaScript — всё то же, что делает десктопный Chrome, но без интерфейса для пользователя.</p>
<p>Для начала — у вас должен быть установлен <a href="https://nodejs.org/en/download/package-manager/">Node.js</a>. Для установки Puppeteer запустите в консоли:</p>
<pre><code tabindex="0" class="language-sh">npm i puppeteer
</code></pre>
<p>Эта команда установит API вместе с Chromium (около 200 МБ). На моей Linux-подсистеме на Windows возникли проблемы во время установки, так как некоторые зависимости не подтянулись. Поэтому мне пришлось установить несколько дополнительных библиотек, чтобы всё заработало. Если у вас возникнут трудности на этом этапе, почитайте <a href="https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md">советы по решению проблем</a>.</p>
<h2>Получение данных</h2>
<p>Сначала мы запустим новый браузер с Puppeteer и откроем новую страницу. Для этого, создадим новый файл index.js с таким содержимым</p>
<pre><code tabindex="0" class="language-js">const puppeteer = require('puppeteer');

(async() =&gt; {
    // Запустим браузер
    const browser = await puppeteer.launch({
        args: ['--no-sandbox'] }
    );

    // Откроем новую страницу
    const page = await browser.newPage();
    const pageURL = 'https://justmarkup.com';

    try {
        // Попробуем перейти по URL
        await page.goto(pageURL);
        console.log(`Открываю страницу: ${pageURL}`);
    } catch (error) {
        console.log(`Не удалось открыть страницу: ${pageURL} из-за ошибки: ${error}`);
    }

    // Всё сделано, закроем браузер
    await browser.close();

    process.exit()
})();
</code></pre>
<p>На первой строке мы подключаем библиотеку Puppeteer. Потом открываем браузер — <code>puppeteer.launch()</code>, создаём новую страницу — <code>browser.newPage()</code>, и переходим по нужному адресу — <code>page.goto(URL)</code>.</p>
<p>Если мы запустим наш файл — <code>node index.js</code>, и всё сработает без ошибок, то в консоли должно появится сообщение <code>Открываю страницу https://justmarkup.com</code>. Либо что-то может пойти не так: неправильный URL, ошибка SSL, превышено время ожидания, ресурс недоступен… Поэтому мы обернули переход по URL в блок <code>try/catch</code> для обработки возможных <a href="https://github.com/GoogleChrome/puppeteer/blob/v1.11.0/docs/api.md#pagegotourl-options">ошибок</a>.</p>
<p>Теперь, когда мы знаем как открыть браузер и перейти по URL, давайте посмотрим, как получить данные из DOM.</p>
<pre><code tabindex="0" class="language-js">const puppeteer = require('puppeteer');

(async() =&gt; {
    // Запустим браузер
    const browser = await puppeteer.launch({
        args: ['--no-sandbox'] }
    );

    // Откроем новую страницу
    const page = await browser.newPage();
    const pageURL = 'https://justmarkup.com';

    try {
        // Попробуем перейти по URL
        await page.goto(pageURL);
        console.log(`Открываю страницу: ${pageURL}`);
    } catch (error) {
        console.log(`Не удалось открыть страницу: ${pageURL} из-за ошибки: ${error}`);
    }

    // Найдём все ссылки на статьи
    const postsSelector = '.main .article h2 a';
    await page.waitForSelector(postsSelector, { timeout: 0 });
    const postUrls = await page.$$eval(
        postsSelector, postLinks =&gt; postLinks.map(link =&gt; link.href)
    );

    // Перейдём по каждой из них
    for (let postUrl of postUrls) {
        // Откроем страницу
        try {
            await page.goto(postUrl);
            console.log('Открываю страницу: ', postUrl);
        } catch (error) {
            console.log(error);
            console.log('Не удалось открыть страницу: ', postUrl);
        }

        // Получим pathname
        let pagePathname = await page.evaluate(() =&gt; location.pathname);
        pagePathname = pagePathname.replace(/\//g, '-');
        console.log('Нашёл pathname:', pagePathname);

        // Получим заголовок статьи
        const titleSelector = '.article h1';
        await page.waitForSelector(titleSelector);
        const pageTitle = await page.$eval(
            titleSelector, titleSelector =&gt; titleSelector.outerHTML
        );
        console.log('Нашёл заголовок статьи: ', pageTitle);

        // Получим контент статьи
        const contentSelector = '.article .entry-content';
        await page.waitForSelector(contentSelector, { timeout: 0 });
        const pageContent = await page.$eval(contentSelector,
        contentSelector =&gt; contentSelector.innerHTML);
        console.log('Нашёл контент: ', pageContent);
    }

    // Всё сделано, закроем браузер
    await browser.close();

    process.exit()
})();
</code></pre>
<p>Сначала мы получим все ссылки на наши посты, которые есть на начальной странице. В моём случае, их можно собрать по селектору <code>.main .article h2 a</code>.</p>
<pre><code tabindex="0" class="language-js">// Найдём все ссылки на посты
const postsSelector = '.main .article h2 a';
await page.waitForSelector(postsSelector, { timeout: 0 });
const postUrls = await page.$$eval(
    postsSelector, postLinks =&gt; postLinks.map(link =&gt; link.href)
);
</code></pre>
<p>Мы определяем селектор и используем <code>waitForSelector()</code> чтобы все DOM-узлы были доступны. После, мы используем <a href="https://github.com/GoogleChrome/puppeteer/blob/v1.11.0/docs/api.md#pageevalselector-pagefunction-args"><code>page.$$eval(selector, pageFunction[,...args])</code></a>, который запускает <code>Array.from(document.querySelectorAll(selector))</code> для всей страницы и передаёт полученное первым аргументом в <code>pageFunction</code>. И наконец, мы используем метод <code>map()</code> для получения ссылок на страницы из атрибута <code>href</code>. Отлично, у нас есть массив со всеми нужными ссылками!</p>
<p>Теперь, самое время для того, чтобы открыть все ссылки, одну за другой, и получить данные (заголовок, контент, <code>pathname</code>), которые нам нужны.</p>
<pre><code tabindex="0" class="language-js">for (let postUrl of postUrls) {
    // Откроем страницу
    try {
        await page.goto(postUrl);
        console.log('Открываю страницу: ', postUrl);
    } catch (error) {
        console.log(error);
        console.log('Не удалось открыть страницу: ', postUrl);
    }

    // Получим pathname
    let pagePathname = await page.evaluate(() =&gt; location.pathname);
    pagePathname = pagePathname.replace(/\//g, '-');
    console.log('Нашёл pathname:', pagePathname);

    // Получим заголовок статьи
    const titleSelector = '.article h1';
    await page.waitForSelector(titleSelector);
    const pageTitle = await page.$eval(
        titleSelector, titleSelector =&gt; titleSelector.outerHTML
    );
    console.log('Нашёл заголовок статьи: ', pageTitle);

    // Получим контент статьи
    const contentSelector = '.article .entry-content';
    await page.waitForSelector(contentSelector, { timeout: 0 });
    const pageContent = await page.$eval(
        contentSelector, contentSelector =&gt; contentSelector.innerHTML
    );
    console.log('Нашёл контент: ', pageContent);
}
</code></pre>
<p>Мы перебираем массив ссылок <code>postUrls</code> и используем <code>page.goto()</code> для перехода по каждому URL. Чтобы получить <code>pathname</code> которое мы будем использовать позже, для того, чтобы сохранить файл, мы используем <code>page.evaluate()</code> чтобы получить <code>pathname</code> из <code>window.location</code>. Мы также заменяем слэши <code>/</code> на дефисы <code>-</code>, чтобы имена файлов были валидными.</p>
<p>Следующий этап — заголовок статьи. Определяем селектор (в моём случае, это — <code>.article h</code>), и, снова используя <code>page.waitForSelector()</code>, ждём, чтобы всё загрузилось и получаем <code>outerHTML</code> заголовка с помощью <code>page.$eval()</code>. Позже мы сможем преобразовать этот HTML в markdown, с помощью HTML-to-Markdown API.</p>
<p>Ну и наконец, мы можем получить основной контент статьи. Определяем ещё одни селектор (в моём случае, это — <code>.article .entry-content</code>), снова используем <code>page.waitForSelector()</code>, и получаем <code>innerHTML</code> контента статьи с помощью <code>page.$eval()</code>.</p>
<p>Отлично, теперь мы знаем, как найти ссылки, перейти по каждой из них и получить данные со страницы.</p>
<h2>Конвертация в Markdown</h2>
<p>Что же, у нас есть все необходимые данные. На следующем шаге, мы будем использовать <a href="https://github.com/domchristie/turndown">Turndown</a> для конвертации HTML в Markdown.</p>
<pre><code tabindex="0" class="language-js">const puppeteer = require('puppeteer');
const TurndownService = require('turndown');

const turndownService = new TurndownService();

(async() =&gt; {
    // Запустим браузер
    const browser = await puppeteer.launch({
        args: ['--no-sandbox'] }
    );

    // Откроем новую страницу
    const page = await browser.newPage();
    const pageURL = 'https://justmarkup.com';

    try {
        // Попробуем перейти по URL
        await page.goto(pageURL);
        console.log(`Открываю страницу: ${pageURL}`);
    } catch (error) {
        console.log(`Не удалось открыть страницу: ${pageURL} из-за ошибки: ${error}`);
    }

    // Найдём все ссылки на статьи
    const postsSelector = '.main .article h2 a';
    await page.waitForSelector(postsSelector, { timeout: 0 });
    const postUrls = await page.$$eval(
        postsSelector, postLinks =&gt; postLinks.map(link =&gt; link.href)
    );

    // Перейдём по каждой из них
    for (let postUrl of postUrls) {
        // Откроем страницу
        try {
            await page.goto(postUrl);
            console.log('Открываю страницу: ', postUrl);
        } catch (error) {
            console.log(error);
            console.log('Не удалось открыть страницу: ', postUrl);
        }

        // Получим pathname
        let pagePathname = await page.evaluate(() =&gt; location.pathname);
        pagePathname = pagePathname.replace(/\//g, '-');
        console.log('Нашёл pathname:', pagePathname);

        // Получим заголовок статьи
        const titleSelector = '.article h1';
        await page.waitForSelector(titleSelector);
        const pageTitle = await page.$eval(
            titleSelector, titleSelector =&gt; titleSelector.outerHTML
        );
        console.log('Нашёл заголовок статьи: ', pageTitle);

        // Получим контент статьи
        const contentSelector = '.article .entry-content';
        await page.waitForSelector(contentSelector, { timeout: 0 });
        const pageContent = await page.$eval(
            contentSelector, contentSelector =&gt; contentSelector.innerHTML
        );
        console.log('Нашёл контент: ', pageContent);

        // Преобразуем HTML в Markdown
        let pageContentMarkdown = turndownService.turndown(
            pageTitle + pageContent
        );
        console.log(
            'Да это заголовок статьи и её контент в Markdown',
            pageContentMarkdown
        );
    }

    // Всё сделано, закроем браузер
    await browser.close();

    process.exit()
})();
</code></pre>
<p>Сначала, нам нужно установить Turndow, запустив в консоли <code>npm install turndown</code>. Потом мы подключаем его в начале index.js и определяем как сервис <code>const turndownService = new TurndownService();</code>
Теперь, нам всего лишь надо добавить <code>let pageContentMarkdown = turndownService.turndown(pageTitle + pageContent);</code> после той части, где мы определили <code>pageTitle</code> и <code>pageContent</code>, и вуаля: у нас есть наш HTML преобразованный в Markdown</p>
<h3>Сохранение файлов Markdown</h3>
<p>Итак, нам осталось сохранить сконвертированный Markdown в файлы — по одному для каждой статьи.</p>
<pre><code tabindex="0" class="language-js">const puppeteer = require('puppeteer');
const TurndownService = require('turndown');
const fs = require('fs');

const turndownService = new TurndownService();

(async() =&gt; {
    // Запустим браузер
    const browser = await puppeteer.launch({
        args: ['--no-sandbox'] }
    );

    // Откроем новую страницу
    const page = await browser.newPage();
    const pageURL = 'https://justmarkup.com';

    try {
        // Попробуем перейти по URL
        await page.goto(pageURL);
        console.log(`Открываю страницу: ${pageURL}`);
    } catch (error) {
        console.log(`Не удалось открыть страницу: ${pageURL} из-за ошибки: ${error}`);
    }

    // Найдём все ссылки на статьи
    const postsSelector = '.main .article h2 a';
    await page.waitForSelector(postsSelector, { timeout: 0 });
    const postUrls = await page.$$eval(
        postsSelector, postLinks =&gt; postLinks.map(link =&gt; link.href)
    );

    // Перейдём по каждой из них
    for (let postUrl of postUrls) {
        // Откроем страницу
        try {
            await page.goto(postUrl);
            console.log('Открываю страницу: ', postUrl);
        } catch (error) {
            console.log(error);
            console.log('Не удалось открыть страницу: ', postUrl);
        }

        // Получим pathname
        let pagePathname = await page.evaluate(() =&gt; location.pathname);
        pagePathname = pagePathname.replace(/\//g, '-');
        console.log('Нашёл pathname:', pagePathname);

        // Получим заголовок статьи
        const titleSelector = '.article h1';
        await page.waitForSelector(titleSelector);
        const pageTitle = await page.$eval(
            titleSelector, titleSelector =&gt; titleSelector.outerHTML
        );
        console.log('Нашёл заголовок статьи: ', pageTitle);

        // Получим контент статьи
        const contentSelector = '.article .entry-content';
        await page.waitForSelector(contentSelector, { timeout: 0 });
        const pageContent = await page.$eval(
            contentSelector, contentSelector =&gt; contentSelector.innerHTML
        );
        console.log('Нашёл контент: ', pageContent);

        // Преобразуем HTML в Markdown
        let pageContentMarkdown = turndownService.turndown(
            pageTitle + pageContent
        );

        // Проверим, существует ли папка, если нет — создадим её
        const postsDirectory = '/posts/';
        if (!fs.existsSync(postsDirectory)) {
            fs.mkdirSync(postsDirectory);
        }

        // Сохраним файл в формате ${pathname}.md
        fs.writeFile(
            postsDirectory + pagePathname + '.md',
            pageContentMarkdown, (err) =&gt; {
                if (err) {
                    console.log(err);
                }

                // Если ошибки нет — значит статья сохранена
                console.log('Сохранил статью!');
            }
        );
    }

    // Всё сделано, закроем браузер
    await browser.close();

    process.exit()
})();
</code></pre>
<p>Здесь мы используем <a href="https://nodejs.org/api/fs.html">API файловой системы — модуль <code>fs</code> из Node.js</a>, поэтому подключим этот модуль начале нашего index.js. Я хочу сохранить все статьи в папке с именем <code>posts</code>. Поэтому, сначала проверим, существует ли папка с таким названием и если нет — создадим её с помощью:</p>
<pre><code tabindex="0" class="language-js">// Проверим, существует ли папка, если нет — создадим её
const postsDirectory = '/posts/';
if (!fs.existsSync(postsDirectory)) {
    fs.mkdirSync(postsDirectory);
}
</code></pre>
<p>И в завершении, сохраним каждую статью в формате Markdown.</p>
<pre><code tabindex="0" class="language-js">// Сохраним файл в формате ${pathname}.md
fs.writeFile(postsDirectory + pagePathname + '.md', pageContentMarkdown, (err) =&gt; {
    if (err) {
        console.log(err);
    }

    // Если ошибки нет — значит статья сохранена
    console.log('Сохранил статью!');
});
</code></pre>
<p>Здесь мы используем <code>fs.writeFile()</code>. Мы хотим сохранить наши файлы в папке <code>/posts/</code>, используя <code>pathname</code> из переменной <code>pagePathname</code> как имена файлов, и <code>.md</code> как расширение. Это будет первым аргументом в функции <code>writeFile()</code>. Вторым аргументом мы передадим <code>pageContentMarkdown</code>, в котором лежит полученный Markdown в формате <code>String</code>. Если всё пройдёт без ошибок, мы получими статьи в формате Markdown, сохранённые одна за другой. Да, мы сделали это!</p>
<p>Я надеюсь, эта статья вас чему-то научила, и, возможно, вы в будущем тоже решите использовать Puppeteer для чего-нибудь интересного. Если вам любопытно, посмотрите финальный код на <a href="https://github.com/justmarkup/html-posts-to-markdown/blob/master/index.js">Гитхабе</a>.</p>

                    ]]></description><pubDate>Thu, 17 Jan 2019 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/puppeteer-crawl-to-markdown/</guid></item><item><title>JavaScript: топ тем и фреймворков для изучения в 2019 году</title><link>https://web-standards.ru/articles/top-themes-and-frameworks/</link><description><![CDATA[
                        <p>Снова пришло время сделать обзор экосистемы JavaScript за год. Моя цель — определить области и технологии с наивысшим потенциалом для поиска работы. Какие технологии используются? Какие тренды развития существуют? Я не буду говорить <em>о лучших</em>, вместо этого предлагаю сфокусироваться на самых востребованных. Тех, которые помогут пройти собеседование и не ударить в грязь лицом, когда интервьюер спросит _«а знаете ли вы ___<em>?»</em> (пустую графу заполните сами). Для достижения цели я использую доступные в сети данные.</p>
<p>Я не буду искать самую быструю технологию. Или оптимальную по качеству кода. Предлагаю сделать допущение, что все они самые быстрые и все делают свою работу. Важно другое: их востребованность.</p>
<h2>Компонентные фреймворки</h2>
<p>Вопрос, требующий внимания — компонентные фреймворки, а именно React, Angular и Vue.js, ведь по популярности эта тройка оставила конкурентов далеко позади.</p>
<p>Я уже писал в прошлом году, как быстро растет Vue.js, и что он имеет шансы в 2018 догнать Angular. Этого не случилось, хотя динамика по-прежнему отличная. Я также говорил, что бороться за сердца разработчиков на React им будет сложнее, поскольку уровень удовлетворенности у React значительно выше, чем у Angular. У них просто нет причин менять стек. Мое предсказание подтвердилось, библиотека Facebook в 2018 году сохранила лидерство.</p>
<p>Что примечательно, популярность всех трёх растет по экспоненте, год за годом.</p>
<h3>Прогноз: в 2019 React продолжит доминировать</h3>
<p>React сохраняет лидерство <a href="https://2018.stateofjs.com/front-end-frameworks/overview/">в рейтинге удовлетворенности</a> технологиями. Третий год подряд с тех пор, как мы учитываем его, React не отдаёт и пяди земли конкурентам. Причин для перемен в этом году я не вижу. Только нечто экстраординарное сможет подорвать его позиции.</p>
<p>Впрочем, React становится только лучше. <a href="https://reactjs.org/docs/hooks-reference.html">Hooks API</a> пришли на смену классам, которые я терпел с версии 0.14. Классы все ещё работают, но хуки — намного, намного лучше! Это и другие нововведения, такие как улучшение поддержки код-сплитинга и <a href="https://reactjs.org/blog/2018/11/13/react-conf-recap.html">параллельного рендеринга</a>, делают его практически непобедимым. На сегодняшний день React — без сомнений наиболее дружелюбный фреймворк во всей экосистеме. А фактора важнее я не знаю.</p>
<h3>Источники данных</h3>
<p>Для оценки интереса к технологиям в нашей отрасли, используем следующие метрики:</p>
<ol>
<li><strong>Тренды поиска Google.</strong> Не назову их лучшим показателем, но он подходит для широкой картины.</li>
<li><strong>Число скачиваний.</strong> Как способ оценки количества реальных пользователей через действие, которое они производят используя фреймворк.</li>
<li><strong>Вакансии на <a href="https://indeed.com/">Indeed</a>.</strong> Используя ту же методику, что и в предыдущие годы для сохранения последовательности.</li>
</ol>
<h3>Тренды поиска Google</h3>
<p><strong>Поисковые запросы по фреймворкам: январь 2014 — декабрь 2018.</strong></p>
<figure>
    <img src="https://web-standards.ru/articles/top-themes-and-frameworks/image/1.png" alt="Поисковые запросы по фреймворкам: январь 2014 — декабрь 2018">
</figure>
<p>По поисковым запросам React обогнал Angular в январе 2018 и сохранял лидерство на протяжении всего года. Vue.js стал занимать видимую, но все еще относительно незначительную позицию. Для сравнения, прошлогодние результаты:</p>
<p><strong>Поисковые запросы по фреймворкам: январь 2014 — декабрь 2017.</strong></p>
<figure>
    <img src="https://web-standards.ru/articles/top-themes-and-frameworks/image/2.png" alt="Поисковые запросы по фреймворкам: январь 2014 — декабрь 2017">
</figure>
<h3>Число скачиваний</h3>
<p>Этот параметр позволяет оценить <em>реальное</em> использование: установка пакета, как правило, означает его необходимость для рабочего процесса.</p>
<p>Кто-то из читателей заметит, что поставить пакет можно и из внутрикорпоративных репозиториев. Отвечу: да, такое возможно, но для всех трех сравниваемых фреймворков. Все они вполне прижились в энтерпрайзе, поэтому имеют сравнимые показатели.</p>
<p><strong>React, количество скачиваний в месяц: 2014–2018</strong></p>
<figure>
    <img src="https://web-standards.ru/articles/top-themes-and-frameworks/image/3.png" alt="React, количество скачиваний в месяц: 2014–2018">
</figure>
<p><strong>Angular, количество скачиваний в месяц: 2014–2018</strong></p>
<figure>
    <img src="https://web-standards.ru/articles/top-themes-and-frameworks/image/4.png" alt="Angular, количество скачиваний в месяц: 2014–2018">
</figure>
<p><strong>Vue.js, количество скачиваний в месяц: 2014–2018</strong></p>
<figure>
    <img src="https://web-standards.ru/articles/top-themes-and-frameworks/image/5.png" alt="Vue.js, количество скачиваний в месяц: 2014–2018">
</figure>
<p><strong>Визуализация по всем трём одновременно:</strong></p>
<figure>
    <img src="https://web-standards.ru/articles/top-themes-and-frameworks/image/6.png" alt="Визуализация по всем трём одновременно">
</figure>
<h1>Но вы забыли про Angular 1.0! Он все еще значителен в энтерпрайзе!</h1>
<p>Нет, не забыл. Angular 1.0 используется во многих проектах, так же как используется Windows XP. Эта версия всё ещё актуальная для заметного количества проектов, но новые — давно превзошли её по популярности, что делает Angular 1.0 гораздо менее востребованным, чем другие фреймворки.</p>
<p>Как так вышло? Потому что разработка в целом, как и повсеместное распространение JavaScript, во <em>всех секторах (включая энтерпрайз),</em> развивается настолько быстро, что новые технологии заменяют старые с невероятной скоростью. Пусть даже некоторые проекты <em>никогда</em> не обновятся.</p>
<p>Примите за доказательство приведенные выше графики скачиваний. В одном 2018 их было больше, чем за все прошлые годы <em>вместе взятые.</em></p>
<h3>Вакансии</h3>
<p>Indeed агрегирует данные разных сервисов по поиску работы. Раз в год мы подсчитываем количество упоминаний того или иного фреймворка, чтобы понять, чего хотят работодатели. В этом году результаты выглядят так:</p>
<p><strong>Вакансии для фреймворков, декабрь 2018.</strong></p>
<figure>
    <img src="https://web-standards.ru/articles/top-themes-and-frameworks/image/7.png" alt="Вакансии для фреймворков, декабрь 2018">
    <figcaption>Вакансии для фреймворков, декабрь 2018</figcaption>
</figure>
<ol>
<li>React: 24 640</li>
<li>Angular: 19 032</li>
<li>jQuery: 14 272</li>
<li>Vue.js: 2 816</li>
<li>Ember: 2 397 (нет на графике)</li>
</ol>
<p>Традиционно, вакансий значительно больше, чем годом ранее. Ember я исключил из списка, так как его показатели не растут, в отличие от показателей конкурентов. Я бы не рекомендовал изучать его, если задумываетесь о смене работы. Схожая ситуация с jQuery, спрос на который практически не изменился.</p>
<p>К счастью, число разработчиков в индустрии также выросло. Тем не менее, нам нужно нанимать и обучать новичков (не забывая про <a href="https://devanywhere.io">синьоров-менторов</a>). Иначе мы просто не успеем за ростом спроса. Ниже, для сравнения, прошлогодний график:</p>
<figure>
    <img src="https://web-standards.ru/articles/top-themes-and-frameworks/image/8.png" alt="Графейнный график">
</figure>
<p>Средняя зарплата продолжила рост: с 110 до 111 тысяч долларов в год <em>(с 9166 до 9250 $ в месяц — прим. редактора).</em> Забавно, но зарплаты при этом отстают от ожиданий соискателей. Рекрутерам будет сложнее нанимать новых и удерживать текущих разработчиков, если они не пойдут навстречу. Удерживание и переманивание кадров остается огромной проблемой. Люди стремятся туда, где больше платят.</p>
<p><strong>Методология:</strong> использованы данные сервиса <a href="http://indeed.com">Indeed</a>. Чтобы отфильтровать ложные вхождения, использован запрос со словом «software». Это повышает релевантность результатов. Затем полученные значения помножил на коэффициент ≈1,5 (приближенное соотношение вакансий со словом «software» и без него). Все данные были отсортированы по дате и проверены по выборке. Конечно, они точны не на 100%, но достаточно для анализа в рамках статьи.</p>
<h2>Основы JavaScript</h2>
<p>Я все время повторяю: сосредоточьтесь на основах. В этом году — я вам немного помогу. Процесс разработки основан на композиции — разделении сложных проблем на мелкие и простые и объединении их решений в полноценную программу.</p>
<p>Однако, когда на интервью я спрашиваю: «что такое функциональная композиция?» или «что такое композиция объектов?» разработчики не могут ответить. Несмотря на то, что используют их каждый день.</p>
<p>Я всегда считал это серьезной проблемой, поэтому написал книгу: <a href="https://leanpub.com/composingsoftware">«Composing Software</a>». Если вам нечего учить в 2019, учитесь правильной композиции.</p>
<h3>TypeScript</h3>
<p>TypeScript растёт. Это по-прежнему переоценённый инструмент, так как <a href="https://medium.com/javascript-scene/the-shocking-secret-about-static-types-514d39bf30a3">безопасность типизированного код преувеличена</a> и не сильно снижает вероятность ошибок, а <a href="https://medium.com/javascript-scene/you-might-not-need-typescript-or-static-types-aa7cb670a77b">работа с типами</a> в JavaScript без TypeScript и так неплоха. Можно даже использовать движок последнего с нативным JS в Visual Studio Code. Или установить Tern.js для вашего редактора.</p>
<p>TypeScript подводит при работе с функциями высшего порядка. Или, возможно, это я не научился пользоваться за все годы работы с ним. В этом случае авторам стоит поразмыслить над удобством или документацией (или обоими). Так или иначе, я до сих пор не понимаю, как нормально обрабатывать map-операции. TypeScript как будто вообще не понимает, что происходит в <a href="https://medium.com/javascript-scene/transducers-efficient-data-processing-pipelines-in-javascript-7985330fe73d">трансдьюсерах</a>. Кроме того, он плохо отлавливает ошибки и часто жалуется на ошибки которые ими не являются вовсе.</p>
<p>Попросту говоря, TypeScript не хватает гибкости и функциональности, чтобы соответствовать моему преставлению о написании кода. Но я продолжаю верить, что когда-нибудь он обрастет недостающими возможностями. Идея управления типами (там, где это необходимо), даже несмотря на все сложности внедрения на реальных проектах, мне нравится.</p>
<p>Мое мнение: очень классная технология для узких случаев. При этом она переоценена и имеет низкий КПД на больших проектах. В этом вся ирония: TypeScript позиционирует себя как «JavaScript, который масштабируется», но кажется они забыли одно слово: «JavaScript, который <em>странно</em> масштабируется».</p>
<p>Что нам нужно, так это система типов больше похожая на Huskell, меньше — на Java.</p>
<h3>Другие темы для изучения по JavaScript</h3>
<ul>
<li><a href="https://graphql.org/">GraphQL</a> для запросов</li>
<li><a href="https://redux.js.org/">Redux</a> для управления состоянием приложения</li>
<li><a href="https://github.com/redux-saga/redux-saga">Redux-saga</a> для работы с сайд-эффектами</li>
<li><a href="https://github.com/paralleldrive/react-feature-toggles">React-feature-toggles</a> для непрерывной интеграции и тестирования</li>
<li><a href="https://github.com/ericelliott/riteway">RITEway</a> для прекрасных читаемых юнит-тестов</li>
</ul>
<h2>Расцвет криптоиндустрии</h2>
<p>Год назад я говорил, что блокчейн и финтех станут значимыми в 2018. Мои слова подтвердились. Одной из важнейших тем последних двух лет стал расцвет криптоиндустрии и появление понятия <em>интернет ценностей</em> (Internet of Value). Запомните его. Скоро вы о нем услышите.</p>
<p>Если вы, подобно мне, следите за децентрализованными приложениями со времен P2P, то знаете, процесс не был быстрым. Теперь, когда Биткоин «поджёг фитиль», доказав самодостаточность технологии на примере криптовалюты, взрыва не остановить.</p>
<p>Биткоин вырос в несколько раз всего за несколько лет. Вы могли слышать, что 2018 стал «зимой для криптовалют», и наверняка думаете, что эта сфера столкнулась с проблемами. Ерунда. Что случилось на самом деле: в конце 2017 Биткоин достиг очередного пика, и рынок немного отыграл назад. Такое происходит при каждом достижении десятикратного рубежа стоимости.</p>
<p><strong>Точки десятикратного роста Биткоина</strong></p>
<figure>
    <img src="https://web-standards.ru/articles/top-themes-and-frameworks/image/9.png" alt="Точки десятикратного роста Биткоина">
</figure>
<p>Здесь видно, вершина стрелки графика находится на десятикратной позиции, относительно предыдущей, и уходит вниз для коррекции.</p>
<p>Фандрайзинг для ICO достиг пика в начале 2018. Пузырь инвестиций в эту сферу привел к взрывному росту спроса на специалистов, который подошел к отметке в 10 000 открытых вакансий в январе 2018. По данным Indeed, после этого значение опустилась до 2 400. Но мы лишь в начале пути.</p>
<figure>
    <img src="https://web-standards.ru/articles/top-themes-and-frameworks/image/10.png" alt="График появления нового ICO">
</figure>
<p>О криптоиндустрии можно говорить много, но это заслуживает отдельной публикации. Если интересно, читайте «<a href="https://medium.com/the-challenge/blockchain-platforms-tech-to-watch-in-2019-f2bfefc5c23">Blockchain Platforms and Tech to Watch in 2019</a>».</p>
<h3>Другие интересные технологии</h3>
<p>Как я и предсказывал, эти технологии продолжили расти в 2018:</p>
<p><strong>Искусственный интеллект/машинное обучение</strong> раскручивается вовсю: 30 000 вакансий на конец 2018 года, <a href="https://en.wikipedia.org/wiki/Deepfake">deepfake</a>, генеративное искусство, потрясающие примеры работы с видео от исследовательских команд таких компаний как Adobe. Еще не было столь подходящего времени для изучения AI.</p>
<p><strong>Прогрессивные веб-приложения</strong>, теперь поддерживаемые Google, Apple, Microsoft, Amazon и другими гигантами, становятся стандартом. Невероятно, как быстро я стал относиться к PWA на моем телефоне как к должному. У меня больше не стоит приложение Twitter для Android, вместо него я использую <a href="http://mobile.twitter.com/home">Twitter PWA</a>.</p>
<p><strong>AR</strong> (augmented reality — дополненная реальность), <strong>VR</strong> (virtual reality — виртуальная реальность), <strong>MR</strong> (mixed reality — смешанная реальность), словно Вольтрон, объединились в <strong>XR</strong> (eXtended reality — расширенная реальность). На пороге — полное погружение. Прогнозирую, что через 5–10 лет XR-очки станут массовым товаром. В течение 20 лет — контактные линзы. Тысячи новых вакансий в 2018 году и взрывной рост в 2019.</p>
<p><strong>Роботы, дроны и беспилотные машины.</strong> Самоуправляемые дроны уже с нами, работы продолжают развиваться, все больше машин на автопилоте. Эти технологии продолжат развиваться и менять мир в 2019 и в течение 20 следующих лет.</p>
<p><strong>Квантовые вычисления</strong> развивались, но мейнстримом не стали. Пока. Очевидно, мой прогноз, что «перелом произойдет в 2019 году или позже», звучит слишком оптимистично.</p>
<p>В криптоиндустрии большое внимание уделяется квантово-устойчивому шифрованию (квантовые вычисления ломают современные представления о «дорогих» алгоритмах, тогда как блокчейн базируется на «дорогих» вычислениях). Но несмотря на череду интересных исследований, реальное положение дел такое, как описано в <a href="https://www.theregister.co.uk/2018/12/06/quantum_computing_slow/">этом докладе</a>:</p>
<blockquote>
<p>В период с 2000 по 2017 годы квантовые вычисления попадали в <a href="https://www.gartner.com/en/research/methodologies/gartner-hype-cycle">Перспективный список Гартнера</a> одиннадцать раз. Каждый раз их оценивали, как находящиеся на начальном этапе и требующих как минимум 10 лет для прохождения цикла зрелости.</p>
</blockquote>
<p>Это напоминает историю развития искусственного интеллекта. Первые разработки появились в 1950-х. Достигли ограниченного, но интересного результата в 1980-х и 1990-х. Но про настоящий прорыв можно говорить где-то с 2010.</p>
<p><strong>Эрик Эллиот</strong> — эксперт по распределённым системам, автор книг «<a href="https://leanpub.com/composingsoftware">Composing Software</a>» и «<a href="https://ericelliottjs.com/product/programming-javascript-applications-ebook/">Programming JavaScript Applications</a>». Как сооснователь <a href="https://devanywhere.io/">DevAnywhere.io,</a> обучает разработчиков навыкам, необходимым для удалённой работы и сохранения баланса работа-жизнь. Создаёт и консультирует команды по разработке криптопроектов, участвовал в разработке проектов для Adobe Systems, Zumba Fitness, The Wall Street Journal, ESPN, BBC и популярных музыкантов, включая Usher, Frank Ocean, Metallica и многих других.</p>

                    ]]></description><pubDate>Wed, 16 Jan 2019 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/top-themes-and-frameworks/</guid></item><item><title>Та же история, только с CSS</title><link>https://web-standards.ru/articles/css-vs-js-same-story/</link><description><![CDATA[
                        <p>Мы видим ту же ситуацию с CSS, которую видели с HTML и JS раньше — она привела к текущей ситуацией с JS-фреймворками. Тред 👇</p>
<p>📱Соперничество с нативными интерфейсами приложений.</p>
<p>🐘 Большие команды, работающие над более сложными приложениями, с потребностями, которые не обеспечиваются текущими стандартизированными технологиями.</p>
<p>🚀 Культура «двигайся быстро, ломай дрова».</p>
<p>🏰 Недоверие к организациям по стандартизации, которые как будто заперлись в своих башнях из слоновой кости и не понимают потребностей разработчиков.</p>
<p>⚔️ Поляризованное сообщество, где прагматизм конфликтует с лучшими практиками.</p>
<p>Как и с JS, последствия самые плохие: взрыв разнообразия инструментов и конкурирующих решений, которые чрезмерно усложняют написание кода для веба, а также возрастающая нагрузка загружаемых ресурсов, вредящая удобству пользователей.</p>
<p>Как нам это исправить?</p>
<p>☮️ Предложить места за столом стандартизации разработчикам популярных альтернативных CSS-решений.</p>
<p>💰 Если они не поддерживаются крупными компаниями, то покрыть их расходы и даже заплатить за их время.</p>
<p>👂 Выслушать, что им есть сказать и очень постараться понять проблемы, которые они пытаются решить и причины выбора решений, которые они нашли.</p>
<p>✋ Прекратить считать, что эти JS-решения для CSS — всего лишь неспособность разработчиков понять CSS, и попробовать разобраться, как исправить текущую ситуацию. Это то, что мы должны делать, согласно <a href="https://www.w3.org/TR/html-design-principles/#priority-of-constituencies">приоритету групп</a>.</p>
<p>🤝 Выбрать комплексный подход для проблем, затрагивающих JS, HTML и CSS (загрузка ресурсов, сборка, изоляция и др.) и улучшить коммуникацию между организациями по стандартизации (<a href="https://twitter.com/TC39">@TC39</a>, <a href="https://twitter.com/csswg">@csswg</a>, <a href="https://twitter.com/WHATWG">@WHATWG</a>, <a href="https://twitter.com/wicg_">@wicg_</a>, <a href="https://twitter.com/w3c">@w3c</a>, и т.д.) Может быть через <a href="https://twitter.com/w3ctag">@w3ctag</a>?</p>
<p>🏭 Убедиться, что за столом стандартизации сидят разработчики больших проектов, чтобы их требования были поняты и потребности учтены.</p>
<p>🏆 Помнить, что сам факт существования различных JS-решений сегодня — это демонстрация успеха <a href="https://github.com/extensibleweb/manifesto">Манифеста расширяемого веба</a>.</p>
<p>🐄 Не забывать, что Манифест расширяемого веба состоит из двух частей: первая — про примитивы, которые позволяют разработчикам экспериментировать и двигаться быстрее, второй — про необходимость определять общие решения и «асфальтировать тропинки».</p>

                    ]]></description><pubDate>Wed, 16 Jan 2019 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/css-vs-js-same-story/</guid></item><item><title>Инклюзивные компоненты: переключатели</title><link>https://web-standards.ru/articles/toggle-buttons/</link><description><![CDATA[
                        <p>Некоторые вещи либо включены, либо выключены. Когда что-то не включено, то оно обязательно выключено, и наоборот. Эта идея настолько элементарна, что я всё усложнил этим объяснением. Однако не все переключатели одинаковы: хотя они выполняют простые функции, их практическое применение и формы сильно различаются.</p>
<p>В этой статье я объясню, как сделать переключатели инклюзивными <em>(включающими, доступными для всех — прим. редактора).</em> Нет единого способа решения этой проблемы, как и в случае любого другого компонента, особенно когда подобные контролы рассматриваются в разных контекстах. Многое можно испортить или просто забыть сделать, так что попробуем этого избежать.</p>
<h2>Изменение состояния</h2>
<p>Если веб-приложение не изменилось так, как ожидал пользователь, такое взаимодействие будет негативным. При этом возможность моментально изменять содержимое страниц, без их обновления, существовала не всегда.</p>
<p>К сожалению, в какой-то момент мы решили, что доступными страницами были те, где мало что происходит: это статические документы, созданные только для чтения. То есть мы прикладывали мало усилий для того, чтобы сделать взаимодействие с ними более богатым, с <em>сохранением выбора для пользователей.</em></p>
<p>Есть распространённое заблуждение о том, что скринридеры <em>не понимают</em> JavaScript. Это совершенно неверно. Все популярные скринридеры реагируют на изменения в DOM по мере их возникновения. Но изменения основных состояний (визуально и для вспомогательных технологий) не обязательно связаны с JavaScript.</p>
<h2>Чекбоксы и радиокнопки</h2>
<p>Элементы форм — базовые элементы страниц. Когда мы не используем их напрямую, то всё равно должны уделять пристальное внимание тому, как они себя ведут. Проработка смены состояний у таких элементов установлена принципами юзабилити, которыми глупо было бы пренебрегать.</p>
<p>В принципе, нативный <code>&lt;input&gt;</code> с типом checkbox идеально подходит для любых ситуаций, когда нужно что-то включить или выключить. Он обладает всеми основными составляющими доступного контрола при наличии правильного <code>&lt;label&gt;</code>: к нему есть доступ у скринридера и клавиатуры на разных платформах и устройствах. Об изменении его состояния с <em>«установлен» (checked)</em> на <em>«не установлен» (unchecked)</em> и наоборот сообщается сразу же.</p>
<p>В примере ниже с помощью чекбокса можно включить или выключить уведомления по электронной почте.</p>
<pre><code tabindex="0" class="language-html">&lt;input type=&quot;checkbox&quot; id=&quot;notify&quot; name=&quot;notify&quot; value=&quot;on&quot;&gt;
&lt;label for=&quot;notify&quot;&gt;Уведомлять по почте&lt;/label&gt;
</code></pre>
<img src="https://web-standards.ru/articles/toggle-buttons/images/notify.png" alt="">
<p>Скринридеры примерно одинаково интерпретируют этот контрол. При фокусе на нём (если перемещаться по странице с помощью клавиши <kbd>Tab</kbd>) будет объявлено что-то вроде <em>«Уведомлять по почте, флажок не установлен».</em> В этом объявлении есть содержимое <code>&lt;label&gt;</code>, роль и состояние элемента.</p>
<p>При выборе чекбокса большинство скринридеров сразу же объявит, что состояние изменилось на <em>«установлен»</em> (иногда повторяя информацию о содержимом <code>&lt;label&gt;</code> и роли). Состояние элемента будет обработано без использования JavaScript, а скринридер сообщит пользователю информацию об изменениях контрола.</p>
<h3>Примечание: скринридеры нужны не только слепым</h3>
<p>Некоторые работают со скринридерами, чтобы было легче понять интерфейс. У других может быть дислексия или низкий уровень грамотности. Есть даже те, у кого имеются незначительные физические или когнитивные проблемы, затрудняющие понимание интерфейса, и кто просто иногда хочет, чтобы им читали вслух.</p>
<p><strong>Поддержка скринридеров — это поддержка скринридеров, а не только слепых пользователей.</strong> Это инструменты для большого числа разных людей, которым нравится ими пользоваться.</p>
<p>В этом случае часть переключателя, в которой содержатся значения <code>on</code> или <code>off</code>, не связана с <code>&lt;label&gt;</code>, но зато отражает состояние элемента. Напротив, содержимое <code>&lt;label&gt;</code> нужно для определения того, что именно мы выключаем или включаем. Если вы считаете, что пользователям необходима более очевидная аналогия с включением и выключением, можно использовать группу радиокнопок.</p>
<pre><code tabindex="0" class="language-html">&lt;fieldset&gt;
    &lt;legend&gt;Уведомлять по почте&lt;/legend&gt;
    &lt;input type=&quot;radio&quot; id=&quot;notify-on&quot; name=&quot;notify&quot; value=&quot;on&quot; checked&gt;
    &lt;label for=&quot;notify-on&quot;&gt;включить&lt;/label&gt;
    &lt;input type=&quot;radio&quot; id=&quot;notify-off&quot; name=&quot;notify&quot; value=&quot;off&quot;&gt;
    &lt;label for=&quot;notify-off&quot;&gt;выключить&lt;/label&gt;
&lt;/fieldset&gt;
</code></pre>
<img src="https://web-standards.ru/articles/toggle-buttons/images/radio.png" alt="">
<p>Заголовок группы элементов — это мощный инструмент. Как следует из названия, он является общей подписью для связанных (сгруппированных) элементов. В этом случае элемент <code>&lt;fieldset&gt;</code> работает вместе с <code>&lt;legend&gt;</code> и даёт возможность задать для двух радиокнопок одно описание «Уведомлять по почте». Эти кнопки объединены общим значением атрибута <code>name</code>. Благодаря этому можно переключаться между ними при помощи стрелок на клавиатуре. Использование семантического HTML не только сообщает информацию об элементах, но и влияет на их поведение.</p>
<p>В Windows, когда пользователь установил фокус на первом контроле, скринридеры JAWS и NVDA добавляют заголовок группы к отдельному <code>&lt;label&gt;</code> этого контрола и считают количество радиокнопок в группе. NVDA ещё добавляет термин «группировка», чтобы стало более очевидно, что это группа радиокнопок. В примере выше фокус сделан на первой (выбранной по умолчанию) радиокнопке. Скринридеры сделают из этого такой вывод: <em>«Уведомлять по почте, группировка, переключатель выделен, один из двух».</em></p>
<p>Несмотря на то, что выбран первый элемент (некоторые скринридеры объявят <em>«выделено»),</em> пользователь может переключаться между ним и второй радиокнопкой. «Включить» и «выключить» — это два возможных, если хотите, <em>лексических состояния</em> у такого составного контрола.</p>
<h3>Примечание: стилизация элементов форм</h3>
<p>Печально известно, что элементы форм сложно стилизовать. Однако есть хорошо поддерживаемые CSS-техники для стилизации радиокнопок и чекбоксов, которые я описал в <a href="https://www.sitepoint.com/replacing-radio-buttons-without-replacing-radio-buttons/">«Replacing Radio Buttons Without Replacing Radio Buttons»</a>. Если нужны советы о том, как стилизовать контролы и поля для загрузки файлов, посмотрите гайдлайн <a href="http://wtfforms.com/">«WTF Forms?»</a> Марка Отто.</p>
<h2>Что-то здесь не так</h2>
<p>Логично реализовывать контролы, которые что-то включают и выключают, в виде чекбоксов и радиокнопок. Они доступны для мыши, сенсорных дисплеев, клавиатуры и вспомогательных технологий на разных устройствах, в браузерах и в операционных системах.</p>
<p>Но доступность — это лишь одна из частей инклюзивного дизайна. Эти контролы также должны <em>иметь смысл</em> для пользователей и восприниматься ими однозначно.</p>
<p>Одна из проблем элементов форм — их давняя связь со сбором данных. То есть чекбоксы и радиокнопки воспринимаются как контролы для установки <em>значений.</em> Когда пользователь устанавливает чекбокс, он просто переключает состояние, но также ему может <em>показаться</em>, что он выбирает значения для отправки на сервер.</p>
<p>Будь то зрячий пользователь, который видит чекбокс, или пользователь скринридера, который слышит информацию об элементе, поведение такого контрола сложно предсказать. Мы ожидаем, что переключатель — это кнопка, а чекбокс и радиокнопка на самом деле части формы.</p>
<h2>Настоящий переключатель</h2>
<p>Иногда мы используем элемент <code>&lt;button&gt;</code> для отправки форм. Для того, чтобы эти кнопки были полностью совместимы и надёжны, стоит задать им в атрибуте <code>type</code> значение <code>submit</code>.</p>
<pre><code tabindex="0" class="language-html">&lt;button type=&quot;submit&quot;&gt;Отправить&lt;/button&gt;
</code></pre>
<p>Но это только один вид кнопки, который встречается в определённом сценарии использования. На самом деле элемент с классом <code>&lt;button&gt;</code> может использоваться для всего, что угодно, а не только в формах. Это же просто <em>кнопки.</em> Мы напоминаем себе об этом, задавая им в <code>type</code> значение <code>button</code>.</p>
<pre><code tabindex="0" class="language-html">&lt;button type=&quot;button&quot;&gt;Отправить&lt;/button&gt;
</code></pre>
<p>Типовая кнопка — это твой ключевой элемент для изменения чего угодно в интерфейсе (используя JavaScript и без обновления страницы). Кроме случаев, когда нужно переместиться внутри одной страницы или перейти на другую. Это уже задача ссылок.</p>
<p>Кнопки, как и ссылки — это интерактивные элементы, которые стоит активно использовать в веб-приложениях. У них уже есть роль <code>button</code> и они по умолчанию доступны для клавиатуры и скринридера. И, в отличие от элементов форм, их просто стилизовать.</p>
<p>Так как нам сделать <code>&lt;button&gt;</code> переключателем? Это ситуация, в которой потребуется WAI-ARIA как часть прогрессивного улучшения. Состояний из WAI-ARIA нет в базовом HTML, например, состояния «нажата» (pressed). Представьте себе кнопку включения компьютера. Когда она нажата, то компьютер включён, когда не нажата, то компьютер должен быть выключен.</p>
<pre><code tabindex="0" class="language-html">&lt;button type=&quot;button&quot; aria-pressed=&quot;true&quot;&gt;
    Уведомление по почте
&lt;/button&gt;
</code></pre>
<p>Атрибут состояния из WAI-ARIA вроде <code>aria-pressed</code> ведёт себя как булевый тип данных. Однако, в отличие от стандартных атрибутов состояний из HTML, например, <code>checked</code>, такие атрибуты должны иметь явно заданные значения <code>true</code> или <code>false</code>. Недостаточно просто добавить <code>aria-pressed</code>. Кроме того, в отсутствие атрибута не будет объявлено <em>«не нажата» (unpressed).</em> Кнопка без атрибута — это просто стандартная кнопка.</p>
<p>Вы можете использовать этот элемент управления внутри формы или за её пределами, в зависимости от ваших целей. Но если вы используете их внутри формы, то важно задать им <code>type=&quot;button&quot;</code>. Если этого не указать, то некоторые браузеры по умолчанию установят <code>type=&quot;submit&quot;</code> и попытаются отправить форму.</p>
<p>Переключить состояние с <code>true</code> (включён) на <code>false</code> (выключен) можно с помощью простого обработчика кликов. Поскольку мы используем <code>&lt;button&gt;</code>, то этот вид события может быть запущен по щелчку мыши, нажатию на клавиши <kbd>Space</kbd> или <kbd>Enter</kbd> или по тапу на сенсорном экране. Отзывчивость для каждого из этих действий — это то, что по умолчанию встроено в элементы <code>&lt;button&gt;</code>. Если вы загляните в <a href="https://developer.mozilla.org/en/docs/Web/API/HTMLButtonElement">HTMLButtonElement</a>, то увидите другие значения, такие как <code>disabled</code>. Это состояние также доступно из коробки. Если элементы <code>&lt;button&gt;</code> не используются, то их поведение нужно воспроизводить с помощью скриптов.</p>
<pre><code tabindex="0" class="language-js">const toggle = document.querySelector('[aria-pressed]');

toggle.addEventListener('click', (e) =&gt; {
    let pressed = e.target.getAttribute('aria-pressed') === 'true';
    e.target.setAttribute('aria-pressed', String(!pressed));
});
</code></pre>
<h2>Более очевидное состояние</h2>
<p>Интересное происходит, когда на кнопку с атрибутом <code>aria-pressed</code> наталкиваются какие-то скринридеры: они определяют её как <em>«переключатель»</em> или, в некоторых случаях, <em>«кнопка нажата».</em> Наличие атрибута состояния изменяет сущность кнопки.</p>
<p>Когда скринридер NVDA сделает фокус на кнопке с <code>aria-pressed=&quot;true&quot;</code> из примера, он объявит: <em>«Уведомлять по почте, переключатель, включено».</em> Состояние <em>«включено» (pressed)</em> подходит в данном случае больше, чем <em>«установлено» (checked).</em> Плюс мы избегаем неоднозначного понимания контрола. Когда по кнопке кликнут, то сразу же будет объявлено <em>«отключено».</em></p>
<h2>Стилизация</h2>
<p>Мы выполняем важную часть работы над дизайном и создаём разные вещи для веба с помощью HTML. Я уверен, что если вы создаёте Первый HTML-прототип™, то можете быть уверены в том, что у вас есть надёжная основа для продукта с фирменным стилем.</p>
<p>В случае с нашим переключателем в его основе лежат семантика и поведение, которые делают кнопку совместимой с разными типами ввода данных (например, с программой активации голосом) и их вывода (например, со скринридером) на разных устройствах. Это возможно благодаря HTML. CSS необходим, чтобы сделать управление интерфейсом понятным визуально.</p>
<p>Форма должна соответствовать определённым функциям, чего просто добиться в CSS: всё, что в нашем HTML добавляет для кнопки функцию простого переключателя, также можно использовать для придания ему определённого внешнего вида.</p>
<ul>
<li><code>&lt;button&gt;</code> → селектор элемента <code>button</code>.</li>
<li><code>aria-pressed=&quot;true&quot;</code> → селектор атрибута <code>[aria-pressed=&quot;true&quot;]</code>.</li>
</ul>
<p>В согласованных и, поэтому, лёгких для понимания интерфейсах, кнопки должны иметь конкретный внешний вид и выглядеть как кнопки. Так что стили нашего базового переключателя, вероятно, должны наследоваться от блочного элемента <code>button</code>:</p>
<pre><code tabindex="0" class="language-css">/* Например… */
button {
    color: white;
    background-color: #000;
    border-radius: 0.5rem;
    padding: 1em 2em;
}
</code></pre>
<p>Есть несколько способов для визуального обозначения того, что кнопка нажата. При буквальной интерпретации, мы могли бы сделать кнопку выглядящей вжатой с помощью <code>inset</code> в <code>box-shadow</code>. Давайте используем селектор атрибута для этого:</p>
<pre><code tabindex="0" class="language-css">[aria-pressed=&quot;true&quot;] {
    box-shadow: inset 0 0 0 0.15rem #000,
                inset 0.25em 0.25em 0 #fff;
}
</code></pre>
<p>Чтобы закончить с аналогией с нажатием и его отсутствием, мы можем использовать позиционирование и <code>box-shadow</code>, чтобы сделать ненажатую кнопку выпуклой. В каскаде этот блок должен располагаться над блоком <code>[aria-prressed=&quot;true&quot;]</code>.</p>
<pre><code tabindex="0" class="language-css">[aria-pressed] {
    position: relative;
    top: -0.25rem;
    left: -0.25rem;
    box-shadow: 0.125em 0.125em 0 #fff,
                0.25em 0.25em #000;
}
</code></pre>
<img src="https://web-standards.ru/articles/toggle-buttons/images/notification.png" alt="">
<p><strong>Примечание:</strong> это лишь один из многих методов стилизации такой кнопки. Вы можете найти что-то более очевидное, вроде использования текста «включить» и «выключить» как в следующем примере. Это будет понятно большинству пользователей.</p>
<h3>Примечание: не полагайтесь только на цвет</h3>
<p>«Включить» часто обозначается зелёным цветом, а «выключить» — красным. Это общепринятое соглашение, и нет ничего плохого в том, чтобы следовать ему. Однако будьте осторожны, когда используете только цвет для описания двух состояний кнопки. В этом случае дальтоники не заметят разницы между ними.</p>
<figure>
    <img src="https://web-standards.ru/articles/toggle-buttons/images/controls.png" alt="">
    <figcaption>
        Эти варианты контрола не соответствуют требованиям <a href="https://www.w3.org/TR/2008/REC-WCAG20-20081211/#visual-audio-contrast-without-color">WCAG 2.0 1.4.1 Use Of Color (Level A)</a>.
    </figcaption>
</figure>
<h2>Стили фокуса</h2>
<p>Это важно для кнопок, как и для <em>всех</em> других интерактивных элементов, у которых есть стили фокуса. Если их не будет, то люди, которые используют клавиатуру для навигации, не увидят какой элемент выбран и что с ним можно взаимодействовать.</p>
<p>Лучшие стили фокуса — это те, которые не влияют на расположение (при переходе от одного элемента к другому элементы вокруг них не должны смещаться и отвлекать внимание). Можно использовать <code>outline</code>, но, в большинстве браузеров, <code>outline</code> — это просто рамка вокруг элемента. Для того, чтобы стиль фокуса повторял закруглённые углы нашей кнопки, лучше подходит свойство <code>box-shadow</code>. Если мы уже используем <code>box-shadow</code>, то нужно быть осторожными: учитывать два значения <code>box-shadow</code>, разделённые запятой, при одновременном нажатии кнопки и фокусе на ней.</p>
<pre><code tabindex="0" class="language-css">/* Удаляет outline по умолчанию и добавляет внешнюю тень */
[aria-pressed]:focus {
    outline: none;
    box-shadow: 0 0 0 0.25rem yellow;
}

/* Отображает и внутреннюю, и внешнюю тени */
[aria-pressed=&quot;true&quot;]:focus {
    box-shadow: 0 0 0 0.25rem yellow,
                inset 0 0 0 0.15rem #000,
                inset 0.25em 0.25em 0 #fff;
}
</code></pre>
<h2>Изменение подписей</h2>
<p>В дизайне предыдущего переключателя есть отдельная и уникальная подпись к нему. Разница между двумя состояниями зависит от изменений в атрибуте, которые влияют и на его стиль. Что если нам понадобится сделать кнопку, текст которой изменится с «включён» на «выключен» или с «пауза» на «воспроизведение»?</p>
<p>Это очень просто сделать на JavaScript, но есть пара нюансов, которые нужно учесть.</p>
<ol>
<li>Если подпись изменяется, то что происходит с состоянием элемента?</li>
<li>Если текст просто «включён» или «выключен» («воспроизведение» или «пауза», «активен» или «неактивен»), то как мы узнаем, что кнопка на самом деле контрол?</li>
</ol>
<p>В примере с предыдущим переключателем подпись к нему описывает, что именно будет включено или выключено. Там, где не указано, что включается и выключается, сразу же возникает путаница: нужно ли после того, как я нажал на кнопку и включил что-то, снова на неё нажать, чтобы опять это выключить?</p>
<p>В соответствии с правилом большого пальца <em>(это общее правило, которое основано на практическом опыте и почти всегда работает — прим. переводчика)</em> никогда не нужно изменять одновременно состояние «нажата» и текст кнопки. Если текст изменился, то и состояние кнопки тоже в некотором смысле изменилось, но не благодаря явному управлению состоянием с помощью WAI-ARIA.</p>
<p>В примере ниже изменяется только текст кнопки.</p>
<pre><code tabindex="0" class="language-js">const button = document.querySelector('button');

button.addEventListener('click', (e) =&gt; {
    let text = e.target.textContent === 'Play' ? 'Pause' : 'Play';
    e.target.textContent = text;
});
</code></pre>
<p>Проблема этого метода заключается в том, об изменениях в тексте не объявляется в тот же момент, когда они происходят. Поэтому, когда вы кликаете по кнопке воспроизведения, нет сообщения о том, что она <em>«нажата».</em> Вместо этого вам нужно сначала вручную убрать с неё фокус, а потом выбрать снова, чтобы услышать, что она изменилась. Для зрячих пользователей это не составит труда, но вызовет неудобства у слепых.</p>
<p>Часто у кнопок воспроизведения и паузы изменяются иконки: значок воспроизведения (треугольник, стоящий на одной из граней) заменяется на значок паузы (две вертикальные линии). Мы могли бы при этом сохранить скрытый текст кнопки и одновременно изменить состояние элемента.</p>
<pre><code tabindex="0" class="language-html">&lt;!-- Пауза --&gt;
&lt;button type=&quot;button&quot; aria-pressed=&quot;false&quot; aria-label=&quot;play&quot;&gt;
    &amp;#x25b6;
&lt;/button&gt;
&lt;!-- Воспроизведение --&gt;
&lt;button type=&quot;button&quot; aria-pressed=&quot;true&quot; aria-label=&quot;play&quot;&gt;
    &amp;#x23f8;
&lt;/button&gt;
</code></pre>
<p>Так как атрибут <code>aria-label</code> перезаписывает содержание текстового узла, в котором находятся символы Юникода, то кнопка паузы будет объявлена так: <em>«Воспроизведение, кнопка не нажата».</em> И, в случае с кнопкой воспроизведения, скринридер произнесёт: <em>«Воспроизведение, кнопка нажата».</em></p>
<p>Это везде хорошо работает, кроме случаев, когда идёт речь о распознавании речи и голосовом управлении. В режиме распознавания речи обычно нужно произносить названия кнопок вслух. И, если пользователь увидел кнопку паузы, то его первой мыслью будет произнести «пауза», а не «воспроизведение». Поэтому более надёжный способ — это изменение подписей к элементам, а не переключение их состояния.</p>
<figure>
    <img src="https://web-standards.ru/articles/toggle-buttons/images/play-and-pause.png" alt="">
    <figcaption>
        Никогда не меняйте одновременно подпись и состояние элемента. В этом примере это приведёт к тому, что состояние паузы станет «нажата». Поскольку видео или аудио в этот момент будут воспроизводиться, с точки зрения языка кнопка паузы не может быть нажата в данный момент.
    </figcaption>
</figure>
<h2>Добавление дополнительных подписей</h2>
<p>В некоторых случаях может понадобиться добавить в переключатель слова «включить» и «выключить». Нужно убедиться в том, что есть чёткая связь между каждым переключателем и соответствующей ему дополнительной подписью.</p>
<p>Представьте настройки уведомления по почте, которые сгруппированы в один список вместе с похожими настройками. Каждый элемент списка содержит описание настройки и выглядит как переключатель. Выражения «включён» и «выключен» — это часть их дизайна. Некоторые элементы <code>&lt;span&gt;</code> тут нужны для стилизации.</p>
<pre><code tabindex="0" class="language-html">&lt;h2&gt;Уведомления&lt;/h2&gt;
&lt;ul&gt;
    &lt;li&gt;
        Уведомлять по почте
        &lt;button&gt;
            &lt;span&gt;вкл.&lt;/span&gt;
            &lt;span&gt;выкл.&lt;/span&gt;
        &lt;/button&gt;
    &lt;/li&gt;
    &lt;li&gt;
        Уведомлять по SMS
        &lt;button&gt;
            &lt;span&gt;вкл.&lt;/span&gt;
            &lt;span&gt;выкл.&lt;/span&gt;
        &lt;/button&gt;
    &lt;/li&gt;
    &lt;li&gt;
        Уведомлять по факсу
        &lt;button&gt;
            &lt;span&gt;вкл.&lt;/span&gt;
            &lt;span&gt;выкл.&lt;/span&gt;
        &lt;/button&gt;
    &lt;/li&gt;
    &lt;li&gt;
        Уведомлять сигнальным огнём
        &lt;button&gt;
            &lt;span&gt;вкл.&lt;/span&gt;
            &lt;span&gt;выкл.&lt;/span&gt;
        &lt;/button&gt;
    &lt;/li&gt;
&lt;/ul&gt;
</code></pre>
<img src="https://web-standards.ru/articles/toggle-buttons/images/notify2.png" alt="">
<p>Преимущество списка заключается в том, что как видимые, так и скрытые элементы группируются вместе, поэтому видна связь между ними. Списки не только помогают понять взаимосвязь элементов, но и полезны для некоторых скринридеров, которые могут быстро перемещаться как внутри одного списка, так и между несколькими. Например, в JAWS есть горячие клавиши для списков (L) и элементов списков (I).</p>
<p>Каждая кнопка и подпись к ней связаны общим элементом списка. Однако отсутствие явного и уникального <code>&lt;label&gt;</code> — это опасная ситуация, особенно в тех случаях, когда включён режим распознавания речи. Мы можем связать каждую кнопку с текстом элементов списка, используя атрибут <code>aria-labelledby</code>:</p>
<pre><code tabindex="0" class="language-html">&lt;h2&gt;Уведомления&lt;/h2&gt;
&lt;ul&gt;
    &lt;li&gt;
        &lt;span id=&quot;notify-email&quot;&gt;Уведомлять по почте&lt;/span&gt;
        &lt;button aria-labelledby=&quot;notify-email&quot;&gt;
            &lt;span&quot;&gt;вкл.&lt;/span&gt;
            &lt;span&gt;выкл.&lt;/span&gt;
        &lt;/button&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;span id=&quot;notify-sms&quot;&gt;Уведомлять по SMS&lt;/span&gt;
        &lt;button aria-labelledby=&quot;notify-sms&quot;&gt;
            &lt;span&gt;вкл.&lt;/span&gt;
            &lt;span&gt;выкл.&lt;/span&gt;
        &lt;/button&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;span id=&quot;notify-fax&quot;&gt;Уведомлять по факсу&lt;/span&gt;
        &lt;button aria-labelledby=&quot;notify-fax&quot;&gt;
            &lt;span&gt;вкл.&lt;/span&gt;
            &lt;span&gt;выкл.&lt;/span&gt;
        &lt;/button&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;span id=&quot;notify-smoke&quot;&gt;Уведомлять сигнальным огнём&lt;/span&gt;
        &lt;button aria-labelledby=&quot;notify-smoke&quot;&gt;
            &lt;span&gt;вкл.&lt;/span&gt;
            &lt;span&gt;выкл.&lt;/span&gt;
        &lt;/button&gt;
    &lt;/li&gt;
&lt;/ul&gt;
</code></pre>
<p>Каждое значение <code>aria-labelledby</code> соответствует значению атрибута <code>id</code> у элементов с тегом <code>&lt;span&gt;</code> и устанавливает связь между ними и уникальными подписями у кнопок. Это работает также, как атрибут <code>for</code> элемента <code>&lt;label&gt;</code> для <code>id</code> в <code>&lt;input&gt;</code>.</p>
<h2>Роль switch</h2>
<p>Важно отметить, что атрибут <code>aria-label</code> из ARIA переопределяет содержимое каждой кнопки. Это значит, что мы можем снова использовать <code>aria-pressed</code> для того, чтобы сообщить о состоянии элемента. Однако, если эти кнопки являются переключателями, то мы можем вместо этого использовать роль switch <a href="https://www.w3.org/TR/wai-aria-1.1/#switch">из WAI-ARIA</a>, которая сообщает о состоянии через атрибут <code>aria-checked</code>.</p>
<pre><code tabindex="0" class="language-html">&lt;h2&gt;Уведомления&lt;/h2&gt;
&lt;ul&gt;
    &lt;li&gt;
        &lt;span id=&quot;notify-email&quot;&gt;Уведомлять по почте&lt;/span&gt;
        &lt;button role=&quot;switch&quot; aria-checked=&quot;true&quot;
                aria-labelledby=&quot;notify-email&quot;&gt;
            &lt;span&gt;вкл.&lt;/span&gt;
            &lt;span&gt;выкл.&lt;/span&gt;
        &lt;/button&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;span id=&quot;notify-sms&quot;&gt;Уведомлять по SMS&lt;/span&gt;
        &lt;button role=&quot;switch&quot; aria-checked=&quot;true&quot;
                aria-labelledby=&quot;notify-sms&quot;&gt;
            &lt;span&gt;вкл.&lt;/span&gt;
            &lt;span&gt;выкл.&lt;/span&gt;
        &lt;/button&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;span id=&quot;notify-fax&quot;&gt;Уведомлять по факсу&lt;/span&gt;
        &lt;button role=&quot;switch&quot; aria-checked=&quot;false&quot;
                aria-labelledby=&quot;notify-fax&quot;&gt;
            &lt;span&gt;вкл.&lt;/span&gt;
            &lt;span&gt;выкл.&lt;/span&gt;
        &lt;/button&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;span id=&quot;notify-smoke&quot;&gt;Уведомлять сигнальным огнём&lt;/span&gt;
        &lt;button role=&quot;switch&quot; aria-checked=&quot;false&quot;
                aria-labelledby=&quot;notify-smoke&quot;&gt;
            &lt;span&gt;вкл.&lt;/span&gt;
            &lt;span&gt;выкл.&lt;/span&gt;
        &lt;/button&gt;
    &lt;/li&gt;
&lt;/ul&gt;
</code></pre>
<p>Как вы будете стилизовать активное состояние контрола зависит от вас. Лично я бы не тратил время на запись классов для <code>&lt;span&gt;</code> с помощью JavaScript. Вместо этого лучше использовать пару CSS-псевдоклассов для поиска элементов <code>&lt;span&gt;</code> с нужными состояниями.</p>
<pre><code tabindex="0" class="language-css">[role=&quot;switch&quot;][aria-checked=&quot;true&quot;] :first-child,
[role=&quot;switch&quot;][aria-checked=&quot;false&quot;] :last-child {
    background: #000;
    color: #fff;
}
</code></pre>
<h3>Перемещаясь по настройкам</h3>
<p>Давайте сейчас поговорим о навигации по этому блоку с настройками, используя два разных способа: с помощью клавиши <kbd>Tab</kbd> (перемещаясь только по элементам с фокусом) и с помощью скринридера (проходя через каждый элемент).</p>
<p>Даже если вы используете клавишу <kbd>Tab</kbd>, скринридеры объявят не только о сущности и состоянии интерактивных элементов, на которых вы устанавливаете фокус. Например, когда вы выберете первый элемент с классом <code>&lt;button&gt;</code>, вы услышите, что это переключатель «оповещать по почте» и что он включён. Состояние элемента с ролью <code>switch</code> и <code>aria-checked=&quot;true&quot;</code> будет определено как <em>«включено».</em></p>
<h3>Примечание: поддержка <code>switch</code></h3>
<p>Роль <code>switch</code> поддерживается не так хорошо, как <code>aria-pressed</code>. Например, её не распознаёт ChromeVox — скринридер, который можно установить для Chrome.</p>
<p>Однако ChromeVox <em>не поддерживает</em> и <code>aria-checked</code>. Это означает, что он, вместо <em>«Переключатель, уведомлять по почте, включено»</em> объявит <em>«Кнопка, уведомлять по почте, нажата».</em> Это вполне корректно, хотя и не так очевидно. Скорее всего, пользователи просто ошибочно примут переключатель за чекбокс.</p>
<p>Любопытно, что NVDA определяет кнопку с <code>role=&quot;switch&quot;</code> и <code>aria-checked=&quot;true&quot;</code> как переключатель, если она нажата. Так как включение и выключение и нажатие и не нажатие равносильны, это вполне допустимо (хотя немного не соответствует ожиданиям).</p>
<p>Однако большинство скринридеров объявит, что вы находитесь в списке из четырёх пунктов и в данный момент выбран первый из них: полезная информация о контексте. Принцип работы примерно такой же, как у заголовка группы элементов, о котором я упоминал выше.</p>
<p>Это важно, потому что мы используем <code>aria-labelledby</code> для того, чтобы связать уточняющий текст с кнопкой, по аналогии с использованием <code>&lt;label&gt;</code>. Это доступно также когда выбран этот режим навигации.</p>
<p>Когда вы перемещаетесь от пункта к пункту (например, с помощью клавиши со стрелкой вниз, когда NVDA включён), будет объявлено всё, что встретится на пути, в том числе заголовки <em>(«уведомления, заголовок второго уровня»).</em> Конечно, когда перемещаешься по странице таким образом, текст <em>«уведомлять по почте»</em> объявляется как отдельно, так и вместе с соседней кнопкой. В какой-то степени это повтор, но в данном случае он имеет смысл: «здесь название настройки, а вот здесь её можно включить или выключить».</p>
<p>Проблема того, насколько явно нужно связать элементы управления с объектами, которыми они управляют, это один из аспектов UX-дизайна, и ему нужно уделить пристальное внимание. В случае, рассмотренном в этом посте, мы сохраняем наш классический переключатель для зрячих пользователей, при этом не запутывая и не вводя в заблуждение пользователей скринридеров вне зависимости от того, какой режим работы клавиатуры они используют. Это достаточно надёжное решение.</p>
<h2>Заключение</h2>
<p>Какой дизайн вы выберите и каким образом реализуете переключатели — это ваше дело. Но, надеюсь, вы вспомните этот пост, когда придёт время добавить подобный контрол в вашу библиотеку компонентов. Не вижу причин для того, чтобы переключатели или любой другой элемент интерфейса стали препятствием для пользователей, как это часто бывает.</p>
<p>Вы можете изучить здесь основы и добавить разные штрихи в свой дизайн, включая анимацию. Но, в первую очередь, важно заложить прочный фундамент.</p>
<h2>Чек-лист</h2>
<ul>
<li>Используйте элементы форм для переключателей, такие как чекбоксы, если уверены, что пользователь понимает, что они не нужны для отправки данных.</li>
<li>Используйте элементы <code>&lt;button&gt;</code> с атрибутами <code>aria-pressed</code> или <code>aria-checked</code>, а не ссылки.</li>
<li>Не меняйте одновременно подпись к элементу и его состояние.</li>
<li>Когда используете в подписи текст «включён» и «выключен» (или что-то подобное), можете переопределить их с помощью атрибута <code>aria-labelledby</code>.</li>
<li>Будьте осторожны и убедитесь, что уровень контраста между текстом кнопки и её фоновым цветом соответствует рекомендациям WCAG 2.0 <em>(сейчас уже актуальна <a href="https://www.w3.org/TR/WCAG21/#contrast-minimum">WCAG 2.1</a> — прим. переводчика).</em></li>
</ul>

                    ]]></description><pubDate>Sat, 05 Jan 2019 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/toggle-buttons/</guid></item><item><title>WebP сегодня: зачем и как?</title><link>https://web-standards.ru/articles/webp-today/</link><description><![CDATA[
                        <p><a href="https://developers.google.com/speed/webp/">WebP</a> — формат графики, разработанный Google в 2010. Он был создан, как альтернатива PNG и JPG, и отличается от них гораздо меньшим размером при том же качестве изображения.</p>
<figure>
    <img src="https://web-standards.ru/articles/webp-today/images/1.jpg" alt="">
    <figcaption>
        Интерфейс оптимизатора <a href="https://squoosh.app/">Squoosh</a>.
    </figcaption>
</figure>
<h2>Почему WebP?</h2>
<p>WebP — крайне полезный формат, ведь он даёт и производительность и возможности. В отличие от других форматов, WebP поддерживает сжатие как с потерями так и без, а также полупрозрачность и анимацию.</p>
<figure>
    <img src="https://web-standards.ru/articles/webp-today/images/2.png" alt="">
    <figcaption>
        Сравнение возможностей форматов WebP, PNG, JPG, GIF.
    </figcaption>
</figure>
<p>И даже со всеми этими возможностями, WebP обеспечивает меньший размер чем его конкуренты. В <a href="https://developers.google.com/speed/webp/docs/c_study#results">сравнительном исследовании формата</a> было установлено, что изображения в формате WebP, сжатые с потерями, в среднем на 30% меньше, чем в формате JPG, а сжатые без потерь — в среднем на 25% меньше чем в формате PNG.</p>
<h2>Как сконвертировать картинки в WebP?</h2>
<p>Есть несколько инструментов, которыми можно конвертировать JPG, PNG и другие форматы в WebP.</p>
<h3>Онлайн-инструменты</h3>
<ul>
<li><a href="https://squoosh.app/">Squoosh</a> — конвертация и сжатие онлайн</li>
<li><a href="http://online-convert.com/">Online-Convert.com</a> — конвертация онлайн</li>
</ul>
<h3>Инструменты командной строки</h3>
<p><a href="https://www.npmjs.com/package/cwebp">Cwebp</a> — самая популярная утилита для командной строки, для преобразовать изображения в формат WebP. После установки, мы можем конвертировать изображения указав качество, входной и выходной файлы.</p>
<pre><code tabindex="0" class="language-sh"># cwebp -q [quality] [input_file] -o [output_file]
cwebp -q 75 image.png -o image.webp
</code></pre>
<h3>Инструменты для Node.js</h3>
<p><a href="https://github.com/imagemin/imagemin">Imagemin</a>, вместе с плагином <a href="https://github.com/imagemin/imagemin-webp">imagemin-webp</a> — самая популярная библиотека для конвертации изображений в формат WebP. Вот пример скрипта, который преобразует в WebP все PNG- и JPG-файлы в папке.</p>
<pre><code tabindex="0" class="language-js">/* convert-to-webp.js */

const imagemin = require('imagemin');
const webp = require('imagemin-webp');

imagemin(['*.png', '*.jpg'], 'images', {
    use: [
    webp({ quality: 75})
    ]
});
</code></pre>
<p>Теперь, мы можем использовать этот скрипт из командной строки или с помощью сборщика:</p>
<pre><code tabindex="0" class="language-sh">node convert-to-webp.js
</code></pre>
<h3>Sketch</h3>
<p>В Sketch мы можем экспортировать любой слой в формате WebP.</p>
<figure>
    <img src="https://web-standards.ru/articles/webp-today/images/3.png" alt="">
    <figcaption>
        Интерфейс экспорта графики в <a href="https://www.sketchapp.com/">Sketch</a>.
    </figcaption>
</figure>
<h2>Что с поддержкой?</h2>
<p>На момент написания статьи <em>(21 ноября 2018 — прим. редактора),</em> WebP поддерживается в 72% браузеров.</p>
<figure>
    <img src="https://web-standards.ru/articles/webp-today/images/4.png" alt="">
    <figcaption>
        <a href="https://caniuse.com/#feat=webp">Поддержка WebP на Can I use</a>.
    </figcaption>
</figure>
<p>Хотя всего этого вполне достаточно чтобы убедиться в преимуществах WebP, всё же не стоит просто полагаться на формат без фолбэка. В браузерах, которые не поддерживают WebP, картинки будут поломаны.</p>
<p>Мы можем сделать фолбэк для WebP используя элемент <code>&lt;picture&gt;</code>. Этот HTML5-элемент позволяет нам добавлять несколько форматов для одной картинки.</p>
<pre><code tabindex="0" class="language-html">&lt;picture&gt;
    &lt;source type=&quot;image/webp&quot; srcset=&quot;image.webp&quot;&gt;
    &lt;source type=&quot;image/jpeg&quot; srcset=&quot;image.jpg&quot;&gt;
    &lt;img src=&quot;image.jpg&quot; alt=&quot;Моя картинка&quot;&gt;
&lt;/picture&gt;
</code></pre>
<p>Для добавления альтернативных форматов, мы используем элемент <code>&lt;source&gt;</code> вместе с <code>&lt;picture&gt;</code>. У элемента <code>&lt;source&gt;</code> есть несколько атрибутов, которые мы можем использовать, чтобы определить изображение и когда оно будет использовано.</p>
<ul>
<li><code>type</code> для <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Complete_list_of_MIME_types">MIME-типа</a> формата</li>
<li><code>srcset</code> для пути к файлу изображения. Несколько файлов могут быть использованы для изображений разных размеров и плотности пикселей.</li>
<li><code>sizes</code> для перечня размеров каждого файла.</li>
<li><code>media</code> для медиавыражения, которое определяет, какое изображение будет использовано.</li>
</ul>
<p><em>Подробнее об этих атрибутах и адаптивных картинках читайте в статье Ире «<a href="https://bitsofco.de/the-srcset-and-sizes-attributes/">Responsive Images — The srcset and sizes Attributes</a>» — прим. редактора.</em></p>
<p>В дополнение к различным <code>&lt;source&gt;</code>, также нужно добавить обычный элемент <code>&lt;img&gt;</code>, как фолбэк для браузеров, которые не поддерживают множественные форматы и <code>&lt;picture&gt;</code>.</p>

                    ]]></description><pubDate>Tue, 25 Dec 2018 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/webp-today/</guid></item><item><title>Доступные SVG-иконки с инлайновыми спрайтами</title><link>https://web-standards.ru/articles/accessible-svg-icons/</link><description><![CDATA[
                        <p>Если вы ищете повод использовать иконки в проекте, то далеко ходить не надо. Как хорошо заметил <a href="https://ia.net">Оливер Рейхенштейн</a> в своём докладе <a href="https://vimeo.com/184776819">«Об иконках»</a> на Smashing Conf во Фрайбурге: <em>«На определённом этапе проекта кто-нибудь обязательно приходит и говорит: нам нужны иконки!».</em> Так зачем они нужны людям?</p>
<ul>
<li>Иконки помогают заполнять небольшие пространства, где сложно использовать текст.</li>
<li>У них может быть общепринятое значение, которое не зависит от языка.</li>
<li>Они — средство индивидуализации.</li>
<li>Они могут привлечь внимание к важным элементам пользовательского интерфейса.</li>
</ul>
<p>Вне зависимости от аргументации, нам нужно быть уверенными в том, что люди, которые не могут видеть или распознать иконки, понимают их назначение.</p>
<figure>
    <img src="https://web-standards.ru/articles/accessible-svg-icons/images/1.png" alt="Иконка с рукой, которая нажимает на кнопку. Рядом с ней иконка с руками, держащими три красных волнистых линии, которые напоминают бекон.">
    <figcaption>Нажать кнопку, получить бекон — это же очевидно.</figcaption>
</figure>
<p>Сами по себе иконки могут неправильно интерпретироваться, что продемонстрировала Мэлори ван Ахтерберг во время своего доклада на Fronteers в 2017. Их самая главная проблема — отсутствие текста. <a href="https://www.w3.org/TR/WCAG20/#guidelines">Текст — самый доступный вид информации в вебе</a>. Скринридеры понимают его лучше всего. Это относится к большинству вспомогательных технологий, например, к приложениям для переводов и брайлевским дисплеям. Поэтому, если на нашей странице есть нетекстовая информация, например, иконки, мы должны добавить текст, который дублирует для пользователей её значение. Иначе мы можем сделать наши интерфейсы сложными для понимания.</p>
<figure>
    <img src="https://web-standards.ru/articles/accessible-svg-icons/images/2.jpg" alt="Что вы сделаете в первую очередь, если увидите, что кто-то тонет? Вызывайте 911.">
    <figcaption>Что вы сделаете в первую очередь, если увидите, что кто-то тонет? LOL.</figcaption>
</figure>
<h2>Как использовать SVG-графику для иконок</h2>
<p>SVG или масштабируемая векторная графика (Scalable Vector Graphic) — это часть спецификации расширяемого языка разметки (Extensible Markup Languag, XML), которая может быть встроена в HTML-документ при помощи элемента <code>&lt;svg&gt;</code>, как в примере кода ниже (в нём SVG — стрелка в браузере, указывающая вниз):</p>
<pre><code tabindex="0" class="language-html">&lt;body&gt;
    &lt;svg viewBox=&quot;0 0 10 10&quot;&gt;
        &lt;path d=&quot;m5 9-3-4h2v-4h2v4h2z&quot;/&gt;
    &lt;/svg&gt;
&lt;/body&gt;
</code></pre>
<p>В этом простом примере есть всё, что нужно для отображения SVG в <a href="https://caniuse.com/#feat=svg">браузерах, которые её поддерживают</a>. Однако, без указания высоты и ширины, SVG будет отображаться настолько большой, насколько это позволит доступная область просмотра. В примере ниже показано как это исправить:</p>
<pre><code tabindex="0" class="language-html">&lt;body&gt;
    &lt;svg width=&quot;10&quot; height=&quot;10&quot;
            viewBox=&quot;0 0 10 10&quot;&gt;
        &lt;path d=&quot;m5 9-3-4h2v-4h2v4h2z&quot;/&gt;
    &lt;/svg&gt;
&lt;/body&gt;
</code></pre>
<p>Одиночный вспомогательный CSS-класс, присвоенный нашим SVG-иконкам, задаёт им нужное поведение. Затем он переопределяет эти значения и отлично масштабирует иконки в соответствии с размером текста.</p>
<pre><code tabindex="0" class="language-css">.svg-icon {
    /* Разместить иконку выше базового текста. */
    position: relative;
    top: 0.125em;

    /* Не дать иконке сжаться внутри flex-контейнера. */
    flex-shrink: 0;

    /*
        * Масштабировать иконку до размера шрифта родительского
        * элемента.
    */
    height: 1em;
    width: 1em;

    /* Установить для иконки любой цвет, который задан родителю. */
    fill: currentColor;

    /*
        * Если иконка используется в ссылке, для которой задан цветовой
        * переход, мы можем также задать для fill нужное значение
        * transition.
    */
    transition: fill 0.3s;
}
</code></pre>
<p>Ниже пример HTML-кода:</p>
<pre><code tabindex="0" class="language-html">&lt;body&gt;
    &lt;svg class=&quot;svg-icon&quot;
            width=&quot;10&quot; height=&quot;10&quot;
            viewBox=&quot;0 0 10 10&quot;&gt;
        &lt;path d=&quot;m5 9-3-4h2v-4h2v4h2z&quot;/&gt;
    &lt;/svg&gt;
&lt;/body&gt;
</code></pre>
<p><a href="https://codepen.io/Nice2MeatU/pen/YNEXMj">Я сделал демо на CodePen</a>, чтобы показать, на что способен этот одиночный CSS-селектор. Мы можем увеличить или уменьшить значение <code>font-size</code> для параграфа в инспекторе CSS и увидеть, как иконка увеличивается вместе с текстом. Мы также можем изменить значение <code>color</code> и посмотреть, как это повлияет на неё. При наведении курсора на ссылку или при фокусе на ней одновременно изменяется как цвет ссылки, так и цвет заливки иконки.</p>
<p>Решая эту проблему, мы сталкиваемся с другой: нам не хватает текстового эквивалента для графики, которую мы добавили на нашу страницу.</p>
<h2>Делаем SVG-иконку доступной</h2>
<p>Давайте предположим, что нам нужно создать кнопку, которая переключает видимость навигации по сайту. При первом нажатии на неё навигация будет показана, а при повторном снова скрыта. Надеюсь, что все согласны, что кнопка — это кнопка, и <a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_button_role">поэтому она должна быть</a> <code>&lt;button&gt;</code>. В нашем случае дизайн-макета недостаточно. Всё, что дал нам дизайнер — это иконка гамбургера, без текста. Переключаемся на редактор: добавляем в кнопку SVG.</p>
<pre><code tabindex="0" class="language-html">&lt;button type=&quot;button&quot;&gt;
    &lt;svg class=&quot;svg-icon&quot;
            width=&quot;10&quot; height=&quot;10&quot;
            viewBox=&quot;0 0 10 10&quot;
            role=&quot;img&quot;&gt;
        &lt;path d=&quot;m1 7h8v2h-8zm0-3h8v2h-8zm0-3h8v2h-8z&quot;/&gt;
    &lt;/svg&gt;
&lt;/button&gt;
</code></pre>
<p>Теперь нужно добавить немного текста, с которым могли бы взаимодействовать вспомогательные технологии. Используя инлайновый SVG, мы можем добавить элемент <code>&lt;title&gt;</code> для первого дочернего элемента <code>&lt;svg&gt;</code> и присвоить ему ID. Затем нам нужно сослаться на нужное значение ID с помощью атрибута <code>aria-labelledby</code>, который задан открывающему тегу <code>&lt;svg&gt;</code>:</p>
<pre><code tabindex="0" class="language-html">&lt;button type=&quot;button&quot;&gt;
    &lt;svg class=&quot;svg-icon&quot;
            width=&quot;10&quot; height=&quot;10&quot;
            viewBox=&quot;0 0 10 10&quot;
            role=&quot;img&quot;
            focusable=&quot;false&quot;
            aria-labelledby=&quot;menu-icon-title&quot;&gt;
        &lt;title id=&quot;menu-icon-title&quot;&gt;Меню&lt;/title&gt;
        &lt;path d=&quot;m1 7h8v2h-8zm0-3h8v2h-8zm0-3h8v2h-8z&quot;/&gt;
    &lt;/svg&gt;
&lt;/button&gt;
</code></pre>
<p>Теперь, когда кто-то установит фокус на кнопке, будет объявлен текст заголовка (некоторые браузеры даже показывают текст внутри <code>&lt;title&gt;</code> в виде всплывающей подсказки). Обратите внимание на сочетание атрибута и значения <code>focusable=&quot;false&quot;</code> в SVG! Это помешает установить фокус на SVG в Internet Explorer и Microsoft Edge. <a href="https://github.com/PolymerElements/iron-icon/issues/71">Это баг</a>, который не исправлен для 10 и 11 версий Internet Explorer.</p>
<h3>У нас есть варианты</h3>
<p>Есть другой способ добавить скрытый текст рядом с иконкой. Для него HTML и вспомогательный CSS-класс нужны чуть меньше, и он работает так же как в примере выше.</p>
<p>Вы можете использовать элемент <code>&lt;span&gt;</code>, чтобы обернуть текст и визуально скрыть от пользователя. С помощью этого мы предоставляем вспомогательным технологиям описание иконки в текстовом виде, не изменяя дизайна кнопки.</p>
<pre><code tabindex="0" class="language-html">&lt;button type=&quot;button&quot;&gt;
    &lt;svg class=&quot;svg-icon&quot;
            width=&quot;10&quot; height=&quot;10&quot;
            viewBox=&quot;0 0 10 10&quot;
            role=&quot;img&quot;
            focusable=&quot;false&quot;
            aria-hidden=&quot;true&quot;&gt;
        &lt;path d=&quot;m1 7h8v2h-8zm0-3h8v2h-8zm0-3h8v2h-8z&quot;/&gt;
    &lt;/svg&gt;
    &lt;span class=&quot;visually-hidden&quot;&gt;
        Меню
    &lt;/span&gt;
&lt;/button&gt;
</code></pre>
<p>Вспомогательный CSS-класс выглядит так:</p>
<pre><code tabindex="0" class="language-css">.visually-hidden {
    /* Убрать текст из потока */
    position: absolute;

    /* Уменьшить его высоту и ширину до одного пикселя */
    height: 1px;
    width: 1px;

    /* Скрыть выходящие за пределы контейнера элементы или текст */
    overflow: hidden;

    /* Установить для clip нулевое значение */
    clip: rect(0, 0, 0, 0);

    /* Текст не будет перенесён на вторую строку */
    white-space: nowrap;
}
</code></pre>
<h3>Не сильно отличающийся пример</h3>
<p>И вот наши дизайнеры решили добавить другую кнопку с <em>текстом</em> и <em>иконкой</em> рядом с ним. Если у вас есть текст рядом с иконкой, то не добавляйте заголовок и описание, а замените <code>role=&quot;img&quot;</code> у SVG на <code>aria-hidden=&quot;true&quot;</code>. Добавление к элементу атрибута <code>aria-hidden=&quot;true&quot;</code> полностью удаляет его и его дочерние элементы из дерева доступности. Такой элемент не будет доступен для Accessibility API:</p>
<pre><code tabindex="0" class="language-html">&lt;button type=&quot;button&quot;&gt;
    Меню
    &lt;svg class=&quot;svg-icon&quot;
            viewBox=&quot;0 0 10 10&quot;
            focusable=&quot;false&quot;
            aria-hidden=&quot;true&quot;&gt;
        &lt;path d=&quot;m1 7h8v2h-8zm0-3h8v2h-8zm0-3h8v2h-8z&quot;/&gt;
    &lt;/svg&gt;
&lt;/button&gt;
</code></pre>
<p>Поскольку у нас есть текст, который объясняет назначение кнопки, иконка стала просто декоративным элементом. Когда мы имеем дело с декоративными изображениями или графикой, нам нужно скрыть их из дерева доступности.</p>
<p>Вот демо, которое демонстрирует все четыре варианта кнопки: <a href="https://codepen.io/Nice2MeatU/pen/dqmypX">создание кнопки с доступной SVG-иконкой</a>.</p>
<p>Установив фокус на кнопке, скринридер объявит: <em>«Меню, кнопка».</em> Прекрасно. Однако теперь, визуально, мы получили тот же SVG, но с другим кодом. Представьте, что вам нужно добавить иконки в другом контексте, в котором подписи из первого варианта не используются. А потом ещё в одном. И ещё. Этот вариант не идеален.</p>
<p>Нам нужно быть осторожными, и не задать <code>aria-hidden=&quot;true&quot;</code> для элемента с <code>role=&quot;none&quot;</code>! У них разные сценарии использования и конечные результаты. <a href="https://www.scottohara.me">Скотт Охара</a> написал <a href="https://medium.com/p/ab1241ea9166">замечательную статью</a> о том, когда и какой из атрибутов использовать.</p>
<h2>Пишите меньше кода — используйте спрайт</h2>
<p>Процитирую слова <a href="https://www.heydonworks.com">Хейдона Пикеринга</a> из его доклада <a href="https://vimeo.com/190834530">«Пишите меньше чёртового кода»</a> с <a href="https://beyondtellerrand.com/events/berlin-2016/speakers">Beyond Tellerrand Berlin 2016</a>: <em>«Чем меньше кода, тем код лучше! Меньше кода, над которым нужно думать. Меньше кода поддерживать. Меньше данных передавать по сети».</em></p>
<p>Представьте следующую ситуацию: спустя несколько недель наш дизайнер добавил все необходимые иконки в дизайн-макет. У нас есть много разных иконок, с которыми нужно что-то делать, и разные варианты использования: некоторым нужен заголовок и описание, а другие должны быть скрыты. Понадобится много времени, чтобы создать иконки с разными атрибутами и текстовыми описаниями. И поэтому появился SVG-спрайт и сэкономил нам кучу времени!</p>
<p>Разместив пустой <code>&lt;svg&gt;&lt;/svg&gt;</code> в качестве первого потомка открывающего тега <code>&lt;body&gt;</code> и скрыв его от всех (зрячих или нет) при помощи инлайнового <code>display: none</code>, мы создали пространство для вставки наших иконок. Важно именно инлайново задать <code>display: none</code>, потому что мы не хотим, чтобы спрайт был видим при загрузке наших таблиц стилей браузером! Если у вас есть критические стили для сайта, вы можете добавить свойство туда, и не трогать спрайт.</p>
<pre><code tabindex="0" class="language-html">    &lt;body&gt;
        &lt;svg style=&quot;display: none&quot;&gt;&lt;/svg&gt;
    &lt;/body&gt;
</code></pre>
<p><strong>Примечание</strong>: это только <em>один</em> из множества вариантов создания SVG-спрайта! Есть несколько способов сделать это, но в этой статье невозможно объяснить их все. <a href="https://www.sarasoueidan.com">Сара Суайдан</a> очень подробно рассказывает о различных <a href="https://www.sarasoueidan.com/blog/overview-of-svg-sprite-creation-techniques/">вариантах создания SVG-спрайта</a> со всеми преимуществами и проблемами, которые имеются в каждом из подходов.</p>
<p>При использовании SVG-спрайта для системы иконок можете не трогать SVG. Поскольку вы собираетесь использовать одну иконку в элементе <code>&lt;svg&gt;</code>, вам понадобится подписать её или вообще скрыть, если она декоративная.</p>
<p>В общем, всегда следует оптимизировать SVG-код независимо от сценария использования. К счастью для нас, поскольку это интернет и в нём есть миллион инструментов, созданных миллионами людей, нам не нужно самим выполнять эту тяжёлую работу.</p>
<h2>SVGO(MG)</h2>
<p>Есть много способов оптимизировать и упростить SVG-код перед тем, как добавить его в HTML-документ. Первый и наиболее результативный — открыть SVG в редакторе и удалить всё ненужное вручную. Это также и самый трудоёмкий вариант, и высока вероятность удалить то, что нам могло бы пригодиться потом. Я советую сделать это один или два раза, чтобы лучше понять структуру SVG-файла, но не рекомендую постоянно так делать.</p>
<p>Для тех, кто предпочитает командную строку графическому интерфейсу, есть <a href="https://github.com/svg/svgo">svgo</a>. Мы можем добавить массу параметров для оптимизации SVG: что удалить, что оставить и даже что оставить принудительно. Для тех, кто предпочитает таск-менеджеры, есть <a href="https://github.com/sindresorhus/grunt-svgmin">grunt-svgmin</a> и точно такой же <a href="https://github.com/ben-eb/gulp-svgmin">gulp-svgmin</a>, в которых используется SVGO. Сара Суайдан сделала <a href="https://www.sarasoueidan.com/blog/svgo-tools/">отличный обзор существующих инструментов</a> с их преимуществами и недостатками.</p>
<p>Если вам больше нравятся инструменты с графическим интерфейсом, есть <a href="https://jakearchibald.github.io/svgomg/">SVGOMG,</a> который сделал <a href="https://jakearchibald.com">Джейк Арчибальд</a>. Он такой же мощный как вариант с командной строкой и сразу показывает результат (код и изображение). Вместо того, чтобы писать параметры в нашем терминале, мы можем на правой панели выбрать нужную опцию или убрать её. Единственная оговорка: может быть довольно утомительно загружать и оптимизировать каждую иконку по отдельности.</p>
<h2>Создание спрайта</h2>
<p>До использования SVGOMG наши иконки могут выглядеть как в примере ниже. Это зависит от того, какими программами для создания графики вы пользуетесь (в примере показан SVG-код, который был создан в Adobe Illustrator при выборе параметра «Сохранить как»):</p>
<pre><code tabindex="0" class="language-xml">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In.
SVG Version: 6.00 Build 0) --&gt;
&lt;svg version=&quot;1.1&quot; id=&quot;Layer_1&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;
xmlns:xlink=&quot;http://www.w3.org/1999/xlink&quot; x=&quot;0px&quot; y=&quot;0px&quot;
width=&quot;24px&quot; height=&quot;24px&quot; viewBox=&quot;0 0 24 24&quot;
enable-background=&quot;new 0 0 24 24&quot; xml:space=&quot;preserve&quot;&gt;
    &lt;path d=&quot;M7.41,8.59L12,13.17l4.59-4.58L18,10l-6,6l-6-6L7.41,8.59z&quot;/&gt;
    &lt;path fill=&quot;none&quot; d=&quot;M0,0h24v24H0V0z&quot;/&gt;
&lt;/svg&gt;
</code></pre>
<p><strong>Примечание:</strong> если выбрать в Adobe Illustrator параметр «Экспорт», то получится более чистый SVG-код!</p>
<p>Открывая SVG в браузере, мы видим угол 90 градусов, направленный вниз. Пропустив этот код через SVGOMG со всеми выбранными чекбоксами, мы получим хороший минифицированный SVG-файл.</p>
<p><strong>Примечание</strong>: единственная опция, которую не надо выбирать — это «Remove ViewBox»! Нам часто требуется сохранить для иконок <a href="https://www.sarasoueidan.com/blog/svg-coordinate-systems/#svg-viewbox">атрибут viewBox</a>, чтобы они правильно отображались!</p>
<p>Посмотрим, что осталось после оптимизации:</p>
<pre><code tabindex="0" class="language-xml">&lt;svg viewBox=&quot;0 0 24 24&quot;&gt;
    &lt;path d=&quot;M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z&quot;/&gt;
    &lt;path fill=&quot;none&quot; d=&quot;M0 0h24v24H0V0z&quot;/&gt;
&lt;/svg&gt;
</code></pre>
<p>После добавления SVG-кода в спрайт, нам только осталось слегка его изменить (я добавил переносы строк для того, чтобы код стал более читаемым):</p>
<pre><code tabindex="0" class="language-xml">&lt;svg style=&quot;display: none&quot;&gt;
    &lt;symbol id=&quot;icon-angle-down&quot;
            viewBox=&quot;0 0 24 24&quot;&gt;
        &lt;path d=&quot;M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z&quot;/&gt;
        &lt;path fill=&quot;none&quot; d=&quot;M0 0h24v24H0V0z&quot;/&gt;
    &lt;/symbol&gt;
&lt;/svg&gt;
</code></pre>
<p>Мы заменили <code>&lt;svg&gt;</code> на <code>&lt;symbol&gt;</code> и задали иконке уникальный ID, поэтому можем использовать её в другой части нашей страницы. Самым большим преимуществом этого подхода является возможность бесконечно использовать одну иконку и задавать ей подпись для каждого случая использования. Нет необходимости создавать иконку больше одного раза:</p>
<pre><code tabindex="0" class="language-html">&lt;button type=&quot;button&quot;&gt;
    &lt;svg class=&quot;svg-icon&quot;
            role=&quot;img&quot;
            focusable=&quot;false&quot;
            aria-labelledby=&quot;toggle-nav-icon-title&quot;&gt;
        &lt;title id=&quot;toggle-nav-icon-title&quot;&gt;Меню&lt;/title&gt;
        &lt;use xlink:href=&quot;#icon-angle-down&quot;/&gt;
    &lt;/svg&gt;
&lt;/button&gt;

&lt;a href=&quot;#next-section&quot;&gt;
    &lt;svg class=&quot;svg-icon&quot;
            role=&quot;img&quot;
            focusable=&quot;false&quot;
            aria-labelledby=&quot;next-section-icon&quot;&gt;
        &lt;title id=&quot;next-section-icon&quot;&gt;Перейти к следующему разделу.&lt;/title&gt;
        &lt;use xlink:href=&quot;#icon-angle-down&quot;/&gt;
    &lt;/svg&gt;
&lt;/a&gt;

&lt;button type=&quot;button&quot;&gt;
    &lt;svg class=&quot;svg-icon&quot;
            aria-hidden=&quot;true&quot;
            focusable=&quot;false&quot;&gt;
        &lt;use xlink:href=&quot;#icon-angle-down&quot;/&gt;
    &lt;/svg&gt;
    Нажмите на кнопку вниз!
&lt;/button&gt;

&lt;p&gt;
    Прокрутите вниз для получения дополнительной информации!
    &lt;svg class=&quot;svg-icon&quot;
            aria-hidden=&quot;true&quot;
            focusable=&quot;false&quot;&gt;
        &lt;use xlink:href=&quot;#icon-angle-down&quot;/&gt;
    &lt;/svg&gt;
&lt;/p&gt;
</code></pre>
<p>С помощью одиночного тега <code>&lt;use&gt;</code> мы можем ссылаться на ID этого SVG в спрайте. В примере выше у нас есть четыре разных сценария использования, все с одной и той же иконкой. Мы можем скрыть их, когда они используются как декоративные элементы, или подписать, когда это нужно. Круто!</p>
<p><a href="https://codepen.io/Nice2MeatU/pen/dqmyaR">Взгляните на демо</a>. Нет никакой разницы между тем, как выглядят иконки, но разница в количестве кода, используемого для их отображения, огромна.</p>
<h2>Заключение</h2>
<p>Надеюсь, что этот материал был вам полезен, и вы прямо сейчас готовы использовать SVG-иконки в своих проектах. В этой маленькой статье невозможно объяснить все основные идеи. Чтобы помочь вам глубже погрузиться в океан SVG, я собрал здесь несколько ссылок на другие статьи и книги по этой теме.</p>
<h2>Дополнительные источники</h2>
<ul>
<li><a href="https://www.sarasoueidan.com">Сара Суайдан</a> написала <a href="https://www.sarasoueidan.com/blog/overview-of-svg-sprite-creation-techniques/">«An overview of SVG Sprite Creation Techniques»</a>.</li>
<li>Она также объясняет, как перейти от иконочного шрифта к SVG-спрайтам в <a href="https://www.sarasoueidan.com/blog/icon-fonts-to-svg/">«Making the Switch Away from Icon Fonts to SVG»</a>.</li>
<li><a href="https://www.hmig.me">Хезер Мильориси</a> рассказывает обо всех упущенных здесь подробностях, как сделать SVG доступным для всех в <a href="https://css-tricks.com/accessible-svgs/">«Accessible SVG»</a>.</li>
<li><a href="https://sarahdrasnerdesign.com">Сара Дрэснер</a> написала замечательную книгу об <a href="http://shop.oreilly.com/product/0636920045335.do">SVG-анимации</a>. В ней есть все советы, которые могут понадобиться для анимации SVG-графики.</li>
<li><a href="https://www.scottohara.me">Скотт Охара</a> написал <a href="https://medium.com/p/ab1241ea9166">подробную статью</a> о том, когда использовать <code>role=&quot;none&quot;</code> или <code>role=&quot;presentation&quot;</code>, а когда <code>aria-hidden=&quot;true&quot;</code>.</li>
<li><a href="https://chriscoyier.net">Крис Койер</a> собрал вместе довольно много полезной информации и статей на эту тему в <a href="https://css-tricks.com/mega-list-svg-information/">«SVG Compendium»</a>.</li>
<li>Он также написал книгу <a href="https://abookapart.com/products/practical-svg">«Practical SVG»</a> для <a href="https://abookapart.com/">A Book Apart</a>.</li>
</ul>
<h2>Благодарность</h2>
<p>Знания, накопленные мной за то время, которое мы говорим об SVG, а также о доступности, получены из книг, постов в блогах и учебных пособий. Также я слежу за профилями людей в социальных сетях, которые работают в этой области и написали эти самые книги, посты и пособия. Конечно же помогли в этом и бесконечные ковыряния в коде, а также написание разных вещей и их ломание.</p>
<p>Итак, без лишних слов, спасибо Леони Уотсон, Лоре Калбаг, Лии Веру, Саре Суайдан, Саре Дрэснер, Вал Хед, Серене Дэвис, Марси Саттон, Кэри Фишер, Зоуи Гилленуотер, Хезер Мильориси, Мэлори ван Ахтерберг, Стиву Фолкнеру, Патрику Лауке, Хейдону Пикерингу, Нилу Милликену, Николасу Стинхауту, Марко Цехе, Хьюго Жироделю, Гуннару Биттерсману, Эрику Бэйли, Алану Далтону, Крису Койеру, Брюсу Лоусону, Аарону Густавсону, Скотту Охаре, всей Paciello Group, A List Apart и A Book Apart, Khan Academy, командам Pa11y и tenon.io, а также тем, кого я забыл здесь упомянуть.</p>
<p>Спасибо всем тем, кто (надеюсь) никогда не сдаётся, и объясняет другим (и мне), почему доступность важна, и как сделать интернет более инклюзивным местом.</p>

                    ]]></description><pubDate>Mon, 17 Dec 2018 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/accessible-svg-icons/</guid></item><item><title>Просто используйте стили для :focus, чёрт возьми</title><link>https://web-standards.ru/articles/just-use-focus/</link><description><![CDATA[
                        <p>За последнее время появилось несколько статей и рекомендаций от разных людей о том, как использовать псевдокласс <code>:focus-visible</code>, чтобы показать стили для <code>:focus</code>, когда кто-то пользуется клавиатурой, и скрывать их, если пользователь использует мышь.</p>
<p>Лучшее из того, что я видел, <a href="https://twitter.com/LeaVerou/status/1045768279753666562">предложила Лия Веру</a>:</p>
<blockquote>
<p>Я собираюсь начать добавлять следующее правило во все мои таблицы стилей:
<code>:focus:not(:focus-visible) { outline: none }</code>
Избавляет пользователей мыши от раздражающего <code>outline</code>, но сохраняет его для пользователей клавиатуры и не учитывается браузерами, которые не поддерживают <code>:focus-visible</code>.</p>
</blockquote>
<p>Аргументация всегда одинаковая: обводка фокуса (эти рамки вокруг ссылок, кнопок и других элементов, на которых можно сделать фокус) — отстой. Нам нужно добавлять их только потому, что они нужны пользователям клавиатуры.</p>
<p>Чушь собачья.</p>
<p>Стили фокуса действительно потрясающие. У моего друга Эрика Бэйли есть отличная презентация об этом:</p>
<p><a href="https://noti.st/ericwbailey/TcMJFP/embed">If it’s interactive, it needs a focus style. October 15, 2018. #a11yTOConf</a></p>
<p>Лично мне они нравятся по той же причине, по которой мне нравятся стили для :visited на перегруженных ссылками страницах.</p>
<p>Даже если ты пользуешься мышью, полезно знать, на чём сейчас сделан фокус, потому что я <em>иногда ещё использую и клавиатуру.</em> Часто они взаимозаменяемы. Может быть мой пример притянут, но знаете ли вы, как каждый из пользователей пользуется вашими сайтами и приложениями?</p>
<p>Мы продолжаем совершать одинаковую ошибку снова и снова, предполагая, что знаем, как наши пользователи хотят использовать то, что мы создаём, или что они такие же, как мы.</p>
<p>К тому же стили фокуса не обязательно должны быть уродливыми. Я думаю, что стили по умолчанию замечательные, но вы можете принимать какие угодно творческие решения, чтобы они выглядели лучше.</p>
<p>Моя позиция: просто используйте стили фокуса на своём сайте. Для всего, на чём можно сделать фокус. Для каждого пользователя.</p>

                    ]]></description><pubDate>Mon, 10 Dec 2018 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/just-use-focus/</guid></item><item><title>Полезные правила доступности, которые останутся в памяти</title><link>https://web-standards.ru/articles/pragmatic-a11y-rules/</link><description><![CDATA[
                        <figure>
    <img src="https://web-standards.ru/articles/pragmatic-a11y-rules/images/1.jpg" alt="">
    <figcaption>
        «Параолимпийские игры изменяют наше восприятие мира», Стивен Хокинг. То же можно сказать о доступности.
    </figcaption>
</figure>
<p>Впервые я столкнулся с доступностью ещё в 2015, когда работал на американского розничного гиганта. Тогда компания получила огромный иск из-за сайта, который нарушал Закон об американцах-инвалидах (Americans with Disabilities Act, ADA). После этого мы с командой много работали над приведением сайта в соответствие с нормами ADA. Тогда я и познакомился со многими принципами доступности.</p>
<p>Однако в последующие годы, я постоянно нарушал эти принципы, хотя регулярно с ними сталкивался. Почему-то я не помнил о них во время кодинга. Не хотелось признавать, но я определённо не полностью их усвоил.</p>
<p>В конце концов я решил, что пора найти время и свести все эти принципы к простым и полезным правилам, которые просто держать в уме. Наконец, я сделал это, и с тех пор они неплохо для меня работают.</p>
<p>Эта статья состоит из двух частей: что такое доступность и три полезных правила доступности. В первой части я напомню, что такое доступность, и поделюсь своим опытом. Если хотите перейти к главному, то можете сразу начать со второй части.</p>
<h2>Что такое доступность?</h2>
<p>Как я уже упоминал, в 2015 году моя компания получила иск за несоблюдение ADA.</p>
<p><a href="https://www.ada.gov">ADA</a> — закон о гражданских правах, который:</p>
<blockquote>
<p>Запрещает дискриминацию людей с инвалидностью во всех сферах жизни общества, включая работу, школы, транспорт и все публичные и частные места, которые доступны для широкого круга граждан.</p>
</blockquote>
<p>Таким образом, ADA требует, чтобы фирмы, государственные и местные органы власти, а также некоммерческие организации предоставляли инвалидам доступ к сервисам наравне с трудоспособными гражданами. Аналогичным образом федеральные органы власти должны соблюдать федеральный закон, который называется <a href="https://www.section508.gov">Section 508</a>.</p>
<p>В контексте интернета все публичные сайты США, которые не соблюдают ADA или Section 508, исключают из числа своих посетителей большие группы пользователей с разной степенью нарушений.</p>
<p>С другой стороны, инклюзивный подход к созданию контента сайта, доступного для всех вне зависимости от их функциональных возможностей, называется <a href="https://en.wikipedia.org/wiki/Web_accessibility">доступностью</a> или просто <a href="https://a11yproject.com/posts/a11y-and-other-numeronyms/">a11y</a>.</p>
<h3>От переводчика</h3>
<p>В России доступность регулируется тремя документами:</p>
<ol>
<li>Государственный стандарт <a href="http://docs.cntd.ru/document/1200103663">«ГОСТ Р 52872–2012 Интернет-ресурсы. Требования доступности для инвалидов по зрению»</a>. Был введён в 2014. Содержит рекомендации, а не жёсткие требования, как и любой другой ГОСТ.</li>
<li><a href="https://minsvyaz.ru/ru/documents/4985/">Приказ министерства связи и массовых коммуникаций России № 483</a> «Об установлении порядка обеспечения условий доступности для инвалидов по зрению официальных сайтов федеральных органов государственной власти, органов государственной власти субъектов Российской Федерации и органов местного самоуправления в сети интернет». Принят в ноябре 2015.</li>
<li><a href="http://www.consultant.ru/document/cons_doc_LAW_171577/">Федеральный закон № 419-ФЗ</a> «О внесении изменений в отдельные законодательные акты Российской Федерации по вопросам социальной защиты инвалидов в связи с ратификацией конвенции о правах инвалидов». Вступил в силу в январе 2016.</li>
</ol>
<h3>Кому нужна доступность?</h3>
<p>Согласно <a href="http://www.who.int/disabilities/world_report/2011/report/en/">Всемирному докладу об инвалидности</a> <em>(есть версия на русском — прим. редактора),</em> опубликованному в 2011 году <a href="http://www.who.int">Всемирной организацией здравоохранения (ВОЗ)</a>, примерно 15% мирового населения живёт с той или иной формой инвалидности. Из них примерно 2–4% испытывают серьёзные трудности с нормальным функционированием.</p>
<p>В превосходной статье замечательного Эдди Османи <a href="https://medium.com/@addyosmani/accessible-ui-components-for-the-web-39e727101a67">«Доступные UI-компоненты для веба»</a>, речь идёт о четырёх основных видах нарушений, которые рассматриваются в контексте доступности:</p>
<ol>
<li><strong>Проблемы со зрением</strong>: могут варьироваться от неспособности различать цвета до полного отсутствия зрения.</li>
<li><strong>Проблемы со слухом</strong>: означают, что у пользователя проблемы с распознаванием звуков, которые звучат со страницы.</li>
<li><strong>Проблемы с мобильностью</strong>: могут включать невозможность управлять мышью, клавиатурой или сенсорным экраном.</li>
<li><strong>Когнитивные проблемы</strong>: означают, что пользователю могут потребоваться вспомогательные технологии для чтения текста, так что важно использовать альтернативы для текстовой информации.</li>
</ol>
<p>Имейте в виду, что диапазон нарушений обширен. Это означает, что совсем не обязательно иметь серьёзные нарушения для того, чтобы нужна была доступность.</p>
<p>Если хотите больше об этом узнать, то рекомендую пройти на Udacity бесплатный курс <a href="https://www.udacity.com/course/web-accessibility--ud891">Web Accessibility</a> от Google. Это видео из курса, которое рассказывает об этих видах нарушений:</p>
<iframe src="https://www.youtube.com/embed/LdVlbO7_hz8" allowfullscreen></iframe>
<h3>Хорошо, как обеспечить поддержку доступности?</h3>
<p>К тому моменту, как мы получили иск в 2015, уже был проведён аудит, который выявил множество проблем с доступностью. Наша команда прошла однодневный курс по доступности, на котором мы узнали о <a href="https://www.w3.org/TR/WCAG21/">Web Content Accessibility Guidelines</a> (сокращённо WCAG, сейчас в версии 2.1). Это руководство считается стандартом по доступности.</p>
<p>WCAG разработан группой <a href="https://www.w3.org/WAI/">Web Accessibility Initiative</a> (WAI), которая входит в <a href="https://www.w3.org">W3C</a>. Она же разработала <a href="https://www.w3.org/TR/wai-aria-1.1/">Accessible Rich Internet Applications</a> (WAI-ARIA или просто ARIA, актуальная версия 1.1). Это спецификация о том, как сделать страницы доступными путём добавления ролей и атрибутов ARIA в HTML-разметку.</p>
<p>Эти спецификации включают три уровня соответствия и приоритетов:</p>
<ul>
<li>A (можно поддерживать, низший).</li>
<li>AA (следует поддерживать, средний).</li>
<li>AAA (нужно поддерживать, наивысший).</li>
</ul>
<p>Многие законы о доступности во всём мире основаны на критериях выполнения WCAG. Например, в январе 2017 года Section 508 был принят в соответствии с критерием AA WCAG 2.0.</p>
<p><em>Прим. переводчика: Не исключение и «ГОСТ Р 52872–2012 Интернет-ресурсы. Требования доступности для инвалидов по зрению». Он разработан на основе WCAG.</em></p>
<p>Отлично обобщает все рекомендации <a href="https://webaim.org/standards/wcag/checklist">чек-лист WebAIM WCGA</a>. В нём для каждого критерия указан уровень соответствия.</p>
<h3>Насколько сложно выучить WCAG и WAI-ARIA?</h3>
<p>Я хотел бы поделиться своим опытом в изучении доступности.</p>
<p>Хотя наш курс был довольно комплексным и его проводили чрезвычайно компетентные люди, мы просто сидели на нём часами и слушали детальные обзоры WCAG, пункт за пунктом. Презентация была огромной, и мы быстро переходили от одного слайда к другому. Если честно, она была перегружена, ведь WCAG нельзя назвать маленьким документом.</p>
<p>Короче говоря, мы были готовы решить много задач, и сразу же начали работать над исправлениями. Однако скоро это стало чем-то повторяющимся, механическим, просто ответом на стимулы. Мы тонули в море доступности.</p>
<p>Каждый знал как хорошо мы стали разбираться в доступности, поэтому никто не критиковал нашу работу. История с доступностью подходила к концу, и у нас появились новые приоритеты. Ожидалось, что мы вынесем многое из проделанной работы. Какое-то время так и было.</p>
<p>Со временем кто-то ушёл из команды, присоединились новые люди, а ещё появилось новое руководство. Рынок быстро развивается. Мы изменили наши приоритеты и командный дух, не говоря уже о том, что были заняты новыми задачами, которые отодвинули доступность на второй план.</p>
<p>Всё было настолько плохо, что шесть месяцев спустя у нас был новый аудит, который показал, что мы всё ещё сидим на огромной куче проблем с доступностью! Скоро мы осознали, что, хотя старые ошибки исправлены, у большей части нового кода — проблемы. Кроме того, мы никогда не включали доступность в наш регламент разработки и новые члены команды не обучались этому.</p>
<p>Вывод: мы просто позволили этому случиться. Доступность проигнорировали и её ключевые идеи не укоренились в нас.</p>
<p>Другими словами, мы оказались изолированы в пузыре наших представлений. Такое случается, когда мы решаем проблемы, руководствуясь своими предубеждениями, как отмечено в методологии <a href="https://www.microsoft.com/design/inclusive/">Microsoft Inclusive Design</a>.</p>
<h3>Оказываясь в изоляции</h3>
<figure>
    <img src="https://web-standards.ru/articles/pragmatic-a11y-rules/images/2.jpg" alt="">
    <figcaption>
        «Изоляция никогда не даёт двигаться вперёд по общему пути к свободе и справедливости», Десмонд Туту.
    </figcaption>
</figure>
<p>Иногда вам нужно что-то испытать на собственном опыте, чтобы лучше это понять. Это и случилось со мной.</p>
<p>Я регулярно сдаю кровь на тромбоциты. У меня первая положительная группа крови, так что я могу помочь многим людям. Один раз в мою вену неправильно вставили катетер, и в этом месте на левой руке появился большой болезненный синяк.</p>
<p>Обычно сдача донорской крови занимает 10 минут, но забор тромбоцитов длится примерно 90. Так как моя рука была накрыта одеялами (потому что при заборе крови становится холодно), персоналу потребовалось около 20 минут, чтобы заметить, что моя вена повреждена.</p>
<p>После того, как это обнаружилось, сдачу тромбоцитов прервали. Мою левую руку раздуло, она стала очень чувствительной на несколько дней. Настолько, что я просто не мог заставить себя ей пользоваться.</p>
<p>После этого я пытался всё делать правой рукой. Неожиданно я заметил, что переключаться с клавиатуры на мышь неудобно, и мне было бы проще выполнять все задачи, используя что-то одного.</p>
<p>Вскоре я обнаружил, что использую только клавиатуру, и обратил внимание, как много сайтов просто не поддерживают её. Тогда до меня дошло: я столкнулся с изоляцией, хотя мои трудности были просто временными.</p>
<p>И тогда, именно в этот момент, я вспомнил как раньше работал над доступностью и не думал о поддержке клавиатуры. Блин!</p>
<h3>Уровни изоляции</h3>
<p>В соответствии с <a href="https://www.microsoft.com/design/inclusive/">Microsoft’s Inclusive 101 Toolkit</a> существует три уровня изоляции:</p>
<ol>
<li><strong>Постоянная:</strong> в ней оказываются люди с такими нарушениями как потеря конечностей, зрения, слуха или речи.</li>
<li><strong>Временная:</strong> переживают те, у кого временная травма или трудности, с которыми они сталкиваются непродолжительное время. Например, они посмотрели на яркий свет, носят гипс или заказывают обед в другой стране.</li>
<li><strong>Ситуативная:</strong> испытывают те, у кого резко изменились доступные возможности из-за специфичной обстановки, например, плохо слышно из-за шумной толпы, снижен обзор в машине или это молодые родители, у которых свободна только одна рука.</li>
</ol>
<p>Мои временные ограничения раскрыли мне глаза, ведь я никогда не сталкивался с такой проблемой во время работы.</p>
<p>Тем не менее, я находился в невероятно выгодном положении, ведь мои трудности длились всего пару дней, тогда как миллионы людей во всём мире изолированы в течение всей жизни.</p>
<h3>Пишем код во имя перемен</h3>
<p>Наконец, до меня дошло: реализация доступности вносит вклад в более инклюзивный мир! Вот несколько вещей, которые мы можем сделать как разработчики:</p>
<ul>
<li>Научиться писать код, поддерживающий доступность.</li>
<li>Сделать доступность частью регламента разработки своей команды (так же, как вы планируете работать над юнит-тестами или документацией).</li>
<li>Обсуждать доступность со своей командой, повышать осведомлённость её участников.</li>
<li>Оценивать, пишет ли команда код, отвечающий требованиям доступности, и фиксировать проблемы, которые можно решить.</li>
<li>Узнавать у бизнеса о требованиях, которые не отвечают принципам доступности или для которых нужны альтернативные решения.</li>
<li>Делиться своим опытом и показывать другим как внедрить доступность в свою практику. Поэтому я и пишу эту статью :)</li>
</ul>
<h2>Три полезных правила доступности</h2>
<p>Это моя попытка свести доступность к трём простым правилам, которые вы запомните. Из этих правил можно вынести основные идеи и найти рекомендации по внедрению доступности в ваш проект.</p>
<p><strong>Внимание!</strong> Эти правила не заменят необходимости изучения доступности. Они не исчерпывающие и станут только основой для того, что вы дальше будете изучать самостоятельно.</p>
<p>Повторюсь, чтобы научиться доступности, рекомендую ознакомиться на Udacity с бесплатным курсом <a href="https://www.udacity.com/course/web-accessibility--ud891">Web Accessibility</a> от Google.</p>
<p>Итак, три полезных правила доступности. Надеюсь, вы сможете взять их на вооружение и использовать каждый день в своей работе:</p>
<p><strong>1. Настаивайте на семантических HTML-элементах, или DIY</strong></p>
<figure>
    <img src="https://web-standards.ru/articles/pragmatic-a11y-rules/images/3.jpg" alt="">
    <figcaption>
        «Семантическай веб несложен по своей природе. Язык семантического веба, по своей сути, очень, очень простой. Речь идёт только о взаимосвязях между вещами». Тим Бернерс-Ли.
    </figcaption>
</figure>
<p>Для меня это золотое правило доступности, для этого не нужно прикладывать никаких усилий.</p>
<p><strong>Семантические элементы</strong> — те, которые передают определённое значение с помощью содержимого, которое они представляют. Например, <code>&lt;button&gt;</code>, <code>&lt;input&gt;</code>, <code>&lt;a&gt;</code>, <code>&lt;h1&gt;</code> и <code>&lt;p&gt;</code>. Они передают контекст агенту пользователя (браузеру, устройству или вспомогательной технологии вроде скринридера), поэтому он будет знать как взаимодействовать с такими элементами и чего от них ждать.</p>
<p>Они отличаются от семантически нейтральных элементов, таких как <code>&lt;div&gt;</code> и <code>&lt;span&gt;</code>, или от презентационных, вроде <code>&lt;center&gt;</code> и <code>&lt;big&gt;</code>, которые не дают агентам представления о контексте.</p>
<p>В большинстве случаев семантические теги уже доступны (и дружественны для SEO). Это означает, что они отвечают многим принципам доступности прямо из коробки, например:</p>
<ul>
<li>Обрабатывают события фокуса при нажатии на клавишу <kbd>Tab</kbd>.</li>
<li>Реагируют на события клавиатуры при нажатии на <kbd>Enter</kbd>, <kbd>Esc</kbd>, пробела, стрелок.</li>
<li>Содержат семантическую информацию (имя, роль, состояние и значение), поэтому их распознают вспомогательные технологии.</li>
<li>Их стили по умолчанию соответствуют минимальному контрасту.</li>
</ul>
<p>Когда вы не используете семантические элементы, вам нужно вручную задать ему всё это, чтобы сделать доступным.</p>
<p>Значит вам потребуется:</p>
<ul>
<li>Добавить <code>tabindex=&quot;0&quot;</code>, чтобы компонент стал частью естественного порядка табуляции, и использовать <code>focus()</code>, <code>display: none</code> или <code>aria-hidden</code> для того, чтобы избежать ловушек для фокуса. Про атрибут <code>tabindex</code> читайте в <a href="https://developers.google.com/web/fundamentals/accessibility/focus/using-tabindex">Using Tabindex</a>».</li>
<li>Следить за событиями клавиатуры. Проверьте требования для ваших компонентов в <a href="https://www.w3.org/TR/wai-aria-practices-1.1/#aria_ex">WAI-ARIA Design Patterns and Widgets</a>.</li>
<li>Использовать атрибут <code>role</code>, чтобы придать семантическое значение элементу и улучшить SEO-значимость. Узнайте обо всех возможных значениях <code>role</code> в <a href="https://www.w3.org/TR/wai-aria-1.1/#roles_categorization">WAI-ARIA Categorization of Roles</a>.</li>
<li>Использовать атрибуты ARIA для описания состояний и значений. Узнайте, как используются атрибуты ARIA в <a href="https://www.w3.org/TR/wai-aria-1.1/#role_definitions">WAI-ARIA Definition of Roles</a>.</li>
<li>Следить за контрастом и фокусом, особенно если используете <code>outline: 0</code> (что не рекомендуется).</li>
</ul>
<p>Продолжаем разговор о семантических элементах. Вот ещё несколько вещей, которые нужно иметь в виду:</p>
<ul>
<li>Используйте <a href="https://www.w3.org/TR/wai-aria-practices/examples/landmarks/HTML5.html">секционные элементы</a>, чтобы разбить страницу на отдельные части. В противном случае нужно использовать роли ориентиров.</li>
<li>Используйте <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Heading_Elements">заголовки</a>, чтобы организовать текстовое содержимое. Так вы опишите связь между секциями и их иерархию. На заметку: на каждой странице может быть только один заголовок первого уровня <code>&lt;h1&gt;</code>.</li>
<li>Используйте <code>&lt;label for=&quot;…&quot;&gt;</code> для элементов форм <code>&lt;input&gt;</code>, <code>&lt;select&gt;</code> и <code>&lt;textarea&gt;</code>.</li>
<li>Выбирайте правильные инструменты, например, если это ссылка, то используйте <code>&lt;a href=&quot;&quot;&gt;</code> и никогда <code>&lt;span onclick=&quot;…&quot;&gt;</code>. А если это кнопка, то <code>&lt;button&gt;</code>, а не <code>&lt;a href=&quot;#&quot; onclick=&quot;…&quot;&gt;</code>.</li>
</ul>
<p>Что ж, семантические элементы кажутся более удобными, не так ли?</p>
<p><strong>2. Обеспечьте альтернативу для картинок, цветов, звуков и движения</strong></p>
<figure>
    <img src="https://web-standards.ru/articles/pragmatic-a11y-rules/images/4.jpg" alt="">
    <figcaption>
        «Живопись — это просто другой способ ведения дневника», Пабло Пикассо.
    </figcaption>
</figure>
<p>Вспомогательные технологии лучше всего работают с текстом. Когда вы используете что-то ещё, всегда добавляйте для них текстовые эквиваленты, например:</p>
<ul>
<li>Для картинок предусмотрите альтернативный текст. Вы можете использовать <code>alt=&quot;description&quot; </code>для информативных изображений (которые несут смысл, как фотографии или иконки без текста) и <code>alt=&quot;&quot;</code> для декоративных изображений (которые не несут смысловой нагрузки, например иконки внутри кнопок или дополненные текстом). Это особенно важно для ссылок в виде картинок.</li>
<li>Продолжая про изображения: когда пользователи должны взаимодействовать с ними, добавьте звук или объясните, как обойтись без картинок. К примеру, можете взглянуть на <a href="https://www.google.com/recaptcha/intro/v3beta.html">Google reCaptcha</a>.</li>
<li>Добавьте дополнительный индикатор для цветов, когда они обозначают состояние элементов форм, определённый элемент в выпадающем списке или другие элементы, которые надо выделить. Это может быть информационный текст, иконка или даже подсказка.</li>
<li>Ещё про цвета. Проверьте контрастность текста и убедитесь, соответствует ли она стандарту. Например, уровень AA в WCAG требует минимум 4.5:1 для текста стандартного размера и 3:1 для большого.</li>
<li>Для звуковых дорожек и видео добавьте подписи или расшифруйте их. Используйте визуальную альтернативу для звуков интерфейса.</li>
<li>Если пользователь перемещается по странице, и мы ждём, что он совершит специфические жесты (например, движение мышью), сделайте их необязательными или предоставьте альтернативу таким взаимодействиям через клавиатуру.</li>
<li>Для автоматических изменений на странице избегайте вспышек, миганий, смещения контента и новых окон. Когда это неизбежно, добавьте элементы управления, с помощью которых можно настроить время, сделать паузу или скрыть этот контент. Также используйте значение <a href="https://www.w3.org/TR/wai-aria-1.1/#aria-live">aria-live</a>, чтобы скринридер мог уведомлять пользователя о том, что действие прервано.</li>
</ul>
<p><strong>3) Привыкните использовать инструменты для проверки доступности в повседневной работе</strong></p>
<figure>
    <img src="https://web-standards.ru/articles/pragmatic-a11y-rules/images/5.jpg" alt="">
    <figcaption>
        «Мы формируем наши инструменты, а затем наши инструменты формируют нас», Маршалл Маклюэн.
    </figcaption>
</figure>
<p>Это, пожалуй, самое полезное правило, которое поможет понять, что вы упустили что-то в двух предыдущих.</p>
<p>Я перечислю здесь несколько инструментов для проверки доступности. Попробуйте их использовать, запустите на своём сайте, посмотрите, что вы можете узнать благодаря им и попробуйте остановиться на каком-то из них. В основном встречается три типа инструментов, которые я рекомендую использовать:</p>
<p><strong>а. Для вашего регламента разработки</strong></p>
<ul>
<li><a href="https://chrome.google.com/webstore/detail/lhdoppojpmngadmnindnejefpokejbdd">Плагин</a><a href="https://chrome.google.com/webstore/detail/lhdoppojpmngadmnindnejefpokejbdd"> aXe</a><a href="https://chrome.google.com/webstore/detail/lhdoppojpmngadmnindnejefpokejbdd"> для Chrome</a>: лёгкий в использовании плагин, проверяющий доступность. Находит проблемы и даёт рекомендации о том, как их исправить.</li>
<li><a href="https://chrome.google.com/webstore/detail/wave-evaluation-tool/jbbplnpkjmmeebjpijfedlgcdilocofh?hl=en-US">Wave</a>: инструмент для оценки доступности, который визуализирует доступность контента, добавляя иконки и индикаторы прямо на страницу.</li>
<li><a href="https://developers.google.com/web/updates/2018/01/devtools#a11y">Панель Accessibility, Audits и контрастность цветов в Chrome DevTools</a>: три инструмента, функции которых позволяют перемещаться по дереву доступности и видеть значения каждого элемента, определять контрастность текстовых элементов и выполнять полностраничный аудит доступности и других параметров.</li>
<li><a href="https://chrome.google.com/webstore/detail/nocoffee/jjeeggmbnhckmgdhmgdckeigabjfbddl">Плагин NoCoffee</a><a href="https://chrome.google.com/webstore/detail/nocoffee/jjeeggmbnhckmgdhmgdckeigabjfbddl"> для Chrome</a>: имитирует проблемы, с которыми сталкиваются люди c нарушениями зрения от лёгких до серьёзных.</li>
<li><a href="https://chrome.google.com/webstore/detail/high-contrast/djcfdncoelnlbldjfhinnjlhdjlikmph">Плагин High Contrast</a><a href="https://chrome.google.com/webstore/detail/high-contrast/djcfdncoelnlbldjfhinnjlhdjlikmph"> для Chrome</a>: позволяет просматривать страницы с цветовыми фильтрами с высокой контрастностью, упрощающими чтение текста. Так вы можете проверить, как выглядит ваш сайт для пользователей, которым нужен высокий уровень контраста.</li>
</ul>
<p><strong>б. Для ручного тестирования с реальными скринридерами</strong></p>
<ul>
<li><a href="https://www.apple.com/voiceover/info/guide/_1124.html">VoiceOver</a> на Mac (встроен в macOS).</li>
<li><a href="https://www.nvaccess.org/download/">NVDA</a> (бесплатный) и <a href="http://www.freedomscientific.com/Products/Blindness/JAWS?gclid=CjwKCAiA8vPUBRAyEiwA8F1oDDzHAjW9-GfooiNT3sCDcTg_7LNXvHz4XL7osONDyf3Y4k9KRbcuihoCKGMQAvD_BwE">JAWS</a> (платный) на Windows.</li>
</ul>
<p><strong>в. Для автоматического аудита</strong></p>
<ul>
<li><a href="https://www.npmjs.com/package/lighthouse">Google Lighthouse</a>: автоматический аудитор, похож на панель Audits в DevTools.</li>
<li><a href="https://www.npmjs.com/package/axe-core">aXe</a>: автоматический сервис проверки правил доступности, тех же, что и в расширении aXe.</li>
<li><a href="https://github.com/pa11y/pa11y-dashboard">Pa11y Dashboard</a>: веб-интерфейс, который поможет отслеживать состояние доступности на сайтах.</li>
</ul>
<h2>Читать дальше</h2>
<ul>
<li><a href="https://www.logicsolutions.com/508-ada-wcag-accessibility-difference/">508, ADA, WCAG: What’s the difference?</a></li>
<li><a href="http://www.who.int/disabilities/world_report/2011/report/en/">WHO’s World Report on Disability</a></li>
<li><a href="https://medium.com/@addyosmani/accessible-ui-components-for-the-web-39e727101a67">Accessible UI Components For The Web</a></li>
<li><a href="https://www.microsoft.com/design/inclusive/">Microsoft Inclusive Design</a></li>
<li><a href="https://www.udacity.com/course/web-accessibility--ud891">Web Accessibility free course on Udacity, by Google</a></li>
<li><a href="https://webaim.org/standards/wcag/checklist">WebAIM’s WCGA 2 checklist</a></li>
<li><a href="https://developers.google.com/web/fundamentals/accessibility/focus/using-tabindex">Using Tabindex</a></li>
<li><a href="https://www.w3.org/TR/wai-aria-practices-1.1/#aria_ex">WAI-ARIA Design Patterns and Widgets</a></li>
<li><a href="https://www.w3.org/TR/wai-aria-1.1/#roles_categorization">WAI-ARIA Categorization of Roles</a></li>
<li><a href="https://www.w3.org/TR/wai-aria-practices/examples/landmarks/HTML5.html">HTML5 Sectioning Elements</a></li>
</ul>

                    ]]></description><pubDate>Wed, 28 Nov 2018 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/pragmatic-a11y-rules/</guid></item><item><title>Важность уровней заголовков для вспомогательных технологий</title><link>https://web-standards.ru/articles/heading-levels/</link><description><![CDATA[
                        <p><a href="https://www.w3.org/TR/html52/sections.html#the-h1-h2-h3-h4-h5-and-h6-elements">Спецификация HTML</a> описывает шесть видов заголовков: <code>&lt;h1&gt;</code>, <code>&lt;h2&gt;</code>, <code>&lt;h3&gt;</code>, <code>&lt;h4&gt;</code>, <code>&lt;h5&gt;</code> и <code>&lt;h6&gt;</code>. Число в названии элемента отражает его приоритет. При этом <code>&lt;h1&gt;</code> самый общий, а <code>&lt;h6&gt;</code> наиболее специфичный.</p>
<p>Это важно знать! WebAIM провела в 2017 году <a href="https://webaim.org/projects/screenreadersurvey7/#finding">опрос пользователей скринридеров</a>, и он показал, что навигация по заголовкам — это самый важный способ навигации для людей, которые пользуются вспомогательными технологиями для поиска информации. Именно поэтому так важно правильно организовать заголовки на вашем сайте.</p>
<h2>Познание</h2>
<p>Прежде чем мы подробно рассмотрим как работают вспомогательные технологии, надо понять причины, по которым СМИ используют главы, разделы, подразделы и параграфы. Дробление контента на отдельные части облегчает его понимание, усвоение, запоминание и работу со ссылками.</p>
<figure>
    <img src="https://web-standards.ru/articles/heading-levels/images/2.jpg" alt="">
    <figcaption>
        <a href="https://medium.com/@nkuepper/the-guardian-with-a-new-design-in-print-web-and-app-30fab6c97529">
            Части контента в газете The Guardian
        </a>
    </figcaption>
</figure>
<p>Несмотря на то, что интернет не печатное издание, он заимствует у типографики все хорошие практики. Заголовки в HTML аналогичны соглашениям в типографике. Это одна из тех вещей, которую каждый считает чем-то очевидным, но она на самом деле помогают огромному количеству людей. <a href="https://webaim.org/articles/cognitive/">Когнитивные нарушения</a> широко распространены и перевешивают другие виды нарушений. Группировка контента на логические части помогает всем: от людей с патологическим состоянием организма, например, деменцией, до людей с временными ограничениями, такими как бессонница или осваивающими новые навыки.</p>
<h2>Навигация с помощью заголовков</h2>
<p>Некоторые виды <a href="https://webaim.org/articles/motor/assistive">вспомогательных технологий</a> (не только скринридеры) позволяют людям перескакивать от одного заголовка к другому. Это похоже на опыт людей, которые не пользуются вспомогательными технологиями, а прокручивают страницы, чтобы понять общий смысл, и останавливаются, когда находят нужную информацию.</p>
<h3>Создание списков заголовков</h3>
<p>Какие-то вспомогательные технологии могут обрабатывать заголовки и представлять их в виде упорядоченного списка. Такой список помогает быстро понять общий смысл страницы.</p>
<img src="https://web-standards.ru/articles/heading-levels/images/3.png" alt="Сгенерированный список заголовков на странице Wikipedia">
Сгенерированный список заголовков на странице Wikipedia
<p>Если вы не соблюдаете логическую последовательность, скажем, переходите от заголовка первого уровня сразу к третьему и пропускаете заголовок второго уровня, это может сбить с толку и вызвать раздражение у человека, которому они необходимы для навигации. Этот пробел в логической последовательности создаст трудности для пользователей и вынудит их использовать другие способы навигации, которые отнимут больше времени и сил на то, чтобы понять общую структуру страницы (если она есть) и определить, есть ли тут нужная информация в принципе.</p>
<h3>Заголовки первого уровня</h3>
<p>Ещё одна важная вещь, которую мы можем сделать, это убедиться, что на странице только <em>один</em> заголовок первого уровня. Такой заголовок должен описывать <a href="https://webaim.org/projects/screenreadersurvey7/#heading">основную идею содержания страницы</a>. Например, у страницы об истории французской выпечки может быть такой заголовок первого уровня:</p>
<pre><code tabindex="0" class="language-html">&lt;h1&gt;История французской выпечки&lt;/h1&gt;
&lt;p&gt;…&lt;/p&gt;
</code></pre>
<h3>Алгоритм структуры документа (несуществующий)</h3>
<p>К сожалению, существует заблуждение о том, что можно использовать заголовки первого уровня для каждого <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element#Content_sectioning">элемента секционного контента</a> на странице, а браузер дальше сам во всём разберётся с помощью алгоритма построения структуры документа (Document Outline Algorithm). Этот алгоритм <a href="https://adrianroselli.com/2016/08/there-is-no-document-outline-algorithm.html">спекулятивная фантастика</a>. В момент написания этой статьи алгоритм не поддерживается ни одним браузером. По этой причине мы должны придерживаться подхода <a href="https://www.w3.org/TR/html-design-principles/#priority-of-constituencies">«Priority of Constituencies»</a>», описанного в спецификации, и использовать другие заголовки.</p>
<h3>Другие заголовки</h3>
<p>Заголовки от второго до шестого уровней должны последовательно размещаться за первым и описывать основные моменты содержимого страницы.</p>
<p>Например, вот структура этой статьи:</p>
<pre><code tabindex="0">1. Важность уровней заголовков для вспомогательных технологий
    1. Познание
    2. Навигация с помощью заголовков
        1. Создание списков заголовков
        2. Заголовки первого уровня
            1. Алгоритм структуры документа (несуществующий)
        3. Другие заголовки
        4. Избыточная описательность
        5. Другие сервисы
    3. Стилизация
        1. Семантика
        2. Целесообразность
            1. Служебные стили
            2. Сброс
        3. Именование
    4. Подводя итоги: воспользуйтесь моментом
</code></pre>
<p>Посмотрев на этот список, вы быстро поняли про что статья. Неплохо, да?</p>
<p>Ещё нужно отметить, что заголовки, подобно иерархическим форматам вроде JSON, могут содержать внутри другие заголовки, при условии, что они логически связаны. Внутри заголовка второго уровня вы можете использовать несколько заголовков третьего уровня. Каждый из этих подзаголовков может, в свою очередь, иметь внутри подзаголовки четвёртого уровня.</p>
<p>Когда вы завершили тему, то можете закрыть вложенные заголовки. Это единственный случай, когда допустимо использовать «пропущенные» уровни: вы закончили описывать конкретную проблему и вернулись к более общей теме.</p>
<pre><code tabindex="0" class="language-html">&lt;h1&gt; История французской выпечки
    &lt;h2&gt; Важные вехи
            &lt;h3&gt; 16 век
                &lt;h4&gt; Слоёное тесто
                &lt;h4&gt; Облатка
            &lt;h3&gt; 17 век
            &lt;h4&gt; Выпечка из слоёного теста
            &lt;h4&gt; Взбитые сливки
                &lt;h5&gt; Шеф-повар принца Конде
            &lt;h3&gt; 18 век
                &lt;h4&gt; Макаруны
            &lt;h3&gt; 19 век
            &lt;h3&gt; 20 век
        &lt;h2&gt; Важные люди
            &lt;h3&gt; …
</code></pre>
<p>Этот пример показывает как правильно использовать заголовок пятого уровня («Шеф-повар принца Конде») перед заголовком третьего уровня («18 век»), пока родительские заголовки располагаются последовательно друг за другом. Если бы заголовок третьего уровня шёл перед заголовком пятого, то это было бы уже неправильно.</p>
<h3>Избыточная описательность</h3>
<p>Если мы используем слишком много заголовков, то перегружаем пользователей вспомогательных технологий информацией. Вы можете подумать, что я противоречу себе, ведь я только что говорил о том, как важны заголовки для навигации.</p>
<p>Подумайте об этом так: так же как и слишком большое количество элементов навигации на странице рассеивает внимание пользователей, так и большое количество заголовков создаёт фоновый шум, который запутывает их и отвлекает от идеи, которую вы пытаетесь донести.</p>
<p>Нет чётких правил о количестве заголовков, которое считается избыточным. Это напрямую зависит от того, какой это контент и в каком объёме нужно раскрыть его содержание. Например, для рецепта достаточно указать название блюда, его ингредиенты и дать инструкцию по приготовлению. Для научной статьи нужна большая детализация.</p>
<h3>Другие сервисы</h3>
<p>Хорошая структура заголовков нужна не только людям, которые пользуются вспомогательными технологиями. Так как заголовки имеют семантическое значение, другие сервисы могут взаимодействовать с описанием элементов, интерпретировать его и представлять в удобном виде для пользователей. Например, вот что делает поисковая система Bing со странницами с хорошей структурой:</p>
<img src="https://web-standards.ru/articles/heading-levels/images/4.png" alt="Результат поиска в Bing">
Результат поиска в Bing
<p>Как и скринридеры, сервисы вроде <a href="https://support.google.com/docs/answer/6367684?co=GENIE.Platform%3DDesktop&amp;hl=en">Google Docs</a> и <a href="https://blogs.dropbox.com/dropbox/2017/03/paper-hidden-gems/">Dropbox Paper</a> могут автоматически создавать перечни заголовков, чтобы помочь вам быстрое понять содержание документа и перемещаться по нему. Многие текстовые редакторы тоже умеют быстро формировать из заголовков динамически обновляемые оглавления, что делать вручную очень утомительно и трудоёмко. И, благодаря совместимости HTML, мы даже можем воссоздать такую навигацию с помощью <a href="https://chrome.google.com/webstore/detail/headingsmap/flbjommegcjonpdmenkdiocclhjacmbi/">расширения для браузера</a>!</p>
<h2>Стилизация</h2>
<p>Все слишком часто забывают, что CSS-классы могут применяться к заголовкам, как к любому другому элементу HTML внутри <code>&lt;body&gt;</code>.</p>
<h3>Семантика</h3>
<p>Причина, по которой я это упомянул, заключается в том, что элементы заголовков часто используются из-за того, как они <em>выглядят</em> («О, мне нравится цвет и шрифт <code>&lt;h3&gt;</code>, использую-ка я его!»), а не из-за того, какой приоритет содержимого они <em>описывают</em> (Бриошь — это вид сдобы). Это распространённая ошибка.</p>
<p>В идеальном академическом мире мы соблюдаем <a href="https://alistapart.com/article/material-honesty-on-the-web">материальную честность</a> (material honesty) заголовков, определяя их размер и стиль в соответствии с тем, какой уровень иерархии они описывают.</p>
<p>Однако мы живём в реальном мире. Современные сайты и веб-приложения — это сложные вещи. Их разрабатывают люди с разным уровнем опыта, интересами, знаниями и возможностями при проектировании и написании кода.</p>
<h3>Целесообразность</h3>
<p>В реальном мире может потребоваться стилизовать заголовок четвёртого уровня как заголовок второго уровня или оформить его как совершенно другой элемент. В этом случае лучше сохранить логическую структуру страницы и изменить только внешний вид заголовка:</p>
<pre><code tabindex="0" class="language-html">&lt;h5 class=&quot;heading-primary--marketing&quot;&gt;
    Забота профессионала по разумной цене
&lt;/h5&gt;
</code></pre>
<p>Это компромисс и признание того, что реальный мир сложный. Этот подход позволяет сохранить важную составляющую: интуитивную и эффективную навигацию для вспомогательных технологий.</p>
<h3>Служебные стили</h3>
<p>Такие методологии как <a href="https://frontstuff.io/in-defense-of-utility-first-css">Utility-First CSS</a> помогают более гибко настраивать внешний вид заголовков:</p>
<pre><code tabindex="0" class="language-html">&lt;h2 class=&quot;u-font-size-tiny u-color-gray u-line-height-tight&quot;&gt;
    Правила и соглашения
&lt;/h2&gt;
</code></pre>
<p>В примере я объявляю несколько классов, которые будут изменять внешний вид заголовков до тех пор, пока я не получу желаемый результат. Вы можете столкнуться с этой методологией на крупных сайтах и в приложениях, где низкая <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity">специфичность</a> классов помогает сохранить согласованность кода (консистентность).</p>
<h3>Сброс</h3>
<p>Чтобы закрепить правильный порядок и вложенность заголовков, можно сбросить их стили. Это поможет избежать использования дополнительного класса, который приводит заголовки к одному виду.</p>
<pre><code tabindex="0" class="language-css">h1,
h2,
h3,
h4,
h5,
h6 {
    color: inherit;
    font-size: 1em;
    line-height: inherit;
    margin: 0;
    padding: 0;
    vertical-align: baseline;
}
</code></pre>
<p>Этот небольшой пример <a href="https://www.smashingmagazine.com/2011/05/getting-started-with-defensive-web-design/">оборонительного дизайна</a> (defensive design) заставляет задуматься над тем, как структура документа формируется в зависимости от его внешнего вида. Эти стили объявляются после импорта <a href="https://cssreset.com/what-is-a-css-reset/">CSS-сброса</a>, так как могут переопределить стили из файла со сбросом.</p>
<h3>Именование</h3>
<p>Одна из сложнейших проблем информатики — это именование сущностей. Она признаёт, что вы ничего не знаете. Я не берусь утверждать, что знаю лучший способ именования классов заголовков для вашего сайта или приложения. Однако, если вы используете подход, который не основан на служебных стилях, я не рекомендую называть их .h1, .h2, .h3 и так далее.</p>
<p>Проблема этого подхода в том, что люди, которые не знакомы с кодовой базой, могут принять такое именование классов за инструкцию, требующую соответствия названия класса уровню заголовка. Это усилит проблему преимущества внешнего вида элемента над его содержанием.</p>
<p>Я также встречал случаи, когда такой подход допускал вообще не использовать для некоторых заголовков семантические теги («А, я могу сделать этот <code>&lt;div&gt;</code> похожим на <code>&lt;h5&gt;</code>!»). Не надо так!</p>
<h2>Подводя итоги: воспользуйтесь моментом</h2>
<p>Опрос пользователей скринридеров, который WebAIM провела в 2017 году, также показал, что пропуск или неподходящие заголовки <a href="https://webaim.org/projects/screenreadersurvey7/#problematic">одна из 10 наиболее распространённых проблем</a>, с которыми сталкиваются пользователи вспомогательных технологий.</p>
<p>И это одна из самых распространённых проблем с доступностью, которую легко решить. Потратьте немного времени и посмотрите на свой сайт или приложение ещё раз. Структура заголовков точно описывает страницу? Соблюдается ли логический порядок? Очень просто поменять пару цифр местами!</p>
<p>Стоит обратить внимание и на то, что заголовки, подобно иерархическим форматам данных, таким как JSON, могут быть вложены в другие, если они логически связаны. Внутри заголовка второго уровня вы можете использовать заголовки третьего. Каждый из этих подзаголовков может, в свою очередь, иметь вложенные подзаголовки четвёртого уровня.</p>

                    ]]></description><pubDate>Tue, 13 Nov 2018 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/heading-levels/</guid></item><item><title>CSS: новый вид JavaScript</title><link>https://web-standards.ru/articles/css-new-kind-of-javascript/</link><description><![CDATA[
                        <p>Те из вас, кто знаком с веб-платформой, наверняка наслышаны и о двух дополнительных технологиях: <strong>HTML</strong> для структуризации документов и <strong>JavaScript</strong> для интерактивности и стилизации.</p>
<p>Сколько мы себя помним, стилизация документов (то есть, влияние на их внешний вид) всегда производилась с помощью JavaScript-свойства <code>style</code>, доступного на каждом поддерживаемом DOM-узле.</p>
<pre><code tabindex="0" class="language-js">node.style.color = 'red';
</code></pre>
<p>До изобретения этого API для стилизации авторам HTML приходилось писать атрибуты <code>style</code> вручную прямо в DOM, что затрудняло процесс редактирования.</p>
<p>Используя выборку узлов по селектору из JS, мы можем стилизовать несколько элементов одновременно. В примере ниже текст во всех <code>&lt;p&gt;</code> стилизован красным цветом.</p>
<pre><code tabindex="0" class="language-js">const nodes = document.querySelectorAll('p');
Array.prototype.forEach.call(nodes, node =&gt; {
    node.style.color = 'red';
});
</code></pre>
<p>Отличная возможность выборки по селектору — обращение к нескольким элементам, перечисленным в списке через запятую.</p>
<pre><code tabindex="0" class="language-js">const nodes = document.querySelectorAll('p, li, dd');
</code></pre>
<p>Гораздо менее удобно применять несколько стилей к одному узлу. Такой подход быстро становится слишком многословным:</p>
<pre><code tabindex="0" class="language-js">node.style.color = 'red';
node.style.backgroundColor = 'black';
node.style.padding = '1rem';
// и т.д.
</code></pre>
<p>Единственная стандартная альтернатива — использовать свойство cssText:</p>
<pre><code tabindex="0" class="language-js">node.style.cssText = 'color: red; background-color: black; padding: 1rem;';
</code></pre>
<p>Управлять несколькими стилями в одной строке проблематично. В будущем будет сложно обновлять, удалять или заменять отдельные стили.</p>
<p>Для этого авторы придумали способы управления информацией о стилях с помощью объектов, часто при помощи прототипа интерфейса Element.</p>
<pre><code tabindex="0" class="language-js">Element.prototype.styles = function(attrs) {
    Object.keys(attrs).forEach(attr =&gt; {
        this.style[attr] = attrs[attr];
    });
}
</code></pre>
<p>Теперь стили к узлу можно добавлять вот так:</p>
<pre><code tabindex="0" class="language-js">node.styles({
    'color': 'red',
    'backgroundColor': 'black',
    'padding': '1rem'
});
</code></pre>
<p>Использование подобного подхода приобрело <em>довольно большую</em> популярность в приложениях и их жизненных циклах. Однако это привело к печально известной проблеме — непониманию, где всё это хранить и как явно отделить стили от интерактивной части, ещё одной области ответственности JavaScript.</p>
<p>А есть и другая, более фундаментальная проблема: эти стили не реактивны. К примеру, у меня есть некоторые стили для неактивных кнопок:</p>
<pre><code tabindex="0" class="language-js">const disableds = document.querySelectorAll('[disabled]');

Array.prototype.forEach.call(disableds, disabled =&gt; {
    disabled.styles({
    'opacity': '0.5',
    'cursor': 'not-allowed'
});
</code></pre>
<p>Эти стили применяются только к неактивным кнопкам, которые уже есть в DOM. Любые кнопки, добавленные в DOM или, что более вероятно, любые кнопки, которые приобретут свойство или атрибут <code>disabled</code>, не получат автоматически подходящую стилизацию.</p>
<pre><code tabindex="0" class="language-js">button.disabled = true;
button.style // ничего нового
</code></pre>
<p>Можно, конечно, слушать изменение атрибутов и реагировать на него с помощью <code>mutationObserver</code>:</p>
<pre><code tabindex="0" class="language-js">const button = document.querySelector('button');

var config = { attributes: true }

var callback = function(mutationsList) {
    for(var mutation of mutationsList) {
        if (mutation.type == 'attributes') {
            if (button.disabled) {
                button.styles({
                    'opacity': '0.5',
                    'cursor': 'not-allowed'
                });
            }
        }
    }
}

var observer = new MutationObserver(callback);

observer.observe(button, config);
</code></pre>
<p>Думаю, что все согласятся, что это — <em>довольно много</em> кода, с учётом того, что он всего лишь заставляет один экземпляр элемента одного типа реагировать на изменение единственного атрибута. Также это меняет стилизацию только в одном направлении: нам бы пришлось обрабатывать отмену стилей при удалении свойства <code>disabled</code>. Это не так уж и просто, учитывая, что мы не знаем изначальных значений свойств <code>opacity</code> или <code>cursor</code>.</p>
<p>Как бы я ни любил JavaScript, я не думаю, что он хорошо спроектирован для задач стилизации. В конце концов, это процедурный и событийный язык, тогда как стили — это то, что у вас либо есть, либо нет.</p>
<p>Мы тратим слишком много времени впустую, когда пишем и поддерживаем стили с помощью JavaScript. Пришло время это менять. Поэтому я с большим удовольствием представляю вам новый стандарт, который называется CSS.</p>
<h2>CSS</h2>
<p>CSS — это декларативное подмножество JavaScript, оптимизированное для задач стилизации. CSS-файл имеет расширение .css и, что важно, парсится независимо от стандартных JS-файлов. С помощью CSS наконец-то стало возможно отделить стили от поведения. Вы можете брендировать своё приложение, не трогая бизнес-логику!</p>
<h3>Синтаксический сахар</h3>
<p>Первое, что вы заметите — это более чистый синтаксис, фанаты CoffeeScript оценят:</p>
<pre><code tabindex="0" class="language-css">[disabled] {
    opacity: 0.5;
    cursor: not-allowed;
}
</code></pre>
<p>Структура, похожая на объектную, сохраняется, но теперь вам не нужно явно вызывать <code>querySelectorAll</code> для перебора DOM-узлов. Вместо этого перебор происходит внутри, и это, конечно же, производительнее.</p>
<p>Пример выше автоматически применяется ко всем DOM-узлам с атрибутом <code>disabled</code>. Более того, все <em>новые</em> кнопки с атрибутом <code>disabled</code> автоматически приобретут связанные стили. Реактивность из коробки!</p>
<h3>Каскад</h3>
<p>CSS расшифровывается как <strong>каскадные таблицы стилей.</strong> Каскадность здесь, пожалуй, самая лучшая особенность. Взгляните на следующий CSS:</p>
<pre><code tabindex="0" class="language-css">button {
    background-color: blue;
    cursor: pointer;
}

[disabled] {
    cursor: not-allowed;
}
</code></pre>
<p>Стили для блока <code>[disabled]</code> идут после блока <code>button</code> в стилях. Любые стили, описанные в блоке <code>[disabled]</code> с такими же ключами (именами свойств), что и в предшествующем <code>button</code>, будут перезаписаны. Самое замечательное заключается в том, что добавление атрибута или свойства <code>disabled</code> кнопке обновит только соответствующие стили. В примере выше свойство cursor обновится, но <code>background-color</code> останется тем же. Своего рода система фильтрации.</p>
<p>Ко всему прочему, если атрибут или свойство <code>disabled</code> удалено, стили по умолчанию будут автоматически возвращены — ведь узел теперь соответствует блоку <code>button</code> выше по каскаду. Не нужно «вспоминать», какие стили и при каких условиях были применены ранее.</p>
<h3>Устойчивость</h3>
<p>При стилизации с помощью JavaScript любое неизвестное свойство или синтаксическая ошибка остановят парсинг скрипта. Все последующие стили или поведение будут отброшены целиком и всё ваше приложение просто рухнет.</p>
<p>CSS гораздо надёжнее. В большинстве случаев любое неизвестное свойство или синтаксическая ошибка приведут только к тому, что только декларация (пара свойство: значение) с ошибкой будет отброшена.</p>
<p>Эта инновация учитывает, что разные браузеры поддерживают разные свойства стилизации, и что отдельные стили не критичны для всей задачи. Устойчивость CSS значит, что больше пользователей получат доступ к функционирующему интерфейсу.</p>
<h2>Заключение</h2>
<p>Явный признак того, что технология не подходит для определённых целей — если для обхода сложностей нам приходится сильно полагаться на трюки и лучшие практики. Другой признак — насколько много кода нам приходится писать, чтобы сделать простые вещи. Когда речь заходит про стилизацию, JavaScript — как раз та самая неподходящая технология.</p>
<p>CSS решает проблемы JavaScript со стилизацией, причём элегантно. Вопрос в том, готовы ли вы принять изменения или вы завязли в неудачной методологии?</p>
<p>Больше информации про CSS и советы для быстрого старта <a href="https://en.wikipedia.org/wiki/Campus_SuperStar">вы найдете здесь</a>.</p>

                    ]]></description><pubDate>Fri, 13 Jul 2018 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/css-new-kind-of-javascript/</guid></item><item><title>Функции-декораторы, которые можно написать с нуля</title><link>https://web-standards.ru/articles/function-decorators/</link><description><![CDATA[
                        <figure>
    <img src="https://web-standards.ru/articles/function-decorators/images/1.jpg" alt="">
    <figcaption>
        Фото <a href="https://unsplash.com/photos/rkT_TG5NKF8">Calum Lewis</a>.
    </figcaption>
</figure>
<blockquote>
<p>Декораторы — это функции высшего порядка, которые принимают в качестве аргумента одну функцию и возвращают другую. Возвращаемая функция является преобразованным вариантом функции-аргумента <a href="https://leanpub.com/javascript-allonge/read#decorators">Javascript Allongé</a></p>
</blockquote>
<p>Давайте самостоятельно напишем некоторые базовые дектораторы, представленные в таких библиотеках, как <a href="http://underscorejs.org/#functions">underscore.js</a>, <a href="https://lodash.com/docs/4.17.5">lodash.js</a>, <a href="http://ramdajs.com/docs/">ramda.js</a>.</p>
<h2><code>once()</code></h2>
<ul>
<li><a href="https://jsfiddle.net/cristi_salcescu/zpLeLp0v/">once(fn)</a> создает экземпляр функции, которая должна быть выполнена только один раз. Паттерн может быть использован, например, для инициализации, когда нужно быть уверенным в единичном запуске функциональности, даже если сама функция вызвана в нескольких местах.</li>
</ul>
<pre><code tabindex="0" class="language-js">function once(fn){
    let returnValue;
    let canRun = true;
    return function runOnce(){
        if(canRun) {
            returnValue = fn.apply(this, arguments);
            canRun = false;
        }
        return returnValue;
    }
}

var processonce = once(process);
processonce(); // process
processonce(); //
</code></pre>
<p>Функция once() возвращает другую функцию — runOnce(), использующую <a href="https://medium.freecodecamp.org/why-you-should-give-the-closure-function-another-chance-31253e44cfa0">замыкание</a>. Обратите также внимание, как осуществлен вызов оригинальной функции, а именно через передачу this и arguments в метод apply: fn.apply(this, arguments).</p>
<p>Если хотите узнать замыкания глубже, обратите внимание на статью «<a href="https://medium.com/p/31253e44cfa0">Why you should give the Closure function another chance</a>».</p>
<h2>after()</h2>
<ul>
<li><a href="https://jsfiddle.net/cristi_salcescu/4evuoxe6/">after(count, fn)</a> создает вариант функции, которая будет выполнена только после определенного количества вызовов. Функция полезна, например, если должна быть выполнена <em>только</em> по завершению асинхронных операций.</li>
</ul>
<pre><code tabindex="0" class="language-js">function after(count, fn) {
    let runCount = 0;
    return function runAfter() {
        runCount = runCount + 1;
        if (runCount &gt;= count) {
            return fn.apply(this, arguments);
        }
    }
}

function logResult() { console.log('calls have finished'); }
let logResultAfter2Calls = after(2, logResult);

setTimeout(function logFirstCall() {
    console.log('1st call has finished');
    logResultAfter2Calls();
}, 3000);

setTimeout(function logSecondCall() {
    console.log('2nd call has finished');
    logResultAfter2Calls();
}, 4000);
</code></pre>
<p>В примере выше при помощи after() я создаю функцию logResultAfter2Calls(). Она в свою очередь выполняет logResult() только после второго вызова.</p>
<h2>throttle()</h2>
<ul>
<li><a href="https://jsfiddle.net/cristi_salcescu/5tdv0eq6/">throttle(fn, wait)</a> создает вариант функции, которая при повторяющихся вызовах выполняется через указанный временной интервал (аргумент wait). Декоратор эффективен для обработки быстро повторяющихся событий.</li>
</ul>
<pre><code tabindex="0" class="language-js">function throttle(fn, interval) {
    let lastTime;
    return function throttled() {
        let timeSinceLastExecution = Date.now() - lastTime;
        if(!lastTime || (timeSinceLastExecution &gt;= interval)) {
            fn.apply(this, arguments);
            lastTime = Date.now();
        }
    };
}

let throttledProcess = throttle(process, 1000);
$(window).mousemove(throttledProcess);
</code></pre>
<p>Здесь движение мыши генерирует множество событий mousemove, тогда как оригинальная функция process() вызывается лишь раз в секунду.</p>
<h2>debounce()</h2>
<ul>
<li><a href="https://jsfiddle.net/cristi_salcescu/424unsa7/">debounce(fn, wait)</a> создает вариант функции, которая выполняет <em>оригинальную</em> функцию спустя wait миллисекунд <em>после</em> предыдущего вызова <em>декорированной</em> функции. Паттерн также применяется в работе с повторяющимися событиями. Он полезен, если функциональность должна быть выполнена по завершению очереди событий.</li>
</ul>
<pre><code tabindex="0" class="language-js">function debounce(fn, interval) {
    let timer;
    return function debounced() {
        clearTimeout(timer);
        let args = arguments;
        let that = this;
        timer = setTimeout(function callOriginalFn() {
            fn.apply(that, args);
        }, interval);
    };
}

let delayProcess = debounce(process, 400);
$(window).resize(delayProcess);
</code></pre>
<p>Функция debounce() часто используется вместе с событиями scroll, resize, mousemove и keypress.</p>
<h2>Частичное применение</h2>
<p>Частичное применение преобразует функцию за счет изменения количества параметров. Это один из примеров движения от общего к частному.</p>
<h2>partial()</h2>
<p>На этот раз <a href="https://jsfiddle.net/cristi_salcescu/sbborekp/">создадим метод partial()</a> и сделаем его доступным для всех функций. В данном примере я использую синтаксис ECMAScript 6, а именно оператор rest. С его помощью набор аргументов функции преобразуется в массив <code>...leftArguments</code>. Это нужно для конкатенации массивов, тогда как специальный объект arguments массивом не является.</p>
<pre><code tabindex="0" class="language-js">function.prototype.partial = function(...leftArguments){
    let fn = this;
    return function partialFn(...rightArguments){
        let args = leftArguments.concat(rightArguments);
        return fn.apply(this, args);
    }
}

function log(level, message){
    console.log(level + ' : ' + message);
}

let logInfo = log.partial('Info');
logInfo('here is a message');
</code></pre>
<p>Обратите внимание, созданная таким образом logInfo() использует лишь один аргумент message.</p>
<h2>Заключение</h2>
<p>Применение указанных функций помогает понять принципы работы декораторов и саму идею инкапсуляции логики внутри них.</p>
<p>Декораторы — мощный инструмент расширения функциональности без изменения исходной функции. Это отличный путь переиспользовать код, и он соответствует функциональной парадигме программирования.</p>
<h3>Больше о ФП в JavaScript</h3>
<ul>
<li><a href="https://medium.com/p/33dcb910303a">How point-free composition will make you a better functional programmer</a></li>
<li><a href="https://medium.com/p/13ba11825319">You will finally understand what Closure is</a></li>
<li><a href="https://medium.com/p/73258b6a8d15">Class vs Factory function: exploring the way forward</a> (см. <a href="https://medium.com/@kanby/%D0%BA%D0%BB%D0%B0%D1%81%D1%81-vs-%D1%84%D0%B0%D0%B1%D1%80%D0%B8%D0%BA%D0%B0-%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D0%BE%D0%B2-%D0%BF%D0%B5%D1%80%D1%81%D0%BF%D0%B5%D0%BA%D1%82%D0%B8%D0%B2%D1%8B-9b4c696823c8">перевод</a>).</li>
<li><a href="https://medium.com/p/94fb8cc69f9d">Make your code easier to read with Functional Programming</a></li>
</ul>

                    ]]></description><pubDate>Wed, 13 Jun 2018 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/function-decorators/</guid></item><item><title>Глубокое погружение в ES-модули в картинках</title><link>https://web-standards.ru/articles/es-modules-cartoon-dive/</link><description><![CDATA[
                        <p>ES-модули приносят в JavaScript официальную, унифицированную модульную систему. Однако, чтобы прийти к этому, потребовалось почти 10 лет работы по стандартизации.</p>
<p>Но ожидание почти закончилось. С выходом Firefox 60 в мае (пока в бете) все основные браузеры будут поддерживать ES-модули, а Рабочая группа Node Modules сейчас работает над добавлением поддержки ES-модулей в Node.js. Также идет интеграция ES-модулей в WebAssembly.</p>
<p>Многие JavaScript-разработчики знают, что ES-модули были противоречивыми. Но мало кто действительно понимает, как они работают. Давайте рассмотрим, какую проблему решают ES-модули и чем они отличаются от модулей в других модульных системах.</p>
<h2>Какую проблему решают модули?</h2>
<p>Написание кода на JavaScript состоит в работе с переменными — в присвоении значений переменным или добавлении чисел в переменные или объединении двух переменных вместе и помещении их в другую переменную.</p>
<img src="https://web-standards.ru/articles/es-modules-cartoon-dive/images/2.png" alt="">
<p>Поскольку большая часть вашего кода связана с изменением переменных, то их организация будет напрямую влиять на качество вашего кода… и на то, насколько хорошо вы сможете поддерживать его в дальнейшем.</p>
<p>Небольшое количества переменных, о которых вам нужно думать, значительно упростило бы ситуацию. У JavaScript есть способ помочь вам в этом — область видимости (scope). Из-за того, как области видимости работают в JavaScript, функции не могут обращаться к переменным, определенным в других функциях.</p>
<img src="https://web-standards.ru/articles/es-modules-cartoon-dive/images/3.png" alt="">
<p>Это хорошо. Значит когда вы работаете над одной функцией, вы можете сосредоточиться только на ней. Вам не нужно беспокоиться о том, что другие функции могут делать с вашими переменными.</p>
<p>Однако у такого подхода есть и недостаток. Он затрудняет обмен переменными между различными функциями.</p>
<p>Что делать, если вы захотите использовать переменную вне текущей области видимости? Обычно способ справиться с этим заключается в том, чтобы положить её в область выше, например, в глобальную область видимости.</p>
<img src="https://web-standards.ru/articles/es-modules-cartoon-dive/images/4.png" alt="">
<p>Это работает, но появляются некоторые проблемы.</p>
<p>Во-первых, все ваши скрипты должны быть расположены в определенном порядке. И вы должны будете внимательно следить за тем, чтобы никто не испортил этот порядок.</p>
<p>Если этот порядок нарушится, то в середине работы ваше приложение выдаст ошибку. Когда функция ищет jQuery там, где она ожидает его — на глобальном уровне — и не находит его, она выбрасывает ошибку и прекращает выполнение.</p>
<img src="https://web-standards.ru/articles/es-modules-cartoon-dive/images/5.png" alt="">
<p>Это делает поддержку кода сложным, а удаление старого кода или скриптов игрой в рулетку. Вы не знаете, что может сломаться. Зависимости между этими различными частями кода неявны. Любая функция может использовать что угодно на глобальном уровне, поэтому вы точно не можете знать, какие функции от чего зависят.</p>
<p>Вторая проблема заключается в том, что поскольку эти переменные находятся в глобальной области видимости, каждая часть кода, находящаяся внутри этой области, может изменить переменную. Вредоносный код может изменить эту переменную специально с целью изменения работы функций или другой код может случайно удалить вашу переменную.</p>
<h2>Как нам помогут модули?</h2>
<p>Модули дают вам лучший способ организовать эти переменные и функции. С модулями вы группируете переменные и функции, которые имеет смысл объединить.</p>
<p>Это помещает эти функции и переменные в область видимости модуля. Область модуля может использоваться для обмена переменными между функциями в модуле.</p>
<p>Но в отличие от областей видимости функций, области модулей позволяют сделать их переменные доступными и для других модулей. Они могут явно указать, какие переменные, классы или функции в модуле должны быть доступны.</p>
<p>Когда что-то становится доступным для других модулей, это называется export. После экспорта другие модули могут явно сказать, что они зависят от этой переменной, класса или функции.</p>
<img src="https://web-standards.ru/articles/es-modules-cartoon-dive/images/6.png" alt="">
<p>Поскольку экспорт указывается явно, это позволяет определить, какие модули будут нарушены при удалении другого.</p>
<p>Когда у вас появляется возможность экспортировать и импортировать переменные между модулями, это значительно облегчает разбиение вашего кода на небольшие куски, которые могут работать независимо друг от друга. Затем вы можете комбинировать и рекомбинировать эти куски, вроде блоков Lego, для создания любых приложений из одного и того же набора модулей.</p>
<p>Поскольку модули настолько полезны, было несколько попыток добавить модульность в JavaScript. Сегодня активно используются две системы</p>
<ol>
<li>CommonJS (CJS) — это то, что Node.js использовал изначально.</li>
<li>ESM (модули ECMAScript) — это более новая система, добавленная в спецификацию JavaScript. Браузеры уже поддерживают ES-модули, а Node.js добавляет поддержку.</li>
</ol>
<p>Давайте подробно рассмотрим, как работает эта новая модульная система.</p>
<h2>Как работают ES-модули</h2>
<p>При разработке с помощью модулей, вы строите граф зависимостей. Связи между различными зависимостями берутся из любых инструкций import, которые вы используете.</p>
<p>Эти операторы импорта определяют, какой код браузеру или Node.js нужно загрузить. Вы даете ему файл для использования в качестве точки входа в граф. Оттуда он просто следует за любым из операторов импорта, чтобы найти остальную часть кода.</p>
<img src="https://web-standards.ru/articles/es-modules-cartoon-dive/images/7.png" alt="">
<p>Но сами файлы не являются тем, что браузер может использовать. Ему необходимо разобрать все эти файлы, чтобы превратить их в структуры данных, называемые записями модулей (module records). Таким образом, он действительно узнает, что происходит в файле.</p>
<img src="https://web-standards.ru/articles/es-modules-cartoon-dive/images/8.png" alt="">
<p>После этого запись модуля необходимо превратить в экземпляр модуля. Экземпляр объединяет две вещи: код и состояние.</p>
<p>Код обычно представляет собой набор инструкций. Это как рецепт, как сделать что-то. Но вы не можете просто использовать этот код. Вам нужны исходные материалы для использования с этими инструкциями.</p>
<p>Что такое состояние? Состояние дает вам эти исходные материалы. Состояние — это фактические значения переменных в любой момент времени. Конечно, эти переменные — это просто псевдонимы для ячеек памяти, которые содержат значения.</p>
<p>Таким образом, экземпляр модуля объединяет код (список инструкций) с состоянием (значениями всех переменных).</p>
<img src="https://web-standards.ru/articles/es-modules-cartoon-dive/images/9.png" alt="">
<p>Нам нужен экземпляр для каждого модуля. Процесс загрузки модуля происходит от файла точки входа к полному графу экземпляров модуля.</p>
<p>Для ES-модулей это происходит в три этапа.</p>
<ol>
<li>Построение (construction) — поиск, загрузка и парсинг всех файлов в записях модулей.</li>
<li>Создание экземпляра (instantiation) — поиск ячеек в памяти для размещения всех экспортируемых значений (но пока без заполнения их значениями) Затем связывание — экспорт и импорт этих полей в памяти.</li>
<li>Оценка (evaluation) — запуск кода для заполнения этих полей фактическими значениями переменных.</li>
</ol>
<img src="https://web-standards.ru/articles/es-modules-cartoon-dive/images/10.png" alt="">
<p>Люди говорят о том, что ES-модули являются асинхронными. Вы можете думать об этом как о асинхронном процессе, т.к работа делится на три разные фазы:</p>
<ol>
<li>Построение</li>
<li>Создание экземпляров</li>
<li>Оценка</li>
</ol>
<p>Все эти этапы могут выполняться отдельно. Это означает, что спецификация вводит какую-то асинхронность, которой не было в CommonJS. Я объясню это позже, но в CJS-модуль и его зависимости загружаются, создаются и анализируются сразу, без каких-либо перерывов.</p>
<p>Однако сами по себе действия не обязательно являются асинхронными. Они могут быть выполнены синхронным способом — зависит от того, что делает загрузка. Это потому, что не все контролируется спецификацией ESM. На самом деле есть два этапа работы, которые покрываются различными спецификациями.</p>
<p>Спецификация ES-модулей говорит о том, как следует анализировать файлы в записях модулей, и как следует создавать экземпляры и оценивать этот модуль. Тем не менее, в спецификации не описывается, как изначально получить эти файлы.</p>
<p>Это загрузчик, который извлекает файлы. И загрузчик указан в другой спецификации. Для браузеров он описан спецификацией HTML. Но у вас могут быть разные загрузчики, основанные на той платформе, которую вы используете.</p>
<img src="https://web-standards.ru/articles/es-modules-cartoon-dive/images/11.png" alt="">
<p>Загрузчик также точно контролирует загрузку модулей. Он вызывает методы ES-модуля: <code>ParseModule</code>, <code>Module.Instantiate</code> и <code>Module.Evaluate</code>.</p>
<img src="https://web-standards.ru/articles/es-modules-cartoon-dive/images/12.png" alt="">
<p>Теперь давайте пройдемся по каждому шагу более подробно.</p>
<h3>Построение (Construction)</h3>
<p>Во время этапа построения для каждого модуля происходят три вещи:</p>
<ol>
<li>Определение, где загрузить файл, содержащий модуль (module resolution).</li>
<li>Загрузка файла (по URL или из файловой системы).</li>
<li>Синтаксический анализ файла в записи модуля.</li>
</ol>
<h3>Поиск и получение файла (fetching)</h3>
<p>Загрузчик позаботится о поиске файла и его загрузке. Сначала ему необходимо найти файл точки входа. В HTML вы указываете загрузчику, где его найти, используя тег <code>&lt;script&gt;</code>.</p>
<img src="https://web-standards.ru/articles/es-modules-cartoon-dive/images/13.png" alt="">
<p>Но как он найдет следующую группу модулей — от которых напрямую зависят модули main.js?</p>
<p>В этом случае используются операторы import. Одна часть оператора импорта называется спецификатором модуля. Он сообщает загрузчику, где он может найти каждый следующий модуль.</p>
<img src="https://web-standards.ru/articles/es-modules-cartoon-dive/images/14.png" alt="">
<p>Одно замечание о спецификаторах модулей: иногда их нужно обрабатывать по-разному в браузере и Node.js. Каждый хост имеет свой собственный способ интерпретации спецификатора модуля. Для этого он использует решение, называемое алгоритмом разрешения модулей, которое отличается между платформами. В настоящее время некоторые спецификаторы модулей, которые работают в Node.js, не будут работать в браузере, но сейчас ведутся <a href="https://github.com/domenic/package-name-maps">работы по устранению этой проблемы</a>.</p>
<p>Пока это не исправлено, браузеры принимают только URL в качестве спецификаторов модуля. Они загружают файл модуля с этого URL. Но это не происходит для всего графа одновременно. Вы не знаете, какие зависимости модуль должен получить, пока вы не проанализировали файл… и вы не сможете проанализировать файл, пока не получите его.</p>
<p>Это означает, что мы должны пройти через дерево поэтапно, слой за слоем, анализировать один файл, выяснить его зависимости, а затем найти и загрузить их.</p>
<img src="https://web-standards.ru/articles/es-modules-cartoon-dive/images/15.png" alt="">
<p>Если бы основной поток ожидал загрузки каждого из этих файлов, в его очереди скопилось бы много других задач. Это потому, что, когда вы работаете в браузере, загрузка занимает большую часть времени.</p>
<img src="https://web-standards.ru/articles/es-modules-cartoon-dive/images/16.png" alt="">
<p>Блокировка основного потока сделает использование модулей в вашем приложении очень медленным. Это одна из причин того, что спецификация ES-модулей разбивает алгоритм на несколько этапов. Разбиение на фазы позволяет браузерам извлекать файлы и строить свое понимание графа модуля перед тем, как приступить к синхронной работе по созданию экземпляров.</p>
<p>Этот подход — разделение алгоритма на фазы — является одним из ключевых различий между ES-модулями и модулями CommonJS.</p>
<p>CommonJS может делать всё по-другому, потому что загрузка файлов из файловой системы занимает гораздо меньше времени, чем загрузка через интернет. Это означает, что Node.js может блокировать основной поток при загрузке файла. И раз файл уже загружен, есть смысл сразу провести построение (construction) и создание экземпляров (без разбивки на фазы). Это также означает, что вы идёте по всему графу, загружаете, создаете экземпляры и оцениваете зависимости перед возвратом экземпляра модуля.</p>
<img src="https://web-standards.ru/articles/es-modules-cartoon-dive/images/17.png" alt="">
<p>Подход CommonJS имеет несколько последствий, и я расскажу об этом позже. Но одно это означает, что в Node.js с модулями CommonJS вы можете использовать переменные в вашем спецификаторе модуля. Вы выполняете весь код в этом модуле (до инструкции <code>require</code>), прежде чем искать следующий модуль. Это означает, что переменная будет иметь значение при переходе к определению модулей.</p>
<p>Но с ES-модулями вы заранее строите весь этот граф модулей, прежде чем выполнять какую-либо оценку. Это означает, что вы не можете использовать переменные в своих спецификаторах модулей, поскольку эти переменные еще не имеют значений.</p>
<img src="https://web-standards.ru/articles/es-modules-cartoon-dive/images/18.png" alt="">
<p>Но иногда очень полезно использовать переменные для путей модулей. Например, может потребоваться переключить загружаемый модуль в зависимости от того, что делает код или в какой среде он выполняется.</p>
<p>Чтобы сделать это возможным для ES модулей, есть <a href="https://github.com/tc39/proposal-dynamic-import">предложение под названием динамический импорт</a>. С его помощью можно использовать импорт вида import(<code>${path}/foo.js</code>).</p>
<p>Это работает так: любой файл, загруженный с помощью import(), обрабатывается как точка входа в отдельный граф. Динамически импортируемый модуль запускает новый граф, который обрабатывается отдельно.</p>
<img src="https://web-standards.ru/articles/es-modules-cartoon-dive/images/19.png" alt="">
<p>Однако следует отметить, что любой модуль, который находится в обоих этих графах, будет совместно использовать экземпляр модуля. Это происходит потому, что загрузчик кэширует экземпляры модулей. Для каждого модуля в определенной глобальной области будет только один экземпляр модуля.</p>
<p>Это уменьшает объём работы для движка. Например, это означает, что файл модуля будет извлечен только один раз, даже если несколько модулей зависят от него (это одна из причин для кэширования модулей. Мы увидим другой в разделе оценки.)</p>
<p>Загрузчик управляет этим кэшем с помощью так называемой <a href="https://html.spec.whatwg.org/multipage/webappapis.html#module-map">карты модулей</a>. Каждый глобальный модуль отслеживает свои модули в отдельной карте.</p>
<p>Когда загрузчик получает URL, он помещает этот URL в карту модуля и отмечает, что он в настоящее время извлекает файл (fetching). Затем он отправит запрос и перейдет к следующему файлу.</p>
<img src="https://web-standards.ru/articles/es-modules-cartoon-dive/images/20.png" alt="">
<p>Что произойдет, если другой модуль зависит от того же файла? Загрузчик будет искать каждый URL в карте модуля. Если он увидит там <code>fetching</code>, он просто перейдет к следующему URL.</p>
<p>Но карта модуля не просто отслеживает, какие файлы извлекаются. Карта модуля также служит в качестве кэша для модулей, как мы увидим далее.</p>
<h3>Парсинг</h3>
<p>Теперь, когда мы извлекли этот файл, нам нужно распарсить его в записи модуля. Это помогает браузеру понять, что представляют собой различные части модуля.</p>
<img src="https://web-standards.ru/articles/es-modules-cartoon-dive/images/21.png" alt="">
<p>После создания запись модуля помещается в карту модуля. Это означает, что всякий раз, когда он запрашивается, загрузчик может вытащить его из этой карты.</p>
<img src="https://web-standards.ru/articles/es-modules-cartoon-dive/images/22.png" alt="">
<p>Есть одна деталь в парсинге, которая может показаться тривиальной, но на самом деле имеет довольно большие последствия. Все модули анализируются так, как если бы они имели <code>use strict</code> вверху. Есть и другие незначительные отличия. Например, ключевое слово <code>await</code> зарезервировано в коде верхнего уровня модуля, а значение <code>this</code> — <code>undefined</code>.</p>
<p>Другой способ парсинга называется целью парсинга (parse goal). Если вы анализируете один и тот же файл, но используете разные цели, вы получите разные результаты. Таким образом, вы хотите знать, прежде чем начать парсинг, какой файл вы анализируете — является ли он модулем или нет.</p>
<p>В браузерах это довольно легко. Вы просто добавляете <code>type=&quot;module&quot;</code> в тег <code>&lt;script&gt;</code>. Это говорит браузеру, что этот файл должен быть проанализирован как модуль. И поскольку импортировать можно только модули, браузер знает, что любой импорт также является модулем.</p>
<img src="https://web-standards.ru/articles/es-modules-cartoon-dive/images/23.png" alt="">
<p>Но в Node.js вы не можете использовать HTML-теги, поэтому у вас нет возможности использовать атрибут type. Сообщество пыталось решить эту проблему с помощью расширения .mjs. Это расширение говорит Node.js что этот файл является модулем. Сообщество говорит об этом, как о метке для цели парсинга. Обсуждение в настоящее время продолжается, поэтому неясно, какую метку сообщество решит использовать в конце.</p>
<p>В любом случае загрузчик определит, следует ли анализировать файл как модуль или нет. Если это модуль и есть импорт, он начнет процесс снова, пока все файлы не будут извлечены и распарсены.</p>
<p>И все готово! По окончании процесса загрузки вы перешли от простого файла точки входа к множеству записей модуля.</p>
<img src="https://web-standards.ru/articles/es-modules-cartoon-dive/images/24.png" alt="">
<p>Следующий шаг — создать экземпляр этого модуля и связать Все экземпляры вместе.</p>
<h3>Создание экземпляра</h3>
<p>Последний шаг — заполнение этих ячеек памяти. JS-движок делает это, выполняя код верхнего уровня — код, который находится вне функций.</p>
<p>Помимо простого заполнения этих ячеек памяти, оценка кода также может вызывать различные сайд-эффекты. Например, модуль может сделать запрос на сервер.</p>
<img src="https://web-standards.ru/articles/es-modules-cartoon-dive/images/25.png" alt="">
<p>Из-за потенциальных побочных эффектов вы хотите только один раз оценить модуль. В отличие от линковки, которая происходит при создании экземпляра, которая может выполняться несколько раз с точно таким же результатом, оценка может иметь разные результаты в зависимости от того, сколько раз вы это делаете.</p>
<p>Это одна из причин наличия карты модулей. Карта модуля кэширует модуль по каноническому URL, так что для каждого модуля имеется только одна запись модуля. Это гарантирует, что каждый модуль выполняется только один раз.</p>
<p>А как насчет тех циклов, о которых мы говорили раньше?</p>
<p>В циклической зависимости вы в конечном итоге получаете цикл в графе. Обычно это длинная петля. Но чтобы объяснить проблему, я буду использовать пример с коротким циклом.</p>
<img src="https://web-standards.ru/articles/es-modules-cartoon-dive/images/26.png" alt="">
<p>Давайте посмотрим, как это будет работать с модулями CommonJS. Во-первых, основной модуль выполнит оператор require. Затем будет загружен модуль counter.</p>
<img src="https://web-standards.ru/articles/es-modules-cartoon-dive/images/27.png" alt="">
<p>Модуль <code>counter</code> попытается получить доступ к <code>message</code> из объекта <code>exports</code>. Но так как он еще не был оценен в основном модуле, вернется <code>undefined</code>. JS-движок выделит пространство в памяти для локальной переменной и установит значение <code>undefined</code>.</p>
<img src="https://web-standards.ru/articles/es-modules-cartoon-dive/images/28.png" alt="">
<p>Оценка продолжается до конца кода верхнего уровня counter. Мы хотим узнать, получим ли мы правильное значение для сообщения в конце концов (после оценки main.js), поэтому мы настроим таймаут. Затем оценка возобновляется на main.js.</p>
<img src="https://web-standards.ru/articles/es-modules-cartoon-dive/images/29.png" alt="">
<p>Переменная message будет инициализирована и добавлена в память. Но поскольку между ними нет никакой связи, она останется неопределенной в требуемом модуле.</p>
<img src="https://web-standards.ru/articles/es-modules-cartoon-dive/images/30.png" alt="">
<p>Если экспорт будет обработан с использованием привязок в реальном времени, в конце концов counter увидит правильное значение. К моменту истечения таймаута оценка main.js завершиться и в переменную присвоится значение.</p>
<p>Поддержка этих циклов является большим основанием для разработки ES-модулей. Именно эта трехфазная архитектура и сделает её возможной.</p>
<h3>Каков текущий статус ES-модулей ?</h3>
<p>С выходом Firefox 60 в начале мая, все основные браузеры будут поддерживать ES-модули по умолчанию. Node.js также добавляет поддержку, создана рабочая группа, занимающаяся выяснением проблем совместимости между CommonJS и ES-модулями.</p>
<p>Это означает, что вы сможете использовать тег <code>&lt;script type=&quot;module&quot;&gt;</code>, <code>import</code> и <code>export</code>. Однако еще больше возможностей впереди. <a href="https://github.com/tc39/proposal-dynamic-import">Dynamic imports proposal</a> находится на Stage 3 в процессе спецификации, также есть <a href="https://github.com/tc39/proposal-import-meta"><code>import.meta</code></a>, а <a href="https://github.com/domenic/package-name-maps">module resolution proposal</a> поможет сгладить различия между браузерами и Node.js. Поэтому работа с модулями станет еще лучше в будущем.</p>
<h2>Благодарность</h2>
<p>Спасибо всем, кто дал обратную связь на этот пост, или чьи письма или дискуссии прошлого года, в том числе Акселю Раухшмаеру, Бредли Фариасу, Дейву Хернану, Доменику Дениколе, Хави Хоффману, Джейсону Везерсби, Джей-Эф Бастьену, Йону Копперду, Люку Вагнеру, Майлсу Боринсу, Тиллю Шнайдериту, Тобаясу Копперсу, Йехуде Кацу, участникам сообщества WebAssembly, Рабочей группе Node Modules, а также TC39.</p>

                    ]]></description><pubDate>Fri, 30 Mar 2018 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/es-modules-cartoon-dive/</guid></item><item><title>Руководство для прохождения интервью по фронтенду</title><link>https://web-standards.ru/articles/front-end-interview-handbook/</link><description><![CDATA[
                        <h2>Часть 1. HTML</h2>
<figure>
    <img src="https://web-standards.ru/articles/front-end-interview-handbook/images/1.png" alt="">
    <figcaption><a href="https://dribbble.com/shots/3831443-Tech-Interview-Handbook">Картинка yangheng</a></figcaption>
</figure>
<h2>Что это такое?</h2>
<p>В отличие от типичного собеседования с разработчиками ПО, на собеседованиях фронтенд-разработчиков меньше внимания уделяется алгоритмам. Большая часть вопросов касается специфичных знаний и компетенций в таких областях, как HTML, CSS, JavaScript.</p>
<p>Несмотря на то, что существуют ресурсы, призванные помочь в подготовке к собеседованию, они сильно отличаются по полноте материалов от тех же ресурсов для разработчиков ПО. Среди того, что существует на сегодняшний день, наиболее полезным может быть сборник вопросов <a href="https://github.com/h5bp/Front-end-Developer-Interview-Questions">Front-end Developer Interview Questions</a>. К сожалению, на многие вопросы я не смог найти в сети полные и удовлетворяющие ответы. Поэтому в документе ниже я постарался самостоятельно ответить на них. Будучи <a href="https://github.com/yangshun/front-end-interview-handbook">открытым репозиторием</a>, этот проект может жить и развиваться благодаря сообществу, поскольку интернет эволюционирует.</p>
<h2>Нужна более общая подготовка?</h2>
<p>Вас может заинтересовать <a href="https://github.com/yangshun/tech-interview-handbook">Tech Interview Handbook</a>, в котором содержится информация для прохождения общих технических интервью, в частности описаны алгоритмы, даны ответы на вопросы по софт-скиллс. Также есть <a href="https://github.com/yangshun/tech-interview-handbook/blob/master/preparing/cheatsheet.md">Interview Cheatsheet</a>!</p>
<h2>Вопросы по HTML</h2>
<p>Ответы на вопросы из списка <a href="https://github.com/h5bp/Front-end-Developer-Interview-Questions#html-questions">Front-end Job Interview Questions — HTML Questions</a>. Комментарии с предложениями по улучшению и дополнению приветствуются!</p>
<h3>Что делает DOCTYPE?</h3>
<p><code>&lt;!DOCTYPE&gt;</code> — это сокращение от «document type» (тип документа). Он объявляется в HTML для того, чтобы различать стандартный режим или <a href="https://quirks.spec.whatwg.org/#history">режим совместимости (quirks mode)</a>. Его наличие говорит браузеру работать со страницей в стандартном режиме.</p>
<p>Мораль истории — просто добавляй <code>&lt;!DOCTYPE html&gt;</code> в начало страницы.</p>
<h3>Ссылки</h3>
<ul>
<li><a href="https://stackoverflow.com/questions/7695044/what-does-doctype-html-do">Stack Overflow: What does <code>&lt;!doctype html&gt;</code> do?</a></li>
<li><a href="https://www.w3.org/QA/Tips/Doctype">W3C: Don’t forget to add a doctype</a></li>
<li><a href="https://quirks.spec.whatwg.org/#history">Quirks Mode: History</a></li>
</ul>
<h3>Как поддерживать страницу на нескольких языках?</h3>
<p>Вопрос немного расплывчатый. Полагаю, что речь о наиболее частом случае: как поддерживать страницу с содержимым на нескольких языках, где одновременно должен отображаться только один язык.</p>
<p>Когда к серверу делается HTTP-запрос, то браузер пользователя обычно отсылает информацию о предпочитаемом языке в заголовке Accept-Language. Сервер может использовать эту информацию, чтобы вернуть версию документа на подходящем языке, если такая возможность есть. В возвращенном HTML-документе обязательно должен быть указан атрибут <code>lang</code> у тега <code>&lt;html&gt;</code>, к примеру <code>&lt;html lang=&quot;en&quot;&gt;</code>.</p>
<p>На бэкенде HTML-разметка будет содержать плейсхолдер i18n, а контент для конкретного языка будет хранится в YML- или JSON-формате. Сервер динамически формирует HTML-страницу с контентом на конкретном языке, чаще всего при помощи бэкенд-фреймворка.</p>
<h3>Ссылки</h3>
<ul>
<li><a href="https://www.w3.org/International/getting-started/language">W3C: Language on the Web</a>.</li>
</ul>
<h3>На что обратить внимание при разработке мультиязычных сайтов?</h3>
<ul>
<li><strong>Используй атрибут <code>lang</code> в HTML.</strong></li>
<li><strong>Перенаправляй пользователей на версию сайта на их языке.</strong> Позволяйте быстро и без проблем изменить страну и язык.</li>
<li><strong>Текст на картинках плохо поддается адаптации.</strong> Многие до сих пор помещают текст на картинки чтобы получить хорошо выглядящий несистемный шрифт на любом компьютере. Однако чтобы перевести текст картинкой, нужно иметь подготовленную картинку с каждой строкой текста для каждого языка. При большом количестве текста это быстро выйдет из под контроля.</li>
<li><strong>Ограничение длины слов и предложений.</strong> Некоторый контент может быть длиннее при написании на другом языке. Будьте внимательны к макету и проверяйте поведение блоков при переполнении. Количество символов важно в таких элементах, как заголовки, лейблы и кнопки. Но не так важно в основном тексте или в блоке комментария.</li>
<li><strong>Помните о восприятии цветов.</strong> В разных языках и культурах цвета имеют разное значение. Дизайн должен учитывать эти особенности.</li>
<li><strong>Форматируете даты и валюты.</strong> Календарные даты иногда пишутся по-разному. Например, «Май 31, 2012» в Америке или «31 мая 2012» в большинстве стран Европы.</li>
<li><strong>Не склеивайте переведенные строки.</strong> Не пишите что-то вроде <code>&quot;Сегодняшняя дата &quot; + date</code>. Эта фраза будет выглядеть коряво на языках с другим порядком слов. Вместо этого используйте параметры шаблона.</li>
<li><strong>Разные направления чтения.</strong> В русском мы читаем слева направо, сверху вниз. В традиционном японском языке текст читается сверху вниз, справа налево.</li>
</ul>
<h3>Ссылки</h3>
<ul>
<li><a href="https://www.quora.com/What-kind-of-things-one-should-be-wary-of-when-designing-or-developing-for-multilingual-sites">What kind of things one should be wary of when designing or developing for multilingual sites?</a></li>
</ul>
<h3>Для чего отлично подойдут <code>data</code>-атрибуты?</h3>
<p>До того, как JavaScript-фреймворки стали популярны, фронтенд-разработчики использовали <code>data</code>-атрибуты чтобы хранить дополнительные данные прямо в DOM без хаков вроде нестандартных атрибутов или дополнительных свойств в DOM. Атрибуты этого семейства предназначены для хранения частных данных пользователя, для которых не существует более подходящих атрибутов или элементов, на странице или в приложении.</p>
<p>На сегодняшний день использование <code>data</code>-атрибутов не поощряется. Одной из причин является то, что пользователь может модифицировать данные в атрибуте, используя инспектор кода в браузере. Данные лучше хранить в самом JavaScript и обновлять DOM при помощи связывания данных через библиотеку или фреймворк.</p>
<h3>Ссылки</h3>
<ul>
<li><a href="https://html5doctor.com/html5-custom-data-attributes/">HTML5 Custom Data Attributes <code>data-*</code></a>.</li>
<li><a href="https://www.w3.org/TR/html5/dom.html#embedding-custom-non-visible-data-with-the-data-attributes">HTML 5.2: Embedding custom non-visible data with the <code>data-*</code> attributes</a></li>
</ul>
<h3>Представьте HTML5 как открытую веб-платформу. Из каких блоков состоит HTML5?</h3>
<ol>
<li><strong>Семантика.</strong> Позволяет более точно описать из чего состоит контент.</li>
<li><strong>Связанность.</strong> Позволяет общаться с сервером новыми и инновационными способами.</li>
<li><strong>Офлайн и хранилище.</strong> Позволяют страницам хранить данные локально на клиентской стороне и более эффективно работать в офлайне.</li>
<li><strong>Мультимедиа.</strong> Ставит создание видео и аудио на первое место в вебе.</li>
<li><strong>2D- и 3D-графика и эффекты.</strong> Позволяет расширить возможности презентации.</li>
<li><strong>Производительность и интеграция.</strong> Обеспечивает большую скорость оптимизации и лучшее использование аппаратных средств.</li>
<li><strong>Доступ к устройствам.</strong> Позволяет взаимодействовать с различными устройствами ввода и вывода.</li>
<li><strong>Стилизация.</strong> Позволяет создавать более сложные темы оформления.</li>
</ol>
<h3>Ссылки</h3>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5">MDN: HTML5</a>.</li>
</ul>
<h3>Объясните разницу между cookie, sessionStorage и localStorage.</h3>
<p>Все вышеупомянутые технологии являются механизмами хранения типа ключ-значение на клиентской стороне. Они могут хранить данные только как строки.</p>
<h3>Cookie</h3>
<ul>
<li><strong>Инициатор:</strong> клиент или сервер. Сервер может использовать заголовок <code>Set-Cookie</code>.</li>
<li><strong>Срок хранения:</strong> устанавливается вручную.</li>
<li><strong>Хранение между сессиями:</strong> зависит от установки срока хранения.</li>
<li><strong>Связь с доменом:</strong> да.</li>
<li><strong>Отправка на сервер с каждым HTTP-запросом:</strong> автоматически, с помощью заголовка <code>Cookie</code>.</li>
<li><strong>Емкость, на один домен:</strong> 4 КБ.</li>
<li><strong>Доступность:</strong> в любом окне.</li>
</ul>
<h3>Local Storage</h3>
<ul>
<li><strong>Инициатор:</strong> клиент.</li>
<li><strong>Срок хранения:</strong> всегда.</li>
<li><strong>Хранение между сессиями:</strong> да.</li>
<li><strong>Связь с доменом:</strong> нет.</li>
<li><strong>Отправка на сервер с каждым HTTP-запросом:</strong> нет.</li>
<li><strong>Емкость, на один домен:</strong> 5 МБ.</li>
<li><strong>Доступность:</strong> в любом окне.</li>
</ul>
<h3>Session Storage</h3>
<ul>
<li><strong>Инициатор:</strong> клиент.</li>
<li><strong>Срок хранения:</strong> до закрытия вкладки.</li>
<li><strong>Хранение между сессиями:</strong> нет.</li>
<li><strong>Связь с доменом:</strong> нет.</li>
<li><strong>Отправка на сервер с каждым HTTP-запросом:</strong> нет.</li>
<li><strong>Емкость, на один домен:</strong> 5 МБ.</li>
<li><strong>Доступность:</strong> в той же вкладке.</li>
</ul>
<h3>Ссылки</h3>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies">https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies</a></li>
<li><a href="http://tutorial.techaltum.com/local-and-session-storage.html">http://tutorial.techaltum.com/local-and-session-storage.html</a></li>
</ul>
<h3>Разница между <code>&lt;script&gt;</code>, <code>&lt;script async&gt;</code> и <code>&lt;script defer&gt;</code></h3>
<ul>
<li><code>&lt;script&gt;</code> — отрисовка HTML блокируется, скрипт извлекается и выполняется немедленно, отрисовка HTML возобновляется после окончания выполнения скрипта.</li>
<li><code>&lt;script async&gt;</code> — скрипт будет извлечен и обработан параллельно с отрисовкой HTML, его выполнение закончится как только это будет возможно (обычно до того, как закончилась отрисовка HTML). Используйте <code>async</code> тогда, когда скрипт не зависит от других скриптов на странице, например для аналитики.</li>
<li><code>&lt;script defer&gt;</code> — скрипт будет извлечен параллельно с отрисовкой HTML, его выполнение произойдет после того, как вся страница будет загружена. Если таких скриптов несколько, то каждый из них будет исполнятся в том порядке, в котором они расположены в документе. Если скрипту нужен полностью распарсеный DOM, то атрибут <code>defer</code> обеспечит уверенность в том, что на момент отработки скрипта весь HTML отрисован. Нет особой разницы со скриптами, расположенными перед тегом <code>&lt;body&gt;</code>. Отложенный скрипт не должен содержать <code>document.write</code>.</li>
</ul>
<p><strong>Примечание:</strong> Атрибуты <code>async</code> и <code>defer</code> игнорируются, если у тега <code>&lt;script&gt;</code> нет атрибута <code>src</code>.</p>
<p><strong>Ссылки</strong></p>
<ul>
<li><a href="http://www.growingwiththeweb.com/2014/02/async-vs-defer-attributes.html">Async vs defer attributes</a>.</li>
<li><a href="https://stackoverflow.com/questions/10808109/script-tag-async-defer">Stack Overflow: Script Tag — async &amp; defer</a>.</li>
<li><a href="https://bitsofco.de/async-vs-defer/">Asynchronous vs Deferred JavaScript</a>.</li>
</ul>
<h3>Почему хорошей практикой считается располагать <code>&lt;link&gt;</code> для подключения CSS между <code>&lt;head&gt;&lt;/head&gt;</code>, а <code>&lt;script&gt;</code> для подключения JS ставить перед <code>&lt;/body&gt;</code>? Знаете ли вы исключения?</h3>
<h3>Размещение <code>&lt;link&gt;</code> внутри <code>&lt;head&gt;</code></h3>
<p>Необходимость помещать теги <code>&lt;link&gt;</code> внутри шапки сайта описана в спецификации. Кроме того, размещение в верхней части разметки позволяет загружать страницу постепенно, что хорошо отражается на опыте использования. Проблема, возникающая при размещении таблиц стилей в нижней части страницы, заключается в том, что этот порядок препятствует прогрессивной загрузке страницы во многих браузерах. В том числе в Internet Explorer. Некоторые браузеры блокируют загрузку страницы, чтобы избежать перерисовки элемента, если его стили изменятся. Все это время пользователь будет пялиться на белый экран. Такое поведение браузеров предотвращает мерцание или отрисовку нестилизованых элементов.</p>
<h3>Размещение <code>&lt;script&gt;</code> прямо перед <code>&lt;/body&gt;</code></h3>
<p>Теги <code>&lt;script&gt;</code> блокируют отрисовку HTML на то время, пока они скачиваются и исполняются. Скачивание скриптов в конце позволяет сперва распарсить и показать пользователю весь HTML.</p>
<p>Исключением является случай, когда в вашем скрипте содержится <code>document.write()</code>. Но на сегодняшний день его использование не считается хорошей практикой. К тому же, расположение скриптов внизу разметки означает, что браузер не может начать их скачивать до тех пор, пока не отрисован весь документ. Единственным рабочим способом, при котором <code>&lt;script&gt;</code> будет расположен внутри <code>&lt;head&gt;</code>, является добавление атрибута <code>defer</code>.</p>
<h3>Ссылки</h3>
<ul>
<li><a href="https://developer.yahoo.com/performance/rules.html#css_top">Yahoo: Put Stylesheets at the Top</a>.</li>
</ul>
<h3>Что такое прогрессивный рендеринг?</h3>
<p>Прогрессивный рендеринг — имя, данное технологиям, используемым для ускорения отрисовки страниц (в частности, уменьшение времени загрузки), чтобы показать пользователю контент как можно скорее.</p>
<p>До того, как широкополосный интернет распространился повсеместно, прогрессивный рендеринг встречался довольно часто. Но этот подход по прежнему полезен в современной разработке, поскольку все более популярным (и ненадежным) становится мобильный доступ в интернет.</p>
<p>Примеры технологий:</p>
<ul>
<li><strong>Ленивая загрузка картинок.</strong> Картинки на странице не загружаются все разом. JavaScript подгрузит картинки тогда, когда пользователь доскроллит до той части страницы, на которой они расположены.</li>
<li><strong>Приоритизация видимого контента.</strong> Только минимум CSS, контента, скриптов, необходимых для отрисовки той части страницы, которую пользователь увидит первой. Вы можете использовать отложенные скрипты или слушать события <code>DOMContentLoaded</code> или <code>load</code>, чтобы загрузить остальные ресурсы и контент.</li>
<li><strong>Асинхронные фрагменты HTML.</strong> Отправка в браузер частей HTML-страницы, созданной на бэкенде. Более подробно про эту технологию можно почитать <a href="http://www.ebaytechblog.com/2014/12/08/async-fragments-rediscovering-progressive-html-rendering-with-marko/">в этой статье</a>.</li>
</ul>
<h3>Ссылки</h3>
<ul>
<li><a href="https://stackoverflow.com/questions/33651166/what-is-progressive-rendering">Stack Overflow: What is progressive rendering?</a></li>
<li><a href="http://www.ebaytechblog.com/2014/12/08/async-fragments-rediscovering-progressive-html-rendering-with-marko/">Async Fragments: Rediscovering Progressive HTML Rendering with Marko</a>.</li>
</ul>
<h3>Приходилось ли вам работать с языками HTML-шаблонизации?</h3>
<p>Да, Pug (ранее известный как Jade), ERB, Slim, Handlebars, Jinja, Liquid и это только некоторые из них. По моему мнению, все они более или менее одинаковые и предоставляют одинаковые возможности экранирования контента и полезных фильтров для работы с отображаемыми данными. Большинство движков позволяют вводить собственные фильтры, если вам требуется дополнительная обработка контента перед его отображением.</p>
<h2>Другие ответы</h2>
<ul>
<li><a href="https://neal.codes/blog/front-end-interview-questions-html/">Front End Interview Questions HTML</a>.</li>
<li><a href="http://peterdoes.it/2015/12/03/a-personal-exercise-front-end-job-interview-questions-and-my-answers-all/">The Definitive «Front-End Developer Job Interview Questions» And My Answers</a>.</li>
</ul>

                    ]]></description><pubDate>Wed, 21 Feb 2018 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/front-end-interview-handbook/</guid></item><item><title>О доступности интерфейсов из первых уст</title><link>https://web-standards.ru/articles/a11y-users-interview/</link><description><![CDATA[
                        <p>Уже не первый год мы в «Студии 15» занимаемся доступностью сайтов для людей с ограниченными возможностями. Чтобы делать эту работу качественнее и быстрее, разрабатываем опенсорсную <a href="https://github.com/15web/web-accessibility">библиотеку интерфейсных решений</a>.</p>
<p>Недавно мы <a href="http://www.15web.ru/blog/duma_accessibility">рассказывали о запуске</a> специальной версии сайта Законодательной Думы Томской области. Сайт мы тестировали на реальной целевой аудитории: позвали супругов Максима и Ольгу.</p>
<p>Максим потерял зрение в последнем классе школы из-за осложнения после болезни. Он кандидат психологических наук, доцент, преподает в <a href="https://tusur.ru/">ТУСУРе</a> психологию и занимается социальной работой с людьми, имеющими ограниченные возможности здоровья.</p>
<p>Ольга частично потеряла зрение уже будучи взрослой. Она может различать только некоторые силуэты и отличать свет от темноты. Занимается частной психологической практикой.</p>
<p>Мы взяли у Максима с Ольгой интервью и расспросили об их опыте пользования сайтами.</p>
<p><strong>Как вы работаете с компьютером, какие программы используете, какие бывают проблемы с ними?</strong></p>
<p><strong>Ольга:</strong> Я научилась работать на компьютере еще когда была зрячая, то есть на момент потери зрения уже была знакома с основными принципами взаимодействия с ПК. После потери зрения пришлось осваивать специальные инструменты — скринридеры. Самой удобной на тот момент была программа JAWS для Windows. Мне ее установил один слепой парень-программист и научил пользоваться. Да и вообще он меня многому научил, прежде чем я сама смогла уверенно использовать компьютер. Сложнее всего было научиться работать используя только клавиатуру. Слепые не могут нормально использовать мышь и все основные команды подают через неё. Также приходилось заново учиться пользоваться ранее знакомым интерфейсом. Если раньше можно было просто кликнуть на иконку печати и готово — документ уходил в печать, то теперь это уже было невозможно.</p>
<p><strong>Максим:</strong> Особенность восприятия тех, кто работает со скринридерами в том, что здесь идет последовательная передача информации. Те, кто пользуется и зрением, и слухом могут быстро охватить всю информацию, а те, кто используют скринридер, должны пройти полностью всю страницу, чтобы найти то, что их интересует.</p>
<p><strong>Скринридеры сильно отличаются друг от друга? Когда мы проводили тестирование, то столкнулись с различиями.</strong></p>
<p><strong>Максим:</strong> Мы хорошо знакомы только с двумя скринридерами — JAWS и NVDA. В основном используем JAWS, он платный, но имеет больше функциональных возможностей. NVDA мне нужен чаще всего, если на компьютере нет JAWS или он сбоит и необходимо наладить его работу.</p>
<figure>
    <img src="https://web-standards.ru/articles/a11y-users-interview/images/screen-readers.jpg" alt="Популярные программы для чтения с экрана.">
    <figcaption>Сравнение платного JAWS и бесплатного NVDA. У обоих скринридеров есть речевой доступ. Плюсы NVDA: портативность и многоголосность. Плюсы JAWS: простота и многоязычность.</figcaption>
</figure>
<p><strong>Какая версия JAWS у вас сейчас установлена?</strong></p>
<p><strong>Максим:</strong> Мы используем 15 версию, установленную под Windows 7 на одном компьютере и 11 версию под Windows XP на другом.</p>
<p><strong>Каким браузером удобнее пользоваться при работе со скринридером?</strong></p>
<p><strong>Ольга:</strong> В основном, Internet Explorer. Opera не озвучивается, а Chrome в большей степени графический браузер. И вот эту графику скринридеры не озвучивают. Когда я устанавливал Chrome, он у меня вообще молчал.</p>
<p><strong>Мы тестировали оба скринридера на Chrome, и они читали всю необходимую информацию.</strong></p>
<p><strong>Максим:</strong> Видимо сейчас что-то изменилось. Когда я попробовал его использовать, у меня это не получилось и на этом мы оставили попытки.</p>
<p><em>Сегодня все основные браузеры корректно работают со скринридерами. В том числе Chrome, Opera, Internet Explorer и Firefox — прим. автора.</em></p>
<p><strong>Какая конкретно версия Internet Explorer у вас установлена?</strong></p>
<p><strong>Максим:</strong> Мы используем 10 версию. А ещё на нашем ноутбуке установлена лицензионная операционная система и мы не стали её обновлять до Windows 10. Причина в том, что уже привыкли к этой версии и заново переучиваться на новую слишком трудозатратно. Кстати, важная для нас проблема, связанная с современными программными продуктами. Каждый раз, когда происходит обновление до новой версии, все старые функции меняют своё местоположение и приходится заново запоминать, где они находятся. Люди вообще склонны выполнять привычные действия.</p>
<p><strong>С какими проблемами вы часто сталкиваете на сайтах?</strong></p>
<p><strong>Максим:</strong> Основная проблема — это графика, которую скринридеры не воспроизводят. Например, есть графическая иконка без подписи и мне абсолютно непонятно что она означает. На портале <a href="https://tusur.ru/">ТУСУРа</a>, чтобы войти в личный кабинет, нужно кликнуть на графическое изображение этой ссылки без каких-либо буквенных пояснений. Я с трудом нашёл эту иконку методом тыка. Ещё большой проблемой для слабовидящих, работающих без присутствия зрячих, являются капчи, где для обеспечения безопасности пользователю необходимо ввести код с картинки в специальное поле. Раньше был сервис «Web of Visio», куда можно было отправлять отсканированную капчу и результат сразу копировался в буфер обмена. С его помощью работа с капчами была возможна, хоть и не всегда — сильно зашумленные картинки могли не распознаваться.</p>
<p><strong>Ольга:</strong> Ещё мы сталкиваемся со сложностями при работе с выпадающими списками. Например, когда нужно выбрать правильную дату рождения, скринридер не озвучивает пункты списка и нет никакой возможности по нему ориентироваться. Помимо этого, большим неудобством может стать автоматическое обновление страницы, при котором фокус скринридера меняет своё местоположение. Бывает сложно сориентироваться когда ты выполняешь какую-то деятельность в одной точке страницы, а потом внезапно фокус переносится в другую. Могу добавить, что для слабовидящих очень важно иметь специальную навигацию на странице, иначе очень усложняется работа с сайтом. Например, в социальной сети «Одноклассники» на страницах вообще нет заголовков. Чтобы дойти до нужной части страницы — необходимо пройти курсором всю страницу, это требует много времени.</p>
<p><strong>Максим:</strong> Я часто сталкиваюсь с некорректным воспроизведением таблиц, расположенных на страницах сайта. Скринридер читает таблицу по HTML-коду: сначала название столбцов, потом в этом же порядке ячейки в строках. Например, при чтении таблицы с расписанием пар в университете сперва читаются все дни недели и только потом все пары. Непонятно что к чему относится. Нужно это учитывать при помещении таблиц на страницу сайта.</p>
<p><strong>Кроме полной потери зрения, бывает неполная, какие-то патологии. Какие проблемы есть у таких пользователей?</strong></p>
<p><strong>Ольга:</strong> Важно понимать, что у всех людей, имеющих нарушения зрения, проблемы самые разные. У одних просто снижена острота зрения, у других цветовая аномалия, у третьих суженное зрение. И тут встает вопрос о том, как покрывать все эти аномалии в рамках одной версии для слабовидящих.</p>
<p><strong>Получается, какое бы нарушение зрения у человека не было, скринридер универсальный инструмент?</strong></p>
<p><strong>Ольга:</strong> Совсем нет, и не потому что скринридер — плохой инструмент. Понимаете, пока человек имеет хоть какое-то остаточное зрение, он не станет пользоваться скринридером точно также как не станет ходить с тростью. Он будет всегда ориентироваться на то зрение, что у него осталось. Даже вот у меня есть какой-то остаток и я всю жизнь пытаюсь им что-то увидеть. Я не ориентируюсь, как полностью слепые, тотально только на слух. Большая часть слабовидящих людей вместо использования скринридера будут увеличивать, растягивать экран, делать все что угодно, но пользоваться глазами, насколько это возможно. Тут психологический момент срабатывает, что использование скринридера — это все равно что быть слепым и ходить с тростью.</p>
<p><strong>В Томске существуют организации, где незрячих людей учат пользоваться компьютерами?</strong></p>
<p><strong>Ольга:</strong> В Пушкинской библиотеке есть отдел «Обслуживания инвалидов по зрению». У них там были специальные курсы, обучающие слепых работе на компьютере. Есть Бийский центр реабилитации, у них там специальные курсы, как профессиональная реабилитация. А вообще очень многие люди самоучки, каждый приспосабливается сам, как может. Часто им в этом помогают специальные пособия по работе с компьютером для слепых.</p>
<h3><strong>Заключение</strong></h3>
<p>Общение с реальными людьми, которым предстоит пользоваться нашей работой было очень полезным. Это помогло нам открыть множество нюансов, которых не найти в интернете. Еще более важным оказалось тестирование сайта. Обязательно расскажем про детали этого процесса в следующих материалах.</p>

                    ]]></description><pubDate>Mon, 19 Feb 2018 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/a11y-users-interview/</guid></item><item><title>Роутинг, бандлинг и ленивая загрузка на Webpack</title><link>https://web-standards.ru/articles/multiple-routes-webpack/</link><description><![CDATA[
                        <h2>Обзор проблемы</h2>
<p>Мы с коллегами пишем клиентское приложение на нативном JS, которое разбивается на маршруты. Маршруты — это способ организации структуры сайта. Они как правило привязаны к отдельным URL. Таким образом, в интернет-магазине вы можете иметь различные маршруты, такие как главная, список категорий, страница продукта, и страница оформления заказа, каждый из которых может иметь свой собственный JavaScript. Например, для списка категорий может потребоваться код для обработки фильтрации, а для страницы оформления заказа может потребоваться иной код (для валидации формы).</p>
<p>Однако мы не создаем полностью клиентское приложение. Пользователь может попасть на сайт через различные страницы, например, ввести URL в адресную строку и загрузить главную страницу, или найти интересный продукт в поисковике и перейти прямо на страницу описания этого продукта. Поэтому каждый из этих маршрутов должен иметь свою собственную структуру URL:</p>
<pre><code tabindex="0">Home: /
Category listing: /category/42
Product page: /product/1234
Checkout: /checkout
</code></pre>
<p>Кроме того, нам не нужно загружать весь JavaScript для всех маршрутов, когда сайт впервые открыт. В идеале, нам достаточно кода для того маршрута, который был открыт, а остальное можно подгружать, когда пользователь переходит на другие страницы сайта. Но если вы делаете единый бандл, то вы получите один массивный файл, который должен быть полностью загружен и проанализирован, прежде чем что-либо произойдет:</p>
<pre><code tabindex="0">dist/
    big-bundle.js
</code></pre>
<p>Это не лучшее решение, ведь подгружается много JavaScript, который не используется. При медленном подключении это может означать, что пользователю придется подождать довольно долгое время, прежде чем начать взаимодействовать со страницей. Вот почему большие бандлы являются антипаттерном.</p>
<p>Вот где нам поможет концепция <strong>точки входа.</strong> Вместо того, чтобы иметь единый главный файл, мы решили иметь точку входа для каждого маршрута, при этом оставшиеся маршруты лениво загружаются по мере необходимости. В конечном итоге мы получим следующее:</p>
<pre><code tabindex="0">dist/
    bundle-home.js
    bundle-category.js
    bundle-product.js
    bundle-checkout.js
    home.js
    category.js
    product.js
    checkout.js
</code></pre>
<p>В зависимости от маршрута, будет загружен только один из файлов пакета. Этот файл будет лениво загружать другие модули, по мере необходимости. И если пользователь открыл продукт, то загрузится только bundle-product.js, необходимый для текущей страницы , а затем лениво подгрузятся home.js, category.js и checkout.js, если это необходимо.</p>
<p>Это решение позволяет загружать весь необходимый код для выполнения немедленного запроса пользователя и только при необходимости загружать остальной. И хотя это и включает в себя один и тот же код в нескольких файлах, фактически пользователь должен загружать только одну версию каждого модуля при навигации по сайту.</p>
<h2>Webpack и модули</h2>
<p>Существует миф, что Webpack трудно сконфигурировать, чтобы использовать его в большинстве ситуаций. Я использую Webpack недавно, и не могу судить насколько было правдиво это утверждение в прошлом, но оказывается, что сейчас Webpack довольно прост в использовании! Если вы знакомы с ES-модулями, статическим и динамическим импортом, вы в значительной степени готовы к созданию некоторых сложных приложений с хорошей практикой загрузки и без особых навыков конфигурирования.</p>
<p>Webpack понимает стандартизированный синтаксис ES-модулей, а также статическую и динамическую загрузку модулей. Он смотрит на ваш код и решает, как разделить его на пакеты в зависимости от того, какой тип импорта вы делаете. Таким образом, если вы загружаете модуль статически, он будет в бандле:</p>
<pre><code tabindex="0" class="language-js">import Foo from './foo.js';
Foo.doSomething();
</code></pre>
<p>Приведенные выше результаты соберутся в один файл bundle.js со всем кодом. Если вместо этого вы загружаете его динамически, то Webpack будет создавать отдельный бандл и получать дополнительный код по требованию:</p>
<pre><code tabindex="0" class="language-js">import('./foo.js').then(module =&gt; {
    module.doSomething();
});
</code></pre>
<p>Приведенные выше результаты соберутся в bundle.js с некоторой магической ленивой загрузкой, и файл 0.js с кодом для <code>foo</code>. 0.js называется «чанком» в терминологии, предназначенной для ленивой загрузки другим кодом Webpack, и не обрабатывается непосредственно собственным кодом.</p>
<h2>Множественные точки входа и роутинг</h2>
<p>Хорошо, похоже, что все готово! Вы можете написать стандартизированный код модуля, который будет работать как изначально в браузере (если ваш браузер уже добавил поддержку), так и через некоторую магию в Webpack.</p>
<p>Итак, давайте напишем код и посмотрим, как это на самом деле выглядит. Я вернусь к исходному примеру и использую маршруты для страниц категории, продукта, оформления заказа и главной, а также соответствующую точку входа для каждого из них.</p>
<p>Модули довольно просты. Вы можете создать один метод с любым именем, так как содержание не имеет значения для примера:</p>
<pre><code tabindex="0" class="language-js">export function printMessage() {
    console.log('Hi! This is the Checkout module.');
}
</code></pre>
<p>Каждый файл точки входа будет загружать соответствующий модуль статически, а остальные модули динамически. Чтобы избежать дублирования кода, вам нужно создать простой, последовательный интерфейс для определения всего остального. Вот пример, который я называю <code>Router</code>.</p>
<p>*<strong>Примечание:</strong> хоть этот пример и не будет делать какие-либо фактические маршрутизации, в традиционном смысле, он будет определять какой код будет загружаться по маршруту. Так что я все еще думаю, что это нормальное имя:)</p>
<pre><code tabindex="0" class="language-js">const _modules = {
    Home: import('./home.js'),
    Category: import('./category.js'),
    Product: import('./product.js'),
    Checkout: import('./checkout.js'),
};

export default class Router {
    static get modules() {
        return _modules;
    }
}
</code></pre>
<p>Каждый модуль хранится как запись в объекте <code>_modules</code>, причем значение является промисом загрузки, возвращаемым <code>import()</code>. Обратите внимание, что это немедленно начнет загружать другие маршруты, которые вам, возможно, не понадобятся, но в этом случае вы всегда можете отложить загрузку явным вызовом, который вызовет фактический импорт. Например:</p>
<pre><code tabindex="0" class="language-js">const _modules = {
    Home: () =&gt; import('./home.js'),
    Category: () =&gt; import('./category.js'),
    Product: () =&gt; import('./product.js'),
    Checkout: () =&gt; import('./checkout.js'),
};

export default class Router {
    static get modules() {
        return _modules;
    }
}
</code></pre>
<p>Но если все четыре модуля загружаются лениво, как избежать дублирования кода в точке входа? Безусловно, один из них должен быть <code>null</code>?</p>
<p>На самом деле в этом нет необходимости! Браузер (с поддержкой модулей) и Webpack делают дедупликацию, где они отслеживают, какие модули были загружены, чтобы они не загружались дважды. Таким образом, вам, как разработчику, не нужно беспокоиться о том, чтобы отслеживать что-либо из этого. Это делает точку входа действительно чистой и легкой для понимания:</p>
<pre><code tabindex="0" class="language-js">import * as Home from './home.js';
import Router from './router.js';

console.log('Hello from the Home entry point!');

Router.modules.Home().then(module =&gt; module.printMessage());
Router.modules.Category().then(module =&gt; module.printMessage());
Router.modules.Product().then(module =&gt; module.printMessage());
Router.modules.Checkout().then(module =&gt; module.printMessage());
</code></pre>
<p>Главная загружается статически, так что она сразу же доступна и не требует ожидания ленивой загрузки. <code>Router</code> также загружается статически. После того, как вы сделаете запрос к <code>Router</code>, он проверяет, был ли загружен этот модуль либо статически, либо динамически. Если он уже был загружен, промис немедленно разрешается. Если этого не произошло, он извлекает файл, загружает код и продолжает цепочку промисов после этого.</p>
<p>Это означает, что все модули могут использоваться точно так же, входным модулем или, если это было реальное приложение с надлежащей маршрутизацией URL, самим <code>Router</code>.</p>
<h2>Как мне заставить это работать в Webpack?</h2>
<p>Как я упоминал ранее, все это должно работать в браузере без изменений, если ваш браузер поддерживает ES-модули и их загрузку. Вы не получите преимущества от сборки без некоторой дальнейшей работы, но код все еще функционален.</p>
<p>Тем не менее, поскольку большинство из нас все еще собирают в бандл, нам нужно найти способ заставить это работать с Webpack. И оказывается все очень просто! Вот файл webpack.config.js:</p>
<pre><code tabindex="0" class="language-js">const path = require('path');
module.exports = {
    entry: {
        'bundle-home': './entry-home.js',
        'bundle-category': './entry-category.js',
        'bundle-product': './entry-product.js',
        'bundle-checkout': './entry-checkout.js',
    },
    output: {
        path: path.resolve('./dist'),
        filename: '[name].js',
    }
}
</code></pre>
<p>Главное здесь — убедиться, что все точки входа являются частью одного формирования, чтобы создать только один набор динамически загружаемых блоков. Выходное имя файла основано на ключе для каждой точки входа, благодаря использованию <code>[name]</code>, поэтому вывод выглядит следующим образом:</p>
<pre><code tabindex="0">dist/
    0.js
    1.js
    2.js
    3.js
    bundle-category.js
    bundle-checkout.js
    bundle-home.js
    bundle-product.js
</code></pre>
<p>Числовые файлы включают в себя модуль, завернутый в JSONP. Собранные файлы имеют код для этой точки входа, связанный с кодом <code>Router</code> и некоторыми методами, обрабатывающими отложенную загрузку.</p>
<p>Если вы загрузите файл bundle-checkout.js на тестовой странице и посмотрите на отладчик Chrome, вы можете убедится, что загружаются только три чанка, как и ожидалось. Вы также можете увидеть, что сообщение из модуля <code>Checkout</code> поступает из bundle-checkout.js, что означает, что модуль <code>Checkout</code> попал в этот файл.</p>
<img src="https://web-standards.ru/articles/multiple-routes-webpack/images/1.png" alt="">
<h2>Выводы и предостережения</h2>
<p>Обратите внимание, что этот пример довольно прост, и реальное приложение будет иметь дополнительные требования, такие как необходимость использовать <code>CommonsChunkPlugin</code> для более легкого разделения битов кода, или, возможно, некоторые потенциальные сложности, возникающих при использовании npm-модулей в качестве зависимостей. Это, вероятно, потребует дополнительной работы над конфигурацией Webpack.</p>
<p>Тем не менее, я чувствовал, что это интересная проблема и достаточно достойное решение, чтобы поделиться, поэтому я надеюсь, что вы найдете его полезным! Особая благодарность Сурме и Сэму Даттону за помощь в поисках более чистого решения!</p>

                    ]]></description><pubDate>Fri, 09 Feb 2018 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/multiple-routes-webpack/</guid></item><item><title>Промисы на примерах из жизни</title><link>https://web-standards.ru/articles/promises-explained/</link><description><![CDATA[
                        <figure>
    <img src="https://web-standards.ru/articles/promises-explained/images/1.jpg" alt="">
    <figcaption>
        Ай промис, фото <a href="https://unsplash.com/photos/tX4-tYibILg">Бена Уайта</a>.
    </figcaption>
</figure>
<h2>Промисы простыми словами</h2>
<p>Представьте это как разговор между двумя людьми:</p>
<blockquote>
<p><strong>Алекс</strong>: Эй, мистер Промис! Можешь сбегать в магазин и принести мне <code>itemA</code> для блюда, которое мы приготовим сегодня вечером?
<strong>Промис</strong>: Отличная мысль!
<strong>Алекс</strong>: Пока ты бегаешь, я подготовлю <code>itemB</code> (асинхронная операция). Только обязательно скажи, нашел ли ты <code>itemA</code> (возвращаемое промисом значение).
<strong>Промис:</strong> А если тебя не будет дома, когда я вернусь?
<strong>Алекс:</strong> Тогда отправь мне смску, что ты вернулся и принес <code>item</code> для меня (успешный колбэк). Если ты не найдешь, позвони мне немедленно (неуспешный колбэк).
<strong>Промис:</strong> Отлично! Увидимся позже!</p>
</blockquote>
<p>Проще говоря, объект <code>promise</code> — это данные, возвращаемые асинхронной функцией. Это может быть <code>resolve</code>, если функция прошла успешно или <code>reject</code>, если функция вернула ошибку.</p>
<h2>Определение</h2>
<p>Промис — это объект, представляющий окончательное завершение или сбой асинхронной операции. По сути, промис — это возвращаемый объект, к которому прикрепляется колбэк, вместо его передачи в функцию.</p>
<h2>Промис в JS</h2>
<p>Давайте сначала поговорим о JavaScript и его параллелизме. JavaScript является однопоточным. Всё происходит в той последовательности, в которой написано, но асинхронные операции происходят в порядке их завершения.</p>
<p>Что, по вашему мнению, выведется в консоль в следующем примере?</p>
<pre><code tabindex="0" class="language-js">console.log('1');
setTimeout(function(){ console.log('2'); }, 3000);

console.log('3');
setTimeout(function(){ console.log('4'); }, 1000);
</code></pre>
<p>Результатом будет 1 3 4 2. Вы можете задаться вопросом, почему 4 встречается раньше чем 2. Причина в том, что, несмотря на то, что строка с 2 описана раньше, она начала выполняться только после 3000 мс, поэтому 4 выводится до 2.</p>
<p>В типичном веб-приложении может выполняться множество асинхронных операций, таких как загрузка изображений, получение данных из JSON, обращение к API и других.</p>
<p>Теперь рассмотрим, как создать промис в JavaScript:</p>
<pre><code tabindex="0" class="language-js">var promise = new Promise(function(resolve, reject) {
    // Делаем, что-то, возможно асинхронное, тогда…

    if (/* Всё прошло отлично */) {
        resolve('Сработало!');
    }
    else {
        reject(Error('Сломалось'));
    }
});
</code></pre>
<p>Конструктор <code>Promise</code> принимает один аргумент: колбэк с двумя параметрами — <code>resolve</code> и <code>reject</code>. Этот промис может быть использован следующим образом:</p>
<pre><code tabindex="0" class="language-js">promise.then(function(result) {
    console.log('Промис сработал');
}, function(err) {
    console.log('Что-то сломалось');
});
</code></pre>
<p>Если промис прошёл успешно, будет выполнен <code>resolve</code>, и консоль выведет Промис сработал, в противном случае выведется Что-то сломалось. Это состояние до получения <code>resolve</code> или <code>reject</code> называется состоянием ожидания, <code>pending</code>. Таким образом, есть три состояния промиса:</p>
<ol>
<li>Ожидание ответа: <code>pending</code>.</li>
<li>Успешное выполнение: <code>resolve</code>.</li>
<li>Выход ошибкой: <code>reject</code>.</li>
</ol>
<figure>
    <img src="https://web-standards.ru/articles/promises-explained/images/2.jpg" alt="">
    <figcaption>
        Промис успешно выполнился, <a href="https://www.pexels.com/photo/man-couple-love-people-136402/">фото Скотта Вебба</a>.
    </figcaption>
</figure>
<h2>Пример</h2>
<p>Чтобы полностью понять концепцию промисов, создадим приложение, которое загрузит изображение. Если изображение загружено, оно будет отображено, иначе будет выводится ошибка.</p>
<p>Сначала создадим промис с <code>XMLHttpRequest</code>:</p>
<pre><code tabindex="0" class="language-js">const loadImage = url =&gt; {
    return new Promise(function(resolve, reject) {
        // Открываем новый XHR
        var request = new XMLHttpRequest();
        request.open('GET', url);

        // После загрузки запроса
        // проверяем, был ли он успешным
        request.onload = function() {
            if (request.status === 200) {
                // Если успешный, то резолвим промис
                resolve(request.response);
            } else {
                // Если нет, то реджектим промис
                reject(Error(
                    'Произошла ошибка. Код ошибки:' + request.statusText
                ));
            }
        };

        request.send();
    });
};
</code></pre>
<p>Теперь, когда изображение успешно загружено, промис вернет <code>resolve</code> с ответом от XHR. Давайте используем этот промис, вызвав функцию <code>loadImage</code>.</p>
<pre><code tabindex="0" class="language-js">const embedImage = url =&gt; {
    loadImage(url).then(function(result) {
        const img = new Image();
        var imageURL = window.URL.createObjectURL(result);
        img.src = imageURL;
        document.querySelector('body').appendChild(img);
    },
    function(err) {
        console.log(err);
    });
}
</code></pre>
<p>Мы сделали это! Неплохо, да?</p>
<p>А теперь сделай несколько промисов сам! Давай :)</p>
<h3>Дополнительные материалы</h3>
<p>Вот некоторые статьи, которые показались мне очень полезным в процессе обучения:</p>
<ol>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises">Статья про промисы на MDN</a></li>
<li><a href="http://https//developers.google.com/web/fundamentals/primers/promises">Введение на Google Developers</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#Run-to-completion">Про concurrency model на MDN</a></li>
</ol>

                    ]]></description><pubDate>Tue, 05 Dec 2017 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/promises-explained/</guid></item><item><title>Сколько стоит JavaScript?</title><link>https://web-standards.ru/articles/the-cost-of-javascript/</link><description><![CDATA[
                        <p>По мере того, как мы создаём сайты, всё более и более зависящие от JavaScript, мы иногда теряем производительность. В этой статье я расскажу про соблюдение некоторых правил, которые могут помочь, если вы хотите, чтобы ваш интерактивный сайт быстро загружался на мобильных устройствах.</p>
<p><strong>TL;DR</strong> Меньше кода = меньше парсить и компилировать + меньше передавать + меньше распаковывать</p>
<h3>Сеть</h3>
<p>Когда большинство разработчиков задумываются о стоимости JavaScript, они думают об этом с точки зрения скорости загрузки и выполнения. Отправка большего количества байт JavaScript обходится дороже при медленном подключении пользователя.</p>
<figure>
    <img src="https://web-standards.ru/articles/the-cost-of-javascript/images/1.png" alt="">
</figure>
<p>Это может быть проблемой даже в развитых странах, так как <em>фактическая</em> скорость соединения пользователя далеко не всегда соответствует максимально возможной в 3G, 4G или Wi-Fi. Вы можете быть подключены к сети Wi-Fi в кафе, но скорость её передачи может не достигать даже 2G.</p>
<p>Вы можете снизить затраты на JavaScript следующими способами:</p>
<ul>
<li>Отправляйте только тот код, который нужен пользователю. Разделение кода может вам в этом помочь.</li>
<li><a href="https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/optimize-encoding-and-transfer">Сокращайте код</a> (<a href="https://github.com/mishoo/UglifyJS">uglify</a> для ES5, <a href="https://github.com/babel/minify">babel-minify</a> или <a href="https://www.npmjs.com/package/uglify-es">uglify-es</a> для ES6)</li>
<li><a href="https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/optimize-encoding-and-transfer">Сжимайте сильнее</a> (используйте <a href="https://www.smashingmagazine.com/2016/10/next-generation-server-compression-with-brotli/">Brotli</a>, <a href="https://twitter.com/paulcalvano/status/924660429846208514">q11</a>, Zopfli или gzip). Brotli превосходит gzip по степени сжатия. Это помогло CertSimple <a href="https://speakerdeck.com/addyosmani/the-browser-hackers-guide-to-instant-loading?slide=30">сэкономить 17%</a> на размере сжатого JS и LinkedIn <a href="https://engineering.linkedin.com/blog/2017/05/boosting-site-speed-using-brotli-compression">сэкономить 4%</a> на времени загрузки.</li>
<li>Удаляйте неиспользуемый код. Его можно отследить в <a href="https://developers.google.com/web/updates/2017/04/devtools-release-notes">DevTools во вкладке Coverage</a>. Посмотрите в сторону <a href="https://webpack.js.org/guides/tree-shaking/">tree-shaking</a>, <a href="https://developers.google.com/closure/compiler/">Closure Compiler</a> и других библиотек оптимизации библиотек, вроде <a href="https://github.com/lodash/babel-plugin-lodash">lodash-babel-plugin</a> или <a href="https://iamakulov.com/notes/webpack-front-end-size-caching/#moment-js">ContextReplacementPlugin</a> для Webpack — особенно для таких библиотек, как Moment.js. Используйте babel-preset-env и browserlist, чтобы избежать транспиляции новых возможностей, которые уже доступны в современных браузерах. Продвинутые разработчики уже умеют делать подробный <a href="https://github.com/webpack-contrib/webpack-bundle-analyzer">анализ бандлов </a><a href="https://github.com/webpack-contrib/webpack-bundle-analyzer">Webpack</a> для избавления от ненужных зависимостей.</li>
<li><a href="https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching">Кэшируйте</a> , чтобы избежать лишней нагрузки на сеть. Определите оптимальное время жизни для скриптов (max-age) и используйте токены ETag, чтобы избежать загрузки неизменённых данных. Сервис-воркеры позволят сделать ваше приложение более независимым от сети и дадут доступ к <a href="https://v8project.blogspot.com/2015/07/code-caching.html">кэшированию кода в V8</a>. Узнайте о долгосрочном кэшировании — <a href="https://webpack.js.org/guides/caching/">хэширование имени файла</a>.</li>
</ul>
<figure>
    <img src="https://web-standards.ru/articles/the-cost-of-javascript/images/2.png" alt="">
</figure>
<h3>Парсинг и компиляция</h3>
<p>После загрузки, JS-движок тратит уйму времени на парсинг и компиляцию кода. В Chrome DevTools время парсинга и компиляции показаны желтым в панели Perfomance.</p>
<figure>
    <img src="https://web-standards.ru/articles/the-cost-of-javascript/images/3.png" alt="">
</figure>
<p>Вкладки Bottom-Up и Call Tree позволят оценить время, затраченное на эти задачи:</p>
<figure>
    <img src="https://web-standards.ru/articles/the-cost-of-javascript/images/4.png" alt="Панель Performance в Chrome DevTools: Bottom-Up.">
    <figcaption>Панель Performance в Chrome DevTools: Bottom-Up.</figcaption>
</figure>
<p>Но, почему это имеет значение?</p>
<figure>
    <img src="https://web-standards.ru/articles/the-cost-of-javascript/images/5.png" alt="">
</figure>
<p>Затраты на парсинг и компиляцию кода могут значительно задержать процесс взаимодействия пользователя с сайтом. Чем больше JavaScript вы отправите, тем больше времени потребуется, чтобы проанализировать и скомпилировать его, а ведь только после этого ваш сайт станет интерактивным.</p>
<figure>
    <img src="https://web-standards.ru/articles/the-cost-of-javascript/images/6.png" alt="">
</figure>
<blockquote>
<p>Байт за байтом, JavaScript является более ресурсозатратным для браузера, чем изображения или шрифты того же размера.
Том Дэйл</p>
</blockquote>
<p>Помимо JavaScript, существует множество затрат, связанных с обработкой изображений эквивалентного размера (они все еще должны быть декодированы!), однако с большей вероятностью именно (загрузка и выполнение) JavaScript негативно скажется на интерактивности страницы.</p>
<figure>
    <img src="https://web-standards.ru/articles/the-cost-of-javascript/images/7.png" alt="170 КБ сжатого JS против байтов JPEG на Moto G4 в медленной сети.">
    <figcaption>170 КБ сжатого JS против байтов JPEG на Moto G4 в медленной сети.</figcaption>
</figure>
<p>Байты JavaScript и изображений имеют разную стоимость. Изображения обычно не блокируют основной поток или не препятствуют взаимодействию интерфейсов при декодировании и растеризации. Однако JS может задерживать взаимодействие с сайтом из-за парсинга, компиляции и выполнения.</p>
<p>Когда мы говорим о медленном выполнении парсинга и компиляции, не стоит забывать, что мы рассматриваем среднестатистическое мобильное устройство. Однако многие пользователи могут иметь телефоны с медленными ЦП и графическими процессорами, без кэша L2 или L3 и могут быть ограничены памятью.</p>
<blockquote>
<p>Возможности сети и устройства не всегда совпадают. Пользователь с удивительно быстрым подключением не обязательно имеет лучший процессор для анализа и исполнения JavaScript, отправленного на его устройство. Это также верно и наоборот: ужасное сетевое подключение, но невероятно быстрый процессор.
Кристофер Бакстер, LinkedIn</p>
</blockquote>
<p>При проведении тестов скорости JavaScript, я замерял стоимость парсинга ≈1 МБ обычного (несжатого) JavaScript на медленных и высокопроизводительных устройствах. Среднестатистическое устройство тратит на парсинг и компиляцию кода в 2–5 раз больше времени, чем флагманские устройства.</p>
<figure>
    <img src="https://web-standards.ru/articles/the-cost-of-javascript/images/8.png" alt="Время парсинга для 1Мб JS бандла (≈250KB gzip) на настольных и мобильных устройствах различных классов.">
    <figcaption>Время парсинга для 1Мб JS бандла (≈250KB gzip) на настольных и мобильных устройствах различных классов.</figcaption>
</figure>
<p>А что если мы проверим реально существующий сайт, например <a href="https://cnn.com/">CNN</a>?</p>
<p>iPhone 8 потребуется для этого всего 4 секунды, в то время как для среднего по характеристикам смартфона (в нашем случае, Moto G4), необходимо около 14 секунд. Это существенно повлияет на то, как быстро пользователь сможет полностью взаимодействовать с этим сайтом.</p>
<figure>
    <img src="https://web-standards.ru/articles/the-cost-of-javascript/images/9.png" alt="Сравнение производительности чипа A11 Bionic и Snapdragon 617.">
    <figcaption>Сравнение производительности чипа A11 Bionic и Snapdragon 617.</figcaption>
</figure>
<p>Это подчеркивает важность проведения тестов на среднестатистических устройствах, а не только на тех, которые могут быть у вас в кармане. Однако контекст очень важен. Оптимизируйте JS для тех типов подключений и устройств, на которых нацелен ваш продукт.</p>
<figure>
    <img src="https://web-standards.ru/articles/the-cost-of-javascript/images/10.png" alt="">
</figure>
<p>Аналитика поможет вам определить какой тип мобильных устройств используют посетители вашего сайта. Это позволит вам понять реальные ограничения ЦП и ГП, с которыми они работают.</p>
<p>А мы точно отправляем слишком много JavaScript? Может я ошибся :)</p>
<p>Используем <a href="https://httparchive.org/">HTTP Archive</a> (топ 500 000 сайтов) для анализа. Прежде чем стать интерактивными, 50% сайтов тратят более 14 секунд, из которых около 4 уходит на парсинг и компиляцию кода.</p>
<figure>
    <img src="https://web-standards.ru/articles/the-cost-of-javascript/images/11.png" alt="">
</figure>
<p>Много времени требуется для анализа и обработки JS и других ресурсов, и не удивительно, что пользователям приходится ждать, прежде чем начать взаимодействовать со страницей. Мы определённо можем ускорить этот процесс.</p>
<p>Удаление некритичного JavaScript с ваших страниц может сократить время передачи, анализа, компиляции и потенциальную нагрузку на память. Это также поможет вашим страницам быстрее стать интерактивными.</p>
<h3>Время исполнения</h3>
<p>Это не просто парсинг и компиляция. Исполнение JavaScript (выполнение кода после анализа и компиляции) — одна из операций, которая должна выполняться в основном потоке. Этот процесс так же влияет на то, как скоро пользователь сможет взаимодействовать с сайтом.</p>
<figure>
    <img src="https://web-standards.ru/articles/the-cost-of-javascript/images/12.png" alt="">
</figure>
<blockquote>
<p>Если скрипт исполняется более 50 мс, то время до интерактивности задерживается на всё время, необходимое для загрузки, компиляции, и исполнения.
Алекс Рассел</p>
</blockquote>
<p>Для решения этой проблемы JavaScript разбивается на небольшие фрагменты, чтобы избежать блокировки основного потока. Посмотрим, можно ли сократить объем выполняемой работы во время выполнения.</p>
<h3>Паттерны для уменьшения стоимости JS</h3>
<p>Существуют подходы, которые могут помочь при медленном парсинге, компиляции и невысокой скорости сетевой передачи. Например разбиение фрагментов на основе маршрутов или <a href="https://developers.google.com/web/fundamentals/performance/prpl-pattern/">PRPL</a>.</p>
<p>PRPL — это паттерн, основанный на агрессивном разделении и кэшировании кода.</p>
<figure>
    <img src="https://web-standards.ru/articles/the-cost-of-javascript/images/13.png" alt="">
</figure>
<p>Давайте наглядно посмотрим, насколько это улучшит производительность. Проанализируем время загрузки популярных мобильных сайтов и прогрессивных веб-приложений, используя статистику вызовов V8 в рантайме. Как мы видим, на парсинг (показано оранжевым цветом) браузеры тратят значительную часть времени:</p>
<figure>
    <img src="https://web-standards.ru/articles/the-cost-of-javascript/images/14.png" alt="">
</figure>
<p>Сайту <a href="https://wego.com">Wego</a>, который использует PRPL, удается поддерживать низкое время синтаксического анализа для своих маршрутов, получая интерактивность очень быстро. Многие из вышеперечисленных сайтов сделали акцент на разбиении кода, чтобы попытаться повысить производительность.</p>
<h3>Другие факторы</h3>
<p>JavaScript может влиять на производительность страниц другими способами:</p>
<ul>
<li>Память. Выполнение страниц может приостанавливаться для очистки памяти. Когда браузер освобождает память, выполнение JS приостанавливается, и это может происходить довольно часто, что нам не очень-то понравится. Избегайте утечек памяти и частых пауз GC, чтобы уменьшить притормаживания страниц.</li>
<li>Длительное исполнение JavaScript может блокировать основной поток, из-за чего страницы могут не отвечать на запросы. Разбивка выполнения на мелкие части (с помощью <code>requestAnimationFrame()</code> или <code>requestIdleCallback()</code>) позволяет свести к минимуму такие проблемы.</li>
</ul>
<h3>Прогрессивная загрузка</h3>
<p>Многие сайты оптимизируют видимую часть контента. Для быстрой первоначальной отрисовки, при наличии JS большого размера, разработчики разделяют этот процесс на два этапа: выполняют первоначальный рендер на сервере, а затем подгружают JavaScript.</p>
<p>Будьте осторожны — это тоже требует ресурсов. Как правило, передается более крупный HTML-ответ, который может ограничить вашу интерактивность. Во-вторых, вы частично лишаете пользователя возможности взаимодействия с интерфейсом, пока JavaScript не подгрузится.</p>
<p>Прогрессивная загрузка может быть лучшим подходом. Отправляется минимально функциональная страница (состоящая только из HTML, JS и CSS, необходимых для текущего состояния). По мере поступления дополнительных ресурсов, приложение выполняет отложенную загрузку и разблокирует дополнительные возможности.</p>
<figure>
    <img src="https://web-standards.ru/articles/the-cost-of-javascript/images/15.png" alt="Progressive Bootstrapping.">
    <figcaption>
        <a href="https://twitter.com/aerotwist/status/729712502943174657">
            Progressive Bootstrapping
        </a>, иллюстрация Пола Льюиса.
    </figcaption>
</figure>
<p>Загрузка кода должны быть оптимальной. PRPL и прогрессивная загрузка — это паттерны, которые могут помочь в достижении этой цели.</p>
<h3>Подведём итоги</h3>
<p>Размер передаваемой информации критически важен для сетей с медленной скоростью. Время парсинга важно для устройств, со слабым ГП. Запомните это.</p>
<p>Команды разработчиков добиваются успеха устанавливая для себя жёсткие ограничения производительности (бюджет быстродействия), соблюдение которых заключается в минимизации затрат на передачу, парсинг и компиляцию JS-кода. Читайте «<a href="https://infrequently.org/2017/10/can-you-afford-it-real-world-web-performance-budgets/">Can You Afford It? Real-world Web Performance Budgets</a>» Алекса Рассела для более подробного изучения темы.</p>
<figure>
    <img src="https://web-standards.ru/articles/the-cost-of-javascript/images/16.png" alt="Полезно посмотреть, какой объём выделяется для логики приложения.">
    <figcaption>Полезно посмотреть, какой объём выделяется для логики приложения.</figcaption>
</figure>
<p>Если вы создаете сайт, ориентированный на мобильные устройства, сделайте все возможное для тестирования на нужных типах устройств, держите на низком уровне время парсинга и компиляции JavaScript и утвердите бюджет производительности для того, чтобы команда могла следить за оптимизацией JavaScript.</p>
<h3>Подробнее</h3>
<iframe src="https://www.youtube.com/embed/_srJ7eHS3IM" allowfullscreen></iframe>
<ul>
<li><a href="https://medium.com/reloading/javascript-start-up-performance-69200f43b201">JavaScript Start-up Performance</a></li>
<li><a href="https://nolanlawson.github.io/frontendday-2016/">Solving the web performance crisis</a> Нолана Лоусон</li>
<li><a href="https://infrequently.org/2017/10/can-you-afford-it-real-world-web-performance-budgets/">Can you afford it? Real-world performance budgets</a> Алекса Рассела</li>
<li><a href="https://twitter.com/kristoferbaxter/status/908144931125858304">Evaluating web frameworks and libraries</a> Кристофера Бакстера</li>
<li><a href="https://blog.cloudflare.com/results-experimenting-brotli/">Results of experimenting with Brotli</a>, опыт Cloudflare</li>
<li><a href="https://medium.com/@samccone/performance-futures-bundling-281543d9a0d5">Performance Futures</a>, Сэма Сакконе</li>
</ul>
<p>Отдельное спасибо Нолану Лоусону, Кристоферу Бакстеру и Джереми Вагнеру.</p>

                    ]]></description><pubDate>Tue, 21 Nov 2017 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/the-cost-of-javascript/</guid></item><item><title>Метрики загрузки страницы</title><link>https://web-standards.ru/articles/performance-metrics/</link><description><![CDATA[
                        <p>Что все они означают?</p>
<p>Измерение производительности загрузки страницы — это сложная задача. Чтобы сделать её проще, <a href="https://medium.com/google-developers">инженеры компании Google</a> вместе с сообществом работают над прогрессивными веб-метриками, или ПВМ.</p>
<p>Что такое ПВМ и зачем они нужны? Для начала, давайте сделаем небольшой экскурс в историю браузерных метрик. Некоторое время назад мы имели всего две точки (события) для измерения производительности:</p>
<ol>
<li><code>DOMContentLoaded</code> срабатывает, когда страница загрузилась, но скрипты только начали выполняться.</li>
<li>Событие <code>load</code> срабатывает, когда страница полностью загрузилась и пользователь может полностью с ней взаимодействовать.</li>
</ol>
<p>Если взглянуть на <a href="https://chromedevtools.github.io/timeline-viewer/?loadTimelineFromURL=drive://0ByCYpYcHF12_YjBGUTlJR2gzcHc">трейс сайта Reddit</a> (Chrome DevTools помогает нам, помечая эти точки синей и красной линиями), мы можем увидеть почему эти метрики не так полезны, как хотелось бы.</p>
<figure>
    <img src="https://web-standards.ru/articles/performance-metrics/images/2.png" alt="">
    <figcaption>
        Трейс сайта Reddit.
    </figcaption>
</figure>
<blockquote>
<p>Сегодня мы видим, что window.onload не отражает восприятие пользователя, как это было когда-то. <a href="https://www.stevesouders.com/blog/2013/05/13/moving-beyond-window-onload/">Стивен Саудерс</a></p>
</blockquote>
<p>Конечно. Так же понятны и недостатки <code>DOMContentLoaded</code> во время парсинга и компиляции JavaScript: выполнение этих операций для больших скриптов может занять достаточно много времени. Например, на мобильных устройствах. Упомянутый выше трейс был замерян в условиях эмуляции 3G и показал разницу 10 секунд между <code>DOMContentLoaded</code> и <code>load</code>.</p>
<p>С другой стороны, <code>load</code> сработал очень поздно, чтобы можно было проанализировать узкие места в производительности загрузки страницы. И главный вопрос: как пользователи <em>воспринимают</em> загрузку страницы с момента старта до полной загрузки?</p>
<p>Почему это восприятие так важно? Читайте подробнее <a href="https://developers.google.com/web/updates/2017/06/user-centric-performance-metrics">в статье разработчиков Chrome</a>, где ещё раз подчеркивается проблема <code>load</code>.</p>
<p>Рассмотрим график: ось X показывает время загрузки, ось Y — количество пользователей, которые ожидали какое-то время. Далеко не для всех пользователей загрузка страницы происходит меньше, чем за 2 секунды.</p>
<img src="https://web-standards.ru/articles/performance-metrics/images/3.png" alt="">
<p>Если вернуться к нашему трейсу, то событие load, которое заняло 17 секунд, ничего нам не говорит о том, как пользователь воспринял загрузку страницы. Что он видел все эти 17 секунд: пустую страницу, частично загруженную? Был ли загруженный контент заблокирован, так что пользователь не мог поставить курсор в поле или прокрутить страницу?</p>
<p>Имея ответы на эти вопросы вы можете:</p>
<ol>
<li>Улучшить ощущения от работы сайта;</li>
<li>Привлечь больше пользователей к вашему приложению;</li>
<li>Увеличить выгоду для владельца продукта (пользователи, заказчики, деньги).</li>
</ol>
<p>Поэтому ребята из команды Chrome попробовали прочитать мысли пользователей и предугадать какие же вопросы они себе задают во время загрузки страницы:</p>
<ol>
<li><strong>Что происходит?</strong> Произошла ли навигация успешно;</li>
<li><strong>Это то, что мне нужно?</strong> Отрендерилась ли страница, достаточно ли там контента, с которым можно взаимодействовать;</li>
<li><strong>Уже можно пользоваться?</strong> Можно ли, наконец, взаимодействовать со страницей или она всё ещё занята;</li>
<li><strong>Это восхитительно?</strong> Испытывал ли пользователь негативный опыт зависания скролла, анимация, медленных шрифтов.</li>
</ol>
<p>Если события <code>DOMContentLoaded</code> и <code>load</code> не могут ответить на эти вопросы, то кто может?</p>
<h2>Прогрессивные веб-метрики</h2>
<p>ПВМ — список метрик, цель которых помочь разработчикам определять узкие места в производительности загрузки страницы. В отличие от событий <code>DOMContentLoaded</code> и <code>load</code>, ПВМ предоставляют разработчикам более детальную информацию.</p>
<p>Давайте начнем разбираться с каждой из метрик на примере трейса Reddit, который мы упоминали выше.</p>
<img src="https://web-standards.ru/articles/performance-metrics/images/4.png" alt="">
<h2>Первая отрисовка</h2>
<p>First Paint (FP)</p>
<p>Я немного слукавил, говоря что у нас есть только две метрики. Chrome DevTools также предоставляют ещё одну метрку — первая отрисовка. Это момент когда страница только начала отрисовываться. Говоря другими словами — это время, когда пользователь видит пустую страницу впервые. Подробнее читайте <a href="https://github.com/w3c/paint-timing">в спецификации</a>.</p>
<figure>
    <img src="https://web-standards.ru/articles/performance-metrics/images/5.png" alt="">
    <figcaption>
        Первая отрисовка сайта <a href="https://www.msn.com/">MSN</a>.
    </figcaption>
</figure>
<p>Чтобы понять, как это работает, можно взглянуть на графический слой (GraphicsLayer) в Chromium.</p>
<figure>
    <img src="https://web-standards.ru/articles/performance-metrics/images/6.png" alt="">
    <figcaption>
        Упрощённый графический слой.
    </figcaption>
</figure>
<p>Событие первой отрисовки срабатывает, когда только начался отрисовываться графический слой, но не картинки, SVG или Canvas. Эта метрика давала хоть какую-то информацию и её использовали сполна. Когда она ещё не была стандартизирована, использовались разные техники:</p>
<ul>
<li>Привязывался requestAnimationFrame;</li>
<li>Отлавливались загруженные CSS-ресурсы;</li>
<li>Даже использовались события <code>DOMContentLoaded</code> и <code>load</code>.</li>
</ul>
<p>Но несмотря на все усилия, она имеет низкое значение. Картинки, SVG или Canvas могут быть отрисованы задолго после срабатывания первой отрисовки под влиянием большого размера страницы, CSS- или JS-ресурсов.</p>
<p>Первая отрисовка не считается частью ПВМ, но понимание процесса работы графического слоя поможет в дальнейшем разобраться с остальными метриками. Итак, другие метрики были нужны, чтобы отдавать информации об отрисовке контента.</p>
<h2>Первая отрисовка контента</h2>
<p>First Contentful Paint (FCP)</p>
<p>Это время, когда пользователь видит что-то полезное, отрисованное на странице. То, что отличается от пустой страницы. Это может быть всё, что угодно: первая отрисовка текста, первая отрисовка SVG или первая отрисовка Canvas.</p>
<p>В результате пользователь может задать вопрос: что происходит? Начала ли страница загружаться после того, как я ввел адрес в строке браузера и нажал <kbd>Enter</kbd>?</p>
<figure>
    <img src="https://web-standards.ru/articles/performance-metrics/images/7.png" alt="">
    <figcaption>
        Первая отрисовка и первая полезная отрисовка.
    </figcaption>
</figure>
<p>В Chromium первая отрисовка контента срабатывает во время реальной отрисовки картинок, текста (кроме того, что ждёт загрузки шрифтов) или Canvas. В результате разница между первой отрисовкой и первой отрисовкой контента может занимать от миллисекунд до секунд. Потому иметь метрику которая отображает реальную отрисовку контента очень важно, <a href="https://docs.google.com/document/d/1kKGZO3qlBBVOSZTf-T8BOMETzk3bY15SC-jsMJWv4IE/edit">см. спецификацию</a>.</p>
<h3>Чем она важна для разработчиков?</h3>
<p>Если первая отрисовка контента занимает очень много времени то:</p>
<ul>
<li>Возможно, есть проблемы на уровне соединения;</li>
<li>Ресурсы (например, сама HTML-страница) очень тяжелые и чтобы их доставить нужно время.</li>
</ul>
<p>Чтобы уменьшить влияние этих факторов, читайте «<a href="https://hpbn.co/">High Performance Browser Networking</a>» Ильи Григорика о сетевой производительности.</p>
<h2>Первая значимая отрисовка</h2>
<p>First Meaningful Paint (FMP)</p>
<p>Это время, когда весь главный контент появился на странице. В результате пользователь может оценить: это то, что мне нужно?</p>
<figure>
    <img src="https://web-standards.ru/articles/performance-metrics/images/8.png" alt="">
    <figcaption>
        Первая отрисовка, первая отрисовка контента, первая значимая отрисовка.
    </figcaption>
</figure>
<p>Что считается главным контентом? Когда показаны:</p>
<ul>
<li>Шапка и текст блога;</li>
<li>Содержимое для поисковиков;</li>
<li>Критичные для интернет-магазинов картинки.</li>
</ul>
<p>Не считаются главным контентом:</p>
<ul>
<li>Спиннер или что-то подобное;</li>
<li>Невидимый текст или FOUC;</li>
<li>Только навигация.</li>
</ul>
<p>Первая значимая отрисовка — это отрисовка, которая следует за большим изменением раскладки. В реализации Chromium, отрисовка рассчитывается с использованием <a href="https://code.google.com/p/chromium/codesearch#chromium/src/third_party/WebKit/Source/core/layout/LayoutAnalyzer.h&amp;sq=package:chromium&amp;type=cs">LayoutAnalyzer</a>, который запоминает все изменения раскладки и находит время того изменения, которое повлияло больше остальных. Это и будет первая значимая отрисовка, <a href="https://docs.google.com/document/d/1BR94tJdZLsin5poeet0XoTW60M0SjvOJQttKT-JK8HI/edit">см. спецификацию</a>.</p>
<h3>Чем она важна для разработчиков?</h3>
<p>Если главный контент не был отрисован долгое время, значит очень много ресурсов (картинки, стили, шрифты, скрипты) имеют высокий приоритет загрузки и в результате блокируют первую значимую отрисовку</p>
<p>Мне бы не хотелось копировать сюда все возможные техники, как этого можно избежать, потому вот вам ссылки:</p>
<ul>
<li><a href="https://medium.com/reloading/preload-prefetch-and-priorities-in-chrome-776165961bbf">Preload, Prefetch And Priorities in Chrome</a>, <a href="https://web-standards.ru/articles/performance-metrics/undefined">Addy Osmani</a></li>
<li><a href="https://css-tricks.com/the-critical-request/">Critical Request</a>, <a href="https://web-standards.ru/articles/performance-metrics/undefined">Ben Schwarz</a></li>
<li><a href="https://medium.com/@fox/talk-the-state-of-the-web-3e12f8e413b3">The State of the Web</a>, <a href="https://web-standards.ru/articles/performance-metrics/undefined">Karolina Szczur</a></li>
<li><a href="https://youtu.be/6m_E-mC0y3Y">Practical Performance</a>, <a href="https://web-standards.ru/articles/performance-metrics/undefined">Paul Irish</a> и <a href="https://web-standards.ru/articles/performance-metrics/undefined">Sam Saccone</a></li>
</ul>
<h2>Визуально готов</h2>
<p>Visually Ready</p>
<p>Метрика разрешает сказать что страничка выглядит «почти» загруженной, но браузер еще не закончил выполнять скрипты.</p>
<h2>Задержка ввода</h2>
<p>Estimated Input Latency</p>
<p>Эта метрика определяет как плавно приложение реагирует на ввод пользователя. Но прежде чем рассказывать детали хотелось бы обратить внимание на терминологию.</p>
<h3>Длинные задачи</h3>
<p>Под капотом браузер оборачивает пользовательский ввод в интерфейсные задачи и складывает в очередь главного потока. Помимо этого, браузер должен парсить, компилировать. выполнять скрипты на странице (задачи приложения). Если время для каждой задачи приложения занимает много времени то интерфейсные задачи могут быть заблокированы пока не будет завершена задача приложения. Как результат страница может отвечать на пользовательский ввод с задержкой и быть «непривлекательной».</p>
<p>Говоря простыми словами, длинные задачи — те, для которых парсинг, компиляция или выполнение скриптов занимает больше чем 50 миллисекунд, <a href="https://w3c.github.io/longtasks/">см. спецификацию</a>.</p>
<p>API для длинных задач реализован в Chrome и используется для расчета занятости главного потока.</p>
<img src="https://web-standards.ru/articles/performance-metrics/images/9.png" alt="">
<p>Возвращаясь к задержке ввода: пользователи ожидают, что страница будет реагировать без задержек, но если главный поток занят обработкой каждой длинной задачи, то станет заметно замедление реакции на ввод. Ощущения от работы с приложением довольно важны, и чтобы избавиться от таких проблем, я рекомендую прочитать «<a href="https://developers.google.com/web/fundamentals/performance/rail">Measure Performance with the RAIL Model</a>».</p>
<h2>Первое взаимодействие</h2>
<p>First Interactive</p>
<p><strong>Уже можно пользоваться?</strong> Да, именно этот вопрос задает пользователь когда видит визуально готовую страницу и хочет с ней взаимодействовать. Первое взаимодействие считается случившимся если удовлетворены все условия:</p>
<ul>
<li>Произошла первая значимая отрисовка;</li>
<li>Сработал <a href="https://developer.mozilla.org/ru/docs/Web/Events/DOMContentLoaded"><code>DOMContentLoaded</code></a>;</li>
<li>Страница визуально готова на 85%.</li>
</ul>
<p>Метрика первого взаимодействия разделена на две метрики:</p>
<ol>
<li>Время до первого взаимодействия (TTFI)</li>
<li>Время до первого последовательного взаимодействия (TTCI)</li>
</ol>
<p><a href="https://docs.google.com/document/d/1GGiI9-7KeY3TPqS3YT271upUVimo-XiL5mwWorDUD4c/edit">См. спецификацию</a>. Причины этого разделения:</p>
<ul>
<li>Чтобы определить минимальное взаимодействие, когда интерфейс отвечает неплохо но возможные небольшие задержки;</li>
<li>Когда приложение или страница полностью интерактивны и строго отвечает условиям <a href="https://developers.google.com/web/fundamentals/performance/rail">RAIL</a>.</li>
</ul>
<h2>Первое последовательное взаимодействие</h2>
<p>Time to First Consistently Interactive (TTCI)</p>
<img src="https://web-standards.ru/articles/performance-metrics/images/10.png" alt="">
<p>Используя реверсивный анализ, который подразумевает анализ трейса с конца, находится период, когда загрузка ресурсов неактивна на протяжении 5 секунд и в этот период отсутствуют длинные задачи. Такой период называется тихое окно (quiet window). Время после тихого окна и перед первой, с конца, длинной задачей будет временем до первого последовательного взаимодействия.</p>
<h2>Время до первого взаимодействия</h2>
<p>Time to First Interactive (TTFI)</p>
<img src="https://web-standards.ru/articles/performance-metrics/images/11.png" alt="">
<p>Определение этой метрики отличается от первого последовательного взаимодействия. Трейс анализируется со старта и до конца. После того, как произошла первая значимая отрисовка, находят тихое окно в 3 секунды. Этого достаточно, чтобы сказать, что страница интерактивна. Но в трейсе могут присутствовать одинокие задачи во время или после тихого окна.</p>
<p>Одинокие задачи выполняются далеко после первой значимой отрисовки и изолированы периодом выполнения в 250 мс (envelope size) и одну секунду тишины до и после этого периода. Иногда одинокие задачи, время которых занимает больше 250 мс, могут сильно влиять на быстродействие страницы. Например:</p>
<blockquote>
<p>A while ago I traced reddit and faced that detecting this kind of extensions are time wasting too.
<a href="https://twitter.com/denar90_/status/883667901819039744">Artem Denysov @denar90_</a></p>
</blockquote>
<h3><strong>Чем полезны эти метрики?</strong></h3>
<p>Они полезны когда главный поток между метриками визуально готов и первое взаимодействие занимает достаточно долго:</p>
<img src="https://web-standards.ru/articles/performance-metrics/images/12.png" alt="">
<p>Это одно из сложных узких мест которые приходится исправлять. Для каждого случая оно индивидуально, потому я рекомендую почитать <a href="https://developers.google.com/web/tools/chrome-devtools/evaluate-performance/">руководства Google об этом</a>, чтобы понимать как избежать таких проблем.</p>
<h2>Визуально готово</h2>
<p>Visually Complete</p>
<p>Метрика визуальной готовности рассчитывается с помощью скриншотов на странице и сравнивает скриншоты состояний с применением <a href="https://sites.google.com/a/webpagetest.org/docs/using-webpagetest/metrics/speed-index">алгоритма индекса скорости</a>.</p>
<p>Бывают случаи, когда метрику визуальной готовности измерить сложно, например если на странице есть «карусели» и другие подвижные элементы. Индекс скорости представляет медиану результатов метрик визуальной готовности. Чем меньше полученное значение тем лучше.</p>
<p>Визуально готово на 100% — это финальная точка, когда мы можем сказать, доволен пользователь процессом загрузки или нет.</p>
<h2>Выводы</h2>
<p>Это были не все ПВМ, только самые важные из них. Ссылки на материалы, приведённые выше, могут вам пригодиться для более подробного изучения метрик. Но ещё хотелось бы поделиться инструментами, которые помогут получить эти метрики:</p>
<ul>
<li><a href="https://www.webpagetest.org/about">Web Pagetest</a></li>
<li><a href="https://github.com/GoogleChrome/lighthouse/">Lighthouse</a></li>
<li><a href="https://github.com/paulirish/pwmetrics">Pwmetrics</a></li>
<li><a href="https://calibreapp.com/">Calibre</a></li>
<li><a href="https://chromedevtools.github.io/timeline-viewer/">DevTools Timeline Viewer</a></li>
</ul>
<p>Если вы хотите делать замеры вручную, то это возможно с <a href="https://developer.mozilla.org/en-US/docs/Web/API/PerformanceObserver">API PerformanceObserver</a>. Небольшой пример из спецификации:</p>
<pre><code tabindex="0" class="language-js">const observer = new PerformanceObserver(list =&gt; {
    list
        .getEntries()
        // Get the values we are interested in
        .map(({ name, entryType, startTime, duration }) =&gt; {
    const obj = {
        'Duration': duration,
        'Entry Type': entryType,
        'Name': name,
        'Start Time': startTime,
    };
    return JSON.stringify(obj, null, 2);
})
// Display them to the console
.forEach(console.log);
    // maybe disconnect after processing the events.
    observer.disconnect();
});
// retrieve buffered events and subscribe to new events
// for Resource-Timing and User-Timing
observer.observe({
    entryTypes: ['resource', 'mark', 'measure'],
    buffered: true
});
</code></pre>

                    ]]></description><pubDate>Wed, 15 Nov 2017 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/performance-metrics/</guid></item><item><title>Генерация HTTP-запросов: Fetch или Axios.js</title><link>https://web-standards.ru/articles/fetch-vs-axios/</link><description><![CDATA[
                        <p>В одном из заключительных разделов курса <a href="https://www.udemy.com/javascript-es6-tutorial/learn/v4/overview">ES6 Javascript: The Complete Developer’s Guide</a> на Udemy Стефан Гридер рассказывает о методе fetch() и о некоторых его недостатках. Он отмечает, что реализация <code>fetch()</code> не идеальна и предлагает другие варианты для выполнения HTTP-запросов. Один из таких вариантов — <a href="https://www.npmjs.com/package/axios">Axios</a>. Раньше я даже не слышал об Axios и подумал, что подвернулась отличная возможность, чтобы немного покопаться и изучить его. Поскольку это ссылка на материал из курса Стивена, я использую похожие примеры.</p>
<p>Axios — это JavaScript-библиотека для выполнения либо HTTP-запросов в Node.js, либо XMLHttpRequests в браузере. Она поддерживает промисы — новинку ES6. Одна из особенностей, которая делает её лучше <code>fetch()</code> — автоматические преобразования JSON-данных.</p>
<p>При использовании <code>fetch()</code> для передачи данных в JSON, необходимо выполнить процесс в два этапа. Сначала сделать фактический запрос, а затем вызвать метод <code>json()</code> для полученных данных с сервера.</p>
<p><strong>Обновление:</strong> по состоянию на 29 мая, Spotify теперь требует проверки подлинности для всех запросов к их API, поэтому примеры ниже не будут работать корректно.</p>
<p>Вот простой пример c использованием API Spotify. Установим URL в качестве переменной и передадим её в <code>fetch()</code>. Затем установим колбэк, выводящий в консоль данные в качестве аргумента <code>then()</code>.</p>
<pre><code tabindex="0" class="language-js">const url = 'https://api.spotify.com/v1/artists/ID';

fetch(url)
    .then(data =&gt; console.log(data));
</code></pre>
<figure>
    <img src="https://web-standards.ru/articles/fetch-vs-axios/images/2.png" alt="">
    <figcaption>Ответ метода <code>fetch()</code> по умолчанию.</figcaption>
</figure>
<p>Это конечно здорово, но это не те данные, которые мы бы хотели увидеть. Этот ответ сервера сообщил нам, что наш запрос прошел просто отлично. Круто, но мы больше не можем ничего с этим сделать.</p>
<p>Чтобы получить данные, сперва нужно передать их в метод json().</p>
<pre><code tabindex="0" class="language-js">fetch(url)
    .then(response =&gt; response.json())
    .then(data =&gt; console.log(data));
</code></pre>
<figure>
    <img src="https://web-standards.ru/articles/fetch-vs-axios/images/3.png" alt="">
    <figcaption>Результат после передачи в <code>json()</code>.</figcaption>
</figure>
<p>Теперь это те данные, которые мы хотели получить. Попробуем реализовать это через Axios. Первый шаг — установка. Существует несколько вариантов установки:</p>
<p>Либо npm, либо bower:</p>
<pre><code tabindex="0" class="language-sh">npm install axios
bower install axios
</code></pre>
<p>Либо прямо с CDN:</p>
<pre><code tabindex="0" class="language-html">&lt;script src=&quot;https://unpkg.com/axios/dist/axios.min.js&quot;&gt;&lt;/script&gt;
</code></pre>
<p>Затем, прямо в консоли, устанавливаем URL в качестве переменной и передаем его в метод <code>axios.get()</code>.</p>
<pre><code tabindex="0" class="language-js">const url = 'https://api.spotify.com/v1/artists/ID';

axios.get(url)
    .then(response =&gt; console.log(response));
</code></pre>
<img src="https://web-standards.ru/articles/fetch-vs-axios/images/4.png" alt="">
<p>Таким образом, с помощью Axios можно обойтись без передачи результатов HTTP-запроса в метод <code>json()</code>. Axios возвращает именно тот объект с данными, который мы ожидаем.</p>
<p>Второй вопрос, который Стивен поднимает: как <code>fetch()</code> обрабатывает ответы на ошибки. Логически можно предположить, что если метод <code>fetch()</code> возвращает сообщение об ошибке, то управление переходит в блок <code>catch()</code> и ошибка обрабатывается там, верно? Не совсем. Вот пример.</p>
<p>Изменим переменную <code>url</code> из предыдущих примеров на некорректную. Ожидается, что ошибка 400 из метода <code>then()</code> перейдёт в блок <code>сatch()</code>, но этого не происходит.</p>
<pre><code tabindex="0" class="language-js">const url = 'https://api.spotify.com/v1/artists/ID';

fetch(url)
    .catch(error =&gt; console.log('BAD', error))
    .then(response =&gt; console.log('GOOD', response));
</code></pre>
<p>Для наглядности выведем в консоль <code>BAD</code> в случае, если ошибка обрабатывается в блоке <code>catch()</code>, в противном случае выведем <code>GOOD</code>.</p>
<img src="https://web-standards.ru/articles/fetch-vs-axios/images/5.png" alt="">
<p>Получаем код ответа 400, но, как вы можете видеть по строке <code>GOOD</code> в консоли, выполнился <code>then()</code>. Как же Axios справляется с этим? Так, как мы и ожидали: выполняется блок <code>сatch()</code> и мы получаем необходимый нам вид ошибки.</p>
<img src="https://web-standards.ru/articles/fetch-vs-axios/images/6.png" alt="">
<p>Строка <code>BAD</code> и ошибка выводятся в консоль.</p>
<p>Метод <code>fetch()</code> — огромный шаг в развитии нативного JS в сторону взаимодействия с сервером. Но он имеет свои недостатки, которых можно избежать, используя сторонние библиотеками, такие как Axios.</p>
<h2>Примечания переводчика</h2>
<p>Метод <code>fetch()</code> по умолчанию не работает с куками. Для этого необходимо выставить значение <code>credentials</code>:</p>
<pre><code tabindex="0" class="language-js">fetch('url', { credentials: 'same-origin' });
</code></pre>

                    ]]></description><pubDate>Wed, 15 Nov 2017 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/fetch-vs-axios/</guid></item><item><title>Критический CSS + прогрессивный CSS = ?</title><link>https://web-standards.ru/articles/critical-and-progressive-css/</link><description><![CDATA[
                        <p>Однажды я прочел статью на Хабре о результатах, полученных при проведении ребятами из Google различных тестов. Они выяснили, что в большинстве сценариев встраивание стилей в HTML приводит к более быстрой загрузке страницы, чем подключение стилей одним файлом. Тем не менее, такой трюк я не применял, за одним исключением: в одном проекте, в котором я принимал участие, было реализовано встраивание стилей на страницу сгенерированного лендинга, что действительно немного ускоряло загрузку. При разработке обычных сайтов заморачиваться с загрузкой стилей не обязательно, но есть и такие проекты, где необходимо показать страницу пользователю как можно скорее, ведь это может улучшить аудиторные показатели. Для этого существуют, как минимум, две практики: «критический CSS» и «прогрессивный CSS».</p>
<h2>Критический CSS</h2>
<p>Основной принцип в обеих практиках заключается в том, что стили загружаются постепенно, а не все сразу. Критический CSS — это стили, которые необходимы для отрисовки первой видимой части страницы. Выделить такие стили автоматически вы можете с помощью пакета <a href="https://github.com/filamentgroup/criticalCSS">CriticalCSS</a>. Для моментальной отрисовки критический CSS встраивают в HTML, все остальные стили загружаются отдельным файлом асинхронно.</p>
<figure>
    <img src="https://web-standards.ru/articles/critical-and-progressive-css/images/1.gif" alt="При обычной загрузке стилей, отрисовка страницы дольше, чем с критическими стилями.">
    <figcaption>Обычная загрузка в &lt;head&gt;.</figcaption>
</figure>
<figure>
    <img src="https://web-standards.ru/articles/critical-and-progressive-css/images/2.gif" alt="Отрисовка страницы с критическими стилями быстрее, чем с обычной загрузкой стилей.">
    <figcaption>Загрузка с критическими стилями.</figcaption>
</figure>
<p>При отрисовке HTML, как только браузер встречает <code>&lt;script&gt;</code> или <code>&lt;link&gt;</code>, он останавливается и ждёт, пока JS или CSS-файл загрузится, и только после этого идет дальше. При использовании <code>&lt;script&gt;</code> с атрибутами <code>async</code> и <code>defer</code>, мы можем попросить браузер загружать скрипт асинхронно, без блокировки рендеринга страницы, но у <code>&lt;link&gt;</code> таких атрибутов нет. Из этой ситуации нас спасёт функция <a href="https://github.com/filamentgroup/loadCSS">loadCSS</a> — её использование позволит вашей странице отрисоваться с критическим CSS и не ждать полной загрузки остальных стилей.</p>
<pre><code tabindex="0" class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
    &lt;head&gt;
        &lt;script&gt;
            // loadCSS.js
        &lt;/script&gt;
        &lt;style&gt;
            /* Критические стили */
            .article,
            .comments,
            .about,
            .footer {
                display: none;
            }
        &lt;/style&gt;
        &lt;script&gt;
            /* Остальные стили */
            loadCSS('style.css');
        &lt;/script&gt;
    &lt;/head&gt;
    &lt;body&gt;
        …
    &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>Как это работает? Если коротко, то эта функция создает <code>&lt;link rel=&quot;stylesheet&quot;&gt;</code> с атрибутом <code>media=&quot;only x&quot;</code>. Таким образом, браузер загружает стили, при этом не блокируя рендеринг страницы. После того, как стили загружены, атрибуту задается значение <code>media=&quot;all&quot;</code>. В современных браузерах, поддерживающих rel=&quot;preload&quot; для <code>&lt;link&gt;</code>, можно обойтись без loadCSS.</p>
<pre><code tabindex="0" class="language-html">&lt;link rel=&quot;preload&quot; href=&quot;/style.css&quot; as=&quot;style&quot; onload=&quot;this.rel='stylesheet'&quot;&gt;
&lt;noscript&gt;
    &lt;link rel=&quot;stylesheet&quot; href=&quot;style.css&quot;&gt;
&lt;/noscript&gt;
</code></pre>
<p>С <code>media=&quot;only x&quot;</code> такой трюк не сработает: разные браузеры загружают CSS по-разному, loadCSS учитывает все эти нюансы.</p>
<h2>Прогрессивный CSS</h2>
<img src="https://web-standards.ru/articles/critical-and-progressive-css/images/3.gif" alt="Последовательная загрузка отдельных блоков страницы.">
<p>Прогрессивный CSS предполагает создание отдельного CSS-файла для каждой части страницы: перед каждой вставляется <code>&lt;link rel=&quot;stylesheet&quot;&gt;</code> со стилем только этой части. Благодаря тому, что <code>&lt;link rel=&quot;stylesheet&quot;&gt;</code> блокирует рендеринг до окончания загрузки стилей, страница будет загружаться последовательно по частям. В этом сценарии выгоднее всего использовать HTTP/2, он хорошо работает с одновременными запросами.</p>
<pre><code tabindex="0" class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
    &lt;head&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;link rel=&quot;stylesheet&quot; href=&quot;header.css&quot;&gt;
        &lt;header class=&quot;header&quot;&gt;…&lt;/header&gt;

        &lt;link rel=&quot;stylesheet&quot; href=&quot;article.css&quot;&gt;
        &lt;main class=&quot;article&quot;&gt;&lt;/main&gt;

        &lt;link rel=&quot;stylesheet&quot; href=&quot;comments.css&quot;&gt;
        &lt;section class=&quot;comments&quot;&gt;…&lt;/section&gt;

        &lt;link rel=&quot;stylesheet&quot; href=&quot;about.css&quot;&gt;
        &lt;section class=&quot;about&quot;&gt;&lt;/section&gt;

        &lt;link rel=&quot;stylesheet&quot; href=&quot;footer.css&quot;&gt;
        &lt;footer class=&quot;footer&quot;&gt;&lt;/footer&gt;
    &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>Пришла пора признаться, что прогрессивный CSS это скорее концепт, чем что-то применимое на практике: в <a href="https://html.spec.whatwg.org/multipage/semantics.html#the-link-element">спецификации HTML</a> никак не объяснено, как рендеринг страницы должен блокироваться загрузкой CSS и как должен вести себя <code>&lt;link rel=&quot;stylesheet&quot;&gt;</code> в <code>&lt;body&gt;</code>, поэтому все браузеры обрабатывают эту ситуацию по-своему. Можно выделить два основных поведения браузеров:</p>
<ol>
<li>Встретил <code>&lt;link rel=&quot;stylesheet&quot;&gt;</code>;</li>
<li>Дождался загрузки;</li>
<li>Рендерит дальше.</li>
</ol>
<p>Или:</p>
<ol>
<li>Встретил <code>&lt;link rel=&quot;stylesheet&quot;&gt;</code>;</li>
<li>Дождался загрузки <strong>всех</strong> обнаруженных стилей;</li>
<li>Рендерит дальше.</li>
</ol>
<p>Подробнее про поведение каждого браузера можно <a href="https://jakearchibald.com/2016/link-in-body/#changes-to-chrome">почитать у Джейка Арчибальда</a>. На реальных проектах прогрессивный CSS в полной мере не применить, но сама концепция выглядит очень интересно.</p>
<h2>Gulp в помощь</h2>
<p>Чтобы с лёгкостью применять критический и прогрессивный CSS вместе во всех браузерах, я написал плагин <a href="https://www.npmjs.com/package/gulp-progressive-css">gulp-progressive-css</a>. Этот плагин обрабатывает HTML-файлы и добавляет возможность использовать атрибуты <code>priority=&quot;critical&quot;</code>, <code>priority=&quot;queued&quot;</code> и <code>priority=&quot;async&quot;</code> у элемента <code>&lt;link rel=&quot;stylesheet&quot;&gt;</code>.</p>
<pre><code tabindex="0" class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
    &lt;head&gt;
        &lt;link rel=&quot;stylesheet&quot; priority=&quot;async&quot; href=&quot;/font.css&quot;&gt;
        &lt;link rel=&quot;stylesheet&quot; priority=&quot;critical&quot; href=&quot;/header.css&quot;&gt;
        &lt;link rel=&quot;stylesheet&quot; priority=&quot;queued&quot; href=&quot;/article.css&quot;&gt;
        &lt;link rel=&quot;stylesheet&quot; priority=&quot;queued&quot; href=&quot;/comments.css&quot;&gt;
        &lt;link rel=&quot;stylesheet&quot; priority=&quot;queued&quot; href=&quot;/about.css&quot;&gt;
        &lt;link rel=&quot;stylesheet&quot; priority=&quot;queued&quot; href=&quot;/footer.css&quot;&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;header class=&quot;header&quot;&gt;&lt;/header&gt;
        &lt;main class=&quot;article&quot;&gt;&lt;/main&gt;
        &lt;section class=&quot;comments&quot;&gt;&lt;/section&gt;
        &lt;section class=&quot;about&quot;&gt;&lt;/section&gt;
        &lt;footer class=&quot;footer&quot;&gt;&lt;/footer&gt;
    &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>Стили, подключенные с атрибутом <code>priority=&quot;critical&quot;</code>, будут встроены в HTML-файл, а стили с атрибутом <code>priority=&quot;queued&quot;</code> или <code>priority=&quot;async&quot;</code> будут загружены асинхронно с помощью функции importCSS. Это <a href="https://github.com/TrigenSoftware/import-css/blob/master/src/link-in-body-async.js">вариация функции</a> <code>loadCSS</code>, её особенность в том, что загрузка стилей начинается одновременно, но стили с <code>priority=&quot;queued&quot;</code> будут применяться в том порядке, в котором они были указаны в HTML-файле. Чтобы загрузка стилей работала одинаково во всех браузерах, подключение стилей переносится в конец <code>&lt;body&gt;</code>. Также добавляется <code>&lt;link rel=&quot;preload&quot;&gt;</code> в <code>&lt;head&gt;</code> для каждого файла стилей, чтобы в браузерах, которые поддерживают <code>rel=&quot;preload&quot;</code>, загрузка начиналась еще при обработке <code>&lt;head&gt;</code>.</p>
<pre><code tabindex="0" class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
    &lt;head&gt;
        &lt;style&gt;/* header.css */&lt;/style&gt;
        &lt;link rel=&quot;preload&quot; as=&quot;style&quot; href=&quot;font.css&quot;&gt;
        &lt;link rel=&quot;preload&quot; as=&quot;style&quot; href=&quot;article.css&quot;&gt;
        &lt;link rel=&quot;preload&quot; as=&quot;style&quot; href=&quot;comments.css&quot;&gt;
        &lt;link rel=&quot;preload&quot; as=&quot;style&quot; href=&quot;about.css&quot;&gt;
        &lt;link rel=&quot;preload&quot; as=&quot;style&quot; href=&quot;footer.css&quot;&gt;
        &lt;noscript&gt;
            &lt;link rel=&quot;stylesheet&quot; href=&quot;font.css&quot;&gt;
            &lt;link rel=&quot;stylesheet&quot; href=&quot;article.css&quot;&gt;
            &lt;link rel=&quot;stylesheet&quot; href=&quot;comments.css&quot;&gt;
            &lt;link rel=&quot;stylesheet&quot; href=&quot;about.css&quot;&gt;
            &lt;link rel=&quot;stylesheet&quot; href=&quot;footer.css&quot;&gt;
        &lt;/noscript&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;header class=&quot;header&quot;&gt;&lt;/header&gt;
        &lt;main class=&quot;article&quot;&gt;&lt;/main&gt;
        &lt;section class=&quot;comments&quot;&gt;&lt;/section&gt;
        &lt;section class=&quot;about&quot;&gt;&lt;/section&gt;
        &lt;footer class=&quot;footer&quot;&gt;&lt;/footer&gt;
        &lt;script&gt;
            importCSS('font.css', 0, true);
            importCSS('article.css');
            importCSS('comments.css');
            importCSS('about.css');
            importCSS('footer.css');
        &lt;/script&gt;
    &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<h2>Результаты применения</h2>
<p>Я решил взять один из лендингов, который однажды верстал, и на его примере продемонстрировать пользу применения асинхронной загрузки стилей.</p>
<p>Тестируем изначальный вариант лендинга на <a href="https://developers.google.com/speed/pagespeed/insights">PageSpeed Insights</a>:</p>
<figure>
    <img src="https://web-standards.ru/articles/critical-and-progressive-css/images/4.jpg" alt="Результат работы PageSpeed.">
    <figcaption>PageSpeed: 74 балла из 100.</figcaption>
</figure>
<p>По результатам тестирования видно, что стили шрифтов и основные стили блокируют рендеринг страницы. Применив прогрессивную загрузку на стили сайта, мы будем иметь те же 74 из 100 баллов, но если сделать загрузку шрифтов асинхронной, то результат уже совсем другой:</p>
<figure>
    <img src="https://web-standards.ru/articles/critical-and-progressive-css/images/5.jpg" alt="Результат работы PageSpeed.">
    <figcaption>PageSpeed: 98 баллов из 100.</figcaption>
</figure>
<p>При сравнении трёх вариантов страницы на <a href="https://www.webpagetest.org/">WebPageTest</a>:</p>
<ol>
<li>Обычный;</li>
<li>Прогрессивная загрузка;</li>
<li>Прогрессивная загрузка и асинхронная загрузка шрифтов;</li>
</ol>
<p>Можно увидеть, что второй вариант отображает первый кадр раньше, чем первый, при этом третий вариант делает это еще раньше со значительным отрывом.</p>
<figure>
    <img src="https://web-standards.ru/articles/critical-and-progressive-css/images/6.jpg" alt="График загрузки контента.">
    <figcaption>Сравнение подходов к загрузке на WebPageTest.</figcaption>
</figure>
<p>На этом примере становится понятно, что небольшому лендингу основное ускорение придает асинхронная загрузка шрифтов, а не прогрессивная загрузка стилей. Так происходит из-за слишком малого размера файла стилей. Прогрессивная загрузка будет давать ощутимую пользу только полноценным сайтам, у которых размер стилей больше в несколько раз.</p>
<h3>Источники</h3>
<ul>
<li><a href="https://habrahabr.ru/company/badoo/blog/320558/">33 способа ускорить ваш фронтенд в 2017 году</a>, Александр Гутников</li>
<li><a href="https://jakearchibald.com/2016/link-in-body/">The future of loading CSS</a>, Джейк Арчибальд</li>
<li><a href="https://web.archive.org/web/20190212114240/http://habrahabr.net/thread/8679">Ускорение Lenta.ru: 3 человека, 2 недели, улучшение глубины просмотра на 27%</a>, Денис Паращий</li>
</ul>

                    ]]></description><pubDate>Tue, 24 Oct 2017 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/critical-and-progressive-css/</guid></item><item><title>Как создать адаптивную сетку</title><link>https://web-standards.ru/articles/responsive-grid-system/</link><description><![CDATA[
                        <p>Адаптивная сетка — один из лучших инструментов для создания уникального дизайна. Вы можете настроить всё, что нужно — количество и ширину колонок, отступы и даже контрольные точки, при достижении которых перестраивается раскладка страницы.</p>
<p>К сожалению, большинство людей даже не пытаются создавать свои сетки из-за того, что им не хватает знаний и уверенности в своих силах.</p>
<p>В этой статье я хочу помочь вам обрести веру в себя и знания, нужные для создания собственной сетки. Надеюсь, после прочтения статьи вы отложите фреймворки и попробуете создать собственную сетку для следующего проекта.</p>
<h2>Что входит в сетку?</h2>
<p>Прежде чем браться за создание сетки, вам нужно сделать три вещи.</p>
<h3>1. Спроектировать сетку</h3>
<p>Сколько будет колонок? Они будут одинаковой или разной ширины? Какие у них будут ширина и отступы? Вы сможете правильно просчитать параметры сетки только после того, как ответите на эти вопросы. Чтобы помочь вам, я написал статью о <a href="https://zellwk.com/blog/designing-grids">проектировании сеток</a>. Прочтите её, что бы научиться грамотно проектировать сетки.</p>
<h3>2. Понять поведение сетки на разных вьюпортах</h3>
<p>Будете ли вы менять размеры колонок и отступов пропорционально ширине вьюпорта? Или вы будете менять только ширину колонок, оставляя отступы фиксированными? Может вы будете менять количество колонок в определённых контрольных точках?</p>
<p>На эти вопросы тоже нужно ответить. Это поможет рассчитать ширину колонок и отступов. Я писал об этом в той же <a href="https://zellwk.com/blog/designing-grids/">статье о проектировании сеток</a>, так что обратитесь к ней в случае сомнений.</p>
<h3>3. Понять, нравится ли вам писать классы в разметке</h3>
<p>Когда речь заходит о сетках, мир фронтенда делится на два лагеря. Один пишет сеточные классы в разметке (например, такой подход используют Bootstrap и Foundation). Я называю это <em>HTML-сетками.</em> Разметка выглядит так:</p>
<pre><code tabindex="0" class="language-html">&lt;div class=&quot;container&quot;&gt;
    &lt;div class=&quot;row&quot;&gt;
        &lt;div class=&quot;col-md-9&quot;&gt;Content&lt;/div&gt;
        &lt;div class=&quot;col-md-3&quot;&gt;Sidebar&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<p>Другие создают сетки на CSS. Я называю это <em>CSS-сетками.</em></p>
<p>С CSS-сетками разметка получается проще, чем с HTML-сетками. Вам не приходится повторять одни и те же классы, размечая визуально похожие части документа. Также вам не нужно помнить, как называются классы сетки:</p>
<pre><code tabindex="0" class="language-html">&lt;div class=&quot;content-sidebar&quot;&gt;
    &lt;div class=&quot;content&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;sidebar&quot;&gt;&lt;/div&gt;
&lt;/div&gt;
</code></pre>
<p>С другой стороны, стили в случае с CSS-сетками получаются сложнее. Придётся думать, чтобы добиться простого решения задачи — если только вы ещё не решали её до сих пор.</p>
<h3>А что выбрал бы я?</h3>
<p>Как и многие эксперты фронтенда, я выбираю CSS-сетки, хотя и не смею называть себя экспертом. Если вам интересно, я <a href="https://zellwk.com/blog/migrating-from-bootstrap-to-susy/">написал статью</a> о том, почему выбрал CSS-сетки вместо HTML-сеток. Также я <a href="https://zellwk.com/blog/from-html-grids-to-css-grids/">написал статью</a>, которая поможет вам перейти с HTML-сеток на CSS-сетки.</p>
<p>Так много статей читать… 😢</p>
<p>В любом случае, вам нужно определиться с тремя вещами, прежде чем создавать свою сетку. Вот они, если кратко:</p>
<ol>
<li>Дизайн сетки;</li>
<li>Как сетка ведёт себя на разных вьюпортах;</li>
<li>Используется CSS- или HTML-сетка.</li>
</ol>
<p>Мы можем двигаться дальше, только определившись с этими вещами. В этой статье условия такие:</p>
<ol>
<li>Сетка имеет максимальную ширину 1140 пикселей, 12 колонок по 75 пикселей и отступы в 20 пикселей. За подсказкой, откуда брать эти числа, обратитесь <a href="https://zellwk.com/blog/designing-grids/">к этой статье</a>.</li>
<li>Колонки меняют свой размер пропорционально вьюпорту, а отступы остаются фиксированными. Почему я выбрал такой поведение, объясняется <a href="https://zellwk.com/blog/designing-grids/">в этой статье</a>.</li>
<li>Я собираюсь создавать CSS-сетку. Почему я их рекомендую <a href="https://zellwk.com/blog/migrating-from-bootstrap-to-susy/">в ещё одной статье</a>.</li>
</ol>
<p>Итак, давайте начнём!</p>
<h2>Создаём сетку</h2>
<p>Процесс создания сетки состоит из восьми шагов:</p>
<ol>
<li>Выбор технологии реализации</li>
<li>Установка <code>box-sizing: border-box</code>;</li>
<li>Создание контейнера сетки;</li>
<li>Расчёт ширины колонок;</li>
<li>Определение положения отступов;</li>
<li>Создание отладочной сетки;</li>
<li>Создание вариаций раскладки;</li>
<li>Адаптация раскладки.</li>
</ol>
<p>Большинство из этих шагов становятся достаточно простыми, как только вы проходите их хотя бы раз. Я объясню всё, что нужно знать для прохождения каждого из них.</p>
<h2>Шаг 1: выбор технологии</h2>
<p>Что вы будете использовать для создания сетки — CSS-гриды, флексы или старые добрые флоаты? Решения и детали реализации зависят от выбранной технологии.</p>
<p>CSS-гриды, безусловно, лучше всего подходят для создания сетки (потому, что гриды 😎). К сожалению, сегодня поддержка гридов оставляет желать лучшего. В каждом браузере они скрыты за флагом, поэтому мы не будем рассматривать гриды в этой статье. Я настоятельно рекомендую ознакомиться <a href="http://gridbyexample.com/">с работой Рейчел Эндрю</a>, если вы хотите узнать о гридах больше.</p>
<p><em>На момент публикации перевода гриды уже поддерживаются без флагов в Chrome, Firefox и Safari — прим. редактора.</em></p>
<p>Перейдём к флексам и флоатам. Соображения по их применению схожи, так что можете выбрать то, что вам по душе и двигаться далее. Я буду использовать флоаты, потому что они проще и понятнее новичкам.</p>
<p>Если вы выбрали флексы, помните об отличиях от флоатов, которые нужно учесть.</p>
<h2>Шаг 2: установка <code>box-sizing</code></h2>
<p>Свойство <code>box-sizing</code> задаёт блочную модель, которую браузеры используют для расчёта свойств <code>width</code> и <code>height</code>. Выставляя свойству <code>box-sizing</code> значение <code>border-box</code>, мы сильно упрощаем расчёт размеров колонок и отступов, позже вы поймёте, почему.</p>
<p>Вот наглядный пример того, как вычисляется <code>width</code> в зависимости от значения свойства <code>box-sizing</code>:</p>
<figure>
    <img src="https://web-standards.ru/articles/responsive-grid-system/images/1.jpg" alt="Свойство box-sizing и его влияние на расчёт ширины.">
    <figcaption>Свойство <code>box-sizing</code> и его влияние на расчёт ширины.</figcaption>
</figure>
<p>Обычно я устанавливаю значение <code>border-box</code> для всех элементов на сайте, благодаря чему расчёт ширины и высоты элементов работает последовательно и интуитивно понятно. Вот как я это делаю:</p>
<pre><code tabindex="0" class="language-css">html {
    box-sizing: border-box;
}

*,
*::before,
*::after {
    box-sizing: inherit;
}
</code></pre>
<p>Примечание: если вам нужно более детальное объяснение работы свойства <code>box-sizing</code>, я рекомендую вам <a href="https://zellwk.com/blog/understanding-css-box-sizing/">прочесть эту статью</a>.</p>
<h2>Шаг 3: создание контейнера сетки</h2>
<p>У каждой сетки есть контейнер, определяющий её максимальную ширину. Как правило, я называю его <code>.l-wrap</code>. Префикс <code>.l-</code> означает layout (раскладка). Я использую такое именование с тех пор, как изучил <a href="https://smacss.com/">SMACSS</a>, методологию <a href="https://twitter.com/snookca">Джонатана Снука</a>.</p>
<pre><code tabindex="0" class="language-css">.l-wrap {
    max-width: 1140px;
    margin-right: auto;
    margin-left: auto;
}
</code></pre>
<p>Примечание: для лучшей доступности и адаптивности я настоятельно рекомендую использовать вместо пикселей относительные единицы измерения вроде <code>em</code> или <code>rem</code>. В примерах я использую пиксели, потому что они проще для понимания.</p>
<h2>Шаг 4: расчёт ширины колонок</h2>
<p>Помните, что мы используем флоаты для вёрстки колонок и отступов? С флоатами мы можем использовать только пять CSS-свойств для создания колонок и отступов, в случае с флексами, их немного больше.</p>
<ol>
<li><code>width</code></li>
<li><code>margin-right</code></li>
<li><code>margin-left</code></li>
<li><code>padding-right</code></li>
<li><code>padding-left</code></li>
</ol>
<p>Если вы помните, при использовании CSS-сеток разметка выглядит примерно так:</p>
<pre><code tabindex="0" class="language-html">&lt;div class=&quot;l-wrap&quot;&gt;
    &lt;div class=&quot;three-col-grid&quot;&gt;
        &lt;div class=&quot;grid-item&quot;&gt;Элемент сетки&lt;/div&gt;
        &lt;div class=&quot;grid-item&quot;&gt;Элемент сетки&lt;/div&gt;
        &lt;div class=&quot;grid-item&quot;&gt;Элемент сетки&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<p>Из этой разметки понятно, что сетка имеет всего три колонки. Также видно, что для создания отступов нет дополнительных <code>&lt;div&gt;</code>. Это означает, что:</p>
<ol>
<li>Мы создаём колонки с помощью свойства <code>width</code>;</li>
<li>Мы создаём отступы с помощью свойств <code>margin</code> или <code>padding</code>.</li>
</ol>
<p>Думать о колонках и отступах одновременно сложно, поэтому давайте для начала представим, что делаем сетку без отступов.</p>
<p>Эта сетка будет выглядеть примерно так:</p>
<figure>
    <img src="https://web-standards.ru/articles/responsive-grid-system/images/2.png" alt="Трёхколоночная сетка без отступов.">
    <figcaption>Трёхколоночная сетка без отступов.</figcaption>
</figure>
<p>А теперь нужно произвести несколько математических вычислений. Мы знаем, что сетка имеет максимальную ширину в 1140 пикселей, значит ширина каждой колонка — 380 пикселей (1140 ÷ 3).</p>
<pre><code tabindex="0" class="language-css">.three-col-grid .grid-item {
    width: 380px;
    float: left;
}
</code></pre>
<p>Пока всё хорошо. Мы сделали сетку, которая отлично работает на вьюпортах больше 1140 пикселей. К сожалению, всё ломается, когда вьюпорт становится меньше.</p>
<figure>
    <img src="https://web-standards.ru/articles/responsive-grid-system/images/3.gif" alt="Сетка ломается, когда вьюпорт меньше 1140 пикселей.">
    <figcaption>Сетка ломается, когда вьюпорт меньше 1140 пикселей.</figcaption>
</figure>
<p>Из-за этого мы не можем использовать пиксели. Нам нужна единица измерения, которая зависит от ширины контейнера: это проценты. Давайте зададим ширину колонок в процентах:</p>
<pre><code tabindex="0" class="language-css">.three-col-grid .grid-item {
    width: 33.33333%;
    float: left;
}
</code></pre>
<p>Код выше задаёт простую резиновую трёхколоночную сетку без отступов. Колонки меняют свою ширину пропорционально ширине окна браузера.</p>
<figure>
    <img src="https://web-standards.ru/articles/responsive-grid-system/images/4.gif" alt="Три колонки без отступов.">
    <figcaption>Три колонки без отступов.</figcaption>
</figure>
<p>Прежде чем двигаться дальше, нужно уточнить одну вещь. Когда все дочерние элементы контейнера плавающие (им задано свойство <code>float</code>), высота контейнера обнуляется. Это явление называется <a href="https://css-tricks.com/all-about-floats/">схлопыванием флоатов</a>. Контейнер ведёт себя так, будто в нём нет дочерних элементов:</p>
<figure>
    <img src="https://web-standards.ru/articles/responsive-grid-system/images/5.png" alt="Схлопывание флоата.">
    <figcaption>Схлопывание флоата. Изображение с CSS Tricks.</figcaption>
</figure>
<p>Чтобы это исправить, нам нужен клиар-фикс. Он выглядит так:</p>
<pre><code tabindex="0" class="language-css">.three-col-grid::after {
    display: table;
    clear: both;
    content: '';
}
</code></pre>
<p>Если вы используете препроцессор вроде Sass, вы можете сделать примесь, чтобы использовать этот код удобно в разных местах:</p>
<pre><code tabindex="0" class="language-scss">// Клиар-фикс
@mixin clearfix {
    &amp;::after {
        display: table;
        clear: both;
        content: '';
    }
}

// Использование
.three-col-grid {
    @include clearfix;
}
</code></pre>
<p>Мы разобрались с колонками. Следующий шаг — отступы.</p>
<h2>Шаг 5: определение положения отступов</h2>
<p>Пока мы только знаем, что их можно реализовать с помощью свойств <code>margin</code> и <code>padding</code>. Но какое из них следует выбрать?</p>
<p>Сделав несколько попыток, вы быстро поймёте, что есть четыре возможных способа как сделать эти отступы: внешние, <code>margin</code> и внутренние, <code>padding</code>. Отступы могут быть расположены:</p>
<ol>
<li>С одной стороны, внешние;</li>
<li>С одной стороны, внутренние;</li>
<li>Равномерно с обеих сторон, внешние;</li>
<li>Равномерно с обеих сторон, внутренние.</li>
</ol>
<figure>
    <img src="https://web-standards.ru/articles/responsive-grid-system/images/6.png" alt="Четыре возможных способа создать колонки и отступы.">
    <figcaption>Четыре возможных способа создать колонки и отступы.</figcaption>
</figure>
<p>Здесь начинаются сложности. Вам нужно по-разному рассчитать ширину колонок в зависимости от используемого метода. Рассмотрим эти методы один за другим и посмотрим на разницу. Не торопитесь, пока читаете.</p>
<p>Поехали:</p>
<h3>Метод 1: внешние односторонние отступы</h3>
<p>Используя этот метод, вы создаете отступы с помощь <code>margin</code>. Этот отступ будет расположен слева или справа от колонки. Вам решать, какую сторону выбрать.</p>
<p>В рамках этой статьи, давайте предположим, что вы задаете отступы справа. Вот что вы будете делать:</p>
<pre><code tabindex="0" class="language-css">.grid-item {
    margin-right: 20px;
    float: left;
}
</code></pre>
<p>Затем пересчитываете ширину колонки как на картинке:</p>
<figure>
    <img src="https://web-standards.ru/articles/responsive-grid-system/images/7.png" alt="Внешние односторонние отступы.">
    <figcaption>Внешние односторонние отступы.</figcaption>
</figure>
<p>Как вы видите на картинке выше, 1440 пикселей — это три колонки и два отступа.</p>
<p>И тут появляется проблема… Нам нужно, чтобы колонки были описаны в процентах, но в то же время отступы зафиксированы на ширине 20 пикселей. Мы не можем делать вычисления с двумя разными единицами измерения одновременно!</p>
<p>Это было невозможно раньше, но возможно сейчас.</p>
<p>Вы можете использовать CSS-функцию <code>calc</code> для сочетания процентов с другими единицами измерения. Она на лету извлекает значение процентов для выполнения вычислений.</p>
<p>Это значит, что вы можете задать ширину в виде функции, и браузер автоматически рассчитает ее значение:</p>
<pre><code tabindex="0" class="language-css">.grid-item {
    width: calc((100% - 20px * 2) / 3);
}
</code></pre>
<p>Это круто.</p>
<p>После получения ширины колонки, вам нужно удалить последний отступ у крайнего правого элемента сетки. Вот как это можно сделать:</p>
<pre><code tabindex="0" class="language-css">.grid-item:last-child {
    margin-right: 0;
}
</code></pre>
<p>Чаще всего, когда вы удаляете последний отступ у крайнего правого элемента, вы также хотите задать ему обтекание по правой стороне для предотвращения ошибок субпикселного округления, из-за которых ваша сетка переносит последний элемент на новую строку. Это происходит только в браузерах, которые округляют пиксели.</p>
<figure>
    <img src="https://web-standards.ru/articles/responsive-grid-system/images/8.png" alt="Ошибка субпикселного округления может сломать сетку, вытолкнув последний элемент на следующую строку.">
    <figcaption>Ошибка субпикселного округления может сломать сетку, вытолкнув последний элемент на следующую строку.</figcaption>
</figure>
<pre><code tabindex="0" class="language-css">​.grid-item:last-child {
    margin-right: 0;
    float: right;
}
</code></pre>
<p>Фух. Почти готово. И ещё одна вещь.</p>
<p>Наш код хорош только в том случае, если сетка содержит лишь одну строку. Но он не справляется, если строк с элементами больше, чем одна 😢</p>
<figure>
    <img src="https://web-standards.ru/articles/responsive-grid-system/images/9.png" alt="Код не справляется, если строк больше, чем одна.">
    <figcaption>Код не справляется, если строк больше, чем одна.</figcaption>
</figure>
<p>Нам нужно удалить правый внешний отступ у каждого крайнего правого элемента в каждой строке. Лучший способ это сделать — использовать <code>nth-child</code>:</p>
<pre><code tabindex="0" class="language-css">/* Для 3-колоночной сетки */
.grid-item:nth-child(3n+3) {
    margin-right: 0;
    float: right;
}
</code></pre>
<p>Это всё, что нужно для создания односторонних внешних отступов. Вот CodePen, чтобы вы сами поиграли:</p>
<figure>
    <iframe height="265" src="https://codepen.io/Lesnevskiy/embed/RoZpva" title="Односторонняя сетка с использованием внешних отступов" allowfullscreen></iframe>
    <figcaption>Односторонняя сетка с использованием внешних отступов.</figcaption>
</figure>
<p><strong>Примечание:</strong> свойство <code>сalc</code> не работает в IE8 и Opera Mini. Смотрите другие подходы, если вам нужно поддерживать эти браузеры.</p>
<h3>Метод 2: внутренние односторонние отступы</h3>
<p>Как и с внешними односторонними отступами, в этом методе требуется разместить отступы на одной из сторон колонки. Предположим, что вы снова выбрали правую сторону.</p>
<pre><code tabindex="0" class="language-css">.grid-item {
    padding-right: 20px;
    float: left;
}
</code></pre>
<p>Затем, вы можете пересчитать ширину колонки как на картинке:</p>
<figure>
    <img src="https://web-standards.ru/articles/responsive-grid-system/images/10.png" alt="Односторонние отступы с использованием внутренних отступов.">
    <figcaption>Односторонние отступы с использованием внутренних отступов.</figcaption>
</figure>
<p>Обратили внимание, что ширина отличается от предыдущего метода? Мы переключили свойство <code>box-sizing</code> в <code>border-box</code>. Теперь <code>width</code> рассчитывается, включая в себя <code>padding</code>.</p>
<p>В этом случае, две из трёх колонок имеют бо́льшую ширину, чем последняя, что в конечном итоге приводит к причудливым расчётам и делает CSS трудным для понимания.</p>
<p>Я предлагаю даже не продолжать с этим методом. Всё обернётся действительно страшно. Пробуйте на свой страх и риск.</p>
<h3>Метод 3: внешние разделённые отступы</h3>
<p>В этом методе мы разделяем отступы на две части и размещаем по половине с каждой стороны колонки. Код выглядит примерно так:</p>
<pre><code tabindex="0" class="language-css">.grid-item {
    margin-right: 10px;
    margin-left: 10px;
    float: left;
}
</code></pre>
<p>Затем пересчитываем ширину колонки как на картинке:</p>
<figure>
    <img src="https://web-standards.ru/articles/responsive-grid-system/images/11.png" alt="Разделение внешних отступов.">
    <figcaption>Разделение внешних отступов.</figcaption>
</figure>
<p>Как мы узнали ранее, рассчитать ширину колонки можно с помощью функции <code>calc</code>. В этой ситуации мы отнимаем три отступа от 100%, прежде чем делить ответ на три для получения ширины колонки. Другими словами, ширина колонки будет <code>calc((100% - 20px * 3) / 3)</code>.</p>
<pre><code tabindex="0" class="language-css">.grid-item {
    width: calc((100% - 20px * 3) / 3);
    margin-right: 10px;
    margin-left: 10px;
    float: left;
}
</code></pre>
<p>Это всё! Вам не нужно ничего дополнительно делать для сеток с несколькими строками 😉 Вот CodePen, чтобы вы могли поиграть:</p>
<figure>
    <iframe height="265" src="https://codepen.io/Lesnevskiy/embed/KNvWYR" title="Сетка с внешними разделенными отступами." allowfullscreen></iframe>
    <figcaption>Сетка с внешними разделенными отступами.</figcaption>
</figure>
<h3>Метод 4: внутренние разделённые отступы</h3>
<p>Этот метод аналогичен предыдущему. Мы делили отступы и размещали их с каждой стороны колонки. На этот раз мы используем <code>padding</code>:</p>
<pre><code tabindex="0" class="language-css">.grid-item {
    padding-right: 10px;
    padding-left: 10px;
    float: left;
}
</code></pre>
<p>Затем вы рассчитываете ширину колонки так:</p>
<figure>
    <img src="https://web-standards.ru/articles/responsive-grid-system/images/12.png" alt="Внутренние разделённые отступы.">
    <figcaption>Внутренние разделённые отступы.</figcaption>
</figure>
<p>Обратили внимание, что в этом случае гораздо легче делать расчеты? Всё верно: это треть ширины сетки в каждой контрольной точке.</p>
<pre><code tabindex="0" class="language-css">.grid-item {
    width: 33.3333%;
    padding-right: 10px;
    padding-left: 10px;
    float: left;
}
</code></pre>
<p>Вот CodePen, чтобы вы могли поиграть:</p>
<figure>
    <iframe height="265" src="https://codepen.io/Lesnevskiy/embed/qqXrzB" title="Сетка с внутренними разделенными отступами." allowfullscreen></iframe>
    <figcaption>Сетка с внутренними разделенными отступами.</figcaption>
</figure>
<p>Прежде чем мы двинемся дальше, я хочу вас предостеречь, если вы используете разделенный <code>padding</code>. Если вы взглянете на разметку в CodePen, то увидите, что я добавил дополнительный <code>&lt;div&gt;</code> внутри <code>.grid-item</code>. Этот дополнительный <code>&lt;div&gt;</code> важен, если компонент содержит фон или рамки.</p>
<p>Всё потому, что фон отображается в границах <code>padding</code>. Надеюсь, эта картинка поможет вам разобраться, показав связь между <code>background</code> и другими свойствами.</p>
<figure>
    <img src="https://web-standards.ru/articles/responsive-grid-system/images/13.jpg" alt="Фон отображается на padding.">
    <figcaption>Фон отображается на `padding`.</figcaption>
</figure>
<h2>Что бы использовал я?</h2>
<p>Когда я начинал делать сетки два года назад, я в основном делал сетки, которые были спроектированы по <a href="https://zellwk.com/blog/designing-grids/#how-big-should-columns-and-gutters-be%3F">нисходящему подходу</a> и построены на <a href="https://zellwk.com/blog/designing-grids/#how-the-grid-responds-to-different-viewports">гибридной системе</a>. При таком подходе, я использовал процентные значения и для ширины, и для отступов.</p>
<p>В то время, я любил простоту настроек отступов с одной стороны колонки. Это было проще потому, что я не так хорош в математике. От дополнительных расчётов <code>отступы ÷ 2</code> я быстро вырубался.</p>
<p>Я рад, что я пошёл этим путем. Хоть CSS и выглядит более сложным, чем для разделенных отступов, я был вынужден <a href="https://css-tricks.com/examples/nth-child-tester/">изучить</a> селектор <code>nth-child</code>. Я также понял важность написания <a href="https://zellwk.com/blog/how-to-write-mobile-first-css/">CSS сначала для мобильных</a>. Насколько я могу судить, это до сих пор является главным препятствием и для молодых, и для опытных разработчиков.</p>
<p>Так или иначе, если вы попросите меня выбрать сейчас, я выберу разделенные отступы вместо односторонних, потому что CSS для них более простой. Также, я рекомендую использовать <code>margin</code> вместо <code>padding</code>, потому что разметка получается чище. Но <code>padding</code> легче рассчитать, поэтому я продолжу статью с ним.</p>
<h2>Шаг 6: создание отладочной сетки</h2>
<p>Когда вы только начинаете, особенно полезно иметь под рукой контрольную сетку, которая поможет отладить вашу разметку. Это помогает быть уверенным, что вы всё делаете правильно.</p>
<p>На сегодня, мне известен лишь кривой способ создания отладочной сетки. Нужно создать HTML-разметку и добавить к ней немного CSS. Вот так примерно выглядит HTML:</p>
<pre><code tabindex="0" class="language-html">&lt;div class=&quot;fixed-gutter-grid&quot;&gt;
    &lt;div class=&quot;column&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;column&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;column&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;column&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;column&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;column&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;column&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;column&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;column&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;column&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;column&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;column&quot;&gt;&lt;/div&gt;
&lt;/div&gt;
</code></pre>
<p>CSS для отладочной сетки выглядит следующим образом. Я использую разделенные внешние отступы для упрощения разметки отладочной сетки:</p>
<pre><code tabindex="0" class="language-css">.column {
    width: calc((100% - 20px * 12) / 12);
    height: 80px;
    margin-right: 10px;
    margin-left: 10px;
    background: rgba(0, 0, 255, 0.25);
    float: left;
}
</code></pre>
<figure>
    <iframe height="265" src="https://codepen.io/Lesnevskiy/embed/RoZVbG" title="Отладочная сетка с фиксированными отступами." allowfullscreen></iframe>
    <figcaption>Отладочная сетка с фиксированными отступами.</figcaption>
</figure>
<p><strong>Ремарка:</strong> Сьюзан Мириам и Собрал Робсон работают над <a href="https://github.com/oddbird/susy/issues/609">фоновым SVG изображением отладочной сетки для Susy v3</a>. Это очень захватывающе, так как вы можете использовать простую функцию для создания вашей отладочной сетки!</p>
<h2>Шаг 7: внесите изменения в раскладку</h2>
<p>Следующий шаг заключается во внесении изменений в раскладку на основе вашего контента. Именно здесь CSS-сетка засияет во всей красе. Вместо того, чтобы создавать разметку с написанием множества сеточных классов, вы можете создать для нее подходящее имя.</p>
<p>Для примера допустим, что у вас есть сетка для раскладки, которая используется только для гостевых статей. На десктопе раскладка выглядит примерно так:</p>
<figure>
    <img src="https://web-standards.ru/articles/responsive-grid-system/images/14.png" alt="Пример сетки для раскладки, которая используется только для гостевых статей.">
    <figcaption>Пример сетки для раскладки, которая используется только для гостевых статей.</figcaption>
</figure>
<p>Разметка для раскладки этой гостевой статьи может быть такой:</p>
<pre><code tabindex="0" class="language-html">&lt;div class=&quot;l-guest-article&quot;&gt;
    &lt;div class=&quot;l-guest&quot;&gt;
        &lt;!-- Профиль гостя --&gt;
    &lt;/div&gt;
    &lt;div class=&quot;l-main&quot;&gt;
        &lt;!-- Главная статья --&gt;
    &lt;/div&gt;
    &lt;div class=&quot;l-sidebar&quot;&gt;
        &lt;!-- Боковые виджеты --&gt;
    &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<p>Итак, сейчас у нас есть 12 колонок. Ширина одной колонки 8,333% (100 ÷ 12).</p>
<p>Ширина <code>.l-guest</code> равна двум колонкам. Поэтому вам нужно умножить 8,333% на два. Достаточно просто. Проделайте тоже самое для остальных элементов.</p>
<p>Здесь я предлагаю использовать препроцессор типа Sass, который позволит вам рассчитывать ширину колонок легче, используя функцию <code>percentage</code>, вместо расчетов вручную:</p>
<pre><code tabindex="0" class="language-scss">.l-guest-article {
    @include clearfix;
    .l-guest {
        // Хм, читается лучше, чем 16.666% :)
        width: percentage(2/12);
        padding-left: 10px;
        padding-right: 10px;
        float: left;
    }

    .l-main {
        width: percentage(7/12);
        padding-right: 10px;
        padding-left: 10px;
        float: left;
    }

    .l-sidebar {
        width: percentage(3/12);
        padding-right: 10px;
        padding-left: 10px;
        float: left;
    }
}
</code></pre>
<figure>
    <iframe height="265" src="https://codepen.io/Lesnevskiy/embed/QGMvLm" title="Сетка с фиксированными отступами для раскладки гостевой статьи." allowfullscreen></iframe>
    <figcaption>Сетка с фиксированными отступами для раскладки гостевой статьи.</figcaption>
</figure>
<p>Должно быть вы заметили, что сейчас часть кода повторяется. Мы можем это исправить, вынеся общие части кода в отдельный селектор <code>.grid-item</code>.</p>
<pre><code tabindex="0" class="language-scss">.grid-item {
    padding-left: 10px;
    padding-right: 10px;
    float: left;
}

.l-guest-article {
    .l-guest {
        width: percentage(2/12);
    }
    .l-main {
        width: percentage(7/12);
    }
    .l-sidebar {
        width: percentage(3/12);
    }
}
</code></pre>
<p>Ну вот, теперь гораздо лучше 🙂</p>
<h2>Шаг 8: создание вариаций раскладки</h2>
<p>Последний шаг — сделать вашу раскладку адаптивной. Давайте предположим, что раскладка нашей гостевой статьи ведет себя следующим образом:</p>
<figure>
    <img src="https://web-standards.ru/articles/responsive-grid-system/images/15.png" alt="">
</figure>
<p>Разметка нашей гостевой статьи не должна меняться. То, что у нас есть — это самая доступная раскладка из возможных. Так что, изменения должны быть полностью в CSS.</p>
<p>При написании CSS для нашей адаптивной гостевой раскладки, я настоятельно рекомендую вам писать <a href="https://zellwk.com/blog/mobile-first-css/">CSS сначала для мобильных</a>, потому что это делает ваш код проще и аккуратнее. В первую очередь, мы можем начать писать CSS для мобильной раскладки.</p>
<p>Вот код:</p>
<pre><code tabindex="0" class="language-scss">.l-guest-article {
    .l-guest {
        /* Здесь пусто */
    }
    .l-main {
        margin-top: 20px;
    }
    .l-sidebar {
        margin-top: 20px;
    }
}
</code></pre>
<p>Нам здесь нечего делать, каждый компонент занимает всю доступную ширину по умолчанию. Однако мы можем добавить верхний отступ к последним двум элементам, чтобы отделить их друг от друга.</p>
<p>Далее двигаемся к планшетной раскладке.</p>
<p>Давайте предположим, что для этой раскладки мы установим контрольную точку в 700 пикселей. <code>.l-guest</code> должен занимать 4 из 12 колонок, а <code>.l-main</code> и <code>.l-sidebar</code> по 8 колонок каждый.</p>
<p>Здесь нам надо удалить свойство <code>margin-top</code> у <code>.l-main</code>, потому что он должен быть на одной линии с <code>.l-guest</code>.</p>
<p>Также, если мы установим <code>.l-sidebar</code> ширину 8 колонок, он автоматически перейдет во второй ряд — в первом ряду не хватит места. Поскольку он находится во втором ряду, нам тоже нужно добавить внешний отступ слева у <code>.l-sidebar</code>, чтобы протолкнуть его на позицию. В качестве альтернативы, мы можем сделать его обтекаемым справа — я так и сделаю, это не требует лишних расчётов.</p>
<p>И поскольку мы сделали наши грид-элементы обтекаемыми, грид-контейнер должен включать клиар-фикс, чтобы очистить обтекание у дочерних элементов:</p>
<pre><code tabindex="0" class="language-scss">.l-guest-article {
    @include clearfix;
    .l-guest {
        @media (min-width: 700px) {
            width: percentage(4/12);
            float: left;
        }
    }
    .l-main {
        margin-top: 20px;
        @media (min-width: 700px) {
            width: percentage(8/12);
            margin-top: 0;
            float: left;
        }
    }
    .l-sidebar {
        margin-top: 20px;
        @media (min-width: 700px) {
            width: percentage(8/12);
            float: right;
        }
    }
}
</code></pre>
<p>Наконец, давайте перейдем к десктопной раскладке.</p>
<p>Допустим, для этой раскладки мы установим контрольную точку в 1200 пикселей. <code>.l-guest</code> будет занимать 2 из 12 колонок, <code>.l-main</code> — 7 из 12 и <code>.l-sidebar</code> — 3 из 12.</p>
<p>Для этого мы создаем новое медиавыражение в рамках каждого элемента сетки и изменяем ширину по необходимости. Обратите внимание, что мы также должны удалить верхний отступ у <code>.l-sidebar</code>.</p>
<pre><code tabindex="0" class="language-scss">.l-guest-article {
    @include clearfix;
    .l-guest {
        @media (min-width: 700px) {
            width: percentage(4/12);
            float: left;
        }

        @media (min-width: 1200px) {
            width: percentage(2/12);
        }
    }
    .l-main {
        margin-top: 20px;
        @media (min-width: 700px) {
            width: percentage(8/12);
            margin-top: 0;
            float: left;
        }
        @media (min-width: 1200px) {
            width: percentage(7/12);
        }
    }
    .l-sidebar {
        margin-top: 20px;
        @media (min-width: 700px) {
            width: percentage(8/12);
            float: right;
        }
        @media (min-width: 1200px) {
            width: percentage(3/12);
            margin-top: 0;
        }
    }
}
</code></pre>
<p>Вот CodePen с финальной раскладкой, которую мы создали:</p>
<figure>
    <iframe height="265" src="https://codepen.io/Lesnevskiy/embed/zodwOQ" title="Сетка с фиксированными отступами для раскладки гостевой статьи, финальная версия." allowfullscreen></iframe>
    <figcaption>Сетка с фиксированными отступами для раскладки гостевой статьи, финальная версия.</figcaption>
</figure>
<p>О, кстати, вы можете добиться таких же результатов со Susy. Только <a href="https://zellwk.com/blog/susy-gutter-positions/">не забудьте</a> выставить <code>gutter-position</code> в <code>inside-static</code>.</p>
<h2>Подводя итог</h2>
<p>Ого. Длинная получилась статья. Я думал, трижды помру, пока писал её. Спасибо, что дочитали до конца. Надеюсь, вы не померли трижды, пока читали! 😛</p>
<p>Как вы могли заметить, в этой статье шаги для создания адаптивной сетки относительно простые. Большинство путается на шагах 5 (определение положения отступов) и 8 (адаптация раскладки).</p>
<p>5-й шаг прост, когда вы учитываете все возможные способы, и мы разобрали их вместе. 8-й шаг проходится легче, если у вас достаточно опыта в написании <a href="https://zellwk.com/blog/mobile-first-css/">CSS сначала для мобильных</a>.</p>
<p>Надеюсь, эта статья дала вам знания для построения вашей собственной адаптивной сетки, и я надеюсь увидеть как вы строите специально созданную сетку для вашего следующего проекта.</p>
<p>До скорого!</p>

                    ]]></description><pubDate>Tue, 03 Oct 2017 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/responsive-grid-system/</guid></item><item><title>Невыполненные обещания веб-компонентов</title><link>https://web-standards.ru/articles/broken-promise-web-comps/</link><description><![CDATA[
                        <p><strong>Дисклеймер:</strong> в статье я часто говорю о React, но вы можете понимать под ним любую библиотеку на JavaScript: Inferno, Preact, Vue, Snabbdom, Virtual DOM или без него: Elm, Om и другие. Точно так же под Polymer можно иметь в виду Vaadin или X-Tag, или…</p>
<p>Кроме того, прочитайте <a href="https://medium.com/p/385d63de4959">великолепный ответ</a> Роба Додсона.</p>
<h2>Короткая, неполная и часто неточная история веб-компонентов</h2>
<h3>Древность</h3>
<p>В 2011 интернет сделал прорыв. Со всеми его фейсбуками, гмейлами, гугл-доками и массой других <em>онлайн-вещиц</em>, которые уже нельзя было называть сайтами. Во всех смыслах это были полноценные приложения — одностраничные веб-приложения, или SPA (Single Page Application). Так просто.</p>
<p>Но их разработчикам приходилось непросто…</p>
<p>Уровень развития веб-интерфейсов в то время подразумевал использование шаблонизаторов, основанных на симбиозе серверной логики или клиентских библиотек: <a href="http://backbonejs.org/">Backbone</a> — для тех, кому повезло больше, <a href="http://jqueryui.com/">jQuery UI</a> или <a href="https://www.sencha.com/products/extjs/">Sencha/ExtJs</a> — если в вас был дух энтерпрайза.</p>
<p>Это было неудобно. Это было негибко. Прототипировать быстро и легко — невозможно. UI-библиотеки накладывали свои ограничения. Список проблем можно продолжить.</p>
<p>Это при том, что в нашем распоряжении был набор всё тех же HTML-элементов: <code>&lt;div&gt;</code>, <code>&lt;p&gt;</code>, <code>&lt;form&gt;</code>…</p>
<p>И вот, в 2011 Алекс Рассел предложил <a href="https://fronteers.nl/congres/2011/sessions/web-components-and-model-driven-views-alex-russell">свое видение будущего</a> (акценты в тексте — мои):</p>
<blockquote>
<p>Мне кажется, сегодня мы застряли в ловушке расширяемости. Мы зацикливаемся на JavaScript, так как это базовый язык нашей экосистемы. Он один выручает нас, когда CSS и HTML не справляются. И вот мы набиваемся в спасательную шлюпку под названием JavaScript. Мы снова и снова делаем это.
Вчера Брюс указал на случай с пустым <code>&lt;body&gt;</code> как примером того самого патологического желания запихнуть всё в JavaScript. Вы цепляетесь за него, пытаясь с его помощью воссоздать всё, что браузер и сам в общем-то способен сделать: передаете по цепочке скрипта кусок разметки, чтобы в конце концов получить тот же результат, как если бы добавили его сразу в HTML. Но есть причина. Gmail содержит пустой <code>&lt;body&gt;</code> не потому, что он сделан глупо. Он содержит пустой <code>&lt;body&gt;</code>, потому что это одновременно выразительный, надёжный и легко поддерживаемый способ реализовать нужную функциональность. А вы пытаетесь сыграть ва-банк, положить все яйца в одну корзину под названием JavaScript.
Браузерам требуется время, чтобы внедрить новые «фичи». Нам нужно сначала выпустить их для пользователей, те должны начать их использовать, после этого можно замерить аудиторию и только тогда сказать «классно, можно использовать эту фичу, теперь она будет полезна».
Это то, от чего выигрывают разработчики. Чем раньше возникнет интерес, тем больше шансов увидеть новинки на рынке и начать их использовать. Тогда все скажут: «да, это классная фича».
<strong>Таким образом, мы скованы невысказанной дилеммой между глубоким прагматизмом и стремлением к платоновскому идеалу.</strong> Но нам не хватает правильной модели, чтобы осмыслить это.
Чем больше функциональности или поведения [привычных разработчикам и реализованных в браузерах] мы забираем на себя, чем больше мы пытаемся сделать «руками» на JavaScript, тем дальше отходим от той идеи негласной договоренности, на которой держится весь мир.
<strong>Мы намерены поддерживать прогресс.</strong> На мой взгляд, мы застряли на обсуждении HTML5, и уже хватит.</p>
</blockquote>
<p>Дальше он показывает демо и рассказывает о веб-компонентах, под которыми в то время подразумевали три разные вещи: инкапсуляцию CSS, Shadow DOM и веб-компоненты. (Я тоже, в целом, рекомендую его доклад).</p>
<p>Но потом случился <a href="https://www.w3.org/">W3C</a>. В лучших традициях он взял инициативу в свои руки и потратил следующие пять лет на поиск того самого платоновского идеала, позабыв о практическом результате.</p>
<h3>Революция Facebook</h3>
<p>Facebook — это сложно устроенное приложение. Возможно, это не очевидно, но это так. Эти маленькие разбросанные по странице блоки — полноценные компоненты. Они должны <em>реиспользоваться, настраиваться и адаптироваться</em> под разный контекст. Разработка с ними должна строиться буквально так: взять элемент <code>box</code>, вставить его в <em>этом месте</em> на странице, применить какие-то стили и при этом не сломать отображение других элементов.</p>
<p>И это должно работать относительно быстро, так как известно: DOM обновляется медленно. Есть бесчисленное количество статей, объясняющих, что минимальное количество обращений к DOM — <em>абсолютная необходимость.</em></p>
<p>Печально, что <code>innerHTML</code> (признак дурного кода) по скорости работы — <a href="http://andrew.hedges.name/experiments/innerhtml/">на одном уровне или быстрее</a> функций управления DOM.</p>
<p>И что же придумал Facebook? Всё просто: они просто написали собственную реализацию веб-компонентов полностью на JavaScript. С XML-подобным синтаксисом внутри. Его назвали <a href="https://facebook.github.io/react/">React</a> и показали миру в 2013 году.</p>
<p>Reaсt позволяет следующее:</p>
<ul>
<li>создавать собственные кастомные элементы на основе HTML-подобного синтаксиса;</li>
<li>вставлять их в DOM;</li>
<li>использовать виртуальный DOM, сводя к минимуму работу напрямую с DOM браузера;</li>
<li>имеет всего несколько ограничений по работе с компонентами и внутри них (так как всё в React — JavaScript, его DSL лишь обертка над небольшим количеством функций).</li>
</ul>
<p>Не удивительно, что React стремительно завоевал свое место в мире веба. Кто не использовал библиотеку — обязательно говорил о ней. Очень быстро появились похожие и конкурирующие инструменты (<a href="https://infernojs.org/">Inferno</a>, <a href="https://preactjs.com/">Preact</a>) или некоторые части для работы с виртуальным DOM (<a href="https://github.com/snabbdom/snabbdom">Snabbdom</a>, <a href="https://github.com/Matt-Esch/virtual-dom">Virtual DOM</a> и другие).</p>
<h3>2017</h3>
<p>К 2017 React смог реализовать всё то, что ждали от спецификации веб-компонентов. Он позволяет создавать независимые реиспользуемые компоненты и работает практически во всех поддерживающих JavaScript браузерах (не поддерживается в IE &lt; 9).</p>
<p>Расцвет экосистемы пошёл дальше идеи веб-компонентов. Если я не ошибаюсь, <a href="https://github.com/css-modules/css-modules">CSS-модули</a> впервые реализовали в React и для React.</p>
<p>В 2017 спецификация веб-компонентов всё ещё <a href="https://caniuse.com/#search=web%20components">в разработке</a>, и это несмотря на выход двух версий для каждого из двух базовых стандартов. На момент написания статьи (март 2017) ситуация с поддержкой такая:</p>
<img src="https://web-standards.ru/articles/broken-promise-web-comps/images/caniuse.jpg" alt="Таблица поддержки веб-компонентов с «Can I use…».">
<h2>Так о каких невыполненных обещаниях идёт речь?</h2>
<p>Ну, первый провал очевиден: что-то их нигде нет. Обещание «поддерживать прогресс» не выполнено. За шесть лет веб-компоненты породили шесть стандартов. Два из них успели устареть. Только один из основных браузеров вкладывается в поддержку (прости Opera, но ты больше не входишь в их число и к тому же работаешь на основе Chromium).</p>
<p>Другое невыполненное обещание, его сегодня много обсуждают в интернетах — зависимость от сторонних библиотек. И это вынуждает оставить здесь этот излишне длинный кусочек размышлений вслух. Потому, что есть <a href="https://www.polymer-project.org/">Polymer</a>.</p>
<p>Polymer — это попытка Google создать совместимую с веб-компонентами реализацию:</p>
<blockquote>
<p><strong>Откройте возможности веб-компонентов.</strong> Polymer — JavaScript-библиотека, которая помогает создавать кастомные реиспользуемые HTML-элементы для быстрых и простых в поддержке приложений.</p>
</blockquote>
<p>Polymer демонстрирует главный минус веб-компонентов — DOM.</p>
<h3>DOM</h3>
<p>Разберём следующий код на React:</p>
<pre><code tabindex="0" class="language-jsx">&lt;MyComponent style={{border: '1px solid gray'}}&gt;
    {
        ['Hello', 'world'].map((text) =&gt; &lt;p&gt;{text}&lt;/p&gt;)
    }
&lt;/MyComponent&gt;
</code></pre>
<p>Перед нами кастомный компонент. Его стили определены в формате JavaScript-объекта. Его дочерние элементы — результат исполнения метода map и, одновременно, ещё один компонент. В данном случае, это <code>&lt;p&gt;</code>, но могло быть что угодно. Внутри — текущее значение элемента массива.</p>
<p>Этот XML-подобный DSL напрямую <a href="https://babeljs.io/repl/#?babili=false&amp;evaluate=true&amp;lineWrap=false&amp;presets=react%2Cstage-2&amp;targets=&amp;browsers=&amp;builtIns=false&amp;code=%3CMyComponent%20style%3D%7B%7Bborder%3A%20'1px%20solid%20gray'%7D%7D%3E%0A%20%20%7B%0A%20%20%20%20%20%5B'Hello'%2C%20'world'%5D.map((text)%20%3D%3E%20%3Cp%3E%7Btext%7D%3C%2Fp%3E)%0A%20%20%7D%0A%3C%2FMyComponent%3E&amp;#babili=false">транслируется в JavaScript</a>:</p>
<pre><code tabindex="0" class="language-js">React.createElement(
    MyComponent,
    { style: { border: '1px solid gray' } },
    ['Hello', 'world'].map(text =&gt; React.createElement(
        'p',
        null,
        text
    ));
);
</code></pre>
<p>А как бы выглядел тот же трюк на веб-компонентах? Ну…</p>
<p>Если взять HTML как контрпример для JSX, то ничего не выйдет:</p>
<pre><code tabindex="0" class="language-html">&lt;my-component style=&quot;только строки для атрибутов&quot;&gt;
    … здесь ничего …
    здесь можно использовать только другой компонент или текст
&lt;/my-component&gt;
</code></pre>
<p>Ну а что с JS API? Помните, я говорил, что веб-компоненты — это DOM?</p>
<pre><code tabindex="0" class="language-js">const MyComponent = document.createElement('my-component');
MyComponent.style.border = '1px solid gray';

['Hello', 'world'].forEach(('text') =&gt; {
    const p = document.createElement('p');
    p.textContent(text);

    MyComponent.appendChild(p);
});
</code></pre>
<p>Код компонента будет неуклонно расти при усложнении каждого элемента. Представьте, мы хотим обернуть <code>text</code> внутри <code>&lt;p&gt;</code> в <code>&lt;span&gt;</code>?</p>
<p>Сделать это на React? Раз плюнуть. Просто добавляем:</p>
<pre><code tabindex="0" class="language-jsx">['Hello', 'world'].map((text) =&gt; {
    return &lt;p&gt;&lt;span&gt;{text}&lt;/span&gt;&lt;/p&gt;
});
</code></pre>
<p>А на веб-компонентах:</p>
<pre><code tabindex="0" class="language-js">['Hello', 'world'].forEach(('text') =&gt; {
    const p = document.createElement('p');
    const span = document.createElement('span');
    span.textContent(text);
    p.appendChild(span);

    MyComponent.appendChild(p);
})
</code></pre>
<p>Ad infinitum.</p>
<h3>Ломаем совместимость</h3>
<p>Итак, представим, как описанные выше проблемы с самого начала возникли на пути Polymer. Что делать? Ну, можно создать что-то вроде собственного языка: «не-совсем-но-типа-джаваскрипт», чтобы и разметку умел и немного логики. И чтобы в виде строк.</p>
<p>Чтобы разобраться, пройдитесь по <a href="https://www.polymer-project.org/2.0/docs/devguide/data-system">data system</a> из Polymer. Здесь я покажу только несколько примеров:</p>
<p><strong>Обратите внимание:</strong> <code>[[]]</code>, <code>{{}}</code>, <code>$=</code> и другие — не являются частью спецификации веб-компонентов.</p>
<pre><code tabindex="0" class="language-jsx">&lt;template&gt;
    &lt;div&gt;[[name.first]] [[name.last]]&lt;/div&gt;
&lt;/template&gt;

&lt;my-input value=&quot;{{name}}&quot;&gt;&lt;/my-input&gt;

static get properties() {
    return {
        active: {
            type: Boolean,
            observer: 'userListChanged(users.*, filter)'
        }
    }
}

&lt;div&gt;[[_formatName(first, last, title)]]&lt;/div&gt;

&lt;a href$=&quot;{{hostProperty}}&quot;&gt;
</code></pre>
<p>И так далее. А вот <a href="https://www.polymer-project.org/2.0/docs/devguide/data-binding#annotated-computed">моё любимое</a>:</p>
<blockquote>
<p><strong>Запятая внутри строки:</strong> каждая запятая внутри строки <strong>должна</strong> обязательно экранироваться символом <code>\</code>.</p>
</blockquote>
<pre><code tabindex="0" class="language-html">&lt;dom-module id=&quot;x-custom&quot;&gt;
    &lt;template&gt;
        &lt;span&gt;
            {{translate('Hello\, nice to meet you', first, last)}}
        &lt;/span&gt;
    &lt;/template&gt;
&lt;/dom-module&gt;
</code></pre>
<p>Ну то есть, <a href="https://www.destroyallsoftware.com/talks/wat">WAT</a>.</p>
<h2>Ну серьёзно</h2>
<p>А теперь серьёзно. Едва ли можно сказать, что веб-компоненты реализовали хотя бы часть возлагавшихся на них надежд. Едва ли они смогли ответить на стоявшие перед ними вызовы.</p>
<p>Спецификации зависят от JavaScript:</p>
<ul>
<li>кастомные элементы <a href="https://html.spec.whatwg.org/multipage/scripting.html#custom-elements">работают на JS</a>;</li>
<li>HTML-шаблоны существует только для <a href="https://html.spec.whatwg.org/multipage/scripting.html#the-template-element">обработки скриптом</a>;</li>
<li>я вообще не уверен, может ли <a href="https://www.w3.org/TR/shadow-dom/">Shadow DOM</a> работать без JavaScript;</li>
<li>и только <a href="https://web.archive.org/web/20191203103432/http://w3c.github.io/webcomponents/spec/imports/">HTML-импорты</a> не нуждаются в JS.</li>
</ul>
<p>Веб-компоненты — это DOM, поэтому:</p>
<ul>
<li><a href="https://html.spec.whatwg.org/#attributes">атрибуты</a> — только строчные значения;</li>
<li><a href="https://html.spec.whatwg.org/#kinds-of-content">типизация содержимого</a> непонятная — нужно ещё посмотреть, как такая модель <a href="https://html.spec.whatwg.org/multipage/scripting.html#custom-elements-autonomous-drawbacks">будет работать</a> для структур с большой вложенностью.</li>
</ul>
<p>Чтобы работать с ограничениями (вроде строчных атрибутов) библиотекам придётся (а куда деваться?) изобретать различные несовместимые друг с другом способы передачи параметров:</p>
<ul>
<li>Разве <code>attr$='{{user.name}}'</code> из Polymer лучше или совместимее, чем <code>item-label-path=&quot;name.first&quot;</code> из Vaadin или <code>&lt;div *ngFor=&quot;let hero of heroes&quot;&gt;{{hero.name}}&lt;/div&gt;</code> из Angular?</li>
<li>Где и какую мне нужно импортировать библиотеку Икс, чтобы справиться с их странными способами обхода ограничений DOM, если я имею дело с несколькими вложенными компонентами?</li>
<li>DOM API ужасны. Они неудобные, негибкие и неэффективные. Polymer сотоварищи делают отважные попытки работать с ним напрямую, но всё же обращаются к <code>innerHTML</code> в случаях, когда это не так принципиально (например, в <a href="https://github.com/Polymer/polymer/search?utf8=%E2%9C%93&amp;q=innerHTML">тестах</a>). Если веб-компоненты укоренятся, интернет будет заполнен неэффективным <code>innerHTML</code> и, вероятно, новыми вариациями Snabbdom и Virtual-DOM.</li>
<li>Как это поможет интеграции и преодолению зависимости от инструментов или библиотек, если каждый использует свою?</li>
</ul>
<p>Изоляция стилей…</p>
<ul>
<li>CSS-модули. Мне продолжать?</li>
</ul>
<p>Всё это — только часть проблем. Те из них, которые в первую очередь приходят мне в голову. И я не встречал их серьезного обсуждения сообществом.</p>
<p>Команда React и вовсе заявила:</p>
<blockquote>
<p>Мы не собираемся использовать веб-компоненты на Facebook. У нас нет планов интегрировать их с React, так как существует большая разница на уровне самих моделей — императивной в веб-компонентах и декларативной в React. В веб-компонентах нет нормального способа идиоматически описать вещи, вроде того, куда идут события. Как вы передадите данные, если все описано строками? Мы рассматриваем их больше как слой, помогающий различным фреймворкам взаимодействовать друг с другом.</p>
</blockquote>
<p>Сейчас React позволяет использовать их в качестве <a href="https://www.youtube.com/watch?t=124&amp;v=g0TD0efcwVg">конечных узлов</a> дерева компонента, так как любой компонент с именем, начинающимся со строчной буквы, React интерпретирует как DOM-элемент.</p>
<p>Цитирую Пита Ханта:</p>
<blockquote>
<p>В веб-компонентах много приятного. Например, возможность кастомизировать <code>&lt;select&gt;</code>. Так что в них много хорошего, однако они не годятся для структурирования приложений. React же нацелен на решение таких задач: создание структуры и управление DOM. Веб-компоненты — продолжение обычного DOM API. Я придерживаюсь такого мнения: эта спека стандартизирует худшие практики.</p>
</blockquote>
<p>Все эти вопросы поднимаются крайне редко, если не брать в расчет комментарии к <a href="http://2ality.com/2015/08/web-component-status.html">некоторым статьям</a> и обсуждения в Твиттере. Складывается ощущение, будто все согласны, что «веб-компоненты — это славное общедоступное и скорое будущее».</p>
<p>Так ли это?</p>
<h2>Примечание</h2>
<ul>
<li>Изображение: <a href="https://www.flickr.com/photos/yoorock/29946893014/">Nobody Home Yet</a> Рика Херрмана, <a href="https://creativecommons.org/licenses/by-nc-nd/2.0/">CC BY-NC-ND 2.0</a></li>
<li>Ссылки на некоторые материалы были взяты из <a href="http://2ality.com/2015/08/web-component-status.html">статьи</a> Акселя Раушмайера.</li>
</ul>

                    ]]></description><pubDate>Mon, 17 Apr 2017 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/broken-promise-web-comps/</guid></item><item><title>Реализуем пуш-уведомления на фронтенде и бэкенде</title><link>https://web-standards.ru/articles/push-front-back/</link><description><![CDATA[
                        <p>В предыдущей <a href="https://justmarkup.com/log/2017/02/introducing-iss-observer-com/">статье</a> об <a href="https://iss-observer.com/">iss-observer.com</a> я обещал рассказать о технической стороне реализации пуш-уведомлений. Изначально я планировал сконцентрироваться на проблемах, с которыми пришлось иметь дело в процессе работы над <a href="https://iss-observer.com/">iss-observer.com</a>. Теперь я думаю, будет полезнее посвятить материал базовым вопросам, и уточнять детали, где это необходимо. Обращаю ваше внимание, что фронтенд частично опирается на <a href="https://developers.google.com/web/fundamentals/getting-started/codelabs/push-notifications/?hl=en">этот урок</a>.</p>
<p><em>Если вы хотите углубиться в исходный код, то смотрите в <a href="https://github.com/justmarkup/demos/tree/gh-pages/push-notifications">GitHub-репозиторий</a> и на <a href="https://push-notifications-vwursywdxa.now.sh">пример</a>, демонстрирующий его работу.</em></p>
<img src="https://web-standards.ru/articles/push-front-back/images/1.jpg" alt="">
<h2>Фронтенд</h2>
<p>Начнем с клиентской части. Первое, что нужно сделать — убедиться, что браузер поддерживает пуш-уведомления. Если да, загружаем наш JavaScript.</p>
<pre><code tabindex="0" class="language-js">if ('serviceWorker' in navigator &amp;&amp; 'PushManager' in window) {
    var s = document.createElement('script');
    s.src = '/script/push.js';
    s.async = true;
    document.head.appendChild(s);
}
</code></pre>
<p>Прежде чем писать код, выполним ряд требований. Нам потребуются Application Server Keys (VAPID Key). Получаем их <a href="https://web-push-codelab.appspot.com">здесь</a>, либо с помощью библиотеки web-push. Кстати, она нам еще потребуется для бэкенд-части. Устанавливаем библиотеку: <code>npm install -g web-push</code>, генерируем ключи: <code>web-push generate-vapid-keys</code>. В независимости от способа в результате у вас должны быть <strong>закрытый ключ</strong> (private key) и <strong>открытый ключ</strong> (public key). Сохраните их в надежном месте.</p>
<p><em>До появления спецификации Application server key/VAPID первые браузеры (Chrome, Opera) реализовали функциональность пуш-уведомлений с помощью <a href="https://developers.google.com/cloud-messaging/">Google Cloud Messaging</a>. Все современные браузеры, за исключением <a href="https://github.com/web-push-libs/web-push#browser-support">Samsung Internet</a>, поддерживают VAPID. Поэтому в этой статье я не буду касаться GCM. Как добавить поддержку для Samsung Internet и старых версий Chrome и Opera — читайте <a href="https://web-push-book.gauntface.com/chapter-06/01-non-standards-browsers/">здесь</a>.</em></p>
<p>Посмотрим на push.js. Здесь мы регистрируем сервис-воркер и подписываемся на уведомления:</p>
<pre><code tabindex="0" class="language-js">'use strict';

const appServerKey = 'BHLCrsFGJQIVgg-XNp8F59C8UFF49GAVxvYMvyCURim3nMYI5TMdsOcrh-yJM7KbtZ3psi5FhfvaJbU_11jwtPY';

const pushWrapper = document.querySelector('.push-wrapper');
const pushButton = document.querySelector('.push-button');

let hasSubscription = false;
let serviceWorkerRegistration = null;
let subscriptionData = false;

function urlB64ToUint8Array(base64String) {
    const padding = '='.repeat((4 - base64String.length % 4) % 4);
    const base64 = (base64String + padding)
        .replace(/\-/g, '+')
        .replace(/_/g, '/');

    const rawData = window.atob(base64);
    const outputArray = new Uint8Array(rawData.length);

    for (let i = 0; i &lt; rawData.length; ++i) {
        outputArray[i] = rawData.charCodeAt(i);
    }
    return outputArray;
}

function updatePushButton() {
    pushWrapper.classList.remove('hidden');

    if (hasSubscription) {
        pushButton.textContent = 'Disable Push Notifications';
    } else {
        pushButton.textContent = 'Enable Push Notifications';
    }
}

function subscribeUser() {
    serviceWorkerRegistration.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: urlB64ToUint8Array(appServerKey)
    })
    .then(function(subscription) {

        fetch('/push/subscribe',{
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(subscription)
        })
        .then(function(response) {
            return response;
        })
        .then(function(text) {
            console.log('User is subscribed.');
            hasSubscription = true;

            updatePushButton();
        })
        .catch(function(error) {
            hasSubscription = false;
            console.error('error fetching subscribe', error);
        });

    })
    .catch(function(err) {
        console.log('Failed to subscribe the user: ', err);
    });
}

function unsubscribeUser() {
    serviceWorkerRegistration.pushManager.getSubscription()
    .then(function(subscription) {
        if (subscription) {
            subscriptionData = {
                endpoint: subscription.endpoint
            };

            fetch('/push/unsubscribe',{
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(subscriptionData)
            })
            .then(function(response) {
                return response;
            })
            .then(function(text) {
                hasSubscription = false;

                updatePushButton();
            })
            .catch(function(error) {
                hasSubscription = true;
                console.error('error fetching subscribe', error);
            });

            hasSubscription = false;

            updatePushButton();
            return subscription.unsubscribe();
        }
    });
}

function initPush() {

    pushButton.addEventListener('click', function() {
        if (hasSubscription) {
            unsubscribeUser();
        } else {
            subscribeUser();
        }
    });

    // Set the initial subscription value
    serviceWorkerRegistration.pushManager.getSubscription()
    .then(function(subscription) {
        hasSubscription = !(subscription === null);

        updatePushButton();
    });
}

navigator.serviceWorker.register('sw.js')
.then(function(sw) {
    serviceWorkerRegistration = sw;
    initPush();
})
.catch(function(error) {
    console.error('Service Worker Error', error);
});
</code></pre>
<p>В первую очередь создаем константу <code>addServiceKey</code>: ей присваиваем значение с открытым ключ VAPID (о нем мы говорили выше). Также создаем несколько элементов и переменных. Записываем функцию <code>urlB64ToUint8Array()</code>: она понадобится для конвертации ключа из base64 в <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array">Uint8Array</a>.</p>
<p>Затем декларируем функцию <code>updatePushButton()</code>. Мы будем вызывать её каждый раз при изменении статуса уведомлений, чтобы обновить отвечающие за него элементы интерфейса.</p>
<p>Далее видим функцию регистрации подписки <code>subscribeUser()</code>. Как вы наверно заметили, в начале скрипта была объявлена переменная <code>let serviceWorkerRegistration</code>. Она содержит результат <a href="https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration">регистрации сервис-воркера</a>: посмотрите в конец файла, мы записываем его в переменную в момент регистрации.</p>
<pre><code tabindex="0" class="language-js">serviceWorkerRegistration.pushManager.subscribe({
    userVisibleOnly: true,
    applicationServerKey: urlB64ToUint8Array(appServerKey)
})
</code></pre>
<p>Метод <code>subscribe()</code> возвращает промис, а в качестве аргумента принимает объект с двумя свойствами:</p>
<ul>
<li><code>userVisibilityOnly</code>: булево значение. Параметр сообщает, что подписка будет использоваться только для сообщений, эффект которых виден для пользователя. Устанавливаем значение <code>true</code>.</li>
<li><code>applicationServiceKey</code>: номер открытого ключа, он используется сервером для отправки уведомлений. Так как ключ должен быть в формате <code>UInt8Attay</code>, используем уже знакомую нам функцию.</li>
</ul>
<pre><code tabindex="0" class="language-js">.then(function(subscription) {
    fetch('/push/subscribe',{
        method: &quot;POST&quot;,
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(subscription)
    })
</code></pre>
<p>Теперь отправляем данные подписки на сервер. Смело используем Fetch API, так как все браузеры, которые поддерживают пуш-уведомления, поддерживают и Fetch. После получения ответа выводим результат — завершена ли подписка успешно.</p>
<p>Этот этап прекрасно подходит для уточнения нужных параметров у пользователя. Например, на <a href="https://iss-observer.com">iss-observer.com</a> он может выбрать время получения уведомлений (утро и/или вечер). Я также отправляю на сервер данные о стране, регионе и городе, по которому пользователь хочет получать уведомления; данные сохраняются и могут потребоваться при отправке уведомлений.</p>
<p>После этого, создаем функцию отписки <code>unsubscribeUser()</code>. Используем метод <code>getSubscription()</code> объекта PushManager, с его помощью получаем детали подписки, которые отправляем на сервер (снова Fetch API). На этот раз, чтобы удалить её базы.</p>
<p>Записываем функцию <code>initPush()</code>. В ней — событие для кнопки <code>pushButton</code>, которое вызывает функцию подписки или отписки в зависимости от текущего состояния. После, не забываем обновить это состояние. Использованный в примере код можно найти на <a href="https://github.com/justmarkup/demos/blob/gh-pages/push-notifications/public/script/push.js">GitHub</a>.</p>
<p>Последний шаг — регистрация сервис-воркера.</p>
<h2>Сервис-воркер</h2>
<p>В этой части мы разберем два необходимых для реализации пуш-уведомлений события: push и notificationonclick.</p>
<pre><code tabindex="0" class="language-js">self.addEventListener('push', function(event) {
    let notificationData = {};

    try {
        notificationData = event.data.json();
    } catch (e) {
        notificationData = {
            title: 'Default title',
            body: 'Default message',
            icon: '/default-icon.png'
        };
    }

    event.waitUntil(
        self.registration.showNotification(notificationData.title, {
            body: notificationData.body,
            icon: notificationData.icon
        })
    );
});
</code></pre>
<p>Начнем с <code>push</code>. Проверяем содержимое объекта <code>notificationData</code> (свойства <code>title</code>, <code>body</code> и <code>icon</code>), и, если не находим их, присваиваем дефолтные значения. После вызываем метод <code>showNotification</code>, он покажет уведомление пользователю.</p>
<p><em>В дополнение к трём перечисленным свойствам могут использоваться и другие, например, <code>badge</code>, <code>tag</code>, <code>vibrate</code>. На момент написания этой статьи (февраль 2017) многие из них поддерживались только некоторыми браузерами. <code>title</code>, <code>body</code> и <code>icon</code>, доступных во всех браузерах, поэтому ограничимся ими.</em></p>
<pre><code tabindex="0" class="language-js">self.addEventListener('notificationclick', function(event) {
    // close the notification
    event.notification.close();

    // see if the current is open and if it is focus it
    // otherwise open new tab
    event.waitUntil(
        self.clients.matchAll().then(function(clientList) {
            if (clientList.length &gt; 0) {
                return clientList[0].focus();
            }

            return self.clients.openWindow('/');
        })
    );
});
</code></pre>
<p>Событие <code>notificationOnClick</code> срабатывает в момент клика по уведомлению. Сначала закрываем уведомление. Затем проверяем, открыт ли наш сайт в текущей вкладке браузера, если нет, то открываем его с помощью <code>openWindow()</code>.</p>
<h2>Бэкенд</h2>
<p>Переходим к серверной части, в которой мы используем библиотеку <a href="https://github.com/web-push-libs/web-push">web-push</a>. В нашем случае это реализация библиотеки для Node.js, но версии для PHP, Java и C# также доступны.</p>
<p><em>Я предполагаю, что у вас есть базовые знания Node.js и опыт использования Express. В ином случае, я рекомендую вам ознакомиться с ними прежде чем продолжить.</em></p>
<p>Итак, в первую очередь:</p>
<ul>
<li>устанавливаем библиотеку командой <code>npm install web-push --save</code>,</li>
<li>получаем доступ к ней с помощью <code>require</code>: <code>const webPush = require('web-push')</code></li>
</ul>
<pre><code tabindex="0" class="language-js">webPush.setVapidDetails(
    'mailto:hallo@justmarkup.com',
    'YOUR_PUBLIC_VAPID_KEY', // process.env.VAPID_PUBLIC_KEY,
    'YOUR_PRIVATE_VAPID_KEY', // process.env.VAPID_PRIVATE_KEY
);
</code></pre>
<p>Теперь передаем данные VAPID. Помимо сгенерированной в начале пары ключей, нужно указать адрес электронной почты (с префиксом <code>mailto:</code>) либо URL сайта. Контактные данные могут потребоваться сервису для связи с вами. Обратите внимание на комментарии: я предпочел сохранить ключи в переменную окружения. Вы можете поступить так же или выбрать свой метод, но главное помните, <strong>закрытый ключ</strong> должен быть всегда защищен от обращений извне. Собственно поэтому он так и назван.</p>
<p>Переходим к функции подписки:</p>
<pre><code tabindex="0" class="language-js">app.post('/push/subscribe', function (req, res) {

    const subscription = {
        endpoint: req.body.endpoint,
        keys: {
            p256dh: req.body.keys.p256dh,
            auth: req.body.keys.auth
        }
    };

    const payload = JSON.stringify({
        title: 'Welcome',
        body: 'Thank you for enabling push notifications',
        icon: '/android-chrome-192x192.png'
    });

    const options = {
        TTL: 3600 // 1sec * 60 * 60 = 1h
    };

    webPush.sendNotification(
        subscription,
        payload,
        options
        ).then(function() {
            console.log('Send welcome push notification');
            res.status(200).send('subscribe');
            return;
        }).catch(err =&gt; {
            console.error('Unable to send welcome push notification', err );
            res.status(500).send('subscription not possible');
            return;
        }
    );
});
</code></pre>
<p>В функции отправки подписки на сервер, получаем доступ к объекту <code>subscription</code>. В нем — значение endpoint и ключи доступа. Здесь я не буду останавливаться на вопросах работы с базой данных. Для примера укажу только, что для демо использована <a href="https://github.com/justmarkup/demos/blob/gh-pages/push-notifications/controllers/push.js#L19">Mongo DB</a>.</p>
<p><em>Прим. переводчика: <code>endpoint</code> — это уникальный URI, создаваемый для каждого пользователя индивидуально в соответствии <a href="https://random-push-service.com/some-kind-ofunique-id-1234/v2/">с паттерном</a>: <code>p256dh</code> — открытый ключ, <code>auth</code> — закрытый ключ.</em></p>
<p>Затем получаем наше первое уведомление — то, которое приветствует подписавшегося пользователя. Метод <code>sendNotification()</code> принимает три аргумента:</p>
<ul>
<li>данные подписки, получаемые от браузера;</li>
<li>информацию для пользователя (заголовок, сообщение, иконка — свойства <code>title</code>, <code>body</code>, <code>icon</code> соответственно);</li>
<li>объект <code>options</code>, <a href="https://github.com/web-push-libs/web-push#sendnotificationpushsubscription-payload-options">см. подробнее</a>.</li>
</ul>
<p><em>TTL (Time To Live) — срок жизни уведомления — по умолчанию четыре недели. Это значит, что оно будет ожидать появления пользователя онлайн в течение этого срока. Например, если вы отправили уведомление пользователю в офлайне, и он подключится к сети только через две недели, сообщение все равно будет доставлено. В моем случае разумно изменить TTL на более короткий срок.</em></p>
<pre><code tabindex="0" class="language-js">app.post('/push/unsubscribe', function (req, res) {
    // remove from database
    Push.findOneAndRemove({endpoint: endpoint}, function (err,data) {
        if (err) {
            console.error('error with unsubscribe', error);
            res.status(500).send('unsubscription not possible');
        }
        console.log('unsubscribed');
        res.status(200).send('unsubscribe');
    });
});
</code></pre>
<p>Если пользователь отменяет подписку, удаляем информацию о ней из базы.</p>
<h2>Дополнительно</h2>
<p>Вероятно, каждый из вас столкнется со своим случаем применения пуш-уведомлений, и просто скопировать код не получится. Однако я надеюсь, что этот урок поможет вам реализовать искомую функциональность как на стороне клиента, так и на стороне сервера.</p>
<p><a href="https://push-notifications-vwursywdxa.now.sh">Демо проекта</a>, исходный код опубликован на <a href="https://github.com/justmarkup/demos/tree/gh-pages/push-notifications">GitHub</a>.</p>
<p>Для более глубокого погружения в тему рекомендую бесплатную книгу <a href="https://web-push-book.gauntface.com">Web Push Book</a> и примеры на <a href="https://serviceworke.rs/">servicewore.rs</a>.</p>
<p>Если вам есть что спросить, или есть что добавить, пишите в <a href="https://twitter.com/justmarkup">Twitter</a> или по <a href="https://web-standards.ru/articles/push-front-back/mailto:hallo@justmarkup.com">электронной почте</a>.</p>

                    ]]></description><pubDate>Mon, 13 Mar 2017 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/push-front-back/</guid></item><item><title>Обещание бургерной вечеринки</title><link>https://web-standards.ru/articles/promise-burger-party/</link><description><![CDATA[
                        <p><em>Я написала этот пост как альтернативное введение в JavaScript-промисы. Я набросала его в своем блокноте во время чтения многочисленных статей на эту тему. Если вам нужно более глубокое руководство, в конце статьи есть список для дальнейшего изучения.</em></p>
<p>Пару недель назад я участвовала в обсуждении кое-какой функциональности на JavaScript. Она должна была работать асинхронно с использованием внешних данных. Я сказала: <em>«ладно, давайте используем <code>fetch()</code>… тогда в коде это… эм-м…»</em> И пока я молчала, пытаясь вспомнить API Fetch, мой собеседник сказал: <em>«он возвращает промис».</em> Я впала в ступор и ответила: <em>«честно говоря, я не очень понимаю, что это значит…»</em></p>
<p>Я множество раз писала код на основе промисов, но в этот раз части почему-то не сложились в единую картину. Я поняла, что в действительности так и не разобралась с ними.</p>
<blockquote>
<p>I can not tell you how hard it is to explain this sentence — «It returns a Promise»
but probably because I really don’t understand Promise.
<a href="https://twitter.com/kosamari/status/819972802220589056">Mariko Kosaka @kosamari</a></p>
</blockquote>
<p>Если вы читаете меня <a href="https://twitter.com/kosamari">в Twitter</a>, то знаете, что я учусь на визуальных примерах: беру <a href="https://twitter.com/kosamari/status/807303762188574720">объекты реального мира</a> как метафоры для сложных концепций в коде и изображаю их в виде <a href="https://twitter.com/kosamari/status/806941856777011200">рисунков</a>. Это позволяет мне совладать с двойным уровнем абстракции: языком программирования и английского как неродного языка. В общем, мне пришлось рисовать и в этот раз.</p>
<img src="https://web-standards.ru/articles/promise-burger-party/images/1.png" alt="">
<p>А вот пример кода, с которым мы будем иметь дело в этой истории.</p>
<pre><code tabindex="0" class="language-js">// Асинхронная операция
function cookBurger (type) { … }

// Обычная операция
function makeMilkshake (type) { … }

// Функция оформления заказа, возвращает промис
function order (type) {
    return new Promise(function(resolve, reject) {
        var burger = cookBurger(type);
        burger.ready = function (err, burger) {
            if (err) {
                return reject(Error('Error while cooking'));
            }
            return resolve(burger);
        }
    })
}

order('JakeBurger')
    .then( burger =&gt; {
        const milkshake = makeMilkshake('vanila');
        return { burger: burger, shake: milkshake }
    })
    .then( foodItems =&gt; {
        console.log('BURGER PARTY !', foodItems);
    })
    .catch( err =&gt; {
        console.log(err);
    });
</code></pre>
<h3>Устроим бургерную вечеринку!</h3>
<p>Добро пожаловать в парк Промис-Сквер, место встречи всех любителей бургеров — кафе «ДжейкШак». Бургеры ДжейкШака очень популярны в окру́ге, но в кафе мало кассовых аппаратов, и очередь из посетителей никогда не убывает. Несмотря на это, на кухне хватает рук, чтобы принимать множество заказов одновременно.</p>
<p><em>Если вы не в курсе: <a href="http://www.foodsmackdown.com/2011/08/shake-shack-new-york-madison-square-park/">кафе ShakeShak в парке Мэдисон-Сквер</a> — это место в Нью-Йорке. Кафе действительно отличное, но туда всегда длинная очередь.</em></p>
<h2>Пообещать сделать</h2>
<p>Чтобы принимать заказы максимально быстро, ДжейкШак использует систему сигнальных брелков. После оплаты заказа на кассе, сотрудник кафе выдает посетителю поднос и такой брелок.</p>
<img src="https://web-standards.ru/articles/promise-burger-party/images/2.png" alt="">
<p>Поднос — это промис, обещание ДжейкШака преподнести вам вкуснейший бургер, как только тот будет готов, а брелок — индикатор состояния вашего заказа. Брелок молчит? — значит заказ <strong>в процессе</strong> и лучшие повара трудятся над вашим бургером. Брелок загорелся красным и гудит? — значит заказ <strong>приготовлен.</strong></p>
<p>Но есть небольшой нюанс: «приготовлен» — не значит «готов». Это значит, что работа поваров над заказом завершена, и они хотят, чтобы вы подошли и забрали его. Вы как клиент, вероятно, хотите просто получить свой бургер, но, в некоторых случаях, предпочтёте уйти. Дело ваше.</p>
<p>Давайте посмотрим, как это работает в коде. Когда вы вызываете функцию <code>order</code> (делаете заказ), она возвращает promise (выдает поднос и брелок как обещание выполнить заказ). Возвращённое значение (бургер) должен появиться на подносе, когда будет исполнен промис (данное вам обещание) и сработал колбэк. Подробнее об этом в следующем разделе!</p>
<img src="https://web-standards.ru/articles/promise-burger-party/images/3.png" alt="">
<h2>Добавьте обработчики промисов</h2>
<p>Ой, кажется брелок загудел. Пойдемте за стойку попросим заказ. На этом этапе возможны два сценария развития событий.</p>
<img src="https://web-standards.ru/articles/promise-burger-party/images/4.png" alt="">
<p><strong>1. Заказ исполнен</strong></p>
<p>Ура-а! Ваш заказ готов, сотрудник кафе выносит свежий ароматный бургер. Обещание приготовить отличный бургер можно считать выполненным.</p>
<p><strong>2. Заказ отклонен</strong></p>
<p>Похоже, на кухне закончились котлеты. Обещание приготовить бургер выполнено не будет, оно отменяется. Не забудьте потребовать назад ваши деньги!</p>
<p>Посмотрим, как мы можем подготовиться к этим двум ситуациям в нашем коде.</p>
<img src="https://web-standards.ru/articles/promise-burger-party/images/5.png" alt="">
<p><em>Метод <code>.then()</code> принимает вторым аргументом функцию. Эта функция является обработчиком для <code>reject</code>. Но для простоты в своем примере я буду использовать для обработки ошибок только <code>.catch()</code>. Если вы хотите узнать подробнее о разнице, вам может пригодиться <a href="https://developers.google.com/web/fundamentals/getting-started/primers/promises#error_handling">эта статья</a>.</em></p>
<h2>Связываем промисы</h2>
<p>Скажем, ваш заказ был успешно выполнен, но тут вы вспоминаете, что для фееричной бургерной вечеринки вам не хватает молочного коктейля… и вы идете в очередь «С» (специальная очередь за напитками, <a href="http://midtownlunch.com/2010/08/02/midtown-times-square-shake-shack-finally-add-a-c-line/">существует в ShakeShack на самом деле</a>, чтобы справиться с наплывом посетителей). При заказе коктейля кассир вручает вам другой поднос и еще один брелок. Так как напитки готовятся быстро, кассир сам выдает заказ, и не нужно ждать, когда загудит брелок (он уже гудит!).</p>
<img src="https://web-standards.ru/articles/promise-burger-party/images/6.png" alt="">
<p>Посмотрим, как это работает в коде. Чтобы соединить промисы в цепочку, достаточно всего лишь добавить еще один <code>then()</code>, он всегда возвращает промис. Просто запомните: каждый <code>.then()</code> возвращает поднос и брелок, а текущее возвращаемое значение передается аргументом в колбэк.</p>
<img src="https://web-standards.ru/articles/promise-burger-party/images/7.png" alt="">
<p>Теперь, когда у вас есть бургер и молочный коктейль, вы готовы к БУРГЕРНОЙ ВЕЧЕРИНКЕ 🎉!</p>
<h2>Другие трюки для вечеринок!</h2>
<p>У промисов есть и другие методы, которые позволяют делать классные трюки.</p>
<p><code>Promise.all()</code> создает промис, принимающий массив промисов (<code>items</code>). Он исполняется, когда исполнены все элементы массива, каждый из которых тоже промис. Представим это так: вы заказали для друзей пять бургеров, но не хотите все пять раз бегать к стойке. Достаточно сделать это один раз, когда все бургеры будут готовы. <code>Promise.all()</code> в этом случае — отличное решение.</p>
<p><code>Promise.race()</code> похож на <code>Promise.all()</code>, но исполнится или будет отклонен, как только будет исполнен или отклонен один из элементов массива промисов. Своего рода принцип «хватай и беги». Скажем, вы очень голодны и заказали бургер, чизбургер и хотдог одновременно, но заберёте только то, что быстрее приготовят. Обратите внимание, в этом случае, если на кухне закончились котлеты для бургеров, и отказ по бургеру вернется первым, то и все прочие заказы будут отменены.</p>
<p>Помимо этого, в JavaScript-промисах есть много других вещей для изучения. Ниже ссылки на материалы, которые я для этого рекомендую:</p>
<ul>
<li><a href="https://github.com/mattdesl/promise-cookbook/blob/master/README.md">Promise-cookbook</a> — на английском, также доступна версия на китайском;</li>
<li><a href="https://developers.google.com/web/fundamentals/getting-started/primers/promises">JavaScript Promises: an Introduction</a> — на английском;</li>
<li><a href="http://azu.github.io/promises-book/">JavaScript Promiseの本</a> — на японском, китайском и корейском языках.</li>
</ul>
<p>Спасибо Джейку Арчибальду и Нолану Лоусону за вычитку статьи и ценные советы, а также Крису Уитли за найденную в коде ошибку.</p>

                    ]]></description><pubDate>Thu, 09 Feb 2017 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/promise-burger-party/</guid></item><item><title>Манифест? А? Что? Зачем?</title><link>https://web-standards.ru/articles/manifest-what-why/</link><description><![CDATA[
                        <p><a href="https://github.com/w3c/manifest/graphs/contributors">Многие из нас</a>, кто работает над вебом, активно стараются уменьшить разрыв между нативными и веб-приложениями.</p>
<p>Но что это за разрыв? Всего несколько лет назад этот разрыв был, в большей степени, технологическим. Если вы хотели получить доступ к GPS устройства, вам приходилось писать нативное приложение. Сейчас ситуация несколько улучшилась: теперь мы можем получать доступ к датчикам устройства, вроде GPS, камеры и ориентации устройства — хотя впереди ещё долгий путь. Благодаря последним успехам веб-технологий, теперь у нас есть платформа, которая может конкурировать с нативными приложениями уже почти на равных.</p>
<p>Сегодня разрыв между нативными и веб-приложениями не столько технологический — дело в удобстве пользователей: они предпочитают устанавливать приложения, которые уютно живут на домашнем экране (или даже на рабочем столе, если речь про десктопные браузеры).</p>
<p>Кроме того, нативные приложения по умолчанию работают в офлайне и интегрируются с возможностями, которые предоставляет операционная система: например, возможность видеть установленные приложения в переключателе задач. Или возможность управлять настройками конфиденциальности приложения в том же самом месте, что и для приложений, установленных из магазина. Чтобы сделать что-нибудь подобное в браузере, мы всё ещё слоняемся по браузеру в поисках открытых вкладок и вводим длинные, скучные адреса.</p>
<p>Нам нужен такой способ «установки» веб-приложений, чтобы они были неотличимы от любого другого приложения, установленного на устройстве пользователя. Но в тоже время, мы не хотим потерять мощные функции, составляющие основу веб-платформы: связанность ссылками, просмотр исходного кода и возможность хостить собственные проекты.</p>
<p>Мы в веб-сообществе, как правило, называем это «<a href="https://en.wikipedia.org/wiki/Progressive_web_app">прогрессивными веб-приложениями</a>».</p>
<h2>Что такое «установка»?</h2>
<p>По сути, «установка» веб-приложения — это добавление «закладки» на домашний экран или в программу запуска приложений. Есть некоторые довольно очевидные вещи, которые вы, как разработчик, должны предоставить браузеру, чтобы тот мог считать сайт приложением: название, иконки, и т.д. Есть и более сложные функции, которые могут вам пригодиться, например, возможность указать предпочтительную ориентацию устройства и нужен ли вам полноэкранный режим.</p>
<p>Спецификация манифеста предлагает вам стандартный способ сделать это с помощью файла JSON. Просто сошлитесь на файл манифеста в HTML-странице следующим образом:</p>
<pre><code tabindex="0" class="language-html">&lt;link rel=&quot;manifest&quot; href=&quot;/manifest.json&quot;&gt;
</code></pre>
<p>Но что находится в этом загадочном файле манифеста? Хорошо, что вы спросили!</p>
<h2>Очень простой манифест</h2>
<p>Самый простой манифест может состоять всего-то из имени и одной или нескольких иконок.</p>
<pre><code tabindex="0" class="language-json">{
    &quot;name&quot;: &quot;Супергонщик 3000&quot;,
    &quot;icons&quot;: [{
        &quot;src&quot;: &quot;icon/lowres.png&quot;,
        &quot;sizes&quot;: &quot;64x64&quot;
    }]
}
</code></pre>
<h2>Типичный манифест</h2>
<p>Более типичный манифест может выглядеть следующим образом. Имена его ключей должны говорить сами за себя, но мы подробнее опишем их использование ниже.</p>
<pre><code tabindex="0" class="language-json">{
    &quot;lang&quot;: &quot;ru&quot;,
    &quot;dir&quot;: &quot;ltr&quot;,
    &quot;name&quot;: &quot;Супергонщик 3000&quot;,
    &quot;description&quot;: &quot;Потрясающая футуристичная гоночная игра из будущего!&quot;,
    &quot;short_name&quot;: &quot;Гонщик3K&quot;,
    &quot;icons&quot;: [{
        &quot;src&quot;: &quot;icon/lowres.webp&quot;,
        &quot;sizes&quot;: &quot;64x64&quot;,
        &quot;type&quot;: &quot;image/webp&quot;
    },{
        &quot;src&quot;: &quot;icon/lowres.png&quot;,
        &quot;sizes&quot;: &quot;64x64&quot;
    }, {
        &quot;src&quot;: &quot;icon/hd_hi&quot;,
        &quot;sizes&quot;: &quot;128x128&quot;
    }],
    &quot;scope&quot;: &quot;/racer/&quot;,
    &quot;start_url&quot;: &quot;/racer/start.html&quot;,
    &quot;display&quot;: &quot;fullscreen&quot;,
    &quot;orientation&quot;: &quot;landscape&quot;,
    &quot;theme_color&quot;: &quot;aliceblue&quot;,
    &quot;background_color&quot;: &quot;red&quot;,
    &quot;screenshots&quot;: [{
        &quot;src&quot;: &quot;screenshots/in-game-1x.jpg&quot;,
        &quot;sizes&quot;: &quot;640x480&quot;,
        &quot;type&quot;: &quot;image/jpeg&quot;
    },{
        &quot;src&quot;: &quot;screenshots/in-game-2x.jpg&quot;,
        &quot;sizes&quot;: &quot;1280x920&quot;,
        &quot;type&quot;: &quot;image/jpeg&quot;
    }]
}
</code></pre>
<h2>Название приложения</h2>
<p>Приложению нужно настоящее название или набор названий (которые обычно совсем не совпадают с содержимым элемента <code>&lt;title&gt;</code> документа). Для этого используются ключи <code>name</code> и <code>short_name</code>.</p>
<pre><code tabindex="0" class="language-json">{
    &quot;name&quot;: &quot;Моё вообще улётное фотоприложение&quot;,
    &quot;short_name&quot;: &quot;Фотки&quot;
}
</code></pre>
<p>Ключ <code>short_name</code> служит названием приложения при отображении в условиях ограниченного пространства (например, под значком на домашнем экране телефона). Ключ name может быть немного длиннее, отображая название приложения полностью. Также он служит дополнительной информацией для пользователя, который ищет ваше приложения на телефоне. Так что, набрав «улётный» или «фото», пользователь сможет найти приложение на своем устройстве.</p>
<p>Если вы опустите название, то браузер может использовать <code>&lt;meta name=&quot;application-name&quot;&gt;</code> или элемент <code>&lt;title&gt;</code>.</p>
<p>Но будьте внимательны: некоторые браузеры могут требовать указать название — иначе, ваше приложение может лишиться статуса «прогрессивное веб-приложение».</p>
<h2>Иконки</h2>
<p>Вместо обычной иконки браузера, у вашего веб-приложения должна быть иконка, которая будет с ним ассоциироваться. Для этого в манифесте есть ключ <code>icons</code>. Он принимает список иконок, их размеров и форматов. Это делает процесс выбора иконки очень эффективным, поскольку у иконок появляется адаптивное решение, которое позволяет избежать ненужных нагрузок и помогает иконкам всегда выглядеть отлично на широком диапазоне устройств и разрешений экрана.</p>
<pre><code tabindex="0" class="language-json">{
    &quot;icons&quot;: [{
        &quot;src&quot;: &quot;icon/lowres&quot;,
        &quot;sizes&quot;: &quot;64x64&quot;,
        &quot;type&quot;: &quot;image/webp&quot;
    }, {
        &quot;src&quot;: &quot;icon/hd_small&quot;,
        &quot;sizes&quot;: &quot;64x64&quot;
    }, {
        &quot;src&quot;: &quot;icon/hd_hi&quot;,
        &quot;sizes&quot;: &quot;128x128&quot;,
    }]
}
</code></pre>
<p>Если вы не укажете иконки, браузер будет искать запасные варианты: <code>&lt;link rel=&quot;icon&quot;&gt;</code>, favicon.ico или, если не найдёт их, может даже использовать скриншот вашего сайта.</p>
<h2>Назначение иконки</h2>
<p>Нужно написать.</p>
<p>Больше подробностей о назначении иконок можно найти в спецификации <a href="https://www.w3.org/TR/appmanifest/#purpose-member">Web App Manifest</a>.</p>
<h2>Режимы отображения и ориентация</h2>
<p>Приложения при запуске должны иметь возможность контролировать свое отображение на экране. Если это игра, то ей, вероятно, нужно быть в полноэкранном режиме и в горизонтальной ориентации. Для этого формат манифеста предоставляет вам два ключа.</p>
<pre><code tabindex="0" class="language-json">{
    &quot;display&quot;: &quot;fullscreen&quot;,
    &quot;orientation&quot;: &quot;landscape&quot;
}
</code></pre>
<p>Доступные значения режимов отображения:</p>
<ul>
<li><strong>Полноэкранный</strong> <code>fullscreen</code> занимает весь экран.</li>
<li><strong>Автономный</strong> <code>standalone</code> открывает приложение со строкой состояния.</li>
<li><strong>Минимальный</strong> <code>minimal-ui</code>, когда приложение отображается в полноэкранном режиме, как на iOS, но некоторые действия могут вызывать панель навигации и появление кнопок назад и вперед.</li>
<li><strong>Браузерный</strong> <code>browser</code> открывает приложение со стандартным набором кнопок и панелью инструментов.</li>
</ul>
<p>Плюс такого указания ориентации в том, что она выступает в качестве «ориентации по умолчанию» для всего приложения. Поэтому, при переходе от одной странице к другой, ваше приложение остается в правильном положении. Вы можете изменить ориентацию по умолчанию с помощью <a href="https://w3c.github.io/screen-orientation/">API ориентации экрана</a>.</p>
<p>Также вы можете применить другие стили для приложение в определённом режиме с помощью характеристики <code>display-mode</code>:</p>
<pre><code tabindex="0" class="language-css">@media all and (display-mode: standalone){
    /* … */
}
</code></pre>
<p>Используйте метод <code>window.matchMedia()</code>, чтобы проверить это медиавыражение в JavaScript.</p>
<pre><code tabindex="0" class="language-js">if (window.matchMedia('(display-mode: standalone)').matches) {
    // интересные модификации интерфейса
}
</code></pre>
<h2>Стартовый адрес</h2>
<p>Иногда при запуске приложения вам нужно, чтобы пользователь всегда попадал на определенную страницу. Ключ <code>start_url</code> даёт возможность это указать.</p>
<pre><code tabindex="0" class="language-json">{
    &quot;start_url&quot;: &quot;/start_screen.html&quot;
}
</code></pre>
<h2>«Область» приложения</h2>
<p>Нативные приложения имеют чёткие границы: как пользователь, вы уверены, что когда вы открываете нативное приложение, оно неожиданно не откроет другое незаметно для вас. Чаще всего, вам предельно ясно, что вы переключились с одного нативного приложения на другое. Обычно эти визуальные подсказки предоставляет операционная система (например, вызов диспетчера задач и выбор другого приложения или нажатие <kbd>Cmd Tab</kbd> или <kbd>Alt Tab</kbd> на компьютере).</p>
<p>С вебом все иначе: это огромная гипертекстовая система, в которой веб-приложение может охватывать несколько доменов: вы можете с легкостью перейти с <em>gmail.com</em> на <em>docs.google.com</em> и пользователь даже этого не заметит. На практике, идея существования границ приложения является абсолютно чуждой для веба. Ведь, в действительности, веб-приложение — это просто серия HTML-документов (представьте «серию труб»… м-м, нет, забудьте!).</p>
<p>В интернете мы знаем, что покидаем область одного приложения и переходим в другое, только благодаря веб-дизайнерам, которые были достаточно добры, чтобы сделать им уникальный различимый дизайн. В случаях, когда это не так, множество пользователей оказываются обмануты сайтами, маскирующимися под другие (старый добрый фишинг).</p>
<p>Формат манифеста решает эту проблему позволяя указывать «область адреса» для вашего приложения. Эта область устанавливает границы для приложения. Это может быть либо домен, либо директория на этом домене.</p>
<pre><code tabindex="0" class="language-json">{
    &quot;scope&quot;: &quot;/myapp&quot;
}
</code></pre>
<h2>Интернационализация: <code>lang</code> и <code>dir</code></h2>
<p>Нужно написать.</p>
<h2>Распространение приложения</h2>
<p>Нужно написать с подробностями и скриншотами.</p>
<h2>Цвет темы и цвет фона</h2>
<p>Нужно написать.</p>
<h2>Как мне определить, что пользователь «установил» приложение?</h2>
<p>Спецификация позволяет вам определить, когда пользователь устанавливает приложение с помощью регистрации события <code>appinstalled</code>.</p>
<pre><code tabindex="0" class="language-js">function handleInstalled(ev) {
    const date = new Date(ev.timeStamp / 1000);
    console.log(`Ура! Установлено в ${date.toTimeString()}`);
}

// Используем атрибут IDL обработчика события
window.onappinstalled = handleInstalled;

// Используем .addEventListener()
window.addEventListener('appinstalled', handleInstalled);
</code></pre>
<p>Однако по причинам конфиденциальности вы не можете непосредственно обнаружить, установлено ли ваше приложение — только узнать, что в вашем веб-приложении используется файл манифеста.</p>
<h2>Что не так с тегами <code>&lt;meta&gt;</code>?</h2>
<p>В ходе обсуждения спецификации шли оживленные дискуссии об использовании тегов <code>&lt;meta&gt;</code> в HTML вместо создания нового формата. В конце концов, реализация функции «добавить на домашний экран» в Chrome использует теги <code>&lt;meta&gt;</code>, да и с самых первых дней веба эти теги были родным домом для всякой нестандартной ерунды.</p>
<p>Причины для использования отдельного файла:</p>
<ul>
<li>это экономит кучу информации в шапке документа при загрузке каждой страницы установленного приложения или сайта;</li>
<li>после загрузки, файл остаётся в HTTP-кэше браузера.</li>
</ul>
<p>В спецификации есть <a href="https://www.w3.org/TR/appmanifest/#relationship-to-html-s-link-and-meta-elements">более подробная информация</a> о том, почему мы выбрали JSON вместо HTML-тегов.</p>
<h2>Кто это внедряет?</h2>
<p>Манифест и прогрессивные веб-приложения реализованы в Chrome, Opera и Samsung Internet для Android. Firefox также подаёт обнадёживающие сигналы, что будет поддерживать эти стандарты (реализации в Gecko уже больше двух лет, но она не используется ни в одном из продуктов).</p>
<h2>Взаимодействие с поисковыми роботами</h2>
<p>Как и другие веб-ресурсы, манифест веб-приложения должен быть доступен для любого веб-браузера или поискового робота.</p>
<p>Если разработчик веб-приложения хочет известить поисковых роботов о запрете на сканирование файла, он <strong>может</strong> сделать это включив манифест веб-приложения в файл robots.txt. Это описано подробнее в протоколе <a href="http://www.robotstxt.org/">robots.txt</a>. Разработчик веб-приложения также может использовать HTTP-заголовок <code>X-Robots-Tag</code>.</p>
<h2>Авторы</h2>
<p>Основная часть этого пояснения первоначально появилась в статье «<a href="https://html5doctor.com/web-manifest-specification/">The W3C App Manifest specification</a>» на <a href="https://html5doctor.com/">HTML5 Doctor</a>, и была написана <a href="https://github.com/marcoscaceres">Маркусом Касересом</a> и <a href="http://www.brucelawson.co.uk/">Брюсом Лоусоном</a>. Данный материал публикуется на основе лицензии <a href="http://creativecommons.org/licenses/by-nc/2.0/uk/">для некоммерческое использования</a>. Вы можете спокойно изменять, повторно использовать, модифицировать и расширять это пояснение. Некоторые авторы сохраняют свои авторские права на отдельные статьи.</p>

                    ]]></description><pubDate>Mon, 09 Jan 2017 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/manifest-what-why/</guid></item><item><title>Улучшение формы комментариев</title><link>https://web-standards.ru/articles/enhancing-comment-form/</link><description><![CDATA[
                        <p>От базовых до улучшенных сообщений об ошибках к фоновой синхронизации</p>
<p>Не исключено, что рано или поздно при разработке сайта вам придётся заняться реализацией формы, будь то регистрационная форма или форма для добавления комментариев. Я сам занимался этим уже много раз, но при создании очередной формы комментариев я задумался, насколько далеко я могу зайти в процессе её улучшения. После добавления одного улучшения мне в голову пришло другое, после реализации которого пришло ещё одно.</p>
<p>Вот почему я хотел бы показать вам как, используя фоновую синхронизацию, вы можете улучшить форму (в данном случае состоящую из <code>&lt;input&gt;</code> для имени, <code>&lt;textarea&gt;</code> для сообщения и <code>&lt;button&gt;</code> для отправки) с базовой до <em>Улучшенной улучшенной™</em> версии с фоновой синхронизацией.</p>
<h2>Базовая версия</h2>
<p>Начнём с базовой версии HTML-формы:</p>
<pre><code tabindex="0" class="language-html">&lt;form action=&quot;./&quot; method=&quot;post&quot;&gt;
    &lt;label for=&quot;name&quot;&gt;Имя&lt;/label&gt;
    &lt;input type=&quot;text&quot; name=&quot;name&quot; id=&quot;name&quot;&gt;

    &lt;label for=&quot;comment&quot;&gt;Комментарий&lt;/label&gt;
    &lt;textarea name=&quot;comment&quot; id=&quot;comment&quot;&gt;&lt;/textarea&gt;

    &lt;button type=&quot;submit&quot;&gt;Опубликовать&lt;/button&gt;
&lt;/form&gt;
</code></pre>
<p>Это будет работать в <em>любом</em> браузере. Теперь начнём улучшать форму без изменения её базовой версии.</p>
<p><a href="https://justmarkup.github.io/demos/form-enhancement/v1/">Демо базовой версии.</a></p>
<h2>Улучшенная HTML-версия</h2>
<img src="https://web-standards.ru/articles/enhancing-comment-form/images/1.png" alt="">
<p>Во-первых, элемент <code>&lt;textarea&gt;</code> никогда не должен быть пустым, поэтому добавим к нему атрибут required. Форму можно проверять на стороне клиента без JavaScript — при условии, что браузер <a href="https://caniuse.com/#feat=form-validation">поддерживает</a> такую возможность. Однако, не стоит на него полагаться (неподдерживаемые браузеры, <a href="https://bugs.webkit.org/show_bug.cgi?id=28649">баги</a> в браузерах и другое), поэтому валидация форм на стороне сервера — очень хорошая идея.</p>
<pre><code tabindex="0" class="language-html">&lt;form action=&quot;./&quot; method=&quot;post&quot;&gt;
    &lt;label for=&quot;name&quot;&gt;Имя&lt;/label&gt;
    &lt;input type=&quot;text&quot; name=&quot;name&quot; id=&quot;name&quot;&gt;

    &lt;label for=&quot;comment&quot;&gt;Комментарий&lt;/label&gt;
    &lt;textarea required placeholder=&quot;Что думаете?&quot; name=&quot;comment&quot; id=&quot;comment&quot;&gt;&lt;/textarea&gt;

    &lt;button type=&quot;submit&quot;&gt;Опубликовать&lt;/button&gt;
&lt;/form&gt;
</code></pre>
<p>Далее, добавим атрибут <code>placeholder</code>. Никогда не заменяйте <code>&lt;label&gt;</code> на <code>placeholder</code>, это важное дополнение, но никак не замена.</p>
<p><a href="https://justmarkup.github.io/demos/form-enhancement/v2/">Демо улучшенной версии HTML.</a></p>
<h2>Делим по способностям</h2>
<p>Для дальнейших улучшений мы будем использовать JavaScript. В <code>&lt;head&gt;</code> нашей страницы добавим <a href="https://justmarkup.com/log/2015/02/cut-the-mustard-revisited/">тест, отсекающий браузеры</a>, которые не поддерживают улучшения. Мы будем использовать технику, с которой я впервые столкнулся в статье Filament Group <a href="https://www.filamentgroup.com/lab/enhancing-optimistically.html">«Enhancing optimistically»</a>.</p>
<pre><code tabindex="0" class="language-js">if ('visibilityState' in document) {
    // Подключаем здесь loadJS
    function loadJS (src) { … }

    // Этот браузер всё сможет, давайте ещё улучшим интерфейс!
    var docElem = window.document.documentElement;

    // Класс для улучшения интерфейса
    var enhancedClass = 'enhanced';
    var enhancedScriptPath = 'enhanced.js';

    // Добавляем класс улучшения
    function addClass () {
        docElem.className += ' ' + enhancedClass;
    }

    // Удаляем класс улучшения
    function removeClass () {
        docElem.className = docElem.className.replace(enhancedClass, ' ');
    }

    // Давайте оптимистично улучшим
    addClass();

    // Подгружаем улучшенный JS-файл
    var script = loadJS(enhancedScriptPath);

    // Если скрипт не загрузился за 8 секунд,
    // удаляем класс улучшения
    var fallback = setTimeout(removeClass, 8000);

    // Когда скрипт загружается, отменяем таймер
    // и добавляем класс снова, на всякий случай
    script.onload = function () {
        // Отменяем таймер фолбэка
        clearTimeout(fallback);
        // Добавляем этот класс на случай, если его уже удалили.
        // Этот запрос не отменить, он может прийти в любое время
        addClass();
    };
}
</code></pre>
<h2>Кастомные сообщения об ошибках</h2>
<img src="https://web-standards.ru/articles/enhancing-comment-form/images/2.png" alt="">
<p>Заменим базовое сообщение об ошибке «Пожалуйста, заполните это поле» на кастомное. Для создания кастомного сообщение в наш скрипт enhanced.js (который будет загружаться только в поддерживаемых браузерах) добавим следующий код:</p>
<pre><code tabindex="0" class="language-js">// Добавляем свой текст сообщения об ошибке
var commentArea = document.querySelector('#comment');

commentArea.addEventListener('invalid', function (e) {
    e.target.setCustomValidity('');
    if (!e.target.validity.valid) {
        e.target.setCustomValidity('Пожалуйста, введите комментарий.');
    }
});

commentArea.addEventListener('input', function (e) {
    e.target.setCustomValidity('');
});
</code></pre>
<p>В таком случае, при отправке формы без комментариев, пользователю будет выводиться сообщение <em>«Пожалуйста, введите комментарий».</em> Вы, наверное, заметили, что я до сих пор ничего не говорил о стилях сообщений. Потому что на данный момент нет никакой возможности оформить их с помощью CSS. Раньше можно было воспользоваться селектором <code>::-webkit-validation-bubble</code> для браузеров на WebKit, но он был удалён. Если вы действительно хотите оформить всплывающее окно, то вам нужно будет <a href="http://developer.telerik.com/featured/building-html5-form-validation-bubble-replacements/">создать своё собственное</a>. Но имейте в виду, что здесь есть много подводных камней, поэтому я советую использовать всплывающие окна по умолчанию.</p>
<p><a href="https://justmarkup.github.io/demos/form-enhancement/v3/">Демо кастомных сообщений об ошибках.</a></p>
<h2>Аяксим</h2>
<p>Теперь, когда пользователь попытается отправить пустое поле, все современные браузеры будут выводить кастомное сообщение об ошибке, но даже если пользователь оставит комментарий, форма по-прежнему будет обрабатываться на стороне сервера, что приведёт к перезагрузке страницы. Давайте это исправим, разместив комментарий с помощью JavaScript.</p>
<pre><code tabindex="0" class="language-js">// Отправляем данные формы на JavaScript
if (window.FormData) {
    var appendComment = function (nameValue, commentValue) {
        var comment = document.createElement('li');
        var commentName = document.createElement('h4');
        var commentComment = document.createElement('p');
        var commentWrapper = document.querySelector('.comments');
        commentName.innerText = nameValue;
        commentComment.innerText = commentValue;
        nameValue ? comment.appendChild(commentName) : '';
        comment.appendChild(commentComment);
        commentWrapper.appendChild(comment);
    };

    form.addEventListener('submit', function (e) {
        var formData = new FormData(form);
        commentValue = commentArea.value;
        nameValue = nameInput.value;

        var xhr = new XMLHttpRequest();
        // Сохраняем комментарий в базу
        xhr.open('POST', './save', true);
        xhr.onload = function () {
            appendComment(nameValue, commentValue);
        };
        xhr.send(formData);

        // Всегда вызывайте preventDefault в конце
        // см. http://molily.de/javascript-failure/
        e.preventDefault();
    });
}
</code></pre>
<p>Сначала проверим поддерживает ли бразуер <a href="https://caniuse.com/#feat=xhr2">FormData</a> и расширенные функции XMLHttpRequest. Если да, то определим функцию appendComment() для добавления нового комментария к другим комментариям. Далее добавим <em>событие отправки</em> к нашей форме для отправки XMLHttpRequest. Если запрос успешный — добавляем комментарий. В самом конце вызовем preventDefault для предотвращения поведения формы по умолчанию. Важно вызывать метод preventDefault в конце, так как мы не знаем <a href="http://molily.de/javascript-failure/">завершится ли выполнение JavaScript ошибкой</a>.</p>
<p>Теперь наша форма прекрасно отправляет комментарии без перезагрузки страницы и при этом по-прежнему работает в неподдерживаемых браузерах.</p>
<p>На этом этапе вы можете задаться вопросом, почему я не использовал Fetch API. Я хотел охватить как можно больше популярных браузеров без использования полифилов и не хотел ограничиваться поддержкой только современных браузеров.</p>
<p><a href="https://justmarkup.github.io/demos/form-enhancement/v5/">Демо AJAX-версии.</a></p>
<h2>Авторасширение <code>&lt;textarea&gt;</code></h2>
<img src="https://web-standards.ru/articles/enhancing-comment-form/images/3.gif" alt="">
<p>Если вы напишите длинный комментарий, вам придётся проматывать текст вверх и вниз, чтобы перепроверить написанное. Исправим это с помощью авторасширения <code>&lt;textarea&gt;</code>.</p>
<p>Для этого мы будем использовать <a href="https://codepen.io/vsync/pen/czgrf">решение</a>, которое я нашёл на CodePen.</p>
<pre><code tabindex="0" class="language-js">commentArea.addEventListener('keydown', autosize);

function autosize () {
    var el = this;
    setTimeout(function () {
        el.style.cssText = 'height: auto;';
        el.style.cssText = 'height: ' + el.scrollHeight + 'px';
    }, 0);
}
</code></pre>
<p>Теперь элемент <code>&lt;textarea&gt;</code> адаптируется к длине комментария, что упрощает проверку текста.</p>
<p><a href="https://justmarkup.github.io/demos/form-enhancement/v5/">Демо с автоматическим расширением.</a></p>
<h2>Успех, ошибка и плохое соединение</h2>
<p>Итак, мы добавили улучшенный HTML (атрибуты <code>placeholder</code> и <code>required</code>), назначили кастомные сообщения об ошибках, добавили AJAX и автоматическое расширение для <code>&lt;textarea&gt;</code>.</p>
<p>Далее, для большего удобства, добавим уведомление о успешной (или неуспешной) отправке комментария и индикатор прогресса для отображения времени загрузки.</p>
<pre><code tabindex="0" class="language-html">&lt;p class=&quot;message&quot; id=&quot;feedback&quot;&gt;&lt;/p&gt;
&lt;button type=&quot;submit&quot;&gt;Опубликовать&lt;/button&gt;
</code></pre>
<p>Сначала добавим новый элемент в нашу форму для отображения сообщений.</p>
<pre><code tabindex="0" class="language-js">var messageElement = document.querySelector('#feedback');
// …
form.addEventListener('submit', function () {
// …
    xhr.onerror = function () {
        messageElement.className = 'message error';
        messageElement.textContent = 'При публикации комментария произошла ошибка. Попробуйте ещё раз.';
    };
    xhr.upload.onprogress = function (e) {
        messageElement.textContent = 'Uploading: ' + e.loaded / e.total * 100;
    };
    xhr.upload.onloaded = function () {
        messageElement.className = 'message success';
        messageElement.textContent = 'Ваш комментарий успешно опубликован.';
    };
// …
});
</code></pre>
<p>В случае возникновения ошибки при отправке комментария, пользователю будет выводиться сообщение, которое определено в событии error. В противном случае будет выводиться сообщение об успешной отправке, как это задано в событии loaded. В событие progress добавим индикатор, который будет показывать сколько процентов страницы уже загрузилось. При условии, что качество подключения хорошее, вы не увидите индикатор загрузки, но если вы набираете длинный комментарий при медленном соединении, индикатор сообщит вам, что «там» что-то происходит и комментарий рано или поздно будет опубликован.</p>
<p><a href="https://justmarkup.github.io/demos/form-enhancement/v6/">Демо версии с прогрессом</a>.</p>
<h2>Сервис-воркер и фоновая синхронизация</h2>
<p>Итак, мы улучшили работу с формой при плохом соединении, а теперь улучшим её при отсутствии соединения.</p>
<pre><code tabindex="0" class="language-js">// Проверяем поддержку сервис-воркера
if ('serviceWorker' in navigator) {
    // Регистрируем сервис-воркер
    navigator.serviceWorker.register('./service-worker.js');

    form.addEventListener('submit', function (e) {
        let formData = new FormData(form);
        // Отправляем сообщение в фоне
        navigator.serviceWorker.ready.then(function (swRegistration) {
            idbKeyval.set('comment', commentArea.value);
            idbKeyval.set('name', nameInput.value ? nameInput.value : false);
            messageElement.className = 'message info';
            messageElement.textContent = 'Похоже вы в офлайне. Комментарий опубликуется автоматически как только вы будете онлайн.';

            return swRegistration.sync.register('form-post');
        });

        // Всегда вызывайте preventDefault в конце
        // см. http://molily.de/javascript-failure/
        e.preventDefault();
    });

    // Событие для получения сообщения
    // отправленного сервис-воркером
    navigator.serviceWorker.addEventListener('message', function (e) {
        if (e.data == 'success') {
            messageElement.className = 'message success';
            messageElement.textContent = 'Ваш комментарий успешно опубликован.';
            let nameValue = false;
            idbKeyval.get('name').then(function (data) {
                nameValue = data;
                let commentValue = '';
                idbKeyval.get('comment').then(function (data) {
                    commentValue = data;
                    appendComment(nameValue, commentValue);
                });
            });
        } else if (e.data == 'error') {
            messageElement.className = 'message error';
            messageElement.textContent = 'При публикации комментария произошла ошибка. Попробуйте ещё раз.';
        }
    });
} else if (window.FormData) {
    // …
}
</code></pre>
<p>Сначала проверим <a href="https://caniuse.com/#feat=serviceworkers">поддерживается ли</a> сервис-воркер браузером. Если да, то используем <a href="https://github.com/WICG/BackgroundSync/blob/master/explainer.md">фоновую синхронизацию</a> для отправки комментария, в противном случае опубликуем комментарий через XMLHttpRequest, как показано выше в версии на AJAX.</p>
<p>Давайте посмотрим, как работает код. При отправке данных формы сохраним значение имени и комментария в <a href="https://developer.mozilla.org/en/docs/Web/API/IndexedDB_API">IndexedDB</a>. В моём случае, я использую хранилище «ключ-значение» на промисах Джейка Арчибальда, реализованное с помощью IndexedDB. Также выведем сообщение о том, что пользователь находится в автономном режиме и, как только появится сеть, комментарий будет автоматически опубликован. Если соединение хорошее и комментарий может быть опубликован сразу, это сообщение выводиться не будет. И, наконец, регистрируем фоновую синхронизацию с тегом <code>form-post</code>.</p>
<p>Чтобы узнать был ли комментарий размещён успешно, добавим обработчик событий, который проверит сообщения, поступающие от сервис-воркера.</p>
<p>Теперь давайте посмотрим, как выглядит наш service-worker.js.</p>
<pre><code tabindex="0" class="language-js">importScripts('idb-keyval.js');

const VERSION = 'v1';

self.addEventListener('install', function (e) {
    self.skipWaiting();
    e.waitUntil(
        caches.open(VERSION).then(function (cache) {
            return cache.addAll([
                './',
                './index.html',
                '../style.css',
                'enhanced.js'
            ]);
        })
    );
});

self.addEventListener('fetch', function (e) {
    let request = e.request;
    if (request.method !== 'GET') {
        return;
    }
});

self.addEventListener('activate', function () {
    if (self.clients &amp;&amp; clients.claim) {
        clients.claim();
    }
});

self.addEventListener('sync', function (e) {
    if (e.tag == 'form-post') {
        e.waitUntil(postComment());
    }
});

function postComment () {
    let formData = new FormData();

    idbKeyval.get('name').then(function (data) {
        formData.append('name', data);
    });

    idbKeyval.get('comment').then(function (data) {
        formData.append('comment', data);
    });

    fetch('./save', {
        method: 'POST',
        mode: 'cors',
        body: formData
    }).then(function (response) {
        return response;
    }).then(function (text) {
        send_message_to_all_clients('success');
    }).catch(function (error) {
        send_message_to_all_clients('error');
    });
}

function send_message_to_client (client, msg) {
    return new Promise(function (resolve, reject) {
        var msg_chan = new MessageChannel();

        msg_chan.port1.onmessage = function (e) {
            if (e.data.error) {
                reject(e.data.error);
            } else {
                resolve(e.data);
            }
        };

        client.postMessage(msg, [msg_chan.port2]);
    });
}

function send_message_to_all_clients (msg) {
    clients.matchAll().then(clients =&gt; {
        clients.forEach(client =&gt; {
            send_message_to_client(client, msg).then(
                msg =&gt; console.log('Сообщение из сервис-воркера: ' + msg)
            );
        });
    });
}
</code></pre>
<p>Сначала импортируем хранилище «ключ-значение» на промисах, которое мы уже использовали в enhanced.js для обработки IndexedDB. Затем определим <code>const</code> для версии кэша и добавим функции для обработки <code>install</code> (добавим ресурсы в кэш), <code>fetch</code> (для обработки запросов) и события активации. Самая важная часть начинается с синхронизированной версии. Здесь мы сначала проверяем является ли тег <code>form-post</code>. Это тот самый тег, который мы зарегистрировали ранее используя <code>swRegistration.sync.register(form-post)</code>. При совпадении тега вызываем <code>e.waitUntil(postComment())</code>. Теперь, как только появится хорошее соединение, функция postComment будет выполнена.</p>
<p>В нашей функции <code>postComment</code> создадим новый объект <code>FormData</code> и добавим имя и значение поля, которые мы получим от IndexedDB. Далее, для того, чтобы сохранить комментарий, используем <code>fetch</code>. В случае получения успешного ответа отобразим сообщения об удачной отправке комментария (в противном случае — об ошибке).</p>
<p>Теперь комментарий будет опубликован в любом случае. Если вы онлайн, комментарий опубликуется сразу, если вы офлайн, он будет опубликован, как только появится связь, даже если вы уже закрыли страницу с формой.</p>
<p><a href="https://justmarkup.github.io/demos/form-enhancement/v7/">Демо <em>Улучшенной улучшенной™</em> версии.</a></p>
<p><strong>Обратите внимание:</strong> если вы хотите проверить работоспособность окончательной версии в автономном режиме, необходимо учитывать, что сейчас в Chrome есть ошибка — просмотр страницы в автономном режиме, при использовании отладчика, не работает. Для этого вам действительно надо быть в офлайне.</p>
<h2>Заключение</h2>
<p>Как видите, есть много вариантов улучшить форму, а также множество других возможностей, которые я не упомянул. Столько всего можно сделать с помощью CSS, о чём я вообще не говорил здесь, но также многое можно сделать с помощью JavaScript. Самое главное здесь, что, благодаря прогрессивному улучшению, мы поддерживаем все браузеры. Некоторые посетители получат более удобный интерфейс, но абсолютно все пользователи смогут выполнить задачу и опубликовать комментарий.</p>
<p>Лишь немногие получат <em>Улучшенную улучшенную™</em> версию, но, в зависимости от обстоятельств, каждый гарантированно получит базовую версию формы.</p>
<p><a href="https://justmarkup.github.io/demos/form-enhancement/v7/">Финальная Улучшенная улучшенная™ версия.</a></p>
<p>Если у вас есть какие-либо идеи по улучшению или вы нашли ошибку в коде, пожалуйста, создайте <a href="https://github.com/justmarkup/demos/issues">ишью на Гитхабе.</a></p>

                    ]]></description><pubDate>Fri, 18 Nov 2016 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/enhancing-comment-form/</guid></item><item><title>Путь наставника</title><link>https://web-standards.ru/articles/path-of-tutor/</link><description><![CDATA[
                        <p>Если звезды фронтенда зажигаются, значит, кто-то их зажигает? Кто? Приоткрываем завесу тайны над темой наставничества в вебе.</p>
<p>На написание этой статьи я решился неспроста, на это меня подвигли две вещи. Во-первых, история, произошедшая после публикации моей статьи <a href="http://css-live.ru/faq/put-verstalshhika.html">«Путь верстальщика»</a>, из-за которой этот пост уже напрашивался сам по себе — мне давно хотелось подробнее пояснить некоторые важные моменты. А во-вторых, <a href="https://youtu.be/qFeCi6E3e14?t=1345">дебютный доклад</a> <a href="https://twitter.com/ABatickaya">Алёны Батицкой</a> о пользе наставничества, ставший для меня последней каплей и одновременно толчком для написания всего этого.</p>
<blockquote>
<p><strong>А.Б.</strong> Хочу сказать, что все, о чем я говорила в докладе и о чем я пишу, что советую в обычной жизни, является моим личным мнением, основанным на опыте в совершенно разных сферах. Предложенные мною «рецепты» не являются истиной в последней инстанции. Вы можете составить свою программу наставничества, выбрать свой путь развития как новичка, так и наставника или использовать мои рекомендации как основу.</p>
</blockquote>
<p>Как вы могли догадаться, и моя статья, и доклад Алёны связаны друг с другом, и вскоре вы поймёте, как именно. Единственное — у меня к вам есть просьба. Перед тем как продолжить читать, посмотрите, пожалуйста, сам доклад, так вам будет легче понять, о чем пойдет речь.</p>
<h3>Откуда ветер дует</h3>
<img src="https://web-standards.ru/articles/path-of-tutor/images/1.png" srcset="images/1@2x.png 2x" alt="Откуда ветер дует.">
<blockquote>
<p><strong>А.Б.</strong> Только придя в разработку, я заметила странную тенденцию. О новичках говорят либо плохо, либо никак. На вопросы новичков смотрят свысока. Придираются к словам, закидывают яйцами за неточные формулировки. И это в лучшем случае! Большинство просто проходят мимо. Исключения, безусловно, встречаются. Но новички в такой обстановке чувствуют себя очень неуверенно, скованно. Они сбиваются в стайки и пытаются научиться хоть чему-то между собой. Понимаете исход? На выходе получается фабрика велосипедов. Я знаю несколько таких историй. А потом вы, опытные разработчики, ругаетесь на невероятные конструкции и страшный код. Подумайте, а могли ли вы этого не допустить? Отсюда и родилась мысль, что в разработке пора начать говорить о наставничестве. Давно прижившееся в других сферах и доказавшее свою эффективность, наставничество шагает по планете. Наше сообщество уже достаточно выросло и окрепло, чтобы начать смотреть вперед на несколько шагов и передавать полученный опыт из уст в уста.</p>
</blockquote>
<h2>Мои личные грабли</h2>
<img src="https://web-standards.ru/articles/path-of-tutor/images/2.png" srcset="images/2@2x.png 2x" alt="Дайте наставника срочно!">
<p>В недалёком 2012 году я написал уже упомянутою мной статью под незамысловатым названием <a href="http://css-live.ru/faq/put-verstalshhika.html">«Путь верстальщика»</a>, где я поведал читателям о своем собственном пути в веб-разработке. В целом статья получила множество положительных откликов, и, казалось бы, всё круто, есть профит, но не тут-то было. В один из разделов этой статьи всё-таки закрался червячок, я бы даже сказал, не просто червячок, а такой жирный огромный червь. Это был раздел «Наставник». В этом пункте я описывал, как мне помогали и помогают учителя, и какие плоды приносили занятия с ними.</p>
<p>Я совершил одну непростительную ошибку, за которую мне до сих пор стыдно. Дело в том, что в то время ещё не было ни таких замечательных обучающих сайтов, как <a href="https://htmlacademy.ru/">«HTML Academy»</a>, ни подобных ресурсов, да и вообще, в Рунете не было столько информации, как сейчас. Поэтому я взял и сдуру послал всех новичков на <a href="https://htmlforum.ru/">htmlforum.ru</a>, сказав им, чтобы они искали себе учителей именно там, как в своё время поступил и я сам.</p>
<p>После нехилого резонанса статьи тысячи новичков восприняли мой посыл буквально и толпами повалили на форум в поисках веб-сэнсэя. Ежедневно на форуме создавалось по сто одинаковых тем, в которых начинающие разработчики предлагали себя в качестве учеников и просили, чтобы им в срочном порядке выдали наставника. Бедные модераторы уставали, не успевая каждый день удалять и отвечать на множество похожих тем, и продолжалось это, не поверите, <strong>аж несколько лет</strong>, моему коллеге даже пришлось <a href="https://htmlforum.ru/topic/47881-%D0%BA%D0%B0%D0%BA-%D0%BD%D0%B0%D0%B9%D1%82%D0%B8-%D0%BD%D0%B0%D1%81%D1%82%D0%B0%D0%B2%D0%BD%D0%B8%D0%BA%D0%B0%D0%B3%D1%83%D1%80%D1%83-%D1%83%D1%87%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D1%8F-%D1%81%D1%8D%D0%BD%D1%81%D1%8D%D1%8F-%D0%BF%D0%BE-%D0%B2%D1%91%D1%80%D1%81%D1%82%D0%BA%D0%B5/">создать отдельную тему</a>, дабы усмирить пыл неофитов! А ещё, как назло, всё это совпало с тем, что мне нужно было уйти из веба на полтора года по семейным обстоятельствам, т.е., по сути всё это происходило в моё отсутствие. Вот такой вот непростительный вброс я сделал и наломал дров. Теперь вы понимаете, почему мне до сих пор так стыдно перед нашими модераторами?</p>
<p>Конечно, мне поступали предложения вообще удалить этот раздел из статьи, но я был решительно против этого, поскольку, во-первых, из песни слов не выкинешь, а во-вторых, я всё-таки рассказывал собственную историю, и мне не хотелось что-то из неё вырезать.</p>
<h3>Этикет наставничества</h3>
<img src="https://web-standards.ru/articles/path-of-tutor/images/3.png" srcset="images/3@2x.png 2x" alt="Этикет наставничества.">
<blockquote>
<p><strong>А.Б.</strong> После доклада ко мне обратилось несколько человек с просьбой подсказать, что нужно делать, чтобы найти наставника. Всем им я дала примерно одинаковые советы.</p>
<ol>
<li>Будьте активны. Явно выражайте интерес к происходящему в мире разработки в целом и в вашем регионе в частности. Посещайте доступные для вас конференции, встречи и митапы.</li>
<li>Не останавливайтесь в развитии. На то время, пока вы ищите наставника, вы не должны переставать самостоятельно обучаться. Продолжайте совершенствовать свои знания, изучайте новое, набивайте руку на тестовых проектах, пополняйте портфолио. Согласитесь, с наставником будет интереснее обсуждать глобальные вопросы, нежели выравнивание по вертикали.</li>
<li>Найдите людей, чьей работой вы восхищаетесь. Чаще всего это публичные люди. Они с удовольствием идут на контакт. Последите за их деятельностью, с кем они общаются, кто в их окружении. Расширяйте свой круг виртуальных знакомств. Беря курс на крутых разработчиков, вы и сами для себя ставите высокую планку.</li>
<li>Спрашивайте в сообществах, чатах, на форумах. Но, пожалуйста, перед тем как написать вопрос, постарайтесь самостоятельно найти решение. Обязательно поищите информацию в англоязычном интернете. И только после того, как вы испробовали на ваш взгляд все возможные варианты, идите задавать вопрос.</li>
<li>Обратите внимание на того, кто на ваш взгляд дал наиболее развернутый ответ на ваш вопрос. Подумайте, вызывает ли этот человек у вас симпатию. Постарайтесь найти его проекты и изучить их. Если вы поняли, что хотели бы чему-то научиться у этого специалиста, то попросите разрешения написать ему лично в следующий раз.</li>
<li>Будьте готовы к отказу. У человека может не оказаться свободного времени. Отнеситесь с пониманием и продолжайте свои поиски.</li>
<li>Не будьте навязчивыми. Уважайте чужое время и пространство. В самом начале личной переписки предложите обговорить удобный для вас обоих формат общения.</li>
<li>Не обязательно официальное предложение. Иногда общение складывается естественным образом или в таких условиях, где официальное предложение стать наставником будет просто неуместно. Старайтесь перенимать любой опыт, который будет вам доступен. Но я лично считаю, что заданный вопрос о наставничестве будет означать честный и открытый договор между двумя людьми, которые понимают природу взаимоотношений. Так вы скорее всего избежите неловкости и недопонимания.</li>
<li>Благодарите наставника при любой возможности. Если у вас есть возможность высказать свою благодарность публично, то обязательно сделайте это. Таким образом вы внесете вклад в личный бренд наставника в сообществе. Про личную благодарность я даже напоминать не буду.</li>
</ol>
</blockquote>
<h2>Не было бы счастья, да несчастье помогло</h2>
<p>На самом деле из этой истории я вынес для себя несколько полезных уроков.</p>
<h3>Говорите о наставничестве</h3>
<img src="https://web-standards.ru/articles/path-of-tutor/images/4.png" srcset="images/4@2x.png 2x" alt="Говорите о наставничестве.">
<p>Первым делом я осознал, что тема наставничества сама по себе очень тонкая, насущная и волнующая массы. Эта тема всегда будет актуальна и будет постоянно пользоваться спросом. Ведь недаром несколько лет кряду новички мучили модераторов на форуме, а вокруг доклада Алёны поднялся такой ажиотаж, что в Слаке несколько дней не утихали споры о пользе и вреде наставничества.</p>
<p>Сама тема наставничества крайне живая и полезная, хотя с последним эпитетом многие не согласятся, но об этом мы поговорим чуть позже. Эта тема напомнила мне очень крутой <a href="https://www.youtube.com/watch?v=xPPCzryZK44">доклад Вадима Макишвили «36»</a> — такой же животрепещущий и способный затронуть самые тонкие струны нашей души. Если вы не совсем понимаете, о чём я, то обязательно посмотрите доклад Вадима.</p>
<p>Но если наставничество — такая важная вещь для сообщества, почему же о ней так мало говорят? Вы часто видели доклады или хотя бы статьи на эту тему? Вот видите, и я не часто. Я считаю, что наш долг — делать веб лучше, а говорить о наставничестве — это как раз и означает делать веб лучше. Не стесняйтесь, пишите статьи, делайте доклады, рассказывайте собственные истории (пусть даже и негативные), делитесь тем, как это происходит в ваших компаниях, да и просто говорите об этом! И тогда, возможно, все больше желающих учить и учиться будет появляться в наших рядах!</p>
<h3><del>Уважение</del> Наставника ещё надо заслужить</h3>
<img src="https://web-standards.ru/articles/path-of-tutor/images/5.png" srcset="images/5@2x.png 2x" alt="Наставника ещё надо заслужить.">
<p>Временами, мысленно возвращаясь к своей статье и к этому злосчастному пункту «Наставник», я осуждаю себя за то, что не догадался упомянуть один важный ключевой момент, из-за которого, возможно, высшие силы и наградили меня учителем. Ведь, чтобы меня взяли на поруки, изначально мне пришлось изрядно попотеть.</p>
<p>Я не просто пришёл на форум, создал пост и получил наставника, как, видимо, подумали многие. Долгое время мне приходилось доказывать, что я чего-то стою, что у меня есть потенциал. Я старался не пропускать ни одну тему на форуме, пытался помогать другим, отписывал тонны комментариев, брался за любые, даже непосильные для меня задачи, пробовал себя то в одном, то в другом деле, я фактически сутками жил на форуме. То есть, я делал <strong>всё</strong>, чтобы меня заметили и захотели обучать. А главное, мне настолько нравилось моё занятие, что в те моменты я даже и не думал о каких-то там наставниках.</p>
<p>Приведу простой пример — героиня моей статьи Алёна Батицкая. Как человек со стороны, я прекрасно вижу, что представляет из себя эта девушка. Я вижу, что она пишет в Твиттере, как она общается, какие статьи она переводит, она даже выступает с докладами! И я понимаю, что этот человек пойдёт далеко, что она любит то, чем занимается, и с каждым днём эта любовь только крепнет. Она не очередной пассажир. Понимаете, о чем я?</p>
<p>Я никого не хочу обидеть, но сегодня не так много альтруистов, готовых тратить своё драгоценное время на кого попало. Мне кажется, что всеми своими действиями нужно доказывать своё право на место под солнцем. Нужно давать понять окружающим, что ты можешь, хочешь, а главное, что ты достоин, чтобы тебя заметили. Нужно выказывать свои стремление и желание расти и быть лучше. Другими словами, <strong>наставника нужно заслужить</strong>! Мало просто прийти на тот же форум и потребовать учителя.</p>
<blockquote>
<p><strong>А.Б.</strong> Новичку стоит помнить, что наставничество — дело добровольное. При этом довольно трудозатратное. Учитель должен ощутить, что он получит пусть и нематериальную, но выгоду от работы с вами. Что его время не будет потрачено зря. Требовать в этом деле вообще нельзя. Между учеником и учителем должна пробежать искра. Они должны комфортно ощущать друг друга в рамках этих партнерских отношений. Каждый должен с удовольствием отдавать и с благодарностью принимать. Взаимное уважение и стремление сделать друг друга лучше — основа крепких отношений между наставником и подопечным.</p>
</blockquote>
<h3>Два лагеря</h3>
<img src="https://web-standards.ru/articles/path-of-tutor/images/6.png" srcset="images/6@2x.png 2x" alt="Два лагеря.">
<p>К сожалению, далеко не все разделяют мнение о пользе наставничества. Здесь люди делятся на два лагеря. Первые одобряют это, а вторые, <strong>которых большинство</strong>, считают, что наставничество — это утопия, путь в никуда, и что веб-сэнсэй может принести только вред. Их мнение сводится к тому, что у них не было никаких наставников, что их никто не учил, и что при этом они всего добились сами, своими силами. Они считают, что если рядом с вами будет человек, который будет тыкать вас в ошибки, искать за вас информацию в интернете и всюду «подтирать» за вами, то вы не сможете стать самостоятельными, не научитесь справляться своими силами, а значит веб-разработка не для вас.</p>
<p>Поскольку я принадлежу к первому лагерю, поначалу я пытался сопротивляться и отстаивал свою точку зрения. Но позже я осознал, что это лишняя трата времени, и что первые никогда ничего не докажут вторым, и наоборот. Я постарался по-взрослому проанализировать эти вещи и понял следующее. Если обратить внимание не на слова, а на дело, точка зрения несогласных с наставничеством вполне имеет право на жизнь. Во-первых, потому что их большинство, а во-вторых, просто потому, что среди них есть очень толковые ребята, которые на деле доказали, что можно всего добиться самому. У меня у самого куча таких знакомых, например, тот же <em>@s0rr0w</em> из Слака «Веб-стандартов», которого наверняка все знают по его прикольной аватарке и огромному опыту!</p>
<p>Я пришёл к мнению, что в этом деле не стоит ссориться, ругаться и устраивать драки. Мы не в политике и не на войне, мы делаем общее дело и ходим под одним <del>небом</del> вебом. Вставлять друг другу палки в колёса — не выход. Если начинающий разработчик чувствует, что ему нужен учитель, то зачем его отговаривать от этого, пытаться разубедить? Посоветуйте лучше хорошего коллегу, который занимается наставничеством, или по крайней мере не навязывайте свое отношение. У каждого свой путь, как и свое мнение.</p>
<blockquote>
<p><strong>А.Б.</strong> Большая просьба к опытным, старшим разработчикам — реагируйте адекватно на джунов, которые приходят и с порога требуют наставника. Как и во многом другом, в природе наставничества они еще не до конца разобрались. Не злитесь на них, не пытайтесь убедить, что «так бывает только в сказках». Лучше пошутите по-доброму и дайте почитать эту статью. В подобных случаях я всегда советую вспомнить как вы сами были новичками. Ваш негатив очень сильно ранит джунов. Они и так не уверены в себе, понимают, что умеют еще очень немного. Но их неопытность не недостаток. Стремление к знаниям и желание развиваться — их основной козырь. Не ломайте им крылья.</p>
</blockquote>
<h2>Почему я должен делиться опытом? Я сам научился всему!</h2>
<img src="https://web-standards.ru/articles/path-of-tutor/images/7.png" srcset="images/7@2x.png 2x" alt="Почему я должен делиться опытом? Я сам научился всему!">
<p>Многие противники наставничества говорили эту фразу. Почему они смогли, а нынешняя молодежь не может?</p>
<p>Стоит оглядеться и сопоставить количество доступных технологий, скорость развития индустрии, разницу подходов, методологий, принципов. Когда учились вы — было несколько сайтов, пара хороших книг и узкий круг таких же, как вы. Было гораздо меньше информации. Было проще понять, что в каком порядке изучать. Было меньше технологий, которые требовались для работы. Вы получили хорошую основу, начали ее применять, а затем постепенно наращивали багаж знаний.</p>
<p>Сегодня доступность и количество информации является не только плюсом, но и минусом. Неопытный человек теряется, пытается ухватиться за все подряд и в итоге ничего толком не может освоить. За редким исключением.</p>
<h2>Наставничество — не работа</h2>
<img src="https://web-standards.ru/articles/path-of-tutor/images/8.png" srcset="images/8@2x.png 2x" alt="Наставничество — не работа.">
<p>Старшие товарищи, не воспринимайте наставничество как обязанность, работу, за которую к тому же не платят. Я, как и моя соавтор, как любой, кто говорит с вами о наставничестве, не пытаемся навязать вам неприятное занятие, которым вы непременно должны заняться. Наставничество — это определенный этап развития вас, как члена сообщества.</p>
<p>Веб является молодой сферой. Сообщество находится на начальном этапе развития внутренних отношений. По моему личному мнению, пришло время задуматься о подрастающем поколении. Условия изменились, выживать в одиночку все сложнее. В первую очередь, у вас должно появиться желание отдавать безвозмездно. Если в отношениях будут фигурировать деньги или иные материальные блага, то речь будет идти уже о репетиторстве в той или иной форме.</p>
<p>Вашего времени должно хватать на еще одного человека. При этом не обязательно быть на связи постоянно. Вы можете обговорить с подопечным удобный формат общения. Например, письмо с вопросами раз в неделю, созвон по Скайпу два раза в месяц или общение только в вечернее время в чатах. Никто не ждет от вас жертвенности. Вам должно быть комфортно.</p>
<p>Если посмотреть на <a href="https://ru.wikipedia.org/wiki/%D0%9F%D0%B8%D1%80%D0%B0%D0%BC%D0%B8%D0%B4%D0%B0_%D0%BF%D0%BE%D1%82%D1%80%D0%B5%D0%B1%D0%BD%D0%BE%D1%81%D1%82%D0%B5%D0%B9_%D0%BF%D0%BE_%D0%9C%D0%B0%D1%81%D0%BB%D0%BE%D1%83">пирамиду Маслоу</a>, то наставничество находится на ее вершине, в пункте «Самоактуализация».</p>
<p>Наставничество — только одна из сотен возможностей внести свой вклад в сообщество, частью которого вы являетесь.</p>
<h2>Живые примеры</h2>
<img src="https://web-standards.ru/articles/path-of-tutor/images/9.png" srcset="images/9@2x.png 2x" alt="Живые примеры.">
<p>К этому моменту у многих из вас уже наверняка возник вопрос: «Чувак, что ты нам за дичь втираешь уже столько времени, у тебя реальные примеры есть? Кому твоё наставничество вообще помогло, а?». На самом деле примеры есть, и прямо сейчас мы их рассмотрим.</p>
<p>На наставничестве специализируются целые организации. Например, ребята из <a href="https://htmlacademy.ru/">«HTML Academy»</a> уже давно и довольно успешно обучают молодое поколение веб-разработчиков. Они сосредоточены на фронтенде и могут запросто пройти с вами путь от новичка до профессионала, стоит вам только захотеть. На их сайте есть множество бесплатных уроков, а также платные интенсивы и курсы, на которых как раз можно нанять преподавателя. И <a href="https://htmlacademy.ru/blog/91-online-intensive-html-css">многочисленные положительные отзывы</a> только подтверждают факт того, что наставник — это здорово!</p>
<p>Есть программы наставничества и у Яндекса, например, <a href="https://academy.yandex.ru/events/frontend/shri_msk-2016/">«ШРИ» (Школа разработки интерфейсов)</a>. Они набирают начинающих фронтенд-разработчиков с любым опытом работы и обучают их всем премудростям вёрстки. Обучение делится на два этапа: первый состоит из курса лекций, а на втором ученикам выпадает возможность поучаствовать в разработке совместного проекта. Занятия проходят в офисах Яндекса, а в качестве наставников выступают сами разработчики Яндекса, которые делают интерфейсы для Поиска, Почты, Карт и других сервисов. Кстати, «ШРИ» проходит в разных городах и абсолютно бесплатно, поэтому у каждого из нас есть шанс туда попасть. Если есть желание, можете почитать <a href="https://habrahabr.ru/company/yandex/blog/189056/">целую увлекательную историю</a> одного из учеников этой школы.</p>
<p>Но, пожалуй, особое везение — найти личного наставника, готового поддерживать добрым словом и полезным советом несколько лет подряд, на разных этапах профессионального роста. Это явление настолько редкое, что примеры надо еще поискать, но у меня как раз есть несколько.</p>
<p>По вышеописанному нетрудно догадаться, что я всегда был двумя руками за наставничество. За что бы я ни брался, у меня всегда были учителя. Например, вёрстке меня обучал искусный мастер своего дела <a href="https://htmlforum.ru/profile/4521-klierik/">Алексей Филиппович</a>, JavaScript — упомянутый выше великий и могучий <a href="https://github.com/s0rr0w">s0rr0w</a> (да, не удивляйтесь, <strong>s0rr0w</strong> приветствует оба лагеря, но с нюансами, о которых он поведает в комментариях), ну а сегодня (и уже на протяжении лет шести) моей путеводной звездой является Илья Стрельцын, <a href="https://twitter.com/SelenIT2">@SelenIT2</a> в Твиттере, человек, которому я безмерно благодарен, и который способен дать не только профессиональный, но и жизненный совет.</p>
<p>Вы можете спросить, мол, и что путного вышло из всего этого наставничества? Да, собственно целый проект — <a href="http://css-live.ru/">css-live.ru</a>, который благодаря наставничеству благополучно живёт и здравствует и по сей день! Чем не доказательство?</p>
<h3>Полегче, новичок!</h3>
<img src="https://web-standards.ru/articles/path-of-tutor/images/10.png" srcset="images/10@2x.png 2x" alt="Два лагеря.">
<blockquote>
<p><strong>А.Б.</strong> Новичок, не думай, что с нахождением наставника ты решишь все свои проблемы. Не воспринимай это как единственный способ развития. Тебе по-прежнему придется делать все самому. Много читать, практиковаться, пробовать. Разница только в том, что рядом будет человек с опытом, с которым ты сможешь обсудить не до конца понятные тебе вопросы, узнать, верно ли твое решение или это очередной велосипед, просто поговорить на интересные темы. Наставник подскажет тебе, что стоит освоить в первую очередь, а к чему можно вернуться позже. Но он не твой персональный репетитор, не твой домашний учитель, не Гугл. Относись с уважением к его желанию вложить в тебя силы и время. Благодари его за опыт, которым он делится. Благодарность — единственная валюта в этих отношениях. Если ты способен что-то дать в замен — давай, не раздумывая. Наставничество — как дружба, нуждается во внимании обоих.</p>
</blockquote>
<h2>Под занавес</h2>
<p>Несмотря на внушительный объём статьи, тема наставничества была раскрыта далеко не полностью. Я попытался рассказать свою историю, а Алёна дала множество замечательных советов. Но, тем не менее, здесь не хватает ваших историй, связанных с наставничеством, тех, которые произошли лично с вами. Не стесняйтесь, делитесь своими примерами в комментариях, уверен, что они окажутся полезными для сообщества! Либо, если не хотите афишировать, можете написать Алёне на <a href="https://web-standards.ru/articles/path-of-tutor/mailto:batickaya.a@gmail.com">почту</a> или <a href="https://www.facebook.com/ABatickaya">на Фейсбуке</a>.</p>
<blockquote>
<p><strong>А.Б.</strong> Пока в русскоязычном интернете нет специализированных площадок, на которых могут встретиться подопечные и наставники. Людям хочется найти друг друга, но иногда для этого нужна общая точка сбора. Я решила, пока возможно, собирать у себя контакты желающих и по возможности сводить учителей и учеников. Для этих целей создана <a href="http://goo.gl/forms/pMC7UVjI4mfWdBU33">небольшая анкета</a>. Если вам интересно найти своего наставника или подопечного — обязательно заполните ее. Это эксперимент, попытка собрать некую общую базу желающих. Я буду вручную отслеживать ответы и высылать контакты. Также на днях <a href="https://gitter.im/FrontendTutors/general">в Гиттере собралась рабочая группа</a>, которая планирует создать специализированный русскоязычный портал для этих целей. Присоединяйтесь!</p>
</blockquote>
<h2>Благодарности</h2>
<p>Огромное спасибо моему соавтору <a href="https://twitter.com/ABatickaya">Алёне Батицкой</a> за такую прекрасную поддержку и ценные дополнения к статье. Также нельзя не поблагодарить <a href="http://positivecrash.com/">Анастасию Бакай</a> за потрясающие иллюстрации. И, конечно же, отдельное спасибо всем нашим учителям!</p>
<blockquote>
<p><strong>А.Б.</strong> От себя хочу сказать спасибо людям, которые были и остаются рядом на пути моего развития: Вера Жукова, Татьяна Тен, Дмитрий Фитискин и Вадим Макеев. Это далеко не полный список тех, кто нашел в себе силы и желание поделиться своим опытом.</p>
</blockquote>

                    ]]></description><pubDate>Thu, 30 Jun 2016 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/path-of-tutor/</guid></item><item><title>Паттерны загрузки веб-шрифтов</title><link>https://web-standards.ru/articles/web-font-loading-patterns/</link><description><![CDATA[
                        <p><em>Загрузка веб-шрифтов может показаться сложной задачей. Однако, на самом деле, она довольно проста, если вы будете использовать описанные ниже паттерны. Комбинируя их, вы сможете управлять загрузкой веб-шрифтов во всех браузерах.</em></p>
<p>В этих паттернах используется <a href="https://github.com/bramstein/fontfaceobserver">Font Face Observer</a>, простая и небольшая библиотека загрузки веб-шрифтов. Font Face Observer выбирает наиболее эффективный способ загрузки шрифта, основываясь на его браузерной поддержке, так что мы можем загружать шрифты, не беспокоясь о кроссбраузерности.</p>
<ol>
<li><a href="https://web-standards.ru/articles/web-font-loading-patterns/#section-1">Обычная загрузка шрифтов</a></li>
<li><a href="https://web-standards.ru/articles/web-font-loading-patterns/#section-2">Загрузка группы шрифтов</a></li>
<li><a href="https://web-standards.ru/articles/web-font-loading-patterns/#section-3">Загрузка шрифтов с таймером</a></li>
<li><a href="https://web-standards.ru/articles/web-font-loading-patterns/#section-4">Приоритетная загрузка</a></li>
<li><a href="https://web-standards.ru/articles/web-font-loading-patterns/#section-5">Особое отображение шрифтов</a></li>
<li><a href="https://web-standards.ru/articles/web-font-loading-patterns/#section-6">Оптимизация для кэширования</a></li>
</ol>
<p>Невозможно посоветовать единый паттерн, который идеально подходил бы каждому. Внимательно изучите свой сайт, его аудиторию, и на основании этого выберите тот способ загрузки или их комбинацию, которые подойдут лучше всего.</p>
<h2>Обычная загрузка шрифтов</h2>
<p>Font Face Observer даёт вам возможность контролировать загрузку веб-шрифтов через простой интерфейс, основанный на промисах. Не имеет значения, откуда будут загружаться шрифты: вы можете размещать их как у себя, так и подключать через сервисы — <a href="https://www.google.com/fonts">Google Fonts</a>, <a href="https://typekit.com/">Typekit</a>, <a href="https://fonts.com/">Fonts.com</a>, и <a href="http://www.webtype.com/">Webtype</a>.</p>
<p>Чтобы не перегружать паттерны лишним кодом, будем считать, что вы размещаете веб-шрифты у себя. Это означает, что в ваших CSS-файлах будет одно или несколько объявлений <code>@font-face</code>, в которых указано, какие шрифты нужно загрузить через Font Face Observer. Для краткости мы не будем объявлять каждое из подобных правил в коде, но будем считать, что они есть.</p>
<pre><code tabindex="0" class="language-css">@font-face {
    font-family: Output Sans;
    src: url(output-sans.woff2) format('woff2'),
            url(output-sans.woff) format('woff');
}
</code></pre>
<p>Рассмотрим самую обычную задачу: вам надо загрузить один или несколько разных шрифтов. Создайте несколько экземпляров <code>FontFaceObserver</code>, по одному на каждый шрифт, и вызовите их метод <code>load</code>.</p>
<pre><code tabindex="0" class="language-js">var output = new FontFaceObserver('Output Sans');
var input = new FontFaceObserver('Input Mono');

output.load().then(function () {
    console.log('Загружен Output Sans');
});

input.load().then(function () {
    console.log('Загружен Input Mono');
});
</code></pre>
<p>Этот способ загрузит каждый шрифт независимо от остальных. Это полезно, когда шрифты не связаны друг с другом, и мы ожидаем, что они отрисуются постепенно (т.е. как только загрузятся, так и отрисуются). В отличие от стандартного <a href="https://www.w3.org/TR/css-font-loading/">API загрузки шрифтов</a> вы не передаёте URL шрифтов в Font Face Observer. Для загрузки шрифтов он использует объявления <code>@font-face</code>, уже доступные в CSS. Это позволяет загружать веб-шрифты вручную через JavaScript, предусмотрев постепенную деградацию к обычному CSS.</p>
<h2>Загрузка групп шрифтов</h2>
<p>Вы можете загружать несколько шрифтов одновременно, группируя их: вся группа либо загрузится полностью, либо выдаст ошибку. Это полезно использовать, когда загружаемые шрифты принадлежат к одному семейству, и вы не хотите, чтобы группа отрисовывалась, пока не загрузятся все шрифты из неё. Тем самым, браузер не будет отображать стили, пока шрифты представлены не полностью.</p>
<pre><code tabindex="0" class="language-js">var normal = new FontFaceObserver('Output Sans');
var italic = new FontFaceObserver('Output Sans', {
    style: 'italic'
});

Promise.all([
    normal.load(),
    italic.load()
]).then(function () {
    console.log('Загружено семейство Output Sans');
});
</code></pre>
<p>Вы можете сгруппировать шрифты через <code>Promise.all</code>. Когда промис будет выполнен, мы будем знать, что все шрифты загружены. Если промис отклонён — как минимум один из шрифтов не смог загрузиться.</p>
<p>Ещё один пример применения группировки шрифтов — уменьшение количества перекомпоновок страницы. Если вы загружаете и отрисовываете веб-шрифты постепенно, браузер будет выполнять перекомпоновку страницы множество раз, так как характеристики начертаний запасного и веб-шрифта различны. Группировка позволит сократить количество перекомпоновок до одной.</p>
<h2>Загрузка шрифтов с таймером</h2>
<p>Иногда шрифты загружаются очень долго. Это может стать проблемой, поскольку веб-шрифты отвечают за текст — главную часть контента вашей страницы. Невозможно ждать загрузки шрифтов бесконечно. Мы можем исправить это, добавив таймер к загрузке.</p>
<p>Следующая вспомогательная функция создаёт таймеры, возвращающие промис, отклоняющийся по срабатыванию таймера.</p>
<pre><code tabindex="0" class="language-js">function timer(time) {
    return new Promise(function (resolve, reject) {
        setTimeout(reject, time);
    });
}
</code></pre>
<p>С помощью <code>Promise.race</code> мы можем заставить таймер и загрузку шрифта «соревноваться» друг с другом. Например, если загрузка завершилась до того, как сработал таймер, то шрифт победил, промис выполнен. А если раньше сработал таймер, промис отклонён.</p>
<pre><code tabindex="0" class="language-js">var font = new FontFaceObserver('Output Sans');

Promise.race([
    timer(1000),
    font.load()
]).then(function () {
    console.log('Загружен Output Sans');
}).catch(function () {
    console.log('Время на загрузку Output Sans истекло');
});
</code></pre>
<p>В этом примере время загрузки шрифта ограничено одной секундой. Вместо того, чтобы работать с одним шрифтом, мы также можем назначить таймер целой группе шрифтов. Это позволит просто и эффективно ограничить время их загрузки.</p>
<h2>Приоритетная загрузка</h2>
<p>Обычно, для того, чтобы отрисовать первую половину экрана <em>(above the fold — верхняя половина первого экрана сайта, которую надо отобразить максимально быстро — прим. переводчика),</em> нужно лишь несколько шрифтов. Если загружать эти шрифты раньше других, менее важных, мы получим выигрыш в производительности сайта. Это называется приоритетной загрузкой.</p>
<pre><code tabindex="0" class="language-js">var primary = new FontFaceObserver('Primary');
var secondary = new FontFaceObserver('Secondary');

primary.load().then(function () {
    console.log('Загружен основной шрифт')

    secondary.load().then(function () {
        console.log('Загружен второстепенный шрифт')
    });
});
</code></pre>
<p>При использовании приоритетной загрузки второстепенный шрифт зависит от основного: если не загрузится основной шрифт, то не загрузится и второстепенный. Это может оказаться полезным.</p>
<p>Например, можно применить приоритетную загрузку так: сначала загрузить небольшой основной шрифт, содержащий ограниченное число символов, а затем полный шрифт с большим количеством символов или стилей. Так как основной шрифт мал, он намного быстрее загрузится и отрисуется. А если основной шрифт не смог загрузиться, то, вполне возможно, не следует запрашивать и второстепенный — скорее всего, он тоже не загрузится.</p>
<p>Такое использование приоритетной загрузки более подробно описано в статьях Зака Лезермана: <a href="https://www.zachleat.com/web/foft/">Flash of Faux Text</a> и <a href="https://www.zachleat.com/web/web-font-data-uris/">Web Font Anti-Patterns: Data URIs</a>.</p>
<h2>Особое отображение шрифтов</h2>
<p>Перед тем как браузер сможет показать веб-шрифт, он должен скачать его по сети. Обычно это занимает какое-то время, и каждый браузер по-разному ведёт себя во время скачивания веб-шрифтов: некоторые из них скрывают текст, а другие сразу же показывают его с помощью запасного шрифта. Обычно это называют <em>мельканием невидимого текста</em> (Flash Of Invisible Text, FOIT) и <em>мельканием текста без стилей</em> (Flash Of Unstyled Text, FOUT).</p>
<figure>
    <img src="https://web-standards.ru/articles/web-font-loading-patterns/images/fout-foit.png" alt="FOUT и FOIT.">
    <figcaption>FOUT и FOIT.</figcaption>
</figure>
<p>Internet Explorer и Edge используют FOUT и отображают запасные шрифты, пока веб-шрифт не закончит свою загрузку. Все остальные браузеры используют FOIT и прячут текст во время загрузки веб-шрифтов.</p>
<p>Для управления этим поведением ввели новое CSS-свойство, называемое <code>font-display</code> (<a href="https://tabatkins.github.io/specs/css-font-display/">CSS Font Rendering Controls</a>). К сожалению, оно до сих пор находится в разработке и ещё не поддерживается ни одним браузером (на данный момент оно спрятано за флагом в Chrome и Opera). Однако мы можем реализовать аналогичное поведение во всех браузерах с помощью Font Face Observer.</p>
<p>Вы можете обмануть браузеры, использующие FOIT, заставив их сразу же отрисовывать текст запасными шрифтами, используя только полностью загруженные шрифты из вашей цепочки. Если шрифт ещё не в цепочке (поскольку он загружается), браузеры не будут пытаться скрыть текст.</p>
<p>Простейший способ сделать это — устанавливать на элемент <code>&lt;html&gt;</code> по классу на каждое из трёх состояний загрузки веб-шрифта: сам процесс загрузки, его завершение и ошибку. Класс <code>fonts-loading</code> устанавливается сразу, как начинается загрузка, <code>fonts-loaded</code> — когда шрифт загружен, и <code>fonts-failed</code> — если загрузка не удалась.</p>
<pre><code tabindex="0" class="language-js">var font = new FontFaceObserver('Output Sans');
var html = document.documentElement;

html.classList.add('fonts-loading');

font.load().then(function () {
    html.classList.remove('fonts-loading');
    html.classList.add('fonts-loaded');
}).catch(function () {
    html.classList.remove('fonts-loading');
    html.classList.add('fonts-failed');
});
</code></pre>
<p>При помощи этих классов и простого CSS вы можете кроссбраузерно использовать FOUT. Начнём с объявления запасных шрифтов для всех элементов, которым понадобятся веб-шрифты. Когда в <code>&lt;html&gt;</code> появляется класс <code>fonts-loaded</code>, мы применяем веб-шрифт, изменяя цепочку шрифтов для всех соответствующих элементов. Изменение правила в CSS заставит браузер загрузить веб-шрифт, однако, поскольку к этому моменту он уже будет загружен, перерисовка начнется практически мгновенно.</p>
<pre><code tabindex="0" class="language-css">body {
    font-family: Verdana, sans-serif;
}

.fonts-loaded body {
    font-family: Output Sans, Verdana, sans-serif;
}
</code></pre>
<p>Такой способ загрузки шрифтов может показаться вам похожим на технику прогрессивного улучшения. Так оно и есть: <em>мелькание текста без стилей</em> (FOUT) соответствует прогрессивному улучшению. Базовый вид в первую очередь отрисовывается запасными шрифтами, а затем улучшается веб-шрифтами.</p>
<p>Реализация FOIT такая же простая. Когда веб-шрифты начинают загрузку, вы скрываете контент, использующий шрифты, а когда они загружены, вы показываете его снова. Не забывайте и об ошибке загрузки — контент должен быть доступен, даже если шрифты не смогли загрузиться.</p>
<pre><code tabindex="0" class="language-css">.fonts-loading body {
    visibility: hidden;
}

.fonts-loaded body,
.fonts-failed body {
    visibility: visible;
}
</code></pre>
<p>Такой способ сокрытия контента кажется вам странным? Хорошо, если так. Этот паттерн следует применять только в очень специфичных случаях. Например, если у вас нет подходящего запасного шрифта, или вы точно знаете, что шрифт был закэширован ранее.</p>
<h2>Оптимизация для кэширования</h2>
<p>Все предыдущие паттерны позволяли вам регулировать когда и как загружаются шрифты. Однако, часто мы хотим, чтобы в зависимости от наличия или отсутствия шрифта в кэше сайт вёл себя по-разному. Например, если шрифт закэширован, нет необходимости отрисовывать текст сначала запасным шрифтом. Такого эффекта можно добиться сохранением в Session Storage статуса о том, был ли шрифт закэширован или нет.</p>
<p>Когда шрифт загружен, мы устанавливаем флаг в Session Storage. Флаг сохраняется на протяжении всей сессии, и с его помощью мы можем определить, находится файл в браузерном кэше или нет.</p>
<pre><code tabindex="0" class="language-js">var font = new FontFaceObserver('Output Sans');

font.load().then(function () {
    sessionStorage.fontsLoaded = true;
}).catch(function () {
    sessionStorage.fontsLoaded = false;
});
</code></pre>
<p>Теперь вы можете использовать эту информацию, чтобы изменить стратегию загрузки закэшированных шрифтов. Например, можно включить такой фрагмент JavaScript в элемент <code>&lt;head&gt;</code> вашей страницы, чтобы сразу же отрисовывать веб-шрифты.</p>
<pre><code tabindex="0" class="language-js">if (sessionStorage.fontsLoaded) {
    var html = document.documentElement;
    html.classList.add('fonts-loaded');
}
</code></pre>
<p>Если вы будете загружать шрифты этим способом, ваши посетители увидят FOUT только при первом посещении сайта, а при всех дальнейших переходах шрифт будет отрисовываться мгновенно. А значит, с одной стороны, вы сохраните выгоды, которые даёт прогрессивное улучшение, а с другой — сделаете сайт удобным, поскольку загрузка страниц будет меньше раздражать при повторных посещениях.</p>

                    ]]></description><pubDate>Tue, 14 Jun 2016 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/web-font-loading-patterns/</guid></item><item><title>Прогрессивное улучшение меня расстраивает</title><link>https://web-standards.ru/articles/pe-makes-me-sad/</link><description><![CDATA[
                        <p>Последнее время только и разговоров в защиту прогрессивного улучшения и «универсальных» (изоморфных) приложений. Оказывается, серверный рендеринг улучшает быстродействие, надёжность, совместимость и упрощает парсинг содержимого.</p>
<p>Я поддерживаю эти аргументы из душевной щедрости.</p>
<p>Но есть и глубокая озабоченность, связанная с применением прогрессивного улучшения и изоморфизма, о которой стоит поговорить. И похоже только у меня хватит смелости, чтобы пойти против толпы. Не благодарите.</p>
<h3>1. Это сложно :(</h3>
<p>Создавать приложения, которые либо рендерят статическое содержимое и потом улучшают его с помощью JavaScript, либо работают как приложения сразу на сервере и клиенте — это очень, очень сложно. Типа <em>очень.</em></p>
<p>Нет, конечно, это значит, что больше пользователей получит доступ к содержимому быстрее и надёжнее — с этим я не спорю. Но кто сказал, что вообще <em>будут</em> какие-то пользователи? Или какое-то <em>содержимое</em>, если уж на то пошло. Половина проектов, над которыми я работаю, уходят в стол из-за реорганизаций компании. В чём смысл тратить всё это дополнительное время на сложности прогрессивного улучшения? Да не глупите.</p>
<p>Но это даже не самая главная проблема. Главная проблема в том, что моя идентичность построена вокруг интеллектуальной компетенции. Это значит, что, когда встречаюсь с интеллектуальной сложностью, я довольно быстро теряю эту самую компетенцию.</p>
<p>Поэтому, прежде чем приступать к архитектуре универсального приложения, спросите себя: «Правда ли мне нужно, чтобы кто-то в страшном когнитивном диссонансе взбесился на меня?»</p>
<p>Думаю нет. <em>(Это будто бы звучит как угроза, гы-гы. Сорьки, лол.)</em></p>
<h3>2. Мне это не нужно</h3>
<p>Прогрессивное улучшение делает жизнь пользователей легче в некоторых ситуациях, вроде медленных сетей, слабых устройств и т.п. — это правда. Не поймите меня неправильно, я двумя руками за то, чтобы создавать приложения для пользователей, но не забыли ли мы о ком-то ещё? Раз уж я создал чёртово приложение, разве у меня нет права голоса в том, как оно создано?</p>
<p>Конечные пользователи — это такое неизвестное множество, и мы мало что можем с этим сделать. С другой стороны, я уже знаю много чего о <em>себе</em> потому, что я — <em>это я.</em> Мне стоит пользоваться всеми преимуществами такой инсайдерской информации.</p>
<figure>
    <img src="https://web-standards.ru/articles/pe-makes-me-sad/images/1.png" alt="">
    <figcaption>
        Целевой рынок или «верхушка колокола».
    </figcaption>
</figure>
<p>И что же я знаю о себе? Ну, для начала, у меня вполне неплохое окружение: оптика, 16 Гб оперативки. А ещё я не выношу работы с прогрессивным улучшением, <strong>фу, гадость.</strong> (См. пункт №1).</p>
<p>Хотя больше всего я люблю делать новые фичи. Чем меньше времени мне придётся потратить на стабильную архитектуру (скукота), тем больше времени останется на создание фич. Знаете, как говорят: <strong>больше фич — лучше экспириенс!</strong> Все в выигрыше.</p>
<h3>3. Это провоцирует плохое поведение</h3>
<p>И если мы лезем вон из кожи (а это действительно заставляет вас лезть из кожи) чтобы убедиться, что содержимое дойдёт слабеньких устройств с крошечными лимитами трафика на дерьмовеньких сетях, мы просто даём аудитории отговорку, чтобы не обновляться.</p>
<p>Я хочу, чтобы ты был лучше, насколько это возможно!</p>
<p>Ну и поезда. То есть у вас проблемы с тем, чтобы загрузить срендеренную на клиенте блокирующе-зависимую от скриптов страничку по 2G потому, что вы в поезде? Решение прямо у вас под носом: <strong>хватит ездить на поезде.</strong> Либо найдите себе комнату в центре города рядом с работой, либо спите под столом. Я так и сделал, почему вы не сможете?</p>
<p>Ну правда, примите уже ответственность.</p>
<h3>4. Кругом только веб-приложения</h3>
<p>Веб изначально был рождён для публикации и распространения текстов в целях приобретения и утверждения знания. К счастью, эти тёмные и примитивные годы остались позади нас и мы перешли к лучшим вещам, вроде создания приложений, которые смотрят на фотографии собак и пытаются определить их возраст.</p>
<p>Нет никакого «документа» в основе приложения, которое определяет возраст вашей собаки. Ну, то есть, как бы он вообще выглядел? Картинка чьей-то собаки с подписью «Я не знаю сколько мне лет, извините» под ней? Это убого. Я бы скорее предпочёл не увидеть вообще ничего, если уж начистоту.</p>
<p>Нет, если вы не хотите знать, что думает ваш браузер о возрасте вашей собаки, пожалуйста — выключите JavaScript или прокатитесь на поезде (см. пункт №3). Вы всё равно не заслуживаете знать, сколько лет вашей собаке.</p>
<p>Но потом не приходите ко мне в слезах, когда кто-нибудь рядом спросит сколько лет вашей собаке, и вам придётся сказать «Я не знаю, где-то между 5 и 8, наверное» вместо «Я не знал, но кто-то запостил это довольно забавное веб-приложение на Фейсбуке. Давайте покажу. Минутку. Вот оно. Секунду. Извините. Сейчас. А, ладно, проехали».</p>

                    ]]></description><pubDate>Tue, 22 Mar 2016 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/pe-makes-me-sad/</guid></item><item><title>Хватит разрушать веб</title><link>https://web-standards.ru/articles/stop-breaking-the-web/</link><description><![CDATA[
                        <p>2014 год. Ниндзя-рок-звезды веба идут против забытой ныне техники <a href="http://alistapart.com/article/understandingprogressiveenhancement">прогрессивного улучшения</a>. Они отказываются от истоков веба и всего того, за что они когда-то стояли. В этой статье я собираюсь громко заявить о том, как <strong>мы разрушаем веб</strong>; о не всегда явных причинах, почему мы должны перестать это делать, и отчего <em>хорошей идеей будет это прекратить.</em></p>
<p><strong>TL;DR</strong> <em>Мы уничтожаем веб. Рендеринг исключительно на клиенте — отстой. Полифилы используются совсем не для того. Отвратительного вида хеш-роутинг ужасен, и мы от этого должны чувствовать себя ужасно. Мы годами говорили друг другу, что прогрессивное улучшение — это замечательно, и мы всё ещё почти ничего для него не делаем!</em></p>
<p>Скриншот ниже — всего лишь рекламный трюк: попытка захватить технический медиа-мир врасплох. Но тот факт, что <em>мы не уверены</em>, трюк это или решение всерьёз, заставляет меня сжиматься от страха за будущее веба.</p>
<figure>
    <img src="https://web-standards.ru/articles/stop-breaking-the-web/images/tacobell.jpg" alt="Реклама Taco Bell.">
    <figcaption>Taco Bell <em>#onlyintheapp</em> — действительно ли это <strong>ловкий рекламный трюк, чтобы спровоцировать загрузку приложения, или это симптом того, что веб истекает кровью?</strong></figcaption>
</figure>
<p><em>Оговорка: эта статья — <strong>не брюзжание об Angular 2.0.</strong> Мысли, озвученные в ней, начали зарождаться у меня давно, до появления Angular 2.0. Так случилось, что объявление планов развития Angular совпало с публикацией этой статьи. И хотя эти новости больше подкрепляют <a href="https://medium.com/este-js-framework/whats-wrong-with-angular-js-97b0a787f903">точку зрения его противников</a>, утверждения, стоящие за этой статьей, являются чем-то большим, чем просто критика изменений в публичном API Angular.</em></p>
<p>Мне грустно от понимания, что <strong>мы как сообщество подвели веб.</strong> Что вообще случилось с <a href="http://alistapart.com/article/understandingprogressiveenhancement">прогрессивным улучшением</a>? Знаете такое простое правило: <strong>помещайте содержимое на первый план</strong>? Все остальное вторично по отношению к содержимому, правильно? В первую очередь люди хотят увидеть ваше содержимое. Как только оно на месте, возможно, они захотят взаимодействовать с ним. Как бы там ни было, если содержимое не появляется первым, потому что <a href="http://ponyfoo.com/articles/critical-path-performance-optimization">ваша страница слишком медленная</a>, или потому что вы загружаете шрифты синхронно до того, как пользователь может что-нибудь прочитать, или потому что вы решили использовать рендеринг исключительно на клиенте — тогда, кажется, <strong>людей очень сильно надули.</strong> Верно?_</p>
<p>Конечно, сейчас интернет-соединения гораздо быстрее. Но у всех ли? Множество людей имеют доступ в сеть через <strong>мобильные соединения, вроде 2G и 3G</strong>, и они ожидают, что ваш сайт будет <em>таким же быстрым, как на десктопе.</em> А этого трудно добиться, когда вы блокируете содержимое загрузкой JavaScript.</p>
<p>Все чаще и чаще это становится нормой. Пофиг на людей, нам нужны все эти клёвые фреймворки, чтобы делать крутой веб! Подождите, но нам нужны люди. У них есть деньги, информация и всё такое. О, я знаю! Давайте дадим им роутинг на клиенте, даже если они на IE6. Это сделает их счастливыми, верно? Ох, тупой IE6 не поддерживает History API. Ладно, в топку IE6. Что? <a href="https://caniuse.com/#feat=history">IE9 тоже не поддерживает History API</a>? Хорошо, я буду делать все под IE10. Нет, погодите, это плохо, <strong>я использую хеш-роутер</strong> — и тогда обеспечу поддержку все IE, вплоть до IE6! О да, жизнь прекрасна, давайте сделаем наш сайт доступным через адреса типа <code>/#/products/nintendo-game-cube</code> — и сделаем JavaScript обязательным, чтобы наш роутер работал! А ещё давайте рендерить страницы исключительно на стороне клиента. Да, вот <em>это</em> точно сработает!</p>
<p>Тем временем, мы добавляем тонны дополнительного веса нашим страницам, «уравнивая» браузеры в правах, снижая удобство в современных браузерах, пытаясь улучшить использование сайта в старых. И вот какая проблема в таком подходе. Люди, использующие старые браузеры, <strong>не ожидают самых новых возможностей.</strong> Они довольны тем, что у них есть. Это и есть основная причина, почему они используют старый браузер. Вместо попыток сделать всё удобнее для таких пользователей (и эти попытки чаще всего проваливаются), нужно включать дополнительные возможности, только если они поддерживаются браузером пользователя — вместо создания хаков для обхода ограничений.</p>
<p>Люди, использующие старые браузеры, будут более чем довольны вашим сайтом, если вы оставите только ту часть, что рендерится на сервере, поскольку им на самом деле не нужен ваш потрясный-и-необыкновенно-сложный <a href="http://danwebb.net/2011/5/28/it-is-about-the-hashbangs">кошмарно поддерживаемый</a> хеш-роутинг. Но нет, подождите! Хеш-роутинг <a href="http://mtrpcic.net/2011/02/fragment-uris-theyre-not-as-bad-as-you-think-really/">такой распрекрасный</a>, разве нет? Кому нужен этот рендеринг на сервере!</p>
<p>Ладно, хорошо, давайте примем, что вы согласны со мной. Хеш-роутинг отстой. Он ничего не делает, чтобы помочь современным браузерам <em>(за исключением замедления работы, <a href="https://blog.twitter.com/2012/improving-performance-on-twittercom">это он делает!</a>),</em> и делает всё, чтобы усложнить разработку и запутать людей, которые пользуются старыми браузерами.</p>
<h2>Заботимся ли мы на самом деле о вебе так, как говорим об этом?</h2>
<p>Недавно некто опубликовал на Медиуме статью, озаглавленную «<a href="https://medium.com/este-js-framework/whats-wrong-with-angular-js-97b0a787f903">Что не так с Angular.js</a>». Меня вывело из себя, что, кажется, мы <em>совсем</em> не заботимся о рендеринге на стороне сервера, пока у нас есть возможность разрабатывать приложения на нашем любимом фреймворке. Тогда как каждая из упомянутых проблем Angular была так или иначе опровергнута, проблема рендеринга на стороне сервера осталась практически незамеченной. Как будто никого это не волнует, или же никто не понимает суть претензии.</p>
<blockquote>
<p>Без сомнительных хаков — никакого рендеринга на сервере. Никогда. Вам не починить то, что сломано изначально. Пока-пока, <a href="http://nerds.airbnb.com/isomorphic-javascript-future-web-apps/">изоморфные веб-приложения</a>.</p>
</blockquote>
<p>Единственное место, где я представляю себе использование фреймворка, основанного исключительно на клиентском рендеринге — это разработка прототипов или внутренних бэкенд-приложений <em>(примерно как мы сейчас используем Bootstrap в основном для внутренних штук).</em> В таких делах эти безразличные к пользователю фреймворки хороши, потому что они повышают продуктивность без особых затрат, так как люди не страдают в процессе. За исключением редких случаев, когда отсутствие возможности рендеринга на стороне сервера ничему не вредит, допускать это бесспорно нельзя, это слишком <strong>медленно, недопустимо, шаг назад и вообще безответственно.</strong></p>
<p>Медленно, потому что люди теперь вынуждены загружать всю вашу разметку, ваш CSS, ваш JavaScript, прежде чем этот JavaScript сможет отрисовать информацию, которую пользователь ожидает в первую очередь. С каких пор мы согласны жертвовать быстродействием ради фреймворков?</p>
<p>Это шаг назад, поскольку вы должны <strong>доставлять информацию в доступной человеку форме в первую очередь</strong>, а не после того, как каждый блокирующий рендеринг запрос будет исполнен. Это означает, что доступное человеку HTML-представление должно быть отрендерено на сервере и доставлено пользователю, и лишь затем можно добавить вашу прикольную JavaScript-магию поверх него, пока пользователь занят информацией, которую вы ему предоставили.</p>
<blockquote>
<p>Держи людей занятыми, или им станет некомфортно.</p>
</blockquote>
<p>Это неосмотрительно, потому что мы годами учили друг друга избегать подобных ситуаций — только другими словами. Мы говорили себе о важности отложенной загрузки скриптов при помощи вставки тегов <code>&lt;script&gt;</code> в самый низ страницы, и, может быть, даже добавляли атрибут <code>async</code>, чтобы они загружались в последнюю очередь. Использование рендеринга на клиенте без поддержки серверной стороны означает, что эти скрипты, которые вы поместили в низ своей страницы, теперь мешают вашим пользователям — потому что загрузка отложена, а без JavaScript нельзя получить содержимое для отображения. Только не начинайте перемещать теги <code>&lt;script&gt;</code> обратно в <code>&lt;head&gt;</code>. Просто поймите, насколько далеко идущие негативные последствия имеет рендеринг на клиенте, когда не берется в расчёт возможность серверного рендеринга.</p>
<p>Но не верьте мне на слово. Тут вот у Твитера есть что сказать. Помните Твитер? Да, <a href="https://blog.twitter.com/2012/improving-performance-on-twittercom">они переключились обратно на распределенный рендеринг в середине 2012 г.</a> и не жалеют об этом.</p>
<blockquote>
<p>Глядя на составляющие, из которых складывается <em>[время до первого твита]</em>, мы обнаружили, что только парсинг и исполнение JavaScript вызывают большие провалы в скорости рендеринга. При нашей архитектуре, полностью основанной на клиентском рендеринге, вы не видели ничего, пока JavaScript не был загружен и выполнен. Проблема обостряется ещё сильнее, если у вас не супермощный компьютер или вы пользуетесь старым браузером. Суть в том, что архитектура на клиенте приводит к снижению быстродействия, поскольку бо́льшая часть кода исполняется на компьютере пользователя, а не на нашей стороне.</p>
<p>Существуют различные методы ускорения JavaScript, но мы хотели сделать всё ещё лучше. Мы полностью исключили исполнение JavaScript из процесса рендеринга. Благодаря рендерингу на стороне сервера и отложенному исполнению любого JavaScript, пока весь этот контент не будет отображён, мы в пять раз снизили время до первого твита.</p>
</blockquote>
<p>Мы <a href="https://blog.andyet.com/2014/10/30/optimize-for-change-its-the-only-constant/">заботимся не о тех вещах</a>. Вчера <a href="https://twitter.com/HenrikJoreteg">Хенрик Йортег</a> озвучил несколько справедливых опасений относительно тяжелого будущего AngularJS. Хотя многие из них спорны. Вам могут нравиться эти изменения, вы можете думать, что они к лучшему, но что в действительности вы получите от большого рефакторинга, который вам предстоит? По правде говоря, Angular — блестящий фреймворк в том, что касается продуктивности разработчика, и <strong>он</strong> «спонсируется Гуглом», иными словами, <em>Гугл его поддерживает.</em> Но обратная сторона этого — порог входа в Angular невероятно высок, и вам нечем похвастаться, когда вы вынуждены менять работу.</p>
<p>Мы делаем всё наоборот. Мы рассматриваем современные браузеры как <em>«статус кво»</em>, и, вполне логично, если какой-то браузер не соответствует <em>«статусу кво»</em>, мы добавляем ему наши клёвые поведенческие полифилы. Таким образом они, <em>как минимум</em>, получают хеш-роутинг!</p>
<p><strong>Мы заботимся не о тех вещах.</strong></p>
<h2>Первым должно идти содержимое</h2>
<p>Что бы нам стоило сделать вместо этого, так это вернуться к истокам. Пустим содержимое по проводам <strong>так быстро, как это возможно</strong> с помощью серверного рендеринга. Затем, <em>как только страница загружена</em> и пользователь видит и может использовать содержимое, добавим дополнительную функциональность на JavaScript. Если вы хотите использовать что-то недоступное устаревшим браузерам (например, History API), в первую очередь <strong>подумайте, стоит ли это вообще делать.</strong> Может быть, вашим пользователям будет лучше без этого. В случае с History API, вероятно, лучше <em>оставить старые браузеры на модели «запрос-ответ»</em>, чем пытаться имитировать History API при помощи хеш-роутинга.</p>
<p>Тот же принцип применим и к другим аспектам веб-разработки. Нужно дать возможность оставлять комментарии? Используйте <code>&lt;form&gt;</code> — или AJAX, если доступен JavaScript и как следует поддерживается <code>XMLHttpRequest</code>. Хотите отложить загрузку CSS, чтобы избежать блокировки рендеринга, и использовать встроенный в страницу критический CSS? Прекрасно, но, пожалуйста, используйте тег <code>&lt;noscript&gt;</code> как запасной вариант для тех, у кого отключен JavaScript. Иначе рискуете поломать стили для них!</p>
<p>Упоминал ли я одно очевидно кривое свойство хеш-адресации? Когда вы не можете рендерить на сервере, поскольку вам неизвестна хеш-часть запроса? Правильно, Твитер будет вынужден поддерживать специальный рендеринг на клиенте и в обозримом будущем — до тех пор, пока хеш-запросы не перестанут стучаться на их сервера.</p>
<h2>Прогрессивно улучшать всё на свете!</h2>
<p>Резюмируя: мы должны перестать изобретать необычайно умные решения для рендеринга на клиенте, которые при этом не дают какой-либо возможности рендеринга на сервере. За исключением <strong>тошнотворных костылей</strong> вроде использования PhantomJS, чтобы рендерить клиентские страницы на сервере. Я уверен, никто не в восторге от <a href="http://jaxenter.com/angular-2-0-112094.html">анонса Angular 2.0</a> — слишком много критических изменений при фактическом отсутствии выгоды. Подождите, вы говорите, тут есть выгода? Я не слышу вас из-за грохота, с которым обваливается поддержка браузеров.</p>
<p>Полагаю, вот что случается, когда вы не беспокоитесь о прогрессивном улучшении.</p>
<p>В следующий раз, когда вы начнете какой-нибудь проект, не надо просто <strong>вслепую совать в него AngularJS, Bootstrap и jQuery</strong>, да и дело с концом. Найдите возможность сделать распределяемый рендеринг, используйте React или Taunus, или что-нибудь ещё, что позволит вам не писать одно и то же дважды. Иначе <strong>не делайте рендеринга на клиенте вообще.</strong></p>
<p><strong>Стремитесь к простоте. Используйте прогрессивное улучшение.</strong> Делайте это не ради людей, у которых отключен JavaScript. Делайте это не ради людей, которые пользуются старыми браузерами. Делайте это не для того даже, <em>чтобы покончить с прошлым подходом.</em> Сделайте это, потому что вы осознаете важность доставки содержимого первым. Сделайте это, потому что осознаете, что <strong>ваш сайт никогда не должен выглядеть одинаково на каждом устройстве и в каждом браузере.</strong> Сделайте это, потому что сайт станет удобнее. Сделайте это, потому что люди с мобильном интернетом не должны страдать от болезненного пользования кривым вебом.</p>
<p>Опирайтесь на основы веба, стройте на них, вместо того чтобы всё ухудшать и требовать, чтобы ваши клёвые вебдванольные JavaScript-фреймворки загружались, парсились и исполнялись до того, как вы начнете заниматься рендерингом содержимого, доступного людям.</p>
<p>Вот чеклист, который вам пригодится:</p>
<ul>
<li>HTML первым делом: выдайте людям полноценную разметку сразу, как только возможно</li>
<li>Отдайте немного CSS, <a href="http://ponyfoo.com/articles/critical-path-performance-optimization">встройте внутрь страницы критически важные стили</a> <em>(кстати, а ведь эта штука тоже пришла из Google!)</em></li>
<li>Отложите загрузку остального CSS при помощи JavaScript до срабатывания <code>onload</code>, но обеспечьте запасной вариант при помощи <code>&lt;noscript&gt;</code></li>
<li>Отложите загрузку картинок со вторых экранов</li>
<li>Отложите загрузку шрифтов</li>
<li>Отложите весь JavaScript</li>
<li><strong>Никогда больше не полагайтесь только на клиентский рендеринг</strong></li>
<li>Приоритизируйте загрузку содержимого</li>
<li>Кэшируйте статические ресурсы</li>
<li>Экспериментируйте с кэшированием динамических ресурсов</li>
<li>Кэшируйте запросы к базе данных</li>
<li><strong>Кэшируйте вообще всё</strong></li>
</ul>
<p>А также в первую очередь пользуйтесь элементами <code>&lt;form&gt;</code>, а затем достраивайте сверху всякий AJAX. Нет! Это не для сумасшедших противников JavaScript. Если JavaScript всё ещё грузится, ваш сайт будет бесполезен без элементов <code>&lt;form&gt;</code>, гарантирующих, что функциональность будет доступной. Некоторые люди вынуждены иметь дело с медленными мобильными соединениями, смиритесь с этим. Например, вы можете <a href="http://www.elijahmanor.com/enhanced-chrome-emulation-tools/">пользоваться Google Chrome, чтобы эмулировать мобильное соединение</a>.</p>
<p>Не приковывайте себя к всеобъемлющей технологии, которая может просто умереть в ближайшие несколько месяцев и оставить вас на мели. С <a href="http://alistapart.com/article/understandingprogressiveenhancement">прогрессивным улучшением</a> вы никогда не прогадаете. Прогрессивное улучшение означает, что ваш код всегда будет работать, поскольку вы всегда фокусируетесь в первую очередь на предоставлении минимальной доступности, а затем добавляете возможности, функциональность и дополнительное поведение — уже поверх содержимого.</p>
<p>Пользуйтесь фреймворками, но выбирайте те, что <strong>дружат с прогрессивным улучшением</strong>: такие как <a href="https://github.com/bevacqua/taunus">Taunus</a>, <a href="http://substack.net/shared_rendering_in_node_and_the_browser">hyperspace</a>, <a href="https://github.com/facebook/react">React</a> или Backbone с <a href="https://github.com/rendrjs/rendr">Rendr</a>. Все они так или иначе позволяют вам сделать распределенный рендеринг, и, хотя <a href="https://github.com/rendrjs/rendr">Rendr</a> слегка нескладный, <a href="https://github.com/buildfirst/buildfirst/tree/master/ch07/11_entourage">он работает</a>. Taunus и <code>hyperspace</code> позволяют работать <em>«модульным способом»</em> — они дополнены небольшими модулями, и вы можете извлечь из этого выгоду. React по-своему ужасен, но, по крайней мере, вы можете использовать его для рендеринга на сервере, и как минимум Facebook им <em>пользуется.</em></p>
<p>Исследуйте способы разработки более модульных архитектур. Прогрессивное улучшение не подразумевает, что в результате вы получите монолитное приложение. На самом деле совсем наоборот. «Прогрессивность» подразумевает, что вы получите приложение, основанное на принципах веба. Это означает, что по большей части оно будет работать, даже когда выключен JavaScript. Оно может даже потерять некоторые ключевые составляющие своей функциональности, если, скажем, вы забыли добавить запасной вариант в виде <code>&lt;form&gt;</code> в важные места вашего сайта.</p>
<p>Но даже это было бы нормально, потому что вы бы поняли значение прогрессивного улучшения, и могли бы добавить этот <code>&lt;form&gt;</code>, чтобы ваш сайт стал более дружелюбным к старым браузерам и людям с мобильниками. Вы бы научились не рендерить ваше приложение только на клиенте, и вместо этого использовать распределенный рендеринг или только лишь рендеринг на сервере. Вы бы осознали значение таких маленьких трюков, как использование элементов <code>&lt;noscript&gt;</code> или <a href="http://ponyfoo.com/articles/implementing-opensearch">установки OpenSearch</a>. Вы бы научились уважать веб. Вы бы вернулись на дорогу для тех, кто действительно заботится о вебе.</p>
<p><strong>Вы бы научились не разрушать веб.</strong></p>

                    ]]></description><pubDate>Mon, 23 Mar 2015 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/stop-breaking-the-web/</guid></item><item><title>Не проверив HTML5-кода, не суйся в воду — с Майком™ Смитом</title><link>https://web-standards.ru/articles/check-it-before-you-wreck-it/</link><description><![CDATA[
                        <p><a href="http://people.w3.org/mike/">Майк™ Смит</a> (известный как @sideshowbarker) из W3C — человек, с головой увязший в исходном коде инструмента W3C для <em>проверки <del>валидности</del> разметки</em>; эта <em>магия</em> работает именно благодаря ему. Вопросы были заданы на радость и в назидание читателю сайта.</p>
<p><strong>Во-первых, расскажите нам немного о том, чем вы занимаетесь и над чем работаете</strong></p>
<figure>
    <img src="https://web-standards.ru/articles/check-it-before-you-wreck-it/images/mike.jpg" alt="Майк™ Смит.">
    <figcaption>Майк™ Смит — заместитель директора @W3C: вариант либерального подхода к работе</figcaption>
</figure>
<p>Я не работаю. Я — старосветский <em>бонвиван.</em> Я пью чай, изящно отставив мизинец, и трачу силы только на то, что меня забавляет. Последние несколько лет меня забавляло тратить время на работу над программой, помогающей людям проверить, соответствуют ли их документы определённым требованиям в спецификации HTML.</p>
<figure>
    <a href="https://validator.w3.org/nu/">
        <img src="https://web-standards.ru/articles/check-it-before-you-wreck-it/images/nu-markup.png" alt="">
    </a>
    <figcaption>Новая проверка разметки.</figcaption>
</figure>
<p><strong>Какая разница между DTD и проверкой, основанной на схеме?</strong></p>
<p>DTD-шки высечены на каменных плитах. И поэтому для обработки они требуют целой кучи камнерезных инструментов. Однако, увы, веб не был построен на обработке каменных плит, поэтому нам пришлось искать другие решения. Для проверки документа на соответствие мы решили использовать такие вещи, как схемы RelaxNG, которые, хоть и не дотягивают до DTD-шной утонченности, представляют собой гораздо более мощные средства для выражения определённых требований соответствия документа. Так что это компромисс.</p>
<figure>
    <a href="https://validator.w3.org/">
        <img src="https://web-standards.ru/articles/check-it-before-you-wreck-it/images/w3c-validator.png" alt="">
    </a>
    <figcaption>Валидатор W3C.</figcaption>
</figure>
<p><strong>В чём разница между проверкой на соответствие и валидацией?</strong></p>
<p><em>Валидация</em> — это что-то из старого мышления. Используйте это слово, когда хотите заставить людей думать, что вы — ископаемое или пережиток прошлого. Это что-то вроде слов <em>«ништяк»</em> или <em>«XHTML».</em></p>
<p>Многие люди не знают этого, но этимология слова «валидация» тянется еще с тех времен, когда наши предки были, в основном, свинокрадами, и их награждали значком за правильное написание своих имён, да ещё похлопывали по спине: «Молодец»!</p>
<p><em>Проверка соответствия документа</em> — хороший, современный, одобренный сообществом способ говорить о поиске проблем в HTML-документах. И заметьте, что мы называем это соответствием <strong>документа</strong>, а не процесса его создания, и мы говорим о требованиях соответствия для <strong>документов</strong>, а не для их создателей. Это потому что вы, автор — человек; техническая спецификация не может содержать требования к вам, она может содержать требования к документу, который вы создаёте. Инструменты для подобной проверки не оценивают, насколько вы, как человек, соблюдаете технологии; вместо этого они лишь оценивают создаваемые вами документы.</p>
<p>Впрочем, словосочетание <em>проверка соответствия документа</em> имеет и противную уродскую часть: <em>соответствие</em> — действительно оскорбительное слово. Но вы не должны обращать на него внимания! Уделяйте внимание только слову <em>«проверка»</em> — веселому и полезному.</p>
<p>Именно поэтому я называю инструмент по адресу <a href="https://validator.w3.org/nu">validator.w3.org/nu</a> <em>«Новая проверка разметки»</em>, а не <em>«Бла-бла-валидатор»</em> — я хочу делиться радостью слова «проверка». Проверка — это что-то, реально приносящее людям пользу, а не просто похлопывающее их по спине. Это штука, которая проверяет ваши документы автоматически и избавляет вас от нудной необходимости делать это вручную. Она помогает вам. Возможно, ее следовало бы назвать <em>«Новая проверка-и-помощь».</em></p>
<p>Что касается самой проверки, она ищет случайные ошибки, которые вы могли допустить: неправильно написанные названия элементов или значения атрибутов, в которые случайно влез какой-то «левый» символ. В общем, такого рода вещи. И она сообщает вам о таких штуках, чтобы вы могли исправить их.</p>
<p>Инструмент проверяет и другие виды требований, заданных в спецификации HTML. Это требования, которые помогут вам избежать создания кривых HTML-документов и веб-приложений, не работающих как надо или еще как-нибудь ухудшающих удобство для пользователей. Некоторые из этих требований можно назвать делом вкуса, но все равно полезно иметь подобный общий набор — что-то вроде отправной точки, реально закрепленной в спецификации.</p>
<p>Другие названия для того, что умеет этот инструмент, пока еще не одобрены сообществом — это <em>«линтинг»</em> или <em>«статический анализ».</em> Но отличие той штуковины, над которой я работаю, в том, что правила линтинга для нее на самом деле определены в спецификации, а не являются чем-то таким, что, скажем, Дуг Крокфорд (пример навскидку) как-то утром взял и вытащил из шляпы.</p>
<p><strong>Какая разница между ошибками и предупреждениями?</strong></p>
<p>Ошибка — это какой-то явный ляп, например, опечатка в названии элемента, или какие-то дикие нечитаемые символы в значении атрибута, или непонятно что, непонятно откуда взявшееся и явно ненужное.</p>
<p>Но ошибкой называются также вещи, про которые спецификация просто говорит, мол, это должно считаться ошибкой. Спецификация объясняет: подобные вещи называются ошибками, поскольку они могут создать определённые проблемы, которые не всегда легко предвидеть.</p>
<p>Существует длинный перечень тех вариантов проблем, которые определены как ошибки. Но в некоторых примерах к ним — образцы разметки, ужасные с точки зрения доступности, удобства, совместимости, безопасности или поддержки, приводящие к низкой производительности или к трудноотлаживаемым глюкам ваших скриптов.</p>
<p>Наряду с вышеописанными случаями бывают и другие, которые тоже считаются ошибками, потому что из-за них вы можете наткнуться на странности при HTML-парсинге и обработке ошибок и в итоге получить неинтуитивный и неожиданный результат в DOM.</p>
<p>Наконец, существует еще один вид ошибок: для разметки, которая не несёт в себе никакого смысла и скорее всего была использована по недоразумению, или для случаев, идущих вразрез с поведением стилей по умолчанию.</p>
<p>Предупреждения, с другой стороны, нужны для вещей, которые спецификация не определяет, как прямые ошибки, но которые всё же могут вызывать проблемы. Иногда предупреждения добавляются к проверке экспериментально, в качестве способа проверить, будут ли они полезны для вас или нет. (Это одна из причин, по которой проверка все еще помечена как <em>экспериментальная.)</em></p>
<p><strong>Есть ли смысл в использовании доктайпов HTML4 или XHTML?</strong></p>
<p>Нет абсолютно никакой причины для использования доктайпа HTML4. Просто вставьте доктайп <code>&lt;!DOCTYPE HTML&gt;</code> в ваши HTML-документы, убедитесь, что они отдаются как <code>text/html</code>, и всё. Живите вашей обычной жизнью. Но если по какой-то причине вы непременно хотите отдавать документы в <code>application/xhtml+xml</code>, то вам не придётся использовать для них доктайп XHTML, т.к. и в этом случае вы можете спокойно использовать <code>&lt;!DOCTYPE HTML&gt;</code>. (Но, вероятно, вы не захотите использовать <code>application/xhtml+xml</code> и XHTML в любом случае. Смените же наконец прическу! Сколько можно жить в прошлом, целый огромный мир ждет вас.)</p>
<p><strong>Какие подводные камни поджидают пользователей HTML-проверки и инструментов валидации?</strong></p>
<p>Я полагаю, те же подводные камни, на которые вы наткнетесь, если обратитесь к какому-нибудь разумному человеку, по-настоящему готовому вам помочь: он действительно попытается это сделать, а не просто отмахнётся или даст вам значок «этот свинокрад может написать своё имя». Его помощь может не всегда оказаться тем, что вы хотели услышать, или это могут быть уже известные вам советы, которые вы спокойно можете игнорировать. Такова жизнь.</p>
<p><strong>Какие плюсы?</strong></p>
<p>Плюсы в том, что вы ловите ошибки, которые в противном случае вы могли бы пропустить.</p>
<p><strong>Как так получилось, что между правилами соответствия W3C HTML и WHATWG HTML есть различия?</strong></p>
<p>Некоторые вещи, определённые как ошибки — дело вкуса. Спецификации пишутся людьми, а не машинами. Разные люди могут рассуждать по-разному — «разумные люди могут не соглашаться друг к другом», или как бы еще не так банально выразить эту мысль. Если вы пойдете по свету, ожидая от человечества полного согласия, в какой-то момент вы будете серьезно разочарованы.</p>
<p><strong>А что, если я найду ошибку в валидаторе или проверке W3C HTML?</strong></p>
<p>Сообщите об этом <a href="https://github.com/validator/validator/issues">в ишью репозитория</a>.</p>
<p><strong>Могу ли я запустить локальную копию проверки соответствия W3C HTML?</strong></p>
<p>Да. Лучший способ сделать это — <a href="https://github.com/validator/validator/releases">скачать релиз</a> и <a href="https://validator.github.io/validator/#web-based-checking">следовать инструкциям</a>. А если вы используете grunt, попробуйте плагин <a href="https://github.com/jzaefferer/grunt-html">grunt-html</a> для проверки HTML, основанный на коде валидатора.</p>
<p><strong>Какие-либо советы или подсказки по разумному использованию инструментов проверки соответствия HTML?</strong></p>
<p>Это какой-то вопрос с подвохом? Пожалуй, единственное, что я мог бы посоветовать — не стоит забывать, что инструменты — это машины, а вы — не машина (предполагаю, что этот вопрос задан не машиной). Поэтому, оценивая любые сообщения об ошибках и предупреждениях от любого средства проверки HTML, полагайтесь на собственный здравый смысл. И если здравый смысл подсказывает вам, что какое-то сообщение на самом деле не помогает вам, просто игнорируйте его. Это не конкурс популярности, вы не заденете ничьи чувства.</p>
<p>А ещё лучше, если у вас найдется время — воспользуйтесь функцией «Фильтрация сообщений» на <a href="https://validator.w3.org/nu">validator.w3.org/nu</a>, которая позволит вам постоянно игнорировать любые сообщения, которые вы сочтёте бесполезными, надоедливыми или которые вы просто больше не желаете видеть.</p>
<p><strong>Сейчас инструмент проверки W3C HTML не проверяет и не показывает ошибки для SVG1.1 и некоторых атрибутов веб-компонентов, планируете ли добавить поддержку этого?</strong></p>
<p>Да. Эти вещи добавлены в мой список задач. В конечном итоге я доберусь до них.</p>
<p><strong>В чём история с ошибками из-за неизвестных атрибутов? Их используют многие JS-библиотеки, что делать разработчикам?</strong></p>
<p>Проблема в том, что проверка — машина, и она недостаточно умна, чтобы понять разницу между какими-то атрибутами с неизвестным названием, которые вы используете умышленно, и атрибутами, название которых вы случайно написали с ошибкой. Если мы просто прикажем сервису пропускать все неизвестные названия атрибутов без проверки, мы не сможем помочь вам выловить случаи, когда вы по ошибке написали что-нибудь неправильно.</p>
<p>Временное решение — если вы намеренно используете какое-то неизвестное имя атрибута, задействуйте галочку в «Фильтрации сообщений» на <a href="https://validator.w3.org/nu">validator.w3.org/nu</a>. Это укажет проверке, что вы не желаете видеть сообщения об этом конкретном атрибуте, и они больше не будут вас беспокоить.</p>
<p><strong>Есть ли в валидаторе функция для проверки использования ARIA? Если да, то что это за проверка?</strong></p>
<p>Да, он проверяет ошибки в использовании разметки ARIA в HTML-документах, а теперь еще и находит некоторые ошибки ARIA как в элементах SVG внутри HTML-документов, так и в отдельных SVG-документах.</p>
<p>Для HTML-элементов это проверка на соответствие требованиям не только спецификации HTML, но и появившемуся сейчас самостоятельному документу <a href="https://web.archive.org/web/20170208162324/https://specs.webplatform.org/html-aria/webspecs/master/">ARIA в HTML</a>. Планируется, что спецификация HTML вскоре будет обновлена, чтобы просто ссылаться на требования ARIA в этом документе.</p>
<p>Что касается SVG-элементов, у меня в планах вскоре обновить проверку, чтобы она следовала аналогичным самостоятельным документам в «<a href="https://web.archive.org/web/20170208160909/https://specs.webplatform.org/SVG1.1-ARIA/webspecs/master/">Web developer rules for use of ARIA attributes on SVG1.1 elements</a>»</p>
<p><strong>А что насчёт проверки ARIA в документах по спецификациям до HTML5? Будет ли это сделано?</strong></p>
<p>Ни один человек не должен пользоваться чем-то, кроме HTML5, и не нужно пытаться помогать ему в этом. HTML5 — это просто HTML. Мы уже давно переросли всё связанное с версиями. <code>&lt;!DOCTYPE HTML&gt;</code> скоро исполнится 10 лет. Здравый смысл победил. В нашем XXI веке нам нечем толком помочь человеку, который прописывает в новом документе HTML4 или какой-то другой древний доктайп. Это гиблое дело. Мы никоим образом не помогли бы, если бы дали какую-то возможность сделать это и вписать разметку ARIA в такие документы, а потом сказали бы, что всё в порядке. Это называется попустительство.</p>
<p><strong>При использовании HTML-валидатора W3C для проверки моего HTML5 я вижу следующее: «Валидатор проверил ваш документ с помощью экспериментальной функции: проверка соответствия HTML5…» — значит ли это, что есть более стабильный инструмент валидации, который мне следовало бы использовать?</strong></p>
<p>Понятие «стабильный» здесь не очень применимо. Но да, существует другой инструмент, которым вам следовало бы пользоваться. Это <a href="https://validator.w3.org/nu">validator.w3.org/nu</a>. У него больше возможностей и он лучше по всем статьям.</p>
<p>Это — инструмент экспериментальный, но в хорошем смысле. Планируется, что он всегда останется таким. Страница <a href="https://validator.w3.org/nu/about.html">validator.w3.org/nu/about.html</a> пытается задать правильные ожидания насчет того, в чем цель и что означает экспериментальный:</p>
<blockquote>
<p>Новая проверка разметки — экспериментальный инструмент, и его поведение ещё может измениться. В частности потому, что в него продолжают добавляться новые типы проверок на ошибки, и нельзя гарантировать, что, если в один момент времени проверка конкретного документа показывает ноль ошибок, она покажет ноль ошибок и в более поздний момент.</p>
<p>Не нужно пытаться применить Новую проверку разметки как средство, чтобы заставить людей сверять документы с какой-либо спецификацией на «соответствует или не соответствует»; инструмент задуман исключительно как средство проверки, а не как механизм для выдачи сертификатов соответствия.</p>
</blockquote>
<p><strong>Что насчёт проверки веб-компонентов?</strong></p>
<p>Если вы имеете в виду проверку нестандартных (custom) элементов, то мой ответ — нестандартные элементы пока недостаточно широко поддерживаются в разных браузерных движках, так что не думаю, что мне или еще кому-то стоит тратить слишком много времени и сил на размышления, как проверке вести себя с документами, в которых они есть.</p>
<p>Когда и если нестандартные элементы всё-таки начнут широко поддерживаться большинством браузерных движков, вот тогда мы и должны будем выяснять, как проверке вести себя с ними. Это, конечно, будет сложно и запутанно — но так обстоят дела со многими вещами в веб-разработке, и я уверен, что мы вместе придумаем что-то, с чем можно будет жить — точно так же, как мы не раз делали для многих других сложных вещей.</p>
<p><strong>В чём разница между валидатором W3C и Новой проверкой разметки?</strong></p>
<p>Старый W3C-валидатор находится на <a href="https://validator.w3.org/">validator.w3.org</a>, и его ядро основано на таких древних вещах, как Perl, DTD-шки, SGML и старые спецификации из XX века, типа HTML4. Никто в данный момент активно не поддерживает его код. Единственная хорошая новость на этот счёт — для проверки любых документов с современным доктайпом <code>&lt;!DOCTYPE html&gt;</code> он фактически использует движок Новой проверки разметки, а затем просто возвращает все сообщения оттуда.</p>
<p>Новая проверка разметки находится по адресу <a href="https://validator.w3.org/nu">validator.w3.org/nu</a>. Она основана на чуть более новых вещах типа Java и RelaxNG и на спецификациях из нынешнего века, таких как HTML5, а также имеет большое преимущество в виде фактической активной поддержки. К тому же у нее больше возможностей, напр., функция «Фильтрация сообщений», позволяющая отфильтровать сообщения, которые вы не хотите видеть.</p>
<p><strong>Что лучше — проверка исходного кода или реального вывода HTML DOM? Известные проблемы?</strong></p>
<p>Я полагаю, что для обоих случаев есть хорошие варианты. Ограничение проверки DOM заключается в невозможности сделать так, чтобы <a href="https://validator.w3.org/nu">validator.w3.org/nu</a> сам забрал DOM какого-то произвольного документа в сети, а потом проверил. Где-то между должен быть браузерный движок, который реально распарсит документ в DOM-представление в памяти, выполнит ваши скрипты, а затем преобразует итоговую DOM обратно в текстовое представление, которое вы можете «скормить» проверке. Но если у вас есть HTML-документ, который вы хотите проверить, и вы уже открыли его в браузере, вы можете использовать <a href="http://codepen.io/stevef/full/LasCJ/">что-то типа букмарклета</a>, чтобы отправить строковое представление DOM этого документа в <a href="https://validator.w3.org/nu">validator.w3.org/nu</a> для проверки.</p>
<h2>Дополнительные вопросы</h2>
<p><strong>Стоит ли помечать предупреждениями в W3C-валидаторе доктайпы, которые были до HTML5, ведь HTML5 теперь рекомендация?</strong></p>
<p>Не знаю, может быть. С одной стороны, существует огромное количество документов со старыми доктайпами, которые прекрасно работают такими как есть, поэтому нет причины посылать их к чёрту. С другой стороны, если кто-то всё же удосужился пропустить один из этих документов через проверку HTML, скорее всего, у него есть на это веская причина. И, вероятно, оповестив его об устаревшем доктайпе, мы бы помогли — он смог бы обновить его.</p>
<p><strong>В HTML5 элементу <code>&lt;a&gt;</code> разрешено содержать блочное или потоковое содержимое, что поменялось (если вообще поменялось) в браузерах?</strong></p>
<p>В браузерах ничего не изменилось. Браузеры всегда это поддерживали, и это не вызывает никаких проблем, и мы никому бы не помогли, выдавая это за ошибку. Так что мы сделали это не-ошибкой.</p>
<h3>Критерий обработки для WCAG 2.0</h3>
<p><strong>Консультант по доступности нашего клиента говорит ему, что для совместимости с WCAG 2.0 у них должен быть валидный HTML. Это правда?</strong></p>
<p>Без понятия. Я не эксперт в WCAG и никогда даже не читал спецификацию WCAG 2.0. И HTML-проверка — не WCAG-проверка. Или, по меньшей мере, она не претендует на это.</p>
<p>У WCAG 2.0 есть критерий успеха, который требует, <a href="https://www.w3.org/TR/WCAG20/#ensure-compat-parses">чтобы в разметке документов не было ошибок парсинга</a>. <a href="https://validator.w3.org/nu">Новая проверка разметки</a> отмечает ошибки парсинга наряду с другими машинно проверяемыми <a href="https://www.w3.org/TR/html/introduction.html#conformance-requirements-for-authors">критериями соответствия HTML</a>. Мы создали <a href="https://validator.w3.org/nu/about.html#extras">букмарклет ошибок парсинга для WCAG 2.0</a>, который фильтрует результаты, полученные из Новой проверки разметки, чтобы отобразить только ошибки или предупреждения парсинга.</p>
<p><strong>Примечание</strong>: этот букмарклет — экспериментальный, это не закон, и даже после фильтрации некоторые выводимые ошибки/предупреждения могут не вызывать каких-либо практических негативных эффектов для доступности документа. Он служит исключительно вспомогательным средством для фильтрации некоторых неактуальных (для WCAG) проблем. Мы с Майком говорили о том, чтобы сделать фильтр встроенной функцией <a href="https://validator.w3.org/nu">Новой проверки разметки</a>, надеюсь, что моими стараниями это случится.</p>
<p><strong>Спасибо, Майк!</strong></p>
<h3>Полезный совет — всегда <a href="http://validator.w3.org/nu">проверяй свой HTML</a> под рок-н-ролл, играющий… ГРОМКО!</h3>

                    ]]></description><pubDate>Fri, 26 Dec 2014 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/check-it-before-you-wreck-it/</guid></item><item><title>Исследуем большие экраны</title><link>https://web-standards.ru/articles/big-screens/</link><description><![CDATA[
                        <p>Прошло больше трёх лет с тех пор, как <a href="https://alistapart.com/article/responsive-web-design">адаптивный веб-дизайн</a> начал появляться в наших портфолио. Сегодня мы имеем солидный набор решений для создания сайтов, достойно отображающихся на маленьких устройствах. А как насчёт больших экранов?</p>
<p>Для сайтов гибкий дизайн стал привычным. Мы растягиваем или сужаем содержимое страницы, чтобы как можно лучше заполнить доступную ширину экрана. Однако многие из таких сайтов ограничены максимальной шириной в 960 пикселей, из-за чего на современных широких экранах остается много пустого пространства.</p>
<p>Проектирование дизайна сайтов для больших экранов — дело непростое: отрицательные отступы, масштабирование, плотность пикселей и раскладки для различных устройств — сетки, модули и колонки — всё это может изменить акценты и усложнить страницы.</p>
<p>Плюс к этому, большие экраны обычно имеют горизонтальную ориентацию, которая плохо подходит для вертикально-ориентированных веб-страниц. Как у маленьких, так и у больших экранов есть свои диапазоны размеров и разрешений, но в случае с большими экранами разница будет более ощутима: от 11-дюймовых ноутбуков до огромных 30-дюймовых мониторов.</p>
<p>Учитывая эти сложности, неудивительно, что многие сайты, даже сайт, на котором вы читаете статью <em>(речь идет о сайте <a href="https://alistapart.com/">alistapart.com</a> — прим. ред.)</em> спроектированы под разрешение 1024×768. Но время, когда сайты были вынуждены вписываться в наиболее популярное разрешение экрана, прошло. Сегодня <a href="http://gs.statcounter.com/#resolution-ww-monthly-201311-201411">большинство пользователей имеют экраны гораздо шире 1024 пикселей</a>, и раскрытое на всю ширину окно браузера превращает тщательно продуманный сайт, дизайн которого ограничен шириной 960 пикселей, в <a href="https://web.archive.org/web/20181006191523/http://www.tcm.com/mediaroom/video/474162/2001-A-Space-Odyssey-Movie-Clip-Monolith.html">монолитный памятник</a> посреди чистого поля.</p>
<p>С каждым годом всё больше пользователей <a href="http://gs.statcounter.com/#mobile_vs_desktop-ww-monthly-201311-201411">выходят в интернет с мобильных устройств</a>, и, естественно, имеет смысл прилагать усилия к созданию сайта, которым было бы удобно пользоваться на маленьких экранах. Более того, мобильными раскладками можно прекрасно пользоваться и на любых устройствах, в том числе с большими экранами. А вот сказать тоже самое про десктопные раскладки, просматриваемые на экранах мобильных, нельзя.</p>
<p>Проектируя дизайн, адаптированный к показу на больших экранах, дизайнеры имеют возможность работать с большим пространством, показывать больше содержимого при меньшей вертикальной прокрутке, делая, таким образом, посещение сайта значительно удобней. А используя те же приемы, с помощью которых мы адаптируем сайты для мобильных устройств, нам не придется увеличивать стоимость разработки или тратить на это больше времени.</p>
<h2>Испытание содержимым</h2>
<p>Как и в работе с любым другим дизайном, главное внимание при адаптации дизайна сайтов для больших экранов уделяется содержимому. Длинные или короткие тексты, фотографии, товары интернет-магазинов, видео или веб-приложения могут быть по-разному представлены на экранах разной ширины.</p>
<p>Фотографии, результаты поиска и другое содержимое, вписывающийся в сетку, прекрасно подходят для широких экранов. Одновременный показ как можно большего количества содержимого позволяет пользователю быстро просматривать и сравнивать результаты поиска.</p>
<p>А вот длинные тексты могут стать серьезным испытанием при адаптации сайтов к широким экранам. Если строки слишком длинные, чтение становится неудобным, а если слишком короткие — может возникнуть ощущение нервозности и ускорения, и как следствие — нарушится комфортный ритм восприятия текста.</p>
<p>Чтение должно быть удобным. Для этого дизайнеру необходимо сбалансировать ширину текста (<a href="https://en.wikipedia.org/wiki/Measure_(typography)">длину строки</a>) исходя из размера и межстрочного интервала каждой строки. Известно, что классическая длина одной строки текста 7–10 слов (<a href="https://www.amazon.com/Systems-Graphic-Systeme-Visuele-Gestaltung/dp/3721201450">по Йозефу Мюллеру-Брокману</a>) или 45–75 символов (<a href="https://www.amazon.com/Elements-Typographic-Style-Version-Anniversary/dp/0881792128/">по Роберту Брингхёрсту</a>). Просто для информации: Брингхёрст также подмечает, что строка в традиционной книжной колонке должна быть примерно в 30 раз больше, чем используемый размер шрифта, но это число может варьироваться от 20 до 40.</p>
<p>Чем шире текст, тем больший межстрочный интервал можно использовать. Это делает более удобным перемещение от строки к строке. Но слишком большой межстрочный интервал может привести к тому, что излишне разреженный текст станет напоминать студенческую работу. При этом надо понимать, что, когда размер текста увеличивается, число строк, помещающихся на экране, уменьшается, вынуждая постоянно прокручивать и мешая «погружению» читателя. Простое масштабирование текста — не самое удачное решение для широких экранов.</p>
<h2>Работа с длинными текстами</h2>
<p>Сайт <a href="http://thegreatdiscontent.com/">The Great Discontent</a> демонстрирует достаточно искусную адаптацию к широкому экрану, без фанатичного стремления заполнить каждый пиксель в окне браузера. Первая картинка в статье растягивается на всю доступную ширину, создавая ощущение визуальной целостности страницы. Содержимое статьи центрировано и ограничено сравнительно узкой шириной, а дополнительная информация, цитаты и картинки выносятся уже за пределы содержимого. Такой вынос содержимого создает асимметрию, которая дополняет растянутую на всю ширину картинку на первом экране — создавая иллюзию полноэкранного представления без ущерба для читаемости. Но не забывайте, что такие большие картинки, как эта, могут быть дорогими с точки зрения быстродействия, поэтому обязательно учитывайте баланс между качеством картинки и <a href="http://alistapart.com/article/improving-ux-through-front-end-performance">допустимым весом страницы</a>.</p>
<figure>
    <img src="https://web-standards.ru/articles/big-screens/images/great-discontent.jpg" alt="Сайт The Great Discontent. Главная страница с большой фотографией и текстом, который написан большими буквами.">
    <figcaption>The Great Discontent использует большие картинки в начале каждой статьи.</figcaption>
</figure>
<figure>
    <img src="https://web-standards.ru/articles/big-screens/images/roger-ebert.jpg" alt="Страница обзора фильма Пятая власть с сайта Роджера Эберта. Перед текстом обзора — кадр из фильма. Внизу фотографии название фильма. Слева расположено боковое меню со списком актёров.">
    <figcaption>Сайт Роджера Эберта масштабирует большинство элементов на странице, увеличивая их и сокращая видимую часть содержимого.</figcaption>
</figure>
<p>Недавно обновившийся сайт Роджера Эберта (Roger Ebert) справляется с адаптацией к широким экранам, увеличивая максимальную ширину страниц и увеличивая элементы пропорционально ей. Вообще, в теории это могло бы сработать. Но на практике получилось не очень. Например, заголовки увеличиваются не только по горизонтали, но и по вертикали, а это означает, что количество видимого содержимого резко уменьшается. Странно и то, что главное содержимое не увеличивается пропорционально другим элементам страницы, и выглядит карликом по сравнению с ними, имея, вдобавок, слишком маленькую ширину для основного содержимого.</p>
<figure>
    <img src="https://web-standards.ru/articles/big-screens/images/medium.jpg" alt="Статья с сайта Medium, которая называется Креативные люди говорят нет. В начале статьи изображение с надписью Нет. Слева от текста комментарии пользователей к нему.">
    <figcaption>Medium располагает комментарии рядом с текстом на свободных полях.</figcaption>
</figure>
<p>Показывать на полях связанное или дополняющее содержимое (например, комментарии на сайте <a href="https://medium.com/thoughts-on-creativity/bad7c34842a2">Medium</a>) — хорошая идея для отображения длинных текстов на больших экранах. Если экран недостаточно широкий, текст статей растягивается на всю ширину и сдвигается в сторону, если пользователь читает комментарии. Если же места на экране достаточно — комментарии просто занимают доступное пространство на полях.</p>
<p>Мне всегда нравилось, как сайт <a href="http://www.grantland.com/story/_/id/9973203/the-colorado-avalanche-edmonton-oilers-rebuilds">Grantland</a> использует правую колонку для пометок. Пометки, с одной стороны, используют преимущество больших экранов, и в то же время фокусируют внимание на центральной колонке. Фотографии, врезки, дополнения, цитаты и другое связанное содержимое на широких экранах может быть вынесен на поля. Это позволит дизайнеру как расширить вертикальную сетку за счет внешних полей, так и создать визуальное разнообразие, сохранив при этом расположение основного содержимого.</p>
<p>Такие новые возможности CSS, как <a href="https://dev.w3.org/csswg/css-multicol/">мультиколонки</a> и <a href="https://web.archive.org/web/20140802151545/http://html.adobe.com/webplatform/layout/regions/">регионы</a>, могут сделать чтение длинных текстов на широких экранах удобнее. Мультиколонки поддерживаются большинством современных браузеров. Их можно использовать внутри статей, с одной стороны, максимально заполняя доступное пространство экрана, а с другой — оставляя текст читабельным. Если у вас большой экран, посмотрите <a href="https://web.archive.org/web/20180603110552/http://alistapart.com/d/386/surveying_the_big_screen/demo/">мой пример с колонками</a> на основе этой статьи.</p>
<p>Что до прогрессивного улучшения, то старые браузеры, не поддерживающие эти свойства, просто покажут единственную колонку соответствующей ширины.</p>
<h2>Разделение содержимого на части</h2>
<p>Деление содержимого на части позволяет сделать взаимодействие пользователя с информацией на насыщенных содержимым страницах быстрым и эффективным. Это очень естественно вписывается в концепцию адаптивности дизайна, так как позволяет варьировать, показывается ли содержимое целиком или же разбивается на столбцы в зависимости от размеров экрана.</p>
<p>На больших экранах преимущество этой техники состоит в том, что блок содержимого или группа блоков могут быть представлены по-разному на экранах разной ширины. Что позволяет улучшить читаемость и создать нужное эмоциональное воздействие. Хороший пример применения этого метода — сайт <a href="http://www.manchester.gov.uk/">Manchester City Council</a>. Он использует ограниченные по ширине группы модулей и врезки фотографий, занимающие всю доступную ширину. Страница плавно адаптируется к различным размерам экрана, подстраивая ширину и сетку каждой части содержимого.</p>
<figure>
    <img src="https://web-standards.ru/articles/big-screens/images/manchester.jpg" alt="Страница сайта Manchester City Council. Сверху меню со списком ссылок и поисковой строкой. В основной части список сервисов с иконками. Снизу слайдер.">
    <figcaption>Сайт Manchester City Council и разделение содержимого на части.</figcaption>
</figure>
<figure>
    <img src="https://web-standards.ru/articles/big-screens/images/juliana.jpg" alt="Главная страница сайта Juliana Bicycles. На ней слайдер и галерея с фотографиями велосипедов и их названиями.">
    <figcaption>Сайт Juliana Bicycles использует прием разделения содержимого более изящно.</figcaption>
</figure>
<p>Сайт <a href="http://www.julianabicycles.com/">Juliana Bicycles</a> использует более изящный подход к делению содержимого на части. Он комбинирует большие горизонтальные блоки во весь экран с группами разделенного на небольшие части содержимого. Главная навигация сайта представлена в виде полноэкранной карусели с насыщенными графическим фоном. Содержимое разделено на небольшие блоки, у которых есть поля. При просмотре с планшета или мобильного устройства эти поля исчезают. Бумажная текстура в фоне заполняет пространство между модулями, а на широких экранах и все доступное пространство. Конечно, большие изображения могут дорого обойтись при загрузке страницы, но это отличный способ зацепить пользователя. Способ показывать, а не рассказывать.</p>
<h2>Плиточное расположение содержимого</h2>
<p>Очевидное преимущество большого экрана — возможность увидеть много содержимого одновременно.</p>
<figure>
    <img src="https://web-standards.ru/articles/big-screens/images/google-images.jpg" alt="Выдача Google Images по запросу Отзывчивость. В ней фотографии разных устройств: планшетов, ноутбуков, телефонов.">
    <figcaption>Google Images показывает столько картинок, сколько вмещает окно.</figcaption>
</figure>
<p>С содержимым, напоминающим коллекцию, например фотографиями, расположение плитками помогает эффективно использовать большой экран. Мы ежедневно наблюдаем это, когда <a href="http://www.google.com/search?q=responsive&amp;um=1&amp;ie=UTF-8&amp;hl=en&amp;tbm=isch&amp;source=og&amp;sa=N&amp;tab=wi">ищем картинки в Google</a> — результаты заполняют весь экран, предоставляя широкий выбор уже при первом просмотре.</p>
<figure>
    <img src="https://web-standards.ru/articles/big-screens/images/pinterest.jpg" alt="Страница сайта Pinterest с галереей пинов по запросу Колоссальный.">
    <figcaption>Плиточное расположение элементов на Pinterest обыгрывает идею скрапбукинга или коллекционирования.</figcaption>
</figure>
<p><a href="http://www.pinterest.com/itscolossal/colossal/">Pinterest</a> использует плиточную раскладку для изображений с подписями. Пространство между плитками позволяет смягчить чрезмерно перегруженную страницу. На больших экранах блоки с картинками выглядят уходящими в бесконечность. На сайте, где поведение пользователя сводится к быстрому созданию своей подборки, заполнение экрана небольшими изображениями как раз и позволяет пробежать глазами по всей коллекции и получить удовлетворение от завершенности картины.</p>
<figure>
    <img src="https://web-standards.ru/articles/big-screens/images/uniqlo.jpg" alt="Страница сайта Uniqlo с каталогом коллекции зимней одежды. На фотографиях жилеты, пуховики, куртки и мужчины в них.">
    <figcaption>Широкоэкранная версия сайта Uniqlo позволяет покупателям сравнивать товары визуально.</figcaption>
</figure>
<p>Сайт <a href="http://uniqlo.com/">Uniqlo</a> использует широкую раскладку, заполненную картинками, хорошо продуманную и достаточно воздушную. Товары разделены на группы большими заголовками, которые выступают разделителями между наборами товаров и добавляют в раскладку «воздуха». Расположение продуктов широкими рядами позволяет покупателям быстро сравнивать товары. В то же время, пустые пространства, фото моделей и различные размеры блоков добавляют изысканности внешнему виду и восприятию и помогают усилить впечатление, что дизайн — важное отличие продуктов Uniqlo.</p>
<p>Ни Pinterest, ни Google не являются адаптивными сайтами — у них есть отдельные версии для пользователей мобильных устройств. Сайт Uniqlo также адаптирован только для больших экранов, маленькие устройства получают самый узкий вариант десктопной раскладки. Так что пока эти сайты не могут быть идеальными примерами отзывчивого дизайна, но мы можем использовать их как образцы применения хороших практик.</p>
<h2>Графические техники</h2>
<p>Другая интересная техника для адаптации сайтов к широким экранам скорее основана на классическом печатном дизайне, чем на реструктуризации и манипуляции содержимым.</p>
<figure>
    <img src="https://web-standards.ru/articles/big-screens/images/choiseul.jpg" alt="Страница французского сайта Института международной политики и геоэкономики. Сетка, состоящая из блоков разных цветов с текстами и ссылками в виде стрелок внутри. Сверху и слева меню.">
    <figcaption>Элементы сетки растягиваются к границам окна на сайте Institut Choiseul.</figcaption>
</figure>
<p>Сайт <a href="http://choiseul.info/">Institut Choiseul</a> ограничивает содержимое страниц центрированной структурированной сеткой, но эффектно обыгрывает пустое пространство возникновением цветных полей, уходящих из центральной колонки к краям экрана. Ссылка «наверх» возникает в нижнем левом углу при прокрутке — маленький штрих, подчеркивающий целостность окна. Жесткая сетка и большие цветные поля придают сайту спокойный солидный тон, напоминающий швейцарский стиль в типографике 50х-60х гг.</p>
<figure>
    <img src="https://web-standards.ru/articles/big-screens/images/lintjes.jpg" alt="Сайт музея канцелярии нидерландских орденов. На странице сверху основное меню и поисковая строка. Дальше расположена фотография с орденами и текстом. За ней фотографии с орденами и их описаниями, выровненные в одну строку. В самом низу страницы футер.">
    <figcaption>Сайт Kanselarij der Nederlandse Orden использует тянущуюся центрированную сетку и асимметричные цветовые вставки</figcaption>
</figure>
<p>Сайт <a href="http://lintjes.nl/">Kanselarij der Nederlandse Orden</a> имеет похожий стиль с асимметричным цветовыми полями, которые растянуты от края окна до противоположного края колонки с содержимым. Поскольку сетка расширяется пропорционально общей ширине экрана, содержимое постепенно заполняет все больше пространства, но прямоугольные цветовые поля делают эту довольно обычную раскладку интереснее.</p>
<p>Такие простые приемы, как цвет или текстура на фоне, или «вытягивание» блоков к краям экрана помогают создать чувство целостности дизайна даже на очень широких экранах. Творческое использование асимметрии вместо вытянутых, башенных раскладок помогает уберечь читателя от блуждания в пустых пространствах.</p>
<h2>В заключение</h2>
<p>Расширив известные нам техники адаптации содержимого для маленьких устройств, мы вполне можем применить их при адаптации сайтов к большим экранам. Сайтам с хорошо структурированной сеткой будет проще подстроиться, просто потому, что у такой сетки не будет проблем при растягивании в ширину.</p>
<p>Очевидно, что самое важное в любом дизайне — это содержимое, поэтому именно с него надо начинать подготовку дизайна для широких экранов. Для полнотекстовых статей очень важно создать хороший ритм и поток. Текст должен выглядеть так, чтобы можно было его читать, не отвлекаясь. В случае с фотографиями и графикой очень влияют на восприятие пространство и масштаб. Правительственные порталы и сайты, ориентированные на предоставление услуг, должны предоставлять удобный доступ как к самим услугам, так и информации. Коммерческие сайты должны быть простыми. Пользователям должно быть удобно выбирать товары и делать покупки. Плотность раскладки должна соответствовать назначению сайта: для сайтов с высокой пользовательской активностью плотность должна быть выше, на сайтах, которыми пользуются вдумчиво и не спеша — более разреженной. Подобно рамке для фотографии, плотное заполнение окна может сделать дизайн крупнее и тяжелее, в то время как добавление свободных пространств сделает его более элегантным и утонченным.</p>
<p>Конечно, десктопные пользователи, в отличие от пользователей мобильных устройств, могут позволить себе роскошь изменить размер окна браузера, если им мешают пустые пространства. Так же, вероятно, не все пользователи с большими экранами используют браузеры в полноэкранном режиме. Но, как и с мобильными, мы не должны строить предположений, на каких устройствах смотрят наше содержимое, и особенно не стоит строить догадок относительно будущего. Большие экраны могут предоставлять больше возможностей пользователям, так же как они открывают новые возможности для дизайнеров. И мы должны воспользоваться преимуществом расширения этих границ уже сейчас.</p>

                    ]]></description><pubDate>Mon, 24 Nov 2014 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/big-screens/</guid></item><item><title>Архитектура CSS</title><link>https://web-standards.ru/articles/css-architecture/</link><description><![CDATA[
                        <p>Мерой хорошего знания CSS для многих веб-разработчиков является то, насколько идеально ты можешь воссоздать в коде данный тебе макет. Ты не используешь таблицы и гордишься собой, когда сводишь к минимуму графику. Если ты действительно хорош, то работаешь с самыми новыми и лучшими техниками — вроде медиавыражений, переходов и трансформаций. Это правда, хорошие CSS-разработчики действительно всё это умеют. Однако существует совсем другая сторона CSS, которая редко упоминается, когда речь заходит об оценке чьих-то способностей.</p>
<p>Что интересно, обычно мы не упускаем из вида эту сторону, когда дело касается других языков. Разработчик на Rails не считается хорошим только потому, что его код работает по спецификации. Это считается базовым уровнем. Конечно, он должен работать по спецификации, но его качество измеряется другим: насколько код читаемый, легко ли его изменить или расширить, достаточно ли он отделён от других частей приложения, будет ли он масштабироваться?</p>
<p>Эти вопросы вполне естественны при оценке кода, и CSS здесь не должен быть исключением. Сегодняшние веб-приложения становятся всё более крупными, поэтому слабая и прямолинейная архитектура CSS может сильно усложнить разработку. Пришло время отнестись к CSS так же, как мы относимся ко всем другим частям наших приложений. Это не та вещь, которая может быть оставлена на потом или сброшена со счетов как «дизайнерская проблема».</p>
<h2>Принципы хорошей архитектуры CSS</h2>
<p>CSS-сообществу редко удаётся прийти к договорённости, что и как нужно делать. Даже если взглянуть только на <a href="http://news.ycombinator.com/item?id=2658948">комментарии с Hacker News</a> или на <a href="http://2002-2012.mattwilcox.net/archive/entry/id/1054/">реакцию разработчиков</a> на выход <a href="http://csslint.net/">CSS Lint</a>, станет ясно, что многие не согласны друг с другом даже по поводу самых базовых вещей, которые стоит или не стоит делать при разработке.</p>
<p>Поэтому вместо того, чтобы разворачивать перед вами мой собственный список правил, я скажу, что нам стоит определиться с принципами. Если мы сможем договориться о принципах, возможно, мы начнем замечать плохой CSS не только потому, что он нарушает наши принципы и убеждения о том, что есть хорошо, но и потому, что он на самом деле усложняет процесс разработки.</p>
<p>Мне кажется, что принципы хорошей архитектуры CSS не сильно отличаются от целей разработки хорошего ПО. Я хочу, чтобы мой CSS был предсказуемым, его можно было использовать повторно, легко поддерживать и масштабировать.</p>
<h3>Предсказуемость</h3>
<p>Предсказуемость для CSS означает, что ваши правила ведут себя ожидаемо. Когда вы добавляете или изменяете какое-то правило, оно не должно влиять на части сайта, которые вы не собирались менять.</p>
<p>Это вряд ли необходимо маленьким сайтам, которые редко изменяются, но для больших сайтов с десятками или сотнями страниц предсказуемый CSS необходим.</p>
<h3>Повторное использование</h3>
<p>CSS-правила должны быть достаточно абстрактны и независимы, чтобы можно было быстро создать новые компоненты из существующих частей без необходимости что-то переписывать или снова бороться с уже решёнными проблемами.</p>
<h3>Поддержка</h3>
<p>Когда нужно добавить или изменить новые компоненты или возможности, это не должно приводить к рефакторингу существующего CSS. Добавление компонента А на страницу не должно своим появлением ломать компонент Б.</p>
<h3>Масштабируемость</h3>
<p>Растущий в размерах и сложности сайт обычно требует для поддержки большего числа разработчиков. Масштабируемость для CSS означает, что он с лёгкостью может поддерживаться как одним человеком, так и большой командой разработчиков. Также это значит, что архитектура CSS вашего сайта достаточно доступна и не требует чрезмерных усилий для понимания. То, что вы сегодня единственный разработчик, имеющий дело с CSS, не значит, что так будет всегда.</p>
<h2>Распространённые ошибки</h2>
<p>Прежде чем перейти к способам соответствия нашим принципам с помощью хорошей архитектуры CSS, мне кажется, будет полезно взглянуть на распространённые ошибки, которые противоречат этим принципам. Часто бывает, что только через повторение ошибок мы можем прийти к пониманию альтернативного пути.</p>
<p>Следующие примеры обобщают код, который я писал сам. Технически они верны, но каждый из них привёл к проблемам и головной боли. Несмотря на мои самые лучшие намерения и обещания себе, что на этот раз всё будет по-другому, описанные подходы снова приводили к неприятностям.</p>
<h3>Изменение компонентов в зависимости от родителя</h3>
<p>Почти на каждом сайте в сети найдётся определённый визуальный элемент, который выглядит одинаково во всех случаях, кроме одного. Встречаясь с такой исключительной ситуацией, почти каждый новичок в CSS (и даже опытные разработчики) поступают одинаково. Они выясняют некоторого уникального родителя исключительно для этого случая (или создают такого) и пишут нужный селектор.</p>
<pre><code tabindex="0" class="language-css">.widget {
    background: yellow;
    border: 1px solid black;
    color: black;
    width: 50%;
}

#sidebar .widget {
    width: 200px;
}

body.homepage .widget {
    background: white;
}
</code></pre>
<p>На первый взгляд этот код выглядит вполне безобидно, но давайте проверим его на соответствие принципам, которые мы определили выше.</p>
<p>Во-первых, виджет в этом примере непредсказуем. Разработчик, уже сделавший несколько таких виджетов, ожидает, что они будут выглядеть определённым образом, однако в боковой колонке и на главной странице они будут выглядеть иначе, несмотря на одинаковую разметку.</p>
<p>Также этот блок плохо масштабируется и не слишком подходит для повторного использования. Что случится, когда его вид на главной понадобится на какой-нибудь другой странице? Придётся добавить новый селектор.</p>
<p>Наконец, этот блок сложен в поддержке потому, что если дизайн виджета изменится, это потребует обновления стилей в нескольких местах. И в отличие от примера выше, правила, приводящие к подобным проблемам, редко так же следуют друг за другом.</p>
<p>Представьте, если бы такой код был написан на любом другом языке. Вы создаёте обычное определение класса и потом, в другой части кода, берёте это определение класса и меняете его для определённого случая. Это прямо противоречит принципу <a href="https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B8%D0%BD%D1%86%D0%B8%D0%BF_%D0%BE%D1%82%D0%BA%D1%80%D1%8B%D1%82%D0%BE%D1%81%D1%82%D0%B8/%D0%B7%D0%B0%D0%BA%D1%80%D1%8B%D1%82%D0%BE%D1%81%D1%82%D0%B8">открытости и закрытости</a> для разработки ПО:</p>
<blockquote>
<p>Части ПО (классы, модули, функции и т.п.) должны быть открыты для расширения, но закрыты для изменения.</p>
</blockquote>
<p>Далее в статье мы рассмотрим, как изменять компоненты, не опираясь на родительский селектор.</p>
<h3>Слишком сложные селекторы</h3>
<p>Удивительно, но эта статья обойдёт стороной демонстрацию всей мощи CSS-селекторов и не расскажет о том, как оформить целый сайт, не используя ни единого класса или ID.</p>
<p>Технически это возможно, но чем больше я работаю с CSS, тем больше стараюсь держаться подальше от сложных селекторов. Чем сложнее селектор, тем больше он привязан к HTML. Опираясь на HTML-теги и комбинаторы в селекторе, вы, может быть, и получите чистый до блеска HTML, но CSS от этого станет грязным и раздутым.</p>
<pre><code tabindex="0" class="language-css">#main-nav ul li ul li div { }
#content article h1:first-child { }
#sidebar &gt; div &gt; h3 + p { }
</code></pre>
<p>Все эти примеры логически верны. Первый, по-видимому, оформляет выпадающее меню, второй говорит, что заголовок в <code>&lt;article&gt;</code> должен выглядеть иначе, чем остальные элементы <code>&lt;h1&gt;</code>, а последний пример, скорее всего, добавляет отступ к первому абзацу в блоках боковой колонки.</p>
<p>Если бы этот HTML никогда не изменился, можно было бы согласиться с таким подходом. Но насколько реалистичным будет предположить, что HTML никогда не изменится? Чересчур сложные селекторы могут выглядеть впечатляюще и даже помогут обойтись без презентационной разметки в HTML, но вряд ли они помогут соответствовать нашим принципам хорошей архитектуры CSS.</p>
<p>Упомянутые примеры совсем не готовы для повторного использования. Поскольку селектор указывает на очень конкретную часть разметки, то как может другой компонент с иной структурой HTML использовать эти стили? Возьмём для примера первый селектор (выпадающее меню) — что, если похожее меню понадобится на другой странице, на которой не будет элемента <code>#main-nav</code>? Тогда вам придётся повторить все стили.</p>
<p>Эти селекторы также очень непредсказуемы в случае, когда меняется оригинальный HTML. Представим, что разработчик решил поменять тег <code>&lt;div&gt;</code> в третьем примере на <code>&lt;section&gt;</code> — в результате весь селектор развалится.</p>
<p>И наконец: поскольку эти селекторы работают, только когда HTML остаётся неизменным, они по определению не поддерживаемы и не масштабируемы.</p>
<p>В больших веб-приложениях приходится идти на уступки и компромиссы. Цена хрупких и сложных селекторов неоправданно высока, чтобы использовать их во имя чистого HTML.</p>
<h3>Слишком общие имена классов</h3>
<p>При создании повторно используемых компонентов зачастую принято ограничивать область действия (ага, если бы) вложенных элементов в рамках имени класса компонента. Например:</p>
<pre><code tabindex="0" class="language-html">&lt;div class=&quot;widget&quot;&gt;
    &lt;h3 class=&quot;title&quot;&gt;…&lt;/h3&gt;
    &lt;div class=&quot;contents&quot;&gt;
        Лорем ипсум…
        &lt;button class=&quot;action&quot;&gt;Жми сюда!&lt;/button&gt;
    &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<pre><code tabindex="0" class="language-css">.widget {}
.widget .title {}
.widget .contents {}
.widget .action {}
</code></pre>
<p>Идея состоит в том, что классы вложенных элементов <code>.title</code>, <code>.contents</code> и <code>.action</code> можно безопасно оформить, не боясь, что стили повлияют на элементы с такими же классами. Это, конечно, так, но совсем не значит, что эти стили не повлияют на элементы с таким же классом, которые могут оказаться внутри.</p>
<p>На больших проектах высока вероятность того, что имя класса, скажем, <code>.title</code> будет использовано в другом контексте или даже само по себе. Если такое произойдёт, то заголовок виджета вдруг станет выглядеть иначе.</p>
<p>Слишком общие имена классов приводят к непредсказуемому CSS.</p>
<h3>Когда правило делает слишком много</h3>
<p>Однажды вы создаёте визуальный компонент, который должен отстоять на 20 пикселей от верхнего левого угла блока на вашем сайте:</p>
<pre><code tabindex="0" class="language-css">.widget {
    position: absolute;
    top: 20px;
    left: 20px;
    background-color: red;
    font-size: 1.5em;
    text-transform: uppercase;
}
</code></pre>
<p>И тут по ходу дела обнаруживается, что вам нужен такой же компонент, но в другом месте. CSS выше не справится с такой ситуацией потому, что он не готов к повторному использованию.</p>
<p>Проблема в том, что вы делаете с помощью одного селектора сразу слишком многое. Вы описываете внешний вид блока вместе с раскладкой и позиционированием в одном правиле. Внешний вид можно использовать повторно, раскладку и позиционирование — нет. И поскольку они описаны вместе, всё правило скомпрометировано.</p>
<p>На первый взгляд это кажется безобидным, но часто приводит к копированию кода из одного места в другое, особенно менее опытными разработчиками. Если новый член команды захочет сделать что-то похожее на конкретный компонент, скажем <code>.infobox</code>, вероятнее всего, он начнёт с того, что применит этот класс. Но если это не сработает из-за того, что в других условиях этот блок спозиционируется совсем не так, как нужно, то что он скорее всего сделает? По моему опыту, большинство молодых разработчиков не разобьют правило на удобные для повторного использования части. Вместо этого они просто скопируют строки кода, необходимые для этого конкретного случая, в новый селектор, дублируя код без необходимости.</p>
<h2>Причина</h2>
<p>Упомянутые выше проблемные примеры объединяет одна особенность: все они слишком полагаются на оформление CSS.</p>
<p>Это заявление может прозвучать странно. В конце концов, это же стили. Разве не должны они нести большую (если не всю) нагрузку для оформления? Не этого ли мы хотим?</p>
<p>Простой ответ на этот вопрос «да», но, как обычно, всё не так просто. Разделять содержимое и представление хорошо, но содержимое не отделяется от представления только потому, что ваш CSS отделён от HTML. Скажем иначе: вы не достигнете цели, если просто уберёте всё представление из HTML, но при этом для работы вашего CSS потребуется подробнейшее знание структуры HTML.</p>
<p>Более того, HTML редко состоит только из содержимого, почти всегда в нём есть и структура. И часто эта структура состоит из контейнеров, единственной задачей которых является объединение некоторой группы элементов для работы CSS. Даже без презентационных классов такие структуры добавляют представление в HTML-код. Но действительно ли это смешивает содержимое с представлением?</p>
<p>Я уверен, что на текущем уровне развития HTML и CSS необходимо и зачастую разумно использовать HTML и CSS в качестве единого слоя представления. Слой содержимого в таком случае может быть отделён с помощью шаблонизаторов или подключаемых фрагментов (как partials в Ruby).</p>
<h2>Решение</h2>
<p>Если HTML и CSS будут работать одним слоем для создания представления вашего веб-приложения, они должны делать это так, чтобы соответствовать всем принципам хорошей архитектуры CSS.</p>
<p>Лучший подход, который мне удалось найти, состоит в том, чтобы как можно меньше опираться на структуру HTML в CSS. CSS должен определять как выглядит набор визуальных элементов. Для минимизации влияния HTML эти элементы должны выглядеть ровно так, как они описаны, независимо от того, где они находятся в HTML. Если некоторые компоненты должны выглядеть по-разному в разных ситуациях, они должны быть вызваны по-другому, и отвечать за этот вызов должен HTML.</p>
<p>Например, в CSS с помощью класса <code>.button</code> описан компонент кнопки. Если в HTML понадобится конкретный элемент, который выглядит как кнопка, то нужно использовать этот класс. Если в другой ситуации этой кнопке понадобится выглядеть иначе (скажем, больше и в полную ширину), в CSS нужно определить этот новый вид, также с помощью класса, и тогда его можно будет добавить в HTML для достижения нужного вида.</p>
<p>CSS определяет, как выглядит ваш компонент, а HTML применяет этот вид к элементам на странице. Чем меньше CSS «знает» про структуру HTML, тем лучше.</p>
<p>Большой плюс указания всего, что нужно, прямо в HTML в том, что это позволяет другим разработчикам, глядя на разметку, точно понимать, как должен выглядеть этот элемент. Мотивы здесь очевидны. Без этого подхода невозможно сказать, является ли вид элемента намеренным или случайным, что приводит к непониманию в команде разработчиков.</p>
<p>Дополнительные усилия для написания классов в разметке обычно становятся главным аргументом против. Одно правило в CSS может быть направлено на тысячи экземпляров конкретного компонента. Действительно ли стоит писать эти классы тысячу раз только для того, чтобы явно их указать в разметке?</p>
<p>Несмотря на то, что эти сомнения справедливы, они могут привести к ошибочным выводам. Вывод такой: либо вы используете родительский селектор в CSS, либо вам придётся написать этот HTML-класс тысячу раз руками. Но, очевидно, есть и другие варианты. Уровень абстракции компонентов в Rails и других фреймворках легко позволяет явно описывать внешний вид прямо в HTML без необходимости снова и снова писать один и тот же класс.</p>
<h2>Правильный подход</h2>
<p>Совершив описанные выше ошибки снова и снова, заплатив позднее за их последствия, я пришёл к некоторым принципам. Не претендуя не всеобъемлемость, мой опыт показывает, что эти принципы помогут достичь хорошей архитектуры CSS.</p>
<h3>Будьте точнее</h3>
<p>Лучший способ добиться того, чтобы ваши селекторы не влияли на ненужные элементы — это не дать им такой возможности. Со временем вы можете обнаружить, что селектор вроде <code>#main-nav ul li ul li div</code> применяется уже совсем не к тому элементу из-за изменившейся разметки. Класс <code>.subnav</code>, напротив, имеет очень мало шансов быть случайно применённым не к тому элементу. Назначать классы прямо элементам, которые вы хотите оформить — лучший способ сохранить ваш CSS предсказуемым.</p>
<pre><code tabindex="0" class="language-css">/* Граната */
#main-nav ul li ul { }

/* Снайперская винтовка */
.subnav { }
</code></pre>
<p>Если взять два примера выше, то первый больше напоминает гранату, а второй снайперскую винтовку. Граната может прекрасно сработать сегодня, но кто может гарантировать, что завтра в радиус поражения не попадёт невинный гражданский?</p>
<h3>Разделяйте ответственность</h3>
<p>Я уже упоминал, что хорошо организованный компонентный слой может уменьшить зависимость CSS от структуры HTML. Помимо этого, сами компоненты должны быть модульными. Компоненты должны знать только, как оформить себя, и делать это хорошо. Но они совсем не должны отвечать за раскладку, позиционирование или делать слишком много предположений насчёт того, в каком окружении они будут располагаться.</p>
<p>В общем случае компоненты должны определять свой внешний вид, а не раскладку и позиционирование. Будьте осторожны, когда видите свойства вроде <code>background</code>, <code>color</code> и <code>font</code> в одном правиле с <code>position</code>, <code>width</code>, <code>height</code> и <code>margin</code>.</p>
<p>Раскладку и позиционирование стоит задавать либо отдельным классом для раскладки, либо использовать для этого отдельный элемент. (Помните, что для эффективного разделения содержимого и представления часто необходимо отделить содержимое от его контейнера).</p>
<h3>Задайте пространство имён</h3>
<p>Мы уже выяснили, почему родительские селекторы не всегда на 100% эффективны для ограничения действия и пересечения стилей. Гораздо лучший подход — добавить пространство имён к самим классам. Если элемент является частью визуального компонента, то каждый из классов его вложенных элементов должен использовать имя класса базового компонента в качестве пространства имён.</p>
<pre><code tabindex="0" class="language-css">/* Высокий риск пересечения имён классов */
.widget { }
.widget .title { }

/* Низкий риск пересечения имён классов */
.widget { }
.widget-title { }
</code></pre>
<p>Добавление пространства имён к классам делает ваши компоненты самодостаточными и модульными. Это также уменьшает вероятность конфликтов с уже существующими классами и снижает специфичность, необходимую для оформления дочерних элементов.</p>
<h3>Расширяйте компоненты модификаторами классов</h3>
<p>Когда существующий компонент должен выглядеть чуть иначе в некотором контексте, создайте модификатор класса, чтобы расширить его.</p>
<pre><code tabindex="0" class="language-css">/* Плохо */
.widget { }
#sidebar .widget { }

/* Хорошо */
.widget { }
.widget-sidebar { }
</code></pre>
<p>Мы уже рассмотрели все недостатки изменения компонентов на основе их родительских элементов, но для закрепления: модификатор класса может использоваться где угодно, переопределение на основе вложенности — только в конкретной ситуации. Модификатор класса также может использоваться сколько угодно раз. Наконец, модификаторы класса очень ясно выражают намерения разработчика прямо в HTML. Классы, основанные на вложенности, напротив, полностью скрыты для разработчика, который смотрит только на HTML, что сильно увеличивает вероятность, что они от него ускользнут.</p>
<h3>Организуйте CSS в логическую структуру</h3>
<p><a href="http://snook.ca/">Джонатан Снук</a> в своей замечательной книге <a href="http://smacss.com/">SMACSS</a> убеждает разделять CSS-правила на четыре отдельные категории: базовые, раскладку, модули и состояния. Базовые состоят из сбросов и умолчаний для элементов. Раскладка — для расположения глобальных элементов сайта, а также общих вспомогательных вещей вроде модульных сеток. Модули — это визуальные элементы для повторного использования и стили состояний для оформления того, что можно включить или выключить с помощью JavaScript.</p>
<p>В системе SMACSS модули (эквивалентные тому, что я называю компонентами) составляют большинство от всех правил в CSS, поэтому я часто прихожу к необходимости разбить их ещё больше, отделив абстрактные шаблоны.</p>
<p>Компоненты — это отдельные визуальные элементы. Шаблоны, напротив, скорее строительные блоки. Шаблоны не живут сами по себе и редко описывают внешний вид. Они являются одиночными повторяемыми элементами, из которых можно собрать компонент.</p>
<p>Приведём конкретный пример: компонент может быть модальным диалоговым окном. Это окно может иметь специальный для этого сайта градиент в заголовке, какую-то тень вокруг, кнопку закрытия в правом верхнем углу, а также фиксированное положение и выравнивание в центре по вертикали и горизонтали. Все четыре свойства этого окна могут быть повторно использованы по всему сайту, поскольку вы вряд ли захотите описывать эти свойства снова и снова. Таким образом, все они являются шаблонами, которые вместе образуют компонент диалогового окна.</p>
<p>Обычно я не использую классы шаблонов прямо в HTML, если для этого нет веских причин. Вместо этого я использую препроцессоры для включения стилей шаблона в описание компонента. Далее мы обсудим подробнее этот подход и мои причины для его использования.</p>
<h3>Используйте классы строго для оформления</h3>
<p>Каждый, кто работал с большим проектом, когда-нибудь сталкивался с HTML-элементом, имеющим класс, назначение которого остаётся загадкой. Вам хочется удалить его, но вы сомневаетесь, потому что у него может оказаться применение, о котором вы не знаете. И когда это случается снова и снова, ваш HTML заполняется классами, которые не несут никакой пользы — только потому, что члены команды боятся их удалить.</p>
<p>Проблема в том, что во фронтенд-разработке принято наделять классы слишком многими ролями. Они оформляют HTML, на них опирается JavaScript, их добавляют в HTML для определения поддержки возможностей, используют для автоматических тестов и т.д.</p>
<p>Это проблема. Когда к классам обращаются слишком многие составляющие приложения, становится страшно удалять их из HTML.</p>
<p>Тем не менее, устоявшиеся договорённости помогут полностью избежать этой проблемы. Когда вы видите класс в HTML, вы должны быть способны чётко сказать, для чего он предназначен. Мой совет — добавлять префикс ко всем неоформительским классам. Я использую <code>.js-</code> для JavaScript и <code>.supports-</code> для классов Modernizr. Все классы без префикса — для оформления и только для оформления.</p>
<p>Это позволяет искать неиспользуемые классы и удалять их из HTML простым поиском по папке со стилями. Вы можете даже автоматизировать этот процесс с помощью JavaScript, сравнивая классы в HTML с классами в объекте <code>document.styleSheets</code>. Классы, которых нет в <code>document.styleSheets</code>, можно безопасно удалять.</p>
<p>Итак, полезно разделять содержимое и представление, и столь же важно отделять представление от поведения. Использование классов с оформлением в качестве основы для JavaScript так сильно связывает CSS с JavaScript, что становится сложно или даже невозможно обновить оформление некоторых элементов, не сломав их поведение.</p>
<h3>Логическая структура в именах классов</h3>
<p>Сегодня большинство пишет CSS с дефисами в качестве разделителя для слов. Но только дефисов обычно недостаточно, чтобы различать между собой разные типы классов.</p>
<p><a href="http://nicolasgallagher.com">Николас Галлахер</a> недавно написал <a href="http://nicolasgallagher.com/about-html-semantics-front-end-architecture/">о своем решении этой проблемы</a>, которое я тоже (с некоторыми изменениями) с большим успехом применил. Чтобы проиллюстрировать необходимость в договорённости об именовании, рассмотрим следующий пример:</p>
<pre><code tabindex="0" class="language-css">/* Компонент */
.button-group { }

/* Модификатор компонента (изменяющий .button) */
.button-primary { }

/* Вложенный объект (находится внутри .button) */
.button-icon { }

/* Это класс компонента или раскладки? */
.header { }
</code></pre>
<p>Глядя на классы выше, нельзя сказать, к какому типу правил они применяются. Это не только добавляет путаницы при разработке, но и усложняет автоматическое тестирование вашего HTML и CSS. Структурированная договорённость об именовании позволяет с первого взгляда на имя класса точно понять, в каких отношениях он состоит с другими классами и в каком месте HTML должен располагаться — что, в отличие от обычной практики, упрощает именование и делает возможным тестирование.</p>
<pre><code tabindex="0" class="language-scss">/* Правила шаблонов (с использованием Sass) */
%template-name
%template-name--modifier-name
%template-name__sub-object
%template-name__sub-object--modifier-name

/* Правила компонентов */
.component-name
.component-name--modifier-name
.component-name__sub-object
.component-name__sub-object--modifier-name

/* Правила для раскладки */
.l-layout-method
.grid

/* Правила состояний */
.is-state-type

/* Классы для JavaScript без оформления */
.js-action-name
</code></pre>
<p>Переработанный первый пример:</p>
<pre><code tabindex="0" class="language-css">/* Компонент */
.button-group { }

/* Модификатор компонента (изменяющий .button) */
.button--primary { }

/* Вложенный объект (находится внутри .button) */
.button__icon { }

/* Класс раскладки */
.l-header { }
</code></pre>
<h2>Инструменты</h2>
<p>Поддержка эффективной и хорошо организованной архитектуры CSS может быть очень сложной, особенно в больших командах. Несколько плохих правил здесь, несколько там — и снежный ком неразрешимых проблем уже покатился. Когда CSS вашего приложения уже дошел до войн за специфичность и сомнительных побед за счёт <code>!important</code>, может оказаться, что пути назад уже нет, разве что переписать всё с нуля.</p>
<p>К счастью, есть инструменты, которые позволят гораздо проще контролировать архитектуру CSS вашего сайта.</p>
<h3>Препроцессоры</h3>
<p>Сегодня сложно говорить про инструменты для CSS и не упомянуть препроцессоры, и эта статья не будет исключением. Но прежде чем я воспою их ценность, мне следует коротко предостеречь вас.</p>
<p>Препроцессоры помогают писать CSS быстрее, но не качественнее. В конце концов код превращается в обычный CSS, к которому применяются все те же правила. Если препроцессор помогает вам писать CSS быстрее, он так же помогает быстрее писать плохой CSS, поэтому важно понимать, что такое хорошая архитектура CSS, а не просто думать, что препроцессор решит все ваши проблемы.</p>
<p>Некоторые так называемые «фичи» препроцессоров на самом деле могут навредить архитектуре CSS. Следующих возможностей я категорически стараюсь избегать (хотя основная идея применима ко всем препроцессорам, эти правила относятся в большей степени к Sass):</p>
<ul>
<li>Никогда не вкладывайте правила только для организации кода. Вкладывайте только тогда, когда это нужно в CSS на выходе.</li>
<li>Никогда не используйте примеси (mixin), если не передаёте аргумент. Примеси без аргументов гораздо лучше использовать в качестве шаблонов, которые можно расширить.</li>
<li>Никогда не используйте <code>@extend</code> для селектора, который не является одиночным классом. Это не имеет смысла с точки зрения дизайна и раздувает скомпилированный CSS.</li>
<li>Никогда не используйте <code>@extend</code> для компонентов интерфейса в модификаторе компонента, иначе вы нарушаете цепь наследования (подробнее об этом дальше).</li>
</ul>
<p>Лучшая часть препроцессоров — это функции, вроде <a href="https://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#extend"><code>@extend</code></a> и <a href="https://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#placeholder_selectors_"><code>%placeholder</code></a>. Обе они позволяют просто управлять абстракциями в CSS, при этом не раздувая код и обходясь без добавления в HTML огромного количества базовых классов, с которыми потом очень сложно управиться.</p>
<p><code>@extend</code> нужно использовать с осторожностью, потому что иногда эти классы будут нужны в HTML. Например, когда вы впервые узнаёте про <code>@extend</code>, то его сразу хочется применить ко всем классам-модификаторам как-то так:</p>
<pre><code tabindex="0" class="language-scss">.button {
    /* Стили кнопки */
}

/* Плохо */
.button--primary {
    @extend .button;
    /* Стили модификатора */
}
</code></pre>
<p>Проблема с этим подходом в том, что вы теряете цепь наследования в HTML. Теперь довольно сложно выбрать все экземпляры кнопки с помощью JavaScript.</p>
<p>Как правило, я не расширяю компоненты интерфейса или что-то другое, тип чего мне может пригодиться знать в будущем. Для этого случая как раз и придуманы компоненты, и это ещё один способ, помогающий отличать шаблоны от компонентов. Шаблон — это нечто, к чему вы никогда не обратитесь в логике вашего приложения, и поэтому его можно безопасно расширить с помощью препроцессора.</p>
<p>Как это может выглядеть на примере модального окна, о котором шла речь выше:</p>
<pre><code tabindex="0" class="language-scss">.modal {
    @extend %dialog;
    @extend %drop-shadow;
    @extend %statically-centered;
    /* Другие стили окна */
}

.modal__close {
    @extend %dialog__close;
    /* Другие стили кнопки закрытия */
}

.modal__header {
    @extend %background-gradient;
    /* Другие стили заголовка окна */
}
</code></pre>
<h3>CSS Lint</h3>
<p><a href="http://www.stubbornella.org/">Николь Салливан</a> и <a href="http://www.nczonline.net/">Николас Закас</a> создали <a href="http://csslint.net/">CSS Lint</a>, инструмент для контроля за качеством кода, который помогает разработчикам находить плохие подходы в их CSS. На сайте он описан так:</p>
<blockquote>
<p>CSS Lint указывает на проблемы в вашем CSS-коде. Он делает базовую проверку синтаксиса, а также применяет к коду набор правил, которые позволяют выделить в нём проблемные подходы или признаки неэффективности. Все правила расширяемы, потому вы легко можете написать свои или пропустить ненужные.</p>
</blockquote>
<p>И если набор правил по умолчанию может не подходить идеально для большинства проектов, главная особенность CSS Lint в том, что он может быть настроен в точности как вам нужно. Это значит, что вы можете взять правила из списка по умолчанию и выбрать нужные, а также написать собственные.</p>
<p>Инструмент вроде CSS Lint необходим любой большой команде, чтобы поддерживать в коде, как минимум, базовую последовательность и соответствие договорённостям. Как я уже упоминал раньше, одна из главных ценностей таких договорённостей в том, что с помощью инструментов вроде CSS Lint можно просто выявить то, что их нарушает.</p>
<p>Основываясь на договорённостях, которые я предложил выше, очень легко написать правила для определения неверных подходов. Вот несколько правил, которые я использую:</p>
<ul>
<li>Не допускайте ID в своих селекторах.</li>
<li>Не используйте несемантические типы селекторов (div и span, напр.) для любого неодиночного правила.</li>
<li>Не используйте больше двух комбинаторов в селекторе.</li>
<li>Не допускайте использование классов, начинающихся с <code>.js-</code>.</li>
<li>Внимание, если раскладка и позиционирование применяются в правилах для элементов без префикса <code>.l-</code> в названии класса.</li>
<li>Внимание, если класс, определённый сам по себе, позднее переопределяется как дочерний или иначе.</li>
</ul>
<p>Это, конечно, всего лишь возможные варианты, но они предложены для того, чтобы направить ваши мысли в сторону контроля стандартов, использующихся в коде вашего проекта.</p>
<h3>HTML Inspector</h3>
<p>Ранее я предположил, что будет довольно просто пройтись по вашим HTML-классам и всем подключенным стилям и выдать предупреждение, если класс был использован в HTML, но не определён ни в одном файле стилей. Сейчас я разрабатываю инструмент, который называется <a href="https://github.com/philipwalton/html-inspector">HTML Inspector</a>, чтобы сделать этот процесс проще.</p>
<p>HTML Inspector проходит по вашему HTML и (почти как CSS Lint) позволяет вам написать собственные правила, которые вызывают ошибки или предупреждения, когда какие-то договорённости нарушаются. Сейчас я использую следующие правила:</p>
<ul>
<li>Внимание, если один и тот же ID используется на странице два раза.</li>
<li>Не используйте классы, не упомянутые ни в одном из файлов стилей или передайте список разрешённых (с префиксом <code>.js-</code>, напр.)</li>
<li>Классы модификаторов не должны использоваться без их базовых классов.</li>
<li>Классы вложенных объектов не должны использоваться в отсутствие родительского базового класса.</li>
<li>Простые элементы <code>&lt;div&gt;</code> и <code>&lt;span&gt;</code> не должны использоваться в HTML без назначенных классов.</li>
</ul>
<h2>Заключение</h2>
<p>CSS — это не просто средство визуального дизайна. Не стоит забывать о правильных подходах к программированию только потому, что вы пишете CSS. Подходы вроде ООП, DRY, принцип открытости и закрытости, разделение ответственности и т.п. также применимы к CSS.</p>
<p>Главная мысль вот в чем: что бы вы ни делали для организации вашего кода, убедитесь, что вы оцениваете свои действия по тому, помогают ли они сделать проще разработку и поддержку кода в будущем.</p>

                    ]]></description><pubDate>Thu, 20 Feb 2014 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/css-architecture/</guid></item><item><title>Культ карго CSS</title><link>https://web-standards.ru/articles/cargo-cult-css/</link><description><![CDATA[
                        <p>Все согласны с тем, что лучше иметь какую-то методологию для написания и поддержки CSS, чем не иметь никакой вовсе. Тем не менее, некоторые популярные среди разработчиков практики ухудшают как семантику кода, так и возможность его долгосрочной поддержки. Я хотел бы рассказать о проблемах с «методологиями фреймворков» CSS, и о том, как мы, веб-разработчики, можем разрешить эти проблемы.</p>
<p>Самая популярная фреймворк-методология для разработки CSS сегодня — <a href="https://github.com/stubbornella/oocss/wiki/faq">OOCSS</a>, хотя существуют и другие похожие техники, например, <a href="http://bem.info/method/definitions/">БЭМ</a>. Эти методологии пытаются применить к CSS принципы объектно-ориентированного программирования. Даже если не обращать внимания на различные концептуальные несоответствия между <em>декларативным языком</em>, на котором пишутся стили, и <em>объектно-ориентированными принципами</em> организации программ, использование этих методологий приводит к сложностям, которые не будут с первого взгляда очевидны начинающим разработчикам. Самое печальное, что эти методологии распространяются крайне широко — в первую очередь благодаря популярным блоггерам, рекламирующим их использование как передовую практику. То, что никаких реальных доказательств заявленных преимуществ этих методологий не приводится (за исключением очень малого числа избранных высоконагруженных сайтов), согласуется с моей точкой зрения: эти методологии — вредный и вводящий в заблуждение <a href="https://ru.wikipedia.org/wiki/%D0%9A%D1%83%D0%BB%D1%8C%D1%82_%D0%BA%D0%B0%D1%80%D0%B3%D0%BE">культ карго</a>.</p>
<h2>Семантика</h2>
<blockquote>
    <p>В программировании есть две сложных вещи: инвалидация кэша и выбор, как правильно что-нибудь назвать.</p>
    <footer>
        <cite>
            Фил Карлтон
        </cite>
    </footer>
</blockquote>
<p>Веб в первую очередь — <em>семантическая среда.</em> Это ключевая функция платформы, которая предназначена для использования людьми, разговаривающими на разных языках, принадлежащими к разным культурам, разного пола и возраста, с абсолютно разными физическими и когнитивными способностями. Для того чтобы быть доступным для всех настолько разных людей, у веба есть невероятный по широте спектр различных технологий. Нет никакой единственно верной точки зрения на то, каким должен быть веб или как он должен выглядеть. Но в том случае, когда вы разрабатываете продукт с хотя бы относительно широкой сферой применения, в основе всего, что вы делаете как веб-разработчик, должен лежать семантический подход.</p>
<p>При написании HTML выразить семантику содержимого или интерфейса сайта/приложения можно тремя основными способами: с помощью <em>типа элементов</em>, из которых состоит разметка; <code>id</code>, которые используется для того, чтобы указать <em>особый, индивидуальный элемент</em> — и <em>имен классов</em>, которые нужны, чтобы описать набор элементов.</p>
<p>Из этих трех вариантов именно имена классов чаще всего используются для того, чтобы привязать особенности отображения к HTML-документу. Важно отметить, что в последней версии рекомендации HTML 5 по версии W3C в описании имен классов сказано:</p>
<blockquote>
<p>Дополнительных ограничений по тем данным, которые авторы документов могут использовать внутри атрибута <code>class</code>, нет, но авторам рекомендуется использовать значения, которые описывают суть содержимого элемента, а не желаемое отображение этого содержимого.</p>
</blockquote>
<p>Нигде в спецификации атрибута <code>class</code> не сказано, для чего <em>предназначен</em> этот атрибут, кроме рекомендации, что его следует использовать для описания семантики содержимого. Несмотря на это, для веб-разработчиков стало совершенно обычным делом описывать атрибут <code>class</code> как «класс CSS» и т.п. Как замечает Тантек Челик:</p>
<blockquote>
<p>Употребляя фразу «CSS-класс» или «имя CSS-класса», вы не просто неточно (неверно!) выражаетесь, а привязываете контекст отображения и саму структуру CSS к именам классов, что предполагает и поощряет использование неверного приема, когда названия классов становятся зависимыми от предполагаемого отображения.</p>
</blockquote>
<p>Поскольку эта рекомендация совершенно несовместима с методологиями фреймворков, они просто решили ее игнорировать. Поскольку в этой методологии имена классов <em>должны</em> описывать отображение элементов, они по определению не являются семантическими — <a href="http://nicolasgallagher.com/about-html-semantics-front-end-architecture/">как бы это ни пытались оспорить</a>. Но почему семантические имена классов вообще имеют такое значение? На самом базовом уровне суть в том, что нужно поддерживать независимый от платформы, всесторонний принцип, который Тим Бернерс-Ли назвал «<a href="http://www.scientificamerican.com/article.cfm?id=long-live-the-web">универсальностью веба</a>». А еще дело в том, что мы не можем предсказать, каким образом будущие технологические инновации будут использовать код, который мы разрабатываем сейчас.</p>
<blockquote>
    <p>Быстрый тест на то, является ли ваш HTML-код семантическим: можно ли использовать его в качестве публичного API?</p>
    <footer>
        <cite>
            Алекс Гейнор, <a href="https://twitter.com/alex_gaynor/statuses/389502802265133056">@alex_gaynor</a>
        </cite>
    </footer>
</blockquote>
<p>Отличный пример: <a href="http://microformats.org/about">микроформаты</a>. Единственная причина, по которой они возникли, в том, что создатели документов использовали <em>семантические имена классов</em>, чтобы разметить часто встречающиеся структуры: адреса или записи в календаре. За счет кодификации договоренностей, которые использовались для описания таких данных, разметка стала полезной не только для посетителей сайтов, но и для автоматических систем: программы смогли понимать и использовать эти данные так, как никто и не мог себе представить. Микроформаты демонстрируют всю пользу от семантических имен классов, так что печально, что на них иногда смотрят свысока.</p>
<blockquote>
    <p>Имена классов не передают (или передают очень мало) семантической информации машинам или пользователям, за исключением тех случаев, когда они входят в небольшой набор договоренностей об именах (которые могут быть прочитаны машиной) — микроформатов.</p>
    <footer>
        <cite>
            Николас Галлахер
        </cite>
    </footer>
</blockquote>
<p>Это совершенно произвольное разделение между «небольшим набором договоренностей об именах» и возможными способами будущего использования контента, который разрабатывается и публикуется в данный момент, имеет свой причиной исключительно потребность в оправдании идеологии CSS-фреймворков. Если мы разрешаем использовать несемантические имена классов, то структура и контент документа начинают контролироваться его презентацией — что является четким нарушением принципа <a href="https://en.wikipedia.org/wiki/Law_of_Demeter">разделения ответственности</a>.</p>
<h2>Поддержка</h2>
<p>При разработке сайтов мы должны обеспечить, чтобы наш продукт в достаточной мере смотрел в будущее, чтобы его можно было легко поддерживать и адаптировать на протяжении всего его существования. Современные сайты и приложения во все большей мере создаются большими командами фронтенд-разработчиков. В этой среде очень важно, чтобы методология, которая используется при написании CSS, успешно принималась всей командой (и теми, кто работает над проектом только на нескольких этапах его развития, и теми, кто присоединился к нему уже после начала разработки). В такой среде возникает множество проблем с написанием CSS, например:</p>
<ul>
<li>Повторение/дублирование кода</li>
<li>Постоянство кода в продукте</li>
<li>Производительность</li>
</ul>
<p>Пожалуй, самая серьезная причина озаботиться методологией CSS-фреймворка — это дать фронтенд-разработчикам возможность решить первую из этих проблем: дублирование CSS-кода сайта. Если использовать короткие селекторы по имени класса, разработчик может сделать селектор более полезным: так, чтобы его правила распространялись на большое количество элементов. Вот пример такого класса, который дает соответствующим элементам одинаковую границу, поля и параметры шрифта:</p>
<pre><code tabindex="0" class="language-css">.box-standard {
    color: blue;
    border: 2px solid blue;
    border-radius: 5px;
    padding: 20px;
    font-family: Helvetica, Arial, sans-serif;
    font-weight: normal;
    font-size: 1rem;
    line-height: 1.4;
}
</code></pre>
<p>Разработчик может применить этот класс к любому числу элементов, и для всех них будет действовать этот стиль. Кроме того, другие элементы могут использовать не весь набор стилей, а переопределять конкретные свойства по мере необходимости, объявив отдельный класс ниже в коде CSS. Поскольку у обоих классов один и тот же уровень специфичности, более позднее определение получит приоритет.</p>
<pre><code tabindex="0" class="language-css">    .box-special {
        color: red;
        border-color: red;
        font-weight: bold;
    }
</code></pre>
<pre><code tabindex="0" class="language-html">&lt;div class=&quot;box-standard box-special&quot;&gt;
    Особенный квадратик!
&lt;/div&gt;
</code></pre>
<p>Конечно, всем хочется уменьшить дублирование кода, но если писать классы таким образом, разметка оказывается навсегда привязанной к присутствию обоих этих классов, ни один из которых не передает какое-то особенное семантическое значение.</p>
<p>Что действительно нужно здесь разработчику — это комбинация примесей и расширений, которые существуют в различных CSS-препроцессорах: например, <a href="http://lesscss.org/">Less</a> и <a href="https://sass-lang.com/">Sass</a>. Набор связанных свойств можно сгруппировать как <a href="https://sass-lang.com/documentation/file.SASS_REFERENCE.html#mixins">примесь</a> или <a href="https://sass-lang.com/documentation/file.SASS_REFERENCE.html#placeholders">шаблон @extend</a>, которым можно без проблем дать полностью презентационное название: он не проявляется нигде в сгенерированном коде (если вы не захотите, конечно — например, для отладки) и может быть встроен в любой CSS-селектор. Вот простой пример примеси в <a href="https://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html">синтаксисе SCSS</a>:</p>
<pre><code tabindex="0" class="language-scss">@mixin news-item($color) {
    border: 2px solid $color;
    border-radius: 5px;
    padding: 20px;
    font-family: Helvetica, Arial, sans-serif;
    font-weight: normal;
    font-size: 1rem;
    line-height: 1.4;
}

div.news {
    @include news-item(blue);
}

div.breaking {
    @include news-item(red);
    font-weight: bold;
}
</code></pre>
<pre><code tabindex="0" class="language-html">&lt;div class=&quot;news&quot;&gt;
    Вот это новость!
&lt;/div&gt;
&lt;div class=&quot;breaking&quot;&gt;
    Вот это важная новость!
&lt;/div&gt;
</code></pre>
<p>Изменения в определениях стиля в примеси обязательно дойдут до селектора <code>div.breaking</code>, а разметка стала настолько гибко связанной, насколько вообще реально. Конечно, возможности писать примеси напрямую в CSS очень не хватает (к счастью, наконец она проходит <a href="https://lists.w3.org/Archives/Public/www-style/2011Mar/0478.html">процесс формализации</a>), но если вы профессионально пишете CSS, нет никаких причин не использовать CSS-препроцессор.</p>
<p>Эта гибкая связь (она же <a href="https://en.wikipedia.org/wiki/Separation_of_concerns">разделение ответственности</a>) невероятно важна для будущей поддержки проекта: если разметка и CSS будут связаны слишком плотно, это может увеличить стоимость будущих изменений. В раннюю пору жизни CSS обилие WYSIWYG-инструментов для веб-разработки (Dreamweaver, Frontpage и т.п.) привело к большому количеству сайтов, код которых выглядел как-то так:</p>
<pre><code tabindex="0" class="language-html">&lt;span style=&quot;display: block; font-family: Arial, sans-serif; font-size: 11px; color: blue; border-style: solid; border-color: blue; border-width: 2px; padding: 20px;&quot;&gt;
    Квадратик!
&lt;/span&gt;
&lt;span style=&quot;display: block; font-family: Arial, sans-serif; font-size: 11px; color: blue; border-style: solid; border-color: blue; border-width: 2px; padding: 20px;&quot;&gt;
    Ещё квадратик.
&lt;/span&gt;
&lt;span style=&quot;display: block; font-family: Arial, sans-serif; font-size: 11px; color: blue; border-style: solid; border-color: blue; border-width: 2px; padding: 20px;&quot;&gt;
    И еще квадратик. Однако, тенденция?
&lt;/span&gt;
</code></pre>
<p>Эта разметка с построчными стилями не сильно продвинулась по сравнению с «супом тегов», который отравлял жизнь вебу в конце 90-х. Когда нужно было изменить дизайн, приходилось отслеживать и заменять все вхождения этих внутренних стилей. Мы выбрались из этого ада для разработчиков, но меня очень печалит, когда я вижу, что уроки тех дней оказались забыты — и мы по-прежнему встречаем примерно такой код:</p>
<pre><code tabindex="0" class="language-html">&lt;span class=&quot;display-block blue-box font-arial color-blue solid-blue-border padding-20&quot;&gt;
    Танцуем, как в 90-е!
&lt;/span&gt;
&lt;span class=&quot;display-block blue-box font-arial color-blue solid-blue-border padding-20&quot;&gt;
    Давно не заходили на K10K.net?
&lt;/span&gt;
&lt;span class=&quot;display-block blue-box font-arial color-blue solid-blue-border padding-20&quot;&gt;
    Аааааааа!
&lt;/span&gt;
</code></pre>
<p>Может быть, это и экстремальный пример, но по сути это логический апофеоз методологии CSS-фреймворков, которая в погоне за «модульностью» отказывается от семантических имен классов и селекторов. Да, нам удается избежать дублирования кода внутри CSS, но дублирование переносится в разметку, и в процессе случаются побочные эффекты, обусловленные чересчур тесной связью элементов.</p>
<p>Еще одна цель этой методологии — обеспечить высокую производительность. Это зависит от многих факторов: какой клиент заходит на веб-страницу, какая у него скорость соединения, был ли контент закэширован и т.п. На базовом уровне сильнее всего влияют на производительность CSS три фактора:</p>
<ul>
<li>Количество HTTP-запросов</li>
<li>Статус кэширования</li>
<li>Размер документа</li>
</ul>
<p>Первые два фактора, как считается, выходят за рамки методологии CSS-фреймворка, а вот третья проблема решается за счет повторного использования имен классов: смысл в том, что если объявить как можно меньше свойств стилей, CSS будет настолько маленьким, насколько возможно.</p>
<p>Однако отдавать на выходе такой CSS-документ не вполне верно: если вы используете маленькие селекторы с меньшим количеством правил, а привязки в большей степени выводите в разметку (с большим количеством более длинных имен классов) — то количество передаваемых данных не идет вниз, а наоборот смещается из CSS-файла (который обычно агрессивно кэшируется и отдается через CDN) внутрь документа, который, как правило, не кэшируется совсем.</p>
<p>Сторонники методологий CSS-фреймворков часто говорят, что если оставлять селекторы короткими, это хорошо влияет на производительность. <a href="http://calendar.perfplanet.com/2011/css-selector-performance-has-changed-for-the-better/">Этот тезис давно опровергнут</a>: современные движки браузеров достаточно эффективны, и практически во всех сложных случаях (за исключением разве что самых экстремальных) выигрыш в скорости от переписывания селекторов пренебрежительно мал. Хотя фронтенд-производительность при этом безусловно важно иметь в виду:</p>
<blockquote>
    <p>Чего многие люди, кажется, не понимают (или просто не говорят об этом) — это того, что у высокой производительности есть вполне реальная и ощутимая цена — бо́льшие проблемы с поддержкой.</p>
    <footer>
        <cite>
            Нильс Маттейс
        </cite>
    </footer>
</blockquote>
<p>Наиболее серьезные проблемы с методологией CSS-фреймворка возникают тогда, когда вам нужно вернуться к старому коду, или когда к проекту присоединяется новый член команды. В первую очередь, это следствие неочевидности подобных подходов. В своей статье о фронтенд-архитектуре Николас Галлахер говорит: «Имена классов должны передавать разработчикам полезную информацию». Я бы пошел дальше: <em>селекторы</em> должны передавать разработчикам полезную информацию. Смысл здесь тот же самый — <em>объясните мне, где используется это правило</em> — но селектор гораздо более информативен для разработчика, потому что он передает <em>контекст.</em> Гораздо проще понять принцип, лежащий за правилом, если селектор подсказывает вам, что он применяется только к элементам <em>внутри каких-то рамок.</em> Например, сравните два возможных способа применить стили к ссылке внутри списка имен членов общества:</p>
<pre><code tabindex="0" class="language-css">.list-link {
    font-weight: bold;
    text-decoration: none;
}
</code></pre>
<p>или:</p>
<pre><code tabindex="0" class="language-css">ul.members li a {
    font-weight: bold;
    text-decoration: none;
}
</code></pre>
<p>Второй пример гораздо быстрее будет понят кем-то, кто не знаком с кодом: будь это потому, что они никогда над ним раньше не работали или потому, что давно к нему не притрагивались. По самой своей природе он более очевиден: новый разработчик в принципе не может знать, есть ли уже в коде какие-то подходящие правила, не прочитав (и не запомнив) всю базу кода по проекту. Это нереалистичное и непрактичное ожидание от разработчика — и со всей вероятностью разработчик просто создаст еще одно правило, которое делает то же самое: тем самым смысл сокращения повторов будет полностью утерян.</p>
<blockquote>
    <p>Красивый код правилен не только в том смысле, что не содержит ошибок — но и в плане прозрачности. Он не просто передает алгоритм компьютеру, но еще дает понимание человеку, который его читает. Делая наш код более красивым, мы его улучшаем. Научиться писать прозрачный код — это первый из многих шагов к пониманию того, как писать красивый код. А когда мы делаем код очевидным, мы учимся тому, как делать его прозрачным. Красивый код одновременно прозрачный и очевидный.</p>
    <footer>
        <cite>
            Эрик Рэймонд
        </cite>
    </footer>
</blockquote>
<p>Таким образом, из-за проблем с очевидностью фреймворк-методология закладывает семена для разбухания кода продукта. Если мы пишем специфические селекторы, мы даем разработчикам, помимо всего прочего, информацию для определения, необходимы они или нет. Вернемся к примеру выше: если список членов общества удаляется с сайта, то весь блок CSS можно удалить, в то время как гибкие селекторы, основанные на именах классов, могут теоретически использоваться где угодно, и без более серьезных усилий по очистке может быть неясно сразу, можно ли удалить правило, ничего не сломав, или нет.</p>
<p>Я оставлю последнее слово о производительности за Мэттом Уилкоксом, который безупречно выражает свою мысль:</p>
<blockquote>
<p>Производительность — проблема на уровне браузера, а не что-то, чем должны заниматься (или обходить стороной) разработчики HTML и CSS. Если мы пишем валидный и вменяемый HTML или CSS, мы не должны опускаться до смехотворных правил только для того, чтобы увеличить скорость, с которой загружается данная конкретная страница. И мы действительно не должны: ведь с каждым релизом браузеры становятся быстрее. О производительности стоит больше всего волноваться тем ребятам, которые занимаются JS. А если вы хотите оптимизировать свою связку HTML и CSS — делайте это в соответствии с передовыми практиками, а не для лучшей производительности. «Плохая» производительность починится сама, когда выйдут новые браузеры, а низкокачественный, непонятный и сложный для поддержки код — нет.</p>
</blockquote>
<h2>Что же мы можем сделать?</h2>
<p>Фронтенд-разработчики тихо и с успехом в течение многих лет разрабатывали крупные веб-проекты <em>без</em> использования методологий CSS-фреймворков. Тот простой факт, что эти крупные проекты обошлись без использования OOCSS, БЭМ и других методологий, показывает, что можно вполне эффективно работать и без них. Вот несколько рекомендаций от людей, с которыми я обсуждал написание CSS, а также — несколько предложений, основанных на моем личном опыте.</p>
<p>Ради всего святого, используйте <code>id</code>!</p>
<p>В ряде методологий CSS-фреймворков <a href="http://csslint.net/about.html">считается, что использовать <code>id</code> в CSS</a> — плохая идея. Объяснение этого следующее: <code>id</code> обладают большой специфичностью, поэтому переопределить правила, описанные для <code>id</code>, можно только с помощью (презираемого) модификатора <code>!important</code>. Я абсолютно не согласен с этим: <a href="http://javascriptweblog.wordpress.com/2011/02/07/truth-equality-and-javascript/">цитируя Ангуса Кролла</a>, «стратегия избегания беспокоит меня тем, что нельзя освоить язык, не зная его с ног до головы, а страх и уклонение — враги знания». Если уж вы профессиональный веб-разработчик, вы должны понимать, как работает специфичность и как она влияет на селекторы, которые вы пишете. Кроме того, <code>id</code> — совершенно полноправная часть структуры документа, и у них есть <a href="http://www.webdirections.org/blog/in-defense-of-the-humble-id-attribute/">очень полезные функциональные и семантические свойства</a>.</p>
<p>Пишите селекторы так, чтобы они были настолько специфическими и настолько описательными, насколько это нужно: ни больше, ни меньше.</p>
<p>Если вы пишете CSS для элемента, который может появляться где угодно в документе, используйте соответствующий селектор. Если же этот CSS применяется только в очень специфических условиях, сделайте так, чтобы написанный вами селектор отражал именно это. Во всяком случае, не делайте ваши селекторы настолько краткими, что люди, которые будут читать ваш код через полгода, вообще не поймут, зачем они нужны.</p>
<p>Не существует модулей для повторного использования (до того, как они действительно не станут повторно использоваться); делайте рефакторинг, переписывайте, выбрасывайте лишний код каждый день.</p>
<p>Страх написать слишком специфичный CSS по сути является преждевременной оптимизацией. Пишите ровно тот CSS, который требуется для наличествующей задачи: когда станет очевидно, что его нужно использовать в другом месте, сделайте рефакторинг и разбейте его на примеси — но только в этот момент, ни минутой раньше! Может быть, на следующей неделе ваши приоритеты изменятся, и всю эту функцию вообще придется выбросить? Вы совсем не хотите, чтобы через полгода вы оказались лицом к лицу с пачкой ненужных селекторов из имен классов, смысла которых никто не понимает, равно как и того, что случится, если их удалить.</p>
<p>Много файлов и процесс сборки.</p>
<p>Черт возьми, вы же профессиональный веб-разработчик. Вы не пишете код в «Блокноте» — нет никакого повода не использовать в своей работе лучшие из существующих инструментов. На каком бы фреймворке или платформе вы ни разрабатывали, всегда есть возможность пользоваться CSS-препроцессорами! Есть много <a href="https://github.com/alphagov/govuk_frontend_toolkit">отличных примеров</a> эффективного повторного использования CSS в разных проектах. Почитайте и посмотрите, как это делают другие люди — расширяйте свои знания.</p>
<p>Разрабатывайте статические прототипы. Делайте скриншоты всего, чего можно.</p>
<p>Сайты и приложения обычно формируются из набора общих шаблонов: форма с подписями и полями ввода, список ссылок внутри элемента <code>&lt;nav&gt;</code>, список описаний и значений и т.п. Когда вы беретесь разрабатывать какой-то новый элемент функциональности, начните с того, чтобы сделать разметку, которая адекватно описывает нужную вам структуру как статический HTML. Это послужит вам не только как предмет для будущего тестирования, не ломается ли ваш код, но и для того, чтобы новые члены команды сразу видели, что уже есть и чем можно пользоваться. Если делать скриншоты этих страниц или всего приложения (вручную или с помощью автоматического набора тестов), это может быть полезным для определения ошибок с помощью методики постоянной интеграции.</p>
<p>Сделать хорошую архитектуру CSS в комплексном веб-приложении непросто (и, в конце концов, именно поэтому на хороших фронтенд-разработчиков такой спрос), но это не значит, что мы должны осложнять жизнь новичкам и себе самим, накладывая на себя обязанность подчиняться произвольным «правилам», которые в конце концов приносят с собой лишнее усложнение и увеличение трудозатрат на поддержку.</p>
<p>Анализируя цель, которой служит ваш продукт, вы сможете принять более верные решения относительно того, как структурировать все его аспекты, а не только внешний вид. Веб — это семантическая среда: следуйте этому принципу.</p>
<blockquote>
    <p>Вот всепроникающий закон всех вещей: органических и неорганических, физических и метафизических, человеческих и сверхчеловеческих, всех истинных проявлений ума, сердца, души — жизнь узнается по тому, как она выражена, форма всегда следует за функцией. Это закон.</p>
    <footer>
        <cite>
            Луис Салливен
        </cite>
    </footer>
</blockquote>
<h2>Благодарности</h2>
<p>Пока я писал эту статью, мне очень помогали своим советом <a href="https://twitter.com/cackhanded">Марк Норман Фрэнсис</a>, <a href="https://twitter.com/intranation">Брэд Райт</a>, <a href="https://twitter.com/rossbruniges">Росс Бранигес</a>, <a href="https://twitter.com/jaffathecake">Джейк Арчибальд</a> и <a href="https://twitter.com/ptg">Патрик Гриффитс</a>.</p>
<h2>Ссылки</h2>
<ul>
<li><a href="http://martinfowler.com/bliki/TwoHardThings.html">Две сложности</a></li>
<li><a href="https://www.w3.org/TR/html5/dom.html#classes">W3C: Семантика, структура и API HTML-документов</a></li>
<li><a href="http://tantek.com/2012/353/b1/why-html-classes-css-class-selectors">Почему правильно говорить: «HTML-классы», «CSS-селекторы класса» и «CSS-псевдоклассы», а «CSS-классы» — нет</a></li>
<li><a href="http://nicolasgallagher.com/about-html-semantics-front-end-architecture/">О семантике HTML и фронтенд-архитектуре</a></li>
<li><a href="http://www.onderhond.com/blog/work/cost-of-performance-css-selector-rewriting">Цена производительности</a></li>
<li><a href="http://www.faqs.org/docs/artu/transparencychapter.html">Искусство программирования в Unix</a></li>
<li><a href="http://2002-2012.mattwilcox.net/archive/entry/id/1054/">CSS Lint — зло</a></li>
<li><a href="https://en.wikipedia.org/wiki/Form_follows_function">Wikipedia: форма следует за функцией</a></li>
</ul>

                    ]]></description><pubDate>Wed, 19 Feb 2014 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/cargo-cult-css/</guid></item><item><title>Я знаю jQuery. И что?</title><link>https://web-standards.ru/articles/jquery-now-what/</link><description><![CDATA[
                        <p>На конференции jQuery UK 2013 я делал доклад под названием «<a href="https://speakerdeck.com/rem/i-know-jquery-now-what">Я знаю jQuery. И что?</a>». Обычно я готовлюсь, устраивая взрыв из стикеров на своём столе, но в этот раз я сперва написал пост, а потом уже сделал слайды. Итак, вот мой практически не отредактированный и немного путаный рассказ о том, как я работал с jQuery, и как я смотрю на использование встроенных браузерных технологий.</p>
<h2>7 лет назад…</h2>
<p>17 июня 2006 года я опубликовал мой самый первый настоящий <a href="http://web.archive.org/web/20061018170852/http://leftlogic.com/info/articles/auto-selecting_navigation">пост в блоге</a>: я взял обычный JavaScript и упростил его с помощью jQuery. В итоге 14 строчек JavaScript превратились в три строчки на jQuery (версии pre-1.0).</p>
<p>Самый важный эффект от jQuery был в том, что вместо муторной навигации по DOM вы писали простой CSS-селектор, после чего к нужному элементу добавлялся класс. К тому же, исходный JavaScript-код был довольно нестабильным, а разметка у вас получалась одна и та же в обоих случаях.</p>
<p>Я показал jQuery команде разработчиков, с которыми я сотрудничал в середине 2000-х, и даже дизайнеры увидели все плюсы этого подхода, поскольку CSS-селекторы были им уже знакомы (именно так и возникла идея «jQuery для дизайнеров»).</p>
<h3>Внезапно навигация по DOM стала простой</h3>
<p>В те времена навигация по DOM была очень сложной. Можно было поспорить — если у вас получалось что-то сделать в Firefox 1.5, то в IE6 это не работало.</p>
<p>Простота, с которой можно было изучить jQuery, стала для меня плюсом. Вся навигация по DOM делалась с помощью CSS-селекторов (реализовано это было какой-то безумной магией из «черного ящика», которую придумал Джон Резиг) — главное, что это экономило мои ограниченные мыслительные ресурсы, и, когда я получал нужные мне элементы DOM, я уже мог делать с ними все что угодно (показывать, скрывать, анимировать и т.п.)</p>
<h3>Понимание Ajax</h3>
<p>jQuery также поставила на доступный мне уровень абстракцию Ajax. Этот термин был придуман буквально только что, в 2005 году, и документации по технологии было мало, понять её было непросто (не забывайте про ограниченные вычислительные способности моего мозга).</p>
<p>Итак, мне нужно было работать с объектом <code>XMLHttpRequest</code>. Когда я увидел его впервые, мне стоило усилий понять, как работает событие <code>onreadystatechange</code> и пара <code>this.status</code> и <code>this.readyState</code>. jQuery, как и ряд других библиотек, разобралась с тем ужасом, который представляли собой XHR-запросы в IE через ActiveX…</p>
<pre><code tabindex="0" class="language-js">function getXmlHttpRequest() {
    var xhr;
        if (window.XMLHttpRequest) {
            xhr = new XMLHttpRequest();
    } else {
            try {
                xhr = new ActiveXObject(&quot;Msxml2.XMLHTTP&quot;);
        } catch (e) {
            try {
                xhr = new ActiveXObject(&quot;Microsoft.XMLHTTP&quot;);
        } catch (e) {
                xhr = false;
        }
    }
}
    return xhr;
}
// Код, который Джон написал в jQuery,
// куда более элегантен!
</code></pre>
<p>Когда я увидел, что в jQuery можно использовать функцию <code>ajax</code>, для того чтобы выцепить HTML по какому-то адресу (именно это мы обычно и хотели делать с помощью Ajax), вся технология внезапно стала для меня понятной.</p>
<p>jQuery сразу и надолго стала моим обычным инструментом. Это был мой «швейцарский нож», если позаимствовать <a href="http://events.jquery.org/2013/uk/schedule.html#adam">название доклада Адама</a>!</p>
<h2>Назад в будущее: сегодня</h2>
<p>Давайте промотаем ленту вперед и вернемся в сегодняшний день. Что случилось за эти годы?</p>
<p>Для начала: моя позиция по умолчанию — это не «всегда подключай jQuery». Я лучше знаю JavaScript, и как в нём все работает. У меня появились собственные критерии, когда нужно подключать jQuery, а когда нет. Но если я не подключаю jQuery, что тогда?</p>
<p>За эти семь лет произошло довольно многое. Вероятно, одним из самых важных шагов вперед стало появление в браузерах <code>querySelectorAll</code>.</p>
<p>Возможность передать встроенной функции внутри браузера CSS-селектор, чтобы сам браузер работал над навигацией по DOM — это огромная (правда!) часть jQuery. Базовая поддержка была в Chrome с самого начала, в IE8 и Firefox 3.5 появилась в середине 2009 года.</p>
<p>Эндрю Ланни (из PhoneGap и Adobe) написал невероятно простую функцию:</p>
<pre><code tabindex="0" class="language-js">var $ = document.querySelectorAll.bind(document);
Element.prototype.on = Element.prototype.addEventListener;

$('#somelink')[0].on('touchstart', handleTouch);
</code></pre>
<p>Это просто и прекрасно.</p>
<p>Я взял его идею, немного развил ее и использовал в ряде довольно специфических проектов, добавив поддержку для чейнинга, циклов и упростив синтаксис. В сжатом виде все занимает меньше 200 байт. Смысл этого в том, что сейчас у нас есть встроенная в браузеры поддержка для ряда функций, и я стараюсь принимать во внимание аудиторию своего проекта, перед тем как по умолчанию подключать jQuery.</p>
<h2>В каких случаях я всегда использую jQuery</h2>
<p>Прежде чем я расскажу о том, как я могу обходиться без jQuery, быть «нагим» — давайте я расскажу о случаях, когда я точно включаю jQuery в проект. Есть несколько довольно специфических причин, которые заставляют меня либо начинать прямо с jQuery, либо переключаться на нее с какого-то специально написанного решения.</p>
<p>Перед этим я должен оговориться относительно случая, когда я абсолютно точно не использую jQuery: если я пытаюсь воспроизвести баг в браузере, я никогда не использую библиотеку. Если вы пытаетесь найти баг, чтобы можно было сообщить о проблеме, необходимо, чтобы в примере было как можно меньше кода (конечно, кроме тех случаев, когда вы отправляете сообщение об ошибке внутри jQuery!).</p>
<h3>1. Когда проект должен работать в устаревших браузерах</h3>
<p>BBC достаточно четко озвучили, <a href="http://responsivenews.co.uk/post/18948466399/cutting-the-mustard">что именно они называют современным браузером</a>, и по некотором размышлении это и есть тот признак, по которому я решаю, включать jQuery по умолчанию или нет.</p>
<p>Если я знаю, что я должен работать с несовременными браузерами, и они составляют часть ядра аудитории, то я начну с jQuery внутри своего кода.</p>
<p>Что значит «современный»? По большому счету, ответ простой: поддерживает ли браузер <code>querySelectorAll</code>? BBC применяет следующий тест на соответствие требованию современности:</p>
<pre><code tabindex="0" class="language-js">if (querySelector in document &amp;&amp;
    localStorage in window &amp;&amp;
    addEventListener in window) {
    // Загружаем JavaScript-приложение
}
</code></pre>
<p>Я знаю наизусть, что IE8 не поддерживает <code>addEventListener</code> (<a href="https://gist.github.com/eirikbacker/2864711">хотя и существует полифил</a>), так что, если поддержка этого браузера важна для проекта, я понимаю, что не хочу начинать проект с хаков для IE8.</p>
<p>Не то чтобы я хочу сказать, что те проекты, которые я начинаю без jQuery, не будут поддерживать IE8. Скорее — что нужно начинать с малого и делать разработку простой с самого начала. Если я начну проект с охапки хаков — проблем не оберёшься.</p>
<p>И еще я считаю это тем случаем, «когда сложность перевешивает простоту».</p>
<h3>2. Когда я делаю что-то дешево и сердито</h3>
<p>Если я создаю какой-то концепт, тестирую идею или просто что-то набрасываю и отправляю в <a href="https://jsbin.com">JS Bin</a>, обычно я просто добавляю jQuery по умолчанию. Так мне не приходится лишний раз думать.</p>
<h2>Без jQuery!</h2>
<p>Наверное, вы думаете: «Так, Реми использует jQuery, а если нет, то просто переписывает все фичи сам?»</p>
<p>Я совершенно не хочу изобретать велосипед. Если я обнаруживаю, что, разрабатывая без jQuery, я в итоге сам переписываю с нуля ее функциональность, тогда, ясное дело, я просто трачу свое время впустую.</p>
<p>Нет, всё не так. Просто есть довольно много сценариев, в которых я буду писать код своего приложения без библиотеки, опираясь на встроенные в браузер технологии. Если какая-то часть этих технологий не поддерживается в том или ином браузере, я могу прибегнуть к <a href="https://remysharp.com/2010/10/08/what-is-a-polyfill/">полифилам</a> — но только после тщательного рассмотрения и понимания, что это имеет смысл.</p>
<p>Итак, как я живу без jQuery, и насколько полной можно считать поддержку нужных технологий в браузерах?</p>
<h2><code>document.ready</code></h2>
<p>Даже когда я использую jQuery, если у меня (или моей компании) есть контроль над проектом, я очень редко использую <code>document.ready</code> (или его короткую версию: <code>$(function)</code>).</p>
<p>Дело в том, что весь JavaScript я размещаю под всем DOM, перед тегом <code>&lt;/body&gt;</code>. Так я всегда уверен, что в этот момент весь DOM уже будет обработан браузером.</p>
<p>Надеюсь, что вы это и так знаете, но JavaScript блокирует рендеринг страницы. Если вы разместите JavaScript над содержимым, и ваш сервер подвиснет — вы получите пустую страницу. Я много раз уже использовал этот пример, но повторюсь, что раньше (довольно давно) виджет Twitter просто вставлялся в HTML вашей страницы. Их сайт частенько падал, и мой блог (с этим виджетом) зависал на пустой странице — так что выглядело все так, будто упал мой сайт.</p>
<h2><code>.attr('value')</code> и <code>.attr('href')</code></h2>
<p>Мне всегда становится грустно, когда я вижу, как jQuery используется для того, чтобы получить значение элемента <code>&lt;input&gt;</code>:</p>
<pre><code tabindex="0" class="language-js">$('input').on('change', function () {
    var value = $(this).attr('value');
    alert('The new value is' + value);
});
</code></pre>
<p>Почему? Потому что всегда можно получить значение элемента с помощью <code>this.value</code>. Что важнее — нужно думать о том, как вы используете JavaScript-библиотеку. Не применяйте jQuery без необходимости.</p>
<p>Дело здесь не в jQuery. Это просто нормальная практика. Нужно, чтобы в коде просто было написано:</p>
<pre><code tabindex="0" class="language-js">$('input').on('change', function () {
    alert('The new value is' + this.value);
});
</code></pre>
<p>Еще люди довольно часто используют jQuery для того, чтобы получить <code>href</code> ссылки: <code>$(this).attr('href')</code>, но можно вполне легко получить путь и из DOM: <code>this.href</code>. Обратите, правда, внимание, что <code>this.href</code> несколько отличается: это абсолютный путь, поскольку мы здесь говорим про DOM API, а не сам элемент. Если вы хотите получить значение атрибута (как это работает в случае jQuery), вы можете использовать <code>this.getAttribute('href')</code>.</p>
<p>Ещё есть сценарий, в котором вы устанавливаете класс для элемента, и здесь вам тоже не нужна jQuery, если вы просто добавите класс. Сколько раз я видел:</p>
<pre><code tabindex="0" class="language-html">    &lt;script src=&quot;http://code.jquery.com/jquery.min.js&quot;&gt;&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;script&gt;
        $('body').addClass('hasJS');
    &lt;/script&gt;
</code></pre>
<p>Но зачем, когда можно так?</p>
<pre><code tabindex="0" class="language-html">&lt;/head&gt;
&lt;body&gt;
    &lt;script&gt;
        document.body.className = 'hasJS';
    &lt;/script&gt;
</code></pre>
<p>Пожалуй, самое главное различие между этими двумя примерами состоит в том, что мы не подключаем jQuery только затем, чтобы установить класс для <code>&lt;body&gt;</code>, определяющий доступность JavaScript.</p>
<p>Если у <code>&lt;body&gt;</code> уже может быть какой-нибудь класс, то просто припишите новый к строке (jQuery тоже нужно обращаться к свойству <code>className</code>): <code>document.body.className += ' hasJS'</code>.</p>
<p>Здесь мы начинаем натыкаться на проблемы с именами классов и отслеживанием того, у каких элементов какой класс есть, а какого нет. Но в браузерах есть и такая функциональность.</p>
<h2>classList — добавляем, удаляем, переключаем</h2>
<p>Свойство <code>classList</code> из спецификации HTML5 поддерживается всеми последними версиями браузеров (кроме IE9 — но в этом случае я могу использовать полифил).</p>
<p>Вместо:</p>
<pre><code tabindex="0" class="language-js">$('body').addClass('hasJS');
// или
document.body.className += ' hasJS';
</code></pre>
<p>Можно написать:</p>
<pre><code tabindex="0" class="language-js">document.body.classList.add('hasJS');
</code></pre>
<p>Красиво, не правда ли? А удалять?</p>
<pre><code tabindex="0" class="language-js">$('body').removeClass('hasJS');
// или какое-нибудь безумное регулярное выражение
</code></pre>
<p>Или можно сделать так:</p>
<pre><code tabindex="0" class="language-js">document.body.classList.remove('hasJS');
</code></pre>
<p>Но больше впечатляет встроенная поддержка переключения классов:</p>
<pre><code tabindex="0" class="language-js">document.body.classList.toggle('hasJS');
// и
document.body.classList.contains('hasJS');
</code></pre>
<p>Для добавления нескольких классов нужно добавить их как аргументы через запятую:</p>
<pre><code tabindex="0" class="language-js">document.body.classList.add('hasJS', 'ready');
</code></pre>
<p>Есть, конечно, некоторые проблемы, вроде этой, с пустой строкой:</p>
<pre><code tabindex="0" class="language-js">document.body.classList.contains('');
// SyntaxError: DOM Exception 12
</code></pre>
<p>Ужасно! Но, с другой стороны, я знаю проблемные места и обхожу их стороной. В принципе, мы выросли, работая с браузерами именно по таким принципам.</p>
<h2>Хранение данных</h2>
<p>Хранение произвольных данных в элементах появилось в jQuery в версии 1.2.3, а хранение объектов — в 1.4, то есть уже довольно давно.</p>
<p>В HTML5 есть встроенное хранение данных внутри элементов, но между jQuery и встроенной поддержкой есть фундаментальная разница: <code>dataset</code> в HTML5 не поддерживает хранение объектов.</p>
<p>Но если вы храните строки или JSON, тогда встроенная поддержка работает идеально:</p>
<pre><code tabindex="0" class="language-js">element.dataset.user = JSON.stringify(user);
element.dataset.score = score;
</code></pre>
<p>К сожалению, встроенной поддержки нет в IE10 (конечно, можно добавить полифил, и все прекрасно заработает — но это нужно принимать во внимание при использовании <code>dataset</code>).</p>
<h2>Ajax</h2>
<p>Как я уже говорил, jQuery помогла мне понять Ajax в полной мере. Сейчас Ajax — это довольно просто. Конечно, у меня нет всяких дополнительных опций, но, по большей части я просто выполняю XHR GET или POST-запросы с JSON.</p>
<pre><code tabindex="0" class="language-js">function request(type, url, opts, callback) {
    var xhr = new XMLHttpRequest(),
        fd;

    if (typeof opts === 'function') {
        callback = opts;
        opts = null;
    }

    xhr.open(type, url);

    if (type === 'POST' &amp;&amp; opts) {
        fd = new FormData();

        for (var key in opts) {
            fd.append(key, JSON.stringify(opts[key]));
        }
    }

    xhr.onload = function () {
        callback(JSON.parse(xhr.response));
    };

    xhr.send(opts ? fd : null);
}

var get = request.bind(this, 'GET');
var post = request.bind(this, 'POST');
</code></pre>
<p>Коротко и просто. XHR — это совсем не сложно, а сейчас есть и хорошая документация. Понимание того, как XHR на самом деле работает и что с его помощью можно сделать, дает нам больше возможностей.</p>
<p>Как насчет событий прогресса? Событий, привязанных к прогрессу загрузки? Что насчёт отправки <code>ArrayBuffer</code>? А если нужно разбираться с <a href="https://ru.wikipedia.org/wiki/Cross-origin_resource_sharing">CORS</a> и заголовком <code>xml-requested-with</code>?</p>
<p>Для этого вам понадобится прямой доступ к объекту XHR (я знаю, что это можно получить и из jQuery), и вам нужно знать, как устроен XHR и что с ним можно делать, потому что такие вещи, как, например, загрузку файлов через перетаскивание сейчас безумно просто реализовать с помощью встроенной функциональности.</p>
<h2>Наконец-то формы!</h2>
<p>jQuery-плагин для валидации форм был стабильным плагином с первых дней jQuery, и честно сделал работу с формами намного проще.</p>
<p>Но вне зависимости от валидации на стороне клиента все равно нужно проводить валидацию на стороне сервера — это необходимо в любом случае, какую бы валидацию вы ни делали.</p>
<p>Но что, если можно было бы выбросить кучу строк JavaScript и плагинов и валидировать эл. адрес как-то так:</p>
<pre><code tabindex="0" class="language-html">&lt;input type=&quot;email&quot;&gt;
</code></pre>
<p>Хотите сделать его обязательным полем?</p>
<pre><code tabindex="0" class="language-html">&lt;input type=&quot;email&quot; required&gt;
</code></pre>
<p>Хотите разрешать пользователю вводить только определенные символы?</p>
<pre><code tabindex="0" class="language-html">&lt;input pattern=&quot;a-z0-9&quot;&gt;
</code></pre>
<p>Неплохо. Здесь даже есть поддержка вспомогательных технологий — например, клавиатура на мобильных устройствах адаптируется и будет выводить символы для эл. адреса.</p>
<p>Так как все эти типы полей при отсутствии в браузере поддержки просто становятся текстовыми полями, и раз уж вам все равно нужно делать валидацию на сервере, я бы на вашем месте выкинул всю JavaScript-валидацию и заменил ее на встроенную в браузеры валидацию HTML5-форм.</p>
<h2>jQuery-анимации против CSS-анимаций и JavaScript-анимаций</h2>
<p>На самом деле это никакое не соревнование. CSS выигрывает. Анимации, для которых используется CSS, вычисляются на видеокарте. В анимациях на JavaScript добавляется еще один уровень расчетов — просто потому, что там есть JavaScript.</p>
<p>Даже в том случае, когда я пишу код сам, я выберу <code>requestAnimationFrame</code> вместо использования анимаций, основанных на <code>setInterval</code>.</p>
<p>Джейк Арчибальд подготовил отличные слайды, которые показывают проблему — <code>setInterval</code> не сделает анимацию плавной, и достаточно скоро начнет пропускать кадры:</p>
<figure>
    <img src="https://web-standards.ru/articles/jquery-now-what/images/set-timeout.jpg" alt="">
    <img src="https://web-standards.ru/articles/jquery-now-what/images/request-animation-frame.jpg" alt="">
</figure>
<p>Кроме того, CSS-анимации проходят через тот же таймер, что и <code>requestAnimationFrame</code> — его мы и хотим использовать.</p>
<p>Так что, если ваш браузер это позволяет, используйте CSS-анимации. Конечно, это посложнее, чем <code>$foo.animate('slow', { x: '+=10px' })</code>, но зато анимация будет чище и плавнее. Стоит знать, что трогать DOM — дорогая операция. Если вы анимируете положение элемента по оси абсцисс, обновляя атрибут <code>el.style.left</code>, вы постоянно читаете и пишете в DOM.</p>
<p>А вот если вы просто сделаете <code>foo.classList.add('animate')</code>, анимация CSS-класса выполнит плавный переход положения левой точки элемента. И если вы точно знаете, что это только значение слева, можно использовать аппаратное ускорение, выполнив <code>translateX</code> с <code>translateZ(0)</code>.</p>
<p>Ну а как же, слышу я ваш крик, как же вызов функции после окончания анимации? Это тоже можно. Хотя синтаксис немножко противный:</p>
<pre><code tabindex="0" class="language-js">el.addEventListener(&quot;webkitTransitionEnd&quot;, transitionEnded);
el.addEventListener(&quot;transitionend&quot;, transitionEnded);
</code></pre>
<p>Обратите внимание, что <code>e</code> в <code>end</code> строчная…</p>
<p>Пара милых людей в Твиттере показали мне <a href="https://github.com/benbarnett/jQuery-Animate-Enhanced">своего рода полифил для jQuery</a>, который дополняет функцию <code>.animate</code> в том случае, если в браузере доступны CSS-анимации.</p>
<p>Еще есть отдельный плагин <a href="http://ricostacruz.com/jquery.transit/">Transit</a>, который дает вам возможность писать CSS-анимации на JavaScript. Приятный момент для меня — поддержка чейнинга. Но так он работает только с CSS-анимациями, для этого требуется IE10 или выше.</p>
<p>Отсюда у меня возникает вопрос: почему этот плагин в обязательном порядке требует jQuery?</p>
<h2>В сторону: jQuery-плагины — просто так</h2>
<p>Я:</p>
<blockquote>
<p>Не знаю почему, но мне очень хочется больно ударить людей, которые пишут такие jQuery-плагины, для которых на самом деле jQuery совершенно не нужна. /требуется-контроль-эмоций</p>
</blockquote>
<p>Ответ:</p>
<blockquote>
<p>@rem У меня то же самое. Я думаю, где-то есть группа, в которой с этим помогают — и, полагаю, довольно большая.</p>
</blockquote>
<p>Я недавно работал над проектом и узнал о <a href="http://fittextjs.com">fitText.js</a>. Я решил включить его в свой код, но потом заметил, что для него <strong>требуется</strong> jQuery.</p>
<p>Хм-м. Зачем?</p>
<p>Этот проект использует следующие методы jQuery:</p>
<ol>
<li><code>.extend</code></li>
<li><code>.each</code></li>
<li><code>.width</code></li>
<li><code>.css</code></li>
<li><code>.on</code> (над производительностью никто особенно не задумывался)</li>
</ol>
<p>Собственно, вот код проекта:</p>
<pre><code tabindex="0" class="language-js">$.fn.fitText = function( kompressor, options ) {
    // Настраиваем
    var compressor = kompressor || 1,
        settings = $.extend({
            'minFontSize' : Number.NEGATIVE_INFINITY,
            'maxFontSize' : Number.POSITIVE_INFINITY
    }, options);

    return this.each(function(){
        // Сохраняем объект
        var $this = $(this);

        // Функция Resizer() изменяет размеры объекта
        // на основе его ширины, поделённой на compressor * 10
        var resizer = function () {
            $this.css('font-size', Math.max(
                Math.min($this.width() / (compressor*10),
                parseFloat(settings.maxFontSize)
            ),
            parseFloat(settings.minFontSize)));
        };

        // Вызываем для установки
        resizer();

        // Вызываем при ресайзе. Opera кеширует вызовы по умолчанию
        $(window).on('resize orientationchange', resizer);
    });
};
</code></pre>
<p><code>.extend</code> используется на объекте, в котором всего две опции, так что я бы переписал его так:</p>
<pre><code tabindex="0" class="language-js">if (options === undefined) options = {};
if (options.minFontSize === undefined) options.minFontSize = Number.NEGATIVE_INFINITY;
if (options.maxFontSize === undefined) options.maxFontSize = Number.POSITIVE_INFINITY;
</code></pre>
<p><code>return this.each</code> используется для того, чтобы итерироваться по элементам. Предположим, что мы хотим, чтобы этот код работал без jQuery: тогда наша функция <code>fitText</code> получит список элементов (так как чейнинга мы делать не будем):</p>
<pre><code tabindex="0" class="language-js">var length = nodes.length,
    i = 0;

// Хотелось бы использовать [].forEach.call, но нет поддержки в IE8
for (; i &lt; length; i++) {
    (function (node) {
        // там, где использовался `this`, теперь `node`
        // …
    })(nodes[i]);
}
</code></pre>
<p><code>$this.width()</code> получает ширину контейнера, чтобы изменять размер текста. Для этого нам нужно получить рассчитанные стили и взять из них значение ширины:</p>
<pre><code tabindex="0" class="language-js">// Функция Resizer() изменяет размеры объекта
// на основе его ширины, поделённой на compressor * 10
var resizer = function () {
    var width = node.clientWidth;
    // …
};
</code></pre>
<p><code>$this.css</code> используется для установки значений, так что тут всего лишь нужно задать стили:</p>
<pre><code tabindex="0" class="language-js">node.style.fontSize = Math.max(…);
</code></pre>
<p><code>$(window).on('resize', resizer)</code> прикрепляет обработчик события (если вы хотите поддержку в IE8, то нужно еще включить <code>addEvent</code>):</p>
<pre><code tabindex="0" class="language-js">window.addEventListener('resize', resizer, false);
</code></pre>
<p>На самом деле, я бы пошел еще дальше и хранил бы функции ресайза в массиве, и во время операции изменения размера проходил бы по массиву, исполняя все эти функции.</p>
<p>Конечно, тут нужно немножко больше работы, но при этом такие изменения довольно легко провести так, чтобы работа в качестве jQuery-плагина была бы для этого проекта дополнительной функциональностью, а не требованием.</p>
<p>Моя тирада скоро закончится: еще меня убивает, когда я вижу полифил, которому требуется jQuery — но я признаю и контраргумент: чрезвычайная распространенность jQuery, наверное, может оправдать то, что столько проектов пишется с зависимостью от нее.</p>
<h2>Заключение</h2>
<p>Моей целью было показать вам, что, хотя jQuery безумно помогала мне все эти годы (особенно годы плохой совместимости между браузерами), и со встроенной функциональностью браузеров можно уйти довольно далеко в плане обычных сценариев — когда я пишу JavaScript для того, чтобы «сделать что-нибудь» в DOM.</p>
<p>Перестаньте думать в парадигме «функция X не работает в браузере Y». Подходите с другой точки зрения. Какую задачу я решаю? Какой инструмент лучше всего подойдет? Для кого это делается? Я по-прежнему верю в методологию прогрессивного улучшения, но я не понимаю удовольствия лезть из кожи вон ради того, чтобы поддерживать воображаемую аудиторию пользователей (по той причине, что у нас нет данных, какие браузеры у наших пользователей).</p>
<p>Google (по моим последним данным) поддерживает последние и предпоследние версии браузеров. Я тоже стараюсь начинать с поддержки этих версий.</p>
<p>Я буду продолжать использовать jQuery так, как мне удобно, и я продолжу убеждать своих читателей и слушателей, что фронтенд-разработчики должны знать, что могут браузеры, с которыми они работают.</p>
<p>Итак, на этом я заканчиваю и надеюсь, что этот текст был вам полезен.</p>
<p>Возможно, некоторые из вас уже знали всё это (правда, в таком случае у меня возникает вопрос, зачем вы это читаете), однако я надеюсь, что хоть кому-то я показал, что за пределами jQuery есть еще целый мир, и вы можете начать осваивать его прямо сейчас в одном из своих проектов.</p>
<p>Может быть, некоторые из вас только знакомятся с jQuery — я надеюсь, что вы станете разбираться и дальше в том, на что способны JavaScript и DOM.</p>
<p>Однако большинству из вас я принёс снег зимой. Вы уже согласны со мной. Вы уже верите в стандарты, делаете все правильно, учитесь и образовываетесь. Но вы должны помочь людям, которые не получают этой информации.</p>
<p>Вам нужно делиться своими ощущениям с другими людьми. Теперь вы — эксперты, и вы должны помочь окружающим достигнуть вашего уровня и превзойти его.</p>
<p>На конференциях будут нужны новые докладчики и новые эксперты: это вы.</p>
<h2>Материалы к статье</h2>
<ul>
<li><a href="https://speakerdeck.com/rem/i-know-jquery-now-what">Слайды презентации</a></li>
<li><a href="http://vimeo.com/68009123">Видео доклада</a></li>
<li><a href="http://vimeo.com/68910118#t=2380">Секция вопросов и ответов с конференции Mobilism</a></li>
<li><a href="https://github.com/remy/min.js">Библиотека min.js</a></li>
</ul>

                    ]]></description><pubDate>Mon, 16 Dec 2013 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/jquery-now-what/</guid></item><item><title>Svable — простая генерация SVG на сервере</title><link>https://web-standards.ru/articles/svable-server-svg/</link><description><![CDATA[
                        <p><em>Мы начинаем серию статей, в которых вы можете рассказать о своём сервисе или разработке, которые могут быть полезны для фронтенд-разработчиков. Есть о чём рассказать? Пишите нам на <a href="https://web-standards.ru/articles/svable-server-svg/mailto:wst@web-standards.ru">wst@web-standards.ru</a>. Редакция.</em></p>
<p>Рассказывать про актуальность векторной графики уже нет никакого смысла — количеством различных размеров, разрешений (а теперь еще и плотностей) экранов веб-разработчика не удивишь. А вот вопрос подготовки и создания изображений для всего этого разнообразия набирает актуальность с каждым днем. В этой статье я хочу рассказать не о том, как научить дизайнера использовать Illustrator, а о том, как облегчить жизнь разработчика при работе с SVG.</p>
<p>В последнее время SVG начинают использовать не только в качестве замены растра для иконок и логотипов начинает, но и для создания сложной графики и динамических диаграмм. За это нужно сказать спасибо множеству отличных JavaScript-библиотек, которые позволяют реализовывать все фантазии дизайнеров быстро и легко: Raphaël, Snap.svg, SVG.js, а также тех, что визуализируют разнообразные данные в любом вообразимом виде: D3, Highcharts, GRaphaël.</p>
<p>Но я говорил об облегчении жизни разработчиков: в чем она нелегка, если хороших инструментов так много? Проблемы начинаются, когда перед программистами ставится одна из следующих задач:</p>
<ul>
<li>отрисовать графики на сервере, чтобы вставить в email-рассылку;</li>
<li>закэшировать результат, потому что каждая отрисовка очередного сложного изображения «подвешивает» браузер на слабой машине;</li>
<li>дать пользователю возможность скачать файл в PNG или PDF.</li>
</ul>
<p>Очевидно, основное преимущество всех этих библиотек становится их самым большим минусом — они работают только в браузере.</p>
<p>Решения этой проблемы уж больно прямолинейные — перетащить браузер на сервер. Тут и решения «headless», например, у <a href="https://www.highcharts.com/component/content/article/2-news/52-serverside-generated-charts">highcharts</a> или <a href="http://mir.aculo.us/2013/04/30/embed-canvas-and-svg-charts-in-emails/">freckle</a>, или попытки перенести <a href="https://github.com/dodo/node-raphael">DOM в Node.js для Raphaël</a>.</p>
<p>Это работает, но инфраструктура для столь небольших потребностей получается монструозной, сложной и часто медленной.</p>
<p>Почему же тогда просто не использовать сторонние серверные библиотеки? Ни один программист в мире не захочет поддерживать две реализации одного и того же на двух разных языках. Это ведет к ошибкам, дополнительным сложностям и высокой итоговой стоимости поддержки и разработки новой функциональности.</p>
<p>Мы все это понимаем, поэтому и создали <a href="http://svable.com" title="Svable">Svable</a>. Это сервис, который позволит вам перенести всю вашу существующую генерацию SVG из браузера на сервер или запускать один и тот же код как на клиенте, так и на сервере.</p>
<p>Как это реализуется? Система состоит из двух частей: самой платформы и адаптеров. Платформа — это API, который принимает на вход специально сформированный JSON с высокоуровневыми командами, вроде: <code>rect</code>, <code>circle</code>, <code>getBBox</code> и т.д. В ответ вы получаете результирующий SVG, PDF или PNG. Мы конвертируем результат, если вам это нужно.</p>
<p>Возьмем для примера <a href="https://codepen.io/anon/pen/jiHkq">вот этот SVG</a>. Как видим, это круг с полупрозрачным <code>stroke</code> и белым квадратом, который рисуется ровно по центру этого круга. И все это на фоне белого прямоугольника с закругленными краями. Допустим, нам позарез нужно отрисовать подобное на сервере. Для этого потребуется сформировать POST-запрос на наш сервис со следующим JSON:</p>
<pre><code tabindex="0" class="language-json">{
    'paper': {
    'attrs': [ { 'width': '640' }, { 'height': '480' }],
    'access_key': 'my_key',
    'format': 'svg',
    'children': [
        {
        'type': 'rect',
        'attrs': [
            { 'x': '0' },
            { 'y': '0' },
            { 'width': '640' },
            { 'height': '480' },
            { 'rx': '10' },
            { 'fill': '#fff' }
        ]},
        {
        'type': 'circle',
        'svable_id': 'main_circle',
        'attrs': [
            { 'cx': '320' },
            { 'cy': '240' },
            { 'r': '60' },
            { 'fill': '#223fa3' },
            { 'stroke': '#000000' },
            { 'stroke-width': '80' },
            { 'stroke-opacity': '0.5' }
        ]},
        {
        'type': 'rect',
        'attrs': [
            { 'x': 'main_circle.cx - 10' },
            { 'y': 'main_circle.cy - 10' },
            { 'fill': '#fff' },
            { 'width': '20' },
            { 'height': '20' }
        ]}
    ]}
}
</code></pre>
<p>Разберем его по частям: в корневом объекте <code>paper</code> вы описываете размеры вашего полотна, <code>viewBox</code>, а также указываете ваш персональный ключ доступа и растровый формат (по умолчанию в ответ вам придет SVG):</p>
<pre><code tabindex="0" class="language-json">'paper': {
    'attrs': [{ 'width': '640' }, { 'height': '480' }],
    'access_key': 'my_key',
    'format': 'svg',
</code></pre>
<p>В массиве <code>children</code> указываются все объекты, которые попадут на ваш холст. Первым из них идет фоновый прямоугольник. Тут все достаточно просто и почти один в один повторяет формат самого SVG-документа:</p>
<pre><code tabindex="0" class="language-json">'type': 'rect',
'attrs': [
    { 'x': '0' },
    { 'y': '0' },
    { 'width': '640' },
    { 'height': '480' },
    { 'rx': '10' },
    { 'fill': '#fff' }
]
</code></pre>
<p>Далее опишем круг. Тут все тоже довольно банально, но есть одно отличие — атрибут <code>svable_id</code>, который позволит вам сослаться конкретно на этот объект в тот момент, когда вам понадобятся любые его параметры:</p>
<pre><code tabindex="0" class="language-json">'type': 'circle',
'svable_id': 'main_circle',
'attrs': [
    { 'cx': '320' },
    { 'cy': '240' },
    { 'r': '60' },
    { 'fill': '#223fa3' },
    { 'stroke': '#000000' },
    { 'stroke-width': '80' },
    { 'stroke-opacity': '0.5' }
]
</code></pre>
<p>Затем опишем последний квадрат. Напомним, что он должен позиционироваться относительно центра круга. Тут вам и пригодится то, что вы указали в <code>svable_id</code>:</p>
<pre><code tabindex="0" class="language-json">'type': 'rect',
'attrs': [
    { 'x': 'main_circle.cx - 10' },
    { 'y': 'main_circle.cy - 10' },
    { 'fill': '#fff' },
    { 'width': '20' },
    { 'height': '20' }
]
</code></pre>
<p>Однако обычно никто не рисует SVG на сервере с нуля. Поэтому мы создали адаптеры под популярные JavaScript-библиотеки: уже готов Raphaël, завершаем работу над Snap.svg и D3.</p>
<p>Адаптеры как раз занимаются тем, что преобразовывают код, написанный для браузерных библиотек в JSON, который понимает наша платформа. В итоге вы легко можете запускать свой код там, где вам сейчас это выгодно, лишь вызовите адаптер в нужный момент.</p>
<p>Возьмем уже знакомый нам SVG и <a href="https://codepen.io/anon/pen/iJext">отрисуем с помощью Raphaël</a>:</p>
<pre><code tabindex="0" class="language-js">var paper = Raphael(0, 0, 640, 480);
    paper
        .rect(0, 0, 640, 480, 10)
        .attr({
            fill: '#fff',
            stroke: 'none'
});

var circle = paper
    .circle(320, 240, 60)
    .attr({
        fill: '#223fa3',
        stroke: '#000',
        'stroke-width': 80,
        'stroke-opacity': 0.5
});

paper
    .rect(circle.attr('cx') - 10, circle.attr('cy') - 10, 20, 20)
    .attr({
        fill: '#fff',
        stroke: 'none'
});
</code></pre>
<p>А теперь перенесем его на сервер и <a href="https://codepen.io/anon/pen/woJgA">отрисуем с помощью Node.js</a>:</p>
<pre><code tabindex="0" class="language-js">var Svable = require('svable');
var paper = Svable(0, 0, 640, 480, 'raphael');

paper
    .rect(0, 0, 640, 480, 10)
    .attr({
    fill: '#fff',
    stroke: 'none'
});

var circle = paper
    .circle(320, 240, 60)
    .attr({
        fill: '#223fa3',
        stroke: '#000',
        'stroke-width': 80,
        'stroke-opacity': 0.5
});

paper
    .rect(circle.attr('cx') - 10, circle.attr('cy') - 10, 20, 20)
    .attr({
        fill: '#fff',
        stroke: 'none'
});

console.log(paper.burnSync());
</code></pre>
<p>Как видите, вся разница лишь в первоначальном вызове объекта и итоговом получении результата.</p>
<p>В данном случае вызов <code>paper.burnSync()</code> синхронно вернет нам необходимый XML, с которым вы уже можете поступить как захотите. Любители асинхронности могут воспользоваться методом <code>burn</code> — он возвращает промис.</p>
<p>Таким образом, вы получаете возможность вставить итоговый файл в почтовую рассылку, отдать пользователю на скачивание, сохранить на сервере, чтобы в следующий раз сэкономить как время пользователя, так и деньги на генерации, и все это без дублирования кода и страданий программистов.</p>
<p>Мы сейчас находимся на финальной стадии разработки, и нам нужны первые клиенты со сложными задачами для пробных интеграций. Мы не только решим их проблемы, но и дадим выгодные условия на время бета-теста. Напишите нам, обсудим конкретно ваш случай: <a href="http://svable.com/">svable.com</a>.</p>

                    ]]></description><pubDate>Fri, 13 Dec 2013 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/svable-server-svg/</guid></item><item><title>Почему Sass?</title><link>https://web-standards.ru/articles/why-sass/</link><description><![CDATA[
                        <p>Я не хотел верить в Sass. «Я пишу стили руками! Мне не нужна помощь. Я не хочу усложнять мой рабочий процесс! Отстаньте от меня!»</p>
<p>Так, во всяком случае, я думал. Но на самом деле, Sass, как и другие препроцессоры, может быть могущественным союзником — инструментом, который любой верстальщик может легко использовать в своей ежедневной работе. Мне потребовалось совсем немного времени, чтобы разобраться в нем, и я действительно рад, что сделал это.</p>
<p>И именно поэтому я захотел написать эту небольшую книгу. Рассказать о том, как я начал использовать Sass, чтобы сделать разработку кода более эффективной, и сохранил при этом рабочий процесс, которому привык следовать на протяжении последних десяти лет. Изначально у меня было много неправильных представлений о Sass, которые удерживали меня от того, чтобы начать им пользоваться. Я беспокоился, что мне придется полностью изменить привычные способы создания и организации стилей. Иногда CSS бывает довольно хрупким, и это объясняет желание разработчиков как-то защитить то, что они делают. Смогу ли я справиться с этим?</p>
<p>Хм…</p>
<p>Итак, я здесь, чтобы показать вам, что Sass не нарушит ваш привычный рабочий процесс, и при этом он может облегчить вашу жизнь. В книге я коснусь различных аспектов Sass: как его установить, как использовать; расскажу, как он помог мне в моих собственных проектах. Если повезет, я смогу сделать так, чтобы вы тоже в него поверили.</p>
<h2>Sass: короткая презентация</h2>
<p>Вам когда-нибудь надо было изменить в стилях, скажем, цвет, и при этом обнаруживалось, что вы вынуждены найти и заменить его значение во многих местах? Вам хотелось бы , чтобы CSS позволял вам сделать это вот так?</p>
<pre><code tabindex="0" class="language-scss">$brand-color: #fc3;

a {
    color: $brand-color;
}

nav {
    background-color: $brand-color;
}
</code></pre>
<p>Что, если бы вы могли изменить значение в одном месте, а оно бы поменялось во всем документе? С Sass это возможно.</p>
<p>Или как насчет повторяющихся блоков правил, которые используются в разных местах таблицы стилей?</p>
<pre><code tabindex="0" class="language-css">p {
    margin-bottom: 20px;
    font-size: 14px;
    line-height: 1.5;
}

footer {
    margin-bottom: 20px;
    font-size: 14px;
    line-height: 1.5;
}
</code></pre>
<p>Разве не было бы прекрасно собрать эти общие правила в один блок, который можно использовать несколько раз? Повторюсь, блок определяется один раз, но при этом его можно использовать везде, где он вам потребуется.</p>
<pre><code tabindex="0" class="language-scss">@mixin default-type {
    margin-bottom: 20px;
    font-size: 14px;
    line-height: 1.5;
}

p {
    @include default-type;
}

footer {
    @include default-type;
}
</code></pre>
<p>Всё это тоже Sass! И два этих очень простых примера демонстрируют лишь небольшую часть возможностей Sass, который может сделать разработку стилей более быстрой, легкой и гибкой. Он желанный помощник в мире веб-дизайна, потому что каждый, кто создает сайты, знает, что…</p>
<h2>CSS сложен</h2>
<p>Ну правда, изучить CSS не просто. Разобраться, что делает каждое свойство, как работает каскад, какие браузеры что поддерживают, селекторы, режимы совместимости, и т.п. Всё это не просто. Добавьте сюда сложность интерфейсов, которые мы разрабатываем и поддерживаем — погодите, почему мы снова и снова это делаем?! Это паззл, и некоторые из нас наслаждаются его финальной завершенностью.</p>
<p>Частично проблема в том, что изначально CSS не был предназначен для того, что мы делаем сегодня с его помощью. Конечно, прогресс движется достаточно быстро благодаря стремительному развитию браузеров, внедрению поддержки CSS3 и так далее. Но мы все еще вынуждены полагаться на техники, которые, несмотря на их полезность, являются хаками. Свойство float, например, было создано для того, чтобы просто выравнивать картинки внутри текстового блока. И только. Тем не менее, мы вынуждены использовать это свойство не по назначению, чтобы создавать раскладку для полноценных интерфейсов.</p>
<p>К тому же, в наших таблицах стилей очень много повторений. Цвета, шрифты, часто используемые группы свойств и т.п. Типичный CSS файл имеет линейную организацию — такой тип кода заставляет «объектно-ориентированного» программиста рвать на себе волосы. (Я не «объектно-ориентированный» программист, но у меня осталось совсем немного волос. Понимайте, как хотите.)</p>
<p>Поскольку интерфейсы и веб-приложения становятся более сложными, мы ставим с ног на голову изначальное предназначение CSS, чтобы сделать вещи, о которых раньше нельзя было и мечтать. Мы хитрим.</p>
<p>К счастью, сейчас браузеры внедряют новые возможности CSS гораздо быстрее, чем раньше, добавляются более эффективные и мощные свойства и селекторы, решающие проблемы, которые подбрасывает нам современный веб. Это новые способы раскладки в CSS3, border-radius, box-shadow, продвинутые селекторы, transitions, transforms, animation и многое другое. Это удивительное время. И всё равно в CSS ещё многого не хватает. Есть ещё пробелы, которые предстоит заполнить, и тогда жизнь разработчиков станет гораздо проще.</p>
<h3>Принцип DRY</h3>
<p>Если мы заглянем в мир разработки ПО (я предпочитаю именно заглянуть, а не оставаться там), мы сразу заметим, что организация кода, переменные, константы, компоненты и т.п. являются неотъемлемой, ключевой частью работы тех, кто разрабатывает сложные системы.</p>
<p>Вы, возможно, слышали о принципе «не повторяй себя» (DRY). Придуманный и описанный Дэвидом Томасом (Dave Thomas) и Эндрю Хантом (Andy Hunt) в книге «<a href="http://bkaprt.com/sass/1/">Программист-прагматик</a>» принцип DRY звучит так:</p>
<blockquote>
<p>Каждая частица знаний должна быть представлена в системе один раз, ясно и недвусмысленно.</p>
</blockquote>
<p>Идея состоит в том, что дублирующийся код может вызвать ошибки и быть <a href="http://bkaprt.com/sass/2/">непонятным для разработчиков</a>. Это же подсказывает и здравый смысл: один раз описать часто используемые паттерны, чтобы затем использовать их в разработке приложения сколько угодно раз. Это более эффективно с точки зрения разработки, и такой код проще поддерживать.</p>
<p>CSS — это всё что угодно, но только не DRY. Временами он может быть забит под завязку повторяющимися правилами, объявлениями и значениями. Мы всё время пишем одни и те же куски кода для цветов, шрифтов и часто используемых паттернов в наших таблицах стилей. Один взгляд на большой CSS-файл — и программист, привычный к коду, следующему принципу DRY, разрыдается, сначала от недоумения, потом от разочарования.</p>
<p>«Как, черт возьми, вы это поддерживаете?» — спросит он.</p>
<p>«Это ты еще не слышал про баги в IE», — ответите вы с некоторой долей самоуничижения.</p>
<h3>Так почему же так трудно работать с CSS?</h3>
<p>Почему CSS имеет такие синтаксические ограничения, мы можем попытаться узнать <a href="http://bkaprt.com/sass/3/">из эссе соавтора CSS Берта Боса</a>:</p>
<blockquote>
<p>В CSS нет огромного количества мощных возможностей, которые есть в языках программирования: макросов, переменных, символических констант, условий, выражений и т.п. Всё потому, что богатые возможности языка дают продвинутым разработчикам больше свободы, в то время как менее продвинутые пользователи просто запутаются; или, что более вероятно, будут настолько напуганы, что даже не притронутся к CSS. Это баланс. И для CSS он имеет особенное значение.</p>
</blockquote>
<p>Архитекторы CSS с самого начала заботились о доступности технологии. Они хотели (совершенно справедливо), чтобы как можно больше людей могли создавать сайты. Они хотели, чтобы CSS был достаточно мощен, чтобы можно было создавать стили для веб-страниц и отделять контент от оформления, и в то же время, был достаточно простым для понимания и использования. Я безусловно уважаю это решение. В то же время, у нас есть задачи, и эти задачи становятся все более сложными, в них больше нюансов и больше требований к поддержке и возможности дальнейшего развития.</p>
<p>К счастью, есть способы справиться с этим, и один из них — Sass.</p>
<h2>Что такое Sass</h2>
<p>Sass — это препроцессор, прослойка между таблицами стилей, которые вы пишете, и css-файлами, которые вы отдаете браузеру. Sass (сокращение от Syntactically Awesome Stylesheets — Синтаксически Потрясающие Таблицы стилей) заполняет те самые пробелы в языке CSS, позволяя вам писать код по принципу DRY, то есть, быстрее, эффективнее и проще в поддержке.</p>
<p>Краткое описание Sass <a href="http://bkaprt.com/sass/4/">с сайта технологии</a>:</p>
<blockquote>
<p>Sass — это метаязык над CSS, который позволяет писать стили прозрачно и структурированно с использованием больших возможностей, чем есть в обычном CSS. Sass предоставляет простой, более элегантный синтаксис CSS, а кроме того, расширяет возможности для разработки легко поддерживаемых таблиц стилей.</p>
</blockquote>
<p>Итак, пока обычный CSS все еще не позволяет использовать такие вещи как переменные, примеси (mixins — повторяющиеся блоки стилей) и другие плюшки, Sass дает нам такую возможность, и даже больше — делает возможной «суперфункциональность» в дополнение к обычному CSS. Затем он компилирует ваш код в привычный CSS-файл с помощью командной строки или плагинов для фреймворка.</p>
<p>Если быть точнее, Sass — это расширение CSS3, и его SCSS-синтаксис («Sassy CSS»), о котором мы будем говорить — надстройка над CSS3. Это означает, что любой валидный CSS-документ также является валидным SCSS-документом. Возможность «быстро вникнуть» — неотъемлемая часть Sass. Начать использовать синтаксис SCSS легко, более того, вы можете начать использовать его в столь малых дозах, как захотите. Что также означает, что преобразование существующих стилей из CSS в SCSS можно производить поэтапно, по мере того, как вы будете больше узнавать Sass.</p>
<p>Позже, когда вы познакомитесь с Sass поближе, он начнет восприниматься как естественное продолжение CSS, как если бы он заполнил пробелы в существующей спецификации CSS. Именно поэтому, с тех пор, как я начал использовать Sass, я никогда не думал, что это тяжело или трудоемко, он воспринимается просто как CSS. Один раз попробовав, вероятно, вы начнете использовать его постоянно.</p>
<p>Более того, Sass помогает CSS становиться лучше. Некоторые возможности, которые немыслимы сегодня без препроцессоров, уже сейчас дают авторам CSS живую реализацию и поле для экспериментов. Позже, если это будет иметь смысл, конкретные возможности Sass могут серьезно повлиять на спецификации CSS.</p>
<h2>Заблуждения о Sass</h2>
<p>Я уже говорил раньше, что не очень-то хотел попробовать Sass. Отчасти причиной было большое количество заблуждений, которые у меня были до начала его использования. «Должен ли я знать Ruby и быть продвинутым пользователем консоли?» «Придется ли мне полностью изменить способ, которым я сейчас пишу CSS?» «Не станет ли конечный CSS раздутым и нечитаемым?»</p>
<p>К счастью, ответ на каждый из этих вопросов, конечно — «нет». Но я вижу, как эти вопросы всплывают каждый раз, когда кто-то упоминает Sass в интернете. Давайте проясним некоторые моменты.</p>
<h3>Я боюсь командной строки!</h3>
<p>Едва ли меня можно назвать экспертом по использованию командной строки, но я нахватался каких-то знаний то тут, то там за эти годы. Я не боюсь с помощью консоли перемещаться по файловой системе, использовать Git команды и т.п.</p>
<p>Тем не менее, я понимаю дизайнеров и фронтенд-разработчиков, не желающих иметь дела с командной строкой. Фобия командной строки действительно существует в некоторых головах. Для Sass использование командной строки сводится к одной команде — и этого достаточно. Более того, существуют приложения и фреймворки, которые избавят вас от необходимости пользоваться командной строкой (я расскажу о них в следующей главе).</p>
<p>Так что даже если вы избегаете командной строки, это не помешает вам попробовать Sass.</p>
<h3>Я не хочу менять свои привычки в написании CSS</h3>
<p>Да, я страдал и от этого заблуждения. Для меня важно, как создаются мои таблицы стилей и как они организованы. Есть целый набор личных скиллов, которые помогают создавать документы. Но помните, что с тех пор, как SCSS стал расширением CSS3, вам не придется менять ваши привычки. Комментирование, отступы — всё ваше форматирование может оставаться неизменными при работе с .scss файлами. Как только я это понял, я смог смело двигаться дальше.</p>
<h3>Я не хочу менять свои привычки в организации стилей!</h3>
<p>Sass не решит всех ваших проблем и не исправит ваши вредные привычки. Неэффективные, раздутые таблицы стилей могут остаться такими же неэффективными и раздутыми, если вы используете Sass. Хорошая организация кода и здравый смысл по-прежнему необходимы. На самом деле, Sass может с тем же успехом сделать плохой код ещё хуже. Но когда он используется уместно и разумно, Sass может оказаться отличным подспорьем в разработке сайтов.</p>
<p>Окей, теперь мы знаем немного больше о том, что нам предстоит. Давайте же получим от этого удовольствие. Я думаю, что возможности Sass вас удивят. В следующей главе мы узнаем, как встроить Sass в рабочий процесс, и как легко его использовать с помощью командной строки или приложения. Давайте займемся Sass-ом!</p>

                    ]]></description><pubDate>Tue, 10 Dec 2013 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/why-sass/</guid></item><item><title>Сite и blockquote: перезагрузка</title><link>https://web-standards.ru/articles/cite-blockquote-reloaded/</link><description><![CDATA[
                        <p>Недавно в <a href="https://www.w3.org/html/wg/drafts/html/master/">спецификации HTML</a> было изменено определение элементов <code>&lt;blockquote&gt;</code> и <code>&lt;cite&gt;</code>. В статье рассказывается, что это значит для разработчиков.</p>
<h2>Изменения в определении <code>&lt;blockquote&gt;</code></h2>
<blockquote>
    <p>Элемент <code>&lt;blockquote&gt;</code> представляет контент, являющийся цитатой из другого источника, <strong>возможно</strong>, включающим упоминание этого источника, которое <strong>должно быть</strong> размещено внутри элементов <code>&lt;footer&gt;</code> или <code>&lt;cite&gt;</code>, и, <strong>возможно</strong>, содержащий примечания и сокращения.</p>
    <p>Контент внутри элемента <code>&lt;blockquote&gt;</code>, за исключением отсылки к источнику и изменений в тексте, <strong>должен быть</strong> точной цитатой из другого источника, адрес которого, если таковой имеется, <strong>может быть</strong> указан в атрибуте <code>cite</code>.</p>
    <footer>
        <cite>
            <a href="https://www.w3.org/html/wg/drafts/html/master/grouping-content.html#the-blockquote-element">4.51 the Blockquote element</a>, Роберт Бержон и соавторы, 2013.
        </cite>
    </footer>
</blockquote>
<p><em>Выделение в цитате авторское — прим. редактора.</em></p>
<h3>Что изменения в <code>&lt;blockquote&gt;</code> значат для разработчиков</h3>
<p><a href="https://www.w3.org/TR/html5/">Прежде в HTML5</a> не было принято включать упоминание источника внутрь элемента <code>&lt;blockquote&gt;</code>. Сейчас ситуация изменилась, при условии, что упоминание источника находится внутри элемента <code>&lt;cite&gt;</code> или <code>&lt;footer&gt;</code>. Упоминание источника внутри цитаты — распространенный кейс (данные показывают, что приблизительно в <a href="https://lists.w3.org/Archives/Public/public-html/2013Aug/0100.html">60% случаев <code>&lt;blockquote&gt;</code></a> содержит упоминание источника), изменения в <a href="https://www.w3.org/html/wg/drafts/html/master/grouping-content.html#the-blockquote-element">спецификации HTML</a> подтверждают это и обеспечивают семантический механизм дифференциации контента цитаты от упоминания ее источника.</p>
<p>Пример использования элементов <code>&lt;footer&gt;</code> и <code>&lt;cite&gt;</code> внутри <code>&lt;blockquote&gt;</code>:</p>
<pre><code tabindex="0" class="language-html">&lt;blockquote&gt;
    &lt;p&gt;
        As my fellow HTML5 Doctor, Oli Studholme has showed,
        people seldom quote exactly – so sacrosanctity of the quoted
        text isn’t a useful ideal – and in print etc, citations almost
        always appear as part of the quotation –
        it’s highly conventional.
    &lt;/p&gt;
    &lt;footer&gt;
        — &lt;cite&gt;&lt;a href=&quot;http://brucelawson.co.uk/2013/on-citing-
        quotations-again/&quot;&gt;Bruce Lawson&lt;/a&gt;&lt;/cite&gt;
    &lt;/footer&gt;
&lt;/blockquote&gt;
</code></pre>
<p>Пример выше показывает, что авторы спецификации, вместо того, чтобы следовать <a href="https://www.w3.org/TR/html-design-principles/#priority-of-constituencies">теоретической чистоте</a>, которая, в данном случае, не имеет практического смысла, предпочли изменить определение, чтобы <a href="https://www.w3.org/TR/html-design-principles/#solve-real-problems">решить реальную проблему</a> используя существующие возможности HTML, а не <a href="https://www.w3.org/TR/html-design-principles/#do-not-reinvent-the-wheel">изобретать колесо</a>.</p>
<h3>Редкий случай</h3>
<p>Один из аргументов против использования cite и footer внутри blockquote для указания источника цитат состоит в том, что цитируемый контент сам по себе может содержать цитаты и ссылки на источники. Мы можем отбросить этот аргумент по той причине, что такие случаи крайне редки. Отказ от использования <code>&lt;cite&gt;</code> и <code>&lt;footer&gt;</code> ради такого редкого кейса — другой пример <a href="http://ln.hixie.ch/?start=1154950069&amp;count=1">теоретической чистоты</a>, которая не будет служить практическим целям.</p>
<p>Но если у вас возник такой случай, в настоящее время <a href="https://www.w3.org/html/wg/drafts/html/master/grouping-content.html#the-blockquote-element">спецификация HTML</a> предлагает вам просто закомментировать указание источника в коде цитаты. (Вопрос все еще <a href="https://www.w3.org/Bugs/Public/show_bug.cgi?id=23175">открыт</a> и этот совет может измениться):</p>
<p><em>(Добавлено 6.11.13 — прим. редактора.)</em> В ответ на отзывы, мы решили изменить наше предложение для спецификации так, чтобы использовать атрибут <code>class</code> (который <a href="https://www.w3.org/html/wg/drafts/html/master/infrastructure.html#extensibility">может использоваться для расширения</a>) элемента <code>&lt;cite&gt;</code> для обозначения, что это часть источника цитаты.</p>
<pre><code tabindex="0" class="language-html">&lt;blockquote&gt;
    &lt;p&gt;
        My favorite book is
        &lt;cite class=&quot;from-quote&quot;&gt;At Swim-Two-Birds&lt;/cite&gt;
    &lt;/p&gt;
    &lt;footer&gt;
        — &lt;cite&gt;Mike[tm]Smith&lt;/cite&gt;
    &lt;/footer&gt;
&lt;/blockquote&gt;
</code></pre>
<h2>Изменения в определении <code>&lt;cite&gt;</code></h2>
<blockquote>
    <p><a href="https://www.w3.org/html/wg/drafts/html/master/text-level-semantics.html#the-cite-element">Элемент <code>&lt;cite&gt;</code></a> <a href="https://www.w3.org/html/wg/drafts/html/master/dom.html#represents">представляет</a> отсылку к оригинальной работе. Он <strong>должен</strong> включать название работы или имя автора (персоналию, группу лиц или организацию), или ссылку, которые <strong>могут быть</strong> в представлены сокращенном виде в соответствии с соглашениями, принятыми при цитировании.</p>
    <footer>
        <cite>
            <a href="https://www.w3.org/html/wg/drafts/html/master/text-level-semantics.html#the-cite-element">4.51 the Cite element</a>, Роберт Бержон и соавторы, 2013.
        </cite>
    </footer>
</blockquote>
<h3>Что изменения в <code>&lt;cite&gt;</code> значат для разработчиков</h3>
<p><a href="https://www.w3.org/TR/html5/">Ранее в HTML5</a> не было принято упоминать автора источника по имени или заключать другую информацию об источнике в элемент <code>&lt;cite&gt;</code>. Применение <code>&lt;cite&gt;</code> было зарезервировано (теоретически) для названия источника. Это было попыткой пересмотреть определение элемента, <a href="https://www.w3.org/TR/REC-html40/">не менявшееся 14 лет</a>.</p>
<p>Разработчики выступали против изменений в определении:</p>
<blockquote>
    <p>Присоединяйтесь к кампании гражданского неповиновения против излишне ограничительных, обратно-несовместимых изменений элемента <code>&lt;cite&gt;</code>. Начните использовать HTML5, но начните использовать его разумно. Давайте посмотрим, как плохой совет канет в лету.</p>
    <footer>
        <cite>
            <a href="http://24ways.org/2009/incite-a-riot/">Джереми Кит, 2009</a>.
        </cite>
    </footer>
</blockquote>
<p>Они также приводили <a href="http://wiki.whatwg.org/wiki/Cite_element">абстрактные</a> и <a href="http://oli.jp/example/blockquote-metadata/">реальные примеры</a> указания источника. Сейчас, в результате <a href="https://dl.dropboxusercontent.com/u/377471/cite1.html">исследований</a>, <a href="https://lists.w3.org/Archives/Public/public-html/2013Aug/0100.html">анализа данных</a> и <a href="https://www.w3.org/Search/Mail/Public/search?keywords=%3Cblockquote%3E+%3Ccite%3E&amp;hdr-1-name=subject&amp;hdr-1-query=&amp;index-grp=Public_FULL&amp;index-type=t&amp;type-index=public-html">дискуссий</a>, разработчики могут вновь использовать <code>&lt;cite&gt;</code> для того, чтобы разными способами сослаться на источник: например, указать <q>название работы, имя автора или ссылку на источник</q>. Мы теряем в теоретической чистоте, но выигрываем в удобстве использования:</p>
<blockquote>
    <p>Сколько раз в день тег <code>&lt;cite&gt;</code>, содержащий гиперссылку, опубликуется на веб-страницах? По меньшей мере, 70 миллиардов раз, т.е. примерно в 10 ссылках на странице результатов поиска Google. Одна из причин, почему элемент <code>&lt;cite&gt;</code> теперь можно использовать для гиперссылок (даже не упоминая про идентичную ситуацию с Bing).</p>
    <footer>
        <cite>
            Стив Фолкнер, <a href="https://twitter.com/stevefaulkner/statuses/392645777874370560">22 октября 2013</a>
        </cite>
    </footer>
</blockquote>
<h3>Что вы думаете?</h3>
<p>Пожалуйста, прочитайте определения в спецификации HTML 5.1, нам интересно ваше мнение!</p>
<ul>
<li><a href="https://www.w3.org/html/wg/drafts/html/master/text-level-semantics.html#the-cite-element">4.6.6 Элемент <code>&lt;cite&gt;</code></a></li>
<li><a href="https://www.w3.org/html/wg/drafts/html/master/grouping-content.html#the-blockquote-element">4.5.4 Элемент <code>&lt;blockquote&gt;</code></a></li>
</ul>
<p>Огромное спасибо «доктору» Оли, чье <a href="http://oli.jp/2011/blockquote/">исследование</a> помогло подготовить изменения, произошедшие с элементами <code>&lt;cite&gt;</code> и <code>&lt;blockquote&gt;</code>. И «доктору» Брюсу за то, что <a href="http://www.brucelawson.co.uk/2013/on-citing-quotations-again/">настаивал</a> на своем праве цитировать свою маму.</p>

                    ]]></description><pubDate>Mon, 11 Nov 2013 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/cite-blockquote-reloaded/</guid></item><item><title>Основные способы вёрстки. Часть вторая: бестабличная вёрстка</title><link>https://web-standards.ru/articles/css-techniques-p2-alternate/</link><description><![CDATA[
                        <p>С развитием CSS появились способы вёрстки без использования таблиц, которым посвящена <a href="https://web-standards.ru/articles/css-techniques-p2-alternate/articles/css-techniques-p1-tables/">первая часть статьи</a>. Таблицы обладают рядом особенностей, из-за которых порой невозможно добиться желаемого эффекта. Особенно это было актуально ранее, когда браузеры были менее совершенными в применении современных стандартов. В таких случаях для оформления лучше подходят альтернативные приёмы.</p>
<h2>Обтекаемые блоки</h2>
<p>Первый приём основан на использовании обтекаемых блоков, так как с их помощью можно произвольно располагать элементы по горизонтали. По сути, это использование свойства <code>float</code> не по назначению, которое сдвигает блок влево или вправо и включает обтекание текста.</p>
<h3>Особенности</h3>
<p>Обтекаемые элементы имеют ширину, зависящую от содержимого, и занимают доступное для этой ширины место. Если его не хватает, они смещаются вниз до тех пор, пока не хватит места или не останется других обтекаемых блоков.</p>
<figure>
    <img src="https://web-standards.ru/articles/css-techniques-p2-alternate/images/floats.svg" alt="">
    <figcaption>Пример возможного расположения произвольных обтекаемых блоков.</figcaption>
</figure>
<p>Обтекаемые элементы частично изымаются из потока и обычные блоки проходят сквозь них. Меняется лишь расположение текста из-за сужения строк. Строчные элементы располагаются там, где им хватает места в строках.</p>
<figure>
    <img src="https://web-standards.ru/articles/css-techniques-p2-alternate/images/float-how.svg" alt="">
    <figcaption>Принцип работы обтекаемых блоков. Адаптированная иллюстрация из спецификации.</figcaption>
</figure>
<p>Обычный блок, в котором находится обтекаемый, заканчивается раньше обтекаемого блока, что не подходит для раскладки. Для получения предсказуемого результата (а также во избежание проблем в Internet Explorer 7 и ниже) желательно каждый ряд с раскладкой обтекаемыми блоками заключать в специальный оборачивающий элемент, создающий новый контекст форматирования, или воспользоваться трюком, известным как «очистка» (clearfix).</p>
<h3>Контекст форматирования</h3>
<p>Когда элемент создаёт новый контекст форматирования, через его границы не проникают обтекаемые элементы, а поля не складываются. В Internet Explorer 7 и ниже аналогично ведут себя элементы с <a href="http://www.satzansatz.de/cssd/onhavinglayout.html">hasLayout</a>, что помогает достичь кроссбраузерности.</p>
<p>Согласно CSS 2.1, <a href="https://www.w3.org/TR/CSS2/visuren.html#block-formatting">новый контекст форматирования</a> создают: обтекаемые блоки; абсолютно позиционированные блоки; содержащие блоки элементы (вроде строчных блоков <code>inline-block</code> и элементов таблиц); блоки, значение свойства <code>display</code> которых отличается от <code>block</code>; и блоки с <code>overflow</code>, отличным от <code>visible</code>. Новые типы блоков из других модулей CSS также имеют подобный эффект.</p>
<h3>Очистка</h3>
<p>Трюк «очистка» заключается в использовании свойства <code>clear</code> для псевдоэлемента <code>::after</code>, генерируемого в конце блока, что вынуждает браузер разместить нижнюю границу после обтекаемых элементов.</p>
<p>Первоначальный код (существуют вариации) для современных браузеров выглядит так:</p>
<pre><code tabindex="0" class="language-css">.clearfix::after {
    clear: both;        /* Сама очистка */
    display: block;     /* По умолчанию — display: inline */
    content: &quot;.&quot;;       /* Предотвращение сложения полей */
    height: 0;          /* Устранение влияния текста */
    visibility: hidden; /* Скрытие текста */
}
</code></pre>
<p>Спустя некоторое время появилась обновлённая версия, «<a href="http://nicolasgallagher.com/micro-clearfix-hack/">микроочистки</a>», выглядящая следующим образом:</p>
<pre><code tabindex="0" class="language-css">.clearfix::before,
.clearfix::after {
    display: table;
    content: &quot;&quot;;
}

.clearfix::after {
    clear: both;
}
</code></pre>
<p>В этом варианте сложение полей <em>(margin collapsing),</em> которое может происходить и сквозь пустой псевдоэлемент, предотвращается с помощью <code>display: table</code>, причём не только в конце элемента, но и в начале.</p>
<p>А благодаря пустой строке в значении свойства <code>content</code> отпадает необходимость прятать сгенерированное содержимое.</p>
<p>Трюк позволяет заключить обтекаемые блоки внутри элемента, подобно тому, как это происходит в блоках с новым контекстом форматирования. Но в отличие от последних, у элемента полная ширина, и он не имеет присущих им ограничений.</p>
<h3>Адаптация</h3>
<img src="https://web-standards.ru/articles/css-techniques-p2-alternate/images/float-overflow.svg" alt="">
<p>Блок, у которого новый контекст форматирования задан с помощью значения <code>overflow</code>, отличного от <code>visible</code> (то есть <code>auto</code>, <code>scroll</code> или <code>hidden</code>), обладает примечательным свойством: такой блок занимает всё доступное место по горизонтали. Если он следует за обтекаемым блоком, его ширина соответственно уменьшается на значение ширины обтекаемого блока.</p>
<p>Хотя такое поведение — необязательное требование спецификации, браузеры солидарны в данном вопросе.</p>
<p>Эта особенность даёт возможность задействовать эффект обрезания неуместившегося текста с многоточием на месте сокращения: <code>text-overflow: ellipsis</code>, которого нельзя достигнуть таблицей с автоматическим расчётом ширины.</p>
<img src="https://web-standards.ru/articles/css-techniques-p2-alternate/images/mail-folders.svg" alt="">
<p>Примером такого использования могут быть папки в почтовом интерфейсе, где напротив имён папок показывается количество сообщений в них. Если имя папки слишком длинно, чтобы поместиться, то оно обрезается.</p>
<p>Количество сообщений, заранее неизвестное, может исчисляться тысячами и даже меняться в реальном времени. Вдобавок, ширина элементов зависит от параметров шрифта и даже механизма отрисовки браузера и операционной системы.</p>
<p>Можно ограничить длину любых имён, выделив место для чисел с большим запасом, но было бы неразумно делать такое ограничение из-за не столь частых, хотя и встречающихся, случаев.</p>
<h3>Ограничения</h3>
<p>Заметный недостаток адаптирующегося по ширине элемента заключается в том, что всё его содержимое обрезается по краям блока. Это препятствует использованию выносных элементов любого рода и даже графических эффектов вроде обводок и теней. Вдобавок, обтекаемые блоки должны идти первыми в исходном коде.</p>
<p>Одним из главных недостатков методов с использованием обтекаемых блоков является отсутствие возможности произвольного выравнивания по вертикали. Хуже того, как можно было заметить из примера, когда они имеют разную высоту, то вместо стройных рядов может выйти хаотичная мозаика.</p>
<p>Расположение элементов по рядам — это то, с чем хорошо справляются строчные блоки.</p>
<h2>Строчные блоки</h2>
<p>Строчный блок — это блок, который размещается в текстовой строке, имеющий свойство <code>display: inline-block</code>. Строчные блоки предоставляют уникальные возможности в рамках CSS 2.1, так как, будучи строчными элементами, они могут содержать любые блоки.</p>
<p>В особенной модели Internet Explorer 7 и ниже строчные блоки — это обычные строчные элементы, у которых есть свойство hasLayout. Правило <code>display: inline-block</code> включает именно это свойство, но эффект достигается только на строчных элементах. Блочным элементам требуется задать <code>display: inline</code> и включить <var>hasLayout</var> другим способом: например, с помощью <code>zoom: 1</code>, или вынести объявление <code>display: inline-block</code> в отдельный блок правил.</p>
<p>Так же, как и у обтекаемых блоков или ячеек таблиц, ширина строчных блоков, если не указана, рассчитывается браузером с учётом содержимого.</p>
<p>Как и ячейки таблиц, они идут в строгом порядке друг за другом: слева направо или справа налево в зависимости от направления письма.</p>
<p>Благодаря своему расположению в строках текста, строчные блоки располагаются строго по рядам, что нередко предпочтительней.</p>
<figure>
    <img src="https://web-standards.ru/articles/css-techniques-p2-alternate/images/inline-blocks.svg" alt="">
    <figcaption>Пример возможного расположения строчных блоков.</figcaption>
</figure>
<p>Примечательной особенностью строчных блоков является возможность их выравнивания подобно другим текстовым элементам как по горизонтали, так и по вертикали.</p>
<h3>Горизонтальное выравнивание</h3>
<p>Кроме привычного выравнивания слева или справа строчным блокам можно делать полную выключку — выравнивание одновременно по обоим краям одновременно — с помощью <code>text-align: justify</code>. Последняя строка, однако, всё равно остаётся выровненной слева или справа в зависимости от направления письма.</p>
<p>Проблему с последней строкой можно обойти при помощи свойства <code>text-align-last: justify</code>, которое поддерживается браузерами Internet Explorer 5.5+ и Firefox 12+ (с префиксом <code>-moz-</code>), и включено в разрабатываемый модуль <a href="https://www.w3.org/TR/css3-text/">CSS3 Text</a>.</p>
<p>Отсутствие поддержки этого свойства в остальных браузерах можно заменить добавлением псевдоэлемента <code>::after</code> или пустого строчного блока со стопроцентной шириной:</p>
<pre><code tabindex="0" class="language-css">.justify::after {
    display: inline-block;
    width: 100%;
    content: &quot;&quot;;
}
</code></pre>
<img src="https://web-standards.ru/articles/css-techniques-p2-alternate/images/inline-justifying.svg" alt="">
<p>Метод хорош тем, что позволяет выравнивать таким образом заранее неизвестное число элементов. Количество элементов в ряду также может меняться, адаптируясь к ширине окна просмотра.</p>
<p>Недостатки метода в дополнительном элементе и лишней строке, добавляющей отступ, если не принять соответствующие меры.</p>
<p>Этот приём можно использовать для размещения двух элементов в одной строке: слева и справа. Если они не умещаются в одну строку, то второй элемент окажется в начале следующей, а не в конце, как было бы в случае с обтекаемыми блоками. Это выглядит аккуратнее с точки зрения дизайна.</p>
<h3>Вертикальное выравнивание</h3>
<p>Строчные блоки по умолчанию выравниваются по базовой линии нижней строки. Но если значение <code>overflow</code> отличается от <code>visible</code>, строчные блоки должны выравниваться по своей нижней границе, чему, однако, не следуют браузеры на основе WebKit.</p>
<p>Одно из полезных применений режимов выравнивания строчных блоков: вертикальное центрирование.</p>
<p>Если нужно разместить какой-либо текст или изображение в ограниченном пространстве, выровняв по середине, то для этого можно использовать вспомогательный строчный блок с заданной высотой. Ему и блоку с содержимым задаётся вертикальное выравнивание по середине <code>vertical-align: middle</code>.</p>
<pre><code tabindex="0" class="language-css">    .holder {
        word-wrap: none;
    }

    .edge,
    .centered {
        display: inline-block;
        vertical-align: middle;
    }

    .edge {
        height: 100px;
    }

    .centered {
        word-wrap: normal;
    }
</code></pre>
<img src="https://web-standards.ru/articles/css-techniques-p2-alternate/images/inline-centering.svg" alt="">
<p>Выровненные блоки будут корректно вести себя при любых размерах. Если содержимого окажется слишком много, то при необходимости увеличится и высота родительского элемента, если она не ограничена неким заданным значением. Этим не может похвастаться метод центрирования абсолютно позиционированного блока.</p>
<p>Чтобы избежать возможного переноса строк между вспомогательным элементом и содержимым, используется <code>word-wrap: none</code>. Следует вернуть значение свойства для содержимого обратно <code>word-wrap: normal</code>, чтобы оставить возможность переноса строк внутри текста.</p>
<h3>Пробельные символы</h3>
<p>Поскольку строчные блоки по определению являются элементами строк, между ними появляются пробелы, если в HTML-коде есть пробельные символы между тегами. Как правило, это нежелательный эффект, поскольку пробелы добавляют лишние отступы.</p>
<p>Иногда пробелов можно избежать с помощью специального оформления кода: написанием тегов в подряд, использованием трюков с угловыми скобками или с помощью комментариев. Но такой подход не всегда возможен, например, использование таких приёмов может не позволять HTML-шаблонизатор.</p>
<p>От лишних промежутков, добавляемых пробелами, можно избавиться с помощью CSS-свойства <code>word-spacing</code>:</p>
<pre><code tabindex="0">word-spacing: -0.25em;
</code></pre>
<p>Данное объявление уменьшает пробелы между словами везде, кроме браузеров, основанных на WebKit. Дефект в WebKit был исправлен только недавно, и на момент написания этих строк его уже нет в Chrome 26, но он ещё присутствует в остальных браузерах на основе старых версий движка WebKit.</p>
<p>Проблему в WebKit можно обойти через аналогичное объявление свойства <code>letter-spacing</code>:</p>
<pre><code tabindex="0">letter-spacing: -0.25em;
</code></pre>
<p>Можно было бы всегда использовать это правило, но оно не работает в браузере Opera вплоть до 12 версии включительно. Есть и другие недостатки: браузер Mozilla Firefox имеет дефект, из-за которого строчные блоки переносятся на следующую строку в обтекаемом блоке из-за неверного расчёта ширины, а в Internet Explorer 7 теряется эффект при одновременном наличии обоих свойств.</p>
<p>Поэтому следует выделить свойство <code>letter-spacing: -0.25em</code> только для WebKit, устранив при этом действие <code>word-spacing</code>. Либо же использовать только <code>letter-spacing</code> и выделить <code>word-spacing</code> отдельно для браузера Opera</p>
<p>Внутри блоков следует вернуть значение свойств по умолчанию <code>0</code>, чтобы избежать слипания слов в тексте.</p>
<p>В примере использовано значение <code>0.25em</code> — примерный размер пробела в гарнитуре Arial и некоторых других. Однако в иных гарнитурах значение может отличаться, например, Verdana имеет пробел шириной <code>0.34em</code>.</p>
<p>Некоторые браузеры не допускают наложение строчных блоков, даже если отрицательное значение свойства по модулю больше ширины пробела. Спецификация допускает ограничение действия отрицательного значения.</p>
<p>Но в WebKit это ограничение отсутствует, и строчные блоки могут накладываться друг на друга. Поэтому нужно тщательно подбирать используемое значение.</p>
<h3>Ограничения</h3>
<p>Основной недостаток строчных блоков, помимо пробелов, заключается в том, что нельзя задать блоку ширину, оставшуюся доступной от других элементов в ряду.</p>
<p>Если ширина не задана, то она зависит от содержимого <em>(shrink-to-fit)</em> и по мере заполнения элемента может увеличиваться вплоть до ширины родительского элемента или даже превысить её.</p>
<h2>Абсолютное позиционирование</h2>
<p>Наибольший контроль над положением элементов можно получить с помощью позиционирования элементов по абсолютным координатам. Такие элементы полностью изымаются из потока и не влияют на остальные элементы страницы.</p>
<h3>Особенности</h3>
<p>Координаты абсолютно позиционированного элемента задаются относительно предка в дереве элементов, который тоже имеет свойство <code>position</code> со значением, отличным от <code>static</code>. С точки зрения оформления этот предок становится родительским блоком абсолютно позиционированного элемента.</p>
<p>Промежуточные элементы между этим предком и абсолютно позиционированным блоком не имеют никакого влияния, даже если у них есть такое свойство, как <code>overflow: hidden</code>.</p>
<p>Согласно спецификации, если координаты не заданы, то абсолютно позиционированный элемент должен располагаться примерно там, где оказался бы, не будучи позиционированным.</p>
<p>Это даёт возможность применения некоторых приёмов, но, к сожалению, в браузерах всё ещё встречаются ошибки, связанные с таким позиционированием. Также при этом игнорируются режимы выравнивания элементов.</p>
<h3>Выравнивание</h3>
<p>Когда заданы координаты с двух противоположных сторон, абсолютно позиционированный элемент растягивается между указанными координатами. Этого не было в Internet Explorer 6, но, начиная с Internet Explorer 7, появилось и работает во всех браузерах.</p>
<p>Если при этом задан соответствующий размер: ширина или высота, а свойство <code>margin</code> по той же оси имеет значение <code>auto</code>, то элемент выровняется посередине этих координат. Эту ситуацию правильно обрабатывают все браузеры, начиная с Internet Explorer 8.</p>
<figure>
    <img src="https://web-standards.ru/articles/css-techniques-p2-alternate/images/position-aligning.svg" alt="">
    <figcaption>Выравнивание абсолютно позиционированного блока между заданными координатами с `margin: auto`.</figcaption>
</figure>
<h3>Заполнение</h3>
<p>Если задана левая координата <code>left</code>, но не задана правая, то по мере заполнения элемент расширяется вплоть до правой границы предка, относительно которого идёт позиционирование, после чего начинается перенос текста. Аналогично и для обратного случая с правой координатой <code>right</code>.</p>
<img src="https://web-standards.ru/articles/css-techniques-p2-alternate/images/position-filling.svg" alt="">
<p>Такое поведение может быть как полезно, чтобы текст не слишком вытянулся в длину, или подсказка не вышла за границы экрана, так и вредно, если размеры позиционированного предка слишком малы.</p>
<p>Браузеры соблюдают данное правило, начиная с Internet Explorer 8. В Internet Explorer 7 бывают ошибки, а в Internet Explorer 6 нет такого ограничения.</p>
<h3>Контекст размещения</h3>
<p>Свойство <code>z-index</code> указывает взаимное расположение позиционированных элементов по оси <code>z</code>, направленной к пользователю. Элементы с большим <code>z-index</code> показываются поверх элементов с меньшим значением свойства.</p>
<figure>
    <img src="https://web-standards.ru/articles/css-techniques-p2-alternate/images/position-stacking.svg" alt="">
    <figcaption>Отображение элементов согласно значениям `z-index`.</figcaption>
</figure>
<p>Хотя может показаться, что взаимная расстановка блоков определяется только их значениями <code>z-index</code>, положение позиционированного предка тоже определяется его значением <code>z-index</code>. Именно это значение является решающим, когда определяется, какой блок должен показываться поверх другого.</p>
<p>Существует такое понятие как <strong>контекст размещения</strong> (stacking context): если значение <code>z-index</code> у родительского элемента отличается от <code>auto</code>, позиционированные элементы располагаются только внутри этого контекста. Даже элементы с отрицательным <code>z-index</code> отображаются поверх оформления родительского элемента, создающего контекст размещения, хотя и остаются под его содержимым.</p>
<p>Вследствие этого, элемент с большим <code>z-index</code> и все его потомки отображаются поверх другого элемента с меньшим <code>z-index</code> и всех его потомков независимо от значений <code>z-index</code> самих потомков.</p>
<figure>
    <img src="https://web-standards.ru/articles/css-techniques-p2-alternate/images/position-stacking-context.svg" alt="">
    <figcaption>Отображение с учётом контекста размещения.</figcaption>
</figure>
<p>Контекст размещения появляется не только при наличии <code>z-index</code>, но и при использовании свойств <code>opacity</code> или <code>transform</code> из модулей CSS 3.</p>
<p>К сожалению, не обошлось без ошибок: Internet Explorer 8 по-особому размещает позиционированные псевдоэлементы <code>::before</code> и <code>::after</code>. Они имеют проблемы с расположением относительно как обычного, так и позиционированного содержимого элемента, к которому относятся. Эти дефекты не воспроизводятся в режиме эмуляции более старших версий вроде «Internet Explorer 10 в режиме Internet Explorer 8».</p>
<p>Internet Explorer 7 (и ниже) по-своему работает с <code>z-index</code>: в этой версии браузера нет значения <code>auto</code>, значение свойства по умолчанию равно нулю. Понятие контекста размещения при этом имеется и действует на все позиционированные элементы.</p>
<p>Интересно отметить, что какое-то время Internet Explorer 6 был единственным браузером, учитывающим контекст размещения.</p>
<h2>Заключение</h2>
<p>Здесь описаны далеко не все приёмы, а лишь дан общий обзор основных способов вёрстки. Они предназначены для обхода недостатков CSS при раскладке элементов. Этих методов явно недостаточно, но они дают возможность использования неочевидных приёмов, и позволяют полнее раскрыть возможности браузеров.</p>
<p>Уже на подходе модули, которые прямо предназначены для сложного оформления. Модуль <a href="https://www.w3.org/TR/css3-flexbox/">CSS Flexible Box Layout</a>, близкий к выпуску на момент написания статьи, позволяет гораздо более гибко выравнивать элементы и распределять доступное пространство между ними.</p>
<p>В разработке находятся многочисленные новые модули CSS: для создания модульной сетки, для достижения недоступных сейчас типографических эффектов, и многие другие, дающие новые возможности и повышающие удобство пользования CSS.</p>
<p>Со временем отпадёт необходимость в использовании описанных способов, и эти свойства будет разумнее использовать только по их прямому назначению.</p>

                    ]]></description><pubDate>Fri, 28 Jun 2013 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/css-techniques-p2-alternate/</guid></item><item><title>Основные способы вёрстки. Часть первая: таблица</title><link>https://web-standards.ru/articles/css-techniques-p1-tables/</link><description><![CDATA[
                        <p>Для профессионального оформления сайтов необходимо знать не только основы CSS, но и понимать, как работает браузер, знать правила, которым он следует. Именно они определяют основные способы и приёмы вёрстки.</p>
<p>Только имея такое понимание, можно выбрать наиболее подходящий способ решения задачи из нескольких возможных, с учётом их достоинств и ограничений. Только так можно наиболее полно задействовать возможности браузера и предупредить потенциальные ошибки.</p>
<p>Существует немало описаний различных приёмов. В этой статье предпринята попытка собрать вместе самые важные приёмы и систематизировать их, чтобы дать представление как об основных возможностях, так и об ограничениях CSS, актуальных в настоящее время.</p>
<p>Статья рассчитана на людей, которые знакомы с основами HTML и CSS и имеют представление об основных свойствах и базовых принципах работы каскадных таблиц стилей.</p>
<h2>Таблица</h2>
<p>Исторически, первым и единственным способом раскладки страницы были таблицы. Описанию поведения таблиц посвящена <a href="https://www.w3.org/TR/CSS2/tables.html">целая глава</a> в спецификации CSS 2.1. Несмотря на такой объем, некоторые моменты описаны скудно или вообще не определены и отданы на усмотрение браузеров.</p>
<h3>Достоинства и недостатки</h3>
<p>Таблица служит для отображения упорядоченных данных в строках и столбцах, имеющих смысловую связь по горизонтали или вертикали. Отсюда следует главное достоинство: ячейки таблиц выравниваются по сетке, что позволяет простым и очевидным образом создать <em>модульную сетку.</em></p>
<p>Это неотъемлемое свойство таблиц позволяет заполнить плоскость окна браузера и создавать «резиновые сайты». Но, как при малых, так и больших размерах окна просмотра браузера структура таблицы не меняется, она не может гибко адаптироваться под доступное пространство.</p>
<p>При использовании таблицы для раскладки, то есть размещения в сетке данных, не имеющих смысловой связи, нарушается семантичность. Применение таких таблиц ухудшает доступность для людей, использующих специальные программы, и снижает положение в поисковой выдаче, поскольку поисковому движку, предположительно, сложнее разобраться в структуре страницы. Как следствие, сайт работает менее эффективно.</p>
<h3>Особенности</h3>
<p>Ячейки таблиц идут в коде строго друг за другом, слева направо или справа налево в зависимости от направления языка, заданного CSS-свойством <code>direction</code> или его аналогом в HTML, атрибутом <code>dir</code>.</p>
<p>Если, к примеру, требуется, чтобы основное содержимое в центральной колонке шло в начале, перед содержимым других колонок в исходном HTML-коде, таблица — неподходящее решение.</p>
<p>Структура таблицы довольно сложна, она описывается большим количеством тегов, что приводит к усложнению исходного кода. Негативный эффект проявляется ещё больше, когда несколько таблиц вложены друг в друга.</p>
<h3>Имитация</h3>
<p>Появившаяся в CSS 2.1 группа свойств <code>display: table-*</code> позволяет создать таблицу из произвольных элементов, имеющих соответствующую структуру.</p>
<p>Согласно спецификации, достаточно только одного объявления вроде <code>display: table</code> или <code>display: table-cell</code> — недостающие элементы должны автоматически достраиваться браузером.</p>
<p>Однако будет надёжнее создать минимальную структуру <code>таблица &gt; ряд &gt; ячейка</code>, аналогично обязательным тегам <code>&lt;table&gt;</code>, <code>&lt;tr&gt;</code>, <code>&lt;td&gt;</code>, с соответствующими значениями свойства <code>display</code>: <code>table</code>, <code>table-row</code> и <code>table-cell</code>.</p>
<p>В противном случае может возникнуть нерегулярно проявляющаяся ошибка, замеченная в Firefox и браузерах на основе WebKit, когда ряд таблицы без элемента с <code>display: table-cell</code> случайным образом разбивается на несколько ячеек. Возможное объяснение может состоять в попадании границы сетевых пакетов среди ячеек при передаче HTML-кода.</p>
<p>Таким образом, блочная разметка с <code>display: table-*</code> почти не отличается от обычной HTML-таблицы ни в чем, кроме имён тегов, однако обычная таблица лучше поддерживается браузерами (а именно в Internet Explorer 7 и ниже) и имеет больше возможностей, таких как объединение ячеек.</p>
<p>Стоит отметить, что, несмотря на необязательность тега <code>&lt;tbody&gt;</code> в HTML, браузер обязательно создаст этот элемент, если только документ не обрабатывается в режиме XHTML, при отсутствии группирующих элементов <code>&lt;tbody&gt;</code>, <code>&lt;thead&gt;</code> и <code>&lt;tfoot&gt;</code>. Этим можно пользоваться при оформлении, и обязательно следует иметь в виду при использовании родительского селектора, который может иметь запись вида <code>table &gt; tbody &gt; tr &gt; td</code>. Селектор <code>table &gt; tr &gt; td</code> работать не будет.</p>
<p>Анонимные элементы при <code>display: table-*</code>, воссоздающие структуру таблицы согласно CSS 2.1, не влияют на дерево элементов. Им нельзя задать CSS-правила, действуют только наследуемые свойства.</p>
<h3>Семантичность</h3>
<p>Существует мнение, что использование <code>display: table</code> более семантично, так как используются теги, лучше соответствующие содержимому, и это поможет различным движкам в обработке страницы. Нередко при этом приводятся в пример программы чтения с экрана.</p>
<p>Однако есть <a href="http://www.456bereastreet.com/archive/201110/using_displaytable_has_semantic_effects_in_some_screen_readers/">исследования</a>, которые показывают, что некоторые такие программы учитывают оформление страницы и воспринимают элементы с <code>display: table</code> точно так же, как и обычную таблицу, размеченную стандартными тегами. Тем не менее такой способ имеет право на существование, когда использование тегов таблицы неуместно.</p>
<p>Рекомендуется использовать методы <a href="https://www.w3.org/TR/WCAG-TECHS/html.html">WCAG</a> для семантичного оформления таблиц, задействуя дополнительные возможности разметки, такие как краткое описание с помощью тега <code>&lt;caption&gt;</code> или атрибута <code>summary</code> и указание области действия заголовков <code>&lt;th&gt;</code> атрибутом <code>scope</code>.</p>
<h3>Ширина</h3>
<p>Ширина таблицы, будучи не задана, рассчитывается браузером с учётом содержимого. Если ширина таблицы меньше ширины родительского элемента, таблица может быть отцентрирована с помощью свойства <code>margin: auto</code> или выровнена по левому или правому краю.</p>
<p>При <code>table-layout: auto</code> (значение по умолчанию) ширина таблицы рассчитывается так, чтобы поместилось всё её содержимое. Если ширина элемента, в котором находится таблица, недостаточна, она выйдет за его пределы.</p>
<p>Это может привести к появлению горизонтальной прокрутки на странице даже тогда, когда казалось бы достаточно места, из-за слишком длинного слова (например, какой-то интернет-адрес) или широкого изображения.</p>
<p>В CSS 2.1 не определено действие свойств <code>min-width</code>, <code>max-width</code>, <code>min-height</code> и <code>max-height</code> на элементы таблицы.</p>
<p>Так как ячейка не может иметь ширину меньшую, чем позволяет её содержимое, вместо <code>min-width</code> можно использовать достаточно широкую «распорку». Для этого можно использовать пустой блок нужной ширины.</p>
<p>С оговоркой, о которой будет сказано далее, ограничить ширину ячейки может задание свойства <code>width</code>. Этот способ работает в современных браузерах, включая Internet Explorer 8 и даже в старших версиях Internet Explorer в режиме Internet Explorer 7, но не в настоящем браузере Internet Explorer 7. Это один из тех случаев, когда поведение в режиме эмуляции отличается от настоящей версии браузера.</p>
<p>Ширина ячейки может не соответствовать предписанному значению в том случае, когда заданная для всей таблицы ширина не равна сумме заданных ширин всех ячеек, или их сумма не равна 100%. Тогда доступное место распределяется среди ячеек пропорционально значениям их ширины.</p>
<p>В соответствии со стандартной блочной моделью CSS ширина ячейки задаётся по области содержимого, исключая толщину рамки и величину отступа <em>(padding).</em> Однако, если ширина колонки таблицы устанавливается через элемент <code>&lt;col&gt;</code> или <code>&lt;colgroup&gt;</code>, то она задаётся уже с учётом ширины рамок и отступа ячеек.</p>
<h3>Гибкость</h3>
<p>Одним из преимуществ является то, что таблица позволяет гибко управлять соотношением ширин колонок через процентные значения. Ширина ячеек при этом учитывает размер содержимого.</p>
<p>Примечательным свойством таблицы среди возможностей CSS 2.1 является возможность установить такую ширину ячейке, которая осталась ей доступна от остальных ячеек в ряду.</p>
<h3>Высота</h3>
<p>По спецификации свойство <code>height</code> задаёт только минимальную высоту ячейки. Если с шириной ячеек браузеры следуют модели CSS, то установку высоты ячейки разные браузеры трактуют по-разному.</p>
<p>Firefox до 15-й версии и Opera до 12-й включительно считают высоту вместе с отступом и рамкой, другими словами, ведут себя как при <code>box-sizing: border-box</code>, что соответствует поведению в режиме обратной совместимости <em>(Quirks Mode).</em></p>
<p>Internet Explorer версии 8 и выше и браузеры на основе WebKit задают высоту только для содержимого аналогично <code>box-sizing: content-box</code>, что является правильным поведением с точки зрения модели CSS.</p>
<p>На текущий момент свойство <code>box-sizing</code> влияет на задание высоты ячейки только в браузере Internet Explorer. А Firefox до 16 версии не учитывал <code>box-sizing</code> даже для ширины ячеек таблиц.</p>
<h3>Раскладка</h3>
<h4>Жёсткая</h4>
<p>При <code>table-layout: fixed</code> ширина ячеек таблицы задаётся непосредственно, либо равномерно распределяется среди колонок. Таблица теряет возможность автоматического расчёта ширины с учётом заполненности ячеек. Переполнение ячеек обрабатывается в соответствии со значением свойства <code>overflow</code>. Спецификация оставляет браузерам возможность всегда использовать этот режим. Его могут применять мобильные браузеры, ограниченные в ресурсах для сложной обработки таблиц.</p>
<h4>Автоматическая</h4>
<p>При <code>table-layout: auto</code> содержимое не может выйти за границы ячейки, и свойство <code>overflow</code> не имеет действия. При этом нельзя заставить таблицу всегда оставаться в заданных рамках. Из-за автоматического расчёта ширины, она не уменьшится, даже если есть <code>overflow: hidden</code> у элемента с неопределенной шириной внутри ячейки.</p>
<figure>
    <img src="https://web-standards.ru/articles/css-techniques-p1-tables/images/logo-n-menu.svg" alt="">
    <figcaption>Дизайнер может желать, чтобы логотип и меню всегда умещались в одной строчке. Таблица не может обрезать длинные надписи для этой цели.</figcaption>
</figure>
<h3>Быстродействие</h3>
<p>Cвойство <code>table-layout: fixed</code> предназначено для того чтобы отображать большие таблицы по мере загрузки данных — так происходит, потому что размеры ячеек указаны заранее.</p>
<p>Если установлено <code>table-layout: auto</code>, браузер должен рассчитывать размеры таблицы, рекурсивно обрабатывая всё её содержимое, что не может не сказаться на быстродействии. При этом в большинстве браузеров таблица будет показана лишь после того, как загрузится полностью.</p>
<h3>Выравнивание</h3>
<p>Таблица позволяет выравнивать содержимое своих ячеек как по горизонтали (<code>text-align</code>), так и по вертикали при помощи <code>vertical-align</code>.</p>
<p>Несмотря на то, что выравнивание по вертикали как строчных элементов, так и содержимого ячеек таблиц задаётся одним и тем же свойством, его действие отличается в каждом случае.</p>
<p>Для строчных элементов оно действует на сами элементы и имеет больше вариантов, тогда как в случае таблиц выравнивается содержимое ячеек, включая блочные элементы.</p>
<p>Возможные значения свойства <code>vertical-align</code> для ячеек таблиц: <code>top</code>, <code>bottom</code>, <code>middle</code> и <code>baseline</code> — выравнивание по верхней границе, нижней границе, середине и базовой линии, соответственно. Выравнивание по базовой линии производится по первой строчке текста, а если таковой нет, то по самой нижней границе блока в ячейке.</p>
<p>Последний вариант уникален: таблица позволяет выровнять строки текста по базовой линии в разных колонках даже при различных свойствах шрифта.</p>
<p>Другими методами в рамках CSS 2.1 такого эффекта добиться практически невозможно из-за погрешностей округления значений и особенностей отрисовки текста операционными системами и браузерами. В лучшем случае можно только подогнать параметры под наиболее вероятную ситуацию: вроде распространённой гарнитуры и стандартного размера шрифта.</p>
<figure>
    <img src="https://web-standards.ru/articles/css-techniques-p1-tables/images/table-form.svg" alt="">
    <figcaption>Таблица позволяет легко и надёжно выровнять заголовки и элементы форм по базовой линии.</figcaption>
</figure>
<h3>Проблемы позиционирования</h3>
<p>В старых версиях WebKit и текущих версиях Mozilla Firefox нельзя позиционировать элементы таблицы, и потому не получится позиционировать внутренние элементы относительно ячейки. Хотя спецификация не определяет действие свойства <code>position</code> на элементы таблиц, имеется открытое <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=35168">сообщение об ошибке</a> браузера Firefox.</p>
<p>Из-за этой особенности нет работающего во всех браузерах способа разместить что-либо сверху и снизу ячейки одновременно. Иногда проблему можно обойти, разбив ряд таблицы на два, но такое решение ведёт к усложнению кода, а полученная сетка может уже скорее мешать, чем помогать.</p>
<p>В соответствии с принципами CSS установить высоту для элементов внутри таблицы, зависящую от высоты ячейки, можно только в том случае, когда самой ячейке задана высота. Также из-за этого не получится расположить элементы одновременно сверху и снизу с помощью позиционирования относительно внутреннего элемента. Ведь если высота не известна точно заранее, никогда нет гарантии, что соседняя ячейка, а значит и весь ряд, не имеет большую высоту.</p>
<h3>Заключение</h3>
<p>В будущем к нам придут на помощь новые модули CSS, но до окончательной разработки и полноценного внедрения, когда ими можно будет реально пользоваться, пройдёт ещё как минимум несколько лет.</p>
<p>Один из таких модулей — <a href="https://www.w3.org/TR/css3-grid-layout/">CSS Grid Layout</a> — не только позволяет создать модульную сетку, аналогичную таблице, но и даёт большие возможности по оформлению и использованию. При совместном использовании с <a href="https://www.w3.org/TR/css3-mediaqueries/">медиавыражениями</a>, модуль позволяет произвольно адаптировать раскладку под размеры окна одними лишь средствами CSS. Он избавит нас в будущем от необходимости использовать таблицу для раскладки и оставить её, наконец, для своего основного предназначения: разметки табличных данных.</p>
<p>О других приёмах и возможностях CSS 2.1 без использования таблиц читайте во второй части статьи «<a href="https://web-standards.ru/articles/css-techniques-p1-tables/articles/css-techniques-p2-alternate/">Бестабличная вёрстка</a>».</p>

                    ]]></description><pubDate>Thu, 27 Jun 2013 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/css-techniques-p1-tables/</guid></item><item><title>Быстродействие фронтенда для дизайнеров и разработчиков</title><link>https://web-standards.ru/articles/front-end-performance/</link><description><![CDATA[
                        <p>Вряд ли кто-то будет отрицать, что быстродействие — один из <em>ключевых</em> аспектов любого более-менее серьёзного веб-проекта: будь это маленький сайт-портфолио, мобильное веб-приложение, или полноценный проект интернет-магазина. Исследования, статьи и ваш собственный опыт подсказывают: чем быстрее, тем лучше.</p>
<p>Быстродействие — это не только крайне важный, но и <em>невероятно</em> интересный предмет, и я все больше и больше увлекаюсь им и на работе (я вечно достаю на этот счет нашего ведущего специалиста по быстродействию), и в собственных проектах, и в рамках CSS Wizardry (здесь достаётся <a href="https://twitter.com/andydavies">Энди Дэвису</a>).</p>
<p>В этой гигантской статье я поделюсь с вами множеством быстрых, простых, но очень интересных вещей, которые я узнал о быстродействии: надеюсь, они пригодятся и веб-дизайнерам, и фронтенд-разработчикам, а вся эта статья послужит вводной для любого, кто хочет начать заниматься быстродействием и сделать свой фронтенд молниеносно быстрым. Все эти советы вы можете воплотить в жизнь сами, и <em>очень</em> просто. Нужно немножко хитрости и базовые знания о том, как работают браузеры — и всё, вы готовы обыграть систему!</p>
<p>Здесь не будет кучи путаных графиков и трудноперевариваемых цифр. Вместо этого мы сделаем акцент на теории и приемах для повышения быстродействия, которые я освоил, читая, отслеживая, учась у коллег и экспериментируя (достаточно много времени я провел, просто уставившись в каскад отрисовки CSS Wizardry). Кроме того, я дам ссылки на другие статьи по теме, чтобы подчеркнуть какие-то ключевые моменты. Наслаждайтесь!</p>
<p><strong>N.B.</strong> Чтобы эта статья была полезной, вам все-таки нужно немножко знать о быстродействии, но все незнакомые термины, которые будут упоминаться в этой статье, очень легко нагуглить!</p>
<ol>
<li><a href="https://web-standards.ru/articles/front-end-performance/#section:the-basics">Принципы</a>
<ol>
<li><a href="https://web-standards.ru/articles/front-end-performance/#section:styles-at-the-top-scripts-at-the-bottom">Стили наверху, скрипты внизу</a></li>
<li><a href="https://web-standards.ru/articles/front-end-performance/#section:make-fewer-requests">Делайте меньше запросов</a></li>
<li><a href="https://web-standards.ru/articles/front-end-performance/#section:maximising-parallelisation">Максимальная параллелизация</a></li>
</ol>
</li>
<li><a href="https://web-standards.ru/articles/front-end-performance/#section:http-requests-and-dns-lookups">HTTP-запросы и поиск DNS</a>
<ol>
<li><a href="https://web-standards.ru/articles/front-end-performance/#section:dns-prefetching">Предзагрузка DNS</a></li>
</ol>
</li>
<li><a href="https://web-standards.ru/articles/front-end-performance/#section:resource-prefetching">Предзагрузка ресурсов</a></li>
<li><a href="https://web-standards.ru/articles/front-end-performance/#section:css-and-performance">CSS и быстродействие</a></li>
<li><a href="https://web-standards.ru/articles/front-end-performance/#section:gzipping-and-minifying">Минификация и gzip</a></li>
<li><a href="https://web-standards.ru/articles/front-end-performance/#section:optimising-images">Оптимизация изображений</a>
<ol>
<li><a href="https://web-standards.ru/articles/front-end-performance/#section:spriting">Спрайты</a></li>
<li><a href="https://web-standards.ru/articles/front-end-performance/#section:retina-images">Изображения для ретины</a></li>
<li><a href="https://web-standards.ru/articles/front-end-performance/#section:progressive-jpgs">JPG с прогрессивной загрузкой</a></li>
<li><a href="https://web-standards.ru/articles/front-end-performance/#section:use-no-images-at-all">Не используйте изображения вообще</a></li>
</ol>
</li>
<li><a href="https://web-standards.ru/articles/front-end-performance/#section:further-reading">Дальнейшее чтение</a></li>
</ol>
<h2>Принципы</h2>
<p>Практически <em>все</em> дизайнеры и фронтенд-разработчики кое-что знают о быстродействии: например, что нужно делать как можно меньше запросов, оптимизировать изображения, размещать ссылки на стили в <code>&lt;head&gt;</code>, размещать JS-код перед <code>&lt;/body&gt;</code>, минимизировать JS и CSS, и т.п. Эти базовые вещи уже помогут вашим страницам загружаться быстрее. Но способов ускорить загрузку ещё много… очень много.</p>
<p>Ещё очень важно помнить о том, что браузеры (из-за которых у нас каждый рабочий день болит голова) стали <em>очень</em> умными: много работы по оптимизации быстродействия они делают за вас, так что значительная часть рекомендаций сводится к тому, как наилучшим образом использовать эту работу. Многие ноу-хау по быстродействию касаются понимания, использования и настройки того, что браузер уже делает.</p>
<h3>Стили наверху, скрипты внизу</h3>
<p>Это простое правило, и следовать ему очень легко, но почему оно имеет такое значение? Если совсем кратко:</p>
<ul>
<li><strong>CSS блокирует отрисовку,</strong> поэтому вам нужно разобраться с ним как можно быстрее (то есть в верхней части документа, внутри <code>&lt;head&gt;</code>).</li>
<li><strong>JS блокирует загрузки,</strong> так что вам нужно разобраться с ним в последнюю очередь, чтобы быть уверенным, что ваши JS-файлы не мешают загрузке других ресурсов на этой странице.</li>
</ul>
<p>CSS блокирует отрисовку, потому что браузеры отображают страницы методом прогрессивной развёртки: они отображают элементы по мере того, как получают их, и по порядку. Если стили находятся внизу страницы, браузер не может обработать этот CSS, пока не доберется до него. Так задумано для того, чтобы браузеру не приходилось перерисовывать стили в том случае, если они изменяют какой-то элемент, который был отображен выше на странице. Браузер не будет рендерить страницу, пока у него нет всей необходимой информации о стилях, и если вы размещаете эту информацию в нижней части документа, вы заставляете браузер ждать и блокируете тем самым отрисовку.</p>
<p>Так что размещайте CSS в верхней части страницы, чтобы браузер мог начать отрисовку сразу.</p>
<p>JavaScript блокирует загрузки по ряду причин (браузеры снова умничают), но для начала мы должны знать, как именно происходит загрузка ресурсов в браузерах: если коротко, браузер загружает столько ресурсов, сколько может загрузить <em>параллельно</em> с одного домена. Чем больше доменов задействовано, тем больше ресурсов могут одновременно загружаться параллельно.</p>
<p>JavaScript прерывает этот процесс и блокирует параллельные загрузки с текущего и всех остальных доменов, потому что:</p>
<ul>
<li>Вызванный скрипт может изменить содержимое страницы, и значит, браузер должен разобраться с этим скриптом перед тем, как начать заниматься чем-то ещё. Для этого браузер просто останавливает загрузку всего остального, чтобы полностью сфокусироваться на скрипте.</li>
<li>Обычно, чтобы скрипты работали, их нужно загружать в определенном порядке: например, нужно загрузить jQuery, а <em>потом</em> плагин. Ясно, что при параллельной загрузке скриптов, сперва загрузится плагин, а _только потом — _jQuery. Поэтому браузеры блокируют параллельную загрузку JavaScript-файлов.</li>
</ul>
<p>Итак, поскольку браузеры приостанавливают загрузки, пока подгружают JavaScript, ссылки на JavaScript-ресурсы следует помещать как можно ниже в документе. Вы наверняка видели пустые части страниц, потому что какой-то сторонний JS грузится веками и блокирует загрузку и обработку ресурсов для всего остального. Это и есть блокирование JavaScript.</p>
<p>Однако, по всей видимости, современные браузеры стали ещё умнее. Я приведу здесь отрывочек из письма <a href="https://twitter.com/andydavies">Энди Дэвиса</a>, потому что он объясняет все гораздо лучше, чем я:</p>
<blockquote>
<p>Современные браузеры загружают JS параллельно, и только отрисовка блокируется до тех пор, пока не исполнится скрипт (разумеется, перед этим браузер должен его загрузить).</p>
<p>Загрузка скрипта часто осуществляется предзагрузчиком браузера.</p>
<p>Когда браузер не может отрендерить страницу (например, ждет загрузки CSS или исполнения JS), предзагрузчик анализирует оставшийся код на странице и ищет ресурсы, которые может загрузить.</p>
<p>Некоторые браузеры (например, Chrome) расставляют приоритеты в загрузке ресурсов: например, если в очередь на загрузку стоят скрипты и изображения, браузер сперва загрузит скрипты.</p>
</blockquote>
<p>Умно!</p>
<p>Итак: для того, чтобы ваша страница могла начать отрисовку как можно быстрее, размещайте стили наверху. Чтобы избежать блокировки загружаемых ресурсов, размещайте скрипты внизу.</p>
<h3>Делайте меньше запросов</h3>
<p>Ещё одна совершенно очевидная и базовая оптимизация быстродействия: загружайте меньше. Каждый ресурс на странице — это лишний HTTP-запрос, на каждый ресурс, необходимый для отрисовки страницы, браузер отвлекается. Каждый из этих запросов может привести к поиску DNS, редиректам, обработке 404-й ошибки и т.п. Таким образом, каждый HTTP-запрос, который вы делаете, будь это запрос стилей, картинок, веб-шрифтов, JS-файла, чего угодно — потенциально весьма дорогостоящая операция. Минимизация количества этих запросов — один из самых простых способов оптимизировать быстродействие.</p>
<p>Возвращаясь к браузерам и параллелизации: большинство браузеров будут загружать всего несколько ресурсов с каждого домена одновременно, а JS, как вы помните, блокирует эти загрузки в любом случае. Каждый HTTP-запрос, который вы делаете, должен быть оправдан, не смотрите на это сквозь пальцы.</p>
<h3>Максимальная параллелизация</h3>
<p>Для того, чтобы заставить браузер загружать больше ресурсов параллельно, вы можете отдавать их с разных доменов. Если браузер может загружать одновременно, например, только два ресурса с домена, то, раздавая ресурсы с двух доменов, вы сможете загружать одновременно четыре ресурса; с трёх — значит браузер может выжать шесть одновременных загрузок.</p>
<p>У большого количества сайтов есть домены для статических ресурсов. Например, Twitter раздает статические ресурсы с <code>si0.twimg.com</code>:</p>
<pre><code tabindex="0" class="language-html">&lt;link rel=&quot;stylesheet&quot; href=&quot;https://si0.twimg.com/…/t1_core.bundle.css&quot; type=&quot;text/css&quot; media=&quot;screen&quot;&gt;
</code></pre>
<p>Facebook использует <code>fbstatic-a.akamaihd.net</code>:</p>
<pre><code tabindex="0" class="language-html">&lt;link rel=&quot;stylesheet&quot; href=&quot;https://fbstatic-a.akamaihd.net/…/76f893pcD3j.css&quot;&gt;
</code></pre>
<p>Используя эти домены для статических ресурсов, Twitter и Facebook могут отдавать больше ресурсов параллельно: ресурсы с <code>twitter.com</code> и <code>si0.twimg.com</code> могут загружаться одновременно. Это очень простой способ получить большее количество одновременных загрузок ресурсов на вашей странице, и его можно улучшить, совместив с технологией CDN (она позволяет уменьшить сетевые задержки за счёт того, что ресурс отдается с наиболее подходящего <em>физического</em> сервера).</p>
<p>Все это замечательно, но ниже мы обсудим, как загрузка ресурсов с поддоменов может при ряде обстоятельств навредить быстродействию.</p>
<p>Итак, вот наши принципы быстродействия:</p>
<ul>
<li>Размещайте ссылки на стили наверху страницы</li>
<li>Размещайте JavaScript внизу (насколько это возможно)</li>
<li>Делайте как можно меньше HTTP-запросов</li>
<li>Раздавая ресурсы с нескольких доменов, вы можете увеличить количество ресурсов, которые браузер будет загружать параллельно.</li>
</ul>
<h2>HTTP-запросы и поиск DNS</h2>
<p>Каждый раз, когда вы делаете запрос на ресурс с какого-либо домена, браузер делает HTTP-запрос с соответствующими заголовками, сервер отвечает и посылает обратно ответ. Это, конечно, очень упрощенная модель процесса, но по-настоящему вам нужно знать только это. Так выглядит HTTP-запрос, и все ресурсы, которые вы запрашиваете, вы получите таким путём. Эти запросы и есть главное узкое место фронтенд-быстродействия: как говорилось выше, браузеры ограничены тем, сколько запросов они могут выполнять параллельно. Вот почему мы часто используем поддомены: так можно одновременно выполнить большее количество запросов, распределенных по нескольким доменам.</p>
<p>С этим, однако, есть и проблема — DNS-запросы. Каждый раз, когда делается запрос к <em>новому</em> домену (с холодного кэша), HTTP-запрос должен произвести довольно долгий DNS-запрос (между 20 и 120 мс). За это время исходящий запрос смотрит, где же, собственно, находится ресурс: интернет организован с помощью IP-адресов, на которые ссылаются доменные имена, которые, в свою очередь, управляются DNS.</p>
<p>В обращение к каждому новому домену, с которого вы запрашиваете ресурсы, включена стоимость DNS-запроса, и вам нужно быть уверенным, что оно того стоит. Если вас сайт небольшой (как, например, CSS Wizardry), тогда вам, скорее всего, не стоит раздавать ресурсы с поддомена: браузер, вероятно, сможет скачать несколько ресурсов с одного домена без параллелизации быстрее, чем сделать DNS-запросы на несколько доменов и распараллелить загрузку ресурсов с них.</p>
<p>Если у вас десять-пятнадцать ресурсов, вам следуем подумать о том, чтобы раздавать их <strong>с одного</strong> поддомена: скорее всего, лишний DNS-запрос стоит того, чтобы обеспечить лучшую параллелизацию этих ресурсов. Если у вас, например, 40 ресурсов, возможно, имеет смысл разделить их <em>на два</em> поддомена: два лишних DNS-запроса будут оправданной ценой за то, чтобы раздавать ваш сайт с целых трёх доменов.</p>
<p>DNS-запросы достаточно дороги, так что вам нужно определить, что больше подходит для вашего сайта: издержки на DNS-запросы или просто раздача одного домена.</p>
<p>Важно помнить, что, если HTML уже запрошен с домена, скажем, <code>foo.com</code>, DNS-запрос для этого хоста уже произошёл, так что дополнительные запросы к любому ресурсу на <code>foo.com</code> больше <em>не проходят</em> через DNS-запрос.</p>
<h3>Предзагрузка DNS</h3>
<p>Если вы, как и я, хотите, чтобы у вас на сайте был виджет Twitter, аналитика или какие-то веб-шрифты, то вам <em>нужно</em> будет ссылаться на внешние домены. Это означает, что вам <em>нужно</em> будет тратить время на DNS-запросы. Мой совет: не использовать какие попало виджеты, не обдумав заранее их влияние на быстродействие. Но если вы решили, что какой-то виджет вам <em>необходим</em>, полезно знать следующее.</p>
<p>Поскольку эти ресурсы располагаются на других доменах, CSS для вашего веб-шрифта, например, будет загружаться параллельно с вашим собственным CSS, что в каком-то смысле хорошо, но скрипты всё равно будут блокировать браузер (<a href="http://css-tricks.com/thinking-async/">если они не асинхронные</a>).</p>
<p>На самом деле, проблема здесь заключается в DNS-запросах, связанных с внешними доменами. К счастью, есть очень быстрый и лёгкий способ ускорить этот процесс: предзагрузка DNS.</p>
<p>Что делает предзагрузка DNS, понятно из названия; реализовать её очень просто. Если вам нужно запросить ресурсы, скажем, с <code>widget.foo.com</code>, вы можете предзагрузить DNS для этого поддомена, просто добавив в начале страницы в секции <code>&lt;head&gt;</code>:</p>
<pre><code tabindex="0" class="language-html">&lt;head&gt;
    &lt;link rel=&quot;dns-prefetch&quot; href=&quot;//widget.foo.com&quot;&gt;
&lt;/head&gt;
</code></pre>
<p>Эта простая строчка объявит браузерам, которые поддерживают предзагрузку, что им стоит начать загружать DNS для этого домена за долю секунды до того, как это понадобится сделать для загрузки ресурса. Таким образом, когда браузер дойдёт до элемента <code>&lt;script&gt;</code>, запрашивающего виджет, DNS-запрос уже будет запущен. Так браузер чуть-чуть опережает события.</p>
<p>Этот простой элемент <code>&lt;link&gt;</code> (<a href="https://github.com/csswizardry/csswizardry.github.com/blob/b52472cea6a0f087944afda750839d7c96fab7d3/_layouts/default.html#L7-L15">я использую его на CSS Wizardry</a>) полностью обратно совместим и не отразится отрицательно на быстродействии. Думайте об этом, как о прогрессивном улучшении быстродействия.</p>
<h3>Дальнейшее чтение</h3>
<p><a href="http://calendar.perfplanet.com/2012/speed-up-your-site-using-prefetching/">Speed Up Your Site Using Prefetching</a>.</p>
<h2>Предзагрузка ресурсов</h2>
<p>Вместе с предзагрузкой DNS очень полезно предзагружать все нужные вашему сайту ресурсы. Чтобы понять, что стоит предзагружать, сначала нужно разобраться, как и когда браузер <em>обычно</em> запрашивает ресурсы.</p>
<p>Веб-шрифты и изображения, упомянутые в CSS, ведут себя точно так же: браузер начнет загружать их, как только наткнётся на ту часть HTML-кода, для которого они нужны. Как я уже говорил выше, браузеры очень умные, и здесь мы видим ещё одно подтверждение этому. Представьте себе, если бы браузеры загружали изображения из CSS, как только увидели в стилях следующее:</p>
<pre><code tabindex="0" class="language-css">.page--home      { background-image: url(home.jpg); }
.page--about     { background-image: url(about.jpg); }
.page--portfolio { background-image: url(portfolio.jpg); }
.page--contact   { background-image: url(contact.jpg); }
</code></pre>
<p>Если бы браузер <em>не ждал,</em> пока дело дойдёт до HTML, требующего эти картинки, при заходе на главную страницу он бы скачивал все четыре изображения. Это крайне расточительное использование ресурсов, поэтому, перед тем, как скачивать картинку, браузер должен быть уверен, что она ему <em>определенно</em> нужна. Проблема здесь в том, соответственно, что загрузки не происходит до последнего момента.</p>
<p>Если же мы полностью уверены, что какое-то изображение из CSS <em>точно</em> будет использоваться на каждой странице, мы можем обмануть браузер и заставить его скачать это изображение как можно раньше, ещё <em>до того,</em> как он доберется до HTML, которому оно требуется. Сделать это невероятно просто, но, в зависимости от того, как именно вы решите это сделать, это может быть немного грязным приёмом.</p>
<p>Итак, грязный, но самый безопасный способ: поставить на каждой странице скрытый <code>&lt;div&gt;</code>, в котором при помощи элементов <code>&lt;img&gt;</code> с пустым атрибутом <code>alt</code> вставлены все изображения из CSS. Я поступил так со <a href="https://github.com/csswizardry/csswizardry.github.com/blob/b52472cea6a0f087944afda750839d7c96fab7d3/_layouts/default.html#L41-L44">спрайтом на CSS Wizardry</a>; я знаю, что они используется на каждой странице, и поэтому с уверенностью могу предзагружать его в своем HTML. Браузеры замечательно обрабатывают теги <code>&lt;img&gt;</code>: предзагружая картинку, браузер обращается к ней очень рано, поэтому, заставив его загрузить спрайты с помощью тега <code>&lt;img&gt;</code> в разметке, мы добиваемся более ранней загрузки изображения, чем затребовал бы CSS. Таким образом, упомянув (скрытое) изображение в HTML, я ускоряю эту загрузку.</p>
<p>Со вторым, более «чистым» способом, который очень похож на пример с предзагрузкой DNS, есть некоторая путаница:</p>
<pre><code tabindex="0" class="language-html">&lt;link rel=&quot;prefetch&quot; href=&quot;sprite.png&quot;&gt;
</code></pre>
<p>Этот код прямо говорит браузеру, что нужно начать предзагружать мое изображение со спрайтами, вне зависимости от того решения, которые он может принять после обработки CSS.</p>
<p>Путаница заключается в несогласованности между двумя статьями: <a href="https://developer.mozilla.org/en-US/docs/Link_prefetching_FAQ">по этой статье с MDN</a> можно понять, что <code>prefetch</code> — подсказка для браузера, что, <em>может быть,</em> стоит предзагрузить ресурс, <em>если</em> браузер сейчас ничем не занят. Напротив, <a href="http://calendar.perfplanet.com/2012/speed-up-your-site-using-prefetching/">в этой статье</a> с Planet Performance сказано, что браузер всегда будет предзагружать ресурсы, если он поддерживает синтаксис <code>rel=&quot;prefetch&quot;</code>, и не упоминается, что это будет сделано «в свободное время». Графики загрузки, на которые я смотрел, <em>вроде бы</em> подтверждают второй вариант, но странный глюк в WebKit, из-за которого нельзя наблюдать результаты предзагрузки, если у тебя открыт отладчик (привет, быстродействие Шредингера!), приводит к том, что я не могу быть уверенным на 100%. Буду <em>очень благодарен</em> за любые комментарии по этому поводу.</p>
<p>Я уже говорил, что шрифты и изображения работают почти одинаково: сказанное выше относится и к файлам шрифтов, но шрифт нельзя загрузить в спрятанном <code>&lt;div&gt;</code>, нужно использовать <code>prefetch</code>.</p>
<pre><code tabindex="0" class="language-html">&lt;link rel=&quot;prefetch&quot; href=&quot;webfont.woff&quot;&gt;
</code></pre>
<p>Проще говоря, мы делаем следующее: заставляем браузер скачать наш ресурс заранее, так что к тому времени, как нужно будет применять наш CSS, он уже будет скачан (ну или хотя бы начнет скачиваться). Ура!</p>
<h3>Дальнейшее чтение</h3>
<p><a href="http://calendar.perfplanet.com/2012/speed-up-your-site-using-prefetching/">Speed Up Your Site Using Prefetching</a>.</p>
<h2>CSS и быстродействие</h2>
<p>Во множестве разных статей написано, что, если вы используете отдельные домены для ресурсов, то с них нужно загружать все статические ресурсы: CSS, JS, изображения и т.п.</p>
<p>Одно из маленьких открытий, которое мы сделали в процессе работы, звучит так: <em>не раздавайте</em> CSS с поддомена.</p>
<p>Помните, как мы обсуждали блокировку отрисовки загрузкой стилей. Браузер хочет получить ваш CSS как можно скорее, CSS находится на <em>критическом пути —</em> то есть на том необходимом пути, который браузер проходит от момента, когда пользователь затребовал страницу, и до того, когда он что-то увидел. В отличие от JS и изображений, CSS находится там из-за своего свойства блокировать отрисовку. В ваших интересах проходить критический путь настолько быстро, насколько это возможно — то есть <strong>без дополнительных DNS-запросов.</strong></p>
<p>Однажды мы разрабатывали сайт, рабочее окружение которого раздавало все ресурсы с одного хоста (например, <code>foo.com</code>), но когда дело дошло до того, чтобы сделать его более приближенным к реальному, мы начали раздавать ресурсы с <code>s1.foo.com</code> и <code>s2.foo.com</code>. Получилось, что все изображения, JS, CSS и шрифты были расположены на разных доменах, что приводило к нескольким DNS-запросам. Проблема здесь в том, что необходимый для получения CSS-файла DNS-запрос, отправленный из холодного кэша, <em>сразу же</em> замедлял наш критический путь. Наши графики ужасно скакнули, показывая задержку, которой в теории не должно было случиться: все прогрессивные практики диктуют, что вы должны распределять большое количество ресурсов по поддоменам, не правда ли? CSS это <strong>не касается.</strong> Необходимый DNS-запрос занимал ощутимое время, на которое в итоге задерживалась отрисовка страницы.</p>
<p>CSS — один из самых страшных врагов быстродействия, <a href="http://www.phpied.com/css-and-the-critical-path/">подчеркивает Стоян Стефанов</a> — именно из-за этой блокировки отрисовки. Также стоит отметить, что браузер скачает <em>все</em> CSS-файлы перед тем, как начнет рендерить страницу. Это значит, что браузер <em>в любом случае</em> запросит <code>print.css</code>, даже если страница всего лишь выводится на экран. Это значит, что все стили, которые используются только вместе с медиавыражениями, например, следующие стили, скачаются в любом случае, даже если они не нужны:</p>
<pre><code tabindex="0" class="language-html">&lt;link rel=&quot;stylesheet&quot; media=&quot;screen and (min-device-width: 800px)&quot; href=&quot;desktop.css&quot;&gt;
</code></pre>
<p>Впрочем, <a href="https://twitter.com/andydavies">Энди Дэвис</a> сообщил мне, что WebKit расставляет приоритеты в порядке загрузки CSS таким образом, что <em>сперва</em> загружается только тот CSS, который нужен для первоначального отображения страницы, а загрузка всех остальных стилей (например, <code>print.css</code>) как можно дольше откладывается. Отлично!</p>
<p>Итак, то, что знаем про CSS, позволяет нам принять несколько решений и все они основаны на знании, что CSS блокирует отрисовку, запрашивается <em>весь сразу</em> и находится на вашем критическом пути:</p>
<ul>
<li><strong>Никогда не раздавайте CSS с отдельного домена,</strong> поскольку это приводит к задерживающим отрисовку DNS-запросам.</li>
<li><strong>Вставляйте CSS в HTML-код как можно раньше,</strong> чтобы браузер мог скачать его и заняться другими вещами.</li>
<li><strong>Собирайте CSS в один файл.</strong> Браузер все равно скачает весь CSS, поэтому гораздо лучше просто собрать все, что у вас есть, в один HTTP-запрос.</li>
<li><strong>Сожмите его с помощью gzip и минифицируйте,</strong> чтобы браузеру пришлось загружать меньше.</li>
<li><strong>Закэшируйте все, что можно,</strong> чтобы весь этот процесс происходил как можно реже.</li>
</ul>
<p>CSS находится на вашем критическом пути, так что вам нужно разобраться с ним быстро; он блокирует отрисовку, это означает, что пользователям загрузка страницы будет казаться медленной. <strong>Когда мы перенесли CSS на поддомен, наше быстродействие резко упало.</strong></p>
<h3>Дальнейшее чтение</h3>
<p><a href="http://www.phpied.com/css-and-the-critical-path/">CSS And the Critical Path</a></p>
<h2>Минификация и gzip</h2>
<p>Есть две простые вещи, которые вы можете (и должны) сделать со своими текстовыми ресурсами: минификация, чтобы убрать комментарии и пробелы, и сжатие с помощью gzip, чтобы ещё сильнее уменьшить размер.</p>
<p>Если выбирать из этого только что-то одно, то gzip эффективнее, чем минификация. Но если вы можете, делайте и то и другое.</p>
<p><em>Обычно</em> для того, чтобы включить gzip, нужно чуть-чуть поиграть с <code>.htaccess</code>, но, как подсказывает мой добрый друг <a href="https://twitter.com/makeusabrew">Ник Пэйн</a>, <code>.htaccess</code> — не самая лучшая вещь с точки зрения серверного быстродействия — <code>.htaccess</code> заново анализируется на <em>каждом</em> входящем запросе, поэтому при его использовании возникает достаточно большая нагрузка.</p>
<p>Итак, <a href="http://httpd.apache.org/docs/2.2/howto/htaccess.html">из документации Apache</a>:</p>
<blockquote>
<p>Вам стоит перестать использовать <code>.htaccess</code> полностью, если у вас есть доступ к конфигурационному файлу главного httpd-сервера. Использование <code>.htaccess</code> замедляет ваш Apache. Любую директиву, которую вы включаете в <code>.htaccess</code>, гораздо лучше поместить в блок <code>Directory</code>, поскольку эффект будет таким же, а быстродействие — лучше…</p>
</blockquote>
<p>Если же у вас есть только доступ к <code>.htaccess</code>, не волнуйтесь: эта нагрузка <em>обычно</em> настолько маленькая, что беспокоиться о нёй на стоит. Включить gzip с помощью <code>.htaccess</code> на самом деле очень просто. С минификацией сложнее, разве что у вас есть процесс сборки проекта, или если вы используете какой-то препроцессор или CodeKit, который компилирует исходники сразу в минифицированную версию.</p>
<p>Кстати, главной причиной, по которой я переписал <a href="http://inuitcss.com/">inuit.css</a> на Sass было (по крайней мере, сперва) то, что я мог легко скомпилировать минифицированную версию.</p>
<p>Основной эффект минификации сводится к тому, что она убирает пробелы и комментарии — если вы комментируете свой код так же подробно, как я, то вы наверняка захотите минифицировать свои ресурсы.</p>
<p>Gzip, как и любой другой алгоритм сжатия, берет любой <em>текстовый</em> исходник и сжимает его на основании повторяющихся строк. В целом, gzip сжимает код очень хорошо, потому что в любом коде, как правило, есть типовые строки, которые повторяются: например, <code>background-image</code> в CSS, <code>&lt;strong&gt;</code> в разметке и т.п.</p>
<p>Gzip <em>действительно</em> очень сильно уменьшает размер ваших ресурсов, и вам определенно нужно включать его. Если вам нужен хороший пример <code>.htaccess</code>, посмотрите, как это сделано <a href="https://github.com/h5bp/html5-boilerplate/blob/dac15682b35ad69f519205e1b82694d0cab189ca/.htaccess#L153">в HTML5 Boilerplate</a>.</p>
<p>Сжатие вашего контента это <em>гигантская</em> экономия. На данный момент <code>inuit.css</code> весит 77 КБ. После минификации и gzip-сжатия он весит всего лишь <strong>5,52 КБ.</strong> Таким образом, эти два приема дают нам экономию в 93%. Ну а раз gzip хорошо работает на текстовых ресурсах, то вы можете сжимать с его помощью и SVG, и даже некоторые форматы шрифтов!</p>
<h2>Оптимизация изображений</h2>
<p>Я не могу сказать, что я какой-то запредельный специалист в науке оптимизации изображений — я просто прогоняю их через утилиту оптимизации, но разбираться собственно с изображениями и их постобработкой очень интересно.</p>
<h3>Спрайты</h3>
<p>Если вы хотите, чтобы ваш сайт обладал хорошим быстродействием, спрайты просто-напросто обязательны: вы загружаете одну большую картинку через один HTTP-запрос, а не несколько изображений за несколько запросов. Проблема состоит в том, что не все изображения можно объединить в спрайт: например, если у вас есть иконка, которая должна быть фоновым изображением на элементе переменной длины, то у вас не получится это сделать — это не работает для элементов, размеры которых не фиксированы. Вы можете оставить много свободного места вокруг картинки в своём спрайте, но <a href="http://blog.vlad1.com/2009/06/22/to-sprite-or-not-to-sprite/">лишние пиксели в спрайтах сами по себе являются проблемой для быстродействия</a>.</p>
<p>Чтобы справиться с изображениями, которые нельзя поместить в спрайты, нам понадобится «спрайтовый элемент». По сути, это просто пустой элемент, обычно <code>&lt;i&gt;</code>, единственная задача которого — оставаться пустым и содержать фоновое изображение. (Всё же элемент <code>&lt;i&gt;</code> имеет <a href="https://web-standards.ru/articles/front-end-performance/articles/i-b-em-strong-elements/">другое назначение</a> и здесь больше подойдёт <code>&lt;span&gt;</code> — <em>прим. редактора.)</em></p>
<p>Я использовал такие элементы, когда разрабатывал <a href="http://www.skybet.com/">Sky Bet</a>, они используются на YouTube и Facebook, а <a href="https://twitter.com/snookca">Джонатан Снук</a> написал <a href="http://smacss.com/book/icon-module">целый раздел о них на SMACSS</a>.</p>
<p>Идея такая: если вы не можете использовать в элементе спрайт из-за того, что элемент тянется, то вы помещаете внутрь пустой элемент, чтобы иметь возможность поправить размеры. После этого сможете уже собрать спрайты в один файл, например:</p>
<pre><code tabindex="0" class="language-html">&lt;li&gt;
    &lt;a href=&quot;/profile/&quot;&gt;
        &lt;i class=&quot;icon icon--person&quot;&gt;&lt;/i&gt; Profile
    &lt;/a&gt;
&lt;/li&gt;
</code></pre>
<p>Здесь мы не можем отправить в спрайты ни для <code>&lt;li&gt;</code>, ни для <code>&lt;a&gt;</code>, так что мы просто ставим пустой <code>&lt;i&gt;</code>, в котором и находится иконка. Именно это — одна из тех вещей, которые я больше всего люблю в теме быстродействия: вы используете разные умные приемы, чтобы увеличить скорость загрузки страницы, но при этом используете разметку, которая традиционно считается «плохой». Отлично же!</p>
<h3>Дальнейшее чтение</h3>
<p><a href="http://blog.vlad1.com/2009/06/22/to-sprite-or-not-to-sprite/">To Sprite Or Not To Sprite</a>.</p>
<h2>Изображения для ретины</h2>
<p>Вам не нужно готовить для ретины <em>все</em> изображения. Картинка в два раза больше по ширине и высоте содержит <strong>в четыре раза</strong> больше пикселей, чем такая же картинка в стандартном разрешении. В четыре. Раза. Хоть это и не значит, что размер файла увеличится соответственно — все-таки у изображений свои методы сжатия — но памяти на это изображение требуется <em>в четыре раза больше.</em></p>
<p>Давайте мы на секунду остановимся и подумаем: изображения для ретины чаще всего (хоть и необязательно) нужны для того, чтобы отображение дизайна на телефонах было более четким. У телефонов гораздо меньше памяти, чем у других устройств. Таким образом, изображения для ретины занимают кучу памяти на устройствах, у которых, как известно, лишней памяти немного… Подумайте ещё раз, действительно ли вам нужны изображения для ретина, или вы можете пойти на разумный компромисс?</p>
<p>Изображения для ретины прекрасны, когда вам нужна красивая четкая графика, но нет никакого смысла в четкой графике, если на то, чтобы загрузить ее, уходит пять секунд. В большинстве случаев скорость должна стоять выше эстетики.</p>
<p>Вы можете побыть умным и отдавать всем устройствам изображения в 1,5 раза больше (чтобы на всех устройствах были достаточно хорошими), но, по-моему, лучше всего будет использовать изображения для ретины только тогда, когда это нужно.</p>
<p>Если ваша статистика использования браузеров позволяет, вместо растровых изображений вы можете использовать SVG или шрифт с иконками. На CSS Wizardry я использую SVG и это даёт мне следующие преимущества:</p>
<ul>
<li>Независимость от разрешения</li>
<li>Можно минифицировать</li>
<li>Можно сжимать в gzip</li>
</ul>
<p>Мой коллега <a href="https://twitter.com/sdmix">Мэтт Аллен</a> сделал шрифт с иконками, который можно использовать вместе со спрайтовым элементом, чтобы на странице были пригодные для ретина-дисплеев масштабируемые иконки.</p>
<p>Ещё вы можете попробовать сервисы вроде <a href="http://resrc.it/">ReSRC.it</a>, чтобы загружать изображения в зависимости от устройства и дополнительных условий.</p>
<h3>JPG с прогрессивной загрузкой</h3>
<p>Ещё один интересный аспект быстродействия — это его <em>восприятие</em>: дело не только в том, что говорят цифры, но и в том, есть ли <em>ощущение,</em> что сайт быстрый.</p>
<p>Вам наверняка знакома прерывистая загрузка больших JPG-изображений: 100 пикселей картинки загрузилось, пауза, ещё 50, пауза, потом ещё 200, потом — <strong>бах! —</strong> вся картинка загрузилась.</p>
<p>Это работа <em>обычного</em> (baseline) JPG, и она действительно выглядит крайне прерывистой. Если вы переключаетесь на JPG с прогрессивной загрузкой, всё происходит намного веселее: сперва появляется вся картинка, но сильно размытая, затем изображение постепенно фокусируется. Звучит хуже, чем предыдущем варианте, но на самом деле <em>ощущается</em>, что загрузка идет быстрее: пользователь сразу что-то видит, а качество постепенно улучшается. Эти изображения, как правило, чуть тяжелее своих стандартных версий, но в целом будет казаться, что изображение загружается <em>намного</em> быстрее.</p>
<p>Для того, чтобы создать JPG с прогрессивной загрузкой, нужно всего лишь включить соответствующий флажок в диалоге «Save for Web» в Photoshop — и дело сделано!</p>
<h3>Дальнейшее чтение</h3>
<p><a href="http://calendar.perfplanet.com/2012/progressive-jpegs-a-new-best-practice/">Progressive JPEGs: a New Best Practice.</a></p>
<h3>Не используйте изображения вообще</h3>
<p>Лучше, чем спрайты, SVG и игнорирование ретины — полный отказ от изображений. Если вы можете сверстать дизайн картинкой со стопроцентной точностью, а повторить его чистым CSS с точностью 75% — лучше выберите CSS-решение (конечно, если для этого не потребуются лишние сто строчек кода!). Если у вас нет картинок — нет и HTTP-запросов, а также это упрощает поддержку сайта. Если вы можете не использовать изображения — не используйте.</p>
<h2>Итоги</h2>
<p>Итак, вот целая куча решений (а на самом деле всего ничего), что вы можете применить, чтобы заставить браузер загружать клиентский код быстрее. Понимая, как работают браузеры, мы можем построить свой код так, чтобы наш фронтенд работал <em>ещё</em> быстрее.</p>
<p>Если у вас есть предложения, что можно было бы сюда добавить — или вы не согласны, или хотите поправить меня — пожалуйста, присоединяйтесь к обсуждению; весь этот мир быстродействия для меня относительно нов, поэтому я очень хочу продолжать учиться у других и узнавать что-то новое.</p>
<p>Я очень надеюсь, что эта простыня хоть немного помогла вам, и, прочитав её, вы получили знания о вещах, о которых вы, возможно, никогда бы и не задумались. Ещё я надеюсь, что эта статья помогла вам хотя бы <em>вполовину</em> так заинтересоваться вопросами быстродействия, как ими заинтересовался я.</p>
<p>Я особенно хотел бы поблагодарить <a href="https://twitter.com/makeusabrew">Ника Пэйна</a> и <a href="https://twitter.com/andydavies">Энди Дэвиса</a> за помощь в прояснении нескольких вещей, которые я описал в этой статье. Спасибо, парни!</p>
<h2>Дальнейшее чтение</h2>
<p>Если вам понравилась эта статья и вы хотите узнать ещё, я настоятельно рекомендую следующее:</p>
<ul>
<li>Книги Стива Саудерса: <a href="https://www.amazon.com/High-Performance-Web-Sites-Essential/dp/0596529309">High Performance Web Sites</a> и <a href="https://www.amazon.com/Even-Faster-Web-Sites-Performance/dp/0596522304">Even Faster Web Sites</a>.</li>
<li><a href="http://www.phpied.com/">Сайт Стояна Стефанова</a>.</li>
<li><a href="http://www.igvita.com/">Сайт Ильи Григорика</a>.</li>
<li><a href="https://twitter.com/andydavies">Читать Энди Дэвиса</a>.</li>
</ul>

                    ]]></description><pubDate>Mon, 15 Apr 2013 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/front-end-performance/</guid></item><item><title>Здравствуй, Blink</title><link>https://web-standards.ru/articles/hello-blink/</link><description><![CDATA[
                        <p>Как же хорошо открыто говорить о <a href="http://blog.chromium.org/2013/04/blink-rendering-engine-for-chromium.html">Blink</a> — новом движке для браузеров Opera <em>(примечание: это мой работодатель, но запись личная)</em> и Сhrome. Я знаю множество людей, которые переживали, что с уходом Opera Presto веб потеряет былое многообразие, но форк WebKit в Blink восстановит баланс сил. Opera будет развивать Blink.</p>
<p><em>(Добавлено позднее)</em> Мой начальник, Ларш Эрик Больстед (Lars Erik Bolstad) официально заявил от лица Opera:</p>
<blockquote>
<p>Мы намерены привнести опыт разработки движка Opera в Blink, начиная с внедрения новых веб-стандартов и заканчивая улучшением существующего кода.</p>
</blockquote>
<p>Мое личное мнение (не представляющее интересы моего работодателя, жены, детей и хомячка): Blink выглядит очень многообещающе. Его архитектура позволяет увеличить скорость работы — то, чему Opera и Google давно уделяют пристальное внимание. Вебу как платформе проще конкурировать с нативными приложениями, когда браузеры быстрые и совместимые. А ещё я надеюсь, что небольшим компаниям и даже отдельным людям станет проще участвовать в разработке движка благодаря более прозрачной системе курирования кода: «Наша цель в том, чтобы любой мог внести свой вклад в разработку, состоит он в какой-либо организации, или нет».</p>
<p>А ещё хорошо, что <a href="http://www.chromium.org/blink#vendor-prefixes">в Blink больше не будет префиксов</a> (только <a href="http://www.chromium.org/blink/developer-faq#TOC-Will-we-see-a--chrome--vendor-prefix-now-">унаследованные от WebKit</a>, да и они будут удалены или отброшены, как только это станет возможным). Префиксы — это как сольная карьера Моррисси (солист британской группы The Smiths — <em>прим. редактора):</em> отличная идея на бумаге и полный кошмар в реальности.</p>
<p>Так что, здравствуй, Blink. При том, что движок Presto проживёт до 2020 года, а Firefox с Samsung анонсировал работу над двумя молодыми проектами в основе нового движка <a href="https://blog.mozilla.org/blog/2013/04/03/mozilla-and-samsung-collaborate-on-next-generation-web-browser-engine/">Servo</a> — разнообразие веба никогда не выглядело так здорово, а совместимость — такой, эм-м, совместимой.</p>

                    ]]></description><pubDate>Sat, 06 Apr 2013 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/hello-blink/</guid></item><item><title>Простое вертикальное центрирование текста с помощью SVG</title><link>https://web-standards.ru/articles/svg-vertical-text-centering/</link><description><![CDATA[
                        <p>Существует множество способов вертикального выравнивания текста в контейнере произвольных размеров:</p>
<ul>
<li>использование <code>display: table</code>;</li>
<li>Flexbox;</li>
<li>хаки с использованием <code>display: inline-block</code>;</li>
<li>оборачивание текста в дополнительный элемент и его абсолютное позиционирование;</li>
</ul>
<p>…и, вероятно, множество других, о которых я уже забыла.</p>
<p>Однако, иногда бывает так, что ни один из них не подходит, и вот — я предлагаю ещё один вариант. Естественно, у него есть свои недостатки, но в некоторых случаях он подойдёт лучше, чем существующие решения.</p>
<p>Всё это началось, когда я обнаружила в SVG-спецификации свойство <code>text-anchor</code>. Оно определяет, куда ссылаются атрибуты <code>x</code> и <code>y</code> элемента <code>&lt;text&gt;</code> <em>(см. подробнее <a href="https://www.w3.org/TR/SVG/text.html#TextElementXAttribute" title="x">про x</a> и <a href="https://www.w3.org/TR/SVG/text.html#TextElementYAttribute" title="y">про y</a> в спецификации SVG. — прим. редактора).</em> Волшебство начинается, когда мы задаем в качестве значения <code>middle</code> и атрибуты <code>x</code> и <code>y</code> указывают на центр текста. Так что если задать значения 50%, они будут указывать в центр SVG-контейнера. А если высота и ширина установлены в 100%, текст окажется в центре контейнера, которым может быть любой HTML-элемент!</p>
<p>Следует учитывать, что центрируется в том числе и базовая линия текста, так что я постаралась найти способ адекватно её сместить. Установка <code>dominant-baseline: middle</code> для элемента <code>&lt;text&gt;</code> вроде бы решила проблему везде, кроме IE. В результате я просто задала элементу <code>&lt;text&gt;</code> <code>dy=&quot;.3em&quot;</code>, что сработало везде, но может требовать настройки сообразно реальной высоте строки <em>(см. подробнее <a href="https://www.w3.org/TR/SVG/text.html#TextElement">про элемент <code>&lt;text&gt;</code></a> и <a href="https://www.w3.org/TR/SVG/text.html#TextElementDYAttribute">про атрибут <code>dy</code></a> в спецификации SVG. — прим. редактора).</em></p>
<p>Кроме того, я вижу у метода следующие недостатки:</p>
<ul>
<li>избыточная разметка (а именно, 2 элемента: <code>&lt;svg&gt;</code> и <code>&lt;text&gt;</code>);</li>
<li>если текст занимает больше одной строки, вам придётся разбивать его на строки вручную;</li>
<li>не получится применить некоторые новомодные CSS-свойства: например, <code>text-shadow</code> в Chrome будет работать, а в Firefox — нет, поскольку формально оно всё ещё не входит в спецификацию SVG;</li>
<li>вам придётся дублировать цвет текста в свойстве <code>fill</code>, так как SVG не понимает СSS-свойство <code>color</code> (значение цвета из CSS можно получить с помощью <code>fill: currentColor</code> — <em>прим. редактора).</em></li>
</ul>
<p>Но есть и некоторые достоинства:</p>
<ul>
<li>не нужно трогать родительский контейнер;</li>
<li>мягкая деградация в браузерах, не поддерживающих SVG;</li>
<li>абсолютно доступный, не помешает SEO;</li>
<li>отлично работает в IE9, в отличие от Flexbox;</li>
<li>можно использовать любые стили для текста, доступные в SVG. Например, <code>stroke</code>!</li>
</ul>
<p>Результат показан ниже, посмотреть на код и поиграть с ним <a href="https://dabblet.com/gist/5229803">можно в Даблете</a>.</p>
<iframe src="https://dabblet.com/gist/5229803" width="510" height="510"></iframe>
<p>Проверено в последних Chrome, Firefox, IE9+. Несмотря на то, что этот способ не подойдёт для всех случаев, надеюсь, он вам пригодится.</p>

                    ]]></description><pubDate>Tue, 26 Mar 2013 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/svg-vertical-text-centering/</guid></item><item><title>Размеры блоков, или Назад в будущее</title><link>https://web-standards.ru/articles/box-sizing/</link><description><![CDATA[
                        <p>Технологии разработки сайтов меняются в корне. XHTML (Strict — мы так гордились своей работой) уступил место более свободному HTML5. Нарезанные картинки и div-обёртки заменяются гораздо более разумными (и, если повезет, семантичными) средствами HTML и CSS3, такими как <code>border-radius</code>, <code>box-shadow</code> и градиенты. Резиновая раскладка превращается в отзывчивый дизайн на медиавыражениях. JavaScript — уже не просто игрушка, и занимает положенное место в триумвирате с HTML и CSS. Может быть, мы уже близки к получению реального инструмента разметки с помощью Flexbox. Может быть, даже почтенные единицы <code>em</code> будут забыты, и их заменят более предсказуемые <a href="https://generatedcontent.org/post/11933972962/css3values"><code>rem</code></a>, а также <a href="https://generatedcontent.org/post/21279324555/viewportunits">гибкие единицы <code>vw</code> и <code>vh</code></a>.</p>
<p>Мы стоим на пороге третьей эры Открытого веба (век таблиц и однопиксельных гифов уже не более, чем история), и наши инструменты и процессы развиваются, начиная походить на инструменты обычных программистов — это <a href="http://rmurphey.com/blog/2012/04/12/a-baseline-for-front-end-developers/">скрипты сборки, препроцессоры, линтеры (при некотором приближении, напоминающие компиляторы), автоматическое тестирование и контроль версий</a>.</p>
<p>Но есть кое-что гораздо менее заметное, о чём бы я хотел поговорить сегодня, тоже способное по-своему изменить разработку сайтов. Это <a href="https://www.w3.org/TR/css3-ui/#box-sizing0">CSS-свойство <code>box-sizing</code></a>.</p>
<p>Те из вас, кто помнит тяжелые старые времена, помнит и неправильную блочную модель, которую IE использовал вместо стандартной модели W3C. В этой модели рамка (<code>border</code>) и отступ (<code>padding</code>) считались частью ширины (<code>width</code>) и высоты (<code>height</code>) блока, в то время как в модели W3C отступ и рамка прибавлялись к ширине и высоте при расчёте конечных размеров блока. В модели IE свойство <code>width</code> отражало конечную ширину блока, в то время как в модели W3C оно отражало ширину контентной части блока (то есть части блока, в которой находится непосредственно его содержимое). Чтобы заставить IE играть по правилам, мы должны были использовать <a href="http://tantek.com/CSS/Examples/boxmodelhack.html">хак блочной модели Тантека Челика</a>.</p>
<p>CSS-свойство <code>box-sizing</code> существует уже некоторое время и дает нам возможность переключаться между стандартной блочной моделью <code>box-sizing: content-box</code> и старой блочной моделью IE <code>box-sizing: border-box</code>. Недавний всплеск интереса к использованию этого забытого свойства связан с выходом статьи Пола Айриша <a href="https://paulirish.com/2012/box-sizing-border-box-ftw/"><code>* { box-sizing: border-box }</code> FTW</a>.</p>
<h2>Пора переключиться на border-box?</h2>
<p>Если вы хотите перейти сразу к делу, пожалуйста, <a href="https://docs.google.com/spreadsheet/viewform?formkey=dE83R3I0Z29PaldPYm5nalEyU3dsNmc6MQ">поучаствуйте в опросе</a> о <code>box-sizing</code>, а если нет — просто читайте дальше.</p>
<p>Использование <code>border-box</code> по умолчанию никогда не будет включено в стандарт, так как это скажется на отображении слишком большого количества сайтов, но не пришло ли время переключиться на использование его для всех элементов на всех проектах с помощью универсального селектора, как предлагает Пол? Об этом я думал в последнее время.</p>
<p>Если дело касается только моих собственных проектов или проектов с небольшими командами разработчиков, которых я знаю и с которыми могу общаться непосредственно, я думаю, сказал бы — да, это стоит сделать. Но когда речь идет о руководстве для многих разработчиков, проектах с открытым исходным кодом или демо-материалах, доступных для просмотра большому количеству пользователей, с которыми я никогда не общался, я всё ещё сомневаюсь.</p>
<h2>За и против</h2>
<p>Определенно, в лагере профессионалов есть несколько наиболее известных разработчиков. Пол Айриш, несомненно, фанат своего дела. Он подытоживает разочарование от текущей модели:</p>
<blockquote>
<p>Уф. Если я говорю, что ширина блока должна быть 200 пикселей, то блок должен занимать 200 пикселей по ширине, даже если у него есть отступы по 20 пикселей.</p>
</blockquote>
<p><a href="http://blog.joelambert.co.uk/">Джоу Ламберт</a> соглашается с Полом <a href="https://paulirish.com/2012/box-sizing-border-box-ftw/#comment-111776">в комментарии</a>:</p>
<blockquote>
<p>Я большой фанат этой техники, я использовал ее в некоторых мобильных веб-приложениях и все больше использую в веб-проектах.</p>
</blockquote>
<p><a href="https://adactio.com/">Джереми Кит</a> просто <a href="https://paulirish.com/2012/box-sizing-border-box-ftw/#comment-111784">называет её</a> «очень крутой». В то же время, <a href="https://twitter.com/stefsull">Стефани Рюис</a> в удивляется <a href="https://paulirish.com/2012/box-sizing-border-box-ftw/#comment-111772">в комментарии</a>:</p>
<blockquote>
<p>Мы должны лоббировать смену модели W3C. Я постоянно слышу, что разработчики разочарованы.</p>
</blockquote>
<p><a href="http://tjkdesign.com/">Терри Кобленц</a> предупреждает:</p>
<blockquote>
<p>К слову о «неправильной блочной модели», как мы привыкли её называть. Я думаю, проблема в том, что вы не можете управлять контентной областью блока, и любое изменение отступа или рамки окажет на нее влияние. Я понимаю, что людям может быть проще оформлять блоки таким способом, но это не волшебная палочка, и вам придется смириться с другими сложностями«.</p>
</blockquote>
<p><a href="http://weblogs.mozillazine.org/bz/">Борис Збарский</a> из Mozilla <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=243412#c21">говорит</a>:</p>
<blockquote>
<p>Спецификация недостаточно подробно описывает, как это должно работать, чтобы реализовать это при помощи CSS3 Box, вот что я могу сказать.</p>
</blockquote>
<p>В то время как <a href="http://dbaron.org/">Дэвид Барон</a> (тоже представитель Mozilla, а также рабочей группы CSS) заявляет:</p>
<blockquote>
<p><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=308801#c31"><code>box-sizing</code> — это слабо проработанное свойство, предназначенное только для обратной совместимости с Quirks Mode</a>.</p>
</blockquote>
<p>Насколько я вижу, «за» и «против» таковы:</p>
<h3>За</h3>
<ul>
<li>Гораздо проще рассчитывать размер элемента. Это тот размер, который я задаю, вне зависимости от того, какую толщину рамки или какой отступ я указываю.</li>
<li>Мы можем указывать ширину блока в процентах для тянущихся макетов, продолжая использовать <code>em</code> или <code>px</code> для указания размеров полей. При использовании традиционной модели нам понадобится свойство <code>calc</code> для решения этой проблемы. Читайте <a href="http://css-tricks.com/box-sizing/">отличный пост Криса Койера</a> о <code>box-sizing</code> с описанием этого случая.</li>
<li>Если вы собаку съели, разрабатывая под IE, то вы уже знаете, как использовать такую модель.</li>
</ul>
<h3>Против</h3>
<ul>
<li>Если вам требуется поддерживать IE7 и ниже, то у вас нет другого варианта, кроме как использовать специальные библиотеки или переводить IE в Quirks Mode (не лучшая идея). Firefox потребует использования префиксов, и вам потребуется префикс <code>-webkit</code>, если вы заботитесь об относительно свежих версиях WebKit. Смотрите <a href="https://caniuse.com/#search=box-sizing">список браузеров, поддерживающих свойство <code>box-sizing</code></a>.</li>
<li>Всё ещё есть баги в поведении Firefox, особенно при работе с <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=308801"><code>min-</code> и <code>max-width/height</code></a> <em>(к счастью, уже исправленные — прим. редактора).</em></li>
<li>Вы теряете контроль над размерами контентной области блока, что может привести к непредсказуемым последствиям. Например, что будет, если размер поля равен или больше ширины или высоты блока? (Самые дотошные могут открыть спецификацию и поискать правильный ответ).</li>
<li>Пожалуй, наибольшая проблема для меня — ожидания разработчиков. Если я использую <code>* { box-sizing: border-box; }</code> в коде, который должен использоваться и расширяться другими разработчиками, я уверен, что они с большей вероятностью будут ожидать блочной модели W3C, и их стили будут вести себя не так, как они ожидают. Более того, если речь идет о разработчиках-новичках, то они могут быть вообще незнакомы с моделью IE, что потребует от них изучения дополнительных материалов перед началом работы, даже если они поймут причину такого поведения.</li>
</ul>
<h2>Что вы думаете?</h2>
<p>С учетом всего сказанного и других причин, которые, я уверен, найдутся — что вы думаете по этому поводу? Если бы вы участвовали в проектах, где другие разработчики будут работать с вашим кодом, вы бы использовали <code>box-sizing: border-box</code>? Я создал <a href="https://docs.google.com/spreadsheet/viewform?formkey=dE83R3I0Z29PaldPYm5nalEyU3dsNmc6MQ">опрос</a>, так что вы можете дать мне об этом знать, и после получения результатов я сделаю отчет о том, что выбирают люди.</p>
<p>Через неделю в блоге появилась еще одна запись, уже с результатами оригинального опроса. Мы решили опубликовать перевод записи с результатами здесь же.</p>
<h2>Результаты опроса</h2>
<p>Прошла неделя с того дня, как я создал <a href="https://generatedcontent.org/post/25832131984/box-sizing">опрос об использовании <code>box-sizing: border-box</code></a>. За это время <a href="https://docs.google.com/spreadsheet/viewform?formkey=dE83R3I0Z29PaldPYm5nalEyU3dsNmc6MQ">форму</a> заполнили 256 человек. Результаты оказались такими:</p>
<div class="content__table-wrapper">
    <table>
        <thead>
            <tr>
                <th>Используете или будете использовать <code>box-sizing: border-box</code>?</th>
                <th>Да</th>
                <th>Нет</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>В текущем личном проекте</td>
                <td>71%</td>
                <td>29%</td>
            </tr>
            <tr>
                <td>В будущем личном проекте</td>
                <td>88%</td>
                <td>12%</td>
            </tr>
            <tr>
                <td>В будущем командном проекте</td>
                <td>85%</td>
                <td>15%</td>
            </tr>
            <tr>
                <td>В будущем «публичном» проекте</td>
                <td>60%</td>
                <td>40%</td>
            </tr>
        </tbody>
    </table>
</div>
<p>В приведённой таблице под «командным проектом» понимается проект, в котором разработчики знакомы с человеком, принимающем решение об использовании модели <code>border-box</code> — например, группа разработчиков в одной компании или группа друзей, с которыми вы близко общаетесь в ходе работы. Под «публичным проектом» понимается проект, в котором разработчиком может быть кто угодно, с кем вы, вероятно, не можете лично согласовать выбор модели — проект с открытым исходным кодом или публичное API.</p>
<p>Хотя это не окончательные результаты голосования, есть признаки того, что разработчики комфортно чувствуют себя при использовании модели <code>border-box</code> (или планируют её использовать) и рады использовать ее в «командных проектах». Меня немного удивило количество разработчиков, уже использующих эту модель. Число разработчиков, предпочитающих применять модель <code>border-box</code> в «публичных проектах» ниже, но все равно на 20% выше, чем число тех, кто не планирует этого делать. Если результаты опроса что-то значат, не удивляйтесь, если встретите все больше проектов, использующих модель, формально считающуюся неправильной.</p>
<p>Хотя разработчики в большинстве своем предпочитают использовать <code>box-sizing: border-box</code>, многие заявили, что будут делать это только при необходимости, не применяя его ко всем элементам с помощью универсального селектора. Многие обратили внимание, что использование может нарушить работу некоторых jQuery-плагинов, и это способно ограничить применимость модели на сайтах, активно использующих jQuery. Другие сообщили, что применяли бы эту модель, если бы она была документирована, как это было с <a href="http://warpspire.com/kss/">KSS-комментариями</a> (это хороший подход при работе в команде или даже в личных проектах, если вам требуется затем поддерживать собственный код).</p>
<p>Я оставлю <a href="https://docs.google.com/spreadsheet/viewform?formkey=dE83R3I0Z29PaldPYm5nalEyU3dsNmc6MQ">опрос открытым</a> в надежде собрать больше ответов. Но поскольку соотношение результатов в <code>box-sizing: border-box;</code> составило 60 к 40 в течение недели, я не жду, что результат значительно изменится.</p>
<p>Вы все еще можете принять участие в опросе. К сожалению, результаты опроса сейчас скрыты. Но это только подогревает наше любопытство. Расскажите: а что вы думаете о возможности выбора блочной модели? Используете <code>box-sizing: border-box;</code> в текущих проектах? Планируете использовать?</p>

                    ]]></description><pubDate>Wed, 23 Jan 2013 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/box-sizing/</guid></item><item><title>Основы адаптивной типографики</title><link>https://web-standards.ru/articles/responsive-typography/</link><description><![CDATA[
                        <p>Когда мы создаём сайты, мы обычно начинаем с определения основных параметров текста. Он диктует нам ширину главной колонки, а остальное выстраивается почти само собой. Так было раньше. До недавнего времени разрешение экрана было более или менее однородным, сегодня же мы имеем дело с разнообразными размерами и разрешениями, и это многое усложняет.</p>
<p>В пылу перезапуска (сайта компании — <em>прим. переводчика</em>) я написал небольшой пост в блог об адаптивной типографике, фокусируясь исключительно на главном аспекте нашего последнего эксперимента: адаптивных шрифтах. Без знания <a href="http://informationarchitects.net/">истории iA</a> вы упустите некоторые ключевые особенности адаптивной типографики и дизайна нашего нового сайта. Вместо того, чтобы смешивать все наши статьи по этому вопросу, я решил начать с нуля и объяснить, что такое адаптивная типографика шаг за шагом. Это первый шаг.</p>
<img src="https://web-standards.ru/articles/responsive-typography/images/perspective.png" alt="">
<p>Чтобы избежать создания различных макетов для каждого из возможных размеров экрана, множество веб-дизайнеров взяли на вооружение концепцию адаптивного веб-дизайна. Если в двух словах, то это идея раскладки, которая автоматически подстраивается к параметрам экрана. Для этого есть <a href="http://viljamis.com/blog/2012/adaptive-vs-responsive-whats-the-difference.php">разные способы</a>. Я предпочитаю использовать эти:</p>
<ol>
<li>Адаптивная раскладка: последовательное определение нескольких раскладок разной конечной ширины.</li>
<li>Резиновая раскладка: непрерывная подстройка раскладки к любой возможной ширине.</li>
</ol>
<p>Несмотря на то, что оба подхода имеют преимущества и недостатки, нам кажется, что адаптивность с наименьшим числом контрольных точек — это верный путь, так как читаемость гораздо важнее, чем всегда занимающая всю ширину экрана раскладка. Это сложный вопрос со спорными мнениями, но для оптимальной читаемости нужен определённый контроль над текстом, а в этом случае резиновая раскладка создаёт больше проблем, чем решает. Подробнее об этом в следующий раз.</p>
<p><em>Примечание:</em> Адаптивный дизайн уже включает в себя множество макропараметров типографики (кегль, интерлиньяж, ширина колонки). Таким образом, в большинстве случаев адаптивный дизайн уже содержит адаптивную типографику. То, о чём мы говорили в первом посте об адаптивной типографике на нашем сайте, в основном касалось использования градации шрифтов. Об этом мне бы хотелось поговорить в следующей раз, а сейчас давайте погрузимся в основы адаптивной макротипографики на экране.</p>
<h2>Выбор гарнитуры</h2>
<h3>Хороший тон</h3>
<p>Рано или поздно вам придётся выбрать, какую гарнитуру использовать. Выбор шрифта — это в основном вопрос вкуса, но, так как каждый шрифт имеет свои качества и требования (или ограничения), выбор шрифта приводит ко множеству визуальных и технологических последствий. Веб-шрифты сейчас представлены большим спектром гарнитур, так что поиски подходящей становятся очередной проблемой.</p>
<p>Чтобы поэкспериментировать с типами шрифтов, для нашего сайта мы разработали свой шрифт. Мы выбрали шрифт с засечками, потому что он подходит нашей манере говорить и подчёркивает нюансы содержимого (или, по крайней мере, нам так кажется). Для <a href="http://www.iawriter.com/">iA Writer</a> мы выбрали моноширинный шрифт. Поскольку главная цель нашей программы — помочь с написанием черновика, мы специально выбрали Nitti — гарнитуру, одновременно строгую и аккуратную. Решение использовать моноширинный шрифт пришло потому, что операционная система первого iPad не применяла кернинг к пропорциональным шрифтам. Вместо использования пропорциональных шрифтов, которые бы плохо отображались на экране, мы решили сразу начать с моноширинного.</p>
<h3>Шрифт с засечками или без?</h3>
<p>Обычно выбор стоит между шрифтами с засечками и без. Это сложный вопрос сам по себе, но есть простое правило, которое может помочь: «<a href="http://typotalks.com/berlin/de/blog/2011/05/19/oliver-reichenstein-we-are-the-medium/">Шрифт с засечками — священник, без засечек — хакер</a>». Один не лучше другого, но, по разным причинам, шрифт с засечками имеет более авторитарный оттенок, в то время как шрифт без засечек кажется более демократичным. Заметьте, что это пять тысяч лет истории типографики, пересказанные в двух небрежных строках, так что не воспринимайте их слишком серьёзно.</p>
<p>Множество людей до сих пор думают, что для экранной типографики задавать вопрос «с засечками или без» бессмысленно. На самом деле, всё не так просто. Вопреки всеобщим убеждениям, оба этих начертания будут хорошо смотреться, <em>если</em> выбрать размер основного текста больше 12 пикселей. Антиква меньше 12 пикселей отрисовываются недостаточно чётко, к тому же на современных мониторах 12 пикселей определённо мало (и это отсылает нас ко второму пункту).</p>
<h2>Какой размер?</h2>
<p>Кегль основного текста не зависит от личных предпочтений. Он зависит от дистанции чтения. Так как чаще всего компьютеры расположены дальше, чем книги, метрический размер шрифта на рабочем столе должен быть больше, чем размер шрифта, используемый в печатном аналоге.</p>
<p>Иллюстрация ниже показывает, что чем дальше ваш текст, тем больше он должен быть. Две чёрные и две красные А имеют одинаковые <em>физические размеры.</em> Но, так как правая пара расположена дальше, <em>воспринимаемый размер</em> меньше. Красная «A» на правой картинке имеет тот же воспринимаемый размер, что и чёрная «A» на левой:</p>
<img src="https://web-standards.ru/articles/responsive-typography/images/relative-type-size.png" alt="">
<p>Чем дальше вы держите текст, тем меньшим он кажется визуально. Чем дальше вы держите текст, тем больше нужно сделать шрифт, чтобы компенсировать большую дистанцию чтения. Насколько больше — это отдельная наука. Если у вас нет опыта, полезным упражнением будет держать книгу с хорошо напечатанным текстом на комфортной для чтения дистанции и сравнивать её с вашим сайтом.</p>
<p>Графические дизайнеры без опыта веб-дизайна удивляются, насколько огромен хороший основной текст в вебе по сравнению с печатным аналогом. Напоминаю, он большой, если их сравнивать рядом, а не в перспективе.</p>
<img src="https://web-standards.ru/articles/responsive-typography/images/relative-readability.png" alt="">
<p>Если после увеличения кегля основного текста до соответствия, новый размер поначалу раздражает, не волнуйтесь, это нормально. Однако, привыкнув к нему, вы не захотите возвращаться к «стандартным» малым размерам.</p>
<p>Мы <a href="http://informationarchitects.net/blog/responsive-typography-the-basics/100E2R">продвигаем эти «перспективно пропорциональные» размеры шрифтов</a> с 2006 года. Первоначально наше утверждение, что Georgia 16px — хороший эталон для основного текста, вызвало много гнева и даже смех, но сейчас это более или менее общий стандарт. С более высокими разрешениями этот стандарт постепенно устаревает. Но об этом чуть позже.</p>
<h2>Межстрочный интервал и контраст</h2>
<p>В то время как кегль может быть определён с помощью трюка с перспективой, интерлиньяж требует некоторых корректировок. С увеличением дистанции чтения и появлением размытости пикселей (как мы это называем), будет мудро делать межстрочный интервал экранного текста немного большим, чем у печатного. 140% — это хороший стандарт, но, конечно, это зависит от используемого шрифта.</p>
<img src="https://web-standards.ru/articles/responsive-typography/images/reading-distance.png" alt="">
<p>На сегодняшний день подразумевается, что вы не будете делать контраст слишком слабым (например, серый текст на светло-сером фоне) или слишком резким (как розовое на жёлтом). Так как экранные шрифты были разработаны для отображения чёрного на белом, тёмный фон использовать сложнее, но это возможно, если всё сделать верно. С современными высококонтрастными экранами также лучше выбрать либо тёмно-серый для текста, либо светло-серый для фона вместо жёсткого чёрного на белом. Но это, опять же, не самый главный вопрос.</p>
<h3>iPhone против iPad</h3>
<p>Многое из того, что мы узнали об адаптивной типографике, пришло из поиска совершенной типографики для нашего приложения. Проектируя iA Writer для iPad, мы потратили несколько недель на то, чтобы определиться с типографикой. Тогда высокое разрешение iPad было совершенно новым испытанием, и прошло какое-то время, прежде чем мы разобрались как это работает. Когда Apple анонсировала Retina-дисплей для iPhone, а позже и для iPad, всё снова изменилось. Можно написать целую книгу, чтобы объяснить, как мы дошли до канонического вида шрифта iA Writer, но хочется ещё рассказать об общих вопросах, так что перейду к делу.</p>
<p>Если сравнить текущую версию Writer для iPhone с версией для iPad, можно заметить, что размер шрифта не совпадает.</p>
<img src="https://web-standards.ru/articles/responsive-typography/images/type-size.png" alt="">
<p>Почему для iPhone и iPad используется разный кегль? Если вы внимательно прочли объяснение выше, вы уже могли догадаться.</p>
<ol>
<li>Поскольку расстояние не всегда одинаковое, обычно вы держите iPad немного дальше. Используете ли вы iPad за столом, кладёте на колени, или держите перед лицом, лёжа в кровати — в каждом случае комфортное для чтения расстояние будет своим. Это оказалось совершенно новой проблемой, так как расстояния до монитора компьютера и до экрана ноутбука не особо различались. Определяя размер шрифта, оптимальный во всех случаях, мы выбрали максимально дальнее положение для комфортного чтения. Следствием будет непривычно большой, но всё ещё комфортный, размер шрифта для чтения в постели, ну и обычно вы не будете печатать текст в приложении, лёжа в кровати на животе.</li>
<li>Экрану iPhone доступна гораздо меньшая площадь, из-за чего приходится вносить правки.</li>
</ol>
<p>К счастью, iPhone держат ближе к лицу, и вынужденное использование меньшего кегля отлично срабатывает. На среднем расстоянии и iPhone, и iPad имеют схожие по восприятию размеры текста.</p>
<img src="https://web-standards.ru/articles/responsive-typography/images/type-sizes.png" alt="">
<p>Так как iPhone держат ближе, то и высоту строки можно делать меньше, что также является предпочтительным для меньшего экрана:</p>
<img src="https://web-standards.ru/articles/responsive-typography/images/distance-differences.png" alt="">
<p>При проектировании для экрана не всё будет работать в вашу пользу. <em>Дизайн взаимодействия — это прикладная наука: не поиск безупречного вида, а поиск лучшего компромисса.</em> В нашем случае нам пришлось уменьшить межстрочный интервал, а также поля и межсимвольный интервал:</p>
<img src="https://web-standards.ru/articles/responsive-typography/images/compromises.png" alt="">
<p>Поправки были так тонки, что, не зная о них, трудно заметить маленький размер полей. Почему мы и вовсе от них не избавились? Поля — не просто вопрос эстетики, они позволяют тексту дышать и помогают переводить взгляд со строки на строку. Если вам кажется, что всё это смахивает на эзотерику — нет, ведь пока что мы рассказали только про самые основы.</p>
<h2>Как насчёт настольных компьютеров?</h2>
<p>Некоторые люди жалуются на большой шрифт в iA Writer для Mac. Аналогично тому, как мы выбрали наибольший минимальный размер шрифта для iPad (который держат на разном расстоянии при чтении), нам пришлось использовать тот же приём для Mac.
В то время ориентиром был 24-дюймовый iMac с высоким разрешением, с воспринимаемым размером примерно таким же, как и у других устройств.</p>
<img src="https://web-standards.ru/articles/responsive-typography/images/mac-ipad-iphone.png" alt="">
<p>Так как разнообразие компьютеров Mac, на которых работает iA Writer, конечно, мы смогли установить все возможные разрешения. Мы изучили каждую конфигурацию, чтобы быть уверенными, что выбранный размер шрифта — лучший компромисс для большинства машин.</p>
<img src="https://web-standards.ru/articles/responsive-typography/images/different-resolutions.png" alt="">
<p>Может возникнуть вопрос: «Почему бы просто не позволить пользователю настраивать размер шрифта?» Ну, подбор размера шрифта — не дело вкуса, а вопрос комфортного расстояния для чтения. Так как у большинства сайтов и приложений размер шрифта слишком мал, новые пользователи изначально выберут наиболее привычный размер шрифта, а именно — слишком маленький размер, и никогда не испытают полное удовольствие от использования нашего приложения. Смысл не в том, чтобы насадить всем пользователям определённое видение, а в том, что мы хотим, чтобы iA Writer работал без настроек, и всё что вы будете в нём делать — писать. Это стало открытым секретом его успеха, и отказ от него стал бы отказом от самой сути приложения. (Что нам нужно улучшить, так это доступность использования для людей со слабым зрением).</p>
<p>Хорошо, почему бы тогда не подстраиваться автоматически под разрешение экрана? Не будет ли это настоящей адаптивной типографикой? Это так, и мы работаем над чем-то похожим. Но сейчас при подстройке к разрешению, нужно правильно выбрать оптический вес, дабы быть уверенным, что шрифт на самом деле работает, как предполагалось с каждым размером и разрешением. С размером шрифта и разрешением оптика шрифта также изменяется. Вот почему у шрифта в iA Writer для Mac, iPad 1/2 и iPad 3 различные градации. Чтобы объяснить всю логику изменений размеров цифровых шрифтов и рассказать, о чём мы думали, создавая новый сайт, требуется чуть больше пространства и времени. Так что ожидайте второй части!</p>
<h2>Отзывы</h2>
<p>Несмотря на <a href="http://informationarchitects.net/blog/sweep-the-sleaze/">отсутствие социальных кнопок</a>, у этой статьи множество ретвитов и очень мало критических замечаний, они касаются в основном противостояния резиновой и адаптивной раскладок, об этом я бы хотел поговорить позднее. Я был удивлён, когда Джошуа Портер спросил:</p>
<blockquote>
<p><a href="https://twitter.com/iA">@iA</a> Вы увлекли меня вплоть до строчки «дизайн взаимодействия — это прикладная наука». Что вы имели ввиду? <a href="https://twitter.com/bokardo/status/208882881223856129">@bokardo</a></p>
</blockquote>
<p>На случай, если и другие люди задались этим вопросом… Полная цитата: «При проектировании для экрана не всё будет работать в вашу пользу. Дизайн взаимодействия — это прикладная наука: не поиск безупречного дизайна, а поиск лучшего компромисса». Обычно я говорю «Веб-дизайн — это прикладная наука: не поиск совершенства, а поиск лучшего компромисса». С термином «веб-дизайн» предложение становится немного яснее, из-за более очевидного технического подтекста. Я использовал «дизайн взаимодействия», потому что я использовал приложение в примерах.</p>
<p>Это означает, что в то время как графический дизайн даёт вам большую степень графического контроля, веб-дизайн с самого начала заставляет думать о компромиссе между визуальным дизайном и технологией. Для получения оптимального результата потребуется изучить множество различных решений, каждое со своими за и против, и попытаться найти лучший компромисс между всеми неоптимальными решениями.</p>
<p>На этом этапе в разговор часто вступают графические дизайнеры и пытаются доказать, что и им приходится иметь дело с большим количеством технологий. Конечно, вам приходится. Дизайн в принципе требует технологических знаний. В этом и заключается разница между производством двигателей автомобилей и сайтов, разница между дизайном сайтов и журналов. И дело в степени вовлечённости в разработку.</p>
<p>Резюмируя — это означает, что в процессе дизайна сайтов и приложений немалая часть нашей работы — это поиск компромиссов и решений с наименьшим количеством недостатков. Это то, что отпугивает многих графических дизайнеров, потому что они привыкли контролировать процесс. Подробнее об этом в великолепной презентации Хой Вин <a href="http://www.slideshare.net/khoiv/control-annotated">о разнице между экранным и графическим дизайном</a> (2007).</p>

                    ]]></description><pubDate>Mon, 10 Sep 2012 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/responsive-typography/</guid></item><item><title>Ещё одна CSS-техника замены текста изображением</title><link>https://web-standards.ru/articles/css-image-replacement/</link><description><![CDATA[
                        <p>Недавно в проект <a href="https://github.com/h5bp/html5-boilerplate">HTML5 Boilerplate</a> была добавлена новая техника замены текста изображением. В этой записи я расскажу, как она работает и как соотносится с альтернативными методами.</p>
<p>Ниже приведен CSS <a href="https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757c9e03dda4e463fb0d4db5a5f82d7">недавно обновлённого</a> вспомогательного класса (для замены текста изображением) из HTML5 Boilerplate. Этот код также попал во фреймворк <a href="http://compass-style.org/">Compass</a>.</p>
<pre><code tabindex="0" class="language-css">.ir {
    font: 0/0 a;
    text-shadow: none;
    color: transparent;
}
</code></pre>
<h2>Что делает каждая из деклараций?</h2>
<ul>
<li><code>font: 0/0 a</code> — сокращенная запись свойства, обнуляет размер шрифта и высоту строки. Значение <code>a</code> здесь выступает в роли очень короткого <code>font-family</code> (идея, взята <a href="https://github.com/bem/bem-bl/blob/c451e7bd44b298d23c7fff9bfafe1f0a514f6aaf/blocks-desktop/b-icon/b-icon.css">из реализации этого метода в БЭМ</a>). Валидатор CSS ругается, что использование <code>0/0</code> в сокращенной форме свойства <code>font</code> не соответствует стандарту, но все браузеры принимают такое объявление, и это, по-видимому, баг в самом валидаторе. Использование <code>font: 0px/0 a</code> позволяет пройти валидацию, но все равно — в коде декларация отображается как <code>font:0/0 a</code>, который валидатор пропускает.</li>
<li><code>text-shadow: none</code> — гарантированно снимает любую унаследованную тень текста. Это предотвращает появление любых остатков тени от текста поверх фона.</li>
<li><code>color: transparent</code> — декларация для браузеров, которые не уменьшают текст полностью (до состояния невидимости). Safari 4 — пример (чрезвычайно редкий) такого браузера. Также эта декларация может быть нужна мобильным браузерам. IE6-8 не распознаёт такое значение свойства <code>color</code>, но, к счастью, IE7-8 и без него не показывают никаких следов от текста. IE6 отображает бледный след.</li>
</ul>
<p>Во вспомогательном классе в HTML5 Boilerplate мы также обнуляем всё, что касается свойств <code>border</code> и <code>background-color</code>, которые могут быть заданы у элементов. Это упрощает использование вспомогательных классов для элементов типа <code>&lt;button&gt;</code> или ссылках, у которых по дизайну может быть задан фон или рамка.</p>
<h3>Преимущества перед методами с text-indent</h3>
<p>Новая техника позволяет избежать различных проблем, присущих методам, основанным на использовании <code>text-indent</code>, включая предложенный <a href="http://www.zeldman.com/2012/03/01/replacing-the-9999px-hack-new-image-replacement/">Скотом Келумом</a> способ избежать проблем с производительностью на iPad 1, связанных с большими отрицательными отступами.</p>
<ul>
<li>Работает в IE6-7 для элементов с <code>inline-block</code>. Техники, основанные на использовании <code>text-indent</code> изначально «сломаны» для <code>inline-block</code> элементах, в этом можно убедиться на <a href="http://jsfiddle.net/necolas/QZvYa/show/">этом примере</a>.</li>
<li>Не приводит к созданию контейнеров, выходящих за пределы экрана. Методы с <code>text-indent</code> приводят к отрисовке блока (иногда выходящего за экран) для любого текста, у которого есть положительный или отрицательный отступ. Иногда это приводит к проблемам с производительностью, но методы, основанные на размере шрифта, обходят эти проблемы.</li>
<li>Не нужно указывать особенный <code>text-align</code> и обрезать контейнер свойством <code>overflow</code>, так как текст сжат и не занимает места.</li>
<li>Не нужно скрывать <code>br</code> или задавать всему дополнительному HTML <code>display: inline</code>, чтобы обойти ограничения отступа текста. В новом методе таких проблем нет.</li>
<li>В результате этих доработок сократилось количество стилей.</li>
</ul>
<h2>Недостатки</h2>
<p>Совершенного метода заметы изображений не существует.</p>
<ul>
<li>Оставляет бледный след от текста в IE6.</li>
<li>С этим подходом невозможно использовать единицы <code>em</code> для полей элементов, которые используют эту технику, потому что размер шрифта равен нулю.</li>
<li>В Windows-Eyes есть баг, мешающий чтению текста, спрятанного этим методом. Ни в одной другой протестированной программе для чтения с экрана никаких проблем не было. Спасибо <a href="https://twitter.com/jkiss">@jkiss</a> за предоставленные <a href="https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757c9e03dda4e463fb0d4db5a5f82d7#commitcomment-1052728">детальные отчеты</a> и <a href="https://twitter.com/wilto">@wilto</a> за подтверждение работоспособности этого метода в JAWS 12 для IE6-8 и для Firefox 4-6.</li>
<li>Как и многие другие методы замены изображений, он не работает, когда CSS загружен, а изображения — нет.</li>
<li>Текст может оказаться не скрытым, если посетитель сайта использует пользовательские стили, в которых явно объявляется важность <code>font-size</code> для элементов, к которым вы применили стили этого метода замены изображений.</li>
</ul>
<p>Стоит отметить, что <a href="http://nicolasgallagher.com/css-image-replacement-with-pseudo-elements/">техника замены изображений NIR</a> свободна от этих недостатков, но не работает в IE6/7.</p>
<h2>Комментарии в заключение</h2>
<p>Я использую эту технику без значительных проблем уже около года, с тех пор как Джонатан Нил и я использовали её в эксперименте с clearfix. Фреймворк БЭМ также использует её для иконок. Основная идея <a href="http://www.maxdesign.com.au/articles/headings-as-images/">была предложена еще в 2003 году</a>, но браузерные проблемы тех дней не давали возможности для широкого использования.</p>
<p>Если вы встретитесь с любыми проблемами этой техники, пожалуйста, занесите их в <a href="https://github.com/h5bp/html5-boilerplate/issues">багтрекер на гитхабе HTML5 Boilerplate</a>, и приложите, если нужно, тестовый пример.</p>

                    ]]></description><pubDate>Mon, 10 Sep 2012 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/css-image-replacement/</guid></item><item><title>Как я научился любить скучные мелочи CSS</title><link>https://web-standards.ru/articles/boring-bits-of-css/</link><description><![CDATA[
                        <p>В будущем CSS есть много того, чего нам стоит ожидать с нетерпением: с одной стороны, там будет целый спектр новых методов, которые произведут революцию в вёрстке; с другой стороны — новый набор графических эффектов, которые позволят создавать на лету фильтры и шейдеры. Людям всё это страшно нравится, в журналах и блогах пишется огромное количество статей об этих нововведениях.</p>
<p>Но если эти инструменты можно назвать выставочными лошадками CSS, тогда, думается мне, стоит обратить немного внимания и на рабочих лошадей: на те компоненты языка, которые являются его составными частями: селекторы, единицы измерения, функции. Я частенько называю их «скучными мелочами» — хотя на самом деле я это говорю с большой теплотой, и вам стоит, мне кажется, разделить моё доброе к ним отношение.</p>
<p>Почему? Давайте пробежимся по парочке лучших нововведений в CSS из разряда скучных мелочей — тех мелочей, над которыми работали в плохо освещенных лабораториях, вдали от блеска новых глянцевых штучек на витринах. Некоторые из этих мелочей уже какое-то время с нами, но заслуживают того, чтобы больше людей о них знали; другие, напротив, только начинают появляться в браузерах. Но тем не менее они произведут революцию в том, как мы работаем — причем тихо, скромно и без претензий.</p>
<h2>Относительные единицы измерения</h2>
<p>Скорее всего, вы, умный и предусмотрительный веб-разработчик, работаете с относительными единицами измерения — то есть с <code>em</code> или процентами — так что эта проблема вам должна быть знакома: вам наверняка приходится сидеть за калькулятором, чтобы вычислить размеры — из-за наследования. Например, сейчас вполне обычный прием — установить базовый размер шрифта для документа, а потом использовать относительные единицы, чтобы установить размер шрифта для всех остальных элементов на странице. В CSS это выглядит примерно так:</p>
<pre><code tabindex="0" class="language-css">html { font-size: 10px; }
p { font-size: 1.4em; }
</code></pre>
<p>Здесь всё отлично, и никакой проблемы нет, пока у вас не появляется дочерний элемент, которому вы хотите установить какой-нибудь другой размер шрифта. Например, в такой разметке:</p>
<pre><code tabindex="0" class="language-html">А и Б сидели на &lt;span&gt;трубе&lt;/span&gt;.
</code></pre>
<p>Если вы хотите, чтобы этот <code>&lt;span&gt;</code> было меньшего размера шрифта, например, <code>1.2em</code>, то что вам делать? Берите калькулятор и считайте, сколько будет 1,2 поделить на 1,4, и в итоге у вас получится:</p>
<pre><code tabindex="0" class="language-css">p span { font-size: 0.85714em; }
</code></pre>
<p>И проблема не ограничивается использованием <code>em</code>. Если вы разрабатываете тянущийся сайт с использованием ширин в процентах, то знаете, что эти проценты соотносятся с размерами контейнера элемента, так что если у вас есть элемент, которому вы хотите поставить ширину в 40% от его родительского элемента, ширина которого — 75%, тогда придется устанавливать ширину этого элемента в 53,33333%.</p>
<p>Мягко говоря, не идеально.</p>
<h2>Размеры относительно корневого элемента</h2>
<p>Чтобы побороть эту проблему с размерами шрифтов, теперь нам доступна новая единица измерения — <code>rem</code> (корневой <code>em</code>). Это всё ещё относительная единица измерения, но она всегда соотносится с фиксированным базовым значением, а именно — с размером шрифта корневого элемента документа (в случае HTML это всегда элемент <code>&lt;html&gt;</code>). Если предположить, что мы используем тот же самый размер шрифта для корневого элемента, что и в прошлый раз (<code>10px</code>), то нам потребуются для этого случая следующие CSS-правила:</p>
<pre><code tabindex="0" class="language-css">p { font-size: 1.4rem; }
p span { font-size: 1.2rem; }
</code></pre>
<p>Теперь оба правила соотносятся с размером шрифта корневого элемента — это куда более элегантно, да и работать с этим удобнее, особенно если у вас простое базовое значение, например, <code>10px</code> или <code>12px</code>. Это как если вернуться назад и использовать значения в пикселях, только с возможностью их масштабировать.</p>
<p>Это одна из функций, перечисленных в этой статье, которая поддерживается браузерами очень хорошо: все современные браузеры, включая IE9, поддерживают эту единицу, <a href="https://caniuse.com/#feat=rem">нет её пока только в Opera Mobile</a>.</p>
<h2>Размеры относительно вьюпорта</h2>
<p>Если вам кажется, что единица <code>rem</code> — крутая (а я так думаю!), то вы будете в восторге от того, что узнаете, что существует ещё и новый набор единиц измерения, который поможет победить проблему с процентами. Эти единицы работают примерно так же, как <code>rem</code>, за исключением того, что они соотносятся не с установленным пользователем значением на корневом элементе документа, а с самим вьюпортом, или областью просмотра устройства.</p>
<p>Две базовых единицы здесь — <code>vh</code> и <code>vw</code>; они соотносятся (соответственно) с высотой и шириной вьюпорта. Каждая единица является числом, и это число равняется соответствующему проценту от указанного измерения (ширины или высоты). Пока я ещё помню уроки в школе сценаристов, давайте я лучше не буду объяснять, а покажу:</p>
<pre><code tabindex="0" class="language-css">div { height: 50vh; }
</code></pre>
<p>В этом примере высота блока будет равна точно половине высоты вьюпорта; <code>1vh</code> — это 1% от высоты вьюпорта, так что равенство <code>50vh</code> = 50% от этой высоты выглядит вполне логичным.</p>
<p>Если размер окна просмотра изменяется, то изменяется и значение соответствующих единиц, и при этом вам не нужно беспокоиться о вложенных элементах: элемент с шириной <code>10vw</code> всегда будет этой ширины, вне зависимости от ширины его родительского элемента.</p>
<p>Также есть единица <code>vmin</code>, которая соответствует меньшему значению — <code>vh</code> или <code>vw</code>; кроме того, недавно стало известно, что в спецификацию добавится и соответствующая единица <code>vmax</code> (хотя на момент написания статьи этого пока не случилось).</p>
<p>Сейчас эти единицы поддерживаются в IE9+, Chrome и Safari 6.</p>
<h2>Рассчитываемые значения</h2>
<p>Если вы будете работать с тянущимся или адаптивным дизайном, вы, без сомнения, столкнетесь с проблемой смешивания единиц измерения — когда вы хотите, чтобы у вас на странице была сетка, ширина у которой задается в процентах, но при этом с фиксированными полями. Например:</p>
<pre><code tabindex="0" class="language-css">div {
    margin: 0 20px;
    width: 33%;
}
</code></pre>
<p>Если в вашей верстке указаны только <code>padding</code> и <code>border</code>, тогда, в принципе, можно решить проблему с помощью <code>box-sizing</code>, но с <code>margin</code> это не поможет. Есть лучший и более гибкий подход — использовать для значения функцию <code>calc()</code>, которая позволяет вам производить математические действия с разными единицами, скажем:</p>
<pre><code tabindex="0" class="language-css">div {
    margin: 0 20px;
    width: calc(33% - 40px);
}
</code></pre>
<p>Вы можете применяться её где угодно, не только для ширин, но и везде, где вам нужны единицы изменения. А если вы хотите зайти совсем далеко, то вы тоже можете даже использовать <code>calc()</code> внутри <code>calc()</code>.</p>
<p>В IE9+ эта функция поддерживается без префикса(!), в Firefox — с префиксом <code>-moz-</code> (в релизе 16 или 17 он должен быть отброшен), а в Chrome и Safari — с префиксом <code>-webkit-</code>. <a href="https://caniuse.com/#feat=calc">В мобильный WebKit он, увы, пока не включен.</a></p>
<h2>Загрузка подмножества символов</h2>
<p>Быстрая загрузка веб-страниц всегда была важна — но сейчас, с появлением на рынке широкого спектра мобильных устройств (с каждым из которых понятие «скорость соединения» становится всё более изменчивым и неопределенным) это, пожалуй, особенно важно. Один из способов ускорить загрузку страницы — сократить размер внешних подгружаемых файлов, и поэтому новое свойство внутри <code>@font-face</code>, которое позволяет делать именно это — весьма полезное добавление.</p>
<p>Свойство, о котором идет речь — <code>unicode-range</code>, и в качестве значения оно принимает набор ссылок на юникод-символы. При загрузке внешних ресурсов из файла шрифта будут загружаться только эти символы, а не все символы, присутствующие в шрифте. Приведенный код показывает, как загрузить только три символа из файла foo.ttf:</p>
<pre><code tabindex="0" class="language-css">@font-face {
    font-family: foo;
    src: url('foo.ttf');
    unicode-range: U+31-33;
}
</code></pre>
<p>Особенно это полезно в том случае, если вы используете <a href="http://net.tutsplus.com/tutorials/html-css-techniques/quick-tip-ever-thought-about-using-font-face-for-icons/">иконки внутри шрифта</a> и хотите показывать на конкретной странице не все из них, а только конкретные. В одном тесте, который я провел, использование <code>unicode-range</code> сократило общее время загрузки файла шрифта в среднем на 0,85 секунды — а это вполне существенно. Конечно, у вас могут получиться и другие цифры.</p>
<p>Это свойство на данный момент поддерживается в IE9+ и браузерах на движке WebKit: Chrome и Safari.</p>
<h2>Новые псевдоклассы</h2>
<p>Единицы измерения и значения — это всё прекрасно, но особенно меня радуют селекторы и псевдоклассы. Когда у меня получается выдумать замысловатый селектор, даже если он в итоге будет запрятан там, где только немногие избранные смогут его найти, я всегда чувствую себя мастером своего дела. <a href="http://hbr.org/2012/04/the-real-leadership-lessons-of-steve-jobs/">Перефразируя отца Стива Джобса</a>: ты должен сделать так, чтобы забор со стороны твоего дома выглядел так же хорошо, как со стороны улицы, даже если больше никто не будет знать, что это так — ведь ты будешь.</p>
<p>Для меня стало откровением, когда я впервые использовал <code>:nth-of-type()</code>, это было словно я вышиб ногой двери восприятия… Окей, я чуть-чуть преувеличиваю. Но есть несколько новых CSS-псевдоклассов, относительно которых действительно стоит испытывать энтузиазм.</p>
<h2>Псевдокласс отрицания</h2>
<p>Вы наверняка не поймете, насколько в действительности полезен новый (относительно) псевдокласс отрицания <code>:not()</code>, пока сами его не попробуете. В качестве аргумента <code>:not()</code> передается простой, а не составной селектор. Когда список элементов создается селектором, включающим <code>:not()</code>, все элементы, которые соответствуют аргументу, исключаются из этого списка. Знаю, звучит сложно, мне тоже так кажется. Но на самом деле всё довольно просто.</p>
<p>Представьте себе: у вас есть список, и вы хотите применить правило ко всем нечётным элементам в списке, но не к последнему. Вам придётся написать что-то вроде такого:</p>
<pre><code tabindex="0" class="language-css">li { color: #00f; }
li:nth-child(odd) { color: #f00; }
li:last-child { color: #00f; }
</code></pre>
<p>С псевдоклассом отрицания вы можете исключить последний элемент из списка, используя в качестве аргумента <code>:last-child</code>. Таким образом количество правил уменьшится, и с кодом станет легче работать:</p>
<pre><code tabindex="0" class="language-css">li { color: #00F; }
li:nth-child(odd):not(:last-child) { color: #F00; }
</code></pre>
<p>Ничего принципиально нового здесь нет, и, как я показал, вполне можно работать и без него, но — это довольно-таки полезно. У меня была возможность применить этот псевдокласс в проекте, где использовался встроенный WebKit, и я раз за разом убеждался в его пользе. Честно, это один из моих любимых псевдоклассов.</p>
<p>Всё верно, у меня есть любимые псевдоклассы.</p>
<p>Из всех функций, рассмотренных в этой статье, это самая широко поддерживаемая; её поддерживают IE9+ и все современные браузеры — без префикса. Если вы работаете с jQuery, вполне возможно, что вы уже привыкли использовать этот синтаксис — в jQuery он есть начиная с версии 1.0, как и метод <code>not()</code>, который делает то же самое.</p>
<h2>Псевдокласс соответствия</h2>
<p>Псевдокласс соответствия <code>:matches()</code> принимает в качестве аргумента простой селектор, составной селектор, список, разделенный запятой, или любую комбинацию этих пунктов. Отлично! Но что же он делает?</p>
<p>Лучше всего он подходит для того, чтобы срезать лишнее с повторяющихся селекторов. В качестве сценария использования представьте себе, что у вас есть несколько элементов <code>&lt;p&gt;</code> в разных контейнерах, но вы хотите выбрать только некоторые из них; тогда правило в стилях будет выглядеть примерно так:</p>
<pre><code tabindex="0" class="language-css">.home header p,
.home footer p,
.home aside p {
    color: #f00;
}
</code></pre>
<p>С селектором <code>:matches()</code> вы можете значительно сократить его, найдя сходство в селекторах; в нашем примере у нас везде в начале стоит <code>.home</code>, а конце — <code>p</code>, так что мы можем использовать <code>:matches()</code> для того чтобы собрать все элементы между ними. Непонятно? Это выглядит вот так:</p>
<pre><code tabindex="0" class="language-css">.home :matches(header,footer,aside) p { color: #f00; }
</code></pre>
<p>В действительности это уже часть CSS4 (если быть совсем точными, спецификации CSS-селекторов уровня 4), и ещё в этой спецификации сказано, что вы сможете использовать такой же синтаксис — составные селекторы, разделенные запятой — в будущих версиях <code>:not()</code>. Здорово-то как!</p>
<p>На сегодняшний день <code>:matches()</code> есть в Chrome и Safari с префиксом <code>-webkit-</code>, а в Firefox он проходит под своим старым названием, <code>:any()</code>, с префиксом <code>-moz-</code>.</p>
<h2>Ну как, уже полюбили рабочих лошадок?</h2>
<p>Самое лучшее во всех этих новых возможностях — они решают совершенно реальные проблемы, от маленького, но раздражающего повторения селекторов до новых и появляющихся в настоящее время сложных задач, связанных с разработкой высокопроизводительных адаптивных сайтов. На самом деле, я могу легко представить, что регулярно использую каждое из этих нововведений.</p>
<p>Может быть, про новые возможности вроде фильтров больше пишут, но вы с гораздо большей вероятностью обнаружите, что именно те мелочи, которые представлены в этой статье, будут вам полезны в разработке.</p>
<p>Каждая из них сделает вашу профессиональную жизнь немножко проще, и в то же время расширит пространство ваших возможностей — а в этом ничего скучного, поверьте, нет.</p>

                    ]]></description><pubDate>Tue, 21 Aug 2012 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/boring-bits-of-css/</guid></item><item><title>Ценность осмысленности</title><link>https://web-standards.ru/articles/value-of-meaning/</link><description><![CDATA[
                        <p><a href="http://nimbu.in/">Дивья Маньян</a> — умница. Когда дело доходит до веба, можно с полной уверенностью сказать: она, чёрт побери, знает, что делает. Она относится к той категории людей, которые заставляют меня безумно им завидовать из-за их креативности и ума.</p>
<p>Всё это заставляет меня думать, что в каком-то смысле она нас сегодня троллит.</p>
<p>«<a href="https://web-standards.ru/articles/value-of-meaning/articles/pointless-semantic/">Наша бессмысленная погоня за семантической ценностью</a>» — недальновидный текст, из которого следует, что ценность разметки сегодня соотносится только с возможностями браузеров (и других программ, работающих с HTML) — и, таким образом, в жертву приносится завтра. Примерно такую статью я ожидал бы прочитать на рубеже нулевых от разработчика интранет-сайта большой корпорации, в которой сотрудникам разрешается использовать только Internet Explorer 6.</p>
<p>Когда я начал получать деньги за разработку сайтов, а не разноску пиццы, на меня постоянно стало обрушиваться понимание ценности разработки сайтов, не вписывающихся в ограничения тех браузеров, которые на тот момент имели меньше возможностей, чем конкуренты (а некоторые из них — один вы, наверное, знаете, с большой синей буквой «E» — были, мягко говоря, несколько устаревшими). Но здесь я занял дальновидную позицию: использовать по максимуму те функции, которые только-только начинают поддерживаться браузерами, и делать в необходимых случаях запасной трюк для тех браузеров, которые эту функцию ещё не поддерживают. Признаться, не вспомню, как мы это называли: <em>грейсфул деградейшн</em> или <em>прогрессив энхансмент</em> (я слышал столько раз эти два термина — что в правильном, что в неправильном смысле — что они у меня в голове сливаются в одно целое). Сейчас мы называем этот приём «здравым смыслом».</p>
<p>Вот, например, добавлял ли я когда-нибудь тэги <code>&lt;span&gt;</code> к элементам, у которых должны были быть скругленные уголки, а потом ставил ли фоновые картинки с четырьмя разными уголками на эти элементы, чтобы Гигантский Полоумный Синий Осел соблаговолил-таки отобразить красивые уголки? Да ни за что на свете! Нет, я использовал <code>border-radius</code>, зная в глубине души, что как-нибудь, когда-нибудь каждый браузер поймет, что, когда я писал это, я имел в виду, что хочу, чтобы милые круглые уголки пришли к нему среди ночи, потерлись носом и начали шептать нежности.</p>
<p>Я в курсе, что HTML — это не CSS, и что применимо к одному, необязательно применимо к другому. Я совершенно уверен, что, пожалуй, сейчас этот принцип даже больше применим к HTML, чем ранее был к CSS. За исключением парочки старинных браузеров, которым нужен <a href="http://code.google.com/p/html5shiv/">HTML5 Shiv/Shim</a> для спасения от неполноценности, на способность браузера отобразить элемент никак не повлияет то, используете вы <code>&lt;div&gt;</code> или <code>&lt;header&gt;</code>. Значит, нет ничего страшного в том, чтобы использовать семантический элемент заранее, до того времени, пока браузеры смогут извлекать какую-то пользу из его семантического значения.</p>
<p>Если кратко пересказать все тезисы статьи Дивьи (лучше <a href="https://web-standards.ru/articles/value-of-meaning/articles/pointless-semantic/">прочитайте её сами</a>, чем доверяйтесь моему пересказу, который в любом случае будет крайне упрощенным), то они состоят в том, что мы не должны забивать себе голову семантикой, потому что:</p>
<ol>
<li>Семантика — это сложно</li>
<li>Современные браузеры, поисковые системы и технологии доступности всё равно её не понимают, так зачем париться</li>
<li>Провести даже 40 минут, изучая разметку HTML5 — пустая трата времени</li>
</ol>
<h2>Семантика — это сложно</h2>
<p>Дивья ссылается на <a href="http://web.archive.org/web/20060428021228/http://diveintomark.org/archives/2002/12/30/the_tag_soup_of_a_new_generation">запись Марка Пилгрима</a> о сложности семантики. Окей, семантика — это сложно. И что? Ещё вчера я, кажется, был профессионалом, который делает работу за деньги. Это моя работа — знать, какая часть страницы сайта относится к навигации, а какая — к главному содержимому. HTML5, честное слово, не требует от меня так уж много мозговых усилий, чтобы принять решение, какой элемент для чего использовать. Я более чем убежден, что базовые понятия, которые выражают элементы <code>&lt;header&gt;</code>, <code>&lt;footer&gt;</code>, <code>&lt;nav&gt;</code>, <code>&lt;time&gt;</code>, <code>&lt;audio&gt;</code>, <code>&lt;video&gt;</code>, <code>&lt;progress&gt;</code> и <code>&lt;summary&gt;</code> вполне доступны для понимания человека, профессионально занимающегося разработкой сайтов. Согласен, разница между <code>&lt;article&gt;</code> и <code>&lt;section&gt;</code> довольно тонкая, но в общем и целом я не думаю, что семантика HTML5 — это запутанный инопланетный лабиринт, который пожирает умы психически здоровых людей.</p>
<h2>Современные браузеры, поисковые системы и технологии доступности всё равно её не понимают, так зачем париться</h2>
<p>Когда несколько лет назад я разрабатывал сайты, Internet Explorer не понимал <code>border-radius</code>. И, несмотря на это, я использовал его в разработке. И дело не только в том, что сейчас IE понимает (и отображает) эти красивые уголочки, а в том, что многие из этих сайтов все ещё существуют, с тем же кодом, что я написал давным-давно. Если бы из-за ограничений прежних браузеров я не решился бы использовать <code>border-radius</code>, эти сайты выглядели бы сейчас плохо из-за моей недальновидности.</p>
<p>HTML5 все ещё устаканивается. Некоторые значения, входящие в спецификацию, все ещё не до конца ясны. HTML5 вовсе не «готов» — об этом свидетельствует произвольная и довольно странная попытка Хикси <em>(редактора спецификации HTML5 — прим. редактора)</em> убрать из спецификации элемент <code>&lt;time&gt;</code>. Значит ли это, что я не должен использовать элементы из спецификации, потому что она ещё не готова? Ничего подобного! Сайты, которые я делаю сейчас, не просто будут висеть в интернете годами, скорее всего, они никогда не увидят редизайна. Поэтому работать, опираясь только на то, что «закончено», было бы медвежьей услугой для моих клиентов: они годами не смогут пользоваться преимуществами новых функций браузеров из-за технической формальности: статуса спецификации или полной браузерной поддержки.</p>
<p>И технологии доступности, и браузеры, и поисковые системы, все в какой-то момент времени в будущем подтянутся и смогут извлекать смысл из семантики. И когда это произойдет, вы бы хотели, чтобы ваш сайт был готов к этому, или вы предпочли бы потратить время (и деньги) на то, чтобы переписать ваш суп из дивов на что-нибудь несколько более осмысленное? Нет ничего плохого в том, чтобы использовать дивы сейчас (хотя элементы с большей семантической осмысленностью были бы лучше). Но существует большая вероятность, что это поставит ваш сайт в невыгодное положение потом, когда технологии разовьются.</p>
<p>Говорить, что я не должен использовать семантическую разметку, потому что технологии доступости, браузеры и поисковые системы в данный момент не используют её постоянно — это примерно то же самое, что сказать, что мне не нужно использовать элементы <code>&lt;video&gt;</code> и <code>&lt;audio&gt;</code>, потому что некоторые браузеры пока ещё не отображают их в полной мере. Это значит слишком крепко держаться за настоящее и тем самым ограничивать свою будущую выгоду. Сколько CSS-фокусов Дивьи, которые она показывает в презентациях и на конференциях, работают во всех современных браузерах? Подозреваю, что ни один. И разве это значит, что в этом нет смысла, что их не нужно использовать?</p>
<p>В чём разница между этим и использованием семантической разметки?</p>
<p>Да ни в чём.</p>
<h2>Провести даже 40 минут, изучая разметку HTML5 — пустая трата времени</h2>
<p>От того, что она это сказала, я просто в шоке. Я бы даже сказал, что это оскорбительно. Мы — профессионалы в такой области, которая требует постоянного обучения.</p>
<p>В конце того же самого поста Дивья предлагает разработчикам учить Javascript. Отличный совет. Учите, любите его. Но как можно говорить о ценности самообразования, если вместе с этим характеризуешь потраченные сорок минут на знакомство со смыслом некоторых элементов HTML5 как впустую потраченное время?</p>
<p>Бывает, что тот выбор, который мы делаем, готовя разметку, не приводит нас к манне небесной. Лично я стараюсь стремиться к осмысленной семантической разметке вовсе не из-за SEO (которую я лично вообще считаю разводкой в духе гербалайфа) или большей доступности. Я это делаю потому, что в том, чтобы делать что-то единообразно и правильно, есть самостоятельная ценность. В том, чтобы постоянно пытаться повышать уровень своего мастерства, есть смысл, и это достойная цель.</p>
<p>Нет ничего бессмысленного в том, чтобы стремиться к хорошим стандартам, в том числе — добавляя семантическую ценность к вашему сайту. Даже если в будущем не было бы никакой пользы от профессиональной, должным образом сделанной разметки (а польза будет), всё равно есть самостоятельная ценность в том, чтобы гордиться своей работой и иметь на выходе результат, который получается более элегантным, чем у вашего торопливого, неряшливого конкурента.</p>
<p>В том, чтобы стремиться к осмысленности, есть смысл.</p>
<p>Или, как Карл <a href="http://coding.smashingmagazine.com/2011/11/11/our-pointless-pursuit-of-semantic-value/#comment-554266">прокомментировал</a> пост Дивьи:</p>
<blockquote>
<p>инжбд, уцсацрйттуцчб — пргьйжед жйюб ирд чузу, ьчуёа футнсечб ихшз ихшзе. вчу цуынербтао путчхепч, пучухао ийоцчжшйч сйлиш теэнсн нийдсн. жа цсйэнжейчй уёхеёучпш н уцсацрйттуцчб. фйьербту.</p>
</blockquote>

                    ]]></description><pubDate>Thu, 09 Aug 2012 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/value-of-meaning/</guid></item><item><title>Наша бессмысленная погоня за семантической ценностью</title><link>https://web-standards.ru/articles/pointless-semantic/</link><description><![CDATA[
                        <blockquote>
<p>Мета-утопия — это мир достоверных метаданных. Когда же отравителю становится выгодно отравить колодец, то мета-воды становятся крайне токсичными очень быстро.
<a href="http://www.well.com/~doctorow/metacrap.htm">Кори Доктороу</a></p>
</blockquote>
<p>Позвольте мне описать одну ситуацию:</p>
<ol>
<li>Вы разрабатываете сайт.</li>
<li>И думаете: «Так. Надо бы добавить элемент».</li>
<li>Потом думаете: «Нет. Добавление <code>&lt;div&gt;</code> вызывает у меня чувство вины. Каша из дивов — это ужасно, я же про это знаю».</li>
<li>Потом: «Ну, нужно что-то ещё использовать. Например, сюда может подойти элемент <code>&lt;aside&gt;</code>».</li>
<li>Погуглив три раза и прочитав пять статей, вы приходите к выводу, что в данном случае использовать <code>&lt;aside&gt;</code> семантически неправильно.</li>
<li>Вы останавливаетесь на <code>&lt;article&gt;</code>, потому что по крайней мере это не <code>&lt;div&gt;</code>.</li>
<li>Только что вы выкинули в помойку 40 минут, ничего не прибавив к вашему коду.</li>
</ol>
<h2>Это просто полный отстой</h2>
<p>Эта тема поднимается уже не в первый раз. В 2003 году Энди Бадд написал статью <a href="http://www.andybudd.com/archives/2004/05/semantic_coding/">о выборе между семантической чистотой и семантическим реализмом</a>.</p>
<p>Если ваша главная проблема с HTML5 — выяснить какая <a href="https://www.impressivewebs.com/aside-vs-blockquote-html5/">разница между <code>&lt;aside&gt;</code> и <code>&lt;blockquote&gt;</code></a> или, скажем, <a href="http://twitter.theinfo.org/29661575610630145">как правильно разметить адрес</a> — знаете, вы используете HTML5 не для того, для чего он предназначен.</p>
<p>Разметка структурирует содержимое, но выбор элементов имеет гораздо меньшее значение, чем нас в течение долгого времени приучали думать. Давайте пройдемся по ряду причин.</p>
<h3>Веб больше не состоит из структурированного содержимого</h3>
<p>В золотые времена веба страницы замысливались как хранилища информации и смыслового содержимого — и ничего больше. Сейчас в вебе есть содержимое, но его смысл — производная от действий пользователей с этим содержимым.</p>
<p>XML, RDFA, Dublin Core и другие структурированные спецификации применимы для очень важных сценариев использования, но в число этих сценариев <a href="http://www.alexa.com/topsites">не входит большинство взаимодействий</a> внутри веба. Чёрт побери, нет ни одного сайта, на котором действительно существовала бы та идеальная семантическая разметка, которую требуют эти спецификации. Об этом гораздо лучше, чем я, пишет <a href="http://web.archive.org/web/20060428021228/http://diveintomark.org/archives/2002/12/30/the_tag_soup_of_a_new_generation">Марк Пилгрим</a>.</p>
<p>Если у вас есть содержимое, которое требует семантической чистоты: библиотечный каталог, документ с оглавлением, онлайн-книга (то есть всё, для чего имеет смысл семантическая чистота) — тогда, разумеется, верстайте его так, оно соответствовало алгоритму разметки HTML5 и спорьте сколько угодно о том, какой элемент должен быть <code>&lt;article&gt;</code>, а какой — <code>&lt;section&gt;</code>. Нет ни одного инструмента, с помощью которого пользователь мог бы напрямую, благодаря этому алгоритму, построить оглавление документа. Ни в одном браузере таких инструментов тоже нет.</p>
<h3>Это делает содержимое доступным?</h3>
<p>Если причина, по которой вы используете семантическую разметку — доступность для людей с нарушениями восприятия, поймите, пожалуйста, что в действительности доступность и семантическая разметка очень слабо коррелируют друг с другом по причине массового неправильного использования HTML-разметки в вебе. Я бы сослалась на пост Марка Пилгрима об этом, но он недоступен, так что <a href="http://krijnhoetmer.nl/irc-logs/whatwg/20090604#l-877">придется вот так</a>.</p>
<p>Теги <code>&lt;b&gt;</code>, <code>&lt;strong&gt;</code>, <code>&lt;i&gt;</code> и <code>&lt;em&gt;</code> совершенно эквивалентны тегу <code>&lt;span&gt;</code> <a href="https://www.w3.org/TR/2011/WD-html-aapi-20110414/">с точки зрения спецификации</a>. И некоторые теги HTML5 — тоже.</p>
<p>Как написано в <a href="http://www.html5accessibility.com/">HTML5 Accessibility</a>, практически каждый новый элемент HTML5 дает специальным возможностям для пользователей ровно столько же семантической информации, сколько элемент <code>&lt;div&gt;</code>. Так что если вы думаете, что использование элементов HTML5 сделает ваш сайт более доступным для людей с нарушениями зрения, подумайте ещё раз. Сколько дополнительной информации содержат теги <code>&lt;figure&gt;</code> и <code>&lt;figcaption&gt;</code>? <a href="http://www.paciellogroup.com/blog/2011/08/html5-accessibility-chops-the-figure-and-figcaption-elements/">Нисколько</a>.</p>
<p>Недавний <a href="https://html5doctor.com/time-and-data-element/">спор (или, скорее, разгромный пост) об элементе <code>&lt;time&gt;</code></a> — ещё одно доказательство недолговечности семантического значения, которое связывается с тем или иным элементом.</p>
<h3>Это облегчает поиск по содержимому?</h3>
<p>Если великая цель, для достижения которой вы используете семантическую разметку — SEO, что ж, знайте, что большинство поисковых систем не станут доверять странице больше из-за её разметки. Единственное, что рекомендуется в <a href="http://www.google.com/support/webmasters/bin/answer.py?hl=en&amp;answer=35291">руководстве Google по поисковой оптимизации</a> — использовать релевантные заголовки и ссылки (другие поисковые системы работают точно так же). Будете ли вы использовать элементы HTML5 или <code>&lt;strong&gt;</code> и <code>&lt;span&gt;</code> — это никак не повлияет на восприятие вашей страницы поисковыми системами.</p>
<p>Есть другой способ предоставлять поисковым системам больше данных — использовать <a href="http://schema.org/">микроданные</a> <em>(Microdata и Schema.org — прим. редактора).</em> Это никак не повлияет на положение вашей страницы в поисковой выдаче, но это <a href="http://www.google.com/support/webmasters/bin/answer.py?answer=1211158">добавит полезную информацию к результатам поиска</a> в том случае, если поисковая система посчитала ваш сайт подходящим.</p>
<h3>Это делает содержимое портируемым?</h3>
<p>Ещё одно широко разрекламированное преимущество семантического веба — портируемость данных. Предполагается, что все устройства должны чудесным образом понимать семантическую разметку, которая везде используется, и без всяких усилий извлекать оттуда информацию. Арье Грегор <a href="https://plus.google.com/105458233028934590147/posts/Q2Wnvy1ysBD">разоблачает этот миф</a>:</p>
<blockquote>
<p>… Ману Спорни сказал, что ребята, занимающиеся семантическим вебом, по результатам исследований заключили, что внешние данные (out-of-band data) сложнее держать синхронизированными с содержимым. Могу сказать, что в случае MediaWiki это не так… Единственные сценарии, которые я могу придумать для использования RDFa или микроданных вместо отдельного RDF — это либо если у вас нет достаточно хороших инструментов для генерации страниц, либо если вы хотите, чтобы метаданные обрабатывались специфическими, заранее известными клиентами, которые поддерживают только встроенные метаданные (например, поисковые системы, поддерживающие спецификацию Schema.org и т.п.). Если страница всё равно обрабатывается скриптом, и если автор скрипта имеет доступ к инструментам на стороне сервера, которые могут извлечь метаданные в отдельный RDF-поток, тогда в обычном случае опубликовать отдельный поток должно быть так же просто, как и встроенный. И это сохраняет довольно большой объём данных, который передается просто так при каждой загрузке страницы.</p>
</blockquote>
<h2>Что же теперь делать?</h2>
<ol>
<li>Никакого вреда от использования элементов <code>&lt;div&gt;</code> не будет, вы можете спокойно продолжить использовать их вместо <code>&lt;section&gt;</code> и <code>&lt;article&gt;</code>. Мне представляется, что мы должны использовать новые элементы для того, чтобы сделать разметку более читаемой, а не для того, чтобы добиться внутреннего семантического соответствия правилам разметки самим по себе. Если вы хотите использовать HTML5-элементы <code>&lt;section&gt;</code> и <code>&lt;article&gt;</code>, чтобы улучшить какую-нибудь текстовую документацию для того, кто действительно её впоследствии прочитает — пожалуйста, делайте это.</li>
<li>Уже сейчас существуют инструменты, которые пользуются элементами <code>&lt;nav&gt;</code>, <code>&lt;header&gt;</code> и <code>&lt;footer&gt;</code>. <a href="http://www.accessibleculture.org/research/html5-aria-2011/">NVDA делает заключение о предполагаемой семантике документа</a> с помощью этих элементов. Эти элементы просто понять и просто использовать.</li>
<li>Стандарт ARIA <a href="http://www.html5accessibility.com/tests/landmarks.html">достаточно хорошо поддерживается</a> программами, читающими с экрана, но <a href="http://www.accessibleculture.org/articles/2011/04/html5-aria-2011/">будьте осторожны</a> при их использовании с элементами HTML5.</li>
<li>В HTML5 масса <a href="http://platform.html5.org/">новых возможностей</a>. Мне кажется, что мы должны изучать их, использовать и высказывать своё мнение, чтобы сделать эти возможности более функциональными и стабильными. Да, большинство этих возможностей предполагают, что вы понимаете JavaScript, пишете на нём и применяете те функции, которые помогают пользователям более полно взаимодействовать с вашим сайтом. Если эта задача кажется вам слишком сложной, <a href="http://www.highercomputingforeveryone.com/">учитесь писать код</a>, <a href="http://yuilibrary.com/theater/douglas-crockford/crockford-tjpl/">особенно на JavaScript</a>.</li>
</ol>
<h3>Что вы думаете?</h3>
<p>Эта статья является авторским мнением и может не соответствовать мнению редакции. Согласны ли вы с автором? Расскажите об этом в комментариях. Если вы готовы высказаться самостоятельно, написав возражение — свяжитесь с нами.</p>

                    ]]></description><pubDate>Thu, 09 Aug 2012 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/pointless-semantic/</guid></item><item><title>В HTML5 добавлен новый атрибут translate</title><link>https://web-standards.ru/articles/translate-attribute/</link><description><![CDATA[
                        <p>В HTML5 недавно <a href="https://dev.w3.org/html5/spec/global-attributes.html#the-translate-attribute">добавился новый атрибут</a> — <code>translate</code>. На трёх <a href="http://multilingualweb.eu/">семинарах MultilingualWeb</a>, которые мы провели за последние два года, идея такого булева флага — «переводить или не переводить» постоянно вызывает довольно большой интерес у локализаторов, создателей текста, и всех тех, кто работает с языковыми технологиями.</p>
<h2>Как это работает?</h2>
<p>Обычно либо автор текста, либо среда вывода, которая автоматически формирует текст, ставит этот атрибут в разметку страницы. Кроме того, в ситуации промышленного перевода локализаторы могут добавлять этот атрибут на стадии подготовки перевода, чтобы избежать множества ошибок перевода при большом количестве языков в одном тексте.</p>
<p>На рендеринге страницы этот атрибут никак не сказывается (впрочем, можно добавить к нему свои стили, если вы найдёте для этого повод). Планируется, что этот атрибут в основном будет полезен для тех инструментов, которые используются в процессе перевода текста — будь это тщательная работа профессиональных переводчиков, быстрые API или облачные сервисы перевода коротких текстовых фрагментов.</p>
<p>Этот атрибут может быть применён к любому элементу, и может иметь одно из двух значений: <code>yes</code> или <code>no</code>. Если значение — <code>no</code>, инструменты перевода должны предохранять текст внутри элемента от перевода. Этот инструмент может быть автоматической системой перевода (как, например, в онлайн-сервисах Google или Microsoft) или профессиональной системой перевода, которая не будет давать переводчику случайно изменить текст.</p>
<p>Если установить этот флаг на элемент, то же самое значение будет применено ко всем вложенным элементам и атрибутам этих элементов.</p>
<p>Для того, чтобы система работала, не нужно использовать <code>translate=&quot;yes&quot;</code>. Если на странице нет атрибута <code>translate</code>, система перевода (или переводчик) должна предположить, что перевести нужно весь текст. Вряд ли значение <code>yes</code> будет использоваться широко, хотя оно может быть очень полезно, если вам нужно переопределить значение флага <code>translate</code> на родительском элементе и указать кусочки текста, которые должны быть переведены. Например, вы можете захотеть перевести комментарии на обычном языке в исходном коде, а сам код оставить без перевода.</p>
<h2>Зачем это нужно?</h2>
<p>Потребность в этом встречается довольно-таки часто. В спецификации HTML5 есть <a href="http://developers.whatwg.org/elements.html#the-translate-attribute">пример с игрой про пчелу и мёд</a>. Вот похожий, но на этот раз реальный пример из моего опыта работы в Xerox, где документация, которая переводилась на другой язык, относилась к устройству, на котором текст находился прямо на «железе» и поэтому не переводился.</p>
<pre><code tabindex="0" class="language-html">&lt;p&gt;Click the Resume button on the Status Display or the &lt;span class=&quot;panelmsg&quot; translate=&quot;no&quot;&gt;CONTINUE&lt;/span&gt; button the printer panel.&lt;/p&gt;
</code></pre>
<p>Оригинальный текст на английском — <em>прим. переводчика.</em></p>
<pre><code tabindex="0" class="language-html">&lt;p&gt;Нажмите кнопку «Продолжить» на дисплее состояния или кнопку &lt;span class=&quot;panelmsg&quot; translate=&quot;no&quot;&gt;CONTINUE&lt;/span&gt; на панели принтера.&lt;/p&gt;
</code></pre>
<p>Текст при переводе на русский язык — <em>прим. переводчика.</em></p>
<p>Вот ещё несколько примеров текста из жизни, которым принесёт пользу атрибут <code>translate</code>. Отрывок из книги, цитирующий название работы.</p>
<pre><code tabindex="0" class="language-html">&lt;p&gt;The question in the title &lt;cite translate=&quot;no&quot;&gt;How Far Can You Go?&lt;/cite&gt; applies to both the undermining of traditional religious belief by radical theology and the undermining of literary convention by the device of “breaking frame”…&lt;/p&gt;
</code></pre>
<p>Оригинальный текст на английском — <em>прим. переводчика.</em></p>
<pre><code tabindex="0" class="language-html">&lt;p&gt;Вопрос в названии, &lt;cite translate=&quot;no&quot;&gt;How Far Can You Go?&lt;/cite&gt;, относится и к отрицательному влиянию радикальной теологии на традиционные религиозные воззрения, и к отрицательному влиянию приема «разрыва рамок» на правила построения литературного произведения…&lt;/p&gt;
</code></pre>
<p>При переводе на русский язык. На русском языке в таких случаях, впрочем, название работы обычно принято переводить — <em>прим. переводчика.</em></p>
<p>Следующий пример со страницы о французском хлебе — «хлеб» по-французски «pain».</p>
<pre><code tabindex="0" class="language-html">&lt;p&gt;Welcome to &lt;strong translate=&quot;no&quot;&gt;french pain&lt;/strong&gt; on Facebook. Join now to write reviews and connect with &lt;strong translate=&quot;no&quot;&gt;french pain&lt;/strong&gt;. Help your friends discover great places to visit by recommending &lt;strong translate=&quot;no&quot;&gt;french pain&lt;/strong&gt;.&lt;/p&gt;
</code></pre>
<p>Оригинальный текст на английском — <em>прим. переводчика.</em></p>
<pre><code tabindex="0" class="language-html">&lt;p&gt;Добро пожаловать на страницу &lt;strong translate=&quot;no&quot;&gt;french pain&lt;/strong&gt; на Facebook. Присоединяйтесь, пишите отчёты и свяжитесь с &lt;strong translate=&quot;no&quot;&gt;french pain&lt;/strong&gt;. Помогите друзьям узнать отличные новые места, порекомендовав им &lt;strong translate=&quot;no&quot;&gt;french pain&lt;/strong&gt;.&lt;/p&gt;
</code></pre>
<p>При переводе на русский язык. Речь идёт о французской компании, которая называется «french pain» — <em>прим. переводчика.</em></p>
<p>Таким образом, добавив на вашу страницу атрибут <code>translate</code>, вы поможете читателям лучше понять ваш текст, если они читают его через системы автоматического перевода, и значительно сэкономить денежные и временные затраты системам перевода с большим потоком информации на разных языках.</p>
<h2>Как обстоят дела у Google Translate и Microsoft Translator?</h2>
<p>Службы перевода Google и Microsoft и ранее предоставляли возможность предохранять текст от перевода посредством добавления разметки, но делали они это (несколькими) разными способами. Будем надеяться, что новый атрибут очень поможет тем, что предоставит стандартный подход.</p>
<p>И Google, и Microsoft в настоящее время поддерживают синтаксис <code>class=&quot;notranslate&quot;</code>, но замена названия класса атрибутом — формальной частью языка — сделает эту функцию гораздо более надёжной, особенно в более широких контекстах. Например, инструмент переводчика может всегда считать, что HTML5-атрибут <code>translate</code> значит именно то, что он должен значить. Становится проще портировать эту идею и для других сценариев использования: скажем, для других API перевода или стандартов локализации (например, XLIFF).</p>
<p>Кстати, онлайн-сервис перевода компании Microsoft (которая, собственно, и предложила флаг <code>translate</code> в HTML5 некоторое время назад) уже поддерживает <code>translate=&quot;no&quot;</code>. До сих пор, конечно, это был проприетарный атрибут, и Google не поддерживал его. Однако, буквально вчера утром я совершенно случайно узнал, что WebKit и Chromium только что добавили поддержку атрибута <code>translate</code>, а вчера днём Google добавил поддержку <code>translate=&quot;no&quot;</code> к своему сервису онлайн-перевода. <a href="https://www.w3.org/International/tests/html-css/translate/results-online">Вот результаты нескольких тестов</a>, которые я провёл этим утром. Ни Microsoft, ни Google пока не поддерживают переопределение <code>translate=&quot;yes&quot;</code>. <em>Статья опубликована 22 февраля 2012 года (прим. переводчика)</em></p>
<p>Во всех этих проприетарных системах, впрочем, есть масса нестандартных способов синтаксически выразить эту же идею (даже если мы говорим только о Google и Microsoft).</p>
<p>Так, Microsoft поддерживает <code>style=&quot;notranslate&quot;</code>. Google не поддерживает этот синтаксис в списке возможных флагов для своего онлайн-сервиса перевода, но зато у них есть варианты, которые недоступны в сервисе Microsoft.</p>
<p>Например, если у вас есть целая страница, которая не должна переводиться, можете добавить <code>&lt;meta name=&quot;google&quot; value=&quot;notranslate&quot;&gt;</code> внутри элемента <code>&lt;head&gt;</code> вашей страницы, и Google не будет переводить никакой текст на этой странице. А ещё они поддерживают <code>&lt;meta name=&quot;google&quot; content=&quot;notranslate&quot;&gt;</code>. Конечно, это не должно быть специфично для Google, и единообразный способ делать это, то есть <code>translate=&quot;no&quot;</code> для элемента <code>&lt;html&gt;</code> — гораздо более удачное решение.</p>
<p>Также непонятно, кстати, и с тем, и с другим сервисом перевода, как делать доступными для перевода вложенные элементы внутри элемента, для которого <code>translate</code> установлено в значении <code>no</code> — а это иногда может понадобиться.</p>
<p>Как говорилось выше, новый HTML5-атрибут использует простую и стандартную возможность HTML, которая может заменить и упростить все эти различные подходы. И, конечно, это поможет авторам создавать такое текстовое содержимое, настройки локализации которого будут работать и с другими системами.</p>
<h2>А почему не использовать просто атрибут <code>lang</code>?</h2>
<p>Конечно, во время дискуссий о том, как правильно реализовать флаг <code>translate</code>, кто-нибудь обязательно должен был предложить именно это, но перегружать текст метками языков — это не решение. Например, языковая метка может указывать, какой орфографический словарь использовать для проверки какого текста. Это не имеет ничего общего с тем, нужно переводить текст или нет. Это разные концепции. Если в документе, где у элемента <code>&lt;html&gt;</code> указан <code>lang=&quot;en&quot;</code>, дальше на странице вы поставите <code>lang=&quot;notranslate&quot;</code>, орфография в тексте не будет проверяться, потому что указанный язык уже не английский. Кроме того, стили для <code>lang</code> перестанут работать, голосовые браузеры не будут правильно произносить текст и т.п.</p>
<h2>Больше, чем просто атрибут <code>translate</code></h2>
<p>Рекомендация <a href="https://www.w3.org/TR/its/">ITS (Internationalization Tag Set)</a>, сделанная W3C, предлагает использовать флаг возможности автоматического перевода в таком виде, как атрибут <code>translate</code>, только что добавленный к HTML5, но идёт дальше и описывает способ присвоить значение флага каким-либо элементам или наборам разметки по всему документу или в наборе документов. Например, вы можете определить (если так нужно для вашего содержимого), что по умолчанию все элементы <code>&lt;p&gt;</code> с указанным классом должны иметь флаг <code>translate</code> в значении <code>no</code> в каком-либо конкретном наборе документов.</p>
<p><em>Накануне публикации перевода был представлен первый <a href="https://www.w3.org/TR/2012/WD-its20-20120731/">рабочий черновик ITS 2.0</a>, следующей версии спецификации — прим. редактора.</em></p>
<p>Microsoft уже предлагает что-то в этом духе, хотя их подход предоставляет гораздо меньше возможностей, чем рекомендация ITS. Если вы пишете где-либо на странице (или в виджете) <code>&lt;meta name=&quot;microsoft&quot; content=&quot;notranslateclasses myclass1 myclass2&quot;&gt;</code>, то эта строчка гарантирует, что все CSS-классы, перечисленные после директивы <code>notranslateclasses</code>, будут вести себя так же, как класс <code>notranslate</code>.</p>
<p>Кроме того, движки автоматического перевода Microsoft и Google не переводят содержимое элементов <code>&lt;code&gt;</code>. Впрочем, обратите внимание, что относительно этого у вас особенно нет выбора — нет никаких инструкций относительно того, как переопределить это значение, если вы хотите, чтобы содержимое вашего элемента <code>&lt;code&gt;</code> всё же было бы переведено.</p>
<p>Кстати, в ближайших планах W3C есть организаций новой рабочей группы совместно с проектом Европейской Комиссии — MultilingualWeb-LT — чтобы дальше развивать идеи по спецификации ITS и разработать ряд её практических реализаций. Помимо всего прочего, эта группа будет искать новые способы интеграции нового атрибута <code>translate</code> в процессы и стандарты индустрии локализации. Ждите, скоро всё будет!</p>

                    ]]></description><pubDate>Mon, 30 Jul 2012 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/translate-attribute/</guid></item><item><title>Ключевые слова в линейных градиентах</title><link>https://web-standards.ru/articles/linear-gradient-keywords/</link><description><![CDATA[
                        <p>Использование линейных градиентов в CSS может приводить к самым разнообразным результатам, которые не назовёшь иначе как странными; иногда даже сам синтаксис кажется странным.</p>
<p>Давайте я сразу замечу, что кое-что из того, о чём я здесь рассказываю, еще не поддерживается широко, и даже, по правде говоря, эта спецификация еще не зафиксирована раз и навсегда. Скорее всего, она такой и останется, но могут быть и какие-то изменения. Даже если этого не случится — эта статья не о новом <strong>«используйте прямо сейчас!»</strong> приёме. Как и многое из того, что я пишу, это скорее маленькая прогулка по одному из уголков CSS с целью посмотреть, есть ли здесь что-нибудь интересное.</p>
<p>Хотя линейные градиенты кажутся очень сложными, в действительности они довольно просты. Вы определяете направление, по которому идет градиент, а затем — перечисляете столько контрольных точек цвета, сколько хотите. Таким образом, вы описываете изображение текстом, примерно как SVG. Это довольно важный пункт, его стоит помнить: линейный (и радиальный) градиент — это картинка, точно такая же, как любой GIF и PNG. Это означает, помимо всего прочего, что можно совместно использовать растровые изображения и градиенты в фоне элемента, используя синтаксис множественных фоновых изображений.</p>
<p>Но вернёмся к градиентам. Вот совсем простое градиентное изображение:</p>
<pre><code tabindex="0">linear-gradient(45deg, red, blue)
</code></pre>
<p>Значение <code>45deg</code> описывает линию градиента, то есть ту линию, которая определяет направление градиента. Линия градиента всегда проходит через центр фоновой области элемента, и в каком именно направлении она пройдет, определяете вы, автор CSS. В этом примере линия градиента направлена на угол в 45% от горизонтальной оси элемента. Значения <code>red</code> и <code>blue</code> — контрольные точки цвета. Здесь в цветах не определены положения контрольных точек, поэтому эти значения принимаются равными 0% и 100% соответственно, то есть вы получаете градиент, который переходит от красного к синему и идёт по указанному направлению.</p>
<p>Можно создать контрольные точки таким образом, чтобы переход между ними был резким:</p>
<pre><code tabindex="0">linear-gradient(45deg, green 50%, lightblue 50%)
</code></pre>
<figure>
    <img src="https://web-standards.ru/articles/linear-gradient-keywords/images/pic-1.png" alt="">
    <figcaption>Рисунок 1.</figcaption>
</figure>
<p>Так вы получите результат, показанный на рисунке 1, к которому я добавил (в Photoshop) стрелку, которая показывает направление линии градиента и центральную точку фоновой области. Каждая «полоска», составляющая градиент, перпендикулярна линии градиента — поэтому граница между двумя цветами в точке 50% перпендикулярна по отношению к линии градиента. Этот закон перпендикулярности работает всегда.</p>
<p>Определять направление в градусах — это замечательно (и, чтобы соответствовать синтаксису анимаций, математическое определение градусов будет заменено на углы по компасу, но об этом в другой раз), но можно ещё использовать и ключевые слова, описывающие направления. Причем двух разных типов.</p>
<p><em><strong>Обратите внимание,</strong> что задания направления градиента с помощью ключевых слов <strong>без</strong> ключевого слова <code>to</code>, описанное дальше, справедливо только для старой версии спецификации и применяется только для градиентов с префиксами. Прим. редактора.</em></p>
<p>Первый вариант использования — это просто объявить направление, перемешав и подставив слова из набора <code>top</code>, <code>bottom</code>, <code>right</code> и <code>left</code>. Диковатенько здесь то, что в этом случае вы определяете не то направление, куда двигается градиент, а откуда он выходит; то есть вы должны определить положение его начальной, а не конечной точки. Соответственно, если вы хотите, чтобы градиент шёл из нижнего левого угла в верхний правый, надо писать <code>bottom left</code>:</p>
<pre><code tabindex="0">linear-gradient(bottom left, green 50%, lightblue 50%)
</code></pre>
<figure>
    <img src="https://web-standards.ru/articles/linear-gradient-keywords/images/pic-2.png" alt="">
    <figcaption>Рисунок 2.</figcaption>
</figure>
<p>Но <code>bottom left</code> — не то же самое, что <code>45deg</code> (это справедливо только в том случае, если фоновая область элемента — правильный квадрат) Если эта область неквадратна, то линия градиента идет из одного угла в другой, а разделительные полоски — перпендикулярно ей, как на рисунке 2. И снова для ясности я добавил стрелку, показывающую градиентную линию, и центральную точку в Photoshop.</p>
<p>Естественно, это означает, что если фоновая область элемента изменит свои размеры по ширине или высоте, угол линии градиента тоже изменится. Если элемент станет шире по вертикали или уже по горизонтали, то линия повернется против часовой стрелки; уже по вертикали и шире по горизонтали — по часовой. Вполне возможно, именно такой вариант вам и нужен. Во всяком случае, он явно отличается от задания значения угла в градусах, при котором градиент не будет поворачиваться никуда, как бы ни менялся размер фона элемента.</p>
<p><em><strong>Обратите внимание,</strong> что способ задания направления градиента с ключевым словом <code>to</code>, описанный дальше, по стабильной версии спецификации является единственным возможным способом, и должен применяться только для градиентов без префикса. Прим. редактора.</em></p>
<p>Другой способ использовать ключевые слова выглядит похоже, но результат у него совершенно другой. Вы используете те же самые определения <code>top/bottom/left/right</code>, но перед ними пишете ключевое слово <code>to</code>, вот так:</p>
<pre><code tabindex="0">linear-gradient(to top right, green 50%, lightblue 50%)
</code></pre>
<figure>
    <img src="https://web-standards.ru/articles/linear-gradient-keywords/images/pic-3.png" alt="">
    <figcaption>Рисунок 3.</figcaption>
</figure>
<p>В этом случае ясно, что вы определяете конечную точку линии градиента, а не начальную — в конце концов, вы же пишете <code>to top right</code>. Однако, в таком случае вы не направляете градиент в правый верхний угол фоновой области элемента. Вы определяете общее направление — вперед и направо. Результат предыдущего определения виден на рисунке 3: снова стрелка с линией градиента добавлена в Photoshop.</p>
<p>Обратите внимание на линию резкого разделения между контрольными точками. Она идет от левого верхнего угла в правый нижний (ни тот, ни другой — не <code>top right</code>). Так происходит потому, что, написав ключевое слово <code>to</code> в начале определения, мы включили то, что называется «магическими углами». Когда «магические углы» включены, то как бы фоновая область элемента ни изменяла свой размер, эта разделительная линия всегда будет идти из левого верхнего в правый нижний угол. Собственно, вот это и есть магические углы. Таким образом, линия градиента направлена не в правый верхний угол (только если фоновая область элемента — правильный квадрат), а в правый верхний квадрант фоновой области. По-видимому, термин «магические квадранты» посчитали менее удачным, чем «магические углы».</p>
<p>Если размер фоновой области изменится, эффект будет тем же самым, что в прошлый раз; уменьшим высоту или увеличим ширину фоновой области — линия градиента повернется по часовой стрелке; сделаем обратные изменения размера — линия пойдет в обратном направлении. Единственная разница — в начальном состоянии.</p>
<p>Итак: если вы хотите использовать ключевые слова, которые заставляют градиент быть всегда направленным в угол (как на рис. 2), но хотите указать направление, а не исходную точку — то этого сделать не получится. Точно так же нельзя указать и исходный квадрант, в котором начинается градиент. Чтобы получилась линия градиента, которая всегда идет из угла в угол, нужно объявить начальную точку линии градиента (рис. 2). Если вы хотите получить эффект «магических углов», когда линия контрольной точки цвета на 50% всегда идет из угла в угол, нужно объявить квадрант направления (рис. 3).</p>
<p>Теперь что касается собственно поддержки: на момент написания статьи <em>(26 апреля 2012 — прим. редактора)</em> только Firefox и Opera поддерживают «магические углы». Все остальные браузеры — IE10 в случае Internet Explorer — поддерживают математические углы и ключевые слова без «магических углов» (то есть Opera и Firefox поддерживают оба типа). Никто ещё не переключился с математических углов на углы по компасу (я совершенно специально использовал значение <code>45deg</code>, поскольку в обеих системах оно описывает одно и то же направление).</p>
<p><em><strong>Обратите внимание,</strong> что на момент публикации перевода (27 июля 2012) спецификацию в полной мере (включая направление поворота и ключевое слово <code>to</code>) поддерживают без префиксов: тестовая версия Firefox 16 Aurora и последняя тестовая версия IE10 в составе Windows 8 Release Preview — прим. редактора.</em></p>
<p>Таково состояние дел с линейными градиентами прямо сейчас. Мне очень интересно узнать, что вы думаете о различных форматах ключевых слов и их принципах работы — у меня самого были вначале некие проблемы с их пониманием, и то, что у двух форматов синтаксиса совершенно разные результаты, по-моему, делает всё это довольно-таки запутанным. А вы что скажете?</p>

                    ]]></description><pubDate>Tue, 24 Jul 2012 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/linear-gradient-keywords/</guid></item><item><title>Элементы figure и figcaption</title><link>https://web-standards.ru/articles/figure-figcaption/</link><description><![CDATA[
                        <p>В обычных печатных изданиях, таких как книги и журналы, изображения, таблицы или примеры кода обычно сопровождаются подписью. До сих пор у нас не было возможности семантически выделять такой тип содержимого напрямую в HTML, вместо того чтобы прибегать к именам классов СSS. HTML5 надеется исправить эту проблему с помощью новых элементов: <code>&lt;figure&gt;</code> и <code>&lt;figcaption&gt;</code>. Давайте разберемся!</p>
<h2>Элемент <code>&lt;figure&gt;</code></h2>
<p>Предполагается, что элемент <code>&lt;figure&gt;</code> будет использоваться в сочетании с элементом <code>&lt;figcaption&gt;</code> для того, чтобы выделять диаграммы, иллюстрации, фотографии и примеры кода (помимо прочего). Вот что говорится о <code>&lt;figure&gt;</code> в спецификации:</p>
<blockquote>
<p>Элемент <code>&lt;figure&gt;</code> представляет собой фрагмент независимого содержимого, совсем необязательно с подписью, который как правило относится к отдельному элементу из основного содержимого документа, и может быть удалён из документа без ущерба его смыслу.
<a href="https://dev.w3.org/html5/markup/figure.html">Спецификация W3C</a></p>
</blockquote>
<h2>Элемент <code>&lt;figcaption&gt;</code></h2>
<p>Этот элемент стал <a href="https://adactio.com/journal/1604/">поводом</a> <a href="https://remysharp.com/2009/08/12/saving-figure-detail/">серьезных</a> <a href="https://html5doctor.com/legend-not-such-a-legend-anymore/">споров</a>. Спецификация изначально предлагала приспособить для этих целей элемент <code>&lt;legend&gt;</code>, вместо того, чтобы придумывать новый элемент. Были и другие предложения, включавшие <code>&lt;label&gt;</code>, <code>&lt;caption&gt;</code>, <code>&lt;p&gt;</code> и даже заголовки с <code>&lt;h1&gt;</code> по <code>&lt;h6&gt;</code>. Семантика элемента <code>&lt;legend&gt;</code> <a href="https://html5doctor.com/legend-not-such-a-legend-anymore/">изменилась</a>, и поэтому мы начали использовать комбинацию <code>&lt;dt&gt;</code> и <code>&lt;dd&gt;</code> внутри <code>&lt;figure&gt;</code> по предложению <a href="https://twitter.com/adactio">Джереми</a>. Но большинство этих идей были отклонены из-за отсутствия обратной совместимости для CSS-оформления.</p>
<p>Наши постоянные читатели знают, что недавно <a href="https://html5doctor.com/summary-figcaption-element/">был представлен новый элемент</a> <code>&lt;figcaption&gt;</code>. Кто знает, приживется ли он, а пока давайте узнаем, что о нём говорит спецификация:</p>
<blockquote>
<p>Элемент <code>&lt;figcaption&gt;</code> представляет собой заголовок или описание для <code>&lt;figure&gt;</code>.</p>
<p><a href="https://dev.w3.org/html5/markup/figcaption.html">Спецификация W3C</a></p>
</blockquote>
<p>Элемент <code>&lt;figcaption&gt;</code> является необязательным и может появляться до <em>или</em> после содержимого внутри <code>&lt;figure&gt;</code>. Только один элемент <code>&lt;figcaption&gt;</code> может быть помещен в <code>&lt;figure&gt;</code>, хотя сам элемент <code>&lt;figure&gt;</code> может содержать несколько дочерних элементов (например, <code>&lt;img&gt;</code> или <code>&lt;code&gt;</code>).</p>
<h2>Использование <code>&lt;figure&gt;</code> и <code>&lt;figcaption&gt;</code></h2>
<p>Итак, мы узнали, что об этих элементах говорится в спецификации. Как же их использовать? Давайте рассмотрим это на примерах.</p>
<h3><code>&lt;figure&gt;</code> для изображения</h3>
<p>Изображение в элементе <code>&lt;figure&gt;</code> без подписи:</p>
<figure>
    <img src="https://web-standards.ru/articles/figure-figcaption/images/orang-utan.jpg" alt="Малыш орангутанга свисает с каната.">
</figure>
<p>Вот код для этого:</p>
<pre><code tabindex="0" class="language-html">&lt;figure&gt;
    &lt;img src=&quot;orang-utan.jpg&quot; alt=&quot;Малыш орангутанга свисает с каната.&quot;&gt;
&lt;/figure&gt;
</code></pre>
<h3><code>&lt;figure&gt;</code> с изображением и подписью</h3>
<p>Изображение внутри элемента <code>&lt;figure&gt;</code> с поясняющей подписью:</p>
<figure>
    <img src="https://web-standards.ru/articles/figure-figcaption/images/macaque.jpg" alt="Макака на дереве.">
    <figcaption>Наглая макака из Борнео. Фото <a href="https://www.flickr.com/photos/rclark/102352241/in/set-72057594082373448/">Ричарда Кларка</a></figcaption>
</figure>
<p>И код, который мы использовали:</p>
<pre><code tabindex="0" class="language-html">&lt;figure&gt;
    &lt;img src=&quot;macaque.jpg&quot; alt=&quot;Макака на дереве.&quot;&gt;
    &lt;figcaption&gt;
        Наглая макака из Борнео.
        Фото &lt;a href=&quot;…&quot;&gt;Ричарда Кларка&lt;/a&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;
</code></pre>
<h3><code>&lt;figure&gt;</code> с несколькими изображениями</h3>
<p>Размещение нескольких изображений внутри одного элемента <code>&lt;figure&gt;</code> с общей подписью:</p>
<figure>
    <img src="https://web-standards.ru/articles/figure-figcaption/images/kookaburra.jpg" alt="Кукабара.">
    <img src="https://web-standards.ru/articles/figure-figcaption/images/pelican.jpg" alt="Пеликан на пляже.">
    <img src="https://web-standards.ru/articles/figure-figcaption/images/lorikeet.jpg" alt="Наглый многоцветный лорикет.">
    <figcaption>
        Слева направо: кукабара, пеликан и многоцветный лорикет.
        Фотографии <a href="https://www.flickr.com/photos/rclark/">Ричарда Кларка</a>
    </figcaption>
</figure>
<p>И сам код:</p>
<pre><code tabindex="0" class="language-html">&lt;figure&gt;
    &lt;img src=&quot;kookaburra.jpg&quot; alt=&quot;Кукабара.&quot;&gt;
    &lt;img src=&quot;pelican.jpg&quot; alt=&quot;Пеликан на пляже.&quot;&gt;
    &lt;img src=&quot;lorikeet.jpg&quot; alt=&quot;Наглый многоцветный лорикет.&quot;&gt;
    &lt;figcaption&gt;
        Слева направо: кукабара, пеликан и многоцветный лорикет.
        Фотографии &lt;a href=&quot;…&quot;&gt;Ричарда Кларка&lt;/a&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;
</code></pre>
<h3><code>&lt;figure&gt;</code> с блоком кода</h3>
<p>Элемент <code>&lt;figure&gt;</code> может быть также использован для примеров кода:</p>
<figure>
    <blockquote>
        <p><code><small>
            <a rel="license" href="http://creativecommons.org/licenses/by-sa/3.0/">
                Creative Commons Attribution Share-alike license
            </a>
        </small></code></p>
    </blockquote>
    <figcaption>
        Использование элемента <code>&lt;small&gt;</code> вокруг ссылки на лицензию Creative Commons с <code>rel="license"</code>.
    </figcaption>
</figure>
<p>Ниже приведен код для этого:</p>
<pre><code tabindex="0" class="language-html">&lt;figure&gt;
    &lt;blockquote&gt;
        &lt;p&gt;&lt;code&gt;&lt;small&gt;
            &lt;a rel=&quot;license&quot; href=&quot;…&quot;&gt;
                Creative Commons Attribution Share-alike license
            &lt;/a&gt;
        &lt;/small&gt;&lt;/code&gt;&lt;/p&gt;
    &lt;/blockquote&gt;
    &lt;figcaption&gt;
        Использование элемента &lt;code&gt;&amp;lt;small&amp;gt;&lt;/code&gt;
        вокруг ссылки на лицензию Creative Commons
        с &lt;code&gt;rel=&quot;license&quot;&lt;/code&gt;.
    &lt;/figcaption&gt;
&lt;/figure&gt;
</code></pre>
<h3>Различия между <code>&lt;figure&gt;</code> и <code>&lt;aside&gt;</code></h3>
<p>Мы уже говорили об элементе <code>&lt;aside&gt;</code> <a href="https://html5doctor.com/aside-revisited/">в предыдущей статье</a>, но важно отметить разницу между ними. При выборе между <code>&lt;aside&gt;</code> или <code>&lt;figure&gt;</code>, стоит спросить себя, имеет ли содержимое элемента важное значение для понимания содержимого:</p>
<ul>
<li>Если содержимое просто имеет отношение и не является существенным, то используйте <code>&lt;aside&gt;</code>.</li>
<li>Если содержимое является важным, но его положение в потоке общего содержимого не так важно, используется элемент <code>&lt;figure&gt;</code>.</li>
</ul>
<p>Обратите внимание, что если положение содержимого в тексте тесно связано с предыдущим и последующим содержимым, следует использовать более подходящие элементы — например, <code>&lt;div&gt;</code>, старый добрый <code>&lt;img&gt;</code>, <code>&lt;blockquote&gt;</code>, или даже <code>&lt;canvas&gt;</code>, в зависимости от типа содержимого.</p>
<h2>Не останавливайтесь на достигнутом!</h2>
<p>Не стоит ограничивать использование <code>&lt;figure&gt;</code> изображениями и примерами кода. Другим содержимым, подходящим по смыслу для использования в элементе <code>&lt;figure&gt;</code> может быть аудио, видео, графики (возможно, с использованием <code>&lt;canvas&gt;</code> или <code>&lt;svg&gt;</code>), стихи или таблицы со статистикой.</p>
<p>Однако использование элемента <code>&lt;figure&gt;</code> не всегда целесообразно. Например, графический баннер не стоит размечать в <code>&lt;figure&gt;</code>. Используйте для этого просто <code>&lt;img&gt;</code>.</p>
<h2>Вывод</h2>
<p>Как мы продемонстрировали в этой статье, элемент <code>&lt;figure&gt;</code> открывает много возможностей. Только не забудьте убедиться, что он подходит для конкретного случая. Хотя вряд ли вы бездумно относитесь к разметке, так ведь?</p>

                    ]]></description><pubDate>Mon, 12 Mar 2012 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/figure-figcaption/</guid></item><item><title>Разбираемся с vertical-align</title><link>https://web-standards.ru/articles/vertical-align/</link><description><![CDATA[
                        <p>«Опять <code>vertical-align</code> не работает!» — вздохнёт веб-разработчик.</p>
<p>CSS-свойство <code>vertical-align</code> — одно из тех, которые с виду очень просты, но могут вызвать вопросы у начинающих разработчиков. Я думаю, что даже у многих ветеранов CSS когда-то были проблемы с тем, чтобы его до конца понять.</p>
<p>В этой статье я постараюсь в понятной форме рассказать про это свойство.</p>
<h2>Чего оно не делает</h2>
<p>Распространенное заблуждение о <code>vertical-align</code> состоит в том, что применяясь к одному элементу, оно заставляет все элементы внутри него изменить свою вертикальную позицию. Например, когда элементу задан <code>vertical-align: top</code>, это подразумевает, что его содержимое поднимется к его же верхней границе.</p>
<p>Вспоминаются времена, когда мы делали раскладки на основе таблиц:</p>
<pre><code tabindex="0" class="language-html">&lt;td valign=&quot;top&quot;&gt;
    Что-нибудь…
&lt;/td&gt;
</code></pre>
<p>В данном примере с ячейкой таблицы использование свойства <code>valign</code> (в настоящее время <a href="https://www.w3.org/TR/html5/obsolete.html#non-conforming-features">исключенного из HTML5</a>) приведёт к тому, что элементы внутри ячейки прижмутся к её верху. И естественно, когда верстальщики начинают использовать <code>vertical-align</code>, они думают, что получится то же самое, и содержимое элемента выровняется в соответствии со значением свойства.</p>
<p>Но <code>vertical-align</code> работает не так.</p>
<h2>Чем оно является на самом деле</h2>
<p>Использование свойства <code>vertical-align</code> может быть разбито на три простых для понимания правила:</p>
<ol>
<li>Оно применяется только к строчным элементам <code>inline</code> или строчным блокам <code>inline-block</code>.</li>
<li>Оно влияет на выравнивание самого элемента, а не его содержимого (кроме случаев, когда применяется к ячейкам таблицы).</li>
<li>Когда оно применяется к ячейке таблицы, выравнивание влияет на содержимое ячейки, а не на неё саму.</li>
</ol>
<p>Иными словами, следующий код не даст никакого эффекта:</p>
<pre><code tabindex="0" class="language-css">div {
    vertical-align: middle; /* эта строка бесполезна */
}
</code></pre>
<p>Почему? Потому что <code>&lt;div&gt;</code> — это не строчный элемент и даже не строчный блок. Конечно, если вы сделаете его строчным или строчным блоком, то применение <code>vertical-align</code> даст желаемый эффект.</p>
<p>С другой стороны, при правильном применении (к строчному элементу или строчному блоку), свойство <code>vertical-align</code> заставит текущий элемент выровняться относительно других строчных элементов.</p>
<p>Выше или ниже расположится элемент, будет зависеть от высоты строчных элементов на этой же строке или от свойства <code>line-height</code>, заданного для неё.</p>
<h2>Несколько картинок</h2>
<p>Вот картинка с пояснительным текстом, которая поможет вам понять, что происходит при вертикальном выравнивании строчных элементов:</p>
<img src="https://web-standards.ru/articles/vertical-align/images/vertical-align.png" alt="">
<p><a href="https://jsbin.com/isuvob/1/edit#html,live">А вот пример</a>, в котором есть несколько строчных элементов, один из которых прижат к верху.</p>
<h2>Ключевые слова</h2>
<p>Несколько ключевых слов, которые можно задавать в качестве значений для свойства <code>vertical-align</code>:</p>
<ul>
<li><code>baseline</code>, значение по умолчанию или «изначальное»</li>
<li><code>bottom</code></li>
<li><code>middle</code></li>
<li><code>sub</code></li>
<li><code>super</code></li>
<li><code>text-bottom</code></li>
<li><code>text-top</code></li>
<li><code>top</code></li>
</ul>
<p>Возможно, многие из них вы не будете использовать, но было бы неплохо знать все имеющиеся варианты. Например, <a href="https://jsbin.com/isuvob/edit#html,live">на демо-странице</a>, из-за того что значение <code>vertical-align</code> для <code>&lt;input&gt;</code> установлено как <code>top</code>, он выровнен по самому высокому элементу в строке (большой картинке).</p>
<p>Однако если вы не хотите выравнивать элемент относительно картинок или других строчных элементов, обладающих блочными свойствами, вы можете выбрать значение <code>text-top</code> или <code>text-bottom</code>, тогда элементы будут выравниваться относительно текста в строке.</p>
<h2>О ключевом слове <code>middle</code></h2>
<p>К сожалению, правило <code>vertical-align: middle</code> не выровняет строчный элемент по середине самого высокого элемента в строке (как вы, возможно, ожидали). Вместо этого значение <code>middle</code> заставит элемент выровняться по середине высоты гипотетической строчной буквы <strong>«x»</strong> (так же называемой <em>x-height</em>). Потому, мне кажется, что это значение на самом деле должно называться <code>text-middle</code>, чтобы стало понятно, какой будет результат.</p>
<p>Взгляните <a href="https://jsbin.com/apiqog/edit#html,live">на пример</a>, где я увеличил размер шрифта так, чтобы размер <em>x-height</em> стал гораздо больше. После этого станет понятно, что значение <code>middle</code> не получится использовать очень часто.</p>
<h2>Числовые значения</h2>
<p>Возможно, вы не знали о том, что <code>vertical-align</code> принимает числовые и процентные значения. Однако это так, и вот примеры их использования:</p>
<pre><code tabindex="0" class="language-css">input {
    vertical-align: 100px;
}

span {
    vertical-align: 50%;
}

img {
    vertical-align: -300px;
}
</code></pre>
<p>Несмотря на то, что вы можете прочитать <a href="https://www.w3.org/TR/CSS21/visudet.html#propdef-vertical-align">в спецификации</a> раздел, описывающий, какие есть ключевые слова и значения, я думаю, гораздо полезней будет самостоятельно <a href="https://jsbin.com/isuvob/edit#html,live">поиграть с ними</a> и сравнить результаты.</p>
<h2>Заключение</h2>
<p>Если в одной фразе подводить итог о том, как использовать это традиционно неправильно понимаемое свойство, я бы сказал:</p>
<p>Свойство <code>vertical-align</code> работает только со строчными элементами или строчными блоками и ячейками таблицы. В случае применения не к ячейкам таблицы, оно действует на сам элемент, а не на его содержимое.</p>

                    ]]></description><pubDate>Fri, 09 Mar 2012 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/vertical-align/</guid></item><item><title>Атрибут contenteditable</title><link>https://web-standards.ru/articles/contenteditable/</link><description><![CDATA[
                        <p>Мы уже давно используем различные технологии для редактирования и хранения текста в браузере. С атрибутом <code>contenteditable</code> это становится намного проще. В этой статье я расскажу для чего этот атрибут, как он работает и куда нам двигаться дальше.</p>
<h2>Основы</h2>
<p>Давайте обратимся к спецификации:</p>
<blockquote>
    <p>Атрибут <code>contenteditable</code> обладает фиксированным набором значений, он может быть пустой строкой, <code>true</code> или <code>false</code>. Пустая строка или <code>true</code> обозначают, что элемент доступен для редактирования. <code>false</code> обозначает, что элемент недоступен для редактирования. Есть еще третье состояние — <code>inherit</code>, это значение атрибута по умолчанию и оно означает, что значение наследуется от родительского элемента.</p>
    <footer>
        <cite><a href="https://www.whatwg.org/specs/web-apps/current-work/multipage/editing.html#contenteditable">WHATWG</a>.</cite>
    </footer>
</blockquote>
<p>В основном, атрибут <code>contenteditable</code> должен был обеспечивать работу WYSIWYG-редакторов. Скорее всего, вы встречали его в инструментах подобных Symphony или на сайтах вроде Flickr, где вы начинаете редактировать материалы, просто кликнув в определенную область.</p>
<p>Как упоминалось выше, <code>contenteditable</code> может быть в трёх возможных состояниях:</p>
<ol>
<li><code>contenteditable=&quot;&quot;</code> или <code>contenteditable=&quot;true&quot;</code> означает, что элемент доступен для редактирования</li>
<li><code>contenteditable=&quot;false&quot;</code> означает, что элемент недоступен для редактирования</li>
<li><code>contenteditable=&quot;inherit&quot;</code> означает, что элемент доступен для редактирования в случае, если его непосредственный родитель доступен для редактирования. Это значение атрибута по умолчанию.</li>
</ol>
<p>Когда вы добавляете элементу атрибут <code>contenteditable</code>, браузер делает его доступным для редактирования. Кроме того, все потомки этого элемента также становятся доступны для редактирования, если атрибут <code>contenteditable</code> у них явно не установлен в <code>false</code>.</p>
<h2>Пример кода</h2>
<pre><code tabindex="0" class="language-html">&lt;div id=&quot;example-one&quot; contenteditable=&quot;true&quot;&gt;
    &lt;style scoped&gt;
        #example-one {
            margin: 12px 0;
        }
        #example-one[contenteditable=&quot;true&quot;] {
            padding: 10px;
            outline: 3px dashed #CCC;
        }
        #example-one[contenteditable=&quot;true&quot;]:hover {
            outline: 3px dashed #2B8BAD;
        }
    &lt;/style&gt;
    &lt;p&gt;Всё что находится в этом блоке, доступно для редактирования в браузерах, поддерживающих &lt;code&gt;HTML5&lt;/code&gt;. Давайте, попробуйте: кликните для начала редактирования.&lt;/p&gt;
&lt;/div&gt;
</code></pre>
<h2>Демонстрация</h2>
<p>Вот два простых примера, показывающие работу <code>contenteditable</code>:</p>
<h3>Пример №1</h3>
<div id="example-one" contenteditable="true">
<style>
    #example-one {
        margin: 12px 0;
        font-family: Consolas, Monaco, monospace;
    }

    #example-one[contenteditable="true"] {
        padding: 10px;
        overflow-x: auto;
        outline: 3px dashed #cccccc;
    }

    #example-one[contenteditable="true"]:hover {
        background: #e4f3f9;
        outline: 3px dashed #2b8bad;
    }
</style>
<p>Всё, что находится в этом блоке, доступно для редактирования в браузерах, поддерживающих HTML5. Давайте, попробуйте: кликните для начала редактирования.</p>
</div>
<p>Редактирование текста.</p>
<p>Я использовал CSS для создания оформления, показывающего, что текст доступен для редактирования. Обратите внимание на ориентированное на будущее использование <code>&lt;style scoped&gt;</code>, которое описано в моей <a href="https://html5doctor.com/the-scoped-attribute/">предыдущей статье</a>.</p>
<h3>Пример №2</h3>
<p><a href="https://twitter.com/chriscoyier">Крис Койер</a> рассказывал на CSS-Tricks, что вы можете позволить вашим пользователям <a href="http://css-tricks.com/show-and-edit-style-element/">редактировать CSS в реальном времени</a>, так как <code>&lt;style&gt;</code> элемент имеет <code>display: none</code> по умолчанию, но ведь значение можно изменить на <code>block</code>.</p>
<p>Попробуйте отредактировать CSS, приведенный ниже:</p>
<div id="example-two" contenteditable="true">
<style contenteditable="true">
    #example-two {
        margin: 12px 0;
        font-family: Consolas, Monaco, monospace;
    }

    #example-two style {
        display: block;
        white-space: pre;
    }

    #example-two[contenteditable="true"] {
        padding: 10px;
        overflow-x: auto;
        outline: 3px dashed #cccccc;
    }

    #example-two[contenteditable="true"]:hover{
        background: #e4f3f9;
        outline: 3px dashed #2B8BAD;
    }
</style>
</div>
<p>Редактирование таблицы стилей.</p>
<h2>Поддержка браузерами</h2>
<p>Поддержка атрибута <code>contenteditable</code> браузерами на удивление хороша:</p>
<div class="content__table-wrapper">
    <table>
        <thead>
            <tr>
                <th>Браузер</th>
                <th>Версия</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>Chrome</td>
                <td>4.0+</td>
            </tr>
            <tr>
                <td>Safari</td>
                <td>3.1+</td>
            </tr>
            <tr>
                <td>Mobile Safari</td>
                <td>5.0+</td>
            </tr>
            <tr>
                <td>Firefox</td>
                <td>3.5+</td>
            </tr>
            <tr>
                <td>Opera</td>
                <td>9.0+</td>
            </tr>
            <tr>
                <td>Opera Mini/Mobile</td>
                <td>Нет</td>
            </tr>
            <tr>
                <td>Internet Explorer</td>
                <td>5.5+</td>
            </tr>
            <tr>
                <td>Android</td>
                <td>3.0+</td>
            </tr>
        </tbody>
    </table>
</div>
<p>Поддержка браузерами свойства <code>contenteditable</code>.</p>
<p>Должен отметить, что появлением и отличной поддержкой атрибута мы обязаны IE 5.5, хотя на самом деле ранний вариант <code>contenteditable</code> был <a href="http://msdn.microsoft.com/en-us/library/ms537837(VS.85).aspx">разработан и внедрен Microsoft в июле 2000 года</a>.</p>
<p>Более подробную таблицу совместимости можно увидеть тут: <a href="https://caniuse.com/contenteditable">When Can I Use</a>.</p>
<h2>Сохранение изменений</h2>
<p>Для написания этого раздела я обратился за помощью к доктору Реми, так как он гораздо более сведущ во всём, что касается хранения <del>данных</del> всего на свете.</p>
<blockquote>
    <p>В зависимости от сложности блока ваш код может отлавливать нажатие <kbd>Enter</kbd> (код 13) для сохранения изменения и <kbd>Esc</kbd> (код 27) для их отмены.</p>
    <p>Когда пользователь нажимает <kbd>Enter</kbd> (предполагаем, что редактируем однострочные данные), получаем <code>innerHTML</code> редактируемого блока и посылаем AJAX-запрос с изменениями на сервер.</p>
    <p>Простой пример можно увидеть тут: <a href="https://jsbin.com/owavu3">Сохранение данных из элемента с <code>сontenteditable</code> при помощи AJAX</a>.</p>
    <footer>
        <cite><a href="https://remysharp.com/">Реми Шарп</a>.</cite>
    </footer>
</blockquote>
<h2>Заключение</h2>
<p>В своих статьях я неоднократно упоминал этот подход: спецификация наконец-то сделала официальным то, что внедрено в браузерах много лет назад.</p>
<p>Атрибут <code>contenteditable</code> — один из самых малоизвестных, но могу поспорить, что вы будете использовать его чаще, чем думаете.</p>
<p>Представьте себе возможность редактирования блока текста после простого клика на него: делать быстрые правки в статьях, редактировать комментарии или даже создавать не завязанные на серверную часть таблицы в веб-приложениях.</p>
<p>Если у вас есть идеи, как использовать этот атрибут — расскажите нам об этом в комментариях.</p>
<h2>Читать дальше</h2>
<ul>
<li><a href="http://blog.whatwg.org/the-road-to-html-5-contenteditable#what">Что такое contenteditable?</a></li>
<li><a href="http://css-tricks.com/expanding-images-html5/">Разворачиваем изображения с помощью HTML5 contenteditable</a></li>
</ul>

                    ]]></description><pubDate>Fri, 09 Mar 2012 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/contenteditable/</guid></item><item><title>Как работает nth-child</title><link>https://web-standards.ru/articles/nth-child/</link><description><![CDATA[
                        <p>Существует такой селектор, точнее псевдокласс, называемый <code>:nth-child</code>. Вот пример его использования:</p>
<pre><code tabindex="0" class="language-css">ul li:nth-child(3n+3) {
    color: #cccccc;
}
</code></pre>
<p>Что делает CSS-код, приведенный выше? Он выбирает каждый третий элемент внутри маркированного списка: это 3-й, 6-й, 9-й, 12-й и т.д. Давайте посмотрим, как именно работает это выражение и что еще можно сделать с помощью <code>:nth-child</code>.</p>
<p>Всё зависит от того, что находится между скобками. Селектор <code>:nth-child</code> принимает два ключевых слова: <code>even</code> и <code>odd</code>. Тут всё просто: <code>even</code> выбирает чётные элементы, такие как 2-й, 4-й, 6-й и т.д., а <code>odd</code> выбирает нечётные элементы, такие как 1-й, 3-й, 5-й и т.д.</p>
<p>Как видно из первого примера, <code>:nth-child</code> также принимает в качестве параметра выражения. В том числе и простейшие уравнения, иначе говоря, просто числа. Если поставить их в скобки, будет выбран только один элемент с соответствующим номером. Например, вот как выбрать только пятый элемент:</p>
<pre><code tabindex="0" class="language-css">ul li:nth-child(5) {
    color: #cccccc;
}
</code></pre>
<p>Однако давайте вернемся к <code>3n+3</code> из первого примера. Как он работает и почему выбирается каждый третий элемент? Весь фокус в понимании переменной <code>n</code> и приведенного алгебраического уравнения. Подумайте об <code>n</code>, как о начинающемся с нуля множестве целых чисел. Потом дополните уравнение. Так <code>3n</code> это <code>3×n</code>, а все уравнение вместе это <code>(3×n)+3</code>. Теперь, подставляя вместо <code>n</code> числа больше или равные нулю, мы получим:</p>
<ul>
<li>(3 × 0) + 3 = 3 = 3-й элемент</li>
<li>(3 × 1) + 3 = 6 = 6-й элемент</li>
<li>(3 × 2) + 3 = 9 = 9-й элемент и т.д.</li>
</ul>
<p>А как насчёт <code>:nth-child(2n+1)</code>?</p>
<ul>
<li>(2 × 0) + 1 = 1 = 1-й элемент</li>
<li>(2 × 1) + 1 = 3 = 3-й элемент</li>
<li>(2 × 2) + 1 = 5 = 5-й элемент и т.д.</li>
</ul>
<p>Так, стоп! Это ведь то же самое, что и <code>odd</code>. Тогда, возможно, не стоит использовать это выражение? Но разве в этом случае мы не подвергаем наш первый пример излишнему усложнению? Что, если вместо <code>3n+3</code> мы запишем <code>3n+0</code> или ещё проще <code>3n</code>?</p>
<ul>
<li>(3 × 0) = 0 = ничего нет</li>
<li>(3 × 1) = 3 = 3-й элемент</li>
<li>(3 × 2) = 6 = 6-й элемент</li>
<li>(3 × 3) = 9 = 9-й элемент и т.д.</li>
</ul>
<p>Итак, как вы можете видеть, результат получится такой же, а значит, нет необходимости в <code>+3</code>. Мы можем использовать и отрицательные значения <code>n</code>, с таким же успехом, как и вычитание в уравнениях. Например, <code>4n-1</code>:</p>
<ul>
<li>(4 × 0) – 1 = –1 = ничего нет</li>
<li>(4 × 1) – 1 = 3 = 3-й элемент</li>
<li>(4 × 2) – 1 = 7 = 7-й элемент и т.д.</li>
</ul>
<p>Использование <code>-n</code> может показаться странным — ведь если конечный результат отрицательный, то никакие элементы в выборку не попадут. Но если дополнить уравнение и снова сделать результат положительным, то выборка окажется довольно интересной: при помощи неё можно будет получить первые <code>n</code> элементов, например так: <code>-n+3</code>:</p>
<ul>
<li>–0 + 3 = 3 = 3-й элемент</li>
<li>–1 + 3 = 2 = 2-й элемент</li>
<li>–2 + 3 = 1 = 1-й элемент</li>
<li>–3 + 3 = 0 = ничего нет и т.д.</li>
</ul>
<p>На SitePoint есть хороший справочник <a href="http://reference.sitepoint.com/css/understandingnthchildexpressions">с милой табличкой</a>, которую я бесстыдно опубликую здесь:</p>
<div class="content__table-wrapper">
    <table>
        <thead>
            <tr>
                <th>n</th>
                <th>2n+1</th>
                <th>4n+1</th>
                <th>4n+4</th>
                <th>4n</th>
                <th>5n-2</th>
                <th>-n+3</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>0</td>
                <td>1</td>
                <td>1</td>
                <td>4</td>
                <td>–</td>
                <td>–</td>
                <td>3</td>
            </tr>
            <tr>
                <td>1</td>
                <td>3</td>
                <td>5</td>
                <td>8</td>
                <td>4</td>
                <td>3</td>
                <td>2</td>
            </tr>
            <tr>
                <td>2</td>
                <td>5</td>
                <td>9</td>
                <td>1</td>
                <td>8</td>
                <td>8</td>
                <td>1</td>
            </tr>
            <tr>
                <td>3</td>
                <td>7</td>
                <td>1</td>
                <td>1</td>
                <td>1</td>
                <td>1</td>
                <td>–</td>
            </tr>
            <tr>
                <td>4</td>
                <td>9</td>
                <td>17</td>
                <td>20</td>
                <td>16</td>
                <td>18</td>
                <td>–</td>
            </tr>
            <tr>
                <td>5</td>
                <td>11</td>
                <td>21</td>
                <td>24</td>
                <td>20</td>
                <td>23</td>
                <td>–</td>
            </tr>
        </tbody>
    </table>
</div>
<h2>Поддержка браузерами</h2>
<p>Селектор <code>:nth-child</code> — один из немногих CSS-селекторов, который почти полностью поддерживается в современных браузерах и абсолютно не поддерживается в IE, даже в IE8. Поэтому когда дело доходит до его использования, и конечный результат сделан по технологии прогрессивного улучшения — вы можете смело использовать его для некоторых оформительских элементов, вроде расцветки строк таблицы. Однако не стоит применять его в более серьезных случаях. Например, полагаться на него в раскладке сайта или удалять правое поле из каждого третьего блока в сетке три на три, чтобы они вошли в ряд.</p>
<p>Спасением здесь может послужить библиотека <a href="http://jquery.com/">jQuery</a>, которая реализует поддержку всех CSS-селекторов, включая <code>:nth-child</code>, даже в Internet Explorer.</p>
<h2>Всё равно неясно?</h2>
<p>Я не большой поклонник фразы <em>я лучше воспринимаю визуальную информацию.</em> И вы, конечно, тоже. Но примеры являются чрезвычайно полезными именно в таких ситуациях. Чтобы облегчить понимание, я сделал <a href="http://css-tricks.com/examples/nth-child-tester/">небольшую тестовую страничку</a>. На ней вы сможете написать свои уравнения и ниже увидеть то, что попадет в выборку.</p>
<p>Также загляните на страничку <a href="http://css-tricks.com/useful-nth-child-recipies/">полезных рецептов <code>:nth-child</code></a>, там вы сможете найти код для наиболее распространенных случаев.</p>

                    ]]></description><pubDate>Mon, 21 Nov 2011 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/nth-child/</guid></item><item><title>Почему у нас нет селектора по родителю</title><link>https://web-standards.ru/articles/parent-selector/</link><description><![CDATA[
                        <p>Относительно регулярно я вижу дискуссии о том, должен ли CSS включать селектор по родителю и, пока я не выяснил, как работает движок браузера, сам строил предположения по этому поводу.</p>
<p>Вкратце: <em>производительность.</em></p>
<h2>Как работает CSS</h2>
<p>В связи с моей работой я делаю много тестов производительности. Для определения «узких мест» мы используем массу приложений. Например, Google Page Speed, который дает рекомендации по улучшению производительности JavaScript и рендеринга. Прежде чем я перейду к рассмотрению этих рекомендаций, нам нужно разобраться как браузеры работают с CSS</p>
<h2>Стиль элемента применяется в момент его создания</h2>
<p>Мы часто рассматриваем веб-страницы как полноценные документы, наполненные элементами и содержимым. Между тем, браузеры разработаны так, чтобы обрабатывать документ потоково. Они начинают получать документ с сервера и могут начать его отображать до момента полной загрузки. Каждый узел анализируется и отображается в окне по мере получения.</p>
<p>Взгляните на этот документ:</p>
<pre><code tabindex="0" class="language-html">&lt;body&gt;
    &lt;div id=&quot;content&quot;&gt;
        &lt;div class=&quot;module intro&quot;&gt;
            &lt;p&gt;Lorem Ipsum&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;module&quot;&gt;
            &lt;p&gt;Lorem Ipsum&lt;/p&gt;
            &lt;p&gt;Lorem Ipsum&lt;/p&gt;
            &lt;p&gt;Lorem Ipsum &lt;span&gt;Test&lt;/span&gt;&lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/body&gt;
</code></pre>
<p>Браузер начинает сначала и видит элемент <code>&lt;body&gt;</code>. В этот момент времени считается, что этот узел не имеет дочерних узлов. Ничего более не рассматривается. Браузер определяет соответствующий ему обобщенный набор стилей и применяет его к элементу. Какой шрифт, цвет, интерлиньяж? После того, как это все будет выяснено, элемент отображается на экране.</p>
<p>Далее браузер видит элемент <code>&lt;div&gt;</code> со значением атрибута ID <code>content</code>. И снова в этот момент времени браузер считает его пустым. Он не рассматривает другие элементы. Как только браузер рассчитает стиль, элемент отображается на экране. Затем браузер определяет нужно ли перерисовать <code>&lt;body&gt;</code> — стал ли элемент шире или выше? Я подозреваю, что там есть масса других проверок, но изменение ширины и высоты — самый распространенный способ повлиять на отображение родительского узла.</p>
<p>Процесс продолжается, пока браузер не достигнет корневого узла документа.</p>
<p>Вот как выглядит визуализация процессов перерисовки в Firefox:</p>
<iframe src="https://www.youtube.com/embed/ZTnIxIA5KGw" allowfullscreen></iframe>
<h2>CSS-селекторы анализируются справа налево</h2>
<p>Чтобы определить, применяется ли CSS-правило к определенному элементу, браузер рассматривает селектор справа налево.</p>
<p>Если у вас есть селектор <code>body div#content p { color: #003366; }</code>, то, когда каждый элемент появляется на странице, браузер проверяет, является ли он параграфом. Если да, он начинает подниматься вверх по DOM и ищет <code>&lt;div&gt;</code> со значением атрибута ID равным <code>content</code>. Если он его находит, то продолжает подниматься по DOM пока не найдет <code>&lt;body&gt;</code>.</p>
<p>Таким образом, читая селектор справа налево, браузер значительно быстрее может определить применяется ли правило к элементу. Чтобы определить, какой из селекторов обладает большей производительностью, нужно выяснить, как много узлов придется рассмотреть, чтобы определить, можно ли применить правило к элементу.</p>
<h2>Правила</h2>
<p>Возвращаясь к Page Speed, давайте рассмотрим <a href="http://code.google.com/speed/page-speed/docs/rendering.html#UseEfficientCSSSelectors">несколько его рекомендаций</a>:</p>
<ul>
<li>Избегайте селектора по потомку: <code>.content .sidebar</code>;</li>
<li>Избегайте селектора по дочернему элементу: <code>.content &gt; .sidebar</code> и селектора по следующему элементу: <code>.content + .sidebar</code>.</li>
</ul>
<p>Конечно, селекторы по ID — самые быстрые. Проверить применимость селектора <code>#content</code> к рассматриваемому элементу можно очень быстро. Есть у него этот ID или нет? Селекторы по классу практически такие же быстрые, так как нет никаких связанных элементов, которые надо проверять.</p>
<p>Селекторы по потомкам, такие как <code>.content .sidebar</code> — более ресурсоемкие, так как, чтобы определить надо ли применять правило к <code>.sidebar</code>, браузер должен найти <code>.content</code>. Cелектор по дочернему элементу, например, <code>.content &gt; .sidebar</code>, лучше селектора по потомку, так как браузер должен проверить только один элемент вместо множества. <em>(К сожалению, селекторы <code>+</code> и <code>&gt;</code> не поддерживаются IE6. Так что если его поддержка актуальна для вас, то про них придется забыть — прим. переводчика).</em></p>
<h2>Селектор по тегу и универсальный селектор</h2>
<p>В рамках рекомендации избегать селекторов по потомку, дочернему или следующему элементам, рекомендуется избегать универсального селектора и селектора по тегу.</p>
<p>Рассмотрим следующий пример:</p>
<pre><code tabindex="0" class="language-css">#content * { color: #039; }
</code></pre>
<p>Так как в селекторе присутствует ID, то можно подумать, что этот селектор обрабатывается очень быстро. Проблема в том, что браузер обрабатывает селектор справа налево и сперва проверяется универсальный селектор. Для того чтобы браузер мог определить, должен ли цвет текста элемента быть тёмно-синим, надо проверить каждый предок элемента, пока не будет найден предок с атрибутом ID равным <code>content</code> или не будет достигнут корень документа.</p>
<p>И это должно быть сделано для каждого элемента на странице.</p>
<p>Теперь, когда мы понимаем, как элемент обрабатывается, как определяется применимость правил к элементу, давайте рассмотрим пример.</p>
<h2>Почему IE долго не поддерживал <code>:last-child</code></h2>
<p>Все жаловались: у всех браузеров, кроме IE, есть поддержка <code>:last-child</code> (она появилась только в IE9!) Некоторые могли подумать <q>насколько же сложнее сделать <code>:last-child</code>, если уже реализован <code>:first-child</code>?</q></p>
<p>Давайте представим, что мы — браузер и мы парсим документ-пример, который я приводил ранее.</p>
<pre><code tabindex="0" class="language-css">.module &gt; p:first-child { color: red; } /* Первое правило */
.module &gt; p:last-child { color: blue; } /* Второе правило */
</code></pre>
<p>Когда мы рассматриваем внутренности первого <code>&lt;div&gt;</code>, мы видим, что там есть параграф. Браузер видит что-то вроде этого:</p>
<pre><code tabindex="0" class="language-html">&lt;div class=&quot;module&quot;&gt;
    &lt;p&gt;Lorem Ipsum&lt;/p&gt;
</code></pre>
<p>Нужно ли применить первое правило к параграфу? Да, это параграф; да, это первый дочерний узел; и, да, это непосредственный потомок элемента с классом <code>module</code>.</p>
<p>Нужно ли применить к этому параграфу второе правило? На данный момент это последний элемент. Но мы могли ещё не загрузить все элементы и не можем быть уверены, что он останется последним.</p>
<p>Вне зависимости от того, как решать эту дилемму, у нас возникает необходимость повторного анализа стилей двух узлов для каждого нового узла, который мы добавляем в DOM. Если я добавлю ещё один параграф следом за первым, мы должны так же повторно перерассчитать стили, которые применяются к предыдущему параграфу.</p>
<h2>Как на самом деле это делает браузер?</h2>
<p>Я не мог сказать с абсолютной уверенностью, как браузеры парсят <code>:last-child</code>, так что я создал несколько тестов:</p>
<ul>
<li><a href="http://testing.snook.ca/css-1.html">Статическая HTML-страница</a>;</li>
<li><a href="http://testing.snook.ca/css-1.php">Рендеринг с паузами в одну секунду</a>.</li>
</ul>
<p>Первый пример весьма скучен. В любом браузере, включая IE9, всё отображается корректно. Внутри <code>&lt;div&gt;</code> первый элемент красный, а последний синий. Но посмотрите на второй пример, и вы увидите интересные отличия в поведении браузеров.</p>
<p>Второй пример приостанавливается перед добавлением каждого параграфа в <code>&lt;div&gt;</code>.</p>
<p>В Firefox первый параграф изначально отображается синим. Когда загружается второй параграф, первый становится красным и второй — синим. Когда загружается третий параграф, второй отображается цветом по умолчанию и третий отображается синим. Firefox рассматривает каждый элемент, который был загружен в качестве последнего элемента, пока не будет загружен ещё один.</p>
<p>В Safari, Chrome и Opera мы увидим другой подход. Первый параграф красный. Второй отображается чёрным. Последний параграф отображается чёрным, пока браузер не получит закрывающий тег <code>&lt;/div&gt;</code>. В этот момент последний параграф становится синим. Эти браузеры не рассматривают элемент как последний, пока не будет закрыт родительский.</p>
<p>В Internet Explorer 9 Beta я нашел интересный баг. В то время, как статическая страница отображается корректно, версия с паузами отрабатывает с любопытным побочным эффектом. Первый параграф синий, второй параграф синий и затем — третий. Когда закрывающий тег <code>&lt;/div&gt;</code> загружен, предпоследний параграф меняет цвет на чёрный. IE9 пытается обрабатывать селектор как WebKit и Opera, но… м-м… не выходит. Надо бы отправить багрепорт в Microsoft.</p>
<h2>Почему у нас нет селектора по родителю?</h2>
<p>Уже дано достаточно пояснений, чтобы можно было вернуться к оригинальному вопросу. Проблема не в том, что у нас не может быть селектора по родителю. Проблема в том, что мы столкнемся с проблемами быстродействия, когда дело дойдет до определения того, какие CSS-правила применимы к данному элементу. Если Google Page Speed не рекомендует использование универсальных селекторов, то можно гарантировать, что селектор по родителю будет первым в списке ресурсоемких селекторов, намного опережая все проблемы с производительностью, которые могут быть вызваны использованием универсального селектора.</p>
<p>Давайте посмотрим почему. Первым делом давайте приведём пример синтаксиса для селектора по родителю.</p>
<pre><code tabindex="0" class="language-css">div.module:has(span) { color: green; }
</code></pre>
<p>Проблема в том, что мы не можем определить применимость правила до тех пор, либо пока мы не найдем соответствия, либо пока все элементы, дочерние по отношению к родителю данного, не будут загружены. Ввиду этого мы должны оценивать правило и все остальные (в случае наличия специфических особенностей), применяющиеся к элементу, для каждого дочернего элемента, который мы загружаем.</p>
<p>Посмотрите на часть нашего документа:</p>
<pre><code tabindex="0" class="language-html">&lt;div class=&quot;module&quot;&gt;
    &lt;p&gt;Lorem Ipsum&lt;/p&gt;
    &lt;p&gt;Lorem Ipsum&lt;/p&gt;
    &lt;p&gt;Lorem Ipsum &lt;span&gt;Test&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
</code></pre>
<p>Исходя из того, что мы видим, <code>.module</code> будет отображён без использования правила, применяемого селектором по родителю. Когда будет загружен первый элемент <code>&lt;p&gt;</code>, нужно повторно оценить применимость селектора по родителю к <code>&lt;div&gt;</code>. Нужно сделать это снова для следующего параграфа. И снова, для следующего. Наконец, когда <code>&lt;span&gt;</code> загружен, селектор по родителю будет применен к родительскому <code>&lt;div&gt;</code>, и элемент нужно будет повторно перерисовать.</p>
<p>И что теперь? Теперь, если изменится любое наследуемое CSS-свойство, каждый потомок должен будет повторно анализироваться и перерисовываться. Ох…</p>
<h2>Почему проблему можно решить с помощью JavaScript?</h2>
<p>Это только кажется, что JavaScript решает проблему. В общем случае JavaScript-заплатки <em>(заплатки — polyfills — части кода, обеспечивающие функциональность, которую должен обеспечивать браузер — прим. переводчика).</em> Или регрессивное усовершенствование (или как там вы, молодежь, это сейчас называете) запускаются только один раз, после полной загрузки DOM.</p>
<p>Для того чтобы действительно имитировать поведение CSS, любой скрипт, решающий эту проблему, должен запускаться после отображения каждого элемента на странице, чтобы определить, нужно ли применить нашу «заплатку». Помните CSS-expressions в Internet Explorer? Именно по этой причине они вызывали такие проблемы с производительностью.</p>
<h2>Не невозможно</h2>
<p>Появится ли когда-нибудь селектор по родителю? Возможно. То, что я описал, не невозможно. На самом деле — наоборот. Это значит только то, что нам придётся иметь дело с ухудшением производительности из-за использования этого селектора.</p>

                    ]]></description><pubDate>Tue, 20 Sep 2011 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/parent-selector/</guid></item><item><title>Элементы details и summary</title><link>https://web-standards.ru/articles/details-summary-elements/</link><description><![CDATA[
                        <p>Вам часто приходилось использовать JavaScript для создания виджета, показывающего и скрывающего какое-то содержимое? Возможно, для этого вы даже скачивали целую JavaScript-библиотеку? Что ж, можете радоваться: HTML5 позволяет создавать подобное всего лишь парой строчек кода, без применения JavaScript. Зависит от браузера, конечно, но мы вернёмся к этому позже. Представляем вам элемент <code>&lt;details&gt;</code>.</p>
<p>Вот что о нём написано <a href="https://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#the-details-element">в спецификации</a>:</p>
<blockquote>
    <p>Элемент <code>&lt;details&gt;</code> представляет собой раскрывающийся виджет, показывающий пользователю дополнительную информацию или элементы управления.</p>
    <footer>
        <cite><a href="https://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#the-details-element">Спецификация WHATWG HTML5</a>.</cite>
    </footer>
</blockquote>
<p>Мы можем использовать <code>&lt;details&gt;</code> для создания «виджетов-аккордеонов», которые пользователь может разворачивать и сворачивать. Внутри этого элемента можно разместить любое содержимое.</p>
<h2>Поддержка браузерами</h2>
<p>Прежде чем мы продолжим, вам нужно учесть, что сейчас только Chrome поддерживает элемент <code>&lt;details&gt;</code>. <a href="http://my.opera.com/ODIN/blog/implementing-html5-details">Скоро к нему присоединится и Opera</a>, но немного костылей нам все-таки понадобится. Что ж, запускайте Chrome, и давайте смотреть.</p>
<h2>Использование <code>&lt;details&gt;</code></h2>
<p>Имеются два элемента: <code>&lt;details&gt;</code> и необязательный <code>&lt;summary&gt;</code>. Элемент <code>&lt;details&gt;</code> — это обёртка для содержимого, которое мы хотим показать и скрыть, а <code>&lt;summary&gt;</code> содержит описание и заголовок этой группы. Формально <code>&lt;summary&gt;</code> нам не нужен. В его отсутствие браузер подставит текст по умолчанию, например «details» в Chrome. Давайте взглянем на код:</p>
<pre><code tabindex="0" class="language-html">&lt;details&gt;
    &lt;summary&gt;Покажи-скрой меня&lt;/summary&gt;
    &lt;p&gt;Бурное развитие внутреннего туризма привело Томаса Кука.&lt;/p&gt;
&lt;/details&gt;
</code></pre>
<p>Вы можете <a href="https://jsbin.com/egefop#html,live">посмотреть это в действии на jsbin</a>. Даже этот простой пример прекрасно демонстрирует эффект переключения. Без JavaScript!</p>
<h3>Атрибут <code>open</code></h3>
<p>В вышеприведенном примере содержимое скрыто при загрузке страницы. Мы можем сделать его видимым по умолчанию, добавив одиночный атрибут <code>open</code> для <code>&lt;details&gt;</code>, <a href="https://jsbin.com/egefop/2#html,live">пример на jsbin</a>:</p>
<pre><code tabindex="0" class="language-html">&lt;details open&gt;
    &lt;summary&gt;Покажи-скрой меня&lt;/summary&gt;
    &lt;p&gt;Бурное развитие внутреннего туризма привело Томаса Кука.&lt;/p&gt;
&lt;/details&gt;
</code></pre>
<p>Атрибута <code>closed</code> не существует. Поэтому, опуская <code>open</code>, вы по умолчанию подразумеваете <code>closed</code>.</p>
<h3>Элемент <code>&lt;summary&gt;</code></h3>
<p>Мы бегло взглянули на <code>&lt;summary&gt;</code> в действии, теперь остановимся на нём подробнее. Внутри <code>&lt;summary&gt;</code> могут использоваться строчные элементы, такие как <code>&lt;span&gt;</code> или <code>&lt;strong&gt;</code>. Для чего это может быть нужно? Например, для дополнительного оформления или, как предлагает спецификация, использования <code>&lt;label&gt;</code> для элемента формы. По крайней мере, <em>было бы</em> удобно, если бы <a href="https://jsbin.com/egefop/3#html">подобная конструкция</a> работала корректно:</p>
<pre><code tabindex="0" class="language-html">&lt;details&gt;
    &lt;summary&gt;&lt;label for=&quot;name&quot;&gt;Имя:&lt;/label&gt;&lt;/summary&gt;
    &lt;input type=&quot;text&quot; id=&quot;name&quot; name=&quot;name&quot; /&gt;
&lt;/details&gt;
</code></pre>
<p>Теоретически, нажатие на <code>&lt;summary&gt;</code> должно раскрывать содержимое элемента <code>&lt;details&gt;</code>. Но в этом примере содержимое не будет развернуто, потому что вы, фактически, взаимодействуете с <code>&lt;label&gt;</code>, который переводит фокус на соответствующий <code>&lt;input&gt;</code> — даже если он скрыт с помощью <code>&lt;details&gt;</code>.</p>
<p>Честно говоря, этот момент еще требует прояснения. А что по вашему мнению должно происходить? Возможно, у разработчиков браузеров, читающих эту статью, есть какие-то идеи? :)</p>
<h3>Вложенность <code>&lt;details&gt;</code></h3>
<p>Вы можете помещать <code>&lt;details&gt;</code> друг в друга, если хотите, как это сделано в <a href="https://jsbin.com/egefop/14#html,live">следующем, вполне валидном примере</a>:</p>
<pre><code tabindex="0" class="language-html">&lt;details&gt;
    &lt;summary&gt;Вопрос 1&lt;/summary&gt;
    &lt;p&gt;Население превышает широкий кристаллический фундамент.&lt;/p&gt;
    &lt;details&gt;
        &lt;summary&gt;Приложенные документы&lt;/summary&gt;
        &lt;ul&gt;
            &lt;li&gt;&lt;a href=&quot;#&quot;&gt;Болгары очень дружелюбны;&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;Скумбрия неумеренно перевозит вулканизм;&lt;/li&gt;
            &lt;li&gt;Дождливая погода, куда входят Пик-Дистрикт;&lt;/li&gt;
            &lt;li&gt;Белый саксаул дегустирует живописный утконос;&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/details&gt;
&lt;/details&gt;
</code></pre>
<h2>Примеры использования</h2>
<p>Так в каких же случаях вы можете использовать <code>&lt;details&gt;</code>? Первое, что приходит в голову — FAQ. Разработчики часто используют для них «аккордеоны», поэтому <code>&lt;details&gt;</code> замечательно подходит.</p>
<p>Также не забывайте о навигации по странице. Это может быть закрепленный блок, передвигающаяся одновременно с прокруткой. <a href="https://jsbin.com/egefop/8#html,live">Возможно, что-то вроде этого?</a></p>
<p>Вы можете использовать <code>&lt;details&gt;</code> для сворачивания и разворачивания блока комментариев в блоге, для профиля пользователя, для описания загружаемого файла, для сложных форм или в веб-приложениях, как показано в этом примере из спецификации:</p>
<figure>
    <img src="https://web-standards.ru/articles/details-summary-elements/images/details-w3c.png" alt="Пример использования details из спецификации.">
    <figcaption>Пример использования <code>&lt;details&gt;</code> из спецификации.</figcaption>
</figure>
<p>На самом деле, даже глядя сейчас на админку WordPress, я вижу множество возможностей использования <code>&lt;details&gt;</code>. Если у вас есть ещё какие-то идеи и предложения — расскажите о них в комментариях.</p>
<h2>Оформление</h2>
<p>Как же нам оформить эту штуку? Для элемента, раскрывающего содержимое, в WebKit вы можете использовать псевдоэлемент <code>::-webkit-details-marker</code>. <a href="https://jsbin.com/egefop/9#html,live">Небольшой пример</a>:</p>
<pre><code tabindex="0" class="language-css">details summary::-webkit-details-marker {
    background: red;
    color: #fff;
    font-size: 500%;
}
</code></pre>
<p>Мы также можем позиционировать этот элемент по отношению к родителю. <a href="https://jsbin.com/egefop/17#html,live">Здесь, например, он прижат к правому краю</a>. В общем-то, и все.</p>
<p>А как же заменить раскрывающий элемент своей иконкой? Используя выборку по атрибуту, вы можете определить, когда <code>&lt;details&gt;</code> раскрыт, а когда закрыт, и применить соответствующее фоновое изображение. Мы делаем примерно то же самое в <a href="https://jsbin.com/egefop/11#html,live">этом примере</a>, за исключением того, что вместо фонового изображения мы используем псевдоэлемент <code>::after</code>:</p>
<pre><code tabindex="0" class="language-css">summary::-webkit-details-marker {
    display: none
}

summary::after {
    background: red;
    border-radius: 5px;
    content: &quot;+&quot;;
    color: #fff;
    float: left;
    font-size: 1.5em;
    font-weight: bold;
    margin: -5px 10px 0 0;
    padding: 0;
    text-align: center;
    width: 20px;
}

details[open] summary::after {
    content: &quot;-&quot;;
}
</code></pre>
<p>В этом примере символы <code>+</code> и <code>-</code> используются в качестве раскрывающей ссылки. В зависимости от дизайна вы можете использовать <code>::before</code> вместо <code>::after</code>, но, в любом случае, оба псевдоэлемента позволяют использование изображения.</p>
<p>Выборка по атрибуту <code>details[open]</code> скрывает в себе некоторые интересные возможности. Как хорошие HTML5-доктора, мы создали <a href="https://jsbin.com/egefop/15#html,live">доработанный пример</a>, показанный на этом скриншоте:</p>
<figure>
    <img src="https://web-standards.ru/articles/details-summary-elements/images/nice-details.png" alt="Элемент details в Chrome.">
    <figcaption>Оформленный <code>&lt;details&gt;</code> в Chrome.</figcaption>
</figure>
<p>Было бы интересно (хотя это и не всегда уместно), если бы мы могли использовать CSS-трансформации для анимации разворачивания и сворачивания <code>&lt;details&gt;</code>, но пока это невозможно.</p>
<h2>Доступность</h2>
<p>К сожалению, на момент написания статьи отсутствует возможность управления <code>&lt;details&gt;</code> с помощью клавиатуры. <a href="http://www.paciellogroup.com/blog/2011/08/accessibility-notes-2nd-august-2011/">Стив Фолкнер пишет</a>:</p>
<blockquote>
<p>Проблема в том, что на данный момент отсутствует поддержки клавиатуры и нет никакой информации для обеспечения доступности.</p>
</blockquote>
<p>Попробуйте сами. Если вы раскроете элемент <code>&lt;details&gt;</code> с помощью мыши, тогда вы сможете использовать клавиатуру для навигации по вложенным элементам, но вы также должны иметь возможность открывать и закрывать <code>&lt;details&gt;</code> с клавиатуры. Что ж, неидеально, но я уверен, что разработчики Chrome скоро с этим разберутся. Правда, ребята?</p>
<h2>Обратная совместимость</h2>
<p>Прежде чем кто-то начнет восклицать, что это не работает в IE6, хочу сказать: мы знаем. Тем не менее, благодаря некоторым умным людям, мы можем обеспечить изящную обратную совместимость. В этой <a href="https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-browser-Polyfills">очень полезной коллекции всевозможных кроссбраузерных костылей</a> я нашел два решения, оба они требуют jQuery:</p>
<ul>
<li>Обратная совместимость для <a href="https://mathiasbynens.be/notes/html5-details-jquery"><code>&lt;details&gt;</code> с помощью jQuery</a> от Матиаса Байненса;</li>
<li><a href="https://github.com/manuelbieh/details-Polyfill">Еще одна альтернатива <code>&lt;details&gt;</code>, также основанная на jQuery</a> от Мануэля Бье.</li>
</ul>
<p>Многие из вас захотят использовать <a href="http://modernizr.com/">Modernizr</a> для определения возможностей браузера, но на данный момент в Modernizr отсутствует проверка поддержки <code>&lt;details&gt;</code>. Матиас, автор приведенного выше решения для обратной совместимости, предлагает использовать <a href="https://github.com/Modernizr/Modernizr/blob/master/feature-detects/elem-details.js">этот Modernizr-сниппет</a>.</p>
<h2>Зачем вообще это использовать?</h2>
<p>Конечно, дарёному коню в зубы не смотрят, но все-таки — почему этот виджет существует в HTML5? Что ж, как и в случае с другими возможностями HTML5, он просто делает нашу жизнь легче. Реализация таких элементов, как календарь, слайдер, прогресс-бар, а теперь ещё и «аккордеон», становится гораздо проще и не требует использования JavaScript. Кто знает, что будет следующим? Нативные табы? Было бы здорово :)</p>
<h2>В заключение</h2>
<p>В этой статье мы продемонстрировали, как использовать элементы <code>&lt;details&gt;</code> и <code>&lt;summary&gt;</code>. Элемент <code>&lt;details&gt;</code> при помощи <code>&lt;summary&gt;</code> создаёт естественный для браузера интерактивный раскрывающийся виджет.</p>
<p>На текущий момент <code>&lt;details&gt;</code> работает только в Chrome, но, надеюсь, эта ситуация в скором времени изменится. Пока что мы можем использовать в CSS только <code>::-webkit-details-marker</code>, но есть и множество других CSS-техник. Если у вас есть какой-либо опыт или идеи для использования элемента <code>&lt;details&gt;</code>, расскажите об этом в комментариях.</p>

                    ]]></description><pubDate>Mon, 29 Aug 2011 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/details-summary-elements/</guid></item><item><title>Вам не нравится IE6? Что, правда?</title><link>https://web-standards.ru/articles/dont-like-ie6-really/</link><description><![CDATA[
                        <p>Сегодня прочёл очередное высказывание на тему <em>Почему я не поддерживаю IE6</em> из длинного-предлинного списка ему подобных. Что за бред? Если поддержка IE6 является частью вашей работы или включена в договор — значит так оно и есть, такова профессия, и в этих сложностях её смысл.</p>
<p>Меня тошнит от подобных старых и скучных публикаций и даже целых сайтов, посвящённых разглагольствованиям, почему IE6 должен умереть. Мы все в курсе, что IE6 должен умереть. Microsoft в курсе, что IE6 должен умереть. Чёрт, даже сам IE6 в курсе, что он должен умереть. Он шатается вокруг уже многие годы как чёртов зомби.</p>
<p>А теперь смотрите, факт: никто сегодня не устанавливает IE6 в качестве основного браузера. Более того, я готов спорить, что никто, будь у него такой выбор, не предпочтёт IE6 любому другому браузеру. Но проблема не в этом. Теперь нам ясно: никто, блин, специально не выбирает IE6 и уж точно не использует этот браузер, чтобы просто вас взбесить!</p>
<p>Кстати, когда IE6 наконец-то помрёт, кто займётся поиском и заменой IE6 на IE7 во всех записях, а потом IE7 на IE8 и так далее?</p>
<p>Вполне очевидно, что когда компания АБВ во времена XP и IE6 приобретала оборудование для своих сотрудников, это стоило ей чёртову кучу денег. Компании не любят тратить деньги, когда им кажется, что это совсем не нужно. Поэтому очередное обновление всех этих ПК не входит в их приоритеты. Не забывайте, что обновление тянет за собой поддержку, обслуживание, простой и так далее — всё, что стоит ещё одну чёртову кучу денег.</p>
<p>Относительно недавно я вёл курсы в одном научном институте, где местные разработчики рассказали мне, что самый часто используемый браузер у них — это IE6. И поддерживать этот браузер — это их работа.</p>
<p>Их работа. За которую им платят. Если вы не хотите поддерживать IE6 — не поддерживайте. Никто не выкручивает вам руки, заставляя фрилансить над подобными проектами. Если вы настолько против IE6 и имеете постоянную работу — я надеюсь, что вы упомянули это на собеседовании, но если нет — и вы действительно ненавидите IE6, то вы можете уволиться и найти другую работу, правильно?</p>
<p>То, что разрабатывать под IE6 сложно из-за того, что он уже не поддерживается и, в частности, просто не совместим с требованиями к современным сайтам — это факт. Поэтому мы просим за это больше денег.</p>
<p>Если ваш клиент создаёт совершенно новый продукт, то у вас нет причин для поддержки старых браузеров, если только у них нет чёткой статистики, показывающей эту необходимость. В любом случае, существует множество способов просчитать потенциальную статистику использования браузеров. Однако если это уже существующая компания, и у них есть поток IE6, обеспечивающий поддержку их бизнесу — тогда это просто часть вашей работы.</p>
<p>Некоторые команды разработчиков имеют такие размеры и способны нанимать новых людей только благодаря браузерам вроде IE. Если бы всё было так просто, куча людей осталась бы без работы.</p>
<p>Разработка для сети подразумевает, что вы знаете как работают браузеры. Суть такой работы заключается в том, чтобы сайты работали везде. Знание всех «подводных граблей» браузеров — это то, что отличает <em>Ваньку Середнячка</em> от <em>господина Николая Великолепного.</em> Если вы не хотите учить трюки IE6 (или IE7, или IE8, или IE9) — не надо. Вашу работу заберёт кто-нибудь другой.</p>
<p>Что до меня, я с нетерпением жду новых причин, согласно которым IE6 должен умереть — как будто это и так прекрасно не задокументировано.</p>

                    ]]></description><pubDate>Tue, 02 Aug 2011 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/dont-like-ie6-really/</guid></item><item><title>Избегаем популярных ошибок в HTML5</title><link>https://web-standards.ru/articles/avoiding-html5-mistakes/</link><description><![CDATA[
                        <p>Разбирая сайты для <a href="http://html5gallery.com/">Галереи HTML5</a> и отвечая на вопросы читателей на сайте <a href="https://html5doctor.com/">Доктор HTML5</a>, я вынужден просматривать целую кучу страниц на HTML5 и, конечно же, их исходный код. В этой статье я расскажу вам об ошибках и сомнительной разметке, которые мне частенько приходится видеть, и объясню, как их избежать.</p>
<h2>Не используйте <code>&lt;section&gt;</code> как обёртку для оформления</h2>
<p>Одна из самых распространённых проблем, которую я часто вижу в разметке сайтов — это произвольная замена элементов <code>&lt;div&gt;</code> структурными элементами из HTML5, особенно замена оформительской обёртки на <code>&lt;section&gt;</code>. В XHTML или HTML4 я бы увидел что-нибудь такое:</p>
<pre><code tabindex="0" class="language-html">&lt;!-- Не копируйте этот код! Он неправильный! --&gt;
&lt;div id=&quot;wrapper&quot;&gt;
    &lt;div id=&quot;header&quot;&gt;
        &lt;h1&gt;Моя супер-пупер страница&lt;/h1&gt;
        &lt;!-- Содержимое шапки --&gt;
    &lt;/div&gt;
    &lt;div id=&quot;main&quot;&gt;
        &lt;!-- Содержимое страницы --&gt;
    &lt;/div&gt;
    &lt;div id=&quot;secondary&quot;&gt;
        &lt;!-- Вторичное содержимое --&gt;
    &lt;/div&gt;
    &lt;div id=&quot;footer&quot;&gt;
        &lt;!-- Содержимое подвала --&gt;
    &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<p>Вместо этого я вижу следующее:</p>
<pre><code tabindex="0" class="language-html">&lt;!-- Не копируйте этот код! Он неправильный! --&gt;
&lt;section id=&quot;wrapper&quot;&gt;
    &lt;header&gt;
        &lt;h1&gt;Моя супер-пупер страница&lt;/h1&gt;
        &lt;!-- Содержимое шапки --&gt;
    &lt;/header&gt;
    &lt;div id=&quot;main&quot;&gt;
        &lt;!-- Содержимое страницы --&gt;
    &lt;/div&gt;
    &lt;section id=&quot;secondary&quot;&gt;
        &lt;!-- Вторичное содержимое --&gt;
    &lt;/section&gt;
    &lt;footer&gt;
        &lt;!-- Содержимое подвала --&gt;
    &lt;/footer&gt;
&lt;/section&gt;
</code></pre>
<p>Честно говоря, это неправильно: <code>&lt;section&gt;</code> — это <strong>не обёртка.</strong> <a href="https://html5doctor.com/section">Элемент <code>&lt;section&gt;</code></a> определяет смысловую секцию содержимого для создания <a href="https://html5doctor.com/outline">структуры документа</a>. Он должен содержать заголовок. Если вы ищете элемент для того чтобы обернуть в него всю страницу (в стиле HTML или XHTML), подумайте, не применить ли стили непосредственно к элементу <code>&lt;body&gt;</code>, <a href="http://camendesign.com/code/developpeurs_sans_frontieres">как описано у Крока Кеймена</a>. Если же вам всё ещё нужна дополнительная обёртка, используйте <code>&lt;div&gt;</code>. Раз уж <a href="https://html5doctor.com/div">Доктор Майк объясняет, что <code>&lt;div&gt;</code> не мёртв</a>, а вам не удаётся найти ничего более удачного, пожалуй, этот элемент будет самым подходящим для создания оформительской обёртки.</p>
<p>Таким образом, корректной разметкой для упомянутого выше примера с использованием HTML5 и пары ролей ARIA будет следующий код. Обратите внимание, что вам, в зависимости от дизайна, всё ещё могут понадобится экстра-элементы <code>&lt;div&gt;</code>.</p>
<pre><code tabindex="0" class="language-html">&lt;body&gt;
    &lt;header&gt;
        &lt;h1&gt;Моя супер-пупер страница&lt;/h1&gt;
        &lt;!-- Содержимое шапки --&gt;
    &lt;/header&gt;
    &lt;div role=&quot;main&quot;&gt;
        &lt;!-- Содержимое страницы --&gt;
    &lt;/div&gt;
    &lt;aside role=&quot;complementary&quot;&gt;
        &lt;!-- Вторичное содержимое --&gt;
    &lt;/aside&gt;
    &lt;footer&gt;
        &lt;!-- Содержимое подвала --&gt;
    &lt;/footer&gt;
&lt;/body&gt;
</code></pre>
<p>Если вы не уверены, какие элементы использовать, я рекомендую вам обратиться к нашей <a href="https://html5doctor.com/happy-1st-birthday-us/#flowchart">пошаговой схеме выбора HTML5-элементов</a> для разметки содержимого.</p>
<h2>Используйте <code>&lt;header&gt;</code> и <code>&lt;hgroup&gt;</code> осознанно</h2>
<p>Элемент <code>&lt;hgroup&gt;</code> удалён из спецификации HTML5 и не рекомендован к использованию, <em>прим. редактора.</em></p>
<p>Нет смысла писать разметку, если этого можно не делать, так ведь? К сожалению, я часто вижу элементы <code>&lt;header&gt;</code> и <code>&lt;hgroup&gt;</code> там, где они совсем не нужны. Вы можете узнать все подробности в наших статьях, посвящённых <a href="https://html5doctor.com/header">элементу <code>&lt;header&gt;</code></a> и <a href="https://html5doctor.com/hgroup">элементу <code>&lt;hgroup&gt;</code></a>, но я коротко резюмирую:</p>
<ul>
<li>Элемент <code>&lt;header&gt;</code> представляет собой группу вводного содержимого или навигационных средств и обычно содержит структурный заголовок.</li>
<li>Элемент <code>&lt;hgroup&gt;</code> группирует набор элементов от <code>&lt;h1&gt;</code> до <code>&lt;h6&gt;</code>, представляя собой структурный заголовок в случае, когда заглавие имеет несколько уровней, вроде подзаголовков, альтернативных названий или слоганов.</li>
</ul>
<h3>Злоупотребление <code>&lt;header&gt;</code></h3>
<p>Думаю, что вы в курсе, что <code>&lt;header&gt;</code> можно использовать в документе несколько раз. Но эта возможность привела к следующим ошибкам:</p>
<pre><code tabindex="0" class="language-html">&lt;!-- Не копируйте этот код! Он неправильный! --&gt;
&lt;article&gt;
    &lt;header&gt;
        &lt;h1&gt;Мой лучший пост&lt;/h1&gt;
    &lt;/header&gt;
    &lt;!-- Содержимое записи --&gt;
&lt;/article&gt;
</code></pre>
<p>Если ваш <code>&lt;header&gt;</code> содержит единственный заголовок, избавьтесь от ненужного <code>&lt;header&gt;</code>. Элемент <code>&lt;article&gt;</code> в любом случае гарантирует, что заголовок войдёт в смысловую структуру документа. И поскольку <code>&lt;header&gt;</code> не содержит нескольких элементов, как указано в его описании, зачем вам код, который, в общем-то, не нужен? Будьте проще:</p>
<pre><code tabindex="0" class="language-html">&lt;article&gt;
    &lt;h1&gt;Мой лучший пост&lt;/h1&gt;
    &lt;!-- Содержимое записи --&gt;
&lt;/article&gt;
</code></pre>
<h3>Неправильное использование <code>&lt;hgroup&gt;</code></h3>
<p>Раз уж зашла речь о заголовках — я часто вижу неправильное использование <code>&lt;hgroup&gt;</code>. Не следует использовать <code>&lt;hgroup&gt;</code> в сочетании с <code>&lt;header&gt;</code> в случае, когда:</p>
<ul>
<li>дочерний заголовок всего один или</li>
<li>элемента <code>&lt;hgroup&gt;</code> будет достаточно и без <code>&lt;header&gt;</code>.</li>
</ul>
<p>Первая проблема выглядит так:</p>
<pre><code tabindex="0" class="language-html">&lt;!-- Не копируйте этот код! Он неправильный! --&gt;
&lt;header&gt;
    &lt;hgroup&gt;
        &lt;h1&gt;Мой лучший пост&lt;/h1&gt;
    &lt;/hgroup&gt;
    &lt;p&gt;Ричард Кларк&lt;/p&gt;
&lt;/header&gt;
</code></pre>
<p>В этом случае стоит избавиться от <code>&lt;hgroup&gt;</code> и оставить только заголовок:</p>
<pre><code tabindex="0" class="language-html">&lt;header&gt;
    &lt;h1&gt;Мой лучший пост&lt;/h1&gt;
    &lt;p&gt;Ричард Кларк&lt;/p&gt;
&lt;/header&gt;
</code></pre>
<p>Следующая проблема состоит в очередном использовании элементов там, где они необязательны:</p>
<pre><code tabindex="0" class="language-html">&lt;!-- Не копируйте этот код! Он неправильный! --&gt;
&lt;header&gt;
    &lt;hgroup&gt;
        &lt;h1&gt;Моя компания&lt;/h1&gt;
        &lt;h2&gt;Основана в 1893 году&lt;/h2&gt;
    &lt;/hgroup&gt;
&lt;/header&gt;
</code></pre>
<p>Когда <code>&lt;hgroup&gt;</code> — это единственный дочерний элемент <code>&lt;header&gt;</code>, то какой смысл в этом <code>&lt;header&gt;</code>? Если в нём нет дополнительных элементов, соседствующих с <code>&lt;hgroup&gt;</code>, смело избавляйтесь от <code>&lt;header&gt;</code>.</p>
<pre><code tabindex="0" class="language-html">&lt;hgroup&gt;
    &lt;h1&gt;Моя компания&lt;/h1&gt;
    &lt;h2&gt;Основана в 1893 году&lt;/h2&gt;
&lt;/hgroup&gt;
</code></pre>
<p>Больше примеров использования <code>&lt;hgroup&gt;</code> вы можете найти <a href="https://html5doctor.com/hgroup">в отдельной, более подробной статье</a>.</p>
<h2>Не оборачивайте все списки ссылок в <code>&lt;nav&gt;</code></h2>
<p>На момент написания статьи существует <a href="https://dev.w3.org/html5/html4-differences/#new-elements">более 30-ти новых элементов</a> и неудивительно, что у нас разбегаются глаза, когда дело доходит до создания осмысленной структурной разметки. Поэтому не стоит злоупотреблять всеми доступными сейчас суперсемантическими элементами. Что, к сожалению, часто происходит с элементом <code>&lt;nav&gt;</code>. Спецификация определяет роль <code>&lt;nav&gt;</code> следующим образом:</p>
<blockquote>
<p>Элемент <code>&lt;nav&gt;</code> представляет собой часть страницы, которая ссылается на другие страницы или части текущей, то есть раздел с навигационными ссылками.</p>
<p>Замечание: не все группы ссылок на странице должны быть обёрнуты в элемент <code>&lt;nav&gt;</code> — этот элемент, главным образом, предназначен для группировки главных навигационных блоков. В частности, в подвалах часто содержатся короткие списки ссылок на различные части сайта, вроде правил использования сервиса, домашней страницы и копирайтов. Элемента <code>&lt;footer&gt;</code> вполне достаточно для группировки такого рода ссылок; и несмотря на то, что элемент <code>&lt;nav&gt;</code> может быть использован в таких случаях, обычно в этом нет необходимости.</p>
<p><a href="https://www.whatwg.org/specs/web-apps/current-work/complete/sections.html#the-nav-element">Спецификация WHATWG HTML</a></p>
</blockquote>
<p>Ключевая фраза — «главных навигационных блоков». Мы можем дискутировать весь день о значении слова «главный», но для меня это значит:</p>
<ul>
<li>Самая главная навигация;</li>
<li>Поиск по сайту;</li>
<li>Вторичная навигация (что спорно);</li>
<li>Внутренняя навигация (по длинной статье, например).</li>
</ul>
<p>И хотя здесь не может быть «правильного» или «неправильного» использования, поверхностный опрос вкупе с моей собственной интерпретацией говорят, что следующие случаи не подходят для использования <code>&lt;nav&gt;</code>:</p>
<ul>
<li>Постраничная навигация;</li>
<li>Социальные ссылки, за исключением тех случаев, когда такие ссылки являются основной навигацией, к примеру, на сайтах <a href="http://about.me/">About Me</a> и <a href="http://flavours.me/">Flavours</a>;</li>
<li>Тэги к записи в блоге;</li>
<li>Категории записи;</li>
<li>Навигация третьего уровня;</li>
<li>Подвалы, набитые ссылками.</li>
</ul>
<p>Если вы не уверены, стоит ли оборачивать список ссылок в <code>&lt;nav&gt;</code>, просто спросите у себя: «главная ли это навигация?» Чтобы было легче ответить на этот вопрос, обратитесь к следующим правилам:</p>
<ul>
<li>«Не используйте <code>&lt;nav&gt;</code>, если кажется, что в этом случае может подойти и <code>&lt;section&gt;</code> с заголовком <code>&lt;hx&gt;</code>», — <a href="http://krijnhoetmer.nl/irc-logs/whatwg/20091209#l-480">Ян Хиксон, IRC</a>.</li>
<li>Добавите ли вы этот блок в список ссылок «перейти» для улучшения доступности?</li>
</ul>
<p>Если ответ на оба эти вопроса «нет», то, скорее всего, это не <code>&lt;nav&gt;</code>.</p>
<h2>Общие ошибки с элементом <code>&lt;figure&gt;</code></h2>
<p>Ах, <code>&lt;figure&gt;</code>. Правильным использованием этого элемента вместе с подельником <code>&lt;figcaption&gt;</code> не так-то просто овладеть. Рассмотрим некоторые общие проблемы, которые я вижу при использовании <code>&lt;figure&gt;</code>.</p>
<h3>Не каждая картинка это <code>&lt;figure&gt;</code></h3>
<p>Ранее я советовал вам не писать лишний код там, где этого не требуется. Та же ошибка и здесь. Я видел сайты, где каждая картинка была обёрнута в <code>&lt;figure&gt;</code>. Нет никакой необходимости в добавлении экстра-разметки вокруг картинок только ради самого процесса. Вы просто делаете лишнюю работу и нисколько не улучшаете описание содержимого страницы.</p>
<p>Спецификация обозначает <code>&lt;figure&gt;</code> как «содержимое в потоке, с необязательным заглавием, самодостаточное, обычно упоминаемое в качестве смысловой единицы в основном тексте». Как раз в этом состоит вся красота элемента <code>&lt;figure&gt;</code>, который может быть перемещён из основного содержимого, скажем, в колонку, что никак не повлияет на основной поток документа.</p>
<p>Если это исключительно оформительская картинка, никаким образом не упомянутая в основном документе, то это точно не <code>&lt;figure&gt;</code>. Есть и другие варианты использования, но просто спросите себя: «Нужна ли эта картинка для лучшего понимания контекста?» Если нет, то это вероятно не <code>&lt;figure&gt;</code>, а, возможно, <code>&lt;aside&gt;</code>. Если да, спросите себя: «Можно ли переместить эту картинку в примечания к тексту?» Если ответ на оба вопроса «да», то это, вероятнее всего, <code>&lt;figure&gt;</code>.</p>
<h3>Ваш логотип — это не <code>&lt;figure&gt;</code></h3>
<p>Плавно переходим к следующей проблеме, вышеупомянутые правила применимы и к ней. Вот пара регулярно встречающихся примеров:</p>
<pre><code tabindex="0" class="language-html">    &lt;!-- Не копируйте этот код! Он неправильный! --&gt;
    &lt;header&gt;
        &lt;h1&gt;
            &lt;figure&gt;
                &lt;img src=&quot;logo.png&quot; alt=&quot;Моя компания&quot;&gt;
            &lt;/figure&gt;
            Название компании
        &lt;/h1&gt;
    &lt;/header&gt;
</code></pre>
<pre><code tabindex="0" class="language-html">    &lt;!-- Не копируйте этот код! Он неправильный! --&gt;
    &lt;header&gt;
        &lt;figure&gt;
            &lt;img src=&quot;logo.png&quot; alt=&quot;Моя компания&quot;&gt;
        &lt;/figure&gt;
    &lt;/header&gt;
</code></pre>
<p>Добавить здесь нечего: это совсем неправильно. Мы можем спорить до посинения насчёт того, должно ли лого находиться внутри <code>&lt;h1&gt;</code>, но мы здесь не за этим. Настоящая проблема — в неправильном употреблении <code>&lt;figure&gt;</code>. Этот элемент должен использоваться, только если он упоминается в документе или контексте общего структурного элемента. Будет честным признать, что ваш логотип вряд ли будет упомянут подобным образом. Просто не делайте так. Всё, что вам нужно, это:</p>
<pre><code tabindex="0" class="language-html">&lt;header&gt;
    &lt;h1&gt;Название компании&lt;/h1&gt;
    &lt;!-- и всё остальное здесь --&gt;
&lt;/header&gt;
</code></pre>
<h3>Элемент <code>&lt;figure&gt;</code> — это не только картинки</h3>
<p>Другое распространённое заблуждение насчёт <code>&lt;figure&gt;</code> — что он может быть использован только для картинок. Это не так. Элемент <code>&lt;figure&gt;</code> может быть видео, аудио, графиком (на SVG, к примеру), цитатой, таблицей, блоком кода, фрагментом текста или любой комбинацией этих и многих других элементов. Не ограничивайте использование <code>&lt;figure&gt;</code> только картинками. Наша работа, как энтузиастов от веб-стандартов, заключается в том, чтобы максимально точно описывать содержимое при помощи разметки.</p>
<p>Некоторое время назад я <a href="https://html5doctor.com/figure">писал про <code>&lt;figure&gt;</code> детальнее</a>. Рекомендую почитать, если вам интересно узнать подробности или просто освежить знания.</p>
<h2>Не используйте ненужные атрибуты <code>type</code></h2>
<p>Эта проблема, пожалуй, является самой распространённой среди заявок в Галерею HTML5. И хотя это, по сути, не ошибка, мне кажется, что правильнее будет её избегать.</p>
<p>Дело в том, что в HTML5 не нужно добавлять атрибут <code>type</code> для элементов <code>&lt;script&gt;</code> и <code>&lt;style&gt;</code>. Если эти элементы автоматически добавляются вашей CMS, то избавиться от них может быть непросто, но если вы пишете код руками или полностью контролируете шаблоны, то нет никакого смысла писать избыточные атрибуты. Поскольку все браузеры ожидают, что скриптами будет JavaScript, а стилями CSS, вам это совсем не нужно:</p>
<pre><code tabindex="0" class="language-html">&lt;!-- Не копируйте этот код! Он неправильный! --&gt;
&lt;link type=&quot;text/css&quot; rel=&quot;stylesheet&quot; href=&quot;styles.css&quot;&gt;
&lt;script type=&quot;text/javascript&quot; src=&quot;scripts.js&quot;&gt;&lt;/script&gt;
</code></pre>
<p>Вместо этого просто напишите:</p>
<pre><code tabindex="0" class="language-html">&lt;link rel=&quot;stylesheet&quot; href=&quot;styles.css&quot;&gt;
&lt;script src=&quot;scripts.js&quot;&gt;&lt;/script&gt;
</code></pre>
<p>Кроме того, вы можете уменьшить объём кода, который пишете для задания кодировки документа и других вещей. Глава про семантику в «<a href="http://diveintohtml5.org/semantics.html">Погружении в HTML5</a>» Марка Пилгрима объясняет всё.</p>
<h2>Неправильное использование атрибутов форм</h2>
<p>Вы, должно быть, знаете, что HTML5 предложил набор новых атрибутов для элементов форм. Мы расскажем о них подробнее в ближайшие месяцы, а пока я покажу вам некоторые вещи, которые делать не стоит.</p>
<h3>Одиночные атрибуты</h3>
<p>Некоторые новые атрибуты для элементов форм являются одиночными, и только одно их присутствие в разметке обеспечивает смену поведения. Эти атрибуты включают:</p>
<ul>
<li><code>autofocus</code></li>
<li><code>autocomplete</code></li>
<li><code>required</code></li>
</ul>
<p>Надо признаться, я нечасто встречал подобное, но если в качестве примера взять атрибут <code>required</code>, то попадалось следующее:</p>
<pre><code tabindex="0" class="language-html">&lt;!-- Не копируйте этот код! Он неправильный! --&gt;
&lt;input type=&quot;email&quot; name=&quot;email&quot; required=&quot;true&quot;&gt;
&lt;!-- Ещё один плохой пример --&gt;
&lt;input type=&quot;email&quot; name=&quot;email&quot; required=&quot;1&quot;&gt;
</code></pre>
<p>В конечном счёте это никому не вредит. HTML-парсер видит атрибут <code>required</code> в разметке, поэтому требуемая функциональность будет включена. Но что, если поставить код вверх ногами и написать <code>required=&quot;false&quot;</code>?</p>
<pre><code tabindex="0" class="language-html">&lt;!-- Не копируйте этот код! Он неправильный! --&gt;
&lt;input type=&quot;email&quot; name=&quot;email&quot; required=&quot;false&quot;&gt;
</code></pre>
<p>Парсер по-прежнему увидит атрибут <code>required</code> и применит поведение, несмотря на то, что вы пытались сказать ему не делать этого. Явно не то, что вам было нужно.</p>
<p>Существует три правильных способа задания одиночных атрибутов, второй и третий из которых нужны только если вы пишете XHTML:</p>
<ul>
<li><code>required</code></li>
<li><code>required=&quot;&quot;</code></li>
<li><code>required=&quot;required&quot;</code></li>
</ul>
<p>Если применить эту запись к нашему примеру (в HTML), получится следующее:</p>
<pre><code tabindex="0" class="language-html">&lt;input type=&quot;email&quot; name=&quot;email&quot; required&gt;
</code></pre>
<h2>Резюмируя</h2>
<p>Было бы просто невозможным перечислить здесь все странные приёмы и образцы разметки, с которыми мне довелось столкнуться, но эти — одни из самых часто встречавшихся. А какие ещё ошибки разметки бросались вам в глаза? Расскажите в комментариях.</p>

                    ]]></description><pubDate>Wed, 27 Jul 2011 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/avoiding-html5-mistakes/</guid></item><item><title>Элементы small и hr</title><link>https://web-standards.ru/articles/small-hr-elements/</link><description><![CDATA[
                        <p>Не так давно мы писали <a href="https://web-standards.ru/articles/small-hr-elements/articles/i-b-em-strong-elements/">о презентационных элементах <code>&lt;i&gt;</code> и <code>&lt;b&gt;</code></a> из HTML4, возродившихся к новой семантической жизни. Другие два элемента, которые претерпели изменения, чтобы получить значения в HTML5 — это <code>&lt;small&gt;</code> и <code>&lt;hr&gt;</code>:</p>
<ul>
<li><code>&lt;small&gt;</code> — использовался раньше для того, чтобы просто сделать текст помельче, теперь же он предназначен для пометок и небольших надписей (<a href="https://dev.w3.org/html5/markup/small.html">W3C:Markup</a>, <a href="https://www.whatwg.org/specs/web-apps/current-work/multipage/text-level-semantics.html#the-small-element">WHATWG</a>);</li>
<li><code>&lt;hr&gt;</code> — использовался раньше для создания горизонтальных линеек, теперь предназначен для смысловой разбивки на уровне текстовых блоков (<a href="https://dev.w3.org/html5/markup/hr.html">W3C:Markup</a>, <a href="https://www.whatwg.org/specs/web-apps/current-work/multipage/grouping-content.html#the-hr-element">WHATWG</a>).</li>
</ul>
<h2>Элемент <code>&lt;small&gt;</code></h2>
<blockquote>
<p>Элемент <code>&lt;small&gt;</code> представляет собой так называемые «надписи мелким шрифтом», вроде тех, что используются для предостережений и оговорок в юридических документах.
<a href="https://dev.w3.org/html5/markup/small.html">Спецификация W3C</a></p>
</blockquote>
<p>Теперь <code>&lt;small&gt;</code> применяется для пометок и является более локальным аналогом элемента <code>&lt;aside&gt;</code> — для второстепенной информации на странице. Типичным примером будет следующая за основным содержимым юридическая болтовня, вроде заявлений об авторском праве в подвале, отказе от ответственности, или же информации о лицензии. Он также может быть использован для указания авторства. <strong>Не используйте его для содержимого на блочном уровне</strong> (параграфы, списки и т.д.), иначе это будет воспринято как основное содержимое.</p>
<pre><code tabindex="0" class="language-html">&lt;p&gt;
    Я использую
    &lt;span class=&quot;museo&quot;&gt;Museo Slab&lt;/span&gt;,
    &lt;small class=&quot;font-license&quot;&gt;
        шрифт Жоса Буйвенга (exljbris)
        &lt;a href=&quot;http://www.exljbris.nl/&quot;&gt;www.exljbris.nl&lt;/a&gt;
    &lt;/small&gt;
&lt;/p&gt;
</code></pre>
<p>Использование <code>&lt;small class=&quot;font-license&quot;&gt;</code> для того, чтобы выполнить требования лицензионного соглашения</p>
<pre><code tabindex="0" class="language-html">&lt;small&gt;
    &lt;a href=&quot;http://creativecommons.org/licenses/by-sa/3.0/&quot;
        rel=&quot;license&quot;&gt;
        Creative Commons Attribution Share-alike license
    &lt;/a&gt;
&lt;/small&gt;
</code></pre>
<p>Использование <code>&lt;small&gt;</code> вокруг ссылки на <a href="http://creativecommons.org/choose/">Creative Commons license</a> с <code>rel=&quot;license&quot;</code></p>
<p>Текст внутри <code>&lt;small&gt;</code> необязательно должен быть меньше, чем окружающий — если же вам нужен именно мелкий текст, то для этого лучше подойдет CSS. Используйте <code>&lt;small&gt;</code> только на строчном уровне. В конце концов, <code>&lt;small&gt;</code> не влияет на семантику <code>&lt;em&gt;</code> или <code>&lt;strong&gt;</code>.</p>
<h2>Элемент <code>&lt;hr&gt;</code></h2>
<blockquote>
<p><code>&lt;hr&gt;</code> служит для смысловой разбивки на уровне блоков текста
<a href="https://dev.w3.org/html5/markup/hr.html">Спецификация W3C</a></p>
</blockquote>
<p>Пообщавшись с <a href="http://ian.hixie.ch/">Яном Хиксоном</a> (редактором HTML5) я понял, что до сих пор неправильно понимал <a href="https://www.whatwg.org/specs/web-apps/current-work/multipage/content-models.html#paragraph">модель поведения содержимого</a>. Я узнал, что <code>&lt;hr&gt;</code> на самом деле означает <q>конец одной секции и начало другой</q> — что семантически равносильно <code>&lt;/section&gt;&lt;section&gt;</code>. Но поскольку элементы вроде <code>&lt;section&gt;</code> и так указывают на это, получается, что <code>&lt;hr&gt;</code> больше подходит для смысловой разбивки, вроде смыслового разделения внутри блока текста или отделения одной сцены в романе от другой. В любом случае, вы можете использовать этот элемент везде, где вы используете элемент <code>&lt;p&gt;</code>. Из-за унылого вида в браузерах по умолчанию, <code>&lt;hr&gt;</code> не слишком распространён сегодня, однако это не мешает оформить его при помощи CSS в виде красивого вензеля.</p>
<img src="https://web-standards.ru/articles/small-hr-elements/images/hr-separator.png" alt="Оформление hr в виде вензеля">
<pre><code tabindex="0" class="language-css">hr {
    margin: 3em 0;
    height: 24px;
    border: 0;
    background: url(flourish.png) 50% 50% no-repeat;
}
</code></pre>
<p>Оформите <code>&lt;hr&gt;</code> красиво: уберите рамку, поля и добавьте фоновую картинку.</p>
<p>IE7 и младше оправдывают свою дурную репутацию, добавляя рамку вокруг изображения, несмотря на наши усилия, <a href="http://blog.neatlysliced.com/2008/03/hr-image-replacement/">но и это можно исправить</a>. Или же вы можете просто скрыть <code>&lt;hr&gt;</code> в стилях для IE7 и младше. Если переход между частями содержимого очевиден или разделение чисто визуальное, а не смысловое, то вместо <code>&lt;hr&gt;</code> лучше нарисовать рамку или фоновую картинку на другом элементе.</p>
<p><img src="https://web-standards.ru/articles/small-hr-elements/images/corkd.png" alt="Неподходящий случай для использования hr на Cork’d."></p>
<p>На <a href="http://content.corkd.com/">Cork’d</a> используется декоративная фоновая картинка для заголовков. Но несмотря на обилие линеек, это совсем неподходящий случай для использования <code>&lt;hr&gt;</code>.</p>
<h2>В заключение</h2>
<p>В момент выхода HTML4, презентационные элементы <code>&lt;basefont&gt;</code>, <code>&lt;font&gt;</code>, <code>&lt;s&gt;</code>, <code>&lt;strike&gt;</code> и <code>&lt;u&gt;</code> уже были помечены как нежелательные для использования, в пользу применения CSS. HTML5 завершил этот процесс, избавившись от элементов <code>&lt;big&gt;</code> и <code>&lt;tt&gt;</code>.</p>
<p>Остальные презентационные элементы из HTML4 — долго игнорируемые <code>&lt;small&gt;</code> и <code>&lt;hr&gt;</code> (<a href="https://web-standards.ru/articles/small-hr-elements/articles/i-b-em-strong-elements/">наряду с <code>&lt;i&gt;</code> и <code>&lt;b&gt;</code></a>) — были пересмотрены в HTML5 с учетом их полезных, медиа-независимых семантических свойств, которые имеют отношение к их типичному использованию. Будете ли <em>вы</em> использовать их? Выскажитесь в комментариях!</p>

                    ]]></description><pubDate>Mon, 30 May 2011 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/small-hr-elements/</guid></item><item><title>Нюансы CSS</title><link>https://web-standards.ru/articles/css-nuances/</link><description><![CDATA[
                        <p>Многие используют CSS для оформления сайтов и веб-страниц, знают основные приёмы вёрстки, а если и нет, то быстро найдут решение в интернете, или им подскажут в тематических сообществах. Тем не менее многие широко используемые решения не оптимальны, и если ознакомиться со спецификациями чуть глубже, можно написать лучший код.</p>
<p>К примеру многие знают, что цвет в шестнадцатеричной нотации вида <code>#RRGGBB</code> можно писать сокращённо как <code>#RGB</code>, если каждый старший разряд совпадает с младшим, или что нуль можно писать без единиц измерения, таких как «px» или «em», так как нуль — в любой системе измерения нуль.</p>
<h2>Что считает браузер</h2>
<p>Также широко известно, что можно отцентрировать блок с шириной меньше родительского элемента при помощи <code>margin: 0 auto</code>. Однако мало кто задумывается, что почти во всех случаях дело касается непозиционированных элементов, и в этом случае значение <code>margin-top: auto</code> или <code>margin-bottom: auto</code> устанавливается равным нулю, а значит данное правило можно сократить до простого <code>margin: auto</code>.</p>
<p>Правила <code>margin-left: auto</code> и <code>margin-right: auto</code> при ширине блока меньше ширины контейнера равномерно распределяют оставшееся место. Так, задав только <code>margin-left: auto</code>, можно выровнять элемент по правому краю.</p>
<p>В обычном потоке элементы идут сверху вниз, поэтому <code>margin-top: auto</code> устанавливается нулевым. Однако для абсолютно спозиционированных элементов действует то же распределение свободного места, и аналогичным образом можно сделать <a href="http://test.csswg.org/suites/css2.1/20110111/html4/absolute-non-replaced-height-003.htm">центрирование по вертикали</a> при заданных высоте и <code>top</code> с <code>bottom</code> (не работает в IE7).</p>
<h2>Сокращения и значения по умолчанию</h2>
<p>Не менее широко распространено использование спрайтов, например: <code>background: url(icons.png) 0 0 no-repeat</code>. Однако, <code>background-position: 0 0</code> является значением по умолчанию, поэтому <code>0 0</code> в таком случае можно опустить.</p>
<p>Но если в <code>background-position</code> задана хоть одна координата: будь то <code>top</code>, <code>left</code> или <code>100%</code>, то вторая принимает значение 50%. Это может быть полезно для значков, выровненных посередине строки по вертикали — достаточно указать лишь положение слева или справа.</p>
<p>Не все знают, что опущенные в сокращённой записи правила принимают своё значение по умолчанию. Поэтому уточняющие правила надо писать после или делать сильнее общей сокращённой надписи, как <code>background-position</code> уточняет положение каждого спрайта после <code>background</code> в предыдущем примере.</p>
<p>При подобном написании:</p>
<pre><code tabindex="0" class="language-css">h1 {
    font: 2em/1 Arial,sans-serif;
}
</code></pre>
<p>задаётся не только шрифт, его размер и интерлиньяж, но и сразу сбрасывается полужирное написание (<code>font-weight: normal</code>), а также другие свойства, такие как <code>font-style</code> (курсив) и <code>font-variant</code> (капитель). Некоторые авторы совершенно зря дописывают в <code>font</code> значение <code>normal</code>. Непонятно даже к какому правилу из перечисленных трёх оно могло бы относиться — порядок следования в сокращённых свойствах неважен, и неучитывание порядка могло приводить к ошибкам лишь в устаревших браузерах.</p>
<p>Другими недопонятыми, но тоже полезными сокращёнными записями являются отдельные правила рамок <code>border-width</code>, <code>border-style</code> и <code>border-color</code>. Например, благодаря им можно задать верхнюю и нижнюю одинаковые рамки не дублированием кода в <code>border-top</code> и в <code>border-bottom</code>, а подобным образом:</p>
<pre><code tabindex="0">border: solid gray;
border-width: 3px 0;
</code></pre>
<p>Кроме меньшего размера кода, такая запись полезна тем, что каждое значение написано только один раз, и поменять, скажем, <code>solid</code> на <code>double</code> не составляет труда.</p>
<p>Более того, если будет использоваться <code>border-image</code>, то здесь явно задано, что размер боковых рамок нулевой. В противном случае <code>border-image</code> приводит к неявному появлению рамок, что может дать неожиданный эффект в углах.</p>
<p>Есть возможность, что <code>border-radius</code> может быть включен в сокращённую запись <code>border</code> (предлагался вариант с косой чертой «/»), поэтому записывайте на всякий случай <code>border-radius</code> после <code>border</code>.</p>
<h2>Переусложенение</h2>
<p>Часто, чтобы избавиться от рамки на картинке внутри ссылки, пишут:</p>
<pre><code tabindex="0" class="language-css">a img {
    border: 0;
}
</code></pre>
<p>Однако в таком случае бразуер будет на каждой картинке проверять, не находится ли она в ссылке. Если для простых страниц это кажется несущественным (вы и глазом моргнуть не успеете за те несколько миллисекунд, на которое задержится отображение страницы), то в какой-нибудь фотогалерее со сложной анимацией это может лишить вас нескольких кадров в секунду, прибавляя ощущения «тормознутости». Куда проще написать так:</p>
<pre><code tabindex="0" class="language-css">img {
    border: 0;
}
</code></pre>
<p>Эффект будет тот же самый. Уточнение, что рамка появляется на ссылках <code>a</code> в данном случае совершенно излишне.</p>
<p>Из тех же соображений производительности, как правило, незачем писать имя тэга вместе с классом и уж тем более с идентификатором, который сам по себе уникален. В данном правиле могут быть только два исключения: уточнение для конкретного тэга (возможно в этом случае у вас очень общий класс), и обход недостатка IE7, где эффекты при наведении <code>:hover</code> тормозят, если в селекторе не указан тэг (то есть надо писать <code>a.class:hover { color: #FC0; }</code>).</p>
<h2>Наследование</h2>
<p>Ещё одна недооценённая многими возможность CSS: наследование стилей. Например, может не устраивать, что по умолчанию содержимое ячеек таблиц центрируется по вертикали, при этом используются следующие правила:</p>
<pre><code tabindex="0" class="language-css">th {
    vertical-align: bottom;
}

td {
    vertical-align: top;
}
</code></pre>
<p>Вроде бы всё здорово, но, сделав так, вы лишаетесь простого способа переопределить выравнивание для целого ряда:</p>
<pre><code tabindex="0" class="language-css">tr.images {
    vertical-align: middle;
}
</code></pre>
<p>Этого можно избежать, воспользовавшись тем, что ячейки таблицы <code>th</code> и <code>td</code> наследуют правила от рядов <code>tr</code>, а те в свою очередь от блоков <code>thead</code>, <code>tfoot</code> и <code>tbody</code>.</p>
<pre><code tabindex="0" class="language-css">thead {
    vertical-align: bottom;
}

tbody,
tfoot {
    vertical-align: top;
}
</code></pre>
<p>Стоит отметить, что все браузеры, кроме IE8, наследуют еще и значение <code>text-align</code> для <code>th</code>, а сам IE8 понимает ключевое слово <code>inherit</code>, что также позволяет наследовать значение <code>text-align</code>.</p>
<h2>Заключение</h2>
<p>Знание основ спецификаций, понимание механизмов работы браузеров и выполняемых действий позволяет оптимизировать написание кода, упростить его, сделать лаконичней, а также упростить дальнейшую разработку и поддержку.</p>

                    ]]></description><pubDate>Thu, 21 Apr 2011 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/css-nuances/</guid></item><item><title>Преимущества многострочного CSS</title><link>https://web-standards.ru/articles/multiline-css/</link><description><![CDATA[
                        <p>Предлагаю вашему вниманию моё видение наиболее удобной методики форматирования CSS-кода. Я знаю, что вопрос этот достаточно холиварный, однако материалов, статей или другого рода информации по этому вопросу очень мало, поэтому я постараюсь аргументированно раскрыть причины существования как многострочного, так и однострочного форматирования, рассказать о том, к чему приводит применение каждого из них и проанализировать, помогает или мешает выбранный стиль в работе.</p>
<p>Для начала определимся в терминологии. В CSS-коде у нас есть: блок правил — селектор и список правил для него; каждое правило состоит из свойства и его значения.</p>
<p>Есть два варианта форматирования CSS-кода, иначе говоря, расположения правил: однострочный и многострочный, а также их комбинации. Однострочное форматирование, правила для селектора записаны в одну строку:</p>
<pre><code tabindex="0" class="language-css">.example { position: absolute; top: 10px; display: block; width: 10px; height: 10px; background: url(icon.png) no-repeat }
</code></pre>
<p>Многострочное форматирование, каждая пара <code>свойство: значение</code> на новой строке:</p>
<pre><code tabindex="0" class="language-css">.example {
    position: absolute;
    top: 10px;
    display: block;
    width: 10px;
    height: 10px;
    background: url(icon.png) no-repeat;
}
</code></pre>
<p>Однострочный способ хорош в «наборах», «батареях» селекторов с одинаковыми свойствами. Например, там, где одно свойство принимает разные значения:</p>
<pre><code tabindex="0" class="language-css">.tag-cloud .xs    { font-size: 0.5em }
.tag-cloud .s     { font-size: 0.75em }
.tag-cloud .m     { font-size: 1em }
.tag-cloud .l     { font-size: 1.25em }
.tag-cloud .xl    { font-size: 1.5em }
.tag-cloud .xxl   { font-size: 1.75em }
</code></pre>
<p>Многострочный: перфекционистский, безопасный, наглядный. Давайте разберем по порядку все его преимущества.</p>
<h2>Анализ</h2>
<p>Работа с кодом — это операции написания, поиска и редактирования текста. Чтобы работать результативно, нужно преследовать две цели:</p>
<h3>Цель №1: минимизация шанса на ошибку</h3>
<p>Некоторые из ошибок бывают вполне безобидны (вроде дублирования одного и того же свойства или одновременного использования двух взаимоисключающих), а некоторые могут быть критичны. Например, ошибочное переопределение или опечатка, которая блокирует работу следующих свойств.</p>
<h3>Цель №2: скорость восприятия свойств</h3>
<p>Основной труд при написании CSS файла — это объявление значений определенных свойств. Значит, нужно иметь набор этих свойств наглядно и под рукой, в рамках одного селектора.</p>
<p>Разве вам нужно следить за огромным количеством строк одновременно? Посмотрите, например, как думает программист, пишущий новую функцию. Его внимание сосредоточено только на этой функции. Так и у верстальщика — работа идет одновременно только с одним селектором. Столбик — самая наглядная запись. Он является потомком примитивного <em>списка в столбик.</em> Вы же не будете отрицать, что вытянутые в строку элементы списка теряют в доступности?</p>
<p>Например, довольно трудно найти отдельный элемент этого списка:</p>
<pre><code tabindex="0" class="language-css">Я полюбил { Машеньку: болтушку; Юшечку: тихоню; Лизоньку: умницу; Алиночку: скромницу; }
</code></pre>
<p>Гораздо проще найти нужный нам пункт в таком виде:</p>
<pre><code tabindex="0" class="language-css">Я полюбил {
    Машеньку: болтушку;
    Юшечку: тихоню;
    Лизоньку: умницу;
    Алиночку: скромницу;
}
</code></pre>
<p>Не нужно думать, что HTML-верстка это нечто обособленное в плане написания кода. Обратите внимание на практики, которые используют программисты, пишущие на JavaScript, PHP или Perl. Пара <code>свойство:значение</code> в CSS — это объявление той же переменной, только с ограниченным вариантом значений.</p>
<p>Пример JavaScript-кода:</p>
<pre><code tabindex="0" class="language-js">function example() {
    var position = 'absolute', display = 'block', width = 10, height = 10, background = 'icon.png';
    // …
}
</code></pre>
<p>То же самое, но в традиционном для JavaScript стиле форматирования:</p>
<pre><code tabindex="0" class="language-js">function example() {
    var position = 'absolute',
        display = 'block',
        width = 10,
        height = 10,
        background = 'icon.png';
    // …
}
</code></pre>
<p>Теперь давайте вернёмся и еще раз перечитаем как <a href="https://web-standards.ru/articles/multiline-css/#target-2">сформулирована цель №2</a>: ключевое слово <em>«свойства».</em> Нам не так часто приходится править селектор или расположение блоков с селекторами, но очень часто приходится править набор правил, иметь ввиду значение свойств, следить, какие уже объявлены, а каких еще нет. Именно поэтому важно <strong>сконцентрировать</strong> свое внимание на наборе правил.</p>
<h2>Автоматизация</h2>
<p>Помимо привычного нам ручного копания в коде, можно использовать автоматические решения. Далее речь пойдет о тех приемах, которые каждый способен применять в IntelliJ IDEA. Возможно, ваш любимый редактор кода тоже способен на нечто подобное.</p>
<p>Среди автоматических способов решения проблем первым стоит упомянуть построчную валидацию CSS-свойств в рамках селектора. Пусть машина помогает вам находить опечатки и проблемные фрагменты кода:</p>
<img src="https://web-standards.ru/articles/multiline-css/images/validation.png" alt="">
<p>При многострочном форматировании сложнее пропустить:</p>
<ul>
<li>переопределение одного правила другим и уже объявленное правило;</li>
<li>пропущенную <code>;</code> в последовательности правил;</li>
<li>незаметные опечатки, вроде <code>height</code> и <code>heigth</code>;</li>
<li>хаки.</li>
</ul>
<h3>Визуализация цветов</h3>
<p>У каждой строки с использованием цветового кода мой редактор визуализирует цвет. Это нагляднее. Привлекает внимание быстрее. Еще один способ быстро найти нужное.</p>
<img src="https://web-standards.ru/articles/multiline-css/images/colors.png" alt="">
<h3>Порядок свойств, селекторов, решений</h3>
<p>Организация свойств и селекторов поможет вам значительно улучшить понимание и читабельность кода, ускорить доступ к тем или иным решениям. Вы можете работать самостоятельно или в команде, над одним файлом или над гигантским проектом. В любой ситуации порядок будет на вашей стороне.</p>
<p>Я рекомендую разбить ваш CSS на три части. Это может быть как в прямом смысле разделение по файлам, так и просто мысленное принятие этого правила. Первая часть — это сброс умолчаний, так называемый reset.css, вторая — правила для HTML-тегов, третья — объявления классов и уникальных идентификаторов. Любой из этих частей может и не быть, но так можно организовать порядок на верхнем уровне. В каждой из этих частей всё тоже можно организовать: теги сгруппировать по функциональному признаку, классы и идентификаторы по решениям.</p>
<p>После того, как сделано всё вышесказанное, возникает вопрос: как организовать свойства в рамках одного селектора — по алфавиту, по длине записи, по частоте использования? Эти методы не годятся потому, что организация, навязываемая ими, не отвечает нашим целям. Вот, например, всё отсортировано по алфавиту, и чтобы увидеть какой <code>z-index</code> у абсолютно позиционированного блока, нужно выискивать его вдалеке от <code>position: absolute</code>, а свойство <code>left</code> совершенно неочевидно будет соседствовать с <code>letter-spacing</code>.</p>
<p>Наиболее правильный метод — это функциональная группировка. Например: позиционирование, поведение блока, размеры, цвета и т.д. Один из наиболее продуманных вариантов сортировки есть <a href="http://code.google.com/p/zen-coding/wiki/ZenCSSPropertiesEn">в документации к проекту Zen Сoding</a>.</p>
<p>Случается, что вам приходится работать с неопрятным кодом, доставшимся вам от других разработчиков. В этом случае можно автоматически пересортировать CSS-свойства в нужном вам порядке утилитой <a href="http://csscomb.ru/">CSScomb</a>.</p>
<h3>Дополнительное окно</h3>
<p>Несколько окон для одного файла позволяют удобно работать с отдаленными участками кода. Можно подсмотреть решение, скопировать, сверить с чем-то. Опять-таки, я берегу внимание, избавляюсь от беготни по файлу вверх и вниз.</p>
<a href="https://web-standards.ru/articles/multiline-css/images/split-view.png">
    <img src="https://web-standards.ru/articles/multiline-css/images/split-view-small.png" alt="">
</a>
<h3>Сравнение содержимого построчно</h3>
<p>Вы можете писать однострочный CSS, но вполне вероятно что вы вырастете до работы над большими проектами с множеством файлов и большим объемом CSS. Вам придётся работать с системой контроля версий, построением проекта и другими разработчиками в вашей команде. Обратите внимание на то, что разница между двумя файлами вычисляется именно построчно, и при однострочной записи вам будет труднее выявить различия, так как строка будет различаться сразу несколькими свойствами:</p>
<a href="https://web-standards.ru/articles/multiline-css/images/diff.png">
    <img src="https://web-standards.ru/articles/multiline-css/images/diff-small.png" alt="">
</a>
<h3>Доступ по селектору через поиск</h3>
<p>Один из главных аргументов <em>однострочников</em> сводится к тому, что перед глазами сразу располагаются только нужные селекторы. Процесс поиска нужных селекторов можно банально и очень эффективно автоматизировать при помощи поиска по файлу или проекту. Пусть машина ищет, пока вы экономите своё драгоценное внимание. Никакого просматривания глазами и выискивания. Никакой прокрутки. Не напрягайте глаза больше, чем это реально нужно.</p>
<h3>Доступ к свойству конкретного селектора по номеру строки</h3>
<p>Номер строки можно найти так: проинспектировать нужный блок в верстке с помощью Firebug, Web Inspector или Dragonfly, затем найти нужный селектор, который срабатывает для данного блока и подсмотреть номер строки и имя файла. Далее дело техники и знания горячих клавиш вашего любимого редактора. Объем CSS-кода и его растянутость (излишняя, как считают некоторые) не имеет никакого значения в этом случае.</p>
<h3>Комментирование</h3>
<p>При построчной записи удобнее комментировать конкретное свойство. Например напомнить, что оно переопределено в файле ie6.css. Или указать, что эта ширина может меняться в зависимости от наличия другого класса.</p>
<a href="https://web-standards.ru/articles/multiline-css/images/comment.png">
    <img src="https://web-standards.ru/articles/multiline-css/images/comment-small.png" alt="">
</a>
<p>Взгляните, как комментируют свой код программисты — люди высокой организации мышления. Сложно оставить комментарий-послание себе или другим специалистам, если у вас однострочный код. И не говорите, что это вам не нужно. Когда понадобится неожиданно — будет мучительно неудобно.</p>
<p>При должной сноровке и знании горячих клавиш вашего любимого редактора закомментировать одно свойство на одной строке — легко. Комментарий <code>/* … */</code> автоматически обхватит свойство в многострочной записи. В однострочной записи вам недоступна такая роскошь. <q>А я комментирую, ломая имя свойства иксиком или другим символом</q> — детский сад, и к тому же небезопасно для обработки браузером дальнейшего кода. Не стоит испытывать на прочность CSS-парсеры.</p>
<h3>А у меня CSS меньше и быстрее</h3>
<p>Теперь о мнениях из серии <q>я пишу код в одну строку потому, что так посетители не грузят пробелы или символы табуляции</q>.</p>
<p>Версия в разработке и версия в бою — разные вещи. Версия для разработки должна быть максимально заточена под нужды разработчика и способствовать минимизации шансов на ошибку или опечатку из-за невнимательности. Для боевой версии актуальны совсем другие вещи: количество запросов к серверу, объем кода, актуальность загрузки всего кода сразу либо по частям, обфускация.</p>
<h2>В итоге</h2>
<p>Рекомендую пересилить себя и в целях повышения продуктивности, как личной, так и вашего проекта в целом, задуматься: почему вы пишете именно так? Может дело в простой привычке? Если вы преследуете <a href="https://web-standards.ru/articles/multiline-css/#target-1">цель №1</a> и <a href="https://web-standards.ru/articles/multiline-css/#target-2">цель №2</a>, то настоятельно рекомендую переходить на многострочный стиль.</p>
<h3>Дополнительное чтение</h3>
<ul>
<li><a href="http://vimeo.com/17498225">CSS-менеджмент. Три года спустя</a>, доклад Вадима Макеева;</li>
<li><a href="http://www.artlebedev.ru/tools/technogrette/soft/eclipse-ant/">Eclipse: знакомство с Ant</a>, статья Сергея Чикуёнка;</li>
<li><a href="http://csscomb.ru/">CSScomb.ru</a>, инструменты для сортировки CSS-свойств.</li>
</ul>

                    ]]></description><pubDate>Mon, 13 Dec 2010 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/multiline-css/</guid></item><item><title>Какой расклад?</title><link>https://web-standards.ru/articles/state-of-layout/</link><description><![CDATA[
                        <p>Эта статья посвящается тем, кто задает правильные вопросы.</p>
<p>В этой статье я постарался рассмотреть все самые распространенные вопросы, сомнения и критику по поводу необходимости создания и внедрения новых механизмов раскладки. Статья дополняет и продолжает содержание моего выступления «<a href="https://youtu.be/ZEd7bEqe6iI">CSS3: будущее механизмов раскладки</a>». Если вы его не смотрели, то рекомендую ознакомиться.</p>
<p>Итак, у нас есть три черновика модулей CSS3: <a href="https://www.w3.org/TR/css3-grid/">Grid Positioning Module</a>, <a href="https://www.w3.org/TR/css3-flexbox/">Flexible Box Layout</a>, <a href="https://www.w3.org/TR/css3-layout/">Template Layout</a>. Каждый ценен своей уникальной идеей. Эти идеи <em>специально спроектированы</em> для решения определенного рода задач по раскладкам. Задач, которые в настоящее время решают <em>не предназначенными</em> для этого методами. Отсюда сложности в разработке и поддержке, ограничения и даже побочные эффекты этих методов.</p>
<h2>Долететь до Луны, используя катапульту</h2>
<p>Эра CSS-вёрстки, как и эра бурного развития клиентских технологий, длится не так уж и долго. С большой натяжкой — каких-то полтора десятка лет. Сначала все сайты не имели никаких раскладок. Даже графика была редкостью. Затем в веб стали приходить бизнес, индустрия развлечений, СМИ… Требования к представлению информации несоизмеримо выросли.</p>
<p>Сегодня мы находимся в ситуации, когда CSS не отвечает требованиям гибких, технологичных и надежных раскладок. Именно поэтому создаются новые модули, посвященные созданию сеток, шаблонов, гибких блоков. Параллельно с этим разрабатываются такие модули, как <a href="https://www.w3.org/TR/css3-mediaqueries/">Media Queries</a>, <a href="https://www.w3.org/TR/css3-page/">Paged Media</a> и другие. Всё это способствует расширению возможностей представления информации в разных условиях и на разных устройствах.</p>
<h2>То, что нельзя прочитать в спецификации</h2>
<p>Теперь давайте рассмотрим несколько вопросов, возникающих при размышлении о раскладках.</p>
<h3>Чем не устраивает <code>float</code>?</h3>
<p>Свойство <code>float</code> способно наделять блоки плавающим поведением. Можно поместить блок слева или справа в контексте других блоков. Недостаточная гибкость <code>float</code> заключается в невозможности поместить блок в контексте других блоков, не прижимая его к краям этого контекста. Такого поведения <a href="https://www.w3.org/TR/css3-grid/#grid-units">можно добиться</a>, используя модуль Grid.</p>
<h3>Почему бы просто не реализовать <code>display: table</code> во всех браузерах и не забыть про эти многочисленные модули?</h3>
<p>Я полностью поддерживаю стремление реализовать <code>display: table</code> во всех браузерах. Но это вовсе не означает, что <code>display: table</code> сможет дать нам гибкий и перспективный механизм создания раскладок. Как можно поменять местами две колонки, не меняя HTML-разметки? Или как, например, возможно поместить блок, который будет одновременно находиться в области этих двух колонок, и при этом их содержимое будет его обтекать? Свойство <code>display: table</code> (как и свойства <code>position</code> и <code>float</code>) проектировалось совсем не для создания раскладок.</p>
<h3>Кто круче: Grid, Flexbox, Template?</h3>
<p>Очень хороший вопрос: какой из этих трёх модулей лучше, и кто в итоге должен выиграть? На самом деле, сила этих модулей именно в комбинировании возможностей и в совместном их использовании. Эти модули не стоит воспринимать как конкурентов друг другу. Наоборот, в основе каждого лежит идея позволяющая реализовать уникальные вещи. Так, Grid предназначен для создания сетки и привязки к этой сетке; Flexbox реализует особый механизм поведения блоков; Template создает набор слотов и позволяет, управляя ими, строить раскладки.</p>
<h3>Что за разговор, это же всё очень сырое и потому неинтересное!</h3>
<p>Это действительно интересный момент. Сырое, согласен. Но почему никого не смущают и, напротив, очень даже интересуют сырые реализации <code>box-shadow</code> и <code>border-radius</code>, а также остальных модных оформительских CSS-свойств. Лично меня очень расстраивает эта возня около вещей, продиктованных модой. Сегодня уголки квадратные, завтра круглые… а послезавтра?</p>
<p>Если серьезно, то на тенюшках, красивых градиентах, кнопках и декоративных вещах нельзя построить большой и серьезный сайт. Основа сайта — раскладка. Именно поэтому мне непонятно, почему жадная до зрелищ толпа разработчиков и дизайнеров давит на производителей браузеров и повышает приоритет и престиж того же <code>border-radius</code>, а потом с салютом и ночными гуляниями отмечает выпуск новой бета-версии браузера с поддержкой этого свойства.</p>
<p>Я считаю, что внедрение, например, модуля Grid или Template может принести гораздо больше пользы, сократить тысячи часов однообразного труда и сэкономить бесчисленное количество денег при разработке раскладок для проектов по всему миру. А это очевидное развитие всей отрасли.</p>
<h3>Mozilla сказала своё слово, разработав и внедрив Flexbox. Чей ход теперь?</h3>
<p>Справедливости ради нужно сказать, что Flexbox работает не только в Firefox, но и в Safari и в Chrome. А это уже три браузера из большой пятерки. Мой прогноз: в ближайший год IE или Opera введут поддержку этого модуля. Причем я больше склоняюсь в сторону IE. Предпосылки к этому есть — активное обсуждение в рассылке <a href="https://lists.w3.org/Archives/Public/www-style/">www-style</a>, где Рабочая группа CSS (<a href="https://www.w3.org/Style/CSS/members.php3">CSS Working Group</a>) и все желающие обсуждают технические вопросы.</p>
<h3>Давайте сделаем тег <code>&lt;layout&gt;</code>?</h3>
<p>Это совсем провальное намерение. Идеология открытого веба, к которому мы все так стремимся, гласит: отделяй данные от их представления. Таким образом, внедрять экстра-разметку или какие-либо заведомо стилизованные теги — тупиковый путь.</p>
<p>И главное: <code>&lt;layout&gt;</code> — это путь в каменный век табличной вёрстки. Подобные методы не оправдали себя в создании гибких раскладок. Вот простой пример: нужно изменять внешний вид страницы и саму раскладку в зависимости от размеров окна браузера. С HTML-разметкой это невозможно. А сейчас для этого существует CSS-модуль <a href="https://www.w3.org/TR/css3-mediaqueries/">Media Queries</a>.</p>
<h3>Какие преимущества мы получаем?</h3>
<p>Создавать раскладку при помощи Grid, Flexbox и Template гораздо проще. Следовательно, не нужно оплачивать труд CSS-гуру или самому быть им. Это совсем не означает, что высококвалифицированному специалисту будет нечего делать. Наоборот, он найдет себе применение в решении более интересных задач вёрстки.</p>
<p>Создавать раскладки можно гораздо быстрее, а значит — можно сократить сроки вёрстки.</p>
<p>Раскладка на Grid, Flexbox и Template более функциональная и гибкая. Таким образом, внесение изменений будет обходиться дешевле, проще и быстрее.</p>
<p>Так как раскладкой в CSS будут заведовать определенные специально предназначенные для этого свойства, легко можно будет наладить автоматическую генерацию раскладок. Возможно, именно тогда CSS-фреймворки обретут второе дыхание.</p>
<h3>Главный вопрос</h3>
<p>Сейчас не нужно выдумывать ничего нового. Нужно сосредоточить свое внимание на этих спецификациях. Осознать, что уже проделана огромная работа по придумыванию и обсуждению этих идей. Также важно, что именно Рабочая группа CSS занимается этими вопросами. Эти идеи родились в их умах. В умах людей, которые сегодня имеют непосредственное отношение к разработке браузеров. На этом уровне вопросы не решаются мгновенно, но на динамику и приоритет новых механизмов раскладок можно повлиять. К профессиональному сообществу веб-разработчиков производители браузеров, их специалисты по связям с разработчиками, технические специалисты и руководство прислушиваются всегда. Так может, спросим все вместе: «Когда же?»</p>

                    ]]></description><pubDate>Mon, 30 Aug 2010 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/state-of-layout/</guid></item><item><title>Префикс или постхак</title><link>https://web-standards.ru/articles/prefix-or-posthack/</link><description><![CDATA[
                        <p>В то время, как поддержка CSS в браузерах улучшается с каждым днём — включая впечатляющие успехи команды разработчиков IE9 — всё больше и больше авторов увлекаются CSS3. По этой причине им приходится сталкиваться с <em>браузерными префиксами</em> — свойствами вида <code>-*-</code>, вроде <code>-moz-border-radius</code>, <code>-webkit-animation</code> и так далее.</p>
<p>И конечно же, по поводу этих префиксов слышится ворчание. Звучат призывы совсем отказаться от них или объединить все специфичные браузерные префиксы в один вида <code>-beta-</code>. Главная причина всего этого шума — никто, на самом деле, не хочет писать одно и то же свойство четыре или пять раз подряд только для того, чтобы, скажем, скруглить уголки.</p>
<p>Даже если это ворчание и можно понять, оно всё равно несправедливо. На самом деле, мы должны благодарить производителей браузеров за использование префиксов и безусловно поощрять их развитие. Более того, я уверен, что префиксы должны стать основой процесса развития стандарта CSS. И я говорю это не от большой любви к повторению правил, а, напротив — от большого желания видеть последовательное развитие CSS. Я верю, что префиксы на самом деле могут ускорить развитие и совершенствование CSS.</p>
<h2>Вспоминая ужасы</h2>
<p>Для понимания того, почему браузерные префиксы в принципе существуют, очень поучительно будет вспомнить историю с <a href="https://www.w3.org/TR/CSS2/box.html">блочной моделью</a>, которая чуть не убила CSS на пороге второго тысячелетия. Противоречивые реализации блочной модели в браузерах привели тогда к серьёзному кризису. Для того, чтобы обезопасить себя от такой ситуации в будущем, мы должны придумать новый механизм на основе существующих возможностей языка и изобрести <em>принципиально новый</em> тип хаков.</p>
<p>Для наших самых маленьких читателей, пропустивших всё веселье — история произошла следующая: среди первых браузеров, поддерживающих CSS, Netscape внедрил блочную модель, найденную в CSS-спецификации. Это означало, что свойства <code>width</code> и <code>height</code> соотносились с шириной и высотой границ содержимого блока. А Internet Explorer внедрил интуитивную блочную модель, в которой свойства <code>width</code> и <code>height</code> обозначали размеры по внешней границе блока.</p>
<p>Какая бы из реализаций ни казалась удачнее, факт оставался фактом — в тот момент на рынке существовало два браузера принципиально несовместимых друг с другом, и у каждого из них была большая пользовательская база. Дело было в конце 90-х, когда мы боролись как проклятые, чтобы вырваться из трясины предупреждений «этот сайт лучше всего отображается в…», и обычной была ситуация, когда одна и та же вёрстка отлично работала в одном браузере, но напрочь разваливалась в другом.</p>
<p>Суть проблемы состояла в том, что каждый из браузеров не мог изменить своего поведения до зеркального соответствия другому. Представим на секунду, что команда разработчиков IE решает изменить поддержку CSS для большего соответствия спецификации. Подобный поступок привёл бы к тому, что десятки или даже сотни тысяч сайтов, работавших в IE, не просто где-то там сломаются, а буквально развалятся на части в определённых версиях браузера. И пока всё сообщество веб-стандартистов будет рукоплескать этому поступку, весь остальной мир откажется от браузера по причине полной его бесполезности. И даже если Рабочая группа CSS (<a href="https://www.w3.org/Style/CSS/members.php3">CSS Working Group</a>) решит изменить спецификацию для соответствия поведению IE, то Netscape столкнётся ровно с теми же трудностями.</p>
<p>Таким образом и был придуман механизм переключения <code>&lt;!DOCTYPE&gt;</code>. Вся эта история со «стандартным» (standards mode) и «хитрым» (quirks mode) режимами выросла как раз из этой ситуации. Механизм переключения <code>&lt;!DOCTYPE&gt;</code> решал многие проблемы, но его появление спровоцировали именно сложности с блочной моделью. Вдумайтесь: из-за того, что два браузера вели себя по-разному, сегодняшние браузеры вынуждены поддерживать два основных режима обработки страниц и выбирать нужный на основании SGML-декларации, в которой <em>вообще ничего</em> не сказано про обработку страниц.</p>
<p>Более того, все CSS-хаки первой волны были посвящены именно этой проблеме. Название классического представителя хаков той поры говорит само за себя: «<a href="http://tantek.com/CSS/Examples/boxmodelhack.html">хак для блочной модели</a>» (The Box Model Hack). Фактически, сам хак был основан на ошибке в синтаксической обработке значения свойства <code>voice-family</code>, но почему-то никто ни разу не назвал его «voice-family-хак».</p>
<p>Самое смешное, что это был не единственный случай, в котором непоследовательность внедрения спецификации привела к проблемам. Спустя некоторое время после появления механизма переключения <code>&lt;!DOCTYPE&gt;</code>, спасшего CSS, команда разработчиков IE внедрила некоторые CSS-свойства позиционирования. Одним из внедрённых свойств было <code>clip</code>. Наученные горьким опытом шумихи с блочной моделью, инженеры Microsoft с большим вниманием отнеслись к спецификации и сделали всё в точности так, как там было сказано.</p>
<p>Вскоре после публичного релиза браузера, Рабочая группа CSS серьёзно поменяла принцип работы свойства <code>clip</code>. Синтаксис выглядел точно так же, однако приводил совсем к другим результатам.</p>
<p>В очередной раз спецификация вошла в противоречие с публично доступным браузером — или, если хотите, наоборот. Окончательным решением стало возвращение к прежнему поведению и полный отказ от нового. Фактически, это свойство стало бесполезным для элементов с непредсказуемой высотой и шириной — т.е. для всех незамещаемых элементов в нормальном потоке, вроде элементов <code>&lt;div&gt;</code> и <code>&lt;p&gt;</code>. Подробнее о типах элементов (replaced, non-replaced) можно прочитать в <a href="https://www.w3.org/TR/CSS2/visudet.html">документации W3C</a>. Несмотря на то, что были предложены и другие варианты решения, они так и не были реализованы, и свойство <code>clip</code> утратило часть своей полезности.</p>
<h2>Представим себе другой исход</h2>
<p>Предположим, что вместо внедрения свойства <code>clip</code> разработчики IE внедрили бы свойство <code>-ms-clip</code>. В этом случае было бы не так сложно справиться с последующим изменением поведения в спецификации. Из-за того, что префикс обозначает для свойства статус «в разработке», в дальнейшем производителю браузера будет гораздо проще пересмотреть работу свойства. Таким образом, разработчики IE могли бы поменять поведение свойства <code>-ms-clip</code>, в следующем релизе и объяснить разработчикам, что они обновили экспериментальное свойство для соответствия спецификации.</p>
<p>И даже если бы они решили, что сделать это невозможно, вся опасность «неправильного» внедрения была бы изолирована в рамках префиксной версии свойства. Другие производители браузеров могли бы внедрить новую версию свойства <code>clip</code> (со своим префиксом) и это никак бы не повлияло на то, что сделали разработчики IE. Один производитель никак не смог бы навредить своими действиями спецификации и другим производителям.</p>
<p>В этом состоит польза префиксов: это способ показать, что свойство находится «в разработке» и не обязательно будет вести себя так же в будущих релизах; это решение для разработчиков, которым необходимо внести изменения в работу свойства; это защита от неудачной или преждевременной реализации, которая вполне может случиться при первом внедрении. Префиксы придают столь необходимую гибкость процессу развития CSS.</p>
<p>Мы конечно можем заявить: «Когда браузер неправ по отношению к спецификации, то он должен изменить реализацию, даже если это сломает вёрстку сайтов». Благодаря предупреждению о рабочем статусе свойства, которое подразумевают префиксы, сделать это становится гораздо проще. Без использования префиксов это крайне сложно или, зачастую, просто невозможно. Microsoft так и не изменила способ расчёта <code>width</code> и <code>height</code> для устаревших сайтов — вместо этого она использовала объявление <code>&lt;!DOCTYPE&gt;</code> для включения другого поведения для новых (теоретически, более совместимых со стандартами) сайтов. Это был очень полезный и необходимый трюк, но один из тех, что срабатывают только один раз.</p>
<h2>И даже сейчас мы страдаем</h2>
<p>И если вы думаете, что все эти глупости стали частью истории, вот вам два примера несовместимости, существующие <em>прямо сейчас</em>:</p>
<p>Браузеры на движках Mozilla и WebKit обрабатывают размытие для свойства <code>box-shadow</code> по-разному, и ни одна из реализаций полностью не соответствует спецификации. И пока я пишу эти строки, длинные и жаркие дебаты кипят в рассылке www-style. По меньшей мере одной, а, возможно, и обеим реализациям размытия тени придётся измениться для достижения совместимости. То же самое справедливо и для реализаций Opera и Microsoft.</p>
<p>Браузеры на движках Mozilla и WebKit поддерживают градиенты, используя <strong>категорически</strong> разный синтаксис для достижения простых результатов. А теперь представьте ситуацию, в которой производители браузеров внедрили бы градиенты без префиксов. В ней мы имеем три варианта:</p>
<ol>
<li>Выбрать, какой браузер получит градиенты, а какой нет;</li>
<li>использовать CSS-хаки или фильтрацию браузеров для выдачи разных стилей разным браузерам;</li>
<li>полностью отказаться от использования градиентов.</li>
</ol>
<p>И мы имеем здесь <em>целых три</em> варианта только потому, что градиенты, реализованные в этих браузерах, имеют очень разный синтаксис, что открывает дорогу для первого варианта. В случае, когда обе реализации имели бы схожий синтаксис значения, но различную реализацию — как в истории со свойством <code>clip</code> — у нас были бы только два последних варианта: хакать и фильтровать для выдачи разных стилей, либо просто отказаться от этой затеи.</p>
<p>Мы уже вдоволь находились по этим граблям за всю историю развития CSS. Нет смысла наступать на них ещё раз, если первая дюжина попыток закончилась плохо.</p>
<h2>Пре-фикс или пост-хак</h2>
<p>Но настолько ли хороши префиксы? В конце концов, было же сказано, что префиксы — это всего лишь новые CSS-хаки. Как сказал Аарон Густавсон в <a href="http://www.alistapart.com/articles/stop-forking-with-css3/">недавней статье</a>, это:</p>
<pre><code tabindex="0">-moz-border-radius: 10px 5px;
-webkit-border-top-left-radius: 10px;
-webkit-border-top-right-radius: 5px;
-webkit-border-bottom-right-radius: 10px;
-webkit-border-bottom-left-radius: 5px;
border-radius: 10px 5px;
</code></pre>
<p>…слишком напоминает вот эту историю:</p>
<pre><code tabindex="0">padding: 10px;
width: 200px;
w\idth: 180px;
height: 200px;
heigh\t: 180px;
</code></pre>
<p>С точки зрения чрезмерного повторения и раздражения — да, они более чем похожи. Но, с другой стороны, они принципиально различаются: префиксы дают нам контроль над судьбой наших хаков. В прошлом нам приходилось выискивать кучу ошибок парсера только для того, чтобы заставить противоречивые реализации работать одинаково, как только мы выясняли, что они работают противоречиво. Таким образом, мы реагировали на имеющуюся проблему. Использование префиксов — это упреждающий подход.</p>
<p>Более того, префиксы — это временный хак. Со временем, когда реализация свойств станет более совместимой, браузеры просто откажутся от префиксов. И тогда авторы смогут писать всего одну строку вместо шести с чем-то для свойства <code>border-radius</code>. Без префиксов мы будем вынуждены ждать очередных сомнительных реализаций и год от года поддерживать их при помощи хаков.</p>
<p>Поэтому создание единого префикса, вроде <code>-beta-</code> или <code>-w3c-</code> — это, по меньшей мере, полшага назад. Это, конечно, позволит производителям помечать свойства префиксом «в разработке» и делать в дальнейшем необходимые изменения, но, к сожалению, полностью закроет для авторов возможность исключать поддержку или просто отдавать различные значения каждому отдельному браузеру, в случае очередной сомнительной реализации. С точки зрения авторов, единый префикс ничем не лучше, чем полный отказ от них.</p>
<p>Иногда мне кажется, что так же история обстоит и с методами предварительной обработки кода для работы с префиксами — серверными (при помощи <a href="http://lesscss.org/">Less</a>) или клиентскими (куча JS-фреймворков). Использование этих инструментов позволяет просто писать правило <code>border-radius</code> и поручать им разворачивание этой строки в список необходимых правил с префиксами. С одной стороны, это уменьшает количество необходимого кода и повышает его чистоту и понятность. С другой стороны, этот подход мало чем отличается от ситуации с единым префиксом или вообще без них — всего одна неудачная реализация, и ваша вёрстка развалится.</p>
<p>Преимущество этого способа состоит в том, что если что-то пойдёт не так, то любой автор сможет вернуться, отключить препроцессинг и написать все префиксы вручную. Или сами препроцессоры кода могут обновиться для решения проблемы. В любом случае, это некоторый перебор сложности для авторов, хотя и не такой большой.</p>
<p>Обратная сторона этого способа более философская, но не менее важная: пряча свойства с префиксами за обработчиком, авторы могут забыть о том, что используют экспериментальные свойства, которые могут измениться. Неосознанно они могут начать думать, что используют что-то устоявшееся и стабильное, хотя это может быть совсем не так.</p>
<h2>Делаем префиксы действительно важными</h2>
<p>Я настолько твёрдо уверен, что префиксы — это хорошая вещь, что готов перейти к следующему логическому заключению: префиксы должны занять центральное место в развитии стандартов. Они должны быть <em>обязательными</em> для только что реализованных свойств, и должны стать механизмом, декларирующим совместимость.</p>
<p>Вот что я имею в виду: предположим, что кто-то изобрёл новое свойство под названием <code>text-curl</code>. Тотчас же трое производителей реализуют его. Каждый из них будет <em>обязан</em> добавить префикс к своей реализации. Таким образом, получается следующая картина:</p>
<pre><code tabindex="0" class="language-css">h1 {
    -webkit-text-curl: minor;
    -moz-text-curl: minor;
    -o-text-curl: minor;
    text-curl: minor;
}
</code></pre>
<p>Со временем производители совершенствуют свои реализации в ответ на сообщения об ошибках и уточнения от Рабочей группы CSS. В какой-то момент Рабочая группа решает, что две из трёх реализаций вполне совместимы со спецификацией. Тогда две эти реализации начинают поддерживать оригинальное свойство. Третья — нет.</p>
<p>В этот момент авторы могут решить упростить свои стили до следующих:</p>
<pre><code tabindex="0" class="language-css">h1 {
    -webkit-text-curl: minor;
    text-curl: minor;
}
</code></pre>
<p>В отличие от ситуации с хаками, которые только разрастаются со временем, мы просто отбрасываем ненужное. В итоге, всё, что остаётся — это одна строчка с <code>text-curl</code>.</p>
<p>И что же происходит, когда дебютирует очередная реализация? В первом своём релизе она также использует префикс, вне зависимости от количества уже существующих удачных реализаций. Значит, нам придётся вернуться к нашему CSS и поменять его следующим образом:</p>
<pre><code tabindex="0" class="language-css">h1 {
    -ms-text-curl: minor;
    text-curl: minor;
}
</code></pre>
<p>Как только Рабочая группа сочтёт реализацию свойства <code>-ms-text-curl</code> полной, префикс сможет быть отброшен в следующей версии IE. После чего CSS снова может быть уменьшен до единственной беспрефиксной строчки. И снова — количество хаков только уменьшается со временем.</p>
<p>Конечно, каждый из производителей продолжит поддерживать свойства с префиксами, так что даже если мы и не удалим эти правила, каждый из поддерживаемых браузеров распознает и использует свойство без префикса, если конечно оно будет следовать после правила с префиксом. Для любого браузера, который внедрил свойство с префиксом и не позаботился о том, чтобы привести его в беспрефиксное состояние, это свойство по-прежнему будет работать. Даже если CSS-код останется нетронутым, он всё равно продолжит функционировать.</p>
<p>Вернёмся к тому моменту, когда Рабочая группа называет две реализации полностью совместимыми и даёт им право избавиться от префиксов. Эта ситуация приводит к двум результатам. Во-первых, как я уже говорил, это говорит о том, что свойство имеет достаточный уровень совместимости, и это позволяет развивать процесс стандартизации дальше.</p>
<p>Но, с другой стороны — что, вероятно, более важно — это заставляет производителей и Рабочую группу сотрудничать в разработке тестов, необходимых для определения условий совместимости. В дальнейшем, эти тесты могут помочь остальным производителям достичь статуса совместимости гораздо раньше. Они могут выпустить версию с префиксом в одной публичной бете и отказаться от префикса буквально в следующем же бета-релизе.</p>
<p>Это полностью меняет существующий сейчас порядок вещей. Так уж пошло, что когда CSS-модуль достигает статуса возможной рекомендации (CR), производители начинают отказываться от префиксов для свойств из этого модуля. Но такая ситуация снова приводит к возможности появления некачественных реализаций и новых хаков для решения возникших проблем.</p>
<p>Как предлагалось выше, модулю должно быть позволено достигнуть статуса возможной рекомендации только в случае, когда все его свойства имеют, как минимум, две реализации без префиксов, работающие в реальных условиях. Любые подобные свойства, появившиеся после этого, должны иметь префикс, от которого они смогут избавиться, только доказав на практике полную совместимость с уже существующими реализациями без префикса. Вместо того чтобы внушать сомнения, свойства с отброшенными префиксами могут стать гарантами стабильности, вместе с уже давно известными свойствами.</p>
<h2>Заключение</h2>
<p>Если история развития веб-стандартов и научила нас чему-то, то скорее тому, что хаки будут необходимы всегда. Подставляя хаки при помощи префиксов и используя их в процессе стандартизации, мы действительно можем справиться с потенциальными проблемами и ускорить разработку CSS.</p>
<p>И когда в следующий раз вы начнёте ворчать по поводу указания одних и тех же правил четыре раза, по одному на каждый из браузеров, вспомните, что это временная проблема. Это немного напоминает вакцинацию — укол побаливает, это правда, но всё не настолько плохо по сравнению с болезнью, которую он предотвращает. И в нашем случае вы вакцинируетесь от многолетней возни с хаками, от фильтрации браузеров, и от других скверных вещей. Мы уже однажды пережили эту эпидемию. При должном использовании префиксы позволят надолго сдержать очередную вспышку этой напасти.</p>

                    ]]></description><pubDate>Thu, 19 Aug 2010 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/prefix-or-posthack/</guid></item><item><title>Элементы i, b, em и strong</title><link>https://web-standards.ru/articles/i-b-em-strong-elements/</link><description><![CDATA[
                        <p>В то время как многие элементы из HTML4 вошли в HTML5 без существенных изменений, некоторые исторически презентационные элементы обрели новую семантику.</p>
<p>Давайте посмотрим на <code>&lt;i&gt;</code> и <code>&lt;b&gt;</code> и сравним их с семантическими преемниками — <code>&lt;em&gt;</code> и <code>&lt;strong&gt;</code>. Что получается:</p>
<ul>
<li><code>&lt;i&gt;</code> — был просто курсивом, а сейчас обозначает <em>дополнительное выделение</em> (например, для иностранных слов, технических терминов) или просто курсивное начертание текста (<a href="https://dev.w3.org/html5/markup/i.html">W3C:Markup</a>, <a href="https://www.whatwg.org/specs/web-apps/current-work/multipage/text-level-semantics.html#the-i-element">WHATWG</a>);</li>
<li><code>&lt;b&gt;</code> — просто выделял текст полужирным, а сейчас обозначает <em>стилистическое усиление</em> текста (например, для ключевых слов) или просто полужирное начертание (<a href="https://dev.w3.org/html5/markup/b.html">W3C:Markup</a>, <a href="https://www.whatwg.org/specs/web-apps/current-work/multipage/text-level-semantics.html#the-b-element">WHATWG</a>);</li>
<li><code>&lt;em&gt;</code> — обозначал выделение, а сейчас обозначает <em>экспрессивно-эмоциональное выделение</em>, т.е. слово или фразу, произнесённые иначе (<a href="https://dev.w3.org/html5/markup/em.html">W3C:Markup</a>, <a href="https://www.whatwg.org/specs/web-apps/current-work/multipage/text-level-semantics.html#the-em-element">WHATWG</a>);</li>
<li><code>&lt;strong&gt;</code> — обозначал большее усиление экспрессии, а сейчас обозначает <em>высокую важность</em>, что, в общем, почти то же самое (ещё большее усиление или важность сейчас определяется уровнем вложенности) (<a href="https://dev.w3.org/html5/markup/strong.html">W3C:Markup</a>, <a href="https://www.whatwg.org/specs/web-apps/current-work/multipage/text-level-semantics.html#the-strong-element">WHATWG</a>).</li>
</ul>
<h2>Новая семантика презентационных элементов</h2>
<p>В HTML4 элементы <code>&lt;i&gt;</code> и <code>&lt;b&gt;</code> были <a href="https://www.w3.org/TR/html401/present/graphics.html#h-15.2.1">презентационными</a>. Они по-прежнему могут использоваться там, где этого требует типографская традиция. Тем не менее, теперь они обрели семантику и могут быть оформлены при помощи CSS, что делает их <em>не только</em> презентационными — элемент <code>&lt;b&gt;</code>, например, совсем не обязательно должен быть полужирным. Поэтому <strong>для обозначения смысловой нагрузки элементов рекомендуется использовать классы</strong>; это позволит легко изменить внешний вид элементов в дальнейшем.</p>
<h3>Элемент <code>&lt;i&gt;</code></h3>
<blockquote>
    <p>Элемент <code>&lt;i&gt;</code> представляет собой фрагмент текста с дополнительным выделением или же экспрессивно-эмоциональным сдвигом относительно обычного текста (то, что обычно обозначается курсивом в типографике).</p>
    <footer>
        <cite><a href="https://dev.w3.org/html5/markup/i.html">Спецификация W3C</a>.</cite>
    </footer>
</blockquote>
<p>Обычно обозначаются курсивом: иностранные слова (с атрибутом <code>lang</code>), таксономия и технические термины, названия кораблей, пометки в сценариях, нотное письмо, вставки размышлений или рукописного текста. Пример:</p>
<pre><code tabindex="0" class="language-html">&lt;blockquote cite=&quot;http://www.trussel.com/bladerun.htm&quot;&gt;
    &lt;ol class=&quot;script semantic-list&quot;&gt;
        &lt;li&gt;
            &lt;b class=&quot;character&quot;&gt;Декард&lt;/b&gt;:
            Шевелись! Прочь с дороги!
        &lt;/li&gt;
        &lt;li class=&quot;action&quot;&gt;
            Декард стреляет и убивает Зору
            в драматической замедленной сцене.
        &lt;/li&gt;
        &lt;li&gt;
            &lt;b class=&quot;character&quot;&gt;Декард&lt;/b&gt;:
            &lt;i class=&quot;voiceover&quot;&gt;В отчёте это будет выглядеть
            рутинным устранением двойника, что, однако, совсем
            не поможет мне чувствовать себя лучше после выстрела
            женщине в спину. И снова. Жалость где-то внутри.
            К ней, к Рэйчел.&lt;/i&gt;
        &lt;/li&gt;
        &lt;li&gt;
            &lt;b class=&quot;character&quot;&gt;Декард&lt;/b&gt;:
            Декард. Б-263-54.
        &lt;/li&gt;
    &lt;/ol&gt;
&lt;/blockquote&gt;
</code></pre>
<p>В указанном примере элемент <code>&lt;i class=&quot;voiceover&quot;&gt;</code> используется для обозначения внутреннего диалога персонажа, произнесённого в другом тоне.</p>
<pre><code tabindex="0" class="language-html">&lt;blockquote&gt;
    &lt;p&gt;
        Прошлой ночью мы ели
        &lt;i lang=&quot;ja-cyrl&quot; title=&quot;угорь&quot;&gt;унаги&lt;/i&gt;,
        &lt;i lang=&quot;ja-cyrl&quot; title=&quot;копчёный лосось&quot;&gt;абури-заке&lt;/i&gt; и
        &lt;i lang=&quot;ja-cyrl&quot; title=&quot;осьминог&quot;&gt;тако&lt;/i&gt;, а все
        &lt;i lang=&quot;ja-cyrl&quot; title=&quot;тунец&quot;&gt;торо&lt;/i&gt; суши кончились.
    &lt;/p&gt;
&lt;/blockquote&gt;
</code></pre>
<p>Элемент <code>&lt;i lang=&quot;ja-cyrl&quot;&gt;</code> используется в этом примере для обозначения «идиоматических фраз из другого языка» (японских слов). Полный список значений атрибута <code>lang</code> вы можете найти в <a href="http://www.iana.org/assignments/character-sets">официальном списке IANA</a>; или же вы можете воспользоваться замечательным <a href="http://rishida.net/utils/subtags/">сервисом по поиску языковых обозначений от Ричарда Исиды из W3C</a>.</p>
<pre><code tabindex="0" class="language-html">&lt;blockquote cite=&quot;https://en.wikipedia.org/wiki/Nanotyrannus&quot;&gt;
    &lt;p&gt;
        &lt;i class=&quot;taxonomy&quot;&gt;Nanotyrannus&lt;/i&gt; («карликовый тиран»)
        принадлежит к семейству тираннозавров, и, возможно, является
        юной особью &lt;i class=&quot;taxonomy&quot;&gt;тираннозавра&lt;/i&gt;.
        Его череп, ныне находящийся в КМЕ (Кливлендском Музее
        естествознания), был найден Чарльзом Гилмором в 1942-м
        году, описан им же в 1946-м. Гилмор обозначил его
        как представителя нового вида
        &lt;i class=&quot;taxonomy&quot;&gt;Gorgosaurus lancensis&lt;/i&gt;.
    &lt;/p&gt;
&lt;/blockquote&gt;
</code></pre>
<p>Элементом <code>&lt;i class=&quot;taxonomy&quot;&gt;</code> в данном примере обозначается таксономическая единица.</p>
<p>Используйте <code>&lt;i&gt;</code> только в том случае, когда не удаётся найти ничего более подходящего: <code>&lt;em&gt;</code> для фрагментов с экспрессивно-эмоциональным выделением, <code>&lt;strong&gt;</code> для смысловой важности, <code>&lt;cite&gt;</code> для имён при цитировании или в библиографии, <code>&lt;dfn&gt;</code> для определений и <code>&lt;var&gt;</code> для математических переменных. Однако для придания курсивного начертания таким блокам текста, как ремарки, стихотворные строфы и цитаты, используйте CSS . Не забывайте использовать атрибут <code>class</code> для задания функциональной роли элемента — это позволит с легкостью переопределить стили в дальнейшем. К примеру, вы можете сделать выборку по атрибуту <code>lang</code> в CSS, используя селектор вида <code>[lang=&quot;ja-cyrl&quot;]</code>.</p>
<h3>Элемент <code>&lt;b&gt;</code></h3>
<blockquote>
    <p>Элемент <code>&lt;b&gt;</code> представляет собой фрагмент текста, который выделяется из окружающего его контекста, но не передает никакого особого значения. Это, например, ключевые слова в выдержках, названия продуктов в обзорах или другие части текста, которые по правилам типографики обычно выделяются полужирным начертанием.</p>
    <footer>
        <cite><a href="https://dev.w3.org/html5/markup/b.html">Спецификация W3C</a>.</cite>
    </footer>
</blockquote>
<p>Для текста, обрамленного в <code>&lt;b&gt;</code> (который должен просто отличаться от основного), нет необходимости использовать <code>font-style: bold</code> — можно обратиться к другим стилям: скруглённому фону, большему размеру шрифта, другому цвет или особому форматированию типа капители. К примеру, в <a href="https://web-standards.ru/articles/i-b-em-strong-elements/#deckard">приведенном выше отрывке</a>, <code>&lt;b class=&quot;character&quot;&gt;</code> используется для указания на того, кто говорит или рассказывает.</p>
<p>Текст, который набран полужирным по типографским соглашениям (а совсем <strong>не</strong> потому, что он более важен) может использоваться для выделения имён в голливудских сплетнях или в качестве вводного текста для сложных или оформленных в классическом стиле текстов:</p>
<pre><code tabindex="0" class="language-html">&lt;p class=&quot;sub-versal&quot;&gt;
    &lt;b class=&quot;opening-phrase&quot;&gt;Было около часу&lt;/b&gt;,
    когда Шерлок Холмс вернулся с прогулки.
    Он держал в руках листок голубой бумаги,
    исчирканный заметками и рисунками.
&lt;/p&gt;
</code></pre>
<p>В данном примере буквица соединяется с текстом при помощи <code>&lt;b class=&quot;opening-phrase&quot;&gt;</code>. Для ее создания используется псевдоэлемент <code>::first-letter</code>. В этом случае открывающая фраза выделена полужирным только из оформительских соображений, но если бы этот фрагмент был семантически важен, то <code>&lt;strong&gt;</code> или другой подобный элемент подошли бы лучше.</p>
<pre><code tabindex="0" class="language-html">&lt;p class=&quot;versal&quot;&gt;
    Просматривая свои записи о приключениях Шерлока Холмса —
    а таких записей, которые я вел на протяжении последних восьми
    лет, у меня больше семидесяти — я нахожу в них немало
    трагических случаев, есть среди них и забавные, есть
    и причудливые, но нет ни одного заурядного.
&lt;/p&gt;
</code></pre>
<p>Несмотря на то, что мы можем использовать <code>&lt;b&gt;</code> для традиционного типографического оформления вроде выделения капителью первого слова, фразы или предложения, псевдоэлемент <code>:first-line</code> больше подходит для таких целей. Например, все первые абзацы <a href="https://html5doctor.com/">HTML5Doctor.com</a> оформлены при помощи элегантного <code>:first-of-type</code>.</p>
<p>Используйте <code>&lt;b&gt;</code> только тогда, когда нет ничего более подходящего, например: <code>&lt;strong&gt;</code> для текста с семантической важностью, <code>&lt;em&gt;</code> для акцентированного текста (с «логическим ударением»), элементы от <code>&lt;h1&gt;</code> до <code>&lt;h6&gt;</code> для заголовков и <code>&lt;mark&gt;</code> для подсвеченного или выделенного текста. Для <a href="https://www.whatwg.org/specs/web-apps/current-work/multipage/links.html#tag-clouds">облака тегов</a> используйте список с соответствующими классами. Для воссоздания традиционных типографских приёмов используйте <a href="https://www.w3.org/TR/CSS2/selector.html#pseudo-element-selectors">CSS-селекторы псевдоэлементов</a>: <code>:first-line</code> и <code>:first-letter</code>, каждый для своего случая. И ещё раз, не забывайте использовать атрибут <code>class</code> для обозначения того, зачем был использован каждый конкретный элемент — это упростит повторное изменение элемента в дальнейшем.</p>
<h2>…и для сравнения: элементы <code>&lt;em&gt;</code> и <code>&lt;strong&gt;</code></h2>
<p>Хотя <code>&lt;em&gt;</code> и <code>&lt;strong&gt;</code> остались практически такими же, как были, в их значении всё же произошли некоторые изменения. В HTML4 они означали «акцент» и «сильный акцент». Сейчас их значение несколько видоизменилось: <code>&lt;em&gt;</code> обозначает <em>экспрессивно-эмоциональное выделение</em> (т.е. нечто, произнесённое иначе), а <code>&lt;strong&gt;</code> обозначает важность.</p>
<h3>Элемент <code>&lt;em&gt;</code></h3>
<blockquote>
    <p>Элемент <code>&lt;em&gt;</code> обозначает часть текста с эмфатическим ударением.</p>
    <footer>
        <cite><a href="https://dev.w3.org/html5/markup/em.html">Спецификация W3C</a>.</cite>
    </footer>
</blockquote>
<p>Термин «ударение» имеет отношение к лингвистике. Ударение изменяет эмоциональную окраску слова, что, в свою очередь, может изменить смысл предложения. Например, фраза «Быстро позови <em>доктора!</em>» акцентирует важность того, чтобы позвали именно доктора — возможно, в ответ на чей-то вопрос «Мне позвать сестру?» Напротив, фраза «<em>Быстро</em> позови доктора!» акцентирует важность немедленного вызова.</p>
<p>Используйте <code>&lt;strong&gt;</code> в других случаях для обозначения важности и <code>&lt;i&gt;</code> для курсивного начертания без эмоциональной окраски. Уровень вложенности обозначает силу акцентирования.</p>
<h3>Элемент <code>&lt;strong&gt;</code></h3>
<blockquote>
    <p>Элемент <code>&lt;strong&gt;</code> обозначает часть текста с высокой важностью.</p>
    <footer>
        <cite><a href="https://dev.w3.org/html5/markup/strong.html">Спецификация W3C</a>.</cite>
    </footer>
</blockquote>
<p>Добавить, в общем-то, и нечего — элемент <code>&lt;strong&gt;</code> хорошо всем нам знаком. Используйте вложенные элементы <code>&lt;strong&gt;</code> для обозначения относительной важности, <code>&lt;em&gt;</code> для эмфатического ударения и <code>&lt;b&gt;</code> для стилистически выделенных или просто полужирных фрагментов текста без подчёркнутой важности.</p>
<h2>Резюмируя…</h2>
<p>И последнее: все эти элементы (как и большинство HTML5-элементов) намеренно были сделаны <em>медиа-независимыми</em>, т.е. их семантика теперь не привязана к отображению в визуальных браузерах.</p>
<p>Что же мы имеем? Две презентационных дворняжки из HTML4 превратились в полноценные, насыщенные смыслом HTML5-элементы, готовые снова вернуться в ваш код. Сможете ли <em>вы</em> устоять перед их блестящими, полными семантики щенячьими глазками? Дайте нам знать!</p>

                    ]]></description><pubDate>Tue, 13 Jul 2010 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/i-b-em-strong-elements/</guid></item><item><title>Кроссбраузерные закруглённые уголки</title><link>https://web-standards.ru/articles/cross-browser-rounded-corners/</link><description><![CDATA[
                        <p>Согласно как древнему учению фен-шуй, так и последним медицинским исследованиям, прямые углы вредно влияют на человека. Издавна дизайнеры всего мира стараются скруглять углы чтобы уменьшить вред и сделать свою работу красивее. Это коснулось и области веб-дизайна.</p>
<p>В свою очередь, должно быть, все верстальщики решают техническую проблему оформления сайтов закруглёнными уголками, требуемыми дизайнерами.</p>
<h2>Существующие решения</h2>
<p>Самым очевидным и простым способом сделать это ещё с ранних версий HTML являются картинки. Увы, самой простой способ не всегда самый лучший или подходящий. Например, можно просто наложить уголки с цветом фона на элемент, но такой способ не годится, когда, оставшаяся за скруглением, часть должна быть прозрачной.</p>
<p>И, конечно же, хочется уменьшить число запросов к серверу, чтобы страница сразу показывалась в задуманном виде без лишних задержек.</p>
<p>Кроме того, что прозрачные картинки усложняют вёрстку, дизайнеры ещё и хотят видеть их сглаженными, а с полупрозрачными картинками есть немалые <a href="http://www.google.ru/search?q=IE6+png">сложности</a> в Internet Explorer 6. Их можно решить через фильтры, но ценой этого будет сильное проседание производительности. Пожалуй, мало кто захочет чтобы с их сайта уходили клиенты, не дождавшиеся загрузки. Но не все об этом подозревают.</p>
<p>Для облегчения работы (и снижения стоимости создания решений, основанных на веб-стандартах) было придумано и реализовано современными браузерами <a href="https://www.w3.org/TR/css3-background/#the-border-radius">CSS-свойство <code>border-radius</code></a>, которое позволяет закруглить уголки прямо средствами браузера без каких-либо дополнительных усилий.</p>
<p>Так же в старых версиях браузера Opera (начиная с 9.5) существует ещё одно <a href="http://dev.opera.com/articles/view/new-development-techniques-using-opera-k/">обходное решение</a>: использовать векторную графику <a href="https://www.w3.org/TR/SVG/">SVG</a> в качестве фона.</p>
<h2>А что же Internet Explorer?</h2>
<p>Однако, пока ещё самым распространённым в мире браузером является Internet Explorer, не поддерживающий решение ни через CSS, ни через SVG. Тем не менее, в Internet Explorer есть ещё одна малоизученная (как мы увидим дальше) возможность: один из предков SVG — векторная графика <a href="http://msdn.microsoft.com/en-us/library/bb250524(VS.85).aspx">VML</a>.</p>
<p>В VML есть предопределённая фигура: прямоугольник с закруглёнными углами <code>roundrect</code>. Решение на её основе уже <a href="http://snook.ca/archives/html_and_css/rounded_corners_experiment_ie/">давно предложено</a>, и даже есть <a href="http://www.dillerdesign.com/experiment/DD_roundies/">подключаемый компонент</a>, который с помощью скриптов создаёт закруглённые углы похожим образом. Но у этой фигуры есть один значительный недостаток: радиус кривизны задаётся через отношение к большей стороне, что делает результат непредсказуемым и неподходящим для многих случаев. Кроме автоматизации работы именно для обхода этой особенности используются скрипты в подключаемом компоненте.</p>
<p>Обычно же требуется какой-то зафиксированный радиус скругления, например, 8 пикселов. Возможно ли так сделать в Internet Explorer, не выполняя работу браузера вместо него самого?</p>
<p>Оказывается, да.</p>
<h2>Изучаем VML</h2>
<p>Для начала, нам надо определить свою фигуру. Пусть это будет простой прямоугольник:</p>
<p><a href="https://web-standards.ru/articles/cross-browser-rounded-corners/demos/1-vml.html">Пример 1. VML</a> (только IE8 и ниже).</p>
<pre><code tabindex="0" class="language-html">&lt;!DOCTYPE HTML&gt;
&lt;xml:namespace ns=&quot;urn:schemas-microsoft-com:vml&quot; prefix=&quot;v&quot;/&gt;
&lt;head&gt;
    &lt;title&gt;Пример: VML&lt;/title&gt;
    &lt;meta charset=&quot;utf-8&quot;&gt;
    &lt;style&gt;
        v\:shape {
            behavior: url(#default#VML);
            display: inline-block;
            width: 100px;
            height: 20px;
    }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;v:shape stroked=&quot;false&quot; fillcolor=&quot;#EEE&quot;
        coordorigin=&quot;0 0&quot; coordsize=&quot;1000 1000&quot;
        path=&quot;m 0,0 l 1000,0, 1000,1000, 0,1000 x e&quot;&gt;
    &lt;/v:shape&gt;
&lt;/body&gt;
</code></pre>
<p>Для запуска VML в IE нужно несколько условий.</p>
<ol>
<li>Объявление XML-документа, с подключенным пространством имён VML, в любом месте документа. Это делает вторая строчка. Указание префикса <code>v</code> говорит, что тэги VML будут записаны с этим префиксом. В примере элемент таким образом обозначен <code>&lt;v:shape&gt;</code>. В скриптах (например, для подгружаемого содержимого) объявление можно сделать с помощью команды <code>document.namespaces.add('v', 'urn:schemas-microsoft-com:vml')</code></li>
<li>Далее надо указать, чтобы элементы вели себя как VML. Это делает CSS-свойство <code>behavior:url(#default#VML)</code></li>
<li>Отображаемые элементы VML должны быть в <code>&lt;body&gt;</code>. Опускать его нельзя.</li>
<li>Для отображаемого элемента должны быть указаны размеры. Для это нужно задать свойство <code>display</code>, не равное <code>inline</code> (оно стоит по умолчанию): <code>display: inline-block</code> или <code>display: block</code> и, конечно же, надо указать сами размеры.</li>
</ol>
<figure>
    <img src="https://web-standards.ru/articles/cross-browser-rounded-corners/images/1-shot.png" alt="Пример 1. Internet Explorer 6.">
    <figcaption>Пример 1. Internet Explorer 6.</figcaption>
</figure>
<p>Можно ещё отметить, что двоеточие имеет специальный смысл в CSS, и поэтому должно быть экранировано в селекторе с помощью обратного слэша. Также популярный ранее селектор <code>v\:*</code> не работает в Internet Explorer 8, из-за чего нужно либо перечислять все элементы, либо использовать дополнительный класс. Я предпочёл первое, так как в таком случае в итоге выходит меньше кода.</p>
<p>Элемент <a href="https://www.w3.org/TR/NOTE-VML#_Toc416858386"><code>shape</code></a> задаёт произвольную фигуру. При этом использованы следующие параметры:</p>
<ul>
<li><code>coordorigin=&quot;0 0&quot;</code> задаёт начало координат в положении <code>(0, 0)</code></li>
<li><code>coordsize=&quot;1000 1000&quot;</code> задаёт размеры «холста» (за них можно выходить, но в некоторых, не связанных с VML, случаях картинка может быть обрезана)</li>
<li><code>path=&quot;m 0,0 l 1000,0, 1000,1000, 0,1000 x e&quot;</code> рисует сам прямоугольник. Использован следующий синтаксис: <code>m</code> — передвигает в начало координат, <code>l</code> рисует прямые линии через ряд координат <code>(x, y)</code>, перечисленные через запятую, <code>x</code> замыкает контур, и <code>e</code> заканчивает рисование;</li>
<li><code>fillcolor=&quot;#CCC&quot;</code> заполняет элемент светло-серым цветом, чтобы мы его смогли увидеть. Следует отметить, что, заданный через CSS, фон у VML-элементов должен быть отключен. Иначе он будет нарисован прямоугольником под фигурой, что лишит нас преимуществ.</li>
<li><code>stroked=&quot;false&quot;</code> указывает, что нам не нужна рамка. (По умолчанию рисуется тонкая.)</li>
</ul>
<h3>Сложности</h3>
<p>Как можно было заметить, все фигуры задаются в неком пространстве со своей координатной сеткой. Главной же в данном случае проблемой является то, что мы не можем так просто задать координаты в точках на экране. Надо каким-то образом связать координаты VML с пикселями, и определить свою фигуру, которая будет обладать требуемыми свойствами.</p>
<p>Как это сделать?</p>
<h3>Формулы</h3>
<p>VML имеет возможность задавать, считать и использовать <a href="https://www.w3.org/TR/NOTE-VML#_Toc416858392">определённые соотношения</a>. Простой пример, пока ещё без вычислений:</p>
<p><a href="https://web-standards.ru/articles/cross-browser-rounded-corners/demos/2-vml-formulas.html">Пример 2. Формулы в VML</a> (только IE8 и ниже).</p>
<pre><code tabindex="0" class="language-xml">&lt;v:shape coordorigin=&quot;0 0&quot; coordsize=&quot;1000 1000&quot;
    path=&quot;m 0,0 l @0,0, @0,@1, 0,@1 x e&quot;
    fillcolor=&quot;#EEE&quot; stroked=&quot;false&quot;&gt;
    &lt;v:formulas&gt;
        &lt;v:f eqn=&quot;val width&quot;/&gt;
        &lt;v:f eqn=&quot;val height&quot;/&gt;
    &lt;/v:formulas&gt;
&lt;/v:shape&gt;
</code></pre>
<figure>
    <img src="https://web-standards.ru/articles/cross-browser-rounded-corners/images/1-shot.png" alt="Пример 2. Internet Explorer 6.">
    <figcaption>Пример 2. Internet Explorer 6.</figcaption>
</figure>
<p>Этот пример выглядит точно так же как первый, что показывает работоспособность формул. Элемент <code>formulas</code> содержит в себе список формул, в атрибуте <code>eqn</code> у каждого <code>f</code> содержатся сами формулы. На результат каждого элемента <code>f</code> можно ссылаться через запись вида <code>@№</code>, где <code>№</code> — номер выражения (перечисление начинается с нуля). Сами формулы также могут ссылаться на результат предыдущих вычислений. Но обо всём по порядку.</p>
<p>Выражение <code>val</code> позволяет ссылаться на определённое значение. Оно может быть задано непосредственно, либо можно использовать какое-то ключевое слово. В данном случае это размеры «холста» (те, что задаются с помощью <code>coordsize</code>).</p>
<p>Также у фигур может быть задан параметр <code>adj</code>, на который можно ссылаться как <code>#0</code> (и далее по счёту). Он пригодится нам в дальнейшем, чтобы задавать радиус кривизны уголков. К тому же нам пригодится то, что его можно переназначать для повторно используемой фигуры.</p>
<h3>Приступаем к главному</h3>
<p>Главное, что нам пригодится в формулах кроме самих вычислений — это возможность использовать в них размер фигуры в пикселах с помощью ключевых слов <code>pixelwidth</code> и <code>pixelheight</code>. Используя это, определим требуемую фигуру:</p>
<p><a href="https://web-standards.ru/articles/cross-browser-rounded-corners/demos/3-vml-pixels.html">Пример 3. VML и пикселы</a> (только IE8 и ниже).</p>
<pre><code tabindex="0" class="language-xml">&lt;v:shape adj=&quot;8&quot; stroked=&quot;false&quot; fillcolor=&quot;#CCC&quot;
    coordorigin=&quot;0 0&quot; coordsize=&quot;1000000 1000000&quot;
    path=&quot;m @6,0 l @8,0 qx @0,@7 l @0,@9 qy @8,@1 l @6,@1 qx 0,@9 l 0,@7 qy @6,0 x e&quot;&gt;
    &lt;v:formulas&gt;
        &lt;v:f eqn=&quot;val width&quot;/&gt;
        &lt;v:f eqn=&quot;val height&quot;/&gt;
        &lt;v:f eqn=&quot;prod @0 1 pixelwidth&quot;/&gt;
        &lt;v:f eqn=&quot;prod @1 1 pixelheight&quot;/&gt;
        &lt;v:f eqn=&quot;sum pixelwidth 0 #0&quot;/&gt;
        &lt;v:f eqn=&quot;sum pixelheight 0 #0&quot;/&gt;
        &lt;v:f eqn=&quot;prod #0 @2 1&quot;/&gt;
        &lt;v:f eqn=&quot;prod #0 @3 1&quot;/&gt;
        &lt;v:f eqn=&quot;prod @4 @2 1&quot;/&gt;
        &lt;v:f eqn=&quot;prod @5 @3 1&quot;/&gt;
    &lt;/v:formulas&gt;
&lt;/v:shape&gt;
</code></pre>
<img src="https://web-standards.ru/articles/cross-browser-rounded-corners/images/coordinates.png" alt="Система координат.">
<figure>
    <img src="https://web-standards.ru/articles/cross-browser-rounded-corners/images/3-shot.png" alt="Пример 3. Internet Explorer 6.">
    <figcaption>Пример 3. Internet Explorer 6.</figcaption>
</figure>
<p>Теперь появились закруглённые уголки. Выражения формулы имеют следующий формат: «операция значение параметр1 параметр2». Над указанным значением производится какая-то операция, использующая указанные параметры. Для суммы <code>sum</code> вычисление будет «значение + параметр1 − параметр2», для умножения <code>prod</code> — «значение × параметр1 / параметр2».</p>
<p>В этом примере первые две формулы дают ссылку на размеры фигуры, как и в предыдущем.</p>
<p>В следующих двух считается отношение размеров в координатах VML к их пиксельным значениям, то есть фактически определяется сколько точек VML приходится на один пиксел по горизонтали и вертикали. Большие значения размера «холста» <code>coordsize</code> заданы для того, чтобы вычисления проводились с достаточной высокой точностью</p>
<p>Далее, из размеров в пикселах вычитается размер закругления, указанный в <code>adj</code>. Наконец, координаты, где заканчиваются уголки, пересчитываются в координаты VML и используются при задании формы фигуры.</p>
<p>Для скругления использованы довольно простые команды рисования четверти эллипса <code>qx</code> и <code>qy</code>. <code>qx</code> начинает рисование с горизонтальной черты, а <code>qy</code> — с вертикальной.</p>
<h3>Позиционирование</h3>
<p>Итак, фигура определена. Теперь нам нужно сделать так, чтобы она отображалась в нужном месте. Сделаем пример, более похожий на настоящий, и добавим код для остальных браузеров:</p>
<p><a href="https://web-standards.ru/articles/cross-browser-rounded-corners/demos/4-1-vml-pos-ib.html">Пример 4.1. VML и содержимое страницы</a>.</p>
<pre><code tabindex="0" class="language-html">&lt;style&gt;
    .round {
        display: inline-block;
        padding: 10px;
        -webkit-border-radius: 8px;
        -moz-border-radius: 8px;
        border-radius: 8px;
        background: #CCC;
    }
&lt;/style&gt;
&lt;!--[if VML&amp;(lt IE 9)]&gt;
&lt;style&gt;
    .round {
        background: none!important;
        padding: 0!important;
    }
    .round-vml {
        position: relative;
        padding: 11px 9px 9px 11px;
        height: 100%;
    }
    v\:shape,
    v\:formulas,
    v\:f { behavior: url(#default#VML) }
    v\:shape { display: inline-block }
&lt;/style&gt;
&lt;xml:namespace ns=&quot;urn:schemas-microsoft-com:vml&quot; prefix=&quot;v&quot;/&gt;
&lt;![endif]--&gt;

&lt;div class=&quot;round&quot;&gt;
&lt;!--[if VML&amp;(lt IE 9)]&gt;
    &lt;v:shape adj=&quot;8&quot; class=&quot;round-vml&quot;
        stroked=&quot;false&quot; fillcolor=&quot;#CCC&quot;
        coordorigin=&quot;0 0&quot; coordsize=&quot;1000000 1000000&quot;
        path=&quot;m @6,0 l @8,0 qx @0,@7 l @0,@9 qy @8,@1
                l @6,@1 qx 0,@9 l 0,@7 qy @6,0 x e&quot;&gt;
        &lt;v:formulas&gt;
            &lt;v:f eqn=&quot;val width&quot;/&gt;
            &lt;v:f eqn=&quot;val height&quot;/&gt;
            &lt;v:f eqn=&quot;prod @0 1 pixelwidth&quot;/&gt;
            &lt;v:f eqn=&quot;prod @1 1 pixelheight&quot;/&gt;
            &lt;v:f eqn=&quot;sum pixelwidth 0 #0&quot;/&gt;
            &lt;v:f eqn=&quot;sum pixelheight 0 #0&quot;/&gt;
            &lt;v:f eqn=&quot;prod #0 @2 1&quot;/&gt;
            &lt;v:f eqn=&quot;prod #0 @3 1&quot;/&gt;
            &lt;v:f eqn=&quot;prod @4 @2 1&quot;/&gt;
            &lt;v:f eqn=&quot;prod @5 @3 1&quot;/&gt;
        &lt;/v:formulas&gt;
&lt;![endif]--&gt;
    Закруглённые уголки — это реальность!
&lt;!--[if VML&amp;(lt IE 9)]&gt;
    &lt;/v:shape&gt;
&lt;![endif]--&gt;
&lt;/div&gt;
</code></pre>
<p>Размеры элемента можно изменять. Радиус закругления от этого не меняется.</p>
<figure>
    <img src="https://web-standards.ru/articles/cross-browser-rounded-corners/images/4.1-ie-shot.png" alt="Пример 4. Узкое окно Internet Explorer 6.">
    <figcaption>Пример 4. Узкое окно Internet Explorer 6.</figcaption>
</figure>
<p>Оказывается, что фигуры VML удачно встают на место существующих элементов, при этом очень ценно то, что для них можно (и нужно) указать стопроцентную высоту, которая будет работать. Ширину, указывать не стоит — элемент «вылезет» из-за того, что по стандартам задаётся ширина содержимого без отступов, к тому же она и так отрабатывается автоматически. В зависимости от задачи: блочный элемент или ширина по содержимому, следует использовать <code>display: block</code> — <a href="https://web-standards.ru/articles/cross-browser-rounded-corners/demos/4-2-vml-pos-b.html">пример 4.2</a> или <code>display: inline-block</code> — <a href="https://web-standards.ru/articles/cross-browser-rounded-corners/demos/4-1-vml-pos-ib.html">пример 4.1</a>. Открывающий и закрывающий тэги должны находится в начале и в конце соответственно.</p>
<figure>
    <img src="https://web-standards.ru/articles/cross-browser-rounded-corners/images/4.1-modern-shot.png" alt="Пример 4. Современный браузер.">
    <figcaption>Пример 4. Современный браузер.</figcaption>
</figure>
<p>Нижний правый угол начинает обрезаться из-за наличия <code>hasLayout</code> на родительском элементе, поэтому нужен <code>position: relative</code>. На этом этапе уже становятся заметными особенности отображения VML. Края выглядят размытыми, а отступы надо подбирать вручную, не забывая перед этим убрать отступ и фон у родительского элемента, а в дальнейшем и рамку.</p>
<h3>Завершающие штрихи</h3>
<p>VML позволяет создавать заготовки, чтобы ссылаться на них при дальнейшем использовании. Это позволяет уменьшить размер страницы благодаря повторному использованию кода. Сделаем это, а заодно и соберём разные примеры использования на одной странице. Также укажем неоднородный фон, чтобы были видны основные преимущества:</p>
<p><a href="https://web-standards.ru/articles/cross-browser-rounded-corners/demos/5-vml-roundies.html">Пример 5. Закруглённые уголки в VML</a>.</p>
<pre><code tabindex="0" class="language-html">&lt;div class=&quot;round&quot;&gt;
    &lt;!--[if VML&amp;(lt IE 9)]&gt;
    &lt;v:shape type=&quot;#vml-round&quot; class=&quot;round-vml&quot;
        stroked=&quot;false&quot; fillcolor=&quot;#FFF&quot;&gt;
    &lt;![endif]--&gt;
        Простые закруглённые уголки
    &lt;!--[if VML&amp;(lt IE 9)]&gt;&lt;/v:shape&gt;&lt;![endif]--&gt;
&lt;/div&gt;
&lt;div class=&quot;round border&quot;&gt;
    &lt;!--[if VML&amp;(lt IE 9)]&gt;
    &lt;v:shape type=&quot;#vml-round&quot;
        class=&quot;round-vml border-vml&quot; adj=&quot;13&quot;
        fillcolor=&quot;#FFF&quot; strokeweight=&quot;6px&quot; strokecolor=&quot;#BADBAD&quot;&gt;
    &lt;![endif]--&gt;
        Закруглённые уголки с рамкой
    &lt;!--[if VML&amp;(lt IE 9)]&gt;&lt;/v:shape&gt;&lt;![endif]--&gt;
&lt;/div&gt;
&lt;div class=&quot;round inline&quot;&gt;
    &lt;!--[if VML&amp;(lt IE 9)]&gt;
    &lt;v:shape type=&quot;#vml-round&quot; class=&quot;round-vml inline-vml&quot;
        stroked=&quot;false&quot; fillcolor=&quot;#FFF&quot;&gt;
    &lt;![endif]--&gt;
        Простые закруглённые уголки в строку
    &lt;!--[if VML&amp;(lt IE 9)]&gt;&lt;/v:shape&gt;&lt;![endif]--&gt;
&lt;/div&gt;
&lt;div class=&quot;round inline border&quot;&gt;
    &lt;!--[if VML&amp;(lt IE 9)]&gt;
    &lt;v:shape type=&quot;#vml-round&quot;
        class=&quot;round-vml inline-vml border-vml&quot; adj=&quot;13&quot;
        fillcolor=&quot;#FFF&quot; strokeweight=&quot;6px&quot; strokecolor=&quot;#BADBAD&quot;&gt;
    &lt;![endif]--&gt;
        Закруглённые уголки с рамкой в строку
    &lt;!--[if VML&amp;(lt IE 9)]&gt;&lt;/v:shape&gt;&lt;![endif]--&gt;
&lt;/div&gt;
</code></pre>
<p>Теперь всё выглядит примерно так:</p>
<figure>
    <img src="https://web-standards.ru/articles/cross-browser-rounded-corners/images/5-shot.png" alt="Пример 5. Internet Explorer 6.">
    <figcaption>Пример 5. Internet Explorer 6.</figcaption>
</figure>
<p>Фигура задаётся с помощью <code>shapetype</code>, затем конкретные применения ссылаются на её с помощью <code>type=&quot;#vml-round&quot;</code>.</p>
<p>Радиус закругление задаётся с помощью параметра <code>adj</code>. Можно определить в заготовке значение по умолчанию и переопределять его по мере необходимости. Толщина и цвет обрамления задаются с помощью <code>strokeweight</code> и <code>strokecolor</code> соответственно.</p>
<p>Здесь во всей красе видны недостатки подхода. Способ достаточно трудоёмкий, особенно при эмуляции рамки. VML требует отдельного прописывания свойств, которые обычно задаются через CSS: цвет фона, параметры рамки, радиус закругления. В версии CSS для Internet Explorer надо отдельно переназначать и подгонять значения отступов. Механизм отрисовки элемента с рамкой отличается от обычного CSS, из-за чего и нужно вручную подбирать параметры.</p>
<p>Вдобавок, в 6 и 7 версии IE при использовании блочных вариантов, появляется горизонтальная полоса прокрутки, с которой, видимо, надо бороться отдельно — в примере по ссылке для этого используется обрамляющий элемент.</p>
<p>VML позволяет <a href="http://msdn.microsoft.com/en-us/library/bb264134(v=VS.85).aspx">определённые действия над рамками</a>, но если вы захотите сделать неоднородную рамку, то придётся повозиться отдельно. В текущей предварительной версии IE9 пример не работает в режиме совместимости со старыми браузерами.</p>
<p>Тем не менее этот способ позволяет решить вопрос создания закруглённых уголков на произвольном фоне без использования картинок, а также при определённой доработке способен в перспективе решить более сложные вопросы: такие как использование градиентов и теней.</p>
<p>Производительность и потребление ресурсов на глаз лучше, чем при использовании фильтров.</p>

                    ]]></description><pubDate>Mon, 28 Jun 2010 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/cross-browser-rounded-corners/</guid></item><item><title>Как воспитать дизайнера</title><link>https://web-standards.ru/articles/coder-vs-designer/</link><description><![CDATA[
                        <p>От автора, 2010-й год. С момента написания этой статьи прошло три года, и сейчас я понимаю, что во многом была слишком категорична, во многом — наивна, но в общем и целом — права. Нижеприведенные рекомендации по-прежнему актуальны. Читайте, фильтруйте, отсеивайте, пользуйтесь.</p>
<p>За несколько лет профессиональной деятельности мне довелось повидать огромное количество макетов сайтов. Среди них были хорошие, плохие и совершенно ужасные, но не было ни одного идеального.</p>
<p>Можно довольно долго рассуждать о том, почему дизайнеры в огромном количестве гонят некачественный продукт. Мне кажется, основная проблема в том, что на веб-дизайнера нигде не учат. Более того, сейчас в Рунете каждый второй считает себя веб-дизайнером. «Умеешь рисовать в фотошопе скруглённые уголки, градиенты и отражения? Отлично, значит, и разработать интерфейс огромного информационного портала тоже сможешь» — такое чувство, что так рассуждает большинство нанимателей этих самых дизайнеров. И очень жаль. Поскольку для комфортной работы с дизайнером он должен иметь представление об информационной архитектуре, юзабилити, веб-технологиях — программировании и вёрстке, чтобы не рисовать заведомо то, что невозможно или очень сложно реализовать. Однако на деле получается, что скруглённые уголки круче всех.</p>
<p>Поскольку у меня по ходу работы всё время возникает масса проблем с дизайнерами, я решила написать некоторые рекомендации, способные серьёзно упростить работу верстальщику. Эта статья адресована мудрым начальникам, которые хотят улучшить работу в своей команде, а также дизайнерам, которые не хотят заработать диарею в результате постоянных проклятий.</p>
<h2>Правило № 1. Макет сайта — в PSD</h2>
<p>Некоторые дизайнеры почему-то очень любят использовать для создания макетов Adobe Illustrator. Это очень крутая программа, но предназначение у неё совершенно иное, для верстальщика ее инструменты неудобны. В результате всё равно приходится экспортировать макет в Photoshop, что частенько сопровождается массой ошибок. Ещё одна модная нынче фишка — Fireworks. Да, это уже гораздо лучше, чем Иллюстратор, но всё равно плохо, потому что традиционно верстальщики учатся работать с Photoshop, и когда дизайнер присылает им файл в формате PNG, они просто-таки не знают, что с ним делать. Про макеты в JPG и прочих подобных форматах я вообще молчу. Да, такое тоже бывает! Фактически, формат PSD уже стал неким негласным стандартом для макетов сайтов. Так что если вы любите рисовать макеты в нестандартной программе — не поленитесь, сделайте экспорт в PSD и проверьте, чтобы ничего не сломалось. Верстальщик будет вам благодарен.</p>
<h2>Правило № 2. Аккуратные слои</h2>
<img src="https://web-standards.ru/articles/coder-vs-designer/images/layers.jpg" alt="Слои в Фотошопе.">
<p>В Photoshop существует очень удобный механизм группировки слоёв. Это очень полезное свойство: гораздо легче разобраться в макете, если слои сгруппированы по элементам (например, шапка-контент-подвал). Также следует уделить внимание осмысленному именованию слоёв и групп, причём, желательно использовать английский язык — верстальщик всё равно именует элементы по-английски. Слои, которые не используются в макете, необходимо удалять, а не оставлять отключёнными — верстальщик в процессе работы отключает разные слои, а потом включает всё сразу и может сильно испугаться. Иногда в одном макете помещают группы или слои для всех разновидностей данного типа страницы: например, форма регистрации и страница о том, что регистрация прошла успешно. В таком случае, отключённую группу или слой нужно помечать цветом, чтобы верстальщик её не пропустил.</p>
<h2>Правило № 3. Модульная сетка</h2>
<img src="https://web-standards.ru/articles/coder-vs-designer/images/guides.jpg" alt="Направляющие в Фотошопе.">
<p>Хороший дизайнер создаёт макет сайта по модульной сетке. Благодаря её присутствию в файле верстальщику гораздо проще высчитать размеры каких-либо элементов. Поэтому, призыв к дизайнерам: оставляйте в макете сетку, по которой работали. Ну, и призыв к тем, кто сетку не использует: немедленно изучите эту основу композиции, пока вас не засмеяли. Все вспомогательные, неиспользуемые линейки необходимо удалять.</p>
<h2>Правило № 4. «Липкие листочки»</h2>
<img src="https://web-standards.ru/articles/coder-vs-designer/images/stickers.jpg" alt="Липкие листочки в Фотошопе.">
<p>В фотошопе есть такой замечательный инструмент, как «липкие листочки» — специально для заметок на каком-либо элементе дизайна. Не ленитесь комментировать свой макет — как любят говорить на Вебмасконе, все телепаты ушли в отпуск: зачастую очень сложно догадаться, является ли какой-нибудь элемент активным, и какие в макетах существуют скрытые закономерности.</p>
<h2>Правило № 5. Помните о динамике</h2>
<p>В отличие от картины, сайт — это динамическая система, в которой есть активные элементы, в которой постоянно что-то происходит и изменяется. Всю динамику поведения активных элементов должен придумать дизайнер, создав дополнительные слои: например, ссылка, ссылка при наведении, посещённая ссылка. Очень часто дизайнеры почему-то не придают значения такому важному аспекту веб-дизайна. Также необходимо описывать, что должно происходить при изменении размеров экрана или при переполнении каких-либо элементов.</p>
<h2>Правило № 6. «Рыба» и типографика</h2>
<img src="https://web-standards.ru/articles/coder-vs-designer/images/fish.jpg" alt="Рыба — произвольный текст, который вписывается в контентные блоки.">
<p>Совершенно естественно, что при разработке сайта почти никогда не используется тот контент, который там будет — зачастую, это контента ещё просто не существует. Поэтому дизайнеры, да и верстальщики используют «рыбу» — произвольный текст, который вписывается в контентные блоки. Но, к сожалению, делается это зачастую без проблеска мысли — например, на русскоязычном сайте везде используется популярная рыба латиницей  — «Lorem Ipsum…», которая не даёт представления, как будет выглядеть этот текст, набранный кириллическим шрифтом. Используйте подходящие к случаю рыбы!</p>
<p>Что касается типографики, то тут я убеждена: дизайнер просто обязан знать основы типографического искусства: осмысленно задавать интерлиньяж, размеры и семейства шрифтов, отступы и поля. А то как-то был у меня случай: прислали макет, в котором достаточно значимые подписи были исполнены шестым кеглем. А когда же я изменила размер шрифта на более читабельный, дизайнер капризно заныл, что у него на макете всё по-другому!</p>
<h2>Правило № 7. Шрифты</h2>
<p>О шрифтах нужно сказать отдельно. Почему-то каждый дизайнер, купивший на Горбушке диск с двумя тысячами бесплатных шрифтов, считает своим долгом тут же эти шрифты засунуть куда только можно. Я даже не знаю, может быть, это последствия непросвещённости? Что ж, просвещаю: на компьютере пользователя, который зайдёт на ваш сайт, в 99% случаев есть только системные шрифты, и в 99.9% — это стандартные шрифты Windows. Поэтому, как ни старайся, милый сердцу маковода Lucida Grande увидит, дай бог, 1% пользователей, а все остальные всё равно увидят Arial. Конечно, в небольших объёмах оригинальные шрифты более чем уместны: это касается логотипов, статичных заголовков и прочих подобных вещей. Но фигачить напропалую весь текст на сайте каким-нибудь Neo Sans Pro, а потом ругаться, что всё не так, как на макете — подвергать себя опасности быть укушенным взбесившимся верстальщиком.</p>
<p>Если вы всё же используете нестандартные шрифты, присылайте их верстальщику вместе с макетом, иначе он не сможет нормально измерить кегль и начертание. За растрированные же текстовые блоки вам вообще могут оторвать голову! Так что будьте осторожны и внимательны.</p>
<h2>Правило № 8. Техническое задание</h2>
<p>Перед тем как приступить к созданию макета, обязательно прочтите техническое задание: что будет на этом сайте, как он должен работать, какую именно интерактивность иметь… Если же в вашей компании экономят на техническом писателе, попытайтесь хотя бы в устной форме ознакомиться с требованиями к сайту. А то очень часто случается ситуация, когда дизайнер нарисовал красивый макет, верстальщик его заверстал, а потом оказывается, что, к примеру, настоящая флэшка, которая должна быть центральным элементом сайта, шире нарисованной на 10 пикселов. Сколько времени и сил впустую! А ещё кошмарнее, когда дизайнер не понимает, что возможно реализовать при помощи веб-технологий, а что нельзя. И в результате на свет появляются блоки с аудио-плеером, в которых по ходу прослушивания радиопередачи должен плавно пролистываться текст этой самой передачи! Нет, это, конечно, реализуемо, но такие задачи должны осмысленно закладываться в планируемое время разработки, чего обычно не происходит.</p>
<h2>Правило № 9. Цветовая гамма</h2>
<p>Милый дизайнер! Я очень рада, что на твоём столе стоит тридцатидюймовый дорогущий монитор с отличной матрицей. Но ты должен понять, что ты пока — в меньшинстве, а большинство пользователей сайта, который ты рисуешь, пользуются обычным ноутбуком или монитором с разрешением 1280 на сколько-нибудь там и не супер-качественной матрицей. Поэтому не нужно рисовать макет в 3000 пикселов шириной. Всё равно обычный пользователь не оценит всей красоты огромной подложки, но может очень рассердиться, когда поймет, что ему пришлось загрузить лишние пол-мегабайта картинки. И воздержись, пожалуйста, от использования очень светлых тонов вроде <code>#F1F1F1</code>. Доказано, что подобные оттенки не воспринимаются на сознательном уровне, однако, создают «шум» в глазах пользователя, который мешает ему сосредоточиться на полезных частях сайта. И ещё: постарайся не пользоваться цветовыми профилями. Обычно это заканчивается тем, что после вёрстки кто-нибудь обязательно начнёт вопить, что в макете-то цвета другие! Побереги нервы: свои и верстальщика.</p>
<h2>Правило № 10. Системные контролы</h2>
<p>Это немного спорная тема, но я всё же её коснусь. Ни для кого не секрет, что дизайнеры очень любят рисовать кастомные элементы форм. В чём-то они правы: в Windows, особенно в стандартной теме, далеко не самые красивые контролы, и они действительно могут испортить внешний вид сайта. Однако, во-первых, все эти красивые скруглённые штучки с градиентами довольно слабо реализуемы на кроссбраузерном уровне, а во-вторых, среднестатистический пользователь потратит куда больше мозговых усилий на то, чтобы понять, куда ему ввести своё имя, если перед ним что-то очень красивое, но абсолютно непохожее на стандартные контролы его операционной системы, к которым он привык. А заставлять пользователей задумываться над интерфейсом — значит, потерять какую-то особо нетерпеливую часть аудитории.</p>
<h2>Правило № 11. Продумайте всё</h2>
<p>Когда я пришла на работу в компанию, которая занималась разработкой сайтов для T-Mobile, первое, что мне пришлось сделать — это прочитать Style Guide. Он представлял собой папку А4, толщиной примерно сантиметров в двадцать, в которой были описаны все цвета, шрифты, размеры и тому подобные вещи, которые можно применять в оформлении каких-либо материалов для T-Mobile. При помощи этого стайлгайда наши дизайнеры рисовали макеты, уделяя внимание всем деталям. Кажется, это были лучшие макеты, с которыми мне доводилось работать. Я веду свою мысль к следующему: не ленитесь отрисовывать все элементы, которые могут встретиться на странице. Все заголовки, абзацы, подписи к картинкам, цитаты, врезки и так далее и тому подобное. Потому что чаще всего, если верстальщик сам берётся додумать недостающие элементы, это заканчивается истерикой у дизайнера. Позаботьтесь обо всём сразу.</p>
<img src="https://web-standards.ru/articles/coder-vs-designer/images/details.jpg" alt="Типовые элементы на макете.">
<p>Резюмируя всё вышесказанное — дизайнер должен уметь не только обосновать, что эти три колонки цветов «вырви глаз» символизируют собой российский флаг, но и тщательно продумать, спроектировать свою работу, аккуратно и дотошно её выполнить, не забывая при том, что с макетом в дальнейшем будут иметь дело ещё несколько человек. Работайте профессионально. Это приятно и полезно.</p>
<p><a href="https://web-standards.ru/articles/coder-vs-designer/example.zip">Пример хорошо сделанного макета сайта</a> (PSD 3,1 МБ). Дизайнер — М. Нозик.</p>

                    ]]></description><pubDate>Tue, 22 Jun 2010 00:00:00 GMT</pubDate><guid>https://web-standards.ru/articles/coder-vs-designer/</guid></item></channel></rss>