<?xml version="1.0" encoding="utf-8" ?><rss version="2.0" xmlns:tt="http://teletype.in/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Александр Архипов</title><generator>teletype.in</generator><description><![CDATA[Пытаюсь общаться с компухтерами на их языках.]]></description><image><url>https://img2.teletype.in/files/d8/81/d8813538-e5c0-42b3-a6d2-de33ec4a8e0b.png</url><title>Александр Архипов</title><link>https://teletype.in/@helvetios</link></image><link>https://teletype.in/@helvetios?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=helvetios</link><atom:link rel="self" type="application/rss+xml" href="https://teletype.in/rss/helvetios?offset=0"></atom:link><atom:link rel="next" type="application/rss+xml" href="https://teletype.in/rss/helvetios?offset=10"></atom:link><atom:link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></atom:link><pubDate>Thu, 16 Apr 2026 07:35:35 GMT</pubDate><lastBuildDate>Thu, 16 Apr 2026 07:35:35 GMT</lastBuildDate><item><guid isPermaLink="true">https://teletype.in/@helvetios/parcel-pug-pictures</guid><link>https://teletype.in/@helvetios/parcel-pug-pictures?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=helvetios</link><comments>https://teletype.in/@helvetios/parcel-pug-pictures?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=helvetios#comments</comments><dc:creator>helvetios</dc:creator><title>Parcel &amp; Pug – особенности работы с изображениями </title><pubDate>Tue, 30 May 2023 03:18:54 GMT</pubDate><media:content medium="image" url="https://img1.teletype.in/files/02/14/02140dc7-9edf-4309-afe9-817ea0873114.png"></media:content><category>Веб-разработка</category><description><![CDATA[<img src="https://img1.teletype.in/files/45/f6/45f6314c-7596-4322-9ff6-007b34bc4d32.png"></img>Привет. Когда в обучении фронту я дошел до картиночек и вставки их на страницу я был слегка удивлён. Когда дошел до их адаптивности уже был не слегка.]]></description><content:encoded><![CDATA[
  <figure id="IagD" class="m_column">
    <img src="https://img1.teletype.in/files/45/f6/45f6314c-7596-4322-9ff6-007b34bc4d32.png" width="1200" />
  </figure>
  <p id="K36f">Привет. Когда в обучении фронту я дошел до картиночек и вставки их на страницу я был слегка удивлён. Когда дошел до их адаптивности уже был не слегка.</p>
  <p id="8uni">Казалось бы, тег <code>&lt;img&gt;</code> ему атрибутом <code>src=&#x27;котик.png&#x27;</code> да и всё, ан нет. Естественно, нужен как минимум <code>alt</code>, а то валидацию не пройдет и дядя SEO-шник даст пизды, потом еще обязательно <code>width</code> / <code>height</code>, а то контент-манагер еще вставит 10мегапикселов фото и всё поедет как твоя кукуха.</p>
  <p id="PYIx">И это только статика, через CSS &quot;адаптивно&quot; ты можешь только тянуть картинку передавая привет всем шакалам, койотам и всем остальным безумно можно быть первым. Можно все пофиксить <code>object-fit</code>, но тогда либо у тебя махонькая картинка на десктопе, либо FullHD на телефоне, что само по себе слегка похоже на выбор стула из... нутыпоэл.</p>
  <p id="Wxfc">Ах, да! Еще же эти ваши ретина-дисплеи и, конечно же, новые модные-молодёжные <s>реп-дискета-дискотека</s> лёгкие форматы WebP и AVIF.</p>
  <p id="X3sw">Итого у нас получается, что одна картинка - это не одна картинка, а в очень минимум варианте две-три, а по хорошему... хм... Восемнадцать! И все их надо верно разметить в <code>&lt;picture&gt;</code>.</p>
  <p id="iLz2">Готовить всё это ручками, конечно, дело не боярское.</p>
  <h2 id="qEQn">Автоматизируй</h2>
  <p id="RgBH">Естественно, мыжпрограммисты. На это есть всякие бандлеры, например - Gulp, Webpack или сегодняшний пациент - <a href="https://parceljs.org/" target="_blank">Parcel</a>. Их вообще дофига, но самые популярные для фронта, насколько я понимаю, эти трое.</p>
  <section style="background-color:hsl(hsl(0,   0%,  var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="dhvj">На самом деле все они, так или иначе, работают через один и тот же инструмент – плагин или модуль Node.js (я еще не до конца просёк терминологию) под названием <a href="https://sharp.pixelplumbing.com/" target="_blank">Sharp</a>. Работает просто - даёшь ему картинку, говоришь чего с ней сделать, отдаёт тебе картинку.</p>
  </section>
  <p id="P2lX">Parcel сам по себе мощная штука, хотя и специфическая, но об этом в другой раз. Самое главное - он умеет из коробки <a href="https://parceljs.org/recipes/image/" target="_blank">работать с Sharp</a>, а как следствие с картинками и более того, позволяет без всяких конфигов прямо в HTML и CSS указать что с картинкой делать. </p>
  <p id="DV6J">Пример из документации:</p>
  <pre id="nDtU" data-lang="html">&lt;picture&gt;
  &lt;source srcset=&quot;image.jpeg?as=avif&amp;width=800&quot; type=&quot;image/avif&quot; /&gt;
  &lt;source srcset=&quot;image.jpeg?as=webp&amp;width=800&quot; type=&quot;image/webp&quot; /&gt;
  &lt;source srcset=&quot;image.jpeg?width=800&quot; type=&quot;image/jpeg&quot; /&gt;
  &lt;img src=&quot;image.jpeg?width=200&quot; alt=&quot;test image&quot; /&gt;
&lt;/picture&gt;</pre>
  <p id="p3wG">Задача для Sharp формируется с помощью query-параметра после знака <code>?</code> в пути к картинке. Если параметра, читай задачи, два - их соединяем символом <code>&amp;</code>. В нашем случае у первого <code>&lt;source&gt;</code> мы &quot;попросили&quot; Parcel сконвертировать <code>image.jpeg</code> в формат AVIF - <code>as=avif</code>, и изменить размер на 800px по ширине - <code>width=800</code>. По умолчанию, высота ресайзится пропорционально.</p>
  <p id="dJvs">Кроме конвертации <code>as=</code> и размеров <code>width=</code>/<code>height=</code>, можно еще задать уровень качества, то бишь степень сжатия через <code>quality=</code>, но я никогда не трогал её, так как дефолтные <code>75</code> меня устраивают.</p>
  <p id="0gQW">То есть, по сути нам нужна только одна jpeg/png картинка-исходник, самая большая - всё остальное можно сделать через Parcel и прям в разметке указать чего куда подставить. Примерный пример - картинка-исходник 960px в ширину для ретина 2х, остальное делаем через параметры:</p>
  <pre id="hQnM" data-lang="html">&lt;picture&gt;
  &lt;!-- desktop: avif, webp, png с ретиной --&gt;
  &lt;source 
    type=&quot;image/avif&quot; media=&quot;(min-width: 1280px)&quot; 
    srcset=&quot;pic.png?as=avif&amp;width=480, pic.png?as=avif 2x&quot; /&gt;
  &lt;source 
    type=&quot;image/webp&quot; media=&quot;(min-width: 1280px)&quot; 
    srcset=&quot;pic.png?as=webp&amp;width=480, pic.png?as=webp 2x&quot; /&gt;
  &lt;source 
    type=&quot;image/png&quot; media=&quot;(min-width: 1280px)&quot; 
    srcset=&quot;pic.png?width=480, pic.png 2x&quot; /&gt;
    
  &lt;!-- tablet, ресайз условный --&gt;
  &lt;source 
    type=&quot;image/avif&quot; media=&quot;(min-width: 768px)&quot; 
    srcset=&quot;pic.png?as=avif&amp;width=320, pic.png?as=avif&amp;width=640 2x&quot; /&gt;
  &lt;source 
    type=&quot;image/webp&quot; media=&quot;(min-width: 768px)&quot; 
    srcset=&quot;pic.png?as=webp&amp;width=320, pic.png?as=webp&amp;width=640 2x&quot; /&gt;
  &lt;source 
    type=&quot;image/png&quot; media=&quot;(min-width: 768px)&quot; 
    srcset=&quot;pic.png?width=320, pic.png?width=640 2x&quot; /&gt;
    
  &lt;!-- mobile --&gt;
  &lt;source 
    type=&quot;image/avif&quot; 
    srcset=&quot;pic.png?as=avif&amp;width=260, pic.png?as=avif&amp;width=520 2x&quot; /&gt;
  &lt;source 
    type=&quot;image/webp&quot;
    srcset=&quot;pic.png?as=webp&amp;width=260, pic.png?as=webp&amp;width=520 2x&quot; /&gt;
  &lt;img
    alt=&quot;picture&quot; width=&quot;260&quot; height=&quot;180&quot;
    src=&quot;pic.png?width=260&quot; srcset=&quot;pic.png?width=520 2x&quot; /&gt;
&lt;/picture&gt;</pre>
  <p id="EAeu">В CSS, кстати, тоже работает, но функцию <code>image-set()</code> пока <a href="https://caniuse.com/css-image-set" target="_blank">хреновенько</a> поддерживают браузеры.</p>
  <section style="background-color:hsl(hsl(0,   0%,  var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="eO3d">В прошлой статье я частично использовал этот инструмент, но не до конца разобрался, плюс нюансы есть с Pug&#x27;ом, о чем дальше.</p>
  </section>
  <p id="bhBX">Штош. Миллион картинок нам теперь не нужны, но верстать всё еще надо весь десяток тегов для каждой контентной картинки. А что если это карточки в  каталоге или галерея? Прокачивай десятипалый набор или...</p>
  <h2 id="8duB">Шаблонизируй</h2>
  <p id="ogCf">Наверное, надо было наоборот, но на примере простого HTML проще понять как происходит конвертация в Parcel/Sharp.</p>
  <p id="Drf5">Я использую <a href="https://pugjs.org/" target="_blank">Pug.js</a> так как просто захотел выучить новенькое и он попался первым. О самом Pug&#x27;е я в <a href="https://blog.arkhelvetios.ru/pugjs-hi" target="_blank">прошлой статье</a> писал, для текущей задачи можно много чего придумать - я решил воспользоваться <code>mixin</code>&#x27;ом.</p>
  <p id="Zlph">Мы создаём шаблон какого-то куска разметки и передавая в него параметры расставляем их в теле шаблона:</p>
  <pre id="aT5H" data-lang="pug">mixin item(name, path, desc) 
  li 
    h3 name
    img(src=path, alt=name) 
    p desc

+item(&#x27;Mr. Puggy&#x27;, &#x27;./mr-puggy.png&#x27;, &#x27;Distinguished gentleman&#x27;)</pre>
  <p id="AZkH">Сами вызовы миксинов, читай создание элемента по шаблону, можно воткнуть, например, в местный цикл, а данные передавать из объекта:</p>
  <pre id="Mnwu" data-lang="pug">each item in gallery-items
  +item(item.name, item.path, item.desc)</pre>
  <p id="jL9L">Объект, кстати, можно подключить внешний, правда, немного через жопу, то есть через конфиг-файл Pug&#x27;а. У конфига <code>.pugrc</code> есть зарезервированный объект <code>locals</code> в который можно пихать свои данные, а в <code>.pug</code> файлах обращаться к самому вложенному объекту, так как <code>locals</code> работает для всей страницы.</p>
  <pre id="ZbjQ" data-lang="javascript">// пример .pugrc (или pug.config.js)
{
  &quot;locals&quot;: {
    &quot;gallery-items&quot;: [
      {
        &quot;name&quot;: &quot;Mr. Puggy&quot;,
        &quot;path&quot;: &quot;./mr-puggy.png&quot;,
        &quot;desc&quot;: &quot;Distinguished gentleman&quot;
      },
      {
        &quot;name&quot;: &quot;Don Fluffy&quot;,
        &quot;path&quot;: &quot;./don-fluffy.png&quot;,
        &quot;desc&quot;: &quot;Mafioso&quot;
      }
    ]
  }
}</pre>
  <h2 id="JlBu">Ах, да! Особенности!</h2>
  <p id="RPNC">Остались нюансы. С первым я столкнулся в прошлый раз - Pug&#x27;овская <a href="https://pugjs.org/language/attributes.html#attribute-interpolation" target="_blank">интерполяция</a>, которая <code>#{foo}</code>, действительно не работает в атрибутах, о чем сказано в документации. Там же в документации, написаны варианты выхода из ситуации – конкатенация прям в атрибуте или использовать ES-ную интерполяцию с бэктиками - <code>&#x60;${foo}&#x60;</code>.</p>
  <p id="6RJQ">Я выбрал второй вариант и пока не пожалел - получилось лаконичнее и, на мой взгляд, даже читабельнее чем с ворохом конкатенаций.</p>
  <pre id="FhDl" data-lang="javascript">mixin item(name, path, desc)
  //- контент шаблона
  ...
  picture
    source( 
      srcset=&#x60;${path}?as=avif&amp;width=480, ${path}?as=avif 2x&#x60;, 
      media=&#x27;(min-width: 1280px)&#x27;, 
      type=&#x27;image/avif&#x27;
      )
    ...
    // остальные sources аналогично</pre>
  <p id="zci0">Второй нюанс это <a href="https://pugjs.org/language/attributes.html#unescaped-attributes" target="_blank">экранирование специальных символов</a> в атрибутах. В Pug символы типа <code>&lt;</code>, <code>&gt;</code> и нужный нам амперсанд <code>&amp;</code> по умолчанию экранируются и преобразуются в мнемоники типа <code>&amp;gt;</code>, <code>&amp;lt;</code> и так далее.</p>
  <p id="BFSV">Чтобы символы не экранировались достаточно просто поставить <code>!</code> перед равно при написании атрибута:</p>
  <pre id="ByDV" data-lang="javascript">source(
  srcset!=&#x60;${path}?as=avif&amp;width=480, ${path}?as=avif 2x&#x60;, 
  ...
)</pre>
  <p id="6ooB"> Да, даже подсветка говорит нам, мол это же &quot;НЕравно&quot;, но такой вот синтаксис.</p>
  <hr />
  <p id="yAO4">По итогу, пару дней покопавшись оказалось, что не так уж всё и страшно с этими картиночками. Вот боевой пример с одной странице, где плитка с портфолио:</p>
  <figure id="HUYO" class="m_original">
    <img src="https://img4.teletype.in/files/f2/6f/f26f752c-2ef1-4e41-bc42-882509c6a3ad.png" width="973" />
  </figure>
  <p id="LHFm">Массив <code>portfolio</code> я вынес в <code>locals</code>, получается там такая база данных на минималках. В конкретном примере 12 картинок, так как нет необходимости делать под таблет - по дизайну они просто смещаются сверху вбок от текста.</p>
  <p id="JBV3">Всё, спасибо за внимание!</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@helvetios/pugjs-hi</guid><link>https://teletype.in/@helvetios/pugjs-hi?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=helvetios</link><comments>https://teletype.in/@helvetios/pugjs-hi?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=helvetios#comments</comments><dc:creator>helvetios</dc:creator><title>Pug.js – мое знакомство с мопсовым препроцессором</title><pubDate>Thu, 18 May 2023 19:08:25 GMT</pubDate><media:content medium="image" url="https://img4.teletype.in/files/39/af/39af021d-a19d-43ea-afe4-c6a8292215c8.png"></media:content><category>Веб-разработка</category><description><![CDATA[<img src="https://img2.teletype.in/files/9b/81/9b81320c-c7bc-4787-9617-557f5fe5742a.png"></img>Привет. Недавно решил собрать себе архив из своих работ по вёб-дезигну, которые делал последние несколько лет, до фронт-энда. Оказалось их немало, большая часть из них, к сожалению, утрачены, но даже так получился список из двадцати с чем-то работ. А раз уж я теперь фронт-энд разработчик, то запилю это всё дело в виде странички.]]></description><content:encoded><![CDATA[
  <figure id="n88W" class="m_column">
    <img src="https://img2.teletype.in/files/9b/81/9b81320c-c7bc-4787-9617-557f5fe5742a.png" width="1200" />
  </figure>
  <p id="GdJ5">Привет. Недавно решил собрать себе архив из своих работ по вёб-дезигну, которые делал последние несколько лет, до фронт-энда. Оказалось их немало, большая часть из них, к сожалению, утрачены, но даже так получился список из двадцати с чем-то работ. А раз уж я теперь фронт-энд разработчик, то запилю это всё дело в виде странички.</p>
  <hr />
  <p id="9bvE">Так как это, своего рода, пет-проект, параллельно захотелось освоить что-то новенькое и как-то сам по себе выбрался препроцессор для вёрстки Pug. Если честно, я из-за названия и выбрал. 😃</p>
  <p id="ALUc">У Pug&#x27;а интересный python&#x27;оподбоный синтаксис, основанный на индентах, то бишь вложенность элементов определяется не закрывающими тегами, а отступом:</p>
  <pre id="RxBf" data-lang="pug">ul.list
  li.list__item Item A
    ul.list__sublist
      li.list__subitem#unique-id Subitem A1
      li.list__subitem Subitem A2
  li.list__item Item B
  li.list__item Item C</pre>
  <p id="Eyos">На выходе получится:</p>
  <pre id="5tNY" data-lang="html">&lt;ul class=&quot;list&quot;&gt;
  &lt;li class=&quot;list__item&quot;&gt;Item A
    &lt;ul class=&quot;list__sublist&quot;&gt;
      &lt;li class=&quot;list__subitem&quot; id=&quot;unique-id&quot;&gt;Subitem A1&lt;/li&gt;
      &lt;li class=&quot;list__subitem&quot;&gt;Subitem A2&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li class=&quot;list__item&quot;&gt;li Item B&lt;/li&gt;
  &lt;li class=&quot;list__item&quot;&gt;li Item C&lt;/li&gt;
&lt;/ul&gt;  </pre>
  <p id="7WYR">Класс можно задать через точку, ID через диез, прям как селектор в CSS, правда на ID-шник через диез ругается линтер:</p>
  <figure id="eZq8" class="m_original">
    <img src="https://img4.teletype.in/files/7a/a0/7aa01b1c-a899-4c4f-95bd-b45d0154ad7c.png" width="502" />
  </figure>
  <p id="aRBL">Лучше его задавать как обычные атрибуты, которые задаются в скобках через запятую, как своеобразные аргументы функции:</p>
  <pre id="CusZ" data-lang="pug">img(src=&#x27;./pic.png&#x27;, alt=&#x27;super pic&#x27;, width=&#x27;100&#x27;, height=&#x27;50&#x27;)</pre>
  <p id="gn5R">Но одним относительно упрощенным синтаксисом, конечно, Pug не ограничивается. Так как это препроцессор, в нём возможны все &quot;программистские&quot; штучки - условия, циклы, интерполяция и функции. Можно еще прямо в .pug файле писать JavaScript, ограничено, но можно.</p>
  <p id="lDSK">Правда, ни условиями ни циклами я так и не воспользовался, но воспользовался тремя инструментами, которые значительно сэкономили мне время.</p>
  <h2 id="xnCr">Mixins</h2>
  <p id="muWJ">Это такая функция-конструктор, мы задаём ей параметры в аргументах, внутри задаём шаблон и эти параметры расставляем где надо, затем вызываем:</p>
  <pre id="OU2w" data-lang="pug">mixin item(foo, baz, bar)
  li
    h3 foo
    img(src=baz, alt=foo)
    p bar
    
+item(&#x27;Mr. Puggy&#x27;, &#x27;./mr-puggy.png&#x27;, &#x27;Distinguished gentleman&#x27;)</pre>
  <p id="f7MA">Я использовал его для карточек в списке своих работ:</p>
  <figure id="TwMA" class="m_original">
    <img src="https://img2.teletype.in/files/99/79/997979f6-4af8-420b-8cba-c254378c000b.png" width="817" />
  </figure>
  <p id="spIe">У меня тут наворочено с датой и с ссылкой на изображение. Дату я задаю в специальном, <a href="https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-date-time-string-format" target="_blank">валидном</a> для атрибута <code>datetime</code> и JS формате, но для отображения на странице преобразую в строки с годом и месяцем на английском, а потом подставляю интерполяцией в контент.</p>
  <p id="V8Y7">С ссылкой на изображения посложнее так как одно изображение, с целью оптимизации загрузки, существует аж в шести вариантах – jpg и jpg@2x для ретины, webp и webp@2x, и еще avif и avif@2x. Я сделал через конкатенацию строк, так как через интерполяцию в <code>srcset</code>, вроде как, не получится, по крайней мере линтер на меня ругался и упаковщик послал меня на 404.</p>
  <h1 id="IakB">Includes</h1>
  <p id="w5t7">По своей сути это аналог <code>require()</code> из PHP - позволяет импортировать кусок внешнего кода в файл. И также позволяет использовать компонентный подход непосредственно в вёрстке.</p>
  <p id="7TWw">Например, у себя я использовал это в хэдере - это блок состоящий из двух блоков - лого и менюшка, которые я подключаю к нему через <code>include</code>:</p>
  <figure id="ODz7" class="m_original">
    <img src="https://img4.teletype.in/files/72/74/7274695c-9769-467f-9b0e-05a0d5fcae71.png" width="408" />
  </figure>
  <p id="4sij">При этом, все блоки лежат в своих директориях со своими стилями и скриптами:</p>
  <figure id="6JBj" class="m_original" data-caption-align="center">
    <img src="https://img3.teletype.in/files/e0/63/e0632c89-1feb-4793-b56f-49a926519313.png" width="267" />
    <figcaption>Конкретно тут скриптов нет :D</figcaption>
  </figure>
  <p id="7Qwx">Возможно, это немножечко перебор, но я решил попробовать и никто меня не остановит. Му-ха-хах!</p>
  <h1 id="Npct">Template Inheritance</h1>
  <p id="jkfw">Google переводит как &quot;наследование шаблонов&quot; - к файлу можно подключить структуру внешнего файла. То есть не вставить как кусок разметки через <code>include</code>, а прям импортировать всё и имея ввиду этот импорт уже работать.</p>
  <p id="iLgN">Такой шаблон должен иметь в себе блоки <code>block</code> - участки шаблона контент которых можно изменять уже в файле куда подключаем шаблон.</p>
  <pre id="DKVF" data-lang="pug">html
  head
    title Mr. Puggy Personal Page
    block style
      link(rel=&#x27;stylesheet&#x27;, href=&#x27;./style.css&#x27;)
  body
    ...</pre>
  <p id="czHe">Импортируем шаблон в рабочий файл через команду <code>extends</code>:</p>
  <pre id="MJnJ" data-lang="pug">extends template.pug</pre>
  <p id="zJHW">А с блоками можно работать тремя способами – можно перезаписать контент блока просто вызвав его:</p>
  <pre id="aH3P" data-lang="pug">block style
  link(rel=&#x27;stylesheet&#x27;, href=&#x27;./super-style.css&#x27;)</pre>
  <p id="QtKE">При этом запись из шаблона удалится, то есть стили <code>style.css</code> не загрузятся. Еще можно добавить записи к шаблону с помощью <code>append</code> - в конец и <code>prepend</code> - в начало блока:</p>
  <pre id="pZlo" data-lang="pug">prepend style
  link(rel=&#x27;stylesheet&#x27;, href=&#x27;./normalize.css&#x27;)
append style
  link(rel=&#x27;stylesheet&#x27;, href=&#x27;./super-style.css&#x27;)</pre>
  <p id="YIcX">Что на выходе, в HTML, даст нам все три тега в нужном нам порядке:</p>
  <pre id="yEbD" data-lang="html">&lt;head&gt;
  &lt;title&gt;Mr. Puggy Personal Page&lt;/title&gt;
  &lt;link rel=&quot;stylesheet&quot; href=&quot;normalize.css&quot;&gt;
  &lt;link rel=&quot;stylesheet&quot; href=&quot;style.css&quot;&gt;
  &lt;link rel=&quot;stylesheet&quot; href=&quot;super-style.css&quot;&gt;
&lt;/head&gt;</pre>
  <p id="lJlR">Самый простой пример, который я и использовал - я создал шаблон <code>_layout.pug</code> для типичной страницы, где есть обязательные теги типа <code>doctype</code>, <code>html</code>, <code>head</code> ну и <code>body</code>. Писать их сто раз на каждой странице дело не боярское, поэтому мы и выводим в шаблон повторяющиеся элементы, а те места которые будут потенциально изменяться оборачиваем в конструкцию <code>block</code>.</p>
  <p id="pWv9">Например, в <code>head</code> явно есть мета-информация которая нужна везде:</p>
  <pre id="8MUN" data-lang="pug">head
  meta(charset=&#x27;utf-8&#x27;) 
  meta(content=&#x27;width=device-width, initial-scale=1&#x27;, name=&#x27;viewport&#x27;) 
  meta(content=&#x27;IE=Edge&#x27;, http-equiv=&#x27;X-UA-Compatible&#x27;)</pre>
  <p id="mbgm">А теги типа <code>title</code>, какие-то дополнительные стили и скрипты, аля пиксели соц. сетей или метрик, не говоря уже о всяких SEO-тегах - нам нужно подставлять индивидуально для страницы.</p>
  <p id="uQCJ">Грубо говоря, я сделал нечто подобное:</p>
  <figure id="tYka" class="m_original" data-caption-align="center">
    <img src="https://img1.teletype.in/files/46/2f/462fbad0-2aae-44b3-901e-189c848b5345.png" width="598" />
  </figure>
  <p id="xJG9">Импортирую этот <code>_layout.pug</code> на страницу, а в <code>head</code> подставляю всё что нужно через <code>append</code>. Таким образом, у меня и мета и фавиконки и общие стили уже есть, остаётся добавить несколько строк.</p>
  <p id="hmhC">В <code>body</code> схема похожая, правда я до конца в ней не уверен:</p>
  <figure id="AR6x" class="m_original" data-caption-align="center">
    <img src="https://img3.teletype.in/files/aa/ca/aaca43ef-1799-4d78-8de3-91e653f69bb3.png" width="412" />
    <figcaption>Нужно еще по хорошему поменять .modal на dialog</figcaption>
  </figure>
  <p id="JqYW">Тут комбинация блоков и инклудов. Так как блоки можно переписать, можно в любой момент поменять любой блок с условного плейсхолдера на то что нужно.</p>
  <hr />
  <p id="Hylr">А, собственно все, спасибо за внимание. Как допилю всё - закину сюда ссылочку или может еще чего напишу. Там я еще со сборщиком Parcel&#x27;ем развлекаюсь, есть о чем по бухтеть. </p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@helvetios/gllacy-javascript-slider</guid><link>https://teletype.in/@helvetios/gllacy-javascript-slider?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=helvetios</link><comments>https://teletype.in/@helvetios/gllacy-javascript-slider?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=helvetios#comments</comments><dc:creator>helvetios</dc:creator><title>Бесконечный слайдер на чистом JavaScript — на примере макета Gllacy от HTML Academy</title><pubDate>Thu, 03 Nov 2022 14:44:33 GMT</pubDate><media:content medium="image" url="https://img4.teletype.in/files/78/ff/78ffb175-11e9-4050-a15d-e6917653f023.png"></media:content><category>Веб-разработка</category><description><![CDATA[<img src="https://img1.teletype.in/files/08/0a/080a5d26-8083-442e-bdb9-8134d8290159.gif"></img>Решил я, значит, доделать все проекты из первого уровня HTML Academy и начал с самого, на мой взгляд, сложного — Глейси. Но я был бы не я, если бы не усложнил себе задачу. Я решил, помимо прочего (попапы, тултипы, модалки), реализовать работу слайдера, а он там, мягко говоря, непростой.]]></description><content:encoded><![CDATA[
  <figure id="oVYd" class="m_original">
    <img src="https://img1.teletype.in/files/08/0a/080a5d26-8083-442e-bdb9-8134d8290159.gif" width="840" />
  </figure>
  <p id="VzpI">Решил я, значит, доделать все проекты из первого уровня HTML Academy и начал с самого, на мой взгляд, сложного — Глейси. Но я был бы не я, если бы не усложнил себе задачу. Я решил, помимо прочего (попапы, тултипы, модалки), реализовать работу слайдера, а он там, мягко говоря, непростой.</p>
  <p id="6069">Небольшая повесть о том как я с базовыми знаниями JS решил сделать бесконечный, зацикленный слайдер, с разными позициями элементов, меняющий стиль всего сайта в зависимости от выбранного элемента.</p>
  <section style="background-color:hsl(hsl(34,  84%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="hkjN"><strong>Дисклеймер</strong>: JS я делал как умею, а на данный момент я никак не умею. <code>:D</code> Различные продвинутые методы программирования, типа ООП, АОП и т. д., с применением классов, объектных методов и сложных абстракций — это мне еще предстоит изучить. Пока — просто эксперимент.</p>
    <p id="q7xf"><strong>TL;DR</strong> – <a href="https://github.com/ArkHelvetios/Gllacy/blob/main/js/slider.js" target="_blank">Полный код на GitHub</a></p>
  </section>
  <p id="9rVH"></p>
  <nav>
    <ul>
      <li class="m_level_1"><a href="#dY9E">Вёрстка слайдера</a></li>
      <li class="m_level_1"><a href="#Oaj3">Реализация слайдера на JavaScript </a></li>
      <li class="m_level_2"><a href="#TAXm">Концепт цикличного слайдера</a></li>
      <li class="m_level_2"><a href="#aVn4">Собираем элементы</a></li>
      <li class="m_level_2"><a href="#2Ga6">Клонируем слайды</a></li>
      <li class="m_level_2"><a href="#TC0b">Замена позиции</a></li>
      <li class="m_level_2"><a href="#Nc2x">Плавно переходим</a></li>
      <li class="m_level_1"><a href="#rRgo">Асинхронность JS, переходы и анимация</a></li>
      <li class="m_level_2"><a href="#vRCs">Анимация против inline-переходов</a></li>
      <li class="m_level_2"><a href="#ild7">Собираем всё для анимации</a></li>
      <li class="m_level_2"><a href="#dXVH">Получаем Сomputed значения свойств</a></li>
      <li class="m_level_2"><a href="#Dh5P">Отсеиваем лишние свойства</a></li>
      <li class="m_level_2"><a href="#lnEY">Финальная подстановка</a></li>
      <li class="m_level_1"><a href="#sph1">Остальные задачи</a></li>
      <li class="m_level_2"><a href="#kxXz">Пагинация</a></li>
      <li class="m_level_2"><a href="#BuSm">Смена стилей сайта</a></li>
      <li class="m_level_1"><a href="#VlXC">Ссылки и финал</a></li>
    </ul>
  </nav>
  <p id="r1z2"></p>
  <h2 id="dY9E">Вёрстка слайдера</h2>
  <p id="yAZI">Итак, слайдер это, по сути, весь первый блок. У каждого мороженного свой заголовок, описание и кнопка — это элементы слева. Ещё на макете видно два следующих, неактивных слайда, то бишь превью, у которых тоже есть свой текстовый блок. При этом, превью текстовых, естественно, не видно.</p>
  <p id="l9fu">Если заранее не подумать о последующей реализации, то в плане вёрстки, тут можно по разному подойти, например, сверстать всё одним списком, текст у превью скрывать <code>display: none</code>, а при слайде показывать. Это первое, что пришло мне в голову.</p>
  <p id="8RIE">И я так даже попробовал, но почти сразу начались огромные проблемы с позиционированием самой картинки, фона-кружочка — пришлось делать обёртку для картинки, отдельно обёртку для текста, проблемы с позиционированием триггеров (они же не должны «слайдиться»). </p>
  <p id="BeLD">До кучи проблема с переполнением — конструкция рушиться если добавить слайд, иначе нужно железно прописывать размер текущего слайда, а из-за этого начинает страдать отображение перехода — ширина скачет от флекс до фикс, некрасиво, а в превью творится вообще кошмар.</p>
  <figure id="XlvX" class="m_column" data-caption-align="center">
    <img src="https://img3.teletype.in/files/a2/45/a2450424-5f72-4710-909e-4d23e70d6263.png" width="1185" />
    <figcaption>Если не фиксировать размер текущего слайда, то flex будет всё сплющивать. </figcaption>
  </figure>
  <p id="lcW7">Я же решил сделать красиво и всё это дело анимировать, чтобы это нормально смотрелось, а не «прыгало» мгновениями. Ну и что бы это вообще работало.</p>
  <p id="Rdrh">Моя вторая идея — разделить блок на два структурно независимых блока — текстовый (слева) и с картинками (справа), задать <code>overflow: hidden</code> контейнерам и поместить внутрь по слайдеру.</p>
  <figure id="9pPK" class="m_column" data-caption-align="center">
    <img src="https://img4.teletype.in/files/37/b2/37b2f891-c08b-4468-856d-cdf972e0804c.png" width="1203" />
    <figcaption>Grid one love</figcaption>
  </figure>
  <p id="Ztvb">Это слегка увеличило вёрстку, но сильно упростило её логику — слайдеры занимают просто <code>100%</code> контейнера и живут в своём мирке, прекрасно убирая переполнение за <code>overflow</code>, а размеры областей заданы в <code>grid</code>.</p>
  <p id="FV0I">Правда, это слегка нарушило семантику — картинки теперь обособленны от текста, хоть и соотносятся визуально. По идее, наверное, их как-то можно грамотно семантически связать, но я пока не знаю как.</p>
  <p id="w7cI">Еще это потенциально усложнило JS — листать теперь надо два слайда, при том, что в одном у нас есть превью на 2 слайда вперёд, а в другом нет. </p>
  <p id="uQDo"></p>
  <h2 id="Oaj3">Реализация слайдера на JavaScript </h2>
  <p id="rZry">Кроме модалок при обучении на примере Техномарта и Кекстаграма в тренажёрах я, в общем-то, ничего не делал. В тренажёрах по JS разбирался базис языка и небольшой блок про работу с DOM. Короче говоря, это мой первый такой самостоятельный практический вызов.</p>
  <p id="hKDp">Поэтому мне было сложно в принципе понять с чего начать. Самая первая проблема которая бросилась мне в голову — превью у картиночного слайдера. Два проклятых итема торчат справа и намекают, что при слайде влево будет образовываться пустота, а при слайде вправо вообще не понятно чего делать.</p>
  <p id="Umft">Вариант отключить <code>disabled</code> кнопку «влево» при текущем первом слайде решал только пол беды. В итоге идея реализации зацикленного слайдера напросилась сама — не будем ничего дизеблить, кликать можно в любую сторону.</p>
  <p id="xnTT"></p>
  <h3 id="TAXm">Концепт цикличного слайдера</h3>
  <p id="U6Cg">Итак, как же сделать зацикленный слайдер? Пока без вопроса реализации самого смещения, только концепт «зацикленности». Первое, что пришло в голову мне — это каким-то образом перемещать слайды к краю массива. Смастерить, такой, как бы портал, которые перемещал бы итемы в зависимости от направления к концу или к началу списка.</p>
  <p id="wBGJ">Сразу две проблемы с которыми я столкнулся:</p>
  <ol id="lb3m">
    <li id="uAFi">Так как смещение не мгновенное, то одновременно на экране в моменте видно сразу два одинаковых итема — первый, ещё не исчезнувший <code>current</code> и второй появляющийся с конца.</li>
    <li id="8PNs">Метод <code>.querySelectorAll()</code> собирает их не в обычный объект (или словарь (?) объектов, тут пока для меня мутная терминология), а в так называемый NodeList, который можно только читать <code>read-only</code> и нельзя изменять. Об этом я узнал уже в процессе и об этом чуть далее.</li>
  </ol>
  <p id="zoup">В поисках я находил реализацию такой swap-техники слайдера, но там какие-то либо супер сложные схемы с пересозданием элементов, либо jQuery, который по сути, вроде как, делал тоже самое, но на своем языке.</p>
  <p id="Slk1">Параллельно я нашел другой подход, который как раз мне понравился — через работу с самим NodeList, <strong>клонирование итемов</strong> через <code>.cloneNode()</code> и подстановка их в конец с помощью <code>.appendChild()</code>.</p>
  <p id="6XIW">Это и решает проблемы выше и даже, насколько я понял, работает в рамках прогрессивного улучшения, так как дополнительные итемы появляются только с подключением JS к файлу.</p>
  <figure id="HRJb" class="m_original">
    <img src="https://img1.teletype.in/files/02/c0/02c0a670-bace-4112-a7e8-782a30094603.png" width="427" />
  </figure>
  <p id="3jYt">Примерный алгоритм работы слайдера по краям:</p>
  <ol id="OjZU">
    <li id="9gsD">Текущий <code>current</code> слайд находится на краю массива, не важно в начале или в конце.</li>
    <li id="gySz">Нажатие на кнопку в сторону «за» конец массива — на 1-м <code>&lt;- влево</code> или на 4-м (1-м клоне) <code>вправо -&gt;</code>, вызывает функцию замены позиции с 1-го на позицию клона или наоборот.</li>
    <li id="lvM5">После замены срабатывает основная функция слайдера с анимацией смещения.</li>
  </ol>
  <p id="GLxH">Ну что, погнали.</p>
  <p id="8iWX"></p>
  <h3 id="aVn4">Собираем элементы</h3>
  <p id="Ca5K">Для начала нам, естественно, нужно собрать элементы с которыми будем работать. Тут должно быть всё просто — используем <code>.querySelector()</code> и <code>.querySelectorAll()</code> для списков и работаем дальше. </p>
  <pre id="SqtQ" data-lang="javascript">const promoSection = document.querySelector(&quot;.promo&quot;);
const picSliderList = promoSection.querySelector(&quot;.slider__list&quot;);
let picSlides = picSliderList.querySelectorAll(&quot;.slider__item&quot;);
...</pre>
  <p id="GFKC">Но не тут-то было.</p>
  <p id="WUeh">В ходе работы над задачей я, возможно преждевременно, но познакомился с понятиями <a href="https://developer.mozilla.org/ru/docs/Web/API/Node" target="_blank">Node</a> и <a href="https://developer.mozilla.org/en-US/docs/Web/API/NodeList" target="_blank">NodeList</a> и тем, что они бывают двух типов — живые (live) и статичные (static). Это заставило на пол пути почти полностью переписать некоторые функции.</p>
  <p id="fJYe">Как я уже писал выше, метод <code>.querySelectorAll()</code> собирает элементы не в массив или обычный объект, а в специальный объект NodeList, который имеет свойство <code>read-only</code>, то есть <strong>неизменяемый</strong> или «статичный». </p>
  <pre id="TaIO" data-lang="javascript">console.log(picSlides) // выведет список из 3-х объектов
picSliderList.appendChild(&quot;клон/объект&quot;); // добавляем 4-й объект
console.log(picSlides) // всё равно выдаст список из 3-х объектов</pre>
  <p id="MvfB">Для нас это не очень хорошо — мы планируем изменять количество элементов в контейнере и работать далее уже с <strong>изменённым</strong> списком. Нам нужен «живой» NodeList или его подобие, который будет реагировать на изменения в DOM.</p>
  <p id="H91c">В этом нам <em>может</em> помочь свойство объекта Node — <code>.childNodes</code>, которое <strong>возвращает все дочерние Node</strong>, то бишь NodeList и он при этом «живой», то есть реагирует на изменения.</p>
  <pre id="g7lb" data-lang="javascript">const picSliderList = promoSection.querySelector(&quot;.slider__list&quot;);
let picSlides = picSliderList.childNodes;</pre>
  <p id="VDC1">Мы, правда, быстро споткнёмся о нюанс — <code>.childNodes</code> собирает еще и какую-то шелуху, которые не являются элементами, но являются Node’ами.</p>
  <figure id="Pb8c" class="m_original">
    <img src="https://img1.teletype.in/files/00/03/000399a7-8763-471e-9737-af6b11447583.png" width="508" />
  </figure>
  <p id="GpPa">Насколько я понял — это пробелы от переноса строки, с ними можно «хорошо» познакомиться, если начать работать с <a href="https://blog.arkhelvetios.ru/block-model-flow-and-float#dxgN" target="_blank">inline-block кнопками</a>, например. Или нет и это что-то другое, я тут не эксперт, не слушайте меня. В любом случае — нам оно не надо, нам надо только элементы.</p>
  <p id="mjyf">Тут нас спасает другое свойство, про которое, кстати, упоминалось в JavaScript тренажёрах, но я вот не помню, было ли там про живые/статичные коллекции. Это свойство <code>.children</code>, которое возвращает <strong>HTMLCollection </strong>— живую коллекцию всех дочерних элементов указанного контейнера.</p>
  <p id="ze7p">Насколько я понял — это немного другой вид данных отличный от NodeList, более «массивоподобный» (array-like object написано в MDN), который для наших целей идеально подходит. В итоге вот что получается:</p>
  <pre id="4SEA" data-lang="javascript">let picSlides = picSliderList.children;

console.log(picSlides) // 3 объекта
picSliderList.appendChild(&quot;клон/объект&quot;);
console.log(picSlides) // 4 объекта</pre>
  <p id="uHdD">Хорошо! Не забываем собрать еще кнопки и пагинацию (кнопки-точки в углу) и переходим к функциональной части.</p>
  <p id="aFNI"></p>
  <h3 id="2Ga6">Клонируем слайды</h3>
  <p id="STv7">Не смотря на то, что у текстового слайдера нет превью-итемов, они все за <code>overflow</code>, ему всё равно надо сделать клона крайнего итема. Иначе при слайде вбок, будет моментальная замена, например, с 3-го на 1-й (или наоборот), а затем слайд-анимация ко 2-му, из-за чего всё ломается.</p>
  <p id="YaiZ">Итак, сама функция клонирования:</p>
  <pre id="VTTS" data-lang="javascript">const addFirstToEnd = (itemsList) =&gt; {
  let cloneItem = itemsList[0].cloneNode(true); //клон 1-го итема
  cloneItem.classList.remove(&quot;current&quot;); //убираем сласс current
  itemsList[0].parentNode.appendChild(cloneItem); //подставляем
}</pre>
  <section style="background-color:hsl(hsl(34,  84%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="kmW0">Параметр true/false у <code>.cloneNode</code> определяет копировать ли элемент вместе с внутренностями или только пустой элемент. А свойство <code>.parentNode</code> обращается к родительскому node указанного элемента.</p>
  </section>
  <p id="EYXm">Тут очередная проблема, которую я не сразу заметил — для текстового слайдера нам хватит и одного клона, а вот для картинок получается, что нет. Одновременно в поле видимости слайдера с картинками у нас 3 слайда, просто 2 как превью. </p>
  <p id="0NBC">Следовательно, для корректной работы нам надо клонировать все видимые слайды, то бишь вообще все. Два последних итема никогда не будут выбраны, но нужны только для отображения при <code>current</code> на 1-м клоне.</p>
  <p id="v45u">Возникает еще одна дилемма — требуется разное количество клонов. Можно забить и сделать одинаковое — текстовых тоже будет 6, просто 2 последних будут совсем мёртвым грузом. Но я решил доработать функцию, что б она могла делать разное количество клонов:</p>
  <pre id="miFO" data-lang="javascript">const addClonesToEnd = (clonesNumber, itemsList) =&gt; {
  for (i = 0; i &lt; clonesNumber; i++) {
    let cloneItem = itemsList[i].cloneNode(true);
    cloneItem.classList.remove(&quot;current&quot;);
    itemsList[i].parentNode.appendChild(cloneItem);
  }
}
addClonesToEnd(3, picSlides); // +2 превью
addClonesToEnd(1, textSlides);</pre>
  <p id="OvCH"></p>
  <h3 id="TC0b">Замена позиции</h3>
  <p id="VYld">Это, наверное, не самое очевидное к чему следует приступать на данном этапе, но мне хотелось сначала понять, смогу ли я вообще реализовать такой слайдер, а уже потом что-то там двигать и анимировать.</p>
  <p id="ou4i">Для проверки будем двигать не слайды, а вспомогательный класс <code>.current</code>, обозначающий выбранный слайд. Благо менять классы у элементов мы уже умеем с помощью методов <code>.classList.add/remove</code>. Сейчас вопрос не «Как», а «Куда двигать?».</p>
  <p id="7sMh">Я решил подойди с точки зрения привязки элементов к их <strong>индексу</strong> в коллекции. Несмотря на то, что у нас разная длина коллекций — 4 у текстового и 6 у картиночного, два последних клона в картиночном нас не интересуют, поэтому позиции во всех случаях у обоих слайдеров будут совпадать.</p>
  <p id="oIOt">Получается, что у нас есть <strong>начальный</strong>, который совпадает с текущим, <strong>конечный</strong> индексы итемов и нам нужен еще <strong>следующий</strong> индекс, который будет рассчитываться нажатием кнопки влево/вправо:</p>
  <pre id="qHNU" data-lang="javascript">let lastSlideIndex = textSlides.length - 1; // 0 - 1 - 2 - [3]
let currentSlideIndex = 0;
let nextSlideIndex = 1;</pre>
  <p id="XMnS">Переходим к самому нажатию на кнопки. Функция нажатия должна высчитывать индекс следующего итема <code>nextSlideIndex</code>, двигать слайд в направлении от текущего к следующему и после этого следующий становиться текущим. </p>
  <p id="kjqD">При этом кнопки у нас две — одна двигает вправо, то бишь <strong>увеличивает</strong> индекс, вторая же влево, то бишь индекс <strong>уменьшает</strong>. Языком JS:</p>
  <pre id="RFnh" data-lang="javascript">buttonNext.addEventListener(&quot;click&quot;, () =&gt; {
  nextSlideIndex = currentSlideIndex + 1; // -1 для buttonPrev
  
  picSlides[currentSlideIndex].classList.remove(&quot;current&quot;);
  textSlides[currentSlideIndex].classList.remove(&quot;current&quot;);
  picSlides[nextSlideIndex].classList.add(&quot;current&quot;);
  textSlides[nextSlideIndex].classList.add(&quot;current&quot;);
  
  currentSlideIndex = nextSlideIndex; // заменяем значение текущего
}</pre>
  <p id="8ZzB">Визуально ничего не двигается (меняются размеры картинок только), зато двигается класс:</p>
  <figure id="3lEs" class="m_original">
    <img src="https://img1.teletype.in/files/0e/c0/0ec025b1-d679-454b-9799-327748dd101a.gif" width="440" />
  </figure>
  <p id="QNER">Быстро упираемся в проблему, когда нажимаем на кнопку влево и получаем <code>nextSlideIndex = -1</code>, такого индекса нет, как и индекса 4, который мы поймаем после нажатия вправо на последнем слайде. </p>
  <p id="Xzqh">В этот момент наступает время <strong>замены слайдов</strong>! Я решил зайти через простую проверку условия — если <code>nextSlideIndex</code> вне диапазона коллекции, то меняем слайды местами. А так как у нас первый и последний слайд-клон одинаковые, замена должна происходить без видимых последствий.</p>
  <p id="wC6i">Для краткости и чтобы много раз не переписывать эти четыре строки с <code>classList.remove/add</code> я вывел их в отдельную функцию <code>switchClasses()</code>:</p>
  <pre id="Bv69" data-lang="javascript">buttonNext.addEventListener(&quot;click&quot;, () =&gt; {
  nextSlideIndex = currentSlideIndex + 1; // = условно [4]
  
  if (nextSlideIndex &gt; lastSlideIndex) { // для buttonPrev (next &lt; 0)
    nextSlideIndex = 0;
    switchClasses();
    currentSlideIndex = nextSlideIndex; // заменяем значение текущего
    nextSlideIndex = currentSlideIndex + 1; // заново считаем next
  }
  
  switchClasses();
  
  currentSlideIndex = nextSlideIndex; // финально заменяем значение
}</pre>
  <p id="b4j3">Внезапно для меня всё сработало! Выглядит, возможно, немного неказисто, зато работает — пока не трогаем. Переходим к переходам!</p>
  <p id="QBx5"></p>
  <h3 id="Nc2x">Плавно переходим</h3>
  <p id="xwQa">Итак, теперь нам нужно натянуть на наш функциональный скелет движение не только класса, но и слайда целиком и еще сделать плавные переходы состояний у картинок — от маленьких полупрозрачных до больших непрозрачных.</p>
  <p id="9zuB">Самый, эм… распространённый вариант из моих поисков на чистом JavaScript (без jQuery) — это использование свойства <code>.style</code> к элементу. Этим способом мы прописываем элементу inline-стили в атрибут. </p>
  <pre id="3Nsb" data-lang="javascript">picSlider.style.transition = &quot;transform 500ms ease-out&quot;;
picSlider.style.transform = &quot;translateX(-100px)&quot;; // подвинет влево</pre>
  <p id="CH6q">Код выше применит в HTML к указанному элементу атрибут <code>style</code>:</p>
  <figure id="NSKS" class="m_original">
    <img src="https://img1.teletype.in/files/0d/33/0d333e4b-3d29-4b5c-a7fe-ea293f963b67.png" width="324" />
  </figure>
  <p id="QzjO">И это, в целом, то что нам нужно — подставим изменение стилей в функцию нажатия кнопки, в <code>translateX()</code> подставим нужную величину сдвига для слайдера, а картинкам можно в CSS прописать <code>transition</code> на размеры и непрозрачность и они сами будут плавно переходить от <code>.current</code> и обратно.</p>
  <p id="jIiU">Величину сдвига можно рассчитывать от индекса и размера итема, подставив через интерполяцию в значение свойства. Таким образом, у каждой позиции будет свой сдвиг. Условный пример:</p>
  <pre id="7DPF" data-lang="javascript">picSlider.style.transform: &#x60;translateX(-${nextSlideIndex * offset}px)&#x60;</pre>
  <p id="ikuz">То есть, условно, [0]-й слайдер — сдвиг на 0 * 150 = 0px, [1]-й — сдвиг на 150px и так далее. На данном этапе я подсмотрел размер итемов в DevTools во вкладке Computed или можно самому посчитать <code>width + padding + border + margin</code>, но об этом подробнее чуть позже.</p>
  <p id="etzK">Получается, по итогу, что нам надо-то добавить несколько строк:</p>
  <pre id="6TJX" data-lang="javascript">buttonNext.addEventListener(&quot;click&quot;, () =&gt; {
  nextSlideIndex = currentSlideIndex + 1;
  
  if (nextSlideIndex &gt; lastSlideIndex) { /* замена, тоси-боси */ }
  
  picSlider.style.transition = &quot;transform 500ms ease-out&quot;;
  textSlider.style.transition = &quot;transform 500ms ease-out&quot;;
  picSlider.style.transform = &#x60;translateX(-${nextSlideIndex * 141}px)&#x60;;
  textSlider.style.transform = &#x60;translateX(-${nextSlideIndex * 540}px)&#x60;;
  switchClasses();
  
  currentSlideIndex = nextSlideIndex;
}</pre>
  <p id="DS6N">Вуаля! Всё работает! Нет, правда! Слайды плавно двигаются, листаются. В конце коллекции происходит замена, как мы и хотели, и… идёт обратно?..</p>
  <figure id="NnWz" class="m_column">
    <img src="https://img4.teletype.in/files/b7/54/b7549676-8cf2-47ab-aacd-6ec5cbce72d5.gif" width="700" />
  </figure>
  <p id="7wwK">В принципе, можно оставить и так. Работает же? Работает. Даже, можно сказать, красиво — анимации итемов, плавно в обе стороны, все ок. Но я всё же решил доделать до изначально задуманного концепта.</p>
  <p id="YQYK"></p>
  <h2 id="rRgo">Асинхронность JS, переходы и анимация</h2>
  <p id="8j0t">Первое что мне пришло в голову — надо обнулить <code>transition</code> при замене в <code>if</code>, добавить туда <code>transform</code> к нулю и тогда, по идее, будет сначала производиться мгновенная замена с конца на начало, а пото-о-ом уже плавный переход на итоговый слайд! Но нет.</p>
  <p id="sTAl">Во-первых, <code>transition</code> у самих картинок, то бишь изменение размеров и непрозрачности, мы так просто не уберём, нужно обращаться к конкретному элементу — к картинке и еще <code>span</code>&#x27;у-кружочку на фоне.</p>
  <p id="2lyX">Во-вторых, как оказалось, JavaScript хитрый язык на многих уровнях. Он вроде как однопоточный, то есть все инструкции выполняются по порядку, но при этом <strong>асинхронный</strong>, то есть не совсем по порядку, могут и параллельно. </p>
  <p id="rZX0">Что и случилось, в общем-то, с нашей заменой слайдов — JS выполнил всё, грубо говоря, одновременно. Он посчитал индексы, финальным оказался индекс [1] — к нему он и применяет <code>translateX()</code>, из-за чего слайдер не «зациклился», а переместился в обратном направлении.</p>
  <section style="background-color:hsl(hsl(34,  84%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="ow6j">Тут я полез разбираться и залез в очень глубокие дебри, в которые, на мой взгляд, пока рановато. Либо учить всё прям до момента понимания, на что я не был готов на данный момент (сидеть месяц над слайдером такое себе), либо искать какое-то другое решение, пусть и на 3-х костылях.</p>
    <p id="CEyP">Я честно пытался разобраться для более изящного решения, начал вникать в асинхронность JS, в работу «стека вызовов», в контекст вызова и колбэки, в итоге в промисы и async/await. Но у меня не получилось, как бы я не пытался, я где-то сутки-двое сидел. На этом этапе просто не хватило знаний и опыта. Но я решил сделать по-своему! <code>:D</code></p>
  </section>
  <p id="HlPX">Итак, не победив асинхронность я решил встать на её сторону — пусть выполняется одновременно, тогда будем мучить сами переходы.</p>
  <p id="raoI">Я нашел несколько примеров подобных слайдеров, большинство из которых были, сука, на jQuery, но именно это мне и помогло. Я решил прям взять и скопировать один из вариантов (правда с 2-мя клонами) в CodePen и проинспектировать в DevTools как они себя ведут.</p>
  <p id="fKQw">Внезапно для себя я кое-что обнаружил! Вот как ведёт себя переход слайдера у меня, внимание на <code>translateX()</code>:</p>
  <figure id="LqHF" class="m_column">
    <img src="https://img4.teletype.in/files/3f/c2/3fc21b1a-dacd-45c2-a80f-8de29d6aced3.gif" width="644" />
  </figure>
  <p id="dcvm">А вот как ведёт себя переход в примере на jQuery, который я нашел на <a href="https://stackoverflow.com/questions/15876754/infinity-loop-slider-concepts" target="_blank">stackoverflow</a>:</p>
  <figure id="64t1" class="m_original">
    <img src="https://img3.teletype.in/files/6f/ed/6fedca29-4d60-4915-a7f2-8f32e06b7260.gif" width="392" />
  </figure>
  <p id="5x4D">Мой переход применяет новое значение мгновенно, в то время как переход в примере применяет какое-то колдовство. В этот момент я полез изучать как работают анимации в JavaScript.</p>
  <p id="THUK"></p>
  <h3 id="vRCs">Анимация против inline-переходов</h3>
  <p id="KEYq">Оказалось, что используемый в примере на jQuery метод (?) <code>:animate()</code> это не то же самое, что навешать <code>.style.tranform</code> на элемент. Это именно функция. Значит и в нативном JS должно быть что-то подобное и оно действительно есть!</p>
  <p id="V8Vj">И тут два пути. Более сложный вариант через создание функции анимации, буквально с нуля, используя <code>requestAnimationFrame()</code>, которая принимает колбэк функцию с расчётом Кривой Безье для timing-функции (которые ease/-in/-out), функцией отрисовки и условием продолжительности. Или…</p>
  <p id="Ej1x">Или использовать более простой <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/animate" target="_blank">метод</a> <code>.animate()</code> DOM элемента, который принимает массив с кейфреймами, работающие аналогично как в CSS, и параметры анимации, которые тоже совпадают с хорошо знакомыми свойствами CSS <code>animation-duration</code>, <code>animation-timing</code> и т. д.</p>
  <p id="k7CN">Я выбрал второй вариант и мне не стыдно. Выглядит все вот так:</p>
  <pre id="SVkS" data-lang="javascript">picSlider.animate([
  {transform: &#x60;translateX(-${currentSlideIndex * 141}px)&#x60;}, // от или 0%
  {transform: &#x60;translateX(-${nextSlideIndex * 141}px)&#x60;} // до или 100%
  ], {
  duration: 500,
  easing: &quot;ease-out&quot;,
  fill: &quot;forwards&quot;
});</pre>
  <p id="gUuD">Вся эта запись эквивалентна созданию <code>@keyframes</code> в CSS и применению её к элементу по классу, но в JS мы можем запрограммировать её вызов ну и подставить интерполяцией значения, что удобно.</p>
  <p id="mjNp">Но самое главное — эта анимация работает, как бы, «покадрово», так же как анимация из примера — применяя в короткие промежутки времени разные значения из указанного интервала. Я чёрт знает как это на нормальном языке объяснить, надеюсь вы поняли. <code>:D</code></p>
  <p id="qDUo">А следствие всего этого то, что таким образом анимация обрабатывается <strong>последовательно</strong>! Теперь если мы запихнём <code>.animate()</code> c нулевым <code>duration</code> в условие для замены, то он сначала заменит, а потом проиграет следующий <code>.animate()</code> с плавным переходом!</p>
  <pre id="akrm" data-lang="javascript">buttonNext.addEventListener(&quot;click&quot;, () =&gt; {
  nextSlideIndex = currentSlideIndex + 1;
  
  if (nextSlideIndex &gt; lastSlideIndex) {
    nextSlideIndex = 0;
  
    switchClasses();
  
    picSlider.animate([
      {transform: &#x60;translateX(-${currentSlideIndex * 141}px)&#x60;},
      {transform: &#x60;translateX(-${nextSlideIndex * 141}px)&#x60;}
      ], { duration: 0 }
    );
    /* ... textSlider аналогично */

    currentSlideIndex = nextSlideIndex;
    nextSlideIndex = currentSlideIndex + 1;
  }
  
  switchClasses();

  picSlider.animate([
    {transform: &#x60;translateX(-${currentSlideIndex * 141}px)&#x60;},
    {transform: &#x60;translateX(-${nextSlideIndex * 141}px)&#x60;}
    ], { duration: 500, easing: &quot;ease-out&quot;, fill: &quot;forwards&quot;}
  );  
  /* ... textSlider аналогично */
  
  currentSlideIndex = nextSlideIndex;
}</pre>
  <p id="JW7H">Смещение работает с правильной заменой, без асинхронной подставы! Но, чёрт возьми, у нас появился еще один неприятный нюанс. Смещение работает идеально, но анимация самих итемов при смене класса <code>.current</code> работает всё равно в асинхронном формате.</p>
  <figure id="MsFA" class="m_column">
    <img src="https://img1.teletype.in/files/00/27/00276fa7-73d9-4a3c-997e-a9b9b4ab21fe.gif" width="700" />
  </figure>
  <p id="z3Ds">Тут я совсем уже отчаялся, вернулся опять к теме async/await, потом попытался сделать через два клона — спереди и сзади коллекции, сделать замену через <code>transitionend</code>, но всё не получалось или приводило в итоге к тому же. </p>
  <p id="jzrK">На следующее утро решил вернуться к <code>.animate()</code> и пойти просто в лоб — будем анимировать всё что двигается! А что не двигается — двигать и анимировать. Соберём все анимируемые элементы и пропишем им анимацию отдельно.</p>
  <p id="NoR8"></p>
  <h3 id="ild7">Собираем всё для анимации</h3>
  <p id="aaWY">Для начала надо собрать в кучу всю информацию — какие элементы изменяются и какие параметры задействованы. Тут многое зависит от вёрстки и в общем от самой идеи анимации.</p>
  <p id="ascR">У меня каждый итем это <code>&lt;li&gt;</code>, внутри в текстовом, внезапно — текст и кнопка, а внутри картиночного — картинка <code>&lt;img&gt;</code> и <code>&lt;span&gt;</code>, имеющий роль кружочка. </p>
  <p id="QD9W">Я сначала сверстал его через <code>::before</code>, как и полагается декорации, но с псевдоэлементами туго работает JS, так как псевдоэлемент на то и <em>псевдо-</em> и является элементом стилей, а не вёрстки.</p>
  <pre id="2OT4" data-lang="html">&lt;!-- без div-обёрток --&gt;
&lt;ul class=&quot;slider-text__list&quot;&gt; &lt;!-- слайды текста --&gt;
  &lt;li class=&quot;slider-text__item current&quot;&gt; ... &lt;/li&gt;
  &lt;li class=&quot;slider-text__item&quot;&gt; ... &lt;/li&gt;
  ...
&lt;/ul&gt;
...
&lt;ul class=&quot;slider__list&quot;&gt; &lt;!-- слайды картинок --&gt;
  &lt;li class=&quot;slider__item current&quot;&gt;
    &lt;img src=&quot;pic1.png&quot;&gt; &lt;!-- картинка --&gt;
    &lt;span&gt;&lt;/span&gt; &lt;!-- кружок на фоне --&gt;
  &lt;/li&gt;
  &lt;li class=&quot;slider__item&quot;&gt; ... &lt;/li&gt;
  ...
&lt;/ul&gt;</pre>
  <p id="wx3Z">Анимация у меня задумана следующая:</p>
  <ol id="MQEk">
    <li id="SQks">Текстовый <code>.slider-text__item</code> итем меняет размер <code>scale()</code> и непрозрачность <code>opacity</code>, имитируя поведение картиночного итема.</li>
    <li id="BI8s">Картиночный <code>.slider__item</code> меняет непрозрачность <code>opacity</code>, а так же по макету у них разные поля <code>padding</code> (для «вылета» кнопок на <code>.current</code>).</li>
    <li id="4x5s">Картинка <code>&lt;img&gt;</code> в итеме меняет размеры <code>width</code> и <code>height</code>.</li>
    <li id="j1sR">Фоновый кружок <code>&lt;span&gt;</code> меняет размеры <code>width</code> и <code>height</code>.</li>
  </ol>
  <p id="gw5B">Сразу, наверное, поясню — я пробовал сделать через <code>scale()</code> на картиночном итеме <code>.slider__item</code>, получалось хреново — размер уменьшается только визуально, при этом занятое место под элемент не уменьшается, нам же надо именно перерасчет ширины итема. </p>
  <p id="aQxU">При этом еще нужно заметить, что при активации у нас анимируется сразу несколько итемов — текущие с <code>.current</code> уменьшаются, а следующие увеличиваются. То бишь одновременно, помимо еще самого смещения слайдера, происходит 4 анимации с итемами.</p>
  <p id="P2mm">Окей, то есть нам надо сделать <code>.animate()</code> для всех элементов еще и в два направления — от маленького к большому и обратно. Получается, нам нужно каждый раз еще получать текущий и следующий элемент, точнее пачку элементов, чтобы применять анимации именно к ним. Кошмар. </p>
  <p id="jnrI">Но ладно. Сначала попробуем, а потом будем решать. Итого нам надо собрать:</p>
  <pre id="o2Ad" data-lang="javascript">/* элементы текущих слайдов */
let textItem = textSlides[currentSlideIndex]; // текстовый
let picItem = picSlides[currentSlideIndex]; // картиночный
let picItemImg = picItem.children[0]; // сама картинка &lt;img&gt;
let picItemSpan = picItem.children[1]; // &lt;span&gt; кружочек

/* элементы следующих слайдов */
let nextTextItem = textSlides[nextSlideIndex];
let nextPicItem = picSlides[nextSlideIndex];
let nextPicItemImg = nextPicItem.children[0];
let nextPicItemSpan = nextPicItem.children[1];</pre>
  <p id="5QCJ">Дальше, так как индексы постоянно меняются, нам нужно при каждом нажатии кнопки эти переменные переназначать, чтобы анимация корректно применялась к текущим позициям. Запихивать эти же строки по 2 раза в каждую кнопку как-то расточительно, поэтому я решил вывести это в отдельную функцию:</p>
  <pre id="HO30" data-lang="javascript">const getItemsByIndex = () =&gt; {
  textItem = textSlides[currentSlideIndex];
  picItem = picSlides[currentSlideIndex];
  picItemImg = picItem.children[0];
  picItemSpan = picItem.children[1];
  
  nextTextItem = textSlides[nextSlideIndex];
  nextPicItem = picSlides[nextSlideIndex];
  nextPicItemImg = nextPicItem.children[0];
  nextPicItemSpan = nextPicItem.children[1];
};</pre>
  <pre id="OpJ5" data-lang="javascript">buttonNext.addEventListener(&quot;click&quot;, () =&gt; {
  nextSlideIndex = currentSlideIndex + 1;
  
  if (nextSlideIndex &gt; lastSlideIndex) {
    nextSlideIndex = 0;
    
    getItemsByIndex(); // получаем элементы по текущим индексам
    switchClasses();
  
    picSlider.animate(/* 0ms анимация слайдера */);
    textSlider.animate(/* 0ms анимация слайдера */);
    /* ... анимируем замену всего остального */

    currentSlideIndex = nextSlideIndex;
    nextSlideIndex = currentSlideIndex + 1;
  }
  
  getItemsByIndex();
  switchClasses();

  picSlider.animate(/* анимация слайдера */);
  textSlider.animate(/* анимация слайдера */);
  /* ... анимируем плавно всё остальное */

  currentSlideIndex = nextSlideIndex;
}</pre>
  <p id="4DbE">Получается уже нечто монструозное, даже с комментариями. Итак, что дальше? Каждому элементу теперь нужно применить <code>.animate()</code> со своими параметрами. Параметры можно взять опять во вкладке Computed, давайте попробуем, пойдем по порядку:</p>
  <pre id="xnhG" data-lang="javascript">textItem.animate([
  {transform: scale(1), opacity: 1},
  {transform: scale(0.5), opacity: 0.5}
  ], {duration: 500, easing: &quot;ease-out&quot;, fill: &quot;forwards&quot;}
);
picItem.animate(
  {padding: 0 20px, opacity: 1},
  {padding: 0 5px, opacity: 0.5}
  ], {duration: 500, easing: &quot;ease-out&quot;, fill: &quot;forwards&quot;}
)
picItemImg.animate(
  {width: 306px, height: 507px}, ...</pre>
  <p id="fnSl">Да ну нахрен! Это только три элемента из восьми и только для плавного 500ms перехода без замены. А это еще надо воткнуть в функцию на кнопку! Ужас!</p>
  <p id="JNB8">Меняем план — выводим все анимации в отдельную функцию и стараемся максимально оптимизировать процесс.</p>
  <p id="cPYg"></p>
  <h3 id="dXVH">Получаем Сomputed значения свойств</h3>
  <p id="OXs0">Мне изначально немного свербило в голове, что приходится что-то калькулировать из DevTools и подставлять это в код. То есть, по сути, я брал значения из JavaScript в браузере и вставлял к себе в JavaScript. Может быть JavаматьегоScript сам умеет находить нужные ему циферки?</p>
  <p id="EORC">И оказывается умеет! Есть несколько способов получить свойства элемента, но мне первым попался, да и больше понравился, вариант использования функции <code>getComputedStyle()</code>, которая принимает элемент и возвращает огромный объект <code>CSSStyleDeclaration</code> со всеми свойствами элемента, коих аж 342.</p>
  <figure id="nLYL" class="m_original">
    <img src="https://img4.teletype.in/files/75/dc/75dc3895-a181-4b41-8e70-1ec4753cd7a2.png" width="288" />
  </figure>
  <p id="klSa">У этой функции свои нюансы, например, она реагирует на свойство <code>box-sizing</code> и по итогу в <code>width</code> выдаёт значение суммы <code>width</code> и <code>padding</code> если стоит значение <code>border-box</code>. Еще я в процессе работы заметил проблему с <code>height</code> — сюда в сумму уходит каким-то хреном <code>line-height</code> из-за чего нормально посчитать высоту итема с текстом будет трудно, но благо нам она и не нужна оказалась.</p>
  <p id="PSee">Что это значит для нас? У нас есть элементы в разных состояниях, с разными свойствами — мы можем просто собрать с них все стили и применять их как состояния в анимации. На примере текстового итема:</p>
  <pre id="x4rl" data-lang="javascript">/* собираем стили состояний */
const currentTextItemStyles = getComputedStyle(textItem); //&quot;большой&quot;
const defaultTextItemStyles = getComputedStyle(nextTextItem); //&quot;маленький&quot;

/* делаем массивы кейфреймов */
const textItemIncrease = [defaultTextItemProps, currentTextItemProps];
const textItemDecrease = [currentTextItemProps, defaultTextItemProps];

/* анимируем используя кейфреймы */
textItem.animate(textItemDecrease, {duration: 500, ...}) // уменьшаем
nextTextItem.animate(textItemIncrease, {duration: 500, ...}) // увеличиваем</pre>
  <p id="UZKB">Отлично! Уже значительно сократили писанину. Но меня на этом моменте смутило, что мы используем в анимации четыре раза два огромных массива по 300+ свойств. А это только один элемент. Нам же по сути нужны 2-3 свойства в каждой. Я решил попробовать избавиться от лишнего.</p>
  <p id="hYzR"></p>
  <h3 id="Dh5P">Отсеиваем лишние свойства</h3>
  <p id="QcS2">Сам объект <code>CSSStyleDeclaration</code> хранит в себе, в том числе, свойства в виде пар «свойство — значение». Я подумал было бы круто их как-то достать, именно те, что мне нужны. Как достать значения я прекрасно знал — по индексу или по ключу. А можно ли как-то вытянуть именно пару ключ-значение?</p>
  <p id="d7hE">Можно, конечно! Используя хитрую функцию <code>.reduce()</code>, которая, как бы, разбирает объект или массив по составляющим, используя колбэк функцию, где прописывается по каким условиям делать разбор.</p>
  <section style="background-color:hsl(hsl(34,  84%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="D2pj">В поисках я нашёл еще <a href="https://dev.to/rajnishkatharotiya/pick-desired-key-value-pair-from-an-object-48aa" target="_blank">более хитрую конструкцию</a> с применением функции <code>.reduce()</code>, на которую натурально минут 40 сидел и просто пялился, тупо чтобы понять что она вообще делает. А делает она как раз то, что мне надо — возвращает массив пар ключ-значение по заданным ключам.</p>
  </section>
  <p id="1nPU">Сама конструкция работает немного в обратную сторону — разбирается массив с искомыми ключами, но в контексте функции с объектом, в котором проверяется наличие этих ключей и функция возвращает объект с найденными ключами в паре со значениями. Завернул вам, блять, в шаурму — держите! </p>
  <figure id="oOCY" class="m_column">
    <img src="https://img3.teletype.in/files/6d/87/6d876638-db35-48e9-b88e-cbbe727c81a6.jpeg" width="700" />
  </figure>
  <p id="SBI3">Короче, я её трансформировал для себя в более понятном (надеюсь) виде. Занимает может и не одну строчку, зато переведено с квантово-индусского:</p>
  <pre id="r4OL" data-lang="javascript">const getElementProps = (element, keys) =&gt; {
  elementStyles = getComputedStyle(element);
  return keys.reduce(
    (props, key) =&gt; {
      if (key in elementStyles) {
        props[key] = elementStyles[key];
        return props;
      }
    }, {/* initialValue */} // пустой объект для сбора пар
  )};</pre>
  <p id="z7jL">Итак, разбираемся что происходит:</p>
  <ol id="l0Jl">
    <li id="XmT0">Функция <code>getElementProps()</code> принимает элемент и массив с ключами искомых свойств вида <code>[&quot;width&quot;, &quot;height&quot;, ... ]</code>.</li>
    <li id="mJjl">Элементу находим все стили через <code>getComputedStyle()</code> и записываем в локальную переменную <code>elementStyles</code>.</li>
    <li id="sjWu"> В возврат <code>return</code> (можно и отдельно) записываем <code>.reduce()</code> функцию, которая пройдется по нашему массиву искомых ключей.</li>
    <li id="DhKz">Функция <code>.reduce()</code> принимает два аргумента — анонимную (не названную) колбэк функцию и <code>initialValue</code> — пустой объект, куда будут записываться возвраты <code>return</code> колбэк функции, то бишь пары ключ-значение.</li>
    <li id="jCuX">Колбэк принимает два аргумента — аккумулятор <code>props</code>, куда записывается каждый результат <code>return</code> колбэка и обрабатываемый в одной итерации элемент <code>key</code>. По сути, это всё такой хитрый эквивалент <code>for (key of keys)</code>.</li>
    <li id="pWIx">Внутри колбэк функции в условии проверяется наличие ключа key, например «width», в объекте через оператор in и в случае нахождения записывает значение по ключу в аккумулятор.</li>
  </ol>
  <p id="qw64">В итоге у нас получается вожделенный объект нужных нам свойств! На примере картиночного слайдера — нам нужны размеры <code>width</code> и <code>height</code> для картинки <code>&lt;img&gt;</code> и кружочка <code>&lt;span&gt;</code>, потом <code>opacity</code> для итема и еще <code>margin-right</code> для расчёта смещения. Получается:</p>
  <pre id="Q9nX" data-lang="javascript">const picItemProps = [&quot;width&quot;, &quot;height&quot;, &quot;opacity&quot;, &quot;marginRight&quot;];

const currentPicItemProps = getElementProps(picItem, picItemProps);
const currentPicItemImgProps = getElementProps(picItemImg, picItemProps);
const currentPicItemSpanProps = getElementProps(picItemSpan, picItemProps);
/* аналогично для default состояния */</pre>
  <figure id="nNZx" class="m_original" data-caption-align="center">
    <img src="https://img4.teletype.in/files/b7/ab/b7ab63df-3636-4747-beb9-621943e65497.png" width="545" />
    <figcaption>Пример что в итоге в currentPicItemProps</figcaption>
  </figure>
  <p id="S29G">А для смещения нам нужно только численное значения ширины <code>width</code> и отступа <code>margin</code>, без <code>px</code>, так как мы потом будем подставлять его через интерполяцию в строку. Получаем его из наших пропсов с помощью функции <code>parseInt()</code>:</p>
  <pre id="nttn" data-lang="javascript">const picSlideOffset = 
  parseInt(defaultPicItemProps.width) // не помещается :D
  + parseInt(defaultPicItemProps.marginRight); // = 101 + 40 = 141</pre>
  <p id="y5F6">Аналогично делаем для всех четырёх элементов и двух состояний, собирая таким образом такую, условную базу кейфреймов для анимации. </p>
  <p id="27nK">Еще для краткости и удобства можем собрать в одном месте наши параметры анимации, которых у нас всего два, но использоваться они будут очень часто:</p>
  <pre id="HxRb" data-lang="javascript">const instant = {duration: 0, fill: &quot;forwards&quot;}; // для замены
const ease500 = {duration: 500, easing: &quot;ease-out&quot;, fill: &quot;forwards&quot;};</pre>
  <p id="XW4I"></p>
  <h3 id="lnEY">Финальная подстановка</h3>
  <p id="6N8G">Итак, у нас есть все действительные значения, записанные в кейфреймы, осталось дописать функции анимации. Продолжим на примере картиночного слайдера, так как он по сложнее.</p>
  <p id="pMPS">Для начала сразу решим вопрос со смещением слайдера. Я не смог придумать как изящно запихнуть кейфреймы с интерполяцией, поэтому сделал функцию, которая просто сама уже подставит нужный оффсет и вернёт весь кейфрейм:</p>
  <pre id="qASG" data-lang="javascript">const moveContainer = (offset) =&gt; {
  return [
    {transform: &#x60;translateX(-${currentSlideIndex * offset}px)&#x60;},
    {transform: &#x60;translateX(-${nextSlideIndex * offset}px)&#x60;}
  ]};</pre>
  <p id="92uZ">И далее в <code>.animate()</code> как аргумент подставляем эту функцию с нужным оффсетом. Остальные <code>.animate()</code> функции принимают уже константы из нашей базы и работают с ними.</p>
  <p id="UL1z">Я также для сокращения писанины решил вывести в отдельную функцию все анимации из кнопки и чтобы функция была универсальной — будем передавать в неё параметры анимации в зависимости от ситуации. Таким образом функция подойдет и в замене и в плавном переходе:</p>
  <pre id="IGzB" data-lang="javascript">const itemsAnimation = (timing) =&gt; {
  textSlider.animate(moveContainer(textSlideOffset), timing);
  picSlider.animate(moveContainer(picSlideOffset), timing);
  
  textItem.animate(textItemDecrease, timing);
  picItem.animate(picItemOpacityOff, timing);
  picItemImg.animate(picItemImgDecrease, timing);
  picItemSpan.animate(picItemSpanDecrease, timing);
  
  nextTextItem.animate(textItemIncrease, timing);
  nextPicItem.animate(picItemOpacityOn, timing);
  nextPicItemImg.animate(picItemImgIncrease, timing);
  nextPicItemSpan.animate(picItemSpanIncrease, timing);
}</pre>
  <p id="upC6">А в кнопке у нас остаётся только:</p>
  <pre id="rDhl" data-lang="javascript">buttonNext.addEventListener(&quot;click&quot;, () =&gt; {
  nextSlideIndex = currentSlideIndex + 1;
  
  if (nextSlideIndex &gt; lastSlideIndex) {
    nextSlideIndex = 0;
    
    getItemsByIndex();
    itemsAnimation(instant);
    switchClasses();
  
    currentSlideIndex = nextSlideIndex;
    nextSlideIndex = currentSlideIndex + 1;
  }
  
  getItemsByIndex();
  itemsAnimation(ease500);
  switchClasses();

  currentSlideIndex = nextSlideIndex;
}</pre>
  <p id="MD7W"><strong>А вот, в общем-то, и всё</strong>. Слайдер готов в том виде, в котором я изначально хотел. Я у себя в коде еще немного намудрил с выведением отдельно функций в кнопке <code>swapSlides()</code> и <code>moveSlider()</code> — которые еще чуть укорачивают запись.</p>
  <p id="zAMC"></p>
  <h2 id="sph1">Остальные задачи</h2>
  <p id="Qe4r">Остались еще две периферийные задачи — пагинация из кнопок-точек в углу, с которой я тоже немножко повозился, но задача значительно легче уже показалась. И, собственно, замена стиля сайта при слайде, что меня изначально и привлекло, но на практике оказалось легчайшей задачей в данном вопросе.</p>
  <p id="p2II"></p>
  <h3 id="kxXz">Пагинация</h3>
  <p id="zjac">Быстренько добьем эти задачки. Начнём с пагинации. Основная проблема у нас состоит в том, что точек всего три, сколько и не клонированных слайдов, то есть изначальная длинна коллекции.</p>
  <p id="04KT">Это значит, что у нас первая точка должна обозначать и 1-й и 4-й слайд и, желательно, не перемещаться между ними при нажатии. При этом нужно не сломать всё остальное, то есть трогать расчёт индексов нельзя.</p>
  <p id="tNct">Для начала навесим на кнопки функции на клик. Кнопок у нас три, но они однотипные — номер кнопки ведёт к номеру слайда. Тут прям напрашивается цикл и его мы и применим. </p>
  <pre id="C6TN" data-lang="javascript">for (let i = 0; i &lt; buttonsDots.length; i++) {
  buttonsDots[i].addEventListener(&quot;click&quot;, () =&gt; {
    nextSlideIndex = i; // номер кнопки от i = 0 равно индексу слайда
    
    getItemsByIndex();
    itemsAnimation(ease500);
    switchClasses();
    
    currentSlideIndex = nextSlideIndex;
});</pre>
  <p id="DK8m">Еще надо сразу учесть проблему в случае позиции слайдера на клоне (4-м слайде) и нажатии, например на 3-й слайд. С кодом выше анимация будет влево, что немного неожиданно для цикличного слайдера — мы же на 1-м слайде, а листается «назад».</p>
  <p id="f295">То есть, нам и здесь надо подставить условие с заменой, правда немного с другой проверкой:</p>
  <pre id="6tm1" data-lang="javascript">if (currentSlideIndex === lastSlideIndex) { // если точно последний
  nextSlideIndex = 0; // меняем на первый
  
  getItemsByIndex();
  itemsAnimation(instant);
  switchClasses();
  
  nextSlideIndex = i;
} // потом уже слайдим</pre>
  <p id="BNb9">Далее решим со стилями точек, напомню, выглядят они вот так:</p>
  <figure id="INRr" class="m_original">
    <img src="https://img4.teletype.in/files/3a/de/3adef89c-1746-404a-a9c8-17df114f1e12.png" width="110" />
  </figure>
  <p id="7KpB">Текущая <code>.current</code> — непрозрачная, а остальные полупрозрачные. Всё привязано к тому же вспомогательному классу, поэтому для «листания» точек немного расширим функцию <code>switchClasses()</code>. При этом надо учесть, опять же, что слайдов на один больше чем точек:</p>
  <pre id="Hlka" data-lang="javascript">const switchClasses = () =&gt; {
  /* ... Меняем классы итемам ... */
  
  if (currentSlideIndex === lastSlideIndex) {
    buttonsDots[0].classList.remove(&quot;current&quot;);
  } else {
    buttonsDots[currentSlideIndex].classList.remove(&quot;current&quot;);
  }  
  
  if (nextSlideIndex === lastSlideIndex) {
    buttonsDots[0].classList.add(&quot;current&quot;);
  } else {
    buttonsDots[nextSlideIndex].classList.add(&quot;current&quot;);
  }
}</pre>
  <p id="5s10">Если у нас текущий/следующий слайд это последний, то бишь клон 1-го, то активной делаем 1-ю точку, в остальных случаях по номеру индекса.</p>
  <p id="rb0N">В этом случае правда будет небольшой баг — при <code>current</code> позиции слайдера на клоне и нажатии на закрашенную, но всё еще активную первую точку (так как <code>i</code> в цикле будет 0, что отлично от <code>current</code>) будет срабатывать кривая анимация.</p>
  <p id="f8hx">В этом случае можно не мудрить и просто дизеблить кнопку по классу. Отключить нажатия по ней можно с помощью свойства <code>pointer-events: none</code>.</p>
  <pre id="OpOM" data-lang="css">.slider-dots__control.current {  pointer-events: none; }</pre>
  <p id="nsZo"></p>
  <h3 id="BuSm">Смена стилей сайта</h3>
  <p id="3tAx">Если не знать как работает каскад стилей, задача может показаться крайне сложной. Но мы-то с вами тёртые калачи, ёпты, и знаем, что для этой магии достаточно буквально нескольких строчек кода.</p>
  <p id="UK67">У меня в стилях заранее заготовлены стили для трёх состояний тега <code>&lt;body&gt;</code>:</p>
  <ol id="msQF">
    <li id="DW2c">Без дополнительных классов стиль сайта розовый.</li>
    <li id="ioh7">С классом <code>.page-body--blue</code> оформление (фон, кнопочки, акценты) меняются на голубенький.</li>
    <li id="Ax0c">С классом <code>.page-body--yellow</code> на жёлтый.</li>
  </ol>
  <p id="oCC3">Нам осталось только воткнуть смену этих классов на <code>&lt;body&gt;</code>, но с определёнными условиями. Нам нужно что бы оформление менялось одно за другим, то бишь надо как-то условно ограничить в зависимости от индексов.</p>
  <p id="aDdq">Долго тут мусолить нечего — берём индексы слайдов и проверку на остатки, чтобы учесть теоретическое переполнение, получаем:</p>
  <pre id="WdnH" data-lang="javascript">if (nextSlideIndex === 0 || nextSlideIndex % lastSlideIndex === 0) {
  document.body.classList.remove(&quot;page-body--blue&quot;, &quot;page-body--yellow&quot;)
} else if (nextSlideIndex === 1 || nextSlideIndex % lastSlideIndex === 1) {
  document.body.classList.add(&quot;page-body--blue&quot;);
  document.body.classList.remove(&quot;page-body--yellow&quot;)
} else if (nextSlideIndex === 2 || nextSlideIndex % lastSlideIndex === 2) {
  document.body.classList.add(&quot;page-body--yellow&quot;)
  document.body.classList.remove(&quot;page-body--blue&quot;);
}</pre>
  <p id="tIv0">Пихаем полученную красоту в ту же функцию <code>switchClasses()</code> и получаем итоговый готовый слайдер. Теперь точно всё!</p>
  <p id="L9pT"></p>
  <h2 id="VlXC">Ссылки и финал</h2>
  <p id="z9zr">Всем кто каким-то чудом дочитал или хотя бы до листал до этого момента огромное спасибо за внимание! У меня это все дело заняло целую неделю, но я даже не жалею. Было очень интересно разобраться самостоятельно.</p>
  <p id="Vcgu">Во время работы я много чего находил, все ссылки со Stackoverflow и MDN я кидать не буду, но ключевые штуки которые мне помогли оставлю:</p>
  <ul id="cnPG">
    <li id="jTYK"><a href="https://stackoverflow.com/questions/15876754/infinity-loop-slider-concepts" target="_blank">Вопрос на Stackoverflow</a> где написан концепт бесконечного слайдера на jQuery за счет клонирования первого и последнего итема.</li>
    <li id="RW89"><a href="https://www.youtube.com/watch?v=wjC8iGt67UE" target="_blank">Вариант слайдера</a> с заменой на <code>transitionend</code>, косвенно помог — натолкнул на идею с индексами и помог разобраться с интерполяцией.</li>
    <li id="P5cX"><a href="https://dev.to/rajnishkatharotiya/pick-desired-key-value-pair-from-an-object-48aa" target="_blank">Хитрая фича</a> с <code>.reduce()</code> сломавшая мне мозг, но которая помогла мне решить вопрос с фильтрацией свойств.</li>
  </ul>
  <p id="R9g0">И еще десятки статей с MDN, спецификаций и других ресурсов.</p>
  <section style="background-color:hsl(hsl(0,   0%,  var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="5att" data-align="center"><a href="https://t.me/arkhelvetios" target="_blank">Telegram</a> — <a href="https://twitter.com/arkhelvetios" target="_blank">Twitter</a> — <a href="https://www.instagram.com/arkhelvetios/" target="_blank">Instagram</a> — <a href="https://vk.com/arkhelvetios" target="_blank">VK</a></p>
  </section>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@helvetios/html-academy-level-1</guid><link>https://teletype.in/@helvetios/html-academy-level-1?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=helvetios</link><comments>https://teletype.in/@helvetios/html-academy-level-1?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=helvetios#comments</comments><dc:creator>helvetios</dc:creator><title>Курс «HTML и CSS. Профессиональная вёрстка сайтов» – асинхронный формат</title><pubDate>Mon, 10 Oct 2022 21:06:31 GMT</pubDate><media:content medium="image" url="https://img2.teletype.in/files/d1/49/d149eca4-354e-4e0a-bd50-b12380f5887a.png"></media:content><category>Веб-разработка</category><description><![CDATA[<img src="https://img2.teletype.in/files/1c/33/1c33759d-e691-4ec8-8d1e-e9f6de2fd0d7.png"></img>Всем привет. Я таки прошел все тренажёры HTML Academy, хоть и о последних блоках про JavaScript писать не стал — подписка кончилась, буквально, в последний день прохождения заданий, а оплачивать её только ради конспекта здесь не хотелось.]]></description><content:encoded><![CDATA[
  <figure id="pFI0" class="m_original">
    <img src="https://img2.teletype.in/files/1c/33/1c33759d-e691-4ec8-8d1e-e9f6de2fd0d7.png" width="1200" />
  </figure>
  <p id="VEZ7">Всем привет. Я таки прошел все тренажёры HTML Academy, хоть и о последних блоках про JavaScript писать не стал — подписка кончилась, буквально, в последний день прохождения заданий, а оплачивать её только ради конспекта здесь не хотелось.</p>
  <p id="6TlO">К тому же, тренажёры — это только начальный этап и темы в блоках посвященные JavaScript, будут еще не раз встречаться далее в обучении. Часть из них уже встретились в конце самого первого курса — «HTML и CSS. Профессиональная вёрстка сайтов».</p>
  <p id="ECtJ">Я проходил этот курс в рамках профессии «Фронтенд-разработчик» в асинхронном или, если по простому, в самостоятельном формате. Сама «Профессия» — это, грубо говоря, пакет курсов, а самостоятельный формат — более дешёвый и гибкий по времени. Для меня такие условия звучали идеально, но на самом деле отличия от обычных курсов не только в этом.</p>
  <p id="Ukcs">Этой статьёй я хочу резюмировать свое самостоятельное прохождение этого курса — что нового узнал, как справился с проектом, что понравилось, а что не очень. Можно назвать это отзывом.</p>
  <p id="oUJD"></p>
  <h2 id="Y8Vt">О чём курс?</h2>
  <p id="c0lc">Решил, что важно немного пояснить, а то заголовок-то у курса кричащий, но не конкретизирующий. Курс, сюрприз — про вёрстку, но не про всю. Первый курс, который в Академии еще называется «Уровень 1», посвящён только статичной вёрстке, <strong>без адаптивности</strong> под разные устройства.</p>
  <figure id="BIAl" class="m_original">
    <img src="https://img4.teletype.in/files/3b/ff/3bffd431-e5c6-444d-8139-d2039d9cb01f.png" width="540" />
  </figure>
  <p id="rAWL">В курсе есть разделы про флексбокс и грид, но ими мы пользуемся для построения сеток в рамках статичной ширины сайта. Основной фокус курса — научить всем навыкам вёрстки без усложнений в виде настройки окружения, оптимизаций, препроцессоров и т. д. </p>
  <p id="Ide1">Я бы не сказал, что это плохо — это просто нюанс.</p>
  <p id="3c12"></p>
  <h2 id="uEe0">Программа обучения</h2>
  <p id="x1g3">В процессе прохождения курса мы работаем над <strong>проектом</strong> — выбираем из макетов какой понравится и начинаем его постепенно верстать проходя от раздела к разделу. Макеты разные по сложности, но всё показывают на относительно простом демо-макете «Барбершоп», который выбрать нельзя. </p>
  <figure id="NV0R" class="m_column" data-caption-align="center">
    <img src="https://img2.teletype.in/files/13/ed/13ed4187-181e-42a0-bf85-81acd40d0417.png" width="1065" />
    <figcaption>Доступные проекты</figcaption>
  </figure>
  <p id="knhQ">То бишь, какие-то вещи в любом случае надо как-то решать самому и даже не по образу и подобию, а решать задачу имеющимися знаниями и средствами. Это на мой взгляд круто, хотя я и видел обратную реакцию, мол за деньги мне должны всё разжевать и в рот положить. Тут, наверное, кому как.</p>
  <p id="PTYY">Выполнять проект нужно в соответствии с <strong>техническим заданием</strong> — там есть прям неочевидные нюансы, и еще в соответствии с <strong>критериями</strong> — достаточно большим списком требований к итоговой работе. Например, переполнение — чтобы сайт не разваливался при любом количестве контента в любом отдельно взятом блоке.</p>
  <p id="pbCw">Курс поделён на 10 разделов, а каждый раздел поделён еще на, назовём это, стадии:</p>
  <ol id="5gZD">
    <li id="MvKl"><strong>Подготовка к вебинару</strong>, которая часто разделена еще на:</li>
    <ol id="tKje">
      <li id="TrcH"><strong>Тренажёры</strong> (да, они здесь как часть обучения).</li>
      <li id="oRgz"><strong>Статьи</strong> из «Учебника» или какие-то внешние статьи по теме раздела.</li>
    </ol>
    <li id="Qwau"><strong>Лайв</strong> или вебинар (точнее запись), на котором разбираются интерактивные «Демки» и авторы курса комментируют подходы к задаче. Это именно не лекции, а разбор задачи и «узких мест» в реализации.</li>
    <li id="lBEk">Сами «<strong>Демки</strong>» — это как тренажёры, но там всё уже решено и каждый шаг идёт с пояснениями.</li>
    <li id="OUlH"><strong>Домашнее задание</strong> — это страницы c описанием чего делать в рамках раздела со своим проектом. Так как формат самостоятельный и наставника нет, есть ещё разбор примера, но только на один лёгкий проект (Nёrds).</li>
    <li id="ZT2X"><strong>Дополнительные материалы</strong> — в них частенько много полезной инфы, но есть и прям филлеры, особенно в одном разделе (позже напишу).</li>
  </ol>
  <figure id="urRQ" class="m_column" data-caption-align="center">
    <img src="https://img4.teletype.in/files/fd/a5/fda529a0-1812-4867-9e0d-51c631bf91cf.png" width="1048" />
    <figcaption>Пример структуры внутри раздела</figcaption>
  </figure>
  <p id="ycYo">Сами разделы описывать не буду, они <a href="https://htmlacademy.ru/intensive/htmlcss#program" target="_blank">вот тут</a> на сайте самого курса есть, там без сюрпризов — разметка, сетки, декорации, доступность, мать её, и щепоть JavaScript для модальных окон. Хотя, сейчас написал про модалки и вспомнил, что всё же есть нюансы.</p>
  <p id="Ublp"></p>
  <h2 id="c5Ln">Отличие асинхронного формата</h2>
  <p id="pOnp">Главное отличие — это, естественно, цена. Но тут сложно посчитать, так как я оплачивал сразу профессию, не уверен, есть ли отдельно сольный курс. В любом случае, надо понимать, что за более дешёвый вариант курса, полагается менее приоритетный сервис.</p>
  <p id="Mqu3">Во-первых, <strong>у нас нет наставников</strong>. Их место занимает Discord-сервер для оплативших профессию, где участники могут задавать вопросы и дежурные наставники отвечают на них. Иногда. </p>
  <p id="vgep">Ладно, справедливости ради, они правда вроде как стараются отвечать, но так как это явно не их основная работа, есть риск, что вопрос просто проигнорится если его лишний раз не пингануть. </p>
  <p id="bj0C">Очень часто отвечают более опытные ученики, что радует:</p>
  <figure id="FIdt" class="m_column">
    <img src="https://img4.teletype.in/files/b2/35/b235b7d9-68b1-4f53-bf89-8d2bfb327f1b.png" width="1087" />
  </figure>
  <p id="rgTO">Наставника, кстати, всего два не просто на курс, а сразу на всю «Профессию», а это, на минуточку, 5 курсов. Народу, конечно, на момент сентября вроде как немного (я не вижу всех кто офлайн), вопросы задаются с переменной частотой, иногда несколько суток ни одного. Поэтому, сажать на дежурство десяток наставников, наверное, нецелесообразно.</p>
  <p id="Axi6">Второе отличие — <strong>количество проектов</strong>. В полном курсе их, не считая «Барбершопа», пять штук. В порядке возрастания сложности:</p>
  <ol id="kMWW">
    <li id="Btlj">Nёrds</li>
    <li id="81qs">Sedona</li>
    <li id="FWjZ">Техномарт</li>
    <li id="OIk5">Device</li>
    <li id="7oPA">Gllacy</li>
  </ol>
  <p id="xIgz">Как видно по скрину выше — в асинхронном формате доступны только первые три, то есть, самые сложные проекты Device и Gllacy не доступны. Я еще когда смотрел сайт курса хотел взять Device, а тут слегка обломилось. В итоге я взял Техномарт, но это не конец истории, об этом чуть позже.</p>
  <p id="ckGF">Третье отличие — <strong>нет обязательного взаимодействия с GitHub</strong>. Это, на самом деле, вредное отличие, так как уже в «Уровне 2» начинается прям полное погружение в Git и уж лучше сразу хотя бы базово с ним разобраться, хотя бы на уровне как сделать эти ваши пуши, коммиты, пул-реквесты и т. д.</p>
  <p id="JU2i">Четвёртое отличие — <strong>нет защиты проекта</strong>. Это скорее касается всей профессии, так как есть только итоговое грейдирование по итогу всего пройденного материала. Что это такое не очень понятно, видимо что-то вроде экзамена по всем курсам, но отдельных защит или экзаменов по каждому курсу нет.</p>
  <p id="Trqh">Пятое отличие — не дадут сертификат и ачивку в личном кабинете. <code>:(</code></p>
  <figure id="YAF6" class="m_original" data-caption-align="center">
    <img src="https://img4.teletype.in/files/bb/35/bb358dca-d24a-4a78-ad7f-aef2dbe2a53f.png" width="272" />
    <figcaption>Успешно прошел, а шарик не дали, в классе заперли.</figcaption>
  </figure>
  <p id="Ljmj">Еще одно такое, мини-отличие, которое заметил в последнем разделе по JS — не разбираются слайдеры, только модальные окна. Увидел в разборе домашнего задания, что у ученика полного курса слайдеры есть. Ну и ладно.</p>
  <p id="lGfQ"></p>
  <h2 id="3HNk">Чему научился?</h2>
  <p id="FUYe">Очень многое в курсе построено на тренажёрах, а так как я их уже прошел, то и значительную часть я уже знал. Плюс тот самый «<a href="https://blog.arkhelvetios.ru/keksby" target="_blank">Великий Кексби</a>» подготовил меня к подобному формату самостоятельной работы.</p>
  <p id="1HXO">Однако, в учебнике все-таки были некоторые особо важные темы. Самые массивные из них — методики. Их несколько в разных разделах, например, методика построения семантически верной разметки, методика БЭМ, методика построения сеток и микросеток, методика работы с отступами и другие.</p>
  <p id="X2mi">Это такие огромные, подробные мануалы с пошаговым разбором действий, типовых ошибок, «бестпрактиз», хинтов и так далее — пожалуй, одни из наиболее полезных штук в курсе.</p>
  <p id="BmTk">Например, в ходе первых разделов, наконец стало точно понятно в чем отличие <code>&lt;section&gt;</code> и <code>&lt;article&gt;</code>. Почему обязательно нужны <code>&lt;h2&gt;</code> для <code>&lt;section&gt;</code> даже если их нет на макете. Где разумно <code>&lt;section&gt;</code>, а где лучше <code>&lt;div&gt;</code>.</p>
  <figure id="pUrB" class="m_column" data-caption-align="center">
    <img src="https://img2.teletype.in/files/da/b8/dab8d817-cba3-4552-a90a-c1a2d65b1942.png" width="707" />
    <figcaption>Выглядит аж красиво. Или я схожу с ума?</figcaption>
  </figure>
  <p id="3M4l">Про БЭМ это вообще отдельный разговор, мир не станет прежним — я всё думал, что за <code>__</code> везде в классах на сайтах, а оно вон чего. Хотя БЭМ в курсе и не требуют, но я сразу начал практиковаться.</p>
  <p id="F7Ei">Научился подключать favicon и webmanifest — ничего сложного, но с нюансами. Сама favicon оказывается работает даже если её тупо шваркнуть в корневую папку, но лучше всё-таки явно прописать.</p>
  <p id="2rrf">Много узнал и изучил про normalize.css и его прародителей. </p>
  <p id="zTKW">Научился грамотно подключать шрифты в CSS через <code>@font-face</code>. В дополнительных материалах есть записи с каких-то выступлений Вадима Макеева, он там очень много интересных штук про это рассказывает.</p>
  <p id="l9hL">Отдельным открытием были кастомные свойства. Это по сути как переменные, но в чистом CSS. Я сначала даже не поверил. Серьезно? А нафига тогда препроцессоры? Видимо, для миксинов. </p>
  <figure id="tMuC" class="m_original" data-caption-align="center">
    <img src="https://img3.teletype.in/files/2e/b6/2eb6e9ae-50be-45e4-9793-7ca13b17e2c7.png" width="621" />
    <figcaption>Тут только цвета, но вообще можно почти любые значения</figcaption>
  </figure>
  <p id="zBQL">Сетки прошли практически без сюрпризов, уж их-то я еще на тренажёрах изучил наперёд. Уверен, что еще споткнусь на нюансах на 2-м уровне, но пока мне хватило опыта — были одни из самых комфортных разделов.</p>
  <p id="77CT">Единственный неизвестный мне хинт который я, внезапно, услышал от наставника в разборе домашнего задания — про расчёт ширины в гриде. </p>
  <pre id="Lf0O" data-lang="css">grid-template-columns: 1fr 2fr;
//не равно
grid-template-columns: repeat(3, 1fr);</pre>
  <p id="LO3n">Вы, возможно, смотрите и такие «Ты чо дурак?». Нюанс приобретает смысл, когда у вас структура колонок 1 к 2 или 1 к 3, в общем дробная, там где так и хочется использовать в лоб <code>fr</code>. </p>
  <figure id="BFXC" class="m_column" data-caption-align="center">
    <img src="https://img2.teletype.in/files/95/40/95406984-0392-416e-b56e-034065ff6aee.png" width="957" />
    <figcaption>Типа как тут – хочется сделать 1fr 3fr</figcaption>
  </figure>
  <p id="NOSk">Однако, надо еще не забывать про <code>gap</code> и что он, по сути, тоже учувствует в расчёте ширины. А от того как вы зададите <code>grid-template-columns</code> будет отличаться количество ячеек, а как следствие гэпов и в конечном итоге размер доли под которую выделен контент.</p>
  <p id="3UYx">Много разных нюансов по поводу интерактивных объектов и их плохой наследуемости свойств. В Техномарте есть форма из одного поля поиска в хэдере, вот с ней прям засел я на пару часиков.</p>
  <figure id="CuqI" class="m_original">
    <img src="https://img3.teletype.in/files/67/3e/673eccd6-7762-48de-9b80-4146a69af995.gif" width="480" />
  </figure>
  <p id="a5j9">Остальные декорации заняли много времени, но в основном в тренажёрах уже разбирались. Немного, правда, провозился на чекбоксах и радио — хотел сделать их не через SVG-иконки, а методами CSS, но споткнулся о галочку в чекбоксе — у нее была обводка закрывающая часть контура.</p>
  <figure id="Ycgx" class="m_original">
    <img src="https://img1.teletype.in/files/06/5f/065fa54d-2d81-4191-b922-74f4fcde7c1a.png" width="182" />
  </figure>
  <p id="nREg">Узнал про переполнение и как его предвидеть. Немного даже перестарался, по-моему — учел даже переполнение в одну строку без пробелов в местах, где текст явно не абзац осмысленного текста, а короткое описание (типа <code>&lt;label&gt;</code>).</p>
  <p id="y0Vw">Базово разобрался с минификаторами CSS и JS, правда, насколько понял из Лайва, что есть технология, которая всё это дело автоматизирует и мы её разберём на 2-м уровне.</p>
  <p id="A3E4">Наконец, самый мутный раздел — доступность. Это тот раздел, где в дополнительных материалах целая плеяда о-очень нудных филлерных статей на эту тему. Столько свой TAB я еще не насиловал. Со скринридером проблем, внезапно, не оказалось. А вот с отображением фокуса на моих кастомных чекбоксах — да. Пришлось допиливать.</p>
  <p id="sj6M">В разделе про JS мы искали элементы через <code>.querySelector()</code>, игрались с классами через <code>.classList</code> и немного разобрались с работой <code>localStorage</code>. Запрограммировали открытие и закрытие модалок и еще настроили автофокус при открытии и автозаполнение полей по ключу из <code>localStorage</code>.</p>
  <p id="cOJJ"></p>
  <h2 id="ZaOk">Про проекты</h2>
  <p id="aSxt">Я сначала пытался затрайхардить и делать параллельно все три доступных проекта. И, в общем-то, до 4го раздела так и было — вся разметка и часть стилизации у меня готово и для Техномарта и для Nёrds с Седоной.</p>
  <p id="umCk">В определённый момент я даже начал искать по интернету остальные два проекта и даже нашёл. Труда особого не составило — они не то чтобы прям в общем доступе, но и не за семью печатями. GitHub и Google вам в помощь.</p>
  <figure id="fMLL" class="m_column">
    <img src="https://img3.teletype.in/files/63/34/6334d3b7-c811-47ff-81d5-d5f84d9b2ee1.png" width="1109" />
  </figure>
  <p id="UKuQ">И в какой-то момент моя душонка такая — «А давай сделаем их все!». Где-то как раз в этот момент мне надо было улетать в другой город на несколько дней по делам и… слава богам, я рад что мне это помешало.</p>
  <p id="7q6o">Дело не в лени или в том, что это какой-то перебор в практике. Дело в том, что лучше реально сделать один проект до конца.</p>
  <p id="0Fxv">Работая над Техномартом я несколько раз тупо переписывал огромные куски как и вёрстки так и стилей, так как повествование в курсе идёт иногда в виде «Оставим пока так — потом поправим/доделаем». А теперь умножьте это на 5, а то и на 10, так как Девайс и Глейси судя по макетам сильно сложнее Техномарта и тем более Nёrds или Седоны.</p>
  <p id="M61W">В идеале — я бы взял Глейси, как, на мой взгляд, самый сложный макет и работал бы с ним в процессе обучения. Сразу разобраться со всеми сложностями и пусть это займет больше времени, но, я уверен, даст фору позже.</p>
  <p id="c9Cx">И уже если потом появилось желание закрепить или тупо закрыть свои гештальты (как у меня) можно приступать к остальным проектам. Опять же если вы в асинхронном формате, а в обычном, насколько я понимаю, особо не по растекаешься на проекты, т. к. там времечко жмёт.</p>
  <p id="NfSV"></p>
  <h2 id="Gis2">Сколько времени заняло</h2>
  <p id="vNwL">Моя любимая часть. Я не так давно занялся этим вот подсчётом личной эффективности, график, таймеры и т. д. Напишу как получилось. Для всего этого дела использовал приложение Toggl Track — оно бесплатное, крайне рекомендую.</p>
  <p id="VbUk">Всё обучение заняло у меня 29 дней — чуть более 4-х недель. Это, правда, с учетом, что мне на 5 дней пришлось улететь в другой город по делам. </p>
  <p id="8Czn">Я не трайхардил до посинения, как делал это на тренажёрах весной, в среднем старался держаться около 20 часов в неделю. Так, в общем-то, и получилось, если учесть пятидневный перерыв.</p>
  <p id="sD0V">В сумме первый курс «HTML и CSS. Профессиональная вёрстка сайтов» занял у меня 68 часов чистых обучения:</p>
  <figure id="2BE7" class="m_column">
    <img src="https://img3.teletype.in/files/a8/68/a868edb8-3905-4662-bc6f-91933e748194.png" width="738" />
  </figure>
  <p id="I3fY">В таймер включены мои повторные прохождения тренажёров — тут, понятное дело, не очень объективно, так как я их пролетал зная чего делать. Так что можно смело еще плюсовать ~8 часов, наверное.</p>
  <p id="IgJP">С другой стороны, я нарочно НЕ учитывал просмотры Лайвов, которые в среднем 1.5-2 часа каждый, хотя я и смотрел их на х2. Они просто, по сути, идут по Демкам, которые я сам пролистывал многократно в ходе работы над заданием и вот это я уже учитывал в таймер.</p>
  <p id="pRqG"></p>
  <hr />
  <p id="gxHn"></p>
  <p id="mBCf">Сейчас я уже приступил к следующему курсу «<a href="https://htmlacademy.ru/intensive/adaptive" target="_blank">HTML и CSS. Адаптивная вёрстка и автоматизация</a>» или «Уровень 2» и это немного не то, что я ожидал. В хорошем ключе. Я еще удивлялся — почему адаптивность отрезали от вёрстки, ведь это в современном мире мастхэв. Оказалось не всё так просто и с адаптивом на 2-м уровне идёт еще пачка технологий и адаптив как таковой не ограничивается шириной экрана или «резиновостью».</p>
  <p id="FIfA">За сим всё, спасибо за внимание! Продолжу в том же духе и встретимся уже после следующего курса.</p>
  <section style="background-color:hsl(hsl(0,   0%,  var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="FDSz" data-align="center"><a href="https://t.me/arkhelvetios" target="_blank">Telegram</a> — <a href="https://twitter.com/arkhelvetios" target="_blank">Twitter</a> — <a href="https://www.instagram.com/arkhelvetios/" target="_blank">Instagram</a> — <a href="https://vk.com/arkhelvetios" target="_blank">VK</a></p>
  </section>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@helvetios/muesli-1-skills-or-theory</guid><link>https://teletype.in/@helvetios/muesli-1-skills-or-theory?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=helvetios</link><comments>https://teletype.in/@helvetios/muesli-1-skills-or-theory?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=helvetios#comments</comments><dc:creator>helvetios</dc:creator><title>Прикладные навыки или глубокая теория?</title><pubDate>Thu, 18 Aug 2022 16:05:07 GMT</pubDate><media:content medium="image" url="https://img1.teletype.in/files/0c/59/0c59cf90-687b-4541-810d-2a2513ebe045.png"></media:content><category>Мюсли в голове</category><description><![CDATA[<img src="https://img1.teletype.in/files/83/45/8345b0f8-bfc5-4e98-9d8f-b64c5567236c.png"></img>У меня всегда был на эту тему диссонанс. С одной стороны — я люблю докапываться до фундаментальной основы всего, что мне интересно. Это проявляется и в быту, и в хобби, и в работе. ]]></description><content:encoded><![CDATA[
  <figure id="sDCE" class="m_column">
    <img src="https://img1.teletype.in/files/83/45/8345b0f8-bfc5-4e98-9d8f-b64c5567236c.png" width="1200" />
  </figure>
  <p id="gR5f">У меня всегда был на эту тему диссонанс. С одной стороны — я люблю докапываться до фундаментальной основы всего, что мне интересно. Это проявляется и в быту, и в хобби, и в работе. </p>
  <p id="XbQt">Из бытового — когда я впервые делал ремонт, не так давно, кстати, я решил не клеить обои, а просто покрасить стены. В процессе изучения вопроса узнал, что перед покраской обязательно нужна некая «Грунтовка», а через пару часов уже изучаю адгезию, когезию, вспоминаю термодинамику, 2-й курс универа и господина Ван-дер-Ваальса.</p>
  <p id="cquA">С физикой у меня вообще полюбовные отношения, не смотря на то, что в универе я не особо отличился каким-то успехом в учёбе. Например, мне безумно нравится смотреть научпоп вообще на любые математические, физические и химические темы. Обнаружить себя в 4 утра с 30+ открытыми вкладками Википедии и лекций — для меня норма. В особо интенсивных случаях достается тетрадь и ручка, скачивается дедушка Mathcad или Labview.</p>
  <p id="T1nz">Отдельный, можно сказать, вид «научпопного искусства» это визуализации квантово-механических процессов и, пользуясь случаем, порекомендую несколько крышесносных видео на эту тему:</p>
  <ul id="abcJ">
    <li id="QIi4"><a href="https://www.youtube.com/watch?v=YiuZUDYSPs8" target="_blank">Общая теория относительности</a> и <a href="https://www.youtube.com/watch?v=3uGQHlmyfVw" target="_blank">её визуализация</a>;</li>
    <li id="0Ftb"><a href="https://www.youtube.com/watch?v=9sv-RElTc6s" target="_blank">Квантовая теория поля</a>;</li>
    <li id="Q98h"><a href="https://www.youtube.com/watch?v=w3jpKJSrV_k" target="_blank">Теория струн</a>;</li>
    <li id="bdhL"><a href="https://www.youtube.com/watch?v=JoDmsVNzT8A" target="_blank">Прыжок в чёрную дыру теория</a> и безумно крутой <a href="https://www.youtube.com/watch?v=17tEg_uTF_A" target="_blank">POV 360</a>;</li>
    <li id="d0pp">Там много всего — <a href="https://www.youtube.com/playlist?list=PL0_0CkR5Zh9vZSH1EifLyESonPwqVmYgZ" target="_blank">перевод на русский</a>, <a href="https://www.youtube.com/playlist?list=PLu7cY2CPiRjVCbSWwSe0mXxWK0S8e8Ssx" target="_blank">оригинал на английском</a>.</li>
  </ul>
  <p id="8VUO">В работе и учебе, в профессиональном плане, тоже частенько проявляется эта моя сторона. Из относительно недавнего — Кривые Безье. Я полез разбираться в математическое построение разных кривых с разным количеством опорных точек. Классические, если можно так сказать, Кривые Безье в графике — кубические в двумерном пространстве. Зачем вам эта информация? А мне она была зачем? Но я потратил на это часов 7-8 точно.</p>
  <hr />
  <p id="mPGZ">С другой стороны — практической пользы от такого дотошного вникания во всё чаще всего нет или она околонулевая. Можно потратить сутки на что-то интересное, но крайне бесполезное. </p>
  <section style="background-color:hsl(hsl(0,   0%,  var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="JHgG">Вопрос которым я озадачил себя — «Так уж ли важно наличие этой пользы»?</p>
  </section>
  <p id="qX38">Когда я начал изучать фронтэнд и программирование, я начал периодически слушать умных и опытных дядек-синьоров. Один из таких, кстати, господин Сергей Немчинский — классный дядька, его канал понравился. </p>
  <p id="ygVu">Так вот, этими дядьками очень часто (скорее, даже — всегда) подаётся идея, что главное освоить востребованный навык или, как сейчас модно, «хардскилл», а остальное прилипнет по мере практики. И с таким тяжело спорить, так как программирование во многом прикладное искусство — практика здесь буквально основа мастерства.</p>
  <p id="w0ny">То есть, если коротко — учишь основы, учишь фреймворк, ищешь трейни/джун оффер. Остальное отбрасывай, не трать время. Глубокая теория не нужна, потому что бизнес диктует дедлайны, а ПиЭм выставляет эстимейты в графике. </p>
  <p id="EGJj">И тут как бы тоже хрен поспоришь.</p>
  <p id="PsL7">И я сначала не спорил, но намедни пришло осознание, что это просто не мой случай. По крайней мере на данный момент. Меня не интересует работа в офисе, пусть даже за тысячи баксов в месяц, меня не интересуют офферы как таковые. Меня не интересует релокация, по крайней мере не в том положении и не по тем причинам по которым это делают сейчас люди.</p>
  <hr />
  <p id="RNG0">Это помогло мне избавиться от странного груза, который висел на душе — мне просто хочется тратить, мать его, время на то что мне интересно, а не на то по каким правилам, якобы, работает индустрия (хотя на самом деле только часть).</p>
  <p id="OJxk">Это же помогло ответить и на вопрос выше. Для меня польза не измеряется количеством фреймворков и технологий или зарплате, которую я получу. То есть она не прямо пропорциональна материальному благу или его подобию.</p>
  <p id="5q3P">Моя «польза» — приятное ощущения от познания и понимания какой угодно и никакому бизнесу ненужной глубокой теории. Пусть даже в 4 утра.</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@helvetios/less-academy</guid><link>https://teletype.in/@helvetios/less-academy?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=helvetios</link><comments>https://teletype.in/@helvetios/less-academy?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=helvetios#comments</comments><dc:creator>helvetios</dc:creator><title>Препроцессор Less — Тренажёр HTML Academy</title><pubDate>Mon, 04 Jul 2022 14:35:15 GMT</pubDate><media:content medium="image" url="https://img4.teletype.in/files/b6/72/b67259bb-f205-4baf-b1b5-3e0e9e55939a.png"></media:content><category>Веб-разработка</category><tt:hashtag>css</tt:hashtag><tt:hashtag>css3</tt:hashtag><tt:hashtag>less</tt:hashtag><tt:hashtag>frontend</tt:hashtag><tt:hashtag>front_end</tt:hashtag><tt:hashtag>обучение</tt:hashtag><tt:hashtag>it</tt:hashtag><description><![CDATA[<img src="https://img3.teletype.in/files/e1/04/e104ae23-a57b-477f-933b-98cea36d1133.png"></img>Разобравшись с вёрсткой переходим к инструментам её ускорения. Мы уже знаем базовые вещи PHP, который формально препроцессор HTML, теперь же займемся CSS и одним из его препроцессоров Less.]]></description><content:encoded><![CDATA[
  <figure id="ayvQ" class="m_column">
    <img src="https://img3.teletype.in/files/e1/04/e104ae23-a57b-477f-933b-98cea36d1133.png" width="1200" />
  </figure>
  <p id="CJUp">Разобравшись с вёрсткой переходим к инструментам её ускорения. Мы уже знаем базовые вещи PHP, который формально препроцессор HTML, теперь же займемся CSS и одним из его препроцессоров Less.</p>
  <p id="nQ2f">Я, кстати, уже писал немножечко про один другой препроцессор CSS — в статье про <a href="https://blog.arkhelvetios.ru/webref-mob#R08H" target="_blank">мобильное приложение</a> для самообучение Webref, там нас познакомили с Sass и его упрощённым вариантом SCSS. Спойлер — отличия от Less всё-таки есть, особенно в синтаксисе, хотя по сути мало что меняется.</p>
  <p id="wW6q"></p>
  <nav>
    <ul>
      <li class="m_level_1"><a href="#I5PR">О препроцессорах</a></li>
      <li class="m_level_1"><a href="#u9om">Возможности Less</a></li>
      <li class="m_level_2"><a href="#5G4Y">Переменные</a></li>
      <li class="m_level_2"><a href="#Z6d6">Математические операции</a></li>
      <li class="m_level_2"><a href="#ZDd3">Цветовые функции</a></li>
      <li class="m_level_2"><a href="#QJRd">Вложенные правила</a></li>
      <li class="m_level_1"><a href="#bF7F">Примеси Less</a></li>
      <li class="m_level_2"><a href="#xUT3">Шаблон примеси</a></li>
      <li class="m_level_2"><a href="#JjQQ">Примесь с условием</a></li>
      <li class="m_level_2"><a href="#oh3y">Вставки</a></li>
      <li class="m_level_2"><a href="#tl70">Циклы</a></li>
      <li class="m_level_1"><a href="#0aoH">Испытания</a></li>
    </ul>
  </nav>
  <p id="9Z1Z"></p>
  <h2 id="I5PR">О препроцессорах</h2>
  <p id="6nUY">Препроцессор позволяет подойти к языку разметки или стилей как к языку программирования — с переменными, условиями, циклами и так далее. Это касается и PHP для HTML, который <a href="https://blog.arkhelvetios.ru/intro-php" target="_blank">мы разбирали</a> уже достаточно давно, и это же касается Less или других вариантов для CSS.</p>
  <p id="CWHR">Почему эти языки называются <u>пре</u>процессорными, я уже <a href="https://blog.arkhelvetios.ru/intro-to-web-development#6bxB" target="_blank">как-то давно писал</a> в самом начале про PHP, но если вкратце — это языки которые обрабатывает сервер и трансформирует его в понятный для браузера HTML и CSS.</p>
  <p id="utrI">В случае с Less мы по сути «программируем» стили, уменьшаем количество повторов и, тем самым, упрощаем себе работу. Меньше повторов, меньше времени на написание, больше логики и наглядности. Хотя по началу может показаться с точностью да наоборот.</p>
  <p id="ZV8M"></p>
  <h2 id="u9om">Возможности Less</h2>
  <p id="35A5">Итак, теперь мы не только описываем стили элементов, но и пытаемся построить логику этого описания. Синтаксис самой основы CSS не меняется — все правила, свойства и их значения, что мы разбирали до этого, остаются такими же. Less же, можно сказать, надстройка в виде дополнительных функций.</p>
  <p id="45IA"></p>
  <h3 id="5G4Y">Переменные</h3>
  <p id="eWgi">Первое, что можно сделать в Less — это <strong>объявить переменную</strong>:</p>
  <pre id="tjWp" data-lang="less">@main-color: red;
@cintainer-width: 150px;</pre>
  <p id="UfoB">Один раз объявив переменную, мы можем использовать её в любом месте кода, при этом, поменяв значение переменной — оно поменяется везде. Таким образом, сильно упрощается работа с большим объемом одинаковых параметров.</p>
  <pre id="MJAW" data-lang="less">.card-container {
  background-color: @main-color;
  width: @container-width; 
}

.description {
  color: @main-color;
}</pre>
  <p id="rI5g">Переменные можно объявлять даже внутри правил, таким образом они становятся локальными для этого правила и работают только внутри него, то есть использовать эту переменную в другом правиле не получится. </p>
  <p id="V8sP">С переменными Less работают стандартные <strong>правила области видимости</strong> — переменные объявленные вне правил являются глобальными и могут применятся везде, при этом они могут переопределяться на локальные значение:</p>
  <pre id="qt8f" data-lang="less">.card-container {
  @container-width: 100px;
  
  background-color: @main-color; /* будет red -глобальное значение */
  width: @container-width; /* будет 100px - локальное значение */
}

.description {
  @main-color: blue;
  
  color: @main-color; /* будет blue - локальное значение */
}</pre>
  <p id="EZ8C"></p>
  <h3 id="Z6d6">Математические операции</h3>
  <p id="ZaYR">Как и в других языках программирования, в переменные и свойства в Less можно записывать не только какие-то конечные значения — цвет, числовые значения, строки и т. д., но и различные комбинации, модификации, функции и так далее.</p>
  <p id="aLsL">Например, самый простой вариант — в Less работают <strong>математические операции</strong>:</p>
  <pre id="6cKM" data-lang="less">@cintainer-width: 150px;
@big-container-width: @container-width + 100; /* 250px */

@par-font-size: 16px;
@header-font-soze: @par-font-size * 1.5; /* 16 * 1.5 = 24 */

@cell-wudth: 50%;
@mini-cell-width: @cell-width / 2; /* 50% / 2 = 25% */</pre>
  <p id="LCVM">Два основных нюанса при написании математических операций в Less:</p>
  <ul id="65ff">
    <li id="vYBx">Вокруг операторов обязательно должны быть пробелы, иначе обработчик может воспринять их за часть строки.</li>
    <li id="aOOp">Единица измерения берётся из первого параметра.</li>
  </ul>
  <p id="EMaV">С числовыми значениями вполне всё очевидно, а вот с цветом не совсем — с ним тоже возможны математические операции, но операторы в любом случае будут работать с показателями RGB и влиять на все три цветовых показателя. </p>
  <p id="imwc">При этом неважно как мы зададим цвет — ключевым словом, HEX или как-то еще — операция будет с показателями RGB:</p>
  <pre id="9aqo" data-lang="less">@rgb-color: rgb(110, 27, 255);
@keyword-color: red; /* эвкивалент rgb(255, 0, 0) */
@hex-color: #aa3399; /* эквивалент rgb(170, 51, 153) */

@new-rgb-color: @rgb-color + 20; /* = rgb(130, 47, 255) – 255 максимум */
@new-keyword-color: @keyword-color + 20; /* = rgb(255, 20, 20) */
@new-hex-color: @hex-color + 20; /* = rgb(190, 71, 173) */</pre>
  <p id="Nrir"></p>
  <h3 id="ZDd3">Цветовые функции</h3>
  <p id="KTb4">Помимо математических операций в Less существуют и функции. Я уверен их достаточно много, но в тренажёре нас сначала знакомят в основном с цветовыми функциями, влияющими на тон, яркость и насыщенность. </p>
  <p id="4QCG">Первая на очереди <strong>функция поворота цветового тона</strong> <code>spin()</code>, которая очень похожа на <a href="https://blog.arkhelvetios.ru/css-filters-kekstagram#IuwB" target="_blank">CSS-фильтр</a> <code>hue-rotate()</code> и делает по сути тоже самое, но меняет именно значение конкретного цвета в свойстве, а не модифицирует уже отрисованный элемент.</p>
  <figure id="Fsuf" class="m_original">
    <img src="https://img1.teletype.in/files/09/d9/09d9972f-f834-42c5-9391-bc14b4feb656.png" width="393" />
  </figure>
  <p id="9Rd4">Функция принимает два аргумента — цвет или переменную с цветом и числовое значение угла поворота, причём без указания единицы измерений <code>deg</code>:</p>
  <pre id="wiD1" data-lang="less">@not-red: spin(red, 60); /* повернет на 60deg по часовой на диаграме */
@true-red: spin(@not-red, -60); /* повернёт обратно, против часовой */

.green-box {
  background-color: spin(@true-red, 120); /* 120deg от red - зелёный */
}</pre>
  <p id="Ofb0">Следующие две функции отвечают за <strong>уменьшение и увеличение яркости</strong> цвета — <code>darken()</code> и <code>lighten()</code> соответственно. Функции тоже принимают два аргумента, первый — это цвет или переменная с цветом, а вторая значение увеличения яркости в процентах <code>%</code>:</p>
  <pre id="dqS8" data-lang="less">@light-blue: lighten(blue, 50%);
@dark-blue: darken(blue, 50%);</pre>
  <p id="WRO7">Во втором аргументе указывается именно процент воздействия на цвет, а не положение на шкале яркости. То есть при 100% значении <code>darken()</code> получится чёрный цвет, а при 100% значении <code>lighten()</code> получится белый, при этом при значении 0% — цвет не изменится.</p>
  <p id="kgeA">И последние две функции из тренажёров отвечающие за <strong>уменьшение и увеличение насыщенности цвета</strong> — <code>desaturate()</code> и <code>saturate()</code> соответственно. Синтаксис у них такой же как и у яркости:</p>
  <pre id="fGmm" data-lang="less">@more-blue: saturate(blue, 30%);
@greyly-blue: desaturate(blue, 50%);
@grey: desaturate(blue, 100%); /* 100% desaturate = оттенки серого */</pre>
  <p id="KO0s">В первом аргументе всех этих функций можно использовать и сами функции, таким образом <strong>вкладывать</strong> их друг в друга:</p>
  <pre id="ouxh" data-lang="less">@dark-greyly-yellow: darken(desaturate(yellow, 50%), 30%);</pre>
  <p id="yhPy">Где всё это применимо? В тренажёре нам приводят в пример всплывающие подсказки и их стилизацию — мы задаём лишь один базовый цвет — фиолетовый, а всё остальное будь то цвет текста, фона и рамок даже разных сообщений мы выводим из базового используя вышеописанные функции.</p>
  <figure id="ibdw" class="m_original">
    <img src="https://img1.teletype.in/files/45/f9/45f9520d-a1a8-4010-a55a-34ed56f843c8.png" width="553" />
  </figure>
  <p id="vXVi">Таким образом меняя всего один параметр — базовый цвет, мы можем поменять палитру всех элементов. Используя эти инструменты по сути можно создавать целые «темы» интерфейса и менять их одним переключателем!</p>
  <p id="v13b"></p>
  <h3 id="QJRd">Вложенные правила</h3>
  <p id="3jPi">Следующие возможности препроцессора связаны с CSS-правилами. Самое простое для понимания — в Less <strong>CSS-правила можно вкладывать друг в друга</strong>:</p>
  <pre id="oZgh" data-lang="less">.card {
  color: @text-color;
  background-color: @bg-color;
  
  a {
    color: @link-color;
    text-decoration: none;
  }
}</pre>
  <p id="aAXd">Что в CSS преобразуется как:</p>
  <pre id="ov6Y" data-lang="less">.card {
  color: red; /* цвета условные */
  background-color: black;
}
.card a {
  color: blue;
  text-decoration: none;
}</pre>
  <p id="Ua6e">Такая древовидная вложенность в Less избавляет код от дублей и делает его более структурным, читаемым и наглядным.</p>
  <p id="4xfe">При работе с такой вложенной структурой в Less есть еще один механизм позволяющий обращаться непосредственно <strong>к родительскому селектору</strong> или к его подстроке:</p>
  <pre id="R3Ti" data-lang="less">.button {
  &amp;-submit {
    color: @btn-text-color;
    background-color: @btn-bg-color;
    
    &amp;:hover {
      background-color: lighten(@btn-bg-color, 30%);
    }
  }
  &amp;-cancel {
    background-color: desaturate(@btn-bg-color, 50%);
  }
}</pre>
  <p id="8F1L">Будет преобразовано в CSS как:</p>
  <pre id="cg7U" data-lang="css">.button-submit {
  color: white; /* цвета снова условные */
  background-color: red;
}

.button-submit:hover {
  background-color: lightred;
}

.button-cancel {
  background-color: #fa8072;
}</pre>
  <p id="OkLX">Символ амперсанда <code>&amp;</code> приравнивается к строке указанной в родительском селекторе и таким образом можно обращаться сразу к группе селекторов в своем пространстве имён. </p>
  <p id="bPOp">Также как и в примере, это используется при работе с псевдоклассами — иначе без подстановки <code>&amp;</code> перед <code>:hover</code> конечный селектор в CSS выглядел бы как <code>.button-submit :hover</code> — именно с пробелом.</p>
  <p id="liko"></p>
  <h2 id="bF7F">Примеси Less</h2>
  <p id="scty">Следующая важная особенность Less — примеси, возможность «примешивать» содержимое одного правила в другой. Если в Less записать:</p>
  <pre id="EyDO" data-lang="less">.big { width: 500px; }

.white { color: white; }

.block {
  .big;
  .white;
}</pre>
  <p id="wHQh">то в CSS преобразуется как:</p>
  <pre id="AQm7" data-lang="css">.big { width: 500px; }
.white { color: white; }
.block {
  width: 500px;
  color: white;
}</pre>
  <p id="u9YH">Это самый примитивный вариант, однако, примеси это целый новый уровень абстракции, позволяющий создавать по сути чуть ли не функции. </p>
  <p id="VjSw">Примесь в её более расширенном понимании — это <strong>комплексное Less-правило</strong>, которое в итоге не выводится в конечный CSS, а применяется для структурного построения и которому можно даже задать аргументы. </p>
  <p id="NlFv">На мой взгляд, примесь действительно похожа на функцию в обёртке CSS синтаксиса — выглядит она как обычный селектор со скобками:</p>
  <pre id="E85a" data-lang="less">.white() { color: white; } /* объявление примеси */
.text { .white(); }      /* применение примеси */</pre>
  <p id="Tnir">При этом в CSS примесь никак не выводится:</p>
  <pre id="HzvI" data-lang="css">.text { color: white; }</pre>
  <p id="EXkJ">Скобочки у примеси не просто так — на самом деле, в них можно передавать параметры, которые далее используются в примеси для её построения. Параметрам можно задать значение по умолчанию, в случае если при вызове примеси их не указали:</p>
  <pre id="g7CY" data-lang="less">.colors (@color) { /* параметр без значения по умолчанию */
  color: darken(@color, 30%);
  background-color: lighten(desaturate(@color, 20%) 20%);
}

.offset(@padding: 10px; @margin: 20px;) { /* со значениями по умолчанию */
  padding: @padding;
  margin: @margin;
}

.block {

  &amp;-1 {
    .colors(blue);
    .offset(5px; 10px); /* значения сопоставляются по порядку */
  }
  
  &amp;-2 {
    .colors(green);
    .offset();
  }
}</pre>
  <p id="UyvA">Результат в CSS:</p>
  <pre id="0osz" data-lang="css">.block-1 {
  color: darkblue;
  background-color: lightskyblue;
  padding: 5px; /* значения при вызове примеси */
  margin: 10px;
}

.block-2 {
  color: darkgreen;
  background-color: lightseagreen;
  padding: 10px; /* значения по умолчанию */
  margin: 20px;
}</pre>
  <p id="WkEn">Таким образом мы можем один раз задать какой-то набор свойств в виде примеси и в дальнейшем вызывать его в любом месте передавая только параметры. Особенно хорошо можно прочувствовать полезность примесей если поработать со свойствами типа <code>border-top-left-radius</code> и написать их хотя бы несколько раз в чистом CSS.</p>
  <p id="rNyj"></p>
  <h3 id="xUT3">Шаблон примеси</h3>
  <p id="nLeg">Можно пойти дальше и создать не просто какое-то количество разных примесей, а структурировать их и создать <strong>шаблоны для однотипных примесей</strong>. </p>
  <p id="n8zJ">Чтобы создать шаблон примеси, достаточно указать его имя в первом аргументе, перед параметрами примеси:</p>
  <pre id="6DcP" data-lang="less">.set-color(@color) { /* примесь */
  background-color: @color;
}
.set-font-size(lighten; @color) { /* шаблон примеси */
  background-color: lighten(@color, 50%);
}</pre>
  <p id="Ft0Y">Самый тривиальный пример — шаблонизировать примеси для текста. Мы можем задать какой-то один базовый размер и от него плясать в зависимости от применяемой области.</p>
  <pre id="vUU7" data-lang="less">@base-font-size: 16px;

.set-font-size(@size) {
  font-size: @size;
  line-height: @size * 1.5;
}

.set-font-size(small, @size) {
  font-size: @size / 2;
  line-height: @size / 2;
}

.set-font-size(big, @size) {
  font-size: @size * 2;
  line-height: @size * 2 * 1.2;
}

.promo-header {
  .set-font-size(big, @base-font-size);
}
.promo-description {
  .set-font-size(@base-font-size);
}
.promo-note {
  .set-font-size(small, @base-font-size)
}</pre>
  <p id="X4Ew">Результат в CSS:</p>
  <pre id="0sKr" data-lang="css">.promo-header {
  font-size: 32px; /* 16 * 2 = 32 */
  line-height: 38px; /* 16 * 2 * 1.2 = 32 * 1.2 = 38.4 */
}
.promo-description {
  font-size: 16px; 
  line-height: 24px; /* 16 * 1.5 = 24 */
}
.promo-note {
  font-size: 8px; /* 16 / 2 = 8 */
  line-height: 8px;
}</pre>
  <p id="Gix4">Таким образом, у нас появляется, своего рода, пространство имён для примесей — лучше для однотипных задач использовать примеси с шаблонами, чем несколько разных примесей с разными названиями.</p>
  <p id="3ovX">У шаблонов есть дополнительная фишка — <strong>универсальный шаблон</strong>, который применяет указанные в нём параметры для всех одноименных примесей. Это полезно в случае если в каком-то наборе шаблонов примесей применяется один и тот же неизменяемый или одинаково изменяемый для всех шаблонов параметр.</p>
  <p id="Niuf">Он обозначается зарезервированным именем <code>_@</code> и на примере шаблонов для текста выше, мы можем, скажем, задать везде одинаковый <code>font-weight</code>:</p>
  <pre id="OFUb" data-lang="less">.set-font-size(_@, @size) {
  font-weight: bold;
}</pre>
  <p id="WCad">В итоге всем элементам, где были применены указанные примеси и их шаблоны будет подставлен этот параметр.</p>
  <p id="9XJd"></p>
  <h3 id="JjQQ">Примесь с условием</h3>
  <p id="zJaU">Вот мы и дошли до «программируемой» части Less — условные конструкции с примесями! Логика, правда, немного отличается от привычной конструкции в языках программирования — мы не задаём условия «if/else», в которых пишем что будет происходить в их случае. </p>
  <p id="trD4">Я немного порылся в англоязычной документации Less и там эта конструкция называется «Mixin Guards», то бишь «Охрана примеси». Суть, в общем-то, практически не меняется — мы ставим <strong>условие выполнения самой примеси</strong> с помощью ключевого слова <code>when</code>:</p>
  <pre id="Hyvm" data-lang="less">.set-font-size(@size) when (@size &lt; 12px) {
  font-weigth: regular;
}</pre>
  <p id="g4S3">Примесь выше сработает только если передаваемое значение, в нашем случае это размер шрифта, окажется меньше 12px.</p>
  <p id="ZluO">В условие в скобках можно помещать как параметры самой примеси, так и внешние факторы, например, соответствие каких-то внешних переменных, причем их соответствие проверяется в момент вызова примеси:</p>
  <pre id="OiKP" data-lang="less">.set-font-color(@color) {
  color: @color;
}

.set-font-color(@color) when (@bg-color = black) {
  color: lighten(@color, 50%);
}

.first-text {
  .set-font-color(red); /* сработает безусловная примесь */
}

@bg-color: black; /* теперь условие корректно */

.second-text {
  .set-font-color(red); /* сработает условная примесь */
}</pre>
  <p id="IgFY">В итоге в CSS получится:</p>
  <pre id="F4VA" data-lang="css">.first-text {
  color: red; /* безусловная примесь */
}

.second-text {
  color: lightred; /* примесь с условием */
}</pre>
  <p id="Sv9k">Помимо этого, в условии или в «охране», можно использовать функции для <strong>проверки типа</strong> передаваемых в примесь данных. Грубо говоря, строго типизировать нашу примесь:</p>
  <pre id="IyQM" data-lang="less">.mixin(@param) when (isnumber(@param)) { … }</pre>
  <p id="6fbw">Функций проверки не так уж и много:</p>
  <ul id="oX9D">
    <li id="Lpaf"><code>isnumber()</code> — из примера выше, проверяет значение — любое числовое значение — просто число, пиксели, проценты и так далее, главное исчисляемое значение.</li>
    <li id="e0iX"><code>iscolor()</code> — проверка значение — цвет, любой вариант записи — ключевое слово, HEX, RGB и так далее;</li>
    <li id="e6BW"><code>isstring()</code> — проверка значение — строка, то есть значения свойств обёрнутые в кавычки <code>&quot;...&quot;</code> — различные названия шрифтов, например;</li>
    <li id="MfFY"><code>iskeyword()</code> — проверка значение — ключевое слово, имеются ввиду ключевые слова в значениях свойств, кроме цветовых — <code>right</code>, <code>center</code>, <code>middle</code> и так далее;</li>
    <li id="EAhL"><code>isurl()</code> — проверка значение — функция <code>url()</code>, то есть проверка на значение-ссылку.</li>
  </ul>
  <p id="tQq6">Есть еще несколько дополнительных, проверяющих конкретные единицы измерения — <code>ispixel()</code>, <code>ispercentage()</code> и <code>isem()</code>. Там еще есть парочку, но про них нет смысла пока писать.</p>
  <p id="6WNs"></p>
  <h3 id="oh3y">Вставки</h3>
  <p id="ATVE">В Less примеси — это целые правила, которые можно примешать к другим правилам. Но есть и возможность подставлять какие-то отдельные куски правил, свойств, значений, да в общем-то чего угодно. Называется это «вставка» или <strong>интерполяция переменных</strong>.</p>
  <p id="IC5e">В переменную мы можем записать по сути что угодно — число, строку, ключевое слово даже часть названия селектора. То есть, переменную не обязательно использовать только как носитель чего-то значимого с точки зрения CSS, это может быть и часть структуры Less.</p>
  <p id="8TEX">Дальше чтобы значение этой переменной подставить в нужном месте используется фигурные скобки вокруг имени — <code>@{var}</code>:</p>
  <pre id="eknN" data-lang="less">@state: success;
@property: color;
@icon: &quot;question&quot;;

.btn-@{state} { /* как часть селектора */
  background-color: green;
}
.btn-error {
  background-@{property}: red; /* как часть свойства */
}
.btn-help {
  background-image: url(&quot;/img/icons/@{icon}.png&quot;); /* как часть url */
}</pre>
  <p id="rAHR">Результат в CSS:</p>
  <pre id="mxah" data-lang="css">.btn-success {
  background-color: green;
}
.btn-error {
  background-color: red;
}
.btn-help {
  background-image: url(&quot;/img/icons/question.png&quot;);
}</pre>
  <p id="YYf2">Еще одна особенность Less, про которую упомянули в этом уроке, но которая на мой скромный взгляд очень важна — это своеобразное <strong>экранирование</strong> или такой режим строки, содержимое которой при вызове не изменяется, то есть игнорируется весь Less синтаксис кроме интерполяций.</p>
  <p id="9FoW">Задаётся такая строка с помощью кавычек и знака тильды <code>~&quot;...&quot;</code>, это позволяет записывать в строку, а как следствие в переменную или в значение целые выражения и даже свойства с подставляемым значением:</p>
  <pre id="KTvD" data-lang="less">@base-size: 10;
@base-color: red;
@default-border: ~&quot;@{base-size}px solid&quot;; /* интерполяция */
/* почему-то подкрашено в другой цвет, но это тоже переменная */

.border-colored(@color) {
  border: @default-border @color;
  }

.element {
  .border-colored(@base-color);
  }</pre>
  <p id="TtYt">Пример прям конкретно говнокодерский, но мне что-то адекватное в голову никак не приходит, но я почему-то уверен, что область применений экранирования очень большая. В CSS выводится вот что:</p>
  <pre id="W4iX" data-lang="css">.element {
  border: 10px solid red;
}</pre>
  <p id="gQIi">Без интерполяции значения в экранированную строку переменная с числом не «склеивается» корректно с единицами измерений. То есть <code>10</code> и <code>px</code> не объединяются в одну сущность. Хотя, судя по англоязычной документации — это постепенно исправляется и много в каких случаях экранирование уже не нужно.</p>
  <p id="HuRA"></p>
  <h3 id="tl70">Циклы</h3>
  <p id="7PDm">Напоследок разберёмся — можно ли в Less делать циклы? Ответ — можно, но встроенных решений в Less для этого нет. В тренажёре описывается хитрый способ создания цикла с помощью примеси с условием и интерполяцией.</p>
  <p id="A6YO">Я не зря писал, что примеси похожи на функции и их как и функцию можно рекурсивно зациклить используя аргумент как счётчик:</p>
  <pre id="Dyq5" data-lang="less">.mixin(@n) {
  .mixin(@n + 1); /* приращение */
}
.mixin(1);</pre>
  <p id="JqWm">Чтобы цикл был не бесконечный можно использовать примесь с условием, в котором указать момент завершения цикла, например достижение счётчиком определенного значения:</p>
  <pre id="uowA" data-lang="less">.mixin(@n) when (@n =&lt; 3) { /* при @n &gt; 3 примесь не выполняется */
   .mixin(@n + 1);
}
.mixin(1);</pre>
  <p id="KWN9">Вполне себе цикл, который можно использовать, например, для создания селекторов с одинаковым префиксом, но разными суффиксами, используя интерполяцию значения счётчика:</p>
  <pre id="Y81o" data-lang="less">.mixin(@n) when (@n =&lt; 3)
   .block-@{n} {
     ...
   }
   
   .mixin(@n + 1);
}
.mixin(1);</pre>
  <p id="YsZO">Что в CSS преобразуется как:</p>
  <pre id="xXgA" data-lang="css">.block-3 {...}
.block-2 {...}
.block-1 {...}</pre>
  <p id="8yQQ"></p>
  <h2 id="0aoH">Испытания</h2>
  <p id="yRCq">Решил вывести их отдельно для обеих частей блока. Все они по сути об одном, плюс я немного переформатировал повествование.</p>
  <p id="g3ub">Первое испытание на цветовые функции — у нас уже есть некоторые области с цветами, а отталкиваясь от них нужно заполнить остальные:</p>
  <figure id="h88A" class="m_column">
    <img src="https://sun9-56.userapi.com/impf/oUT2YqCO90Bh7TmX88bAiN8jkfyV3YWAB6TW_A/9AfSmKjGyyY.jpg?size=1322x924&quality=96&sign=aaa5bb39805fcddcb1e67e7df99cdda8&type=album" width="1322" />
  </figure>
  <p id="25hG">Следующее два испытания на понимание работы примесей и их параметров, как со значениями по умолчанию так и без. Плюс, немного вспоминаем про двумерные трансформации:</p>
  <figure id="dO3t" class="m_column">
    <img src="https://sun9-19.userapi.com/impf/UASK09IXVlA9nuS4JpeUCFER-qF27SR9hLfpxg/DZXgXsy_M8k.jpg?size=1319x920&quality=96&sign=fe85b34fcd963b85ca3b7aaae3e507e1&type=album" width="1319" />
  </figure>
  <p id="M4JS">Третье испытание модифицируется шаблонами примесей:</p>
  <figure id="oM64" class="m_column">
    <img src="https://sun9-82.userapi.com/impf/FPZeQruZdFKQId4KD7wYBF_bPC-h5GpfvFLhAA/JwnZzUBUeFE.jpg?size=1318x921&quality=96&sign=5ddc1a76f849c4b2a398f560779dfcb1&type=album" width="1318" />
  </figure>
  <p id="mm98">Четвёртое испытание полностью на понимание работы условных примесей — нужно расставить существующим примесям условия, чтобы они срабатывали только для определённых элементов:</p>
  <figure id="NKz8" class="m_column">
    <img src="https://sun9-70.userapi.com/impf/d1FQAD-QEyyzimXMma0tgBMOAa_aYb3mskIpPQ/yET_9mnAhow.jpg?size=1321x922&quality=96&sign=879e20ff3c83b5b8da8cce769c517974&type=album" width="1321" />
  </figure>
  <p id="27lP">Пятое испытание на создание цикла с использованием интерполяции. Не уверен, что решение того требовало, но я еще использовал и экранирование:</p>
  <figure id="t6ov" class="m_column">
    <img src="https://sun9-4.userapi.com/impf/Xu5MLLMxEhqb8b5BoXEHd_jjQaV-11ELVz_oUA/yPQ9V2keHEI.jpg?size=1321x922&quality=96&sign=722866a2ba6ce3ddcde76a5e4797afb4&type=album" width="1321" />
  </figure>
  <p id="5TbS">Последнее испытание комбинирует всё пройденное — все примеси созданы, но не применены к элементам, нужно все корректно склеить:</p>
  <figure id="lMuD" class="m_column">
    <img src="https://sun9-29.userapi.com/impf/LmCySx3Mz7hcd-CFUcMMqu71vys8Yq26xb7wxg/7nsimu4pIJo.jpg?size=1319x922&quality=96&sign=d098805bc7d11a6b6f9a0cd3f69eaac9&type=album" width="1319" />
  </figure>
  <p id="9dAO"></p>
  <hr />
  <p id="CmUm"></p>
  <p id="HQIr">Не самый простой был блок, но очень интересный! У меня, правда, по ходу прохождения возникло много вопросов — например, пресеты цветов можно явно использовать в динамическом изменении цветовой схемы. Как оно будет работать — нужно всё-таки инициировать обновление страницы, чтобы сработал препроцессор и пересобрал все? Или можно как-то с помощью JS это все настроить в динамике в моменте?</p>
  <p id="7FlZ">Еще я понял, что тут ну прям о-очень небольшая часть от Less, гуляя по документации обнаружил много интересных штук. Так что к Less я еще вернусь, но у же в рамках чего-то другого.</p>
  <p id="FXah">А вам большое спасибо за внимание! Желаю вам пережить эту жару с максимальным комфортом!</p>
  <p id="86dx">Ссылки на мои социальные сети, там анонсы постов, некоторые мои короткие записи, мыслишки и всякое на отвлечённые темы:</p>
  <section>
    <p id="kfMs" data-align="center"><a href="https://t.me/arkhelvetios" target="_blank">Telegram</a> — <a href="https://twitter.com/arkhelvetios" target="_blank">Twitter</a> — <a href="https://www.instagram.com/arkhelvetios/" target="_blank">Instagram</a> — <a href="https://vk.com/arkhelvetios" target="_blank">VK</a></p>
  </section>
  <p id="qKEx">Заходите куда удобно вам и подписывайтесь! Еще раз спасибо за внимание!</p>
  <tt-tags id="eUyi">
    <tt-tag name="css">#css</tt-tag>
    <tt-tag name="css3">#css3</tt-tag>
    <tt-tag name="less">#less</tt-tag>
    <tt-tag name="frontend">#frontend</tt-tag>
    <tt-tag name="front_end">#front_end</tt-tag>
    <tt-tag name="обучение">#обучение</tt-tag>
    <tt-tag name="it">#it</tt-tag>
  </tt-tags>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@helvetios/transition-and-animation</guid><link>https://teletype.in/@helvetios/transition-and-animation?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=helvetios</link><comments>https://teletype.in/@helvetios/transition-and-animation?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=helvetios#comments</comments><dc:creator>helvetios</dc:creator><title>Плавные переходы, Кривые Безье и CSS-анимация — Тренажёр HTML Academy</title><pubDate>Wed, 29 Jun 2022 08:26:20 GMT</pubDate><media:content medium="image" url="https://img3.teletype.in/files/ec/d9/ecd96d7b-8d82-4d86-a86a-33166dfebd38.png"></media:content><category>Веб-разработка</category><tt:hashtag>html</tt:hashtag><tt:hashtag>css</tt:hashtag><tt:hashtag>html5</tt:hashtag><tt:hashtag>css3</tt:hashtag><tt:hashtag>frontend</tt:hashtag><tt:hashtag>front_end</tt:hashtag><tt:hashtag>обучение</tt:hashtag><tt:hashtag>it</tt:hashtag><description><![CDATA[<img src="https://img4.teletype.in/files/b8/fd/b8fd3350-ca19-4695-bcd9-53c21c674227.png"></img>Наконец, я добрался до анимации в CSS! Для начала вспомним про трансформации, разберёмся как сделать их плавными и затем перейдем уже к анимации по ключевым кадрам!]]></description><content:encoded><![CDATA[
  <figure id="p5Ah" class="m_column">
    <img src="https://img4.teletype.in/files/b8/fd/b8fd3350-ca19-4695-bcd9-53c21c674227.png" width="1200" />
  </figure>
  <p id="fBKy">Наконец, я добрался до анимации в CSS! Для начала вспомним про трансформации, разберёмся как сделать их плавными и затем перейдем уже к анимации по ключевым кадрам!</p>
  <p id="jhXK">Кстати, я не прогадал, когда решил пройти сначала <a href="https://blog.arkhelvetios.ru/css-tricks-part1" target="_blank">Тонкости</a> — в части про переходы мы будем по полной эксплуатировать тему с <code>:checked</code> и <code>~</code> селектором. Вообще, появилась у меня мыслишка запилить что-нибудь с использованием этого инструмента. Просто for fun и ради, своего рода, тренировки.</p>
  <p id="2dmo"></p>
  <nav>
    <ul>
      <li class="m_level_1"><a href="#GFMg">Плавные переходы</a></li>
      <li class="m_level_2"><a href="#Rvtn">Свойства перехода</a></li>
      <li class="m_level_2"><a href="#d16h">Функция перехода</a></li>
      <li class="m_level_2"><a href="#P2BI">Мини-мастерская переходов</a></li>
      <li class="m_level_1"><a href="#hy1S">CSS Анимация</a></li>
      <li class="m_level_2"><a href="#jvu8">Ключевые кадры</a></li>
      <li class="m_level_2"><a href="#c0Ei">Свойства анимации</a></li>
    </ul>
  </nav>
  <p id="TERu"></p>
  <h2 id="GFMg">Плавные переходы</h2>
  <p id="ebCu">Мы немного затрагивали плавные переходы, когда разбирали какие бывают <a href="https://blog.arkhelvetios.ru/position-and-transform#ZPgz" target="_blank">трансформации</a>, но по умолчанию, все они происходят мгновенно. Точнее трансформацией <code>transform</code> мы лишь задавали разные свойства состояния — у элемента их может быть несколько, например, базовое и <code>:hover</code>. </p>
  <p id="Mm3F">Переход же между этими состояниями мгновенен и за плавность или, как написали в тренажёре, «нежность» перехода из одного состояния элемента в другой отвечает семейство свойств <code>transitions</code>.</p>
  <p id="PIJ3"></p>
  <h3 id="Rvtn">Свойства перехода</h3>
  <p id="dkT0">Первое и самое важное свойство, без которого вообще не работает плавность переходов — свойство <strong>длительности перехода</strong> <code>transition-duration</code>. Оно принимает числовое значение в секундах <code>s</code> или миллисекундах <code>ms</code>:</p>
  <pre id="Ww3D" data-lang="css">transition-duration: 0.3s; 
transition-duration: 300ms; </pre>
  <p id="OY1a">По умолчанию, свойства семейства <code>transitions</code> принимают во внимание все свойства, которые можно плавно изменить или анимировать — это размерные, цветовые и позиционные свойства. Чтобы контролировать <strong>какой конкретно параметр</strong> нужно изменять, используется свойство <code>transition-property</code>.</p>
  <p id="kuCb">По умолчанию имеет значение <code>all</code>, то бишь все, в иных случаях принимает буквально названия CSS-свойств на которые будет распространяться плавный переход и его параметры. Можно задавать сразу несколько параметров через запятую — в этом плане работает схема из <code>background</code>, значения сопоставляются по их позиции:</p>
  <pre id="G2rX" data-lang="css">transition-property: width, height;
transition-duration: 1s, 5s; /* ширина меняется за 1s, высота за 5s */</pre>
  <p id="GYBX">Следующее полезное свойство — <strong>задержка перехода</strong> <code>transition-delay</code>. Как и длительность принимает значение секунд <code>s</code> и миллисекунд <code>ms</code>. Критично необходимое свойство при создании плавной анимации интерфейсов, хотя для новичков может показаться иначе.</p>
  <p id="8ZWA"></p>
  <h3 id="d16h">Функция перехода</h3>
  <p id="esZe">Последнее свойство супер полезное, но и достаточно сложное для понимания — это свойство <strong>функции перехода</strong> <code>transition-timing-function</code>. Все переходы помимо длительности имеют, можно сказать, «скорость» перехода — она может быть постоянной или «линейной», а может быть более сложной, например, замедляться в начале или в конце.</p>
  <p id="MdIT">Отношение времени к изменению состояния можно, условно, записать в функцию, а построив график этой функции можно наглядно посмотреть как будет вести себя элемент. Именно эту функцию принимает за значение свойство <code>transition-timing-function</code>.</p>
  <figure id="cDGZ" class="m_original">
    <img src="https://img1.teletype.in/files/09/1e/091e952c-3ce3-440a-acc0-59061f2acdf2.png" width="543" />
  </figure>
  <p id="n6Ex">Но не стоит пугаться математики! Во-первых, математика проще чем думает большинство, но об этом как-нибудь в другой раз. Во-вторых, для нашего свойства есть заготовки функций в виде ключевых слов:</p>
  <ul id="g5pb">
    <li id="vg7K"><code>ease</code> — значение по умолчанию для всех переходов, немного замедляется в начале, резкий переход в середине и плавное замедление к концу;</li>
    <li id="GQep"><code>linear</code> — линейный, равномерный переход, без ускорений и замедлений;</li>
    <li id="gHNK"><code>ease-in</code> — слегка замедляется на старте;</li>
    <li id="T69U"><code>ease-out</code> — слегка замедляется в конце;</li>
    <li id="1Q8d"><code>ease-in-out</code> — похож на <code>ease</code>, но имеет более резкий переход в середине.</li>
  </ul>
  <figure id="Hk9s" class="m_original">
    <img src="https://img2.teletype.in/files/dc/5b/dc5bd896-d6ea-48b9-9a72-6556b2b5255a.png" width="810" />
  </figure>
  <p id="pDAF">На самом деле все эти функции являются упрощёнными образами функций кубических <a href="https://ru.wikipedia.org/wiki/%D0%9A%D1%80%D0%B8%D0%B2%D0%B0%D1%8F_%D0%91%D0%B5%D0%B7%D1%8C%D0%B5" target="_blank">Кривых Безье</a>. Свойство <code>transition-timing-function</code> может принимать значение в виде самой функции <code>cubic-bezier(x1, y1, x2, y2)</code>, где аргументами являются координаты опорных точек.</p>
  <p id="kZFX">Что за опорные точки? Те кто хотя бы раз в жизни работал с векторной графикой в каком-нибудь Adobe Illustrator уже знают про кривые Безье, хотя могут об этом и не догадываться. Зато опорные точки они точно видели.</p>
  <figure id="w1R8" class="m_original">
    <img src="https://img2.teletype.in/files/9a/d0/9ad06117-fb6b-492c-95be-58e7e452668a.png" width="499" />
    <figcaption>Это из Иллюстратора</figcaption>
  </figure>
  <p id="KKbZ">Итак, разбираемся. Обычный отрезок состоит из двух точек — начало и конец отрезка. У этих точек могут быть свои координаты, но для упрощения сделаем начало в нуле <code>0</code>, а конец в единице по обеим осям <code>1, 1</code>. </p>
  <p id="hFwE">Получается прямая линия, как на графике с линейной <code>linear</code> функцией (см. выше), но нас больше интересуют кривые. Как нам их деформировать? Нужно добавить больше точек! Точнее две — по одной для начала и для конца. </p>
  <p id="5zYj">Соединив эти опорные точки с началом и концом мы получим ломанную кривую, очень отдалённо похожую на то, что нам надо:</p>
  <figure id="mz7o" class="m_original">
    <img src="https://img1.teletype.in/files/c5/97/c597f778-5d09-4b43-8e78-e3a4c99b5e97.png" width="627" />
  </figure>
  <p id="L26e">Плавной её делает уже «магия» математики, вдаваться в её подробности не будем, но всю эту магию несёт в себе сама функция <code>cubic-bezier()</code>. В любимой Википедии отлично показывается как строится плавная кривая из ломанной, если кому-то интересно.</p>
  <figure id="9ZvO" class="m_original">
    <img src="https://upload.wikimedia.org/wikipedia/commons/d/db/B%C3%A9zier_3_big.gif" width="360" />
  </figure>
  <p id="UuhY">Наша функция <code>cubic-bezier()</code> в аргументы принимает координаты опорной точки начала — <code>x1</code> по горизонтали, <code>y1</code> по вертикали, и аналогично опорной точки конца — <code>x2</code> и <code>y2</code>. </p>
  <p id="wb60">Координаты могут совпадать, как друг с другом, так и с крайними точками. Например, линейная <code>linear</code>, формально, тоже имеет опорные точки, просто их координаты совпадают с началом и концом:</p>
  <pre id="2OQV" data-lang="css">cubic-bezier(0, 0, 1, 1)</pre>
  <p id="9Ob3">У <code>ease-in</code> и <code>ease-out</code> совпадают координаты одной из опорных точек:</p>
  <pre id="ZkFi" data-lang="css">cubic-bezier(0.42, 0, 1, 1) /* ease-in */
cubic-bezier(0, 0, 0.58, 1) /* ease-out */</pre>
  <p id="MuBo">Естественно, на эту тему есть уже много сервисов, одно из них рекомендует Академия — <a href="https://cubic-bezier.com/" target="_blank">https://cubic-bezier.com/</a> — в нём можно поиграться с опорными точками и построить ту функцию, которая вам нужна. Другой вопрос, что в 90% случаях хватает <code>ease</code> и её производных, но для оставшихся 10% пригодиться!</p>
  <p id="adil">На последок — у свойства <code>transition-timing-function</code> есть еще одно значение! Это тоже функция — <code>steps()</code>, она преобразует переход в пошаговый. Она принимает два аргумента, первый — количество шагов, а второй — одно из двух ключевых слов <code>start</code> или <code>end</code>. </p>
  <p id="bjbm">При заданном <code>start</code> первый шаг выполняется одновременно с началом перехода, а в случае c <code>end</code> последний шаг будет выполнен вместе с завершением перехода. Грубо говоря, указывается под что подстроится функция.</p>
  <p id="bzY6"></p>
  <h3 id="P2BI">Мини-мастерская переходов</h3>
  <p id="24lC">Больше половины части занимают именно примеры, так как свойств на самом деле не много. Я сначала хотел их пропустить, но решил, что хотя бы упомяну, так как сделано реально интересно и делаем прикольные штуки.</p>
  <p id="Fk99">Первым делом мы сделали самое очевидное — кнопочки. Смена происходит через добавление класса с помощью JS. Добавили еще эффект расширения тени, который, на мой взгляд, сделан хреновенько:</p>
  <figure id="hVAH" class="m_original">
    <img src="https://img4.teletype.in/files/bd/29/bd29ccfb-2303-48cd-bef2-0e38cd03f94e.gif" width="300" />
  </figure>
  <p id="4ogF">Следом сделали красивые чекбоксы. Здесь и далее уже используется <code>:checked</code> и селектор <code>~</code> для стилизации псевдоэлементов выбранных пунктов. Сама идея с галочкой из поля прикольная, но явно требует доработать момент возвращения галочки в состояние пустого квадрата, а то стороны начинают появляться раньше чем надо:</p>
  <figure id="BKNy" class="m_original">
    <img src="https://img4.teletype.in/files/f8/ce/f8ce3f6d-ed63-4d50-9102-36fc0a006531.gif" width="200" />
  </figure>
  <p id="dNP8">Далее, конечно же, потренировались на радиокнопках, тут всё ещё проще — два псевдоэлемента, один перекрывает другой при выделении, анимирована <code>scale()</code> трансформация:</p>
  <figure id="ZIch" class="m_original">
    <img src="https://img4.teletype.in/files/36/cb/36cb516f-6338-489f-8b7e-85dae18f019c.gif" width="204" />
  </figure>
  <p id="2jhW">Следом сделали переключатели — их достаточно большое количество в интерфейсах. Здесь мы сделали их через чекбоксы — один псевдоэлемент это линия, а второй анимируемый ползунок, простой translate со сменой цвета:</p>
  <figure id="24kq" class="m_original">
    <img src="https://img2.teletype.in/files/19/93/199307c2-e396-436e-afff-c1374fb321ba.gif" width="232" />
  </figure>
  <p id="PqEC">Следующим мы сделали так называемый «бургер» <s>и точка</s> меню, обычно такие делают в мобильных приложениях или в мобильных версиях сайта. Честно скажу, я до этого делал их тупо через смену картинок, не приходило в голову делать через элементы и псевдоэлементы. А подобные анимации, я думал, делаются исключительно через JS, но нет! </p>
  <figure id="oq4f" class="m_original">
    <img src="https://img2.teletype.in/files/59/80/5980a51b-bf1c-47db-a7fb-45a5dd5321fd.gif" width="192" />
  </figure>
  <p id="fPqy">И на последок поигрались с полями формы. Поле формы тоже одно из самых «труднодоступных» для стилизации элементов. Здесь мы используем <code>&lt;label&gt;</code> как <code>placeholder</code> и псевдоэлементы как подчеркивания:</p>
  <figure id="Pgzo" class="m_original">
    <img src="https://img4.teletype.in/files/7a/20/7a2065b2-775a-434c-8e1c-b17a091526bb.gif" width="352" />
  </figure>
  <p id="aQvF"></p>
  <h2 id="hy1S">CSS Анимация</h2>
  <p id="yolL">Мы можем управлять стилями некоторых состояний одного элемента, например, выбранным <code>:checked</code>, наведённым <code>:hover</code>, ну и базовым. Выше мы разобрали как сделать плавный переход между этими заготовленными состояниями, то есть между двумя стилями, с помощью семейства свойств <code>transition</code>.</p>
  <p id="4xS2">И хотя сами по себе переходы очень крутой инструмент и решает, наверное, большую часть задач анимации в интерфейсах, всё же мы можем пойти дальше — с помощью CSS <strong>создавать сложные анимации</strong> с большим количеством состояний одного элемента и переходов между ними. За это отвечает семейство свойств <code>animation</code>.</p>
  <p id="T0UX">Сама анимация в CSS состоит из двух частей — функция самой анимации или, как её назвали в тренажёре, <strong>набор ключевых кадров</strong> <code>@keyframes</code> и самих свойств семейства <code>animation</code>, которые применяются к элементам.</p>
  <p id="Kj6n"></p>
  <h3 id="jvu8">Ключевые кадры</h3>
  <p id="HUCH">Сначала разберёмся с ключевыми кадрами <code>@keyframes</code> — если сильно упростить описание этой конструкции, то с её помощью мы задаём состояния элемента — ключевые кадры, и стили этих состояний в нужный момент анимации:</p>
  <pre id="GGID" data-lang="css">@keyframes stretching { /* название функции-анимации */
  0% { /* состояние в начале */
    width: 100px;
  }
  100% { /* состояние в конце */
    width: 200px;
  }
}</pre>
  <p id="mKFe">Конструкция похожа, на мой взгляд, на объявление какой-либо функции из любого C-подобного языка, что недалеко от правды. Мы объявляем, что функция <code>stretching</code> — это анимация, которая изменит элемент от состояния ключевого кадра <code>0%</code>, до состояния ключевого кадра <code>100%</code>.</p>
  <p id="eRMI">Имя анимации можно задавать любое, главное помнить, что оно чувствительно к регистру. Ключевые кадры обозначаются либо процентами, либо есть два ключевых слова — <code>from</code>, что по сути равно <code>0%</code> и <code>to</code>, что равно <code>100%</code>.</p>
  <pre id="j8Sq" data-lang="css">@keyframes square-move {
  from   {top: 0px; left: 0px; background: red;}
  25%  {top: 0px; left: 100px; background: blue;}
  50%  {top: 100px; left: 100px; background: yellow;}
  75%  {top: 100px; left: 0px; background: green;}
  to {top: 0px; left: 0px; background: red;}
}</pre>
  <figure id="Df3z" class="m_original">
    <img src="https://img3.teletype.in/files/e4/43/e44303e8-3555-4c58-a7c1-5583cfbfc62f.gif" width="213" />
  </figure>
  <p id="Dajy">Кадры можно группировать, если какое-то состояние одинаково для двух моментов анимации. Это используется в разных целях, например, анимацию <code>square-move</code> можно немного сократить:</p>
  <pre id="Lz0X" data-lang="css">@keyframes square-move {
  from, to   {top: 0px; left: 0px; background: red; } /* или 0%, 100% */
  25%  {top: 0px; left: 100px; background: blue;}
  50%  {top: 100px; left: 100px; background: yellow;}
  75%  {top: 100px; left: 0px; background: green;}
}</pre>
  <p id="194O">При этом никакой ошибки не будет — мы описываем состояния, их порядок указания лишь вопрос читабельности кода. Если таким образом сгруппировать два состояния, между которыми нет других ключевых кадров, то анимация приостановится на пропорциональное количество времени:</p>
  <pre id="eU7X" data-lang="css">@keyframes square-move-stop {
  from, to   {top: 0px; left: 0px; background: red;}
  40%, 60%  {top: 0px; left: 100px; background: blue;}
  /* между 40% и 60% нет ключевых кадров - их состояние одинаково */
}</pre>
  <figure id="4HmM" class="m_original">
    <img src="https://img1.teletype.in/files/4a/4e/4a4ef2f2-2c53-4ee6-97e9-e67d2934a590.gif" width="220" />
  </figure>
  <p id="k3Uc">Итак, создали мы крутой набор ключевых кадров, что с ним дальше делать? </p>
  <p id="Lxy1"></p>
  <h3 id="c0Ei">Свойства анимации</h3>
  <p id="OebS">Переходим к свойствам семейства <code>animation</code>! Они во многом очень похожи на свойства <code>transition</code> с небольшими дополнениями.</p>
  <p id="b5pA">Первое свойство, собственно, <strong>применяет набор ключевых кадров</strong> к элементу — это свойство <code>animation-name</code>. Оно принимает имя заготовленного набора ключевых кадров или, иными словами, вызывает функцию анимации.</p>
  <p id="Low2">Второе свойство, без которого анимация работать не будет, это свойство <strong>длительности анимации</strong> <code>animation-duration</code>. Как и его брат-близнец из переходов <code>transition-duration</code>, принимает числовое значение в секундах <code>s</code> или миллисекундах <code>ms</code>.</p>
  <p id="ZP8G">Без этих двух свойств анимация элементарно не применится, они обязательны. Одному элементу можно задать сразу <strong>множество наборов ключевых кадров</strong>, они указываются в <code>animation-name</code> через запятую, а все остальные параметры в свойствах, например тот же <code>animation-duration</code>, сопоставляются по порядку:</p>
  <pre id="hSiF" data-lang="css">.element {
  animation-name: move, stretch; 
  animation-duration: 2s, 6s; /* 2s длится move, 6s stretch */
}</pre>
  <p id="MjZy">Еще два свойства-родственника из переходов <code>transition</code> — это <strong>свойство задержки анимации</strong> <code>animation-delay</code> и свойство задающее <strong>функцию перехода</strong>, но уже для всей анимации <code>animation-timing-function</code>.</p>
  <p id="uvf2">Про задержку, я думаю, и так понятно, с ней, кстати, тоже работают правила множественных значений. Функция перехода тоже работает похожим образом — такие же значения, типа <code>ease</code>, <code>linear</code> и т. д., и тоже можно применять функции <code>cubic-bezier()</code> и <code>steps()</code>. </p>
  <p id="KRMV">Немного, возможно, не очевидный нюанс — функция перехода заданная в <code>animation-timing-function</code> применяется ко всем переходам между ключевыми кадрами, а не к прогрессу анимации в целом. То есть каждая анимация между ключевыми кадрами будет по умолчанию <code>ease</code>.</p>
  <p id="YVRV">Переходим к уникальным для анимации свойствам. Первое такое — свойство <strong>количества итераций</strong> или проигрывания анимации <code>animation-iteration-count</code>. Принимает либо число итераций, либо ключевое слово <code>infinite</code>, обозначающее бесконечное проигрывание анимации.</p>
  <p id="z2CF">Свойство задержки <code>animation-delay</code>, кстати, применяется только один раз до начала всей анимации, то есть при повторении никакой задержки не будет. Нужно это иметь ввиду, если задержка критична для каждой итерации.</p>
  <p id="96xh">Следующее свойство отвечает за <strong>направление анимации</strong> <code>animation-direction</code>. По умолчанию оно «прямое», то есть анимация выполняется от <code>0%</code> до <code>100%</code> и свойство имеет значение <code>normal</code>. Другие же значения:</p>
  <ul id="vD86">
    <li id="9dM8"><code>reverse</code> — «разворачивает» анимацию, она начинает выполняться в обратную сторону — со <code>100%</code> до <code>0%</code>, при этом функция перехода НЕ разворачивается.</li>
    <li id="CdGd"><code>alternate</code> — разворачивает каждую чётную итерацию, таким образом помогает зациклить анимацию без «разрывов» в состояниях элемента.</li>
    <li id="hAEi"><code>alternate-reverse</code> — разворачивает каждую нечётную итерацию.</li>
  </ul>
  <p id="3jMg">По умолчанию, после завершения анимации элемент возвращается в своё исходное состояние. Этим, естественно, можно управлять и с помощью комбинаций вышеописанных свойств, но конкретно за это отвечает свойство <code>animation-fill-mode</code>, которое явно указывает на <strong>состояние элемента после выполнения анимации</strong>, пусть даже с одной итерацией.</p>
  <p id="oQJ4">Имеет всего четыре значения, включая значение по умолчанию:</p>
  <ul id="tYF4">
    <li id="4qp6"><code>none</code> — по умолчанию, после завершения анимации элемент <em>вернётся в исходное состояние</em>, которое может отличаться от ключевого кадра <code>0%</code> или <code>from</code> — это важно, это не равнозначные состояния.</li>
    <li id="a3Rp"><code>frowards</code> — после завершения анимации элемент останется в состоянии <em>последнего момента анимации</em> — это особенно важно при каком-либо значении свойства <code>animation-duration</code>, т. к. последним состоянием может быть и <code>0%</code>, если задан <code>reverse</code> или <code>alternate</code>.</li>
    <li id="j548"><code>backwards</code> — наоборот, после завершения анимации элемент примет состояние <em>начального кадра анимации</em>, который, опять же, может быть не <code>0%</code> или <code>from</code>, в зависимости от направления анимации. Это значение имеет еще один эффект — элемент даже до начала анимации принимает состояние указанное в кадре <code>0%</code>, даже если у анимации есть задержка.</li>
    <li id="LnU7"><code>both</code> — комбинирует два значения, до начала анимации элемент принимает состояние первого ключевого кадра, а после завершения анимации элемент останется в состоянии последнего момента анимации.</li>
  </ul>
  <p id="BUbQ">Последнее свойство — это <strong>управление паузой</strong> или состоянием проигрывания анимации <code>animation-play-state</code>. Имеет всего два значение — <code>running</code> по умолчанию и <code>paused</code>, то бишь — поставить анимацию на паузу. Понятное дело, это свойство-триггер, как например, <code>display: none;</code>.</p>
  <p id="NU0b"></p>
  <hr />
  <p id="stNM"></p>
  <p id="VIRW">На этом с анимацией всё! Более того, на этом мы, формально, заканчиваем изучение HTML/CSS в тренажёрах! Далее у нас Less, который, конечно, препроцессор CSS, но всё-таки уже не совсем про стили, а скорее про оптимизацию работы с CSS. Потом SVG и после добьем JavaScript.</p>
  <p id="QT0W">Поздравляю себя! :D </p>
  <p id="OaKz">На самом деле я в какой-то момент закопался так, что уже не думал когда закончу, а оно пришло неожиданно. Я уверен, я еще помучаюсь на JavaScript, но в остальном конец уже более-менее ощутим. По крайней мере с вёрсткой.</p>
  <p id="jcux">Спасибо вам за ваше внимание! Продуктивной вам работы!</p>
  <p id="bDyc">Ссылки на мои социальные сети, там анонсы постов, некоторые мои короткие записи, мыслишки и всякое на отвлечённые темы:</p>
  <section>
    <p id="TQk6" data-align="center"><a href="https://t.me/arkhelvetios" target="_blank">Telegram</a> — <a href="https://twitter.com/arkhelvetios" target="_blank">Twitter</a> — <a href="https://www.instagram.com/arkhelvetios/" target="_blank">Instagram</a> — <a href="https://www.facebook.com/arkhelvetios" target="_blank">Facebook</a> — <a href="https://vk.com/arkhelvetios" target="_blank">VK</a></p>
  </section>
  <p id="FALh">Заходите куда удобно вам и подписывайтесь! Еще раз спасибо за внимание!</p>
  <tt-tags id="hdUH">
    <tt-tag name="html">#html</tt-tag>
    <tt-tag name="css">#css</tt-tag>
    <tt-tag name="html5">#html5</tt-tag>
    <tt-tag name="css3">#css3</tt-tag>
    <tt-tag name="frontend">#frontend</tt-tag>
    <tt-tag name="front_end">#front_end</tt-tag>
    <tt-tag name="обучение">#обучение</tt-tag>
    <tt-tag name="it">#it</tt-tag>
  </tt-tags>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@helvetios/css-tricks-part2</guid><link>https://teletype.in/@helvetios/css-tricks-part2?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=helvetios</link><comments>https://teletype.in/@helvetios/css-tricks-part2?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=helvetios#comments</comments><dc:creator>helvetios</dc:creator><title>Сборник тонкостей CSS — оформление текста и CSS-таблицы — Тренажёр HTML Academy</title><pubDate>Sat, 25 Jun 2022 00:44:49 GMT</pubDate><media:content medium="image" url="https://img3.teletype.in/files/e9/02/e902623c-4059-4a8b-8b46-c77a7494c13f.png"></media:content><category>Веб-разработка</category><tt:hashtag>html</tt:hashtag><tt:hashtag>css</tt:hashtag><tt:hashtag>html5</tt:hashtag><tt:hashtag>css3</tt:hashtag><tt:hashtag>frontend</tt:hashtag><tt:hashtag>front_end</tt:hashtag><tt:hashtag>обучение</tt:hashtag><tt:hashtag>it</tt:hashtag><description><![CDATA[<img src="https://img2.teletype.in/files/58/96/5896e3c8-a73f-4838-b823-76d1f1bbf804.png"></img>Продолжаем собирать тонкости, коих осталось не много. В этой статье поговорим об остатках текстовых свойств и о том как работают таблицы, которые изначально и не таблицы вовсе.]]></description><content:encoded><![CDATA[
  <figure id="dI5y" class="m_column">
    <img src="https://img2.teletype.in/files/58/96/5896e3c8-a73f-4838-b823-76d1f1bbf804.png" width="1200" />
  </figure>
  <p id="O5Kb">Продолжаем собирать тонкости, коих осталось не много. В этой статье поговорим об остатках текстовых свойств и о том как работают таблицы, которые изначально и не таблицы вовсе.</p>
  <p id="Ze6B"></p>
  <nav>
    <ul>
      <li class="m_level_1"><a href="#yb82">Оформление текста</a></li>
      <li class="m_level_2"><a href="#Netr">Испытания</a></li>
      <li class="m_level_1"><a href="#tfFf">CSS-таблицы</a></li>
      <li class="m_level_2"><a href="#Wemh">Испытания</a></li>
    </ul>
  </nav>
  <p id="Rszu"></p>
  <h2 id="yb82">Оформление текста</h2>
  <p id="JwXi">На самом деле, мало чего интересного осталось. Первым на рассмотрение свойство отвечающее за <strong>падающую тень текста</strong> <code>text-shadow</code>. Мы уже разбирали и <a href="https://blog.arkhelvetios.ru/box-shadow-linear-gradient#jFa8" target="_blank">тень блока</a> <code>box-shadow</code>, и <a href="https://blog.arkhelvetios.ru/css-filters-kekstagram#kV1F" target="_blank">фильтр падающей тени</a> <code>drop-shadow()</code>, и вот, наконец, дошли до тени текста.</p>
  <p id="f3CW">Больше всего работа <code>text-shadow</code> похожа на фильтр <code>drop-shadow()</code> — тень повторяет контуры текста и не поддерживает растяжение. Синтаксис записи тоже абсолютно идентичен — первые два значения отвечают за смещение по горизонтали и по вертикали соответственно, третий отвечает за размытие и наконец указывается цвет тени.</p>
  <pre id="ZWgo" data-lang="css">text-shadow: 5px -5px 0px #333333;</pre>
  <p id="TAHh">Далее в части вспоминаем про <strong>подключение шрифтов</strong> через <code>@font-face</code>, которое <a href="https://blog.arkhelvetios.ru/advanced-html-css-part1#IrZx" target="_blank">мы разбирали</a> в начале «среднего уровня» тренажёров. Правда, здесь нам показали, что за «шрифт» можно выдать набор SVG фигур, как бы, упакованных в шрифтовой формат. Я такое даже пару раз видел во время работы и насколько знаю, многие конструкторы примерно так и загружают свои «библиотеки иконок».</p>
  <p id="IkYs">Следующее свойство <code>letter-spacing</code> отвечающее за <strong>расстояние между символами</strong>. Принимает числовое значение — положительные увеличивают расстояние между символами, отрицательные уменьшают. По умолчанию равно нулю — базовое расстояние между символами.</p>
  <p id="OOQT">По умолчанию, перенос строки происходит только по словам, но с помощью свойства <code>overflow-wrap</code> или его альтернативной записью, как написано в тренажёре, <code>word-wrap</code> — можно задать <strong>перенос по символам</strong> используя значение <code>break-word</code> — «ломать слово». По умолчанию же значение <code>normal</code>.</p>
  <p id="p9do">Очень специфичное свойство <code>text-overflow</code>, отвечающее за то, как будет выглядеть текст в конце непереносимой строки, например, очень длинное слово не поместилось, а <code>overflow-wrap: normal;</code>. По умолчанию имеет значение <code>clip</code> — текст просто обрезается, но можно задать значение <code>ellipsis</code> и придать более красивый вид — слово обрежется раньше, но в конце строки появится многоточие.</p>
  <figure id="2bCL" class="m_original">
    <img src="https://img2.teletype.in/files/5d/d0/5dd0e325-6321-4181-9936-1fd53538f722.png" width="729" />
  </figure>
  <p id="U5Om">Далее, на мой взгляд, немного архаичное свойство <code>text-indent</code>, отвечающее за <strong>смещение начала первой строки абзаца</strong>. Та самая «красная строка» задаётся через это свойство. Мы его <a href="https://blog.arkhelvetios.ru/workshop-academy#NzrI" target="_blank">уже использовали</a>, правда в других целях — задавали значение <code>-1000px</code>, чтобы скрыть текст над спрайтами.</p>
  <p id="Ydxv">Пару уроков отведено псевдоэлементам <code>::first-letter</code> и <code>::first-line</code>, которые мы уже <a href="https://blog.arkhelvetios.ru/advanced-html-css-part2#bhP8" target="_blank">немного разбирали</a> в части про селекторы. Лишним не будет, кончено, но на мой взгляд они тоже малоприменимы.</p>
  <p id="4xWX">А вот что интересно, так это семейство свойств отвечающих за <strong>многоколоночную вёрстку текста</strong> и первое из них <code>column-count</code> отвечает за <strong>количество столбцов</strong> на которое нужно разделить текст. Выглядит это как журнальная или газетная вёрстка — длинный текст разделяется на какое-то количество столбцов, это упрощает чтение на большом листе. </p>
  <p id="fgd9">Свойство <code>column-count</code> принимает числовое значение этих столбцов и текстовый блок автоматически разделяется на них. Честно признаюсь, я какое-то время делал это все отдельным текстовыми блоками во флекс-контейнере!</p>
  <p id="YXfj">Другой вариант задать не число колонок, а <strong>минимальную ширину колонки</strong> с помощью свойства <code>column-width</code> — браузер автоматически разделит текст на максимально доступное количество колонок указанной ширины.</p>
  <p id="0jQ0">Последнее свойство из семейства — <code>column-gap</code>, отвечающее за <strong>расстояние между колонками</strong>. По умолчанию имеет значение <code>1em</code>, но можно задать любое в пикселях, пунктах и других абсолютных единицах.</p>
  <p id="gQk2"></p>
  <h3 id="Netr">Испытания</h3>
  <p id="rk96">Испытаний целых три штуки и все крайне простые. С текстом особо ничего, видимо, не придумать. Первое испытание на тени:</p>
  <figure id="wRYi" class="m_column">
    <img src="https://sun9-85.userapi.com/impg/QCphIp3hxFcSX6LuCfJ-G-dqYFP0X-z0Lv1L2w/8mE661yohBU.jpg?size=1318x921&quality=96&sign=f76e973591f73bcb1e009dc4629f8d3a&type=album" width="1318" />
  </figure>
  <p id="HQrn">Второе испытание тоже на тени и, по моему, еще легче первого. Здесь только ловушка небольшая с текстом снизу и подобрать пиксели для смайликов.</p>
  <figure id="i6dc" class="m_column">
    <img src="https://sun9-54.userapi.com/impg/OuT1QN4R-4IjCFbZoxM9uo2wB504MuCdoC9BBg/P9dwxTiXf6g.jpg?size=1321x922&quality=96&sign=0580582bae9eddecdc9aef351fd5fd59&type=album" width="1321" />
  </figure>
  <p id="36IX">Третье испытание тоже лёгкое — сделать из текста логотип, используя свойство overflow-wrap и парочку других из пройдённой части.</p>
  <figure id="3uzD" class="m_column">
    <img src="https://sun9-21.userapi.com/impg/_RPzd-en2XD9qN4A0N7olR-UH9wMFYKgwXvtaQ/-DhXk468rGE.jpg?size=1319x920&quality=96&sign=db63e9f14d1d78b575e7be3dfb0245f9&type=album" width="1319" />
  </figure>
  <p id="IkWJ"></p>
  <h2 id="tfFf">CSS-таблицы</h2>
  <p id="JZwH">Честно говоря, меня немного разочаровала истина — я думал, таблицы на CSS будут как-то принципиально отличаться от таблиц в HTML, но по сути нет. Хрен знает чего я вообще ждал, сам не понимаю, но я себе каким-то образом завысил ожидания.</p>
  <p id="3Rfo">Таблицы на CSS это когда не табличным элементам мы задаём табличное поведение через свойство <code>display</code>. Формально, практически любую структуру можно завернуть в таблицу.</p>
  <p id="SPde">У меня сразу же появились вопросы:</p>
  <ol id="his9">
    <li id="jkF6">А в чем смысл? Почему не сверстать заранее таблицу в HTML если она нужна? Количество символов будет примерно таким же.</li>
    <li id="3I9a">Что по поводу семантики? Дерево div’ов с табличными свойствами лучше чем табличные теги? Как к этому относятся поисковики?</li>
    <li id="bvBd">Зачем вообще был придуман этот велосипед?</li>
  </ol>
  <p id="V92q">Частично я сам себе отвечу — чтобы перенести все построения в CSS и управлять из одного места свойствами типа <code>colspan</code> и подобными. Но вот вопрос про семантику открытый.</p>
  <p id="EzO7">В остальном, можно описать CSS таблицы так — каждому табличному тегу есть свой аналог в виде значения свойства <code>display</code>. Первую половину части мы просто рассматриваем каждый вариант отдельно строя таким образом таблицу.</p>
  <ul id="qYUu">
    <li id="75b8"><code>display: table;</code> — аналогичен тегу <code>&lt;table&gt;</code>, обозначающий таблицу;</li>
    <li id="IZcz"><code>display: table-row;</code> — аналогичен тегу <code>&lt;tr&gt;</code>, строка таблицы;</li>
    <li id="2gb6"><code>display: table-cell;</code> — аналогичен тегу <code>&lt;td&gt;</code>, ячейка таблицы;</li>
    <li id="W5z0"><code>display: table-caption;</code> — аналогичен тегу <code>&lt;caption&gt;</code>, заголовок таблицы;</li>
    <li id="kABg"><code>display: table-header-group;</code> — аналогичен <code>&lt;thead&gt;</code>, группа строк шапки;</li>
    <li id="z3LZ"><code>display: table-footer-group;</code> — аналогичен <code>&lt;tfoot&gt;</code>, группа строк нижней части таблицы;</li>
    <li id="FAQm"><code>display: table-row-group;</code> — аналогичен <code>&lt;tbody&gt;</code>, просто группа строк;</li>
  </ul>
  <p id="xOG7">Дальше идут два тега, про которые я впервые увидел в этой части, поэтому про них поговорим отдельно. Оказывается, можно задавать размер столбцов не через ячейку, а сразу всему столбцу.</p>
  <p id="HXQX">В HTML для этого есть одиночный тег <code>&lt;col&gt;</code>, который указывается в самом начале сразу после тега <code>&lt;table&gt;</code> и каждый такой тег отвечает за столбец. Им можно задать атрибут <code>width</code>, который и будет влиять на всю таблицу, точнее на все ячейки в этом столбце.</p>
  <pre id="vEn6" data-lang="html">&lt;table&gt;
  &lt;col width=&quot;20%&quot;&gt; &lt;!-- Первый столбец – ячейки 1.1 и 1.2 --&gt;
  &lt;col width=&quot;80%&quot;&gt; &lt;!-- Второй столбец – ячейки 2.1 и 2.2 --&gt;
  &lt;tr&gt;
    &lt;td&gt;1.1&lt;/td&gt; &lt;td&gt;2.1&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;1.2&lt;/td&gt; &lt;td&gt;2.2&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;</pre>
  <p id="Hvcn">Столбцы можно группировать с помощью тега <code>&lt;colgroup&gt;</code>, которой тоже можно задать атрибут <code>width</code>, а все <code>&lt;col&gt;</code> внутри этого тега получат равные доли ширины.</p>
  <p id="gGPg">Так вот, в CSS таблицах есть аналоги и этим тегам:</p>
  <ul id="fxB4">
    <li id="RKg9"><code>display: table-column;</code> — аналогичен <code>&lt;col&gt;</code>;</li>
    <li id="eWzP"><code>display: table-column-group;</code> — аналогичен <code>&lt;colgroup&gt;</code>;</li>
  </ul>
  <p id="IGSe">Все табличные свойства при этом начинают работать, включая специфичные для таблиц типа <code>border-collapse</code> и <code>border-spacing</code>. То есть дальнейшая стилизация идет как с обычной таблице.</p>
  <p id="YdvU">Кстати один урок в части посвящен тому, что таблицу можно сделать блочно-строчной с помощью значение <code>inline-table</code>. Понятия не имею где это можно применить, но даже такое можно сделать.</p>
  <p id="KtD6">Еще отдельно отмечу то как оформлена часть, она мне напомнила часть про двумерные трансформации — явный реверанс в сторону РПГ или ДНД, тут мы тоже собираем инвентарь. Кажется мелочью, а даже такую пресную информацию делает увлекательной! Мое уважение составителю!</p>
  <p id="r0RN"></p>
  <h3 id="Wemh">Испытания</h3>
  <p id="kvTZ">На них придётся чутка посидеть, они не то чтобы сложные, но в голове кирпичики сложить придётся, чтобы понять чего и куда. В первом заблокирован HTML и только с помощью CSS надо превратить список в таблицу:</p>
  <figure id="TPuK" class="m_column">
    <img src="https://sun9-33.userapi.com/impg/_RpeGlOkogpWad-OlVyGwjoMah-AGBAW3H2HVQ/3qGo09UXKug.jpg?size=1319x921&quality=96&sign=45484e8f55099d1c2124ffc2826a03d4&type=album" width="1319" />
  </figure>
  <p id="DlVb">Второе испытание на табличные свойства и работу с рамками и отступами в таблице, плюс вспоминаем псевдоклассы:</p>
  <figure id="WzGZ" class="m_column">
    <img src="https://sun9-77.userapi.com/impg/YNJ28ki1px2FMuQLMsXK-3l9Qic6dQk8KSAr2g/YYuOeW70pwE.jpg?size=1319x923&quality=96&sign=220679c49e3d3d7951a2d71bc0eab11c&type=album" width="1319" />
  </figure>
  <p id="9781"></p>
  <hr />
  <p id="Gvbr"></p>
  <p id="VbR8">Так и знал, что получится такой, можно сказать, огрызок от первой части про тонкости. Но да ладно, это лучше чем лонгрид на миллиард символов. Надо охлаждать свое графоманство.</p>
  <p id="LxwD">Вам огромное спасибо за ваше внимание! Увлекательного вам обучения!</p>
  <p id="jArj">Ссылки на мои социальные сети, там анонсы постов, некоторые мои короткие записи, мыслишки и всякое на отвлечённые темы:</p>
  <section style="background-color:hsl(hsl(263, 48%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="kDzq" data-align="center"><a href="https://t.me/arkhelvetios" target="_blank">Telegram</a> — <a href="https://twitter.com/arkhelvetios" target="_blank">Twitter</a> — <a href="https://www.instagram.com/arkhelvetios/" target="_blank">Instagram</a> — <a href="https://www.facebook.com/arkhelvetios" target="_blank">Facebook</a> — <a href="https://vk.com/arkhelvetios" target="_blank">VK</a></p>
  </section>
  <p id="6QCd">Заходите куда удобно вам и подписывайтесь! Еще раз спасибо за внимание!</p>
  <tt-tags id="P420">
    <tt-tag name="html">#html</tt-tag>
    <tt-tag name="css">#css</tt-tag>
    <tt-tag name="html5">#html5</tt-tag>
    <tt-tag name="css3">#css3</tt-tag>
    <tt-tag name="frontend">#frontend</tt-tag>
    <tt-tag name="front_end">#front_end</tt-tag>
    <tt-tag name="обучение">#обучение</tt-tag>
    <tt-tag name="it">#it</tt-tag>
  </tt-tags>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@helvetios/css-tricks-part1</guid><link>https://teletype.in/@helvetios/css-tricks-part1?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=helvetios</link><comments>https://teletype.in/@helvetios/css-tricks-part1?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=helvetios#comments</comments><dc:creator>helvetios</dc:creator><title>Сборник тонкостей CSS — селекторы, фоны и рамки — Тренажёр HTML Academy</title><pubDate>Fri, 24 Jun 2022 19:41:38 GMT</pubDate><media:content medium="image" url="https://img1.teletype.in/files/01/c1/01c1cfe3-c919-4ffd-acf7-ba8a95c54c5c.png"></media:content><category>Веб-разработка</category><tt:hashtag>html</tt:hashtag><tt:hashtag>css</tt:hashtag><tt:hashtag>html5</tt:hashtag><tt:hashtag>css3</tt:hashtag><tt:hashtag>frontend</tt:hashtag><tt:hashtag>front_end</tt:hashtag><tt:hashtag>обучение</tt:hashtag><tt:hashtag>it</tt:hashtag><description><![CDATA[<img src="https://img2.teletype.in/files/d1/35/d135546e-551b-44fc-aaf1-6a7c6d7d9571.png"></img>Перескочим на «продвинутый» уровень тренажёров. Под «тонкостями» в тренажёрах имеются ввиду несколько дополнительных частей к уже пройденным — более сложные селекторы, специфичные свойства рамок, фонов и текста. А еще изучим построение таблиц на CSS.]]></description><content:encoded><![CDATA[
  <figure id="kgeS" class="m_column">
    <img src="https://img2.teletype.in/files/d1/35/d135546e-551b-44fc-aaf1-6a7c6d7d9571.png" width="1200" />
  </figure>
  <p id="ivri">Перескочим на «продвинутый» уровень тренажёров. Под «тонкостями» в тренажёрах имеются ввиду несколько дополнительных частей к уже пройденным — более сложные селекторы, специфичные свойства рамок, фонов и текста. А еще изучим построение таблиц на CSS.</p>
  <p id="HWTU">Перескакиваю я потому, что в среднем остался только JavaScript и еще блок про SVG. Я подумал, раз после определённого момента порядок блоков уже условный, то лучше доучить остатки CSS из продвинутого уровня, а потом уже браться за JS и остальное и потом плавно перейдём уже на курсы.</p>
  <p id="u1FP"></p>
  <nav>
    <ul>
      <li class="m_level_1"><a href="#JF5h">Селекторы</a></li>
      <li class="m_level_2"><a href="#WfYc">Продвинутый селектор по атрибуту</a></li>
      <li class="m_level_2"><a href="#H6nN">Больше псевдоклассов</a></li>
      <li class="m_level_2"><a href="#ZMRh">Испытание</a></li>
      <li class="m_level_1"><a href="#lH01">Рамки и фоны</a></li>
      <li class="m_level_2"><a href="#Mc4U">Фоны</a></li>
      <li class="m_level_2"><a href="#7sGa">Рамки</a></li>
      <li class="m_level_2"><a href="#uArk">Фигуры из рамок</a></li>
      <li class="m_level_2"><a href="#lk0n">Испытания</a></li>
    </ul>
  </nav>
  <p id="EqsH"></p>
  <h2 id="JF5h">Селекторы</h2>
  <p id="0bfF">Мы уже забегали в эту часть когда проходили <a href="https://blog.arkhelvetios.ru/advance-workshop-academy#5DXr" target="_blank">Мастерские</a> и делали слайдер на чистом CSS. Точнее, на неё там активно ссылались. Итак, в этой части мы доучим селекторы по атрибутам и специфичные, интересные псевдоклассы.</p>
  <p id="6Da2"></p>
  <h3 id="WfYc">Продвинутый селектор по атрибуту</h3>
  <p id="04bS">Итак, про <a href="https://blog.arkhelvetios.ru/advanced-html-css-part2#JgQG" target="_blank">селекторы по атрибуту</a> мы уже знаем из прошлых частей, выглядит он как тег с указанием атрибута и его значением:</p>
  <pre id="cot1" data-lang="css">input[type=&quot;text&quot;] {...} /* все input с type=&quot;text&quot; */</pre>
  <p id="VJwX">У этого вида селекторов есть дополнительные механизмы поиска элементов — перед знаком равно <code>=</code> добавляется модификатор для поиска «подстроки», то есть части строки в значении. Модификаторов несколько и насколько я понял, все они взяты из <a href="https://ru.wikipedia.org/wiki/%D0%A0%D0%B5%D0%B3%D1%83%D0%BB%D1%8F%D1%80%D0%BD%D1%8B%D0%B5_%D0%B2%D1%8B%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F#%D0%9F%D0%BE%D0%B7%D0%B8%D1%86%D0%B8%D1%8F_%D0%B2%D0%BD%D1%83%D1%82%D1%80%D0%B8_%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B8" target="_blank">регулярных выражений</a> и работают аналогичным образом.</p>
  <ul id="wsxl">
    <li id="v8Dr"><strong>Начало строки</strong> <code>[attr^=&quot;value&quot;]</code> — символ <code>^</code> указывает на поиск всех элементов с атрибутом <code>attr</code> со значением начинающегося на <code>value</code>. Под это подойдет, например <code>value-1</code>, <code>valueone</code>, <code>value__top</code> и т. д.</li>
    <li id="UMIh"><strong>Конец строки</strong> <code>[attr$=&quot;value&quot;]</code> — символ <code>$</code> указывает на поиск всех элементов с атрибутом <code>attr</code> со значением заканчивающимся на <code>value</code>. Подойдёт, например, <code>header-value</code>, <code>notvalue</code>, <code>file.value</code> и т. д.</li>
    <li id="4t4E"><strong>Содержит в строке</strong> <code>[attr*=&quot;value&quot;]</code> — символ <code>*</code> указывает на поиск всех элементов с атрибутом <code>attr</code> со значением содержащим <code>value</code> в любом месте строки атрибута. Подойдет <code>textvalue1</code>, <code>shop value rub</code>, <code>text__extd value</code>, в общем любые совпадения.</li>
    <li id="4wIF"><strong>Содержит слово в строке</strong> <code>[attr~=&quot;value&quot;]</code> — символ <code>~</code> работает подобно <code>*</code>, но теперь ищет именно слово отдельно, то есть разделённое с обеих сторон либо пробелом, либо переносом строки. Подойдет <code>shop value rub</code>, но уже не подойдет <code>textvalue1</code>.</li>
    <li id="Zo1I"><strong>Содержит префикс</strong> <code>[attr|=&quot;value&quot;]</code> — под префиксом имеется ввиду значение вида <code>value-</code> — именно с дефисом в конце, то есть частный, но распространённый случай начала строки. Также этот селектор посчитает за корректный для себя значение <code>value</code> — то есть одно целое слово.</li>
  </ul>
  <p id="ZIYo">Эти селекторы мне бы очень пригодились в свое время для настройки Google Tag Manager’а, когда надо было прицепиться к каким-нибудь хитро сделанным полям в каком-нибудь конструкторе.</p>
  <p id="Q5kk"></p>
  <h3 id="H6nN">Больше псевдоклассов</h3>
  <p id="B1gI">Часть из представленных здесь псевдоклассов мы уже тоже встречали, большинство из них работают с булевыми атрибутами типа <code>checked</code> и им подобными для полей форм.</p>
  <ul id="8L5R">
    <li id="OUSU">Псевдоклассы <code>:disabled</code> и <code>:enabled</code> — обращаются к элементам, чаще всего полям, с одноимённым атрибутом <code>disabled</code> или без него соответственно.</li>
    <li id="w3RD">Псевдоклассы <code>:required</code> и <code>:optional</code> — обращаются к элементам, которым проставлен и, соответственно, не проставлен атрибут <code>required</code> — поле обязательно к заполнению.</li>
    <li id="gqMn">Псевдоклассы <code>:read-only</code> и <code>:read-write</code> — аналогично, обращается к элементам, которым проставлен атрибут <code>readonly</code> и которым он не проставлен. Нам в тренажёре напоминают, что <code>disabled</code> визуально и функционально похож, но суть и селектор у него другой.</li>
    <li id="frS0">Псевдокласс <code>:checked</code> — обращается к элементам типа radio или checkbox, которые в данный момент выбраны.</li>
  </ul>
  <p id="Yhw3">И отдельно можно выделить псевдоклассы которые работают, можно сказать, с корректностью информации вводимой в поля. Как мы <a href="https://blog.arkhelvetios.ru/advanced-html-css-part1#2uam" target="_blank">уже знаем</a>, у некоторых типов полей, например, у <code>email</code> или <code>url</code>, есть автоматическая валидация введённых данных, а большинству остальных — область допустимых значений можно задать отдельно через атрибут <code>pattern=&quot;&quot;</code>.</p>
  <ul id="kJdL">
    <li id="FOvf">Псевдокласс <code>:valid</code> — обращается ко всем элементам, у которых введённое значение попадает под требования.</li>
    <li id="IkUs">Псевдокласс <code>:invalid</code> — напротив, обращается ко всем элементам, у которых введённое значение некорректно и не попадает под требования.</li>
  </ul>
  <p id="QFOQ">Для типов полей, где задан диапазон допустимых значений <code>min</code> и <code>max</code> — это например, <code>number</code> и, возможно, <code>range</code>, есть специальные псевдоклассы проверяющие именно попадание в диапазон.</p>
  <ul id="W16I">
    <li id="erE4">Псевдокласс <code>:in-range</code> — обращается ко всем элементам, значение которых попадает в диапазон</li>
    <li id="O2mw">Псевдокласс <code>:out-of-range</code> — выбирает все элементы, значение которых за пределами диапазона.</li>
  </ul>
  <p id="UmhN">Именно в конце этой части нам показывают чудеса с <code>checked</code> и селектором <code>~</code>, правда здесь мы просто скрываем блоки. Надо будет что-нибудь интересное с этим сделать.</p>
  <p id="j8dh"></p>
  <h3 id="ZMRh">Испытание</h3>
  <p id="dbiC">Всего одно испытание с уже знакомой формой. В этот раз играемся с атрибутными селекторами. Всё уже сверстано, нам надо только подобрать правильные селекторы.</p>
  <figure id="mNcc" class="m_column">
    <img src="https://sun9-49.userapi.com/impg/iXGKNgjEIJlbFeQci_VGgxiXE-GcWZbE7bCdUw/ogSskjJySiE.jpg?size=1321x923&quality=96&sign=1fbf40f7551926fe85e54fd254c99f07&type=album" width="1321" />
  </figure>
  <p id="reax">Задача не сложная, может возникнуть затык с декоративными чекбоксами и радиокнопками — это псевдоэлементы, а сами переключатели скрыты.</p>
  <p id="Tgyw"></p>
  <h2 id="lH01">Рамки и фоны</h2>
  <p id="fkdj">Касаемо фонов — большую часть из указанного здесь мы уже видели и даже использовали. А вот с рамками, оказывается, можно делать очень интересные штуки!</p>
  <p id="HpUj"></p>
  <h3 id="Mc4U">Фоны</h3>
  <p id="Sugh">Сначала разберёмся окончательно с фонами. Первым на очереди у нас свойство отвечающее за <strong>размер фонового изображения</strong> <code>background-size</code>. Да, мы уже его использовали пару раз, он даже был задействован в <a href="https://blog.arkhelvetios.ru/keksby" target="_blank">Кексби</a>, будь он неладен.</p>
  <p id="8aaZ">Это свойство принимает два типа значений — численные в <code>px</code> или <code>%</code>, которые явно обозначают ширину и высоту фонового изображения, и ключевые слова:</p>
  <ul id="BMqH">
    <li id="maLe"><code>auto</code> — значение по умолчанию для ширины и высоты.</li>
    <li id="Sbmx"><code>contain</code> — изображение полностью умещается в блок по большей стороне, сохраняя свои пропорции, таким образом фоновое изображение может закрывать не всё пространство блока, если их размеры и пропорции не совпадают.</li>
    <li id="rGdm"><code>cover</code> — изображение полностью закрывает пространство блока, сохраняя при этом свои пропорции, части изображения не поместившиеся в блок — обрезаются.</li>
  </ul>
  <figure id="wzeP" class="m_column">
    <img src="https://img4.teletype.in/files/7d/ec/7dec7762-12f1-4295-a8d2-ac383b093011.png" width="663" />
  </figure>
  <p id="ERNd">Следующее свойство <code>background-origin</code> отвечает за то <strong>в каких границах</strong> будет отображаться фоновое изображение:</p>
  <ul id="r8YH">
    <li id="cS49"><code>content-box</code> — отображается только в области содержимого, то есть не отображается даже на внутренних отступах.</li>
    <li id="QE8W"><code>padding-box</code> — значение по умолчанию, отображается во внутренней области бокса, исключая рамки и внешние отступы.</li>
    <li id="lx5p"><code>border-box</code> — в отображение включаются рамки, даже если они непрозрачны, то есть изображение будет залезать под них.</li>
  </ul>
  <figure id="ao1d" class="m_original">
    <img src="https://htmlacademy.ru/assets/courses/88/img/box-sizing.jpg" width="461" />
    <figcaption>margin-box, видимо, не завезли.</figcaption>
  </figure>
  <p id="Zxgp">Его сосед по парте — свойство <code>background-clip</code>, которое отвечает <strong>за обрезку всего фона</strong> — и изображения, и цвета. Имеет абсолютно такие же значения и работает по тому же принципу, но отвечает за то в какой области будет видно, а не то на какую область растянуто. По умолчанию стоит значение <code>border-box</code>, то есть без обрезания фона совсем.</p>
  <p id="D0AN">В этой части нам еще раз показывают принцип работы <strong>множественных фонов</strong> — в каждом специфичном фоновом свойстве порядок значений через запятую сопоставляется с порядком указанным в <code>background-image</code>. </p>
  <p id="8UIv">Мы уже рассматривали эту особенность когда строили <a href="https://blog.arkhelvetios.ru/box-shadow-linear-gradient#1q5k" target="_blank">множественные градиенты</a> и проставляли им <code>background-position</code>. Кстати, о нём. У свойства <strong>позиции фонового изображения</strong> есть возможность указать не просто числовые координаты от левого верхнего угла, а уточнить сторону от которой надо вести отсчет, например:</p>
  <pre id="8lE8" data-lang="css">background-position: right 30px bottom 60px; /* справа 30px, снизу 60px */
background-position: left 50px bottom 10px; /* слева 50px, снизу 10px */</pre>
  <p id="L5O8">И последним свойством рассмотрим знакомый нам <code>background-repeat</code>, отвечающий за <strong>повторение фонового изображения</strong>. Во-первых, он может принимать сразу два значение — первое отвечает за повтор по горизонтали, а второй по вертикали. Во-вторых, имеет в своем запасе два очень крутых значения, которые будто из флексбоксов и гридов (или наоборот?):</p>
  <ul id="RIQ8">
    <li id="xD7X">Значение <code>round</code> — изображение будет повторятся, но не будет обрезаться у краёв элемента. Картинка будет растягиваться, чтобы заполнить некратные размеру изображения зазоры. Это поведение ну прям <a href="https://blog.arkhelvetios.ru/easy-grids#lQfB" target="_blank">о-очень похоже</a> на работу функций <code>repeat()</code> и <code>minmax()</code> с <code>auto-fit</code> в гридах.</li>
    <li id="yahf">Значение <code>space</code> — изображение тоже не обрезается, но и не растягивается. Зазоры компенсируются расстоянием между изображениями, которые равномерно распределяются по пространству блока. Их поведение тоже очень похоже на уже знакомый нам <code>space-between</code> в <a href="https://blog.arkhelvetios.ru/flexy-flexbox#W9S0" target="_blank">выравнивании</a> флекс-итемов во флекс-контейнере. </li>
  </ul>
  <p id="I1ri"></p>
  <h3 id="7sGa">Рамки</h3>
  <p id="71Y7">Для начала отметим, что помимо <code>border</code> существует еще и <strong>внешняя рамка</strong> или «обводка» <code>outline</code>. У нее аналогичная запись, но ей нельзя задать какую-либо сторону отдельно, то бишь только всему элементу. </p>
  <p id="d9Bj">Еще одна особенность обводки <code>outline</code> — она не учувствует в расчёте размера бокса и строится после <code>border</code> и, соответственно, может залезать на соседние элементы.</p>
  <p id="Ugg0">У обводки есть уникальное свойство <code>outline-offset</code>, которое отвечает за отступ от элемента или рамки. Причем, принимает как положительные значения — отступ во вне, так и отрицательные — обводка сожмётся внутрь.</p>
  <p id="W1d6">Пару слов про <strong>скругление углов</strong> <code>border-radius</code> — мы <a href="https://blog.arkhelvetios.ru/advance-workshop-academy#54zz" target="_blank">уже знаем</a>, что можно задавать скругление каждому углу отдельно. Оказывается, для этого даже есть специальные отдельные свойства — <code>border-top-left-radius</code>, <code>border-top-right-radius</code>, короче, <code>border-угол-radius</code>.</p>
  <p id="Bn1V">Мало того, оказывается, скругление можно задать эллиптическое, то есть задать ширину и высоту круга по которому будет скругляться угол:</p>
  <pre id="Hi7T" data-lang="css">/* горизонтальный радиус 30px, вертикальный 15px */
border-top-right-radius: 30px 15px;</pre>
  <figure id="636x" class="m_original">
    <img src="https://htmlacademy.ru/assets/courses/88/img/border-radius-theory-2.jpg" width="289" />
  </figure>
  <p id="yY3r">Всё это безобразие можно записать и в короткой форме <code>border-radius</code> — радиусы задаются через <code>/</code>, при этом можно также задать сразу для всех углов:</p>
  <pre id="TlLf" data-lang="css">/* горизонтальный радиус всех углов 10px, вертикальный 5px */
border-radius: 10px / 5px;

/* разные горизонтальные и вертикальные радиусы у каждого угла */
border-radius: 10px 20px 30px 40px / 5px 15px 25px 35px;</pre>
  <hr />
  <p id="5xrV">Далее мы переходим к обширной теме — <strong>фоновое изображение рамки</strong> и семейство свойств отвечающих за это — <code>border-image</code>.</p>
  <p id="VafN">За <strong>источник изображения</strong> отвечает свойство <code>border-image-source</code> и его синтаксис аналогичен свойству <code>background-image</code>, но для рамок.</p>
  <pre id="h8vw" data-lang="css">border-image-source: url(&quot;image.jpg&quot;);</pre>
  <p id="rfaf">Однако, свойство для рамок работает немного иначе — по умолчанию изображение не растягивается как подложка под рамкой, как ожидается, а будет раскидано по углам рамки.</p>
  <figure id="uI2n" class="m_original">
    <img src="https://img3.teletype.in/files/eb/04/eb047507-3d6f-4972-8dab-8e0159e04d96.png" width="643" />
  </figure>
  <p id="8OYH">Дело всё в том, что рамка по свой сути имеет аж 9 областей — 4 угла, 4 стороны и центр. По умолчанию, браузер не умеет подстраивать изображение под задачи рамки, поэтому просто пихает его по углам.</p>
  <p id="dOoG">За <strong>нарезку фонового изображения</strong> на области для рамки отвечает свойство <code>border-image-slice</code> — оно принимает от одного до четырёх числовых значений и работает по сути как функция <code>rect()</code> которую мы уже <a href="https://blog.arkhelvetios.ru/advance-workshop-academy#LFZo" target="_blank">разбирали в Мастерских</a>.</p>
  <figure id="iLQR" class="m_original">
    <img src="https://htmlacademy.ru/assets/courses/88/img/border-slice.jpg" width="433" />
  </figure>
  <p id="7bLf">Кстати, свойство принимает какие-то условные единицы, то есть в самом свойстве, почему-то не надо ставить <code>px</code> — выдаст ошибку. Одно значение задаёт отступы от своих краёв для всех линий, а отдельно заданные идут в стандартном порядке — верх, право, низ, лево:</p>
  <pre id="OBTr" data-lang="css">border-image-slice: 60; /* нарезка как на картинке выше */
border-image-slice: 10 20 30 40; /* каждая линия отдельно */</pre>
  <p id="BPuc">Таким образом, мы нарезаем изображение на те самые 9 частей и уже с ними работает браузер. По умолчанию, центральная область скрывается, но это можно изменить подставив в конце значений нарезки ключевое слово <code>fill</code> — тогда центральная часть перекроет фон, но не перекроет контент блока.</p>
  <figure id="JSgf" class="m_original">
    <img src="https://img2.teletype.in/files/97/02/97027e93-a573-455f-afed-3b8f0276fcf0.png" width="1061" />
  </figure>
  <p id="Nb9n">Изображение на сторонах, по умолчанию, растягиваются под всю длинны рамки, что далеко не для всех ситуаций подойдет. Но этим поведением можно управлять с помощью свойства <code>border-image-repeat</code> — он отвечает за <strong>способ заполнения фона</strong> по сторонам рамки. Именно по сторонам!</p>
  <p id="KLJR">Свойство работает похожим на <code>background-repeat</code> образом, но по умолчанию имеет свой свойство <code>stretch</code> — которое, как раз, растягивает изображение по сторонам. Остальные значения, такие же:</p>
  <ul id="fkzJ">
    <li id="PTcX"><code>repeat</code> — повторяет части в размере отрезка, заполняя сторону полностью, при этом обрезает изображение в конце, если оно не поместилось.</li>
    <li id="CzxA"><code>round</code> — работает абсолютно также как и у фона блока, крайне полезный и универсальный вариант.</li>
    <li id="tnoj"><code>space</code> — тоже самое, работает аналогично значению из фона блока, но в тренажёре пишут мол, не везде поддерживается и часто заменяется на значение <code>repeat</code>.</li>
  </ul>
  <p id="pVyJ">Следующее свойство <code>border-image-width</code> — оно отвечает за <strong>размер уже нарезанных областей</strong>. Синтаксис похож на <code>margin</code> — одно значение для всех сторон, два для горизонтальных и вертикальных, четыре для всех сторон отдельно.</p>
  <p id="CwHt">По умолчанию, размер области равен размеру рамки, причем неважно на какие куски мы нарезали изображение — это отдельная, как бы, сущность, можно сказать отдельные изображения для каждого сектора. </p>
  <p id="kPH3">Расчёт идет от внешней границы рамки. Если размер фонового изображения области рамки задать больше чем размер рамки, то изображение начнёт залезать на фон блока, даже если не задан <code>fill</code>.</p>
  <p id="SmoM">И последним рассмотрим свойство <code>border-image-outset</code> — оно отвечает за <strong>смещение фонового изображения рамки</strong> от элемента. Работает примерно как <code>outline-offset</code>, но принимает только положительные значения.</p>
  <p id="ERJL"></p>
  <h3 id="uArk">Фигуры из рамок</h3>
  <p id="tEWl">У рамок есть одно интересное свойство — каждая сторона строится по сути в виде трапеции, а при нулевом размере блока превращается в треугольник.</p>
  <figure id="p9eT" class="m_original">
    <img src="https://img1.teletype.in/files/41/fb/41fbe45c-f5a1-49c0-84c3-73043a5fe87f.png" width="700" />
  </figure>
  <p id="4dv9">Таким образом, у нас в арсенале фигур еще две — квадраты и прямоугольники строить легче всего, круги и эллипсы можно строить через <code>border-radius</code>, а теперь и треугольники с трапециями. </p>
  <p id="mY80">Вторая часть про рамки посвящена такой мини-мастерской — мы строим стрелки и некоторые элементы интерфейса. Насколько это применяется в реальной жизни вопрос открытый, но прикольно. Конспектировать там, правда, нечего.</p>
  <p id="RWVP"></p>
  <h3 id="lk0n">Испытания</h3>
  <p id="ehHe">Два испытания и оба на рамки, с фонами мы, видимо, наигрались в Catademy или как её там. Первое испытание на круглую рамку:</p>
  <figure id="0b7a" class="m_column">
    <img src="https://sun9-85.userapi.com/impg/b-blMge1CUhSQcNFLCtvugIbLIaD_6Qi60tnIA/jcD9Fy6Sa80.jpg?size=1320x924&quality=96&sign=63da0d7f6c0c90f8f9e4d9970af3a3ee&type=album" width="1320" />
  </figure>
  <p id="F6i6">Тут почти все рассмотренные свойства части задействованы, включая фоновые. Второе испытание посвящено особенностям рамок и построению из них всяких фигур. Всё свёрстано, только подобрать селекторы.</p>
  <figure id="cGyp" class="m_column">
    <img src="https://sun9-6.userapi.com/impg/KNCb6jnDUNrZsCNsKJzrfzERC9nh8tsvgQVO2Q/FptDpre38oI.jpg?size=1321x923&quality=96&sign=b829a08aad01510669a404477ba07c07&type=album" width="1321" />
  </figure>
  <p id="rK9c"></p>
  <hr />
  <p id="I1E1"></p>
  <p id="RjW7">Тонкости получаются какие-то не тонкие, я надеялся будет одна коротенькая статья. Ну да ладно! В следующий раз быстренько разберёмся с тонкостями в оформлении текста и посмотрим на CSS-таблицы.</p>
  <p id="zTpj">Спасибо большое за внимание! Я стараюсь ускорять темп, так как тренажёры затянулись уже что-то.</p>
  <p id="LqYO">По поводу ссылок на предыдущие статьи — я решил пока перестать городить их тут, это тот еще геморрой и занимают они место, не очень красиво. Я скорее всего как-нибудь сделаю такой «пост-sitemap» и буду просто ссылаться на него, а пока пусть будет просто чистенько. </p>
  <p id="TlDT">А вот соц.сеточки я оставлю! Я, кстати, завёл еще страницу на Facebook и заодно буду оставлять свой Instagram. Максимальное присутствие!</p>
  <section style="background-color:hsl(hsl(263, 48%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="RC2J" data-align="center"><a href="https://t.me/arkhelvetios" target="_blank">Telegram</a> — <a href="https://twitter.com/arkhelvetios" target="_blank">Twitter</a> — <a href="https://www.instagram.com/arkhelvetios/" target="_blank">Instagram</a> — <a href="https://www.facebook.com/arkhelvetios" target="_blank">Facebook</a> — <a href="https://vk.com/arkhelvetios" target="_blank">VK</a></p>
  </section>
  <p id="ZtgO">Заходите куда удобно вам и подписывайтесь! Еще раз спасибо за внимание!</p>
  <tt-tags id="axN4">
    <tt-tag name="html">#html</tt-tag>
    <tt-tag name="css">#css</tt-tag>
    <tt-tag name="html5">#html5</tt-tag>
    <tt-tag name="css3">#css3</tt-tag>
    <tt-tag name="frontend">#frontend</tt-tag>
    <tt-tag name="front_end">#front_end</tt-tag>
    <tt-tag name="обучение">#обучение</tt-tag>
    <tt-tag name="it">#it</tt-tag>
  </tt-tags>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@helvetios/keksby</guid><link>https://teletype.in/@helvetios/keksby?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=helvetios</link><comments>https://teletype.in/@helvetios/keksby?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=helvetios#comments</comments><dc:creator>helvetios</dc:creator><title>Испытание «Великий Кексби» — Тренажёр HTML Academy</title><pubDate>Sun, 19 Jun 2022 22:22:58 GMT</pubDate><media:content medium="image" url="https://img3.teletype.in/files/a0/3c/a03c2716-bb88-45ec-94d7-b09e067cd14f.png"></media:content><category>Веб-разработка</category><tt:hashtag>html</tt:hashtag><tt:hashtag>css</tt:hashtag><tt:hashtag>html5</tt:hashtag><tt:hashtag>css3</tt:hashtag><tt:hashtag>frontend</tt:hashtag><tt:hashtag>front_end</tt:hashtag><tt:hashtag>обучение</tt:hashtag><tt:hashtag>it</tt:hashtag><description><![CDATA[<img src="https://img4.teletype.in/files/bb/67/bb67d0da-2cba-4122-8ad1-31ccbd2a34e0.png"></img>Мы изучили достаточно для того, чтобы приступить к самому большому испытанию в тренажёрах Академии. «Великий Кексби» — это целых 13 частей практики, разделённых на два блока. Или не совсем. В этой статье разберём всё испытание.]]></description><content:encoded><![CDATA[
  <figure id="VaEl" class="m_column">
    <img src="https://img4.teletype.in/files/bb/67/bb67d0da-2cba-4122-8ad1-31ccbd2a34e0.png" width="1200" />
  </figure>
  <p id="74nW">Мы изучили достаточно для того, чтобы приступить к самому большому испытанию в тренажёрах Академии. «Великий Кексби» — это целых 13 частей практики, разделённых на два блока. Или не совсем. В этой статье разберём всё испытание.</p>
  <p id="UZMo"></p>
  <nav>
    <ul>
      <li class="m_level_1"><a href="#sygG">Введение</a></li>
      <li class="m_level_1"><a href="#GT59">Этап 1 — Вёрстка</a></li>
      <li class="m_level_1"><a href="#Famz">Этап 2 — Стилизация</a></li>
      <li class="m_level_2"><a href="#XICh">Сетка </a></li>
      <li class="m_level_2"><a href="#U5qY">Макет</a></li>
      <li class="m_level_2"><a href="#QbZE">Проверка задания</a></li>
      <li class="m_level_1"><a href="#v6Fv">Итог</a></li>
    </ul>
  </nav>
  <p id="t0K6"></p>
  <h2 id="sygG">Введение</h2>
  <p id="EH4F">Немного поясню почему «не совсем». Не смотря на то, что частей 13, а заданий итого аж 166, но по сути Испытаниями являются только две части в конце каждого блока. </p>
  <p id="b26z">Остальные части это обычные уроки с подводкой к самостоятельной работе. Разница в том, что тут мы почти всё пишем ручками с нуля и общая идея – вёрстки проекта, а не отдельно взятой задачи.</p>
  <p id="zl1h">Испытание заключается в самостоятельной вёрстке макета — нам дают сам макет в виде картинки, архив с текстом и изображениями. Верстать надо у себя в редакторе, а не в браузере. Макет отличается от того, что был в уроках, но сильно похож структурно.</p>
  <p id="8gTS"></p>
  <h2 id="GT59">Этап 1 — Вёрстка</h2>
  <p id="VMku">Первый этап на 90% состоит из разметки в HTML. Только последние несколько уроков касаются CSS в таблицах, но это не существенно.</p>
  <figure id="T8rn" class="m_original">
    <img src="https://img3.teletype.in/files/21/49/214901ec-3276-4580-9dba-50717c8f4fdf.png" width="449" />
  </figure>
  <p id="vP36">И тут сразу хочу поругать эту часть тренажёра за неактуальность или, может быть, ленивость. Помнится, что нам чуть ли не сразу <a href="https://blog.arkhelvetios.ru/intro-html-css-part2#Hyz2" target="_blank">рассказали</a> про «диватоз» и семантическую вёрстку. И вот мы дошли до большого испытания где мы… верстаем все на &lt;div&gt;&#x27;ах. Прям вообще всё, включая шапку и подвал.</p>
  <p id="bivt">Наверное, это такое «упрощение», но мы же уже прошли всё это, мы уже знаем как семантически корректно верстать. Почему тут всё нарочно наоборот? Обескураживает. Окей, я понимаю, этот Кексби может быть сделан еще когда гридов даже не было, но, на мой взгляд, такое себе оправдание.</p>
  <p id="3ETd">Первый пять частей мы верстаем по указке, а вот шестая часть «Промежуточное испытание» уже самостоятельная работа. Нам дают архив с материалами, картинку и вперёд!</p>
  <p id="ucIl">Свёрстанный результат нужно обратно заархивировать и отправить на проверку. Надо сказать, что тут сделано все очень круто, я впечатлён. </p>
  <figure id="sgzY" class="m_column">
    <img src="https://img4.teletype.in/files/bb/8b/bb8b7424-2f97-4455-99d5-2900caef4c0f.png" width="762" />
  </figure>
  <p id="b7xu">Однако, из-за того, что задание проверяется автоматически, код нужно подводить к определённому шаблону. Про этот шаблон сказано в сопроводительном видео и сразу за ним есть «эталонный» вариант.</p>
  <p id="kHS1">То есть работа всё-таки не совсем самостоятельная, можно сверяться по ходу написания. Пусть это всего лишь картинки, но по ней можно точно понять, что от тебя требуется в итоге:</p>
  <figure id="adF8" class="m_original">
    <img src="https://img1.teletype.in/files/c9/60/c96094bc-aae1-484c-9135-c785e6d82a7f.png" width="364" />
  </figure>
  <p id="MExa">Кстати, я пользовался Visual Studio Code и, наверное, продолжу работать на нём. Пока всё удобно и интуитивно понятно:</p>
  <figure id="CTlo" class="m_column">
    <img src="https://img3.teletype.in/files/21/4e/214e23fb-c7fb-4eee-8d11-5eac59d1f9c4.png" width="1920" />
  </figure>
  <p id="qIbQ">Итак, готовую работу отправляем на проверку и в течение минуты получаем результат:</p>
  <figure id="hC9o" class="m_column">
    <img src="https://sun9-55.userapi.com/s/v1/ig2/TE7bKyNIXTsq7JH8c_gFUSsGjYXyxFFEPzF8yHuWUdDnMx6k2afaO6hUhqRff3oFwSzp_a54IaTonBoala0Spoe3.jpg?size=1315x634&quality=96&type=album" width="1315" />
  </figure>
  <p id="joAO">У меня получилось всё с первого раза, кроме блока с формой, там проглядел один перенос строки <code>&lt;br&gt;</code>. Повторно отправил уже полностью верно.</p>
  <p id="jnFV"></p>
  <h2 id="Famz">Этап 2 — Стилизация</h2>
  <p id="Ktqo">Технически реализовано почти также как и первый этап — первые части блока вводные, показывают как и какими методами будем стилизовать.</p>
  <figure id="qhpu" class="m_original">
    <img src="https://img4.teletype.in/files/f5/8f/f58f70d8-19da-4c1f-a533-60c1259f173c.png" width="441" />
  </figure>
  <p id="FFIO">Этот блок полностью про CSS, мы кое-где изредка добавляем класс и добавляем контейнер «центровщик» в разметку. Итоговая проверка проходит в таком же формате — нужно отправить архив, теперь уже с прилинкованным файлом стилей.</p>
  <p id="gKBv">Однако, есть несколько нюансов, которые я бы хотел написать. Простите, но часть вызвала много негативных эмоций.</p>
  <p id="daoi"></p>
  <h3 id="XICh">Сетка </h3>
  <p id="TOBq">Методы стилизации выбраны, мягко говоря, интересные. Базовая стилизация — всё ок, а вот сетки мы строим на <code>float</code>. Да, флоаты, да, с <code>clearfix</code> и всем вытекающим из этого геморроем в позиционировании, например, трёх колонок. </p>
  <figure id="839r" class="m_original">
    <img src="https://img1.teletype.in/files/45/24/452453c6-bf25-4683-938b-081ad175b85d.png" width="323" />
    <figcaption>Да он нам и нахрен не нужОн, этот грид ваш!</figcaption>
  </figure>
  <p id="Qm7u">Когда я проходил часть про сетки на <code>float</code> я уже тогда понял, что эта технология как минимум не удобная и как оказалось, устаревшая. Почему она в самом большом испытании на все тренажёры? Ну какие нафиг флоуты в 2022?</p>
  <figure id="6rr0" class="m_column">
    <img src="https://img3.teletype.in/files/ab/6c/ab6c98d2-cbe3-46ee-9bf7-fa937c5a25c5.png" width="967" />
  </figure>
  <p id="C4sd">Да, я понимаю, Кексби как и Кекстаграм, видимо, делались давно, судя по всему, 2014–2016 года или около того. Хотя, даже тогда флексы уже хорошо поддерживались. Оправдывает ли это академию?</p>
  <p id="ODRa">Да, я понимаю, что до сих пор огромное количество сайтов свёрстано на флоатах, тот же VK, например, и есть гуру синдрома утёнка, кто и сейчас верстает на них.</p>
  <p id="OS47">Но зачем новичку, который потенциально ни разу не верстал даже на удобных современных сетках, давать самостоятельную работу на супер устаревшем методе? При том, что мы уже прошли все эти методы!</p>
  <p id="sHxr">Ах, да, кстати, микросетки — все возможные кнопки и иконки мы тоже делаем на <code>inline-block</code>, тоже с вытекающими, в плане пробелов, нулевого размера шрифта и прочих костылей. Да, это иногда применимо, но тут это везде.</p>
  <p id="bcRL">Ладно, может это делается намеренно, ради «тренировки», я не знаю. Допускаю, что у меня бомбит от своего невежества и на самом деле флоат — это мастхэв (сомневаюсь).</p>
  <p id="JRzL"></p>
  <h3 id="U5qY">Макет</h3>
  <p id="D3g8">Едем дальше. На этом этапе нам дают уже не картинку JPG, а полноценный макет в PSD формате со слоями и элементами, что очень круто. Можно сказать, бесплатный практический макет с возможностью его проверить!</p>
  <p id="V6B1">Но макет полное говно. </p>
  <p id="u3wZ">Начиная от критичных погрешностей в расположении элементов, заканчивая неправильными, не подходящими параметрами текста. Я уже молчу про слои — это просто в кучу сваленные шейпы без подписей, без каких-то группировок.</p>
  <figure id="aHq5" class="m_original">
    <img src="https://img3.teletype.in/files/aa/b7/aab7c495-efac-4a0d-9e12-6a67612df791.png" width="333" />
  </figure>
  <p id="35TF">«Верстальщику не нужны слои», кто-то скажет. А я скажу — попробуйте выделить авто-селектом шейп толщиной 1px чтобы понять «задан ли ему такой тусклый цвет?» или «цвет с прозрачностью?» или «прозрачность задана самой фигуре?».</p>
  <p id="qC4g">Почему это меня беспокоит? Да потому, что все три варианта есть на макете. Часть рамок <code>border</code> в сплошном цвете, а часть в полупрозрачном. А учитывая, что прозрачность <code>opacity</code> можно задать еще и слою отдельно — работа с таким макетом превращается в то еще удовольствие. Сиди выясняй — это #404040 или это чёрный полупрозрачный?</p>
  <figure id="p5Z7" class="m_original">
    <img src="https://img3.teletype.in/files/a3/a4/a3a423cc-6dbb-445e-b0fc-dd32ffa4277d.png" width="666" />
    <figcaption>Здесь обводка черная, а у слоя прозрачность 30%. </figcaption>
  </figure>
  <figure id="WndP" class="m_original">
    <img src="https://img1.teletype.in/files/c8/e6/c8e6b50e-a95a-4d2d-9e08-173d910d179f.png" width="623" />
    <figcaption>А здесь это цвет такой прост))00))0</figcaption>
  </figure>
  <p id="uwfF">Но это всё фигня — дизайнеры разные бывают, тут ок, хотя я бы попросил всё-таки как-то привести в порядок это всё. Ладно.</p>
  <p id="lInl">А вот числовые ошибки уже как-то нихрена не «ладно». У всех заголовков, включая подзаголовки и разные акцентные надписи — в макете указано неверное значение высоты строки <code>line-height</code>.</p>
  <figure id="cR9z" class="m_original">
    <img src="https://img1.teletype.in/files/0c/07/0c07be8b-ed87-4d1e-b579-39e5dc494e3f.png" width="549" />
    <figcaption>Высота строки у заголовка секции в макете указана 61px</figcaption>
  </figure>
  <p id="eqJx">Везде указано либо 61px, либо там 41px/31px — уже лень искать. Но везде вот этот странный 1 пиксель сверху. Я сначала не придал значения, но при проверке у меня везде, во всех блоках, как бы я там не танцевал туда-сюда по 5px — в эталон проверки не попадало.</p>
  <p id="vgh8">Поменял все на 60px и надо же! Сработало! Почему я уверен, что это ошибка макета, а не я где-то упустил эти пиксели? После прохождения всех блоков — получаешь эталонный 100%-й вариант. И что бы вы думали?</p>
  <figure id="aAdM" class="m_original">
    <img src="https://img2.teletype.in/files/d2/de/d2de3d41-7f02-4699-a77a-9a512916bb92.png" width="218" />
  </figure>
  <p id="b6nP">И там такого дофига. Размеры таблиц практически наугад задаёшь, высоту полей формы в итоге легче задать фиксировано через <code>height</code>, положение бутылок во втором блоке тоже наугад. </p>
  <figure id="pPiC" class="m_original">
    <img src="https://img4.teletype.in/files/ff/95/ff95b9f6-5acd-474f-885b-5bac85bbfade.png" width="423" />
    <figcaption>Это один текстовый блок на две ячейки таблицы. Поди отсюда ноги и растут.</figcaption>
  </figure>
  <p id="8pSz">Ах да, еще — высота первого блока в макете тоже неправильная, а это влияет на фоновое изображение. Высота в макете 550px, тогда как высота блока которая должна быть оказывается 490px. Потому что.</p>
  <p id="K3N0">«Зачем так придираться?» — спросите вы. Никто же не будет требовать «pixel perfect» от учебного проекта? Тем более такого костыльного?</p>
  <p id="C8jU"></p>
  <h3 id="QbZE">Проверка задания</h3>
  <p id="k25U">Хрен там — скажу я вам! Проверка идёт так же по блокам страницы, но теперь проверяется еще сетка и оформление. Работает также — сравнивается по скрину наложением. Более того проверяется еще и вложенность.</p>
  <figure id="ZClC" class="m_original">
    <img src="https://img1.teletype.in/files/42/b3/42b3867d-8c22-46bf-b38f-aaa9eed74c62.png" width="576" />
  </figure>
  <p id="tI2M">То есть нужно попасть пикселями в эталон без погрешностей, попасть цветом и всеми стилевыми атрибутами и если по вложениям ты что-то поменял, то это тоже ошибка. Например, не забудьте, что лого должно быть в дополнительном контейнере и им обоим надо задать одинаковые размеры.</p>
  <p id="5NjT">В самой проверке тоже есть какие-то придурошные приколы — блок с формой заказа, в сетке явно указывает на дополнительный отступ сверху от текста с призывом, а во вкладке стилей все идеально подходит.</p>
  <figure id="4B9u" class="m_column">
    <img src="https://img2.teletype.in/files/d7/e3/d7e3bae3-c2ff-4eaa-bb50-63570aa1c814.png" width="971" />
    <figcaption>Если что – браузерные стили обнулены, дело не в них.</figcaption>
  </figure>
  <p id="lvYu">И такого там тоже дофига. Большую часть времени ты не занимаешься вёрсткой, ты занимаешься танцами с бубном на одном пальце левой ноги, чтобы понять какой куда там пиксель не попал и почему.</p>
  <p id="wEnb">В итоге я плюнул на это дело — тот случай когда перфекционизм нужно отключать и не трахать себе мозги. Выполнил, в среднем, все блоки и все вкладки на 96-97% и хрен с ними, себе дороже.</p>
  <figure id="IQjv" class="m_column">
    <img src="https://img2.teletype.in/files/58/19/5819de8d-d88d-481d-b7a9-eeb01474763f.png" width="988" />
  </figure>
  <p id="rM7a"></p>
  <h2 id="v6Fv">Итог</h2>
  <p id="G3x9">Самое обидное, что идея просто потрясающая! И сама реализация тоже крутая, но дьявол, сука, в мелочах. Ну почему всё в дивах? Зачем мы учили семантическую вёрстку до этого?</p>
  <p id="dNpU">Ну что мешало сделать задачу на флексах, пусть без всяких <code>flex-grow</code> и других модификаторов? Да если даже с ними, у нас фиксированный макет, с фиксированной шириной!</p>
  <p id="qRnf">Ну почему такой убогий макет? Почему в нём ошибки? Они намерено там есть? Типа как в некоторых школах специально дают не полную задачу, что бы тренировать ученика задавать уточняющие вопросы? Так это не про тренажёры! У кого тут спрашивать-то?</p>
  <p id="sJme">После прохождения Кексби, да еще и в комбо со спорным Кекстаграмом до этого, у меня появились сомнения стоит ли вообще в Академии продолжать учиться. Может и на курсах всё также? В любом случае, профессия «Фронтендера» меня там уже ждёт, поэтому никуда я уже не денусь.</p>
  <p id="K4kq"></p>
  <hr />
  <p id="E5vl"></p>
  <p id="hgD9">Вот такой вот поток эмоций вызвал у меня этот Кексби. Возможно, на момент выхода он был самый крутой, но сейчас мне прям вот вообще не понравилось.</p>
  <p id="iviB">Вам спасибо большое за внимание и прошу прощения за негатив! Надеюсь, вам хотя бы было весело почитать про мои страдания.</p>
  <p id="jX0N">Ссылки на другие статьи по HTML Academy:</p>
  <p id="r7tW"><a href="/intro-to-web-development">Знакомство с Веб-разработкой</a><br /><a href="/intro-html-css-part1">Знакомство с HTML и CSS</a><br /><a href="/intro-javascript">Знакомство с JavaScript</a><br /><a href="/intro-php">Знакомство с PHP</a><br /><a href="/advanced-html-css-part1">Таблицы и подробно о формах</a><br /><a href="/advanced-html-css-part2">Наследование, каскады и селекторы</a><br /><a href="/block-model-flow-and-float">Блочная модель, поток и сетка на float</a><br /><a href="/flexy-flexbox">Гибкие флексбоксы display: flex</a><br /><a href="/easy-grids">Удобные сетки на гридах display: grid</a><br /><a href="/why-skip-one-block">Пропуск блока «Погружение»</a><br /><a href="/position-and-transform">Позиционирование и двумерные трансформации</a><br /><a href="/box-shadow-linear-gradient">Теневое искусство и линейные градиенты</a><br /><a href="/css-filters-kekstagram">CSS-фильтры и Кекстаграм</a><br /><a href="/workshop-academy">Мастерские</a><br /><a href="https://blog.arkhelvetios.ru/advance-workshop-academy" target="_blank">Продвинутые Мастерские</a><br />…</p>
  <section>
    <p id="nhBq">Остальные статьи можно посмотреть у меня на <a href="/">главной странице</a> блога.</p>
  </section>
  <p id="scdz">Также мои соц. сетки, которые я продолжаю вести:</p>
  <p id="AxOF"><a href="https://twitter.com/arkhelvetios" target="_blank">Мой Twitter</a><br /><a href="https://t.me/arkhelvetios" target="_blank">Мой Telegram</a><br /><a href="https://vk.com/arkhelvetios" target="_blank">Мой Паблик ВК</a></p>
  <p id="Z3UO">Заходите куда удобно вам и подписывайтесь! Еще раз спасибо за внимание!</p>
  <tt-tags id="tYzB">
    <tt-tag name="html">#html</tt-tag>
    <tt-tag name="css">#css</tt-tag>
    <tt-tag name="html5">#html5</tt-tag>
    <tt-tag name="css3">#css3</tt-tag>
    <tt-tag name="frontend">#frontend</tt-tag>
    <tt-tag name="front_end">#front_end</tt-tag>
    <tt-tag name="обучение">#обучение</tt-tag>
    <tt-tag name="it">#it</tt-tag>
  </tt-tags>

]]></content:encoded></item></channel></rss>