Repository: ninelines-team/ninelines-docs Branch: master Commit: 2bcd745c733b Files: 29 Total size: 151.1 KB Directory structure: gitextract_sqxq8fj5/ ├── .gitignore ├── 01_technologies.md ├── 02_requirements.md ├── 03_installation.md ├── 04_tasks.md ├── 05_structure.md ├── 06_libraries.md ├── 07_images.md ├── 08_templates.md ├── 09_styles.md ├── 10_scripts.md ├── 11_resources.md ├── 13_pixel-perfect.md ├── 15_metatags.md ├── 16_codestyle-pug.md ├── 17_codestyle-scss.md ├── 18_codestyle-javascript.md ├── 19_video-js.md ├── 20_hls.md ├── 21_bem.md ├── 22_crossbrowser-adaptive.md ├── 23_perfomance.md ├── 24_git.md ├── 25_checklist.md ├── 26_short-checklist.md ├── 27_validation.md ├── 28_dynamic-share-for-spa.md ├── 29_helpers.md └── README.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .idea/ .DS_Store ================================================ FILE: 01_technologies.md ================================================ # Основные возможности и используемые технологии * Система сборки [Gulp](https://gulpjs.com/) * Оптимизация изображений. * Генерация PNG- и SVG-спрайтов. * Шаблонизация с помощью [Pug](https://pugjs.org/). * CSS-препроцессор [SCSS](http://sass-lang.com/) и [Autoprefixer](https://autoprefixer.github.io/ru/). * ES6 и [jQuery](https://jquery.com/). * Встроенное определение устройства, браузера и операционной системы пользователя. * Проверка кода линтерами ([pug-lint](https://www.npmjs.com/package/pug-lint), [stylelint](https://stylelint.io/), [ESLint](http://eslint.org/)). * [Browsersync](https://www.browsersync.io/), автоматическое обновление страницы при разработке. * Возможность быстро создать архив проекта. * Множество дополнительных параметров сборки. ================================================ FILE: 02_requirements.md ================================================ # Минимальные требования * node >= 9.5.0 * npm >= 5.6.0 * gulp >= 4.0.0 * gulp-cli >= 2.0.1 [Ссылка на инструкцию по переходу с gulp 3 на gulp 4](https://demisx.github.io/gulp4/2015/01/15/install-gulp4.html). ================================================ FILE: 03_installation.md ================================================ # Начало работы Для установки рекомендуется использовать [Yeoman](http://yeoman.io/): ```bash npm install -g yo ``` После yeoman, необходимо установить шаблон самой сборки: ```bash npm install -g generator-ninelines-template ``` Теперь находясь в пустой папке с проектом выполняем команду: ```bash yo ninelines-template ``` Генератор задаст несколько вопросов: - Название проекта (по умолчанию — название папки проекта). - Описание проекта. - Запустить ли `npm install` (по умолчанию — да). Если пропустить установку npm-пакетов, то перед началом работы над проектом необходимо самостоятельно запустить `npm install`. Теперь можно запустить `gulp` и приступить к работе. ================================================ FILE: 04_tasks.md ================================================ # Gulp-задачи * `default` — основная задача, запускает `build`, `watch` и `serve`. * `build` — сборка всех файлов, запускает задачи `copy`, `images`, `sprites:png`, `sprites:svg`, `pug`, `scss`, `js`. * `watch` — запускает слежение за файлами, так что при изменении они автоматически пересобираются. * `serve` — запускает сервер Browsersync. * `pug` — запускает сборку Pug-шаблонов. * `images` — запускает сборку изображений. * `sprites:png` — запускает генерацию PNG-спрайтов. * `sprites:svg` — запускает генерацию SVG-спрайтов. * `scss` — запускает сборку стилей. * `js` — запускает сборку скриптов. * `copy` — запускает сборку дополнительных ресурсов. * `lint` — последовательно запускает линтеры `lint:js`, `lint:pug`, `lint:scss`. * `lint:js` — проверяет JavaScript-файлы линтером [ESLint](http://eslint.org/). * `lint:pug` — проверяет Pug-файлы линтером [pug-lint](https://github.com/pugjs/pug-lint). * `lint:scss` — проверяет SCSS-файлы линтером [stylelint](https://stylelint.io/). * `optimize:svg` — оптимизирует и форматирует код SVG-файлов в папке `src/images`. * `optimize:images` — оптимизирует изображения в папке `src/images`. * `zip` — создает архив проекта. ## Дополнительные параметры: * `--ci` — включает режим CI (`--no-cache --no-notify --no-open --throw-errors`). * `--fix` — автоматически исправляет ошибки при проверке кода линтером (только для `lint:js`). * `--minify` — включает минификацию файлов (только для `sprites:svg`, `pug`, `scss` и `js`). * `--minify-html` — включает минификацию HTML-файлов (имеет приоритет перед `--minify`). * `--minify-css` — включает минификацию CSS-файлов (имеет приоритет перед `--minify`). * `--minify-js` — включает минификацию JS-файлов (имеет приоритет перед `--minify`). * `--minify-svg` — включает минификацию SVG-файлов (имеет приоритет перед `--minify`). * `--no-cache` — отключает кэширование (только для `copy`, `images` и `pug`). * `--no-debug` — отключает отладочный вывод списка обрабатываемых файлов. * `--no-notify` — отключает уведомления об ошибках. * `--no-open` — отключает автоматический запуск браузера (только для `serve`). * `--port` — задает порт сервера (только для `serve`). * `--spa` — включает режим одностраничного приложения (только для `serve`). * `--throw-errors` — прерывает сборку при возникновении ошибки. ================================================ FILE: 05_structure.md ================================================ # Структура папок и файлов ```text ninelines-template ├── src │   ├── images │   │   └── sprites │   │   ├── png │   │   │   └── .keep │   │   └── svg │   │   └── .keep │   ├── js │   │   ├── vendor │   │   │   └── .keep │   │   ├── main.js │   │   └── vendor.js │   ├── pug │   │   ├── mixins │   │   │   └── svg.pug │   │   ├── base.pug │   │   └── mixins.pug │   ├── resources │   │   └── fonts │   │   └── .keep │   ├── scss │   │   ├── functions │   │   │   └── _sprites.scss │   │   ├── mixins │   │   │   ├── _clearfix.scss │   │   │   ├── _retina.scss │   │   │   ├── _sprites.scss │   │   │   ├── _triangle.scss │   │   │   └── _visually-hidden.scss │   │   ├── vendor │   │   │   └── .keep │   │   ├── _base.scss │   │   ├── _fonts.scss │   │   ├── _functions.scss │   │   ├── _mixins.scss │   │   ├── _sprites.hbs │   │   ├── _sprites.scss │   │   ├── _variables.scss │   │   ├── _vendor.scss │   │   └── main.scss │   └── index.pug ├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .npmrc ├── .pug-lintrc.json ├── .stylelintignore ├── .stylelintrc ├── gulpfile.js ├── package.json ├── README.md └── webpack.config.js ``` ## `src` В папке `src` хранятся исходные файлы проекта. ## `src/images` Папка `images` предназначена для хранения изображений. При сборке файлы из данной папки попадают в `build/images`. ## `src/images/sprites` Папка `src/images/sprites` предназначена для хранения векторных (SVG) и растровых (PNG) иконок. ## `src/images/sprites/png` Папка `src/images/sprites/png` предназначена для хранения растровых иконок. При сборке файлы из данной папки объединяются в два спрайта: `build/images/sprites.png` и `build/images/sprites@2x.png`. ## `src/images/sprites/svg` Папка `src/images/sprites/svg` предназначена для хранения векторных иконок. При сборке файлы из данной папки объединяются в один спрайт: `build/images/sprites.svg`. ## `src/js` Папка `src/js` предназначена для хранения скриптов. ## `src/js/vendor` Папка `src/js/vendor` предназначена для хранения скриптов сторонних библиотек, которых нет в репозитории npm. ## `src/js/main.js` Файл `src/js/main.js` предназначен для хранения основной логики сайта. При сборке данный файл попадает в `build/js`. ## `src/js/vendor.js` Файл `src/js/vendor.js` предназначен для подключения сторонних библиотек. При сборке данный файл попадет в `build/js`. ## `src/pug` Папка `src/pug` предназначена для хранения шаблонов. ## `src/pug/mixins` Папка `src/pug/mixins` предназначена для хранения Pug-миксин. ## `src/pug/base.pug` В файле `src/pug/base.pug` хранится базовый шаблон страниц сайта. ## `src/pug/mixins.pug` Файл `src/pug/mixins.pug` предназначен для подключения Pug-миксин из папки `src/pug/mixins`. ## `src/resources` Папка `src/resources` предназначена для хранения различных файлов проекта. При сборке файлы из данной папки попадают в `build`. ## `src/resources/fonts` Папка `src/resources/fonts` предназначена для хранения шрифтов. При сборке файлы из данной папки попадают в `build/fonts`. ## `src/scss` Папка `src/scss` предназначена для хранения стилей. ## `src/scss/functions` Папка `src/scss/functions` предназначена для хранения SCSS-функций. ## `src/scss/mixins` Папка `src/scss/mixins` предназначена для хранения SCSS-миксин. ## `src/scss/vendor` Папка `src/scss/vendor` предназначена для хранения стилей сторонних библиотек, которых нет в репозитории npm. ## `src/scss/_base.scss` Файл `src/scss/_base.scss` предназначен для хранения базовых стилей. ## `src/scss/_fonts.scss` Файл `src/scss/_fonts.scss` предназначен для подключения шрифтов. ## `src/scss/_functions.scss` Файл `src/scss/_functions.scss` предназначен для подключения функций из папки `src/scss/functions`. ## `src/scss/_mixins.scss` Файл `src/scss/_mixins.scss` предназначен для подключения миксин из папки `src/scss/mixins`. ## `src/scss/_sprites.hbs` `src/scss/_sprites.hbs` — шаблон, на основе которого генерируется содержимое файла `src/scss/_sprites.scss`. ## `src/scss/_sprites.scss` Файл `src/scss/_sprites.scss` предназначен для работы с PNG-спрайтами. Содержимое данного файла автоматически генерируется на основе шаблона `src/scss/_sprites.hbs` и иконок из папки `src/images/sprites/png`. ## `src/scss/_variables.scss` Файл `src/scss/_variables.scss` предназначен для хранения SCSS-переменных. ## `src/scss/_vendor.scss` Файл `src/scss/_vendor.scss` предназначен для подключения стилей сторонних библиотек. ## `src/scss/main.scss` Файл `src/scss/main.scss` предназначен для хранения основных стилей сайта. При сборке данный файл преобразуется в CSS и сохраняется в `build/css` вместе с файлом `main.css.map`. ## `src/index.pug` `src/index.pug` — шаблон главной страницы. При сборке все Pug-файлы из папки `src` преобразуются в HTML и сохраняются в `build`. ## `.babelrc` `.babelrc` — файл настроек JavaScript-транспайлера Babel. ## `.editorconfig` `.editorconfig` — файл настроек редактора. ## `.eslintignore` `.eslintignore` — файл настроек ESLint для игнорирования файлов. ## `.eslintrc` `.eslintrc` — файл настроек ESLint. ## `.gitignore` `.gitignore` — файл настроек Git для игнорирования файлов. ## `.npmrc` `.npmrc` — файл настроек npm. ## `.pug-lintrc.json` `.pug-lintrc.json` — файл настроек pug-lint. ## `.stylelintignore` `.stylelintignore` — файл настроек stylelint для игнорирования файлов. ## `.stylelintrc` `.stylelintrc` — файл настроек stylelint. ## `gulpfile.js` `gulpfile.js` — основной файл сборки, содержащий Gulp-задачи. ## `package.json` `package.json` — файл, содержащий базовую информацию о проекте и список требуемых библиотек. ## `README.md` `README.md` — описание проекта. ## `webpack.config.js` `webpack.config.js` — файл настроек webpack. ================================================ FILE: 06_libraries.md ================================================ # Подключение сторонних библиотек Библиотеки подключаются с помощью npm. При установке следует указывать ключ `--save` или `--save-dev`. Пример: ```bash npm install --save jquery npm install --save-dev gulp ``` `--save` указывается для библиотек, код которых попадает в итоговую сборку (папку `build`) и будет использоваться на сайте. `--save-dev` указывается для библиотек, которые используются только для сборки. После установки необходимо подключить нужные файлы библиотеки: * скрипты — в `src/js/vendor.js` или `src/js/main.js`. * стили — в `src/scss/_vendor.scss`. * изображения — в `src/images`. * любые другие файлы — в `src/resources`. Полный пример, описывающий установку библиотеки fancybox: 1. Установка: ```bash npm install --save fancybox ``` 2. Подключение скриптов в файл `src/js/vendor.js`: ```js import 'fancybox'; ``` 3. Подключение стилей в файл `src/scss/_vendor.scss`: ```scss $fancybox-image-url: "../images"; @import "../../node_modules/fancybox/dist/scss/jquery.fancybox"; ``` 4. Копирование изображений в `src/images`: ```text ninelines-template └── src ├── images │   ├── blank.gif │   ├── fancybox_loading.gif │   ├── fancybox_loading@2x.gif │   ├── fancybox_overlay.png │   ├── fancybox_sprite.png │   ├── fancybox_sprite@2x.png │   └── ... └── ... ``` Если библиотека отсутствует в npm, либо её нужно модифицировать, то файлы следует скачать и закинуть в папки `src/js/vendor` и `src/scss/vendor`. ================================================ FILE: 07_images.md ================================================ # Работа с изображениями Изображения следует хранить в папке `src/images`. При запуске задачи `images` файлы из папки `src/images` копируются в `build/images`. ```text ninelines-template ├── build │   └── images └── src └── images ``` Для оптимизации изображений можно использовать задачу `optimize:images`. > `optimize:images` оптимизирует только исходные файлы из папки `src/images`! Предварительно оптимизированные изображения рекомендуется хранить в папке `src/resources/images`. В таком случае при запуске задачи `optimize:images` данные файлы не будут затронуты. ```text ninelines-template └── src └── resources └── images ``` ## Работа с PNG-спрайтами Работа с PNG-спрайтами строится следующим образом: 1. Берем две версии иконки — обычную и retina (увеличенную в два раза). Сохраняем в `src/images/sprites/png`: ```text ninelines-template └── src    └── images       └── sprites       └── png          ├── phone.png          └── phone@2x.png ``` 2. Запускаем задачу `sprites:png` (если уже запущен `gulp watch` или `gulp`, то данный шаг можно пропустить): ```bash gulp sprites:png ``` 3. Генератор оптимизирует и объединяет иконки в спрайты: ```text ninelines-template └── build    └── images ├── sprites.png └── sprites@2x.png ``` На основе предзаданного шаблона `src/scss/_sprites.hbs` генерируется файл `src/scss/_sprites.scss`, содержащий вспомогательную информацию о получившихся спрайтах: ```text ninelines-template └── src    └── scss ├── _sprites.hbs └── _sprites.scss ``` Для каждой иконки создается CSS-класс в формате `.sprite-[name]`. В нашем случае получим класс `.sprite-phone`. В сборке также содержится ряд SCSS-функций и миксин для работы со спрайтами. `src/scss/functions/_sprites.scss`: ```scss @function sprite($name, $size: normal) { /* ... */ } @function sprite-width($name, $size: normal) { /* ... */ } @function sprite-height($name, $size: normal) { /* ... */ } @function sprite-image($name, $size: normal) { /* ... */ } @function sprite-x($name, $size: normal) { /* ... */ } @function sprite-y($name, $size: normal) { /* ... */ } @function sprite-total-width($name, $size: normal) { /* ... */ } @function sprite-total-height($name, $size: normal) { /* ... */ } ``` `src/scss/mixins/_srites.scss`: ```scss @mixin sprite-width($name, $size: normal) { /* ... */ } @mixin sprite-height($name, $size: normal) { /* ... */ } @mixin sprite-background-image($name, $size: normal) { /* ... */ } @mixin sprite-background-position($name, $size: normal) { /* ... */ } @mixin sprite-background-size($name, $size: normal) { /* ... */ } @mixin sprite-background($name, $size: normal) { /* ... */ } @mixin sprite($name) { /* ... */ } ``` 4. Полученные спрайты можно использовать в Pug (с помощью классов): ```jade footer a(href="tel:+71234567890") span.sprite-phone | +7 (123) 456-78-90 ``` Или в SCSS (с помощью миксин): ```scss footer { a { &::before { @include sprite("phone"); content: ""; } } } ``` ## Работа с SVG-спрайтами Принцип работы с SVG-спрайтами: 1. Получаем векторные иконки в формате `.svg` (либо заранее подготовленные, либо экспортируем с помощью редактора). Сохраняем в папку `src/images/sprites/svg`: ```text ninelines-template └── src    └── images       └── sprites       └── svg          └── phone.svg ``` 2. Запускаем задачу `sprites:svg` (если уже запущен `gulp watch` или `gulp`, то данный шаг можно пропустить): ```bash gulp sprites:svg ``` 3. Генератор оптимизирует и объединяет иконки в один спрайт: ```text ninelines-template └── build    └── images └── sprites.svg ``` В сборке содержится Pug-миксин для подключения SVG-спрайтов.
`src/pug/mixins/svg.pug`: ```jade mixin svg(name) svg&attributes(attributes) use(xlink:href=`${baseDir}images/sprites.svg#${name}`) ``` 4. Подключаем иконку в Pug: ```jade footer a(href="tel:+71234567890") +svg("phone") | +7 (123) 456-78-90 ``` При необходимости иконку можно стилизовать: ```scss footer { a { svg { display: inline-block; vertical-align: middle; width: 30px; height: 30px; fill: $color-black; } } } ``` Если цвет заливки или обводки не удается изменить с помощью CSS, то необходимо открыть SVG-файл иконки в редакторе и удалить соответствующие атрибуты (`fill`, `stroke`) из кода. ## Избавляемся от обрезанных краев SVG-иконок 1. Общий пример: Шаг 1: исходная иконка без полей ```html ``` Шаг 2: добавляем поле размером {padding} ```html ``` Шаг 3: запускаем optimize:svg и получаем иконку без лишних трансформаций ```html ``` 2. Конкретный пример: Шаг 1: исходная иконка без полей ```html ``` Шаг 2: добавляем поле размером 1px ```html ``` Шаг 3: запускаем optimize:svg и получаем иконку без лишних трансформаций ```html ``` ================================================ FILE: 08_templates.md ================================================ # Работа с шаблонизатором Pug > При работе с шаблонизатором **важно** придерживаться установленных [правил по оформлению кода](16_codestyle-pug.md). В сборке используется шаблонизатор [Pug](https://pugjs.org/) (ранее назывался Jade). Pug предоставляет множество возможностей, упрощающих работу с шаблонами: * Переменные. * Циклы. * Условия. * Фильтры. * Наследование шаблонов. * Миксины. Шаблоны страниц размещаются в `src`, а дополнительные файлы и миксины в `src/pug`: ```text ninelines-template └── src    ├── pug    │   ├── mixins    │   │   └── svg.pug    │   ├── base.pug    │   └── mixins.pug    └── index.pug ``` За сборку и преобразование Pug в HTML отвечает задача `pug`: ```bash gulp pug ``` После выполнения команды в папке `build` появятся HTML-файлы: ```text ninelines-template └── build    └── index.html ``` ## Базовый шаблон и создание страниц В качестве базового шаблона используется `src/pug/base.pug`. Пример наследования и использования шаблона: ```jade extends pug/base block content // Содержимое страницы ``` Базовый шаблон определяет блоки (участки кода или место в шаблоне), которые можно изменять и дополнять при наследовании. ### `vars` Блок `vars` хранит основные настройки шаблона: * `baseDir` — корневая директория сайта (по умолчанию `/`). * `title` — заголовок страницы (используется в `` и метатегах). * `description` — описание страницы (используется в метатегах). * `image` — изображение страницы (используется в метатегах). * `html` — настройки тега `<html>`: * `html.attrs` — объект для задания дополнительных атрибутов. * `html.classList` — массив классов. * `body` — настройки тега `<body>`: * `body.attrs` — объект для задания дополнительных атрибутов. * `body.classList` — массив классов. * `meta` — значения метатегов. * `link` — значения тегов `<link>`. Пример использования: ```jade prepend vars - title = 'Заголовок' - description = 'Описание' - image = 'http://example.com/images/image.png' append vars - link.icon16x16 = '/favicon_16x16.png' - link.icon32x32 = '/favicon_32x32.png' ``` ### `head-start` Блок `head-start` является альтернативой `prepend meta`. ### `meta` В блоке `meta` подключаются метатеги. Пример использования: ```jade append meta meta(name="referrer" content="none") ``` ### `links` В блоке `links` подключаются внешние ресурсы. Пример использования: ```jade append links link(rel="prefetch" href="/images/background.jpg") ``` ### `styles` В блоке `styles` подключаются стили. Пример использования: ```jade append styles link(rel="stylesheet" href="/css/custom.css") ``` ### `head-end` Блок `head-end` является альтернативой `append links`. ### `body-start` Блок `body-start` является альтернативой `prepend content`. ### `content` Блок `content` предназначен для хранения содержимого страницы. Пример использования: ```jade block content .container h1 | Заголовок страницы ``` ### `scripts` В блоке `scripts` подключаются скрипты. Пример использования: ```jade append scripts script(src="/js/custom.js") ``` ### `body-end` Блок `body-end` является альтернативой `append scripts`. ## Правила написания кода и использование линтера В сборку интегрирован линтер [pug-lint](https://www.npmjs.com/package/pug-lint). Файл настроек — `.pug-lintrc.json`. Данный линтер позволяет поддерживать Pug-код в соответствии с заданным регламентом. Проверка осуществляется с помощью задачи `lint:pug`. Пример использования (`src/index.pug`): ```jade extends pug/base append vars - html.classList.push('page-index') block content a(href='#').link Ссылка ``` Результаты проверки: ```text ninelines-template/src/index.pug:7:14 5| 6| block content > 7| a(href='#').link Ссылка --------------------^ 8| All class literals must be written before any attribute blocks ninelines-template/src/index.pug:7:5 5| 6| block content > 7| a(href='#').link Ссылка -----------^ 8| Invalid attribute quote mark found ninelines-template/src/index.pug:4:1 2| 3| append vars > 4| - html.classList.push('page-index') -------^ 5| 6| block content 7| a(href='#').link Ссылка Invalid indentation ninelines-template/src/index.pug:7:1 5| 6| block content > 7| a(href='#').link Ссылка -------^ 8| Invalid indentation ``` Исправленный код: ```jade extends pug/base append vars - html.classList.push('page-index') block content a.link(href="#") | Ссылка ``` В дополнение к проверкам кода линтером следует придерживаться следующих правил: * Повторяющиеся участки кода по возможности выносить в отдельные миксины. * Схожие по структуре страницы выносить в отдельный шаблон и наследоваться от него. ================================================ FILE: 09_styles.md ================================================ # Работа со стилями > При работе со стилями **важно** придерживаться установленных [правил по оформлению кода](17_codestyle-scss.md). В сборке используется препроцессор [SCSS](http://sass-lang.com/) и PostCSS-плагин [Autoprefixer](https://autoprefixer.github.io/ru/). Стили размещаются в папке `src/scss`: ```text ninelines-template └── src    └── scss       ├── functions       │   ├── _responsive.scss │   └── _sprites.scss       ├── mixins       │   ├── _breakpoint.scss       │   ├── _clearfix.scss       │   ├── _retina.scss │   ├── _sprites.scss │   ├── _triangle.scss │   └── _visually-hidden.scss       ├── vendor       │   └── .keep       ├── _base.scss       ├── _fonts.scss       ├── _functions.scss       ├── _mixins.scss       ├── _sprites.hbs       ├── _sprites.scss       ├── _variables.scss       ├── _vendor.scss       └── main.scss ``` За сборку и преобразование SCSS в CSS отвечает задача `scss`: ```bash gulp scss ``` После выполнения команды в папке `build/css` появятся файлы `main.css` и `main.css.map`: ```text ninelines-template └── build    └── css       ├── main.css       └── main.css.map ``` ## Правила написания кода ### БЭМ Для именования классов рекомендуется использовать [БЭМ-нотацию](https://ru.bem.info/methodology/naming-convention/). ```scss .block { &__element { &--modificator { // ... } } } ``` ### Классы состояний Классы состояний рекомендуется записывать кратко: ```scss .is-active { // ... } .is-current { // ... } .is-open { // ... } .is-hidden { // ... } ``` ### Порядок CSS-свойств CSS-свойства следует записывать в определенном порядке. Порядок задан в файле `.stylelintrc` (ключ `order/properties-order`). Проверить правильность порядка свойств можно с помощью линтера: ```bash gulp lint:scss ``` ### Переменные В файл `src/scss/_variables.scss` следует выносить лишь основные переменные: * `font-family` для шрифтов. Пример: ```scss $font-family-roboto: Roboto, sans-serif; $font-family-pt-serif: PT Serif, serif; ``` * Цвета. Пример: ```scss $color-aqua-deep: #005741; $color-black: #000; $color-white: #fff; ``` Для именования цветов можно пользоваться [данным сервисом](http://chir.ag/projects/name-that-color/). Переменные, используемые лишь в одном блоке или компоненте следует записывать в том же файле, где они используются. ### `@mixin` и `@extend` Повторяющиеся участки кода (20-30 строк и более), отличающиеся лишь значениями, следует выносить в отдельные миксины. Не рекомендуется использовать директиву `@extend`. Вместо неё следует воспользоваться `@mixin`. ### Вендорные префиксы В SCSS-коде не должно присутствовать вендорных префиксов. Они автоматически расставляются в процессе сборки. Однако существуют исключения и некоторые префиксы необходимо добавлять вручную. **Неправильно:** ```scss input { -webkit-transition: border-color 0.3s; transition: border-color 0.3s; &::-webkit-input-placeholder { color: #000; } &:-moz-placeholder { color: #000; } &::-moz-placeholder { color: #000; } &:-ms-input-placeholder { color: #000; } &::placeholder { color: #000; } } ``` **Правильно:** ```scss input { transition: border-color 0.3s; &::placeholder { color: #000; } } ``` ## Использование линтера В сборку интегрирован линтер [stylelint](https://stylelint.io/). Файл настроек — `.stylelintrc`. Данный линтер позволяет поддерживать SCSS-код в соответствии с заданным регламентом. Проверка осуществляется с помощью задачи `lint:scss`: ```bash gulp lint:scss ``` Пример использования: ```scss .block { &__element { display: inline-block } border-radius: 0px; height: 30px; width:30px; } ``` Результаты проверки: ```text 2:3 ⚠ Expected indentation of 1 tab (indentation) [stylelint] 3:5 ⚠ Expected indentation of 2 tabs (indentation) [stylelint] 3:25 ⚠ Expected a trailing semicolon (declaration-block-trailing-semicolon) [stylelint] 4:3 ⚠ Expected indentation of 1 tab (indentation) [stylelint] 5:3 ⚠ Expected indentation of 1 tab (indentation) [stylelint] 5:3 ⚠ Expected declaration to come before rule (order/order) [stylelint] 5:3 ⚠ Expected empty line before declaration (declaration-empty-line-before) [stylelint] 5:19 ⚠ Unexpected unit (length-zero-no-unit) [stylelint] 6:3 ⚠ Expected indentation of 1 tab (indentation) [stylelint] 7:3 ⚠ Expected indentation of 1 tab (indentation) [stylelint] 7:3 ⚠ Expected "width" to come before "height" (order/properties-order) [stylelint] 7:9 ⚠ Expected single space after ":" with a single-line declaration (declaration-colon-space-after) [stylelint] ``` Исправленный код: ```scss .block { border-radius: 0; width:30px; height: 30px; &__element { display: inline-block } } ``` ================================================ FILE: 10_scripts.md ================================================ # Работа со скриптами > При работе со скриптами **важно** придерживаться установленных [правил по оформлению кода](18_codestyle-javascript.md). Скрипты размещаются в папке `src/js`: ```text ninelines-template └── src    └── js       ├── vendor       │   └── .keep       ├── main.js       └── vendor.js ``` За сборку и преобразование JS отвечает задача `js`: ```bash gulp js ``` После выполнения команды в папке `build/js` появятся файлы `main.js` и `vendor.js`: ```text ninelines-template └── build    └── js       ├── main.js       └── vendor.js ``` Также дополнительно подключены библиотеки: * [jQuery](https://jquery.com/) * [ninelines-ua-parser](https://github.com/ninelines-team/ninelines-ua-parser) [ninelines-ua-parser](https://github.com/ninelines-team/ninelines-ua-parser) основана на [ua-parser-js](https://github.com/faisalman/ua-parser-js) и отвечает за определение устройства, браузера и операционной системы пользователя, а также автоматически проставляет классы `<html>` элементу: * `.is-os-mac-os` * `.is-os-windows` * `.is-os-linux` * `.is-os-android` * `.is-os-ios` * `.is-device-mobile` * `.is-device-tablet` * `.is-device-desktop` * `.is-engine-webkit` * `.is-engine-gecko` * `.is-browser-chrome` * `.is-browser-firefox` * `.is-browser-ie` * `.is-browser-safari` Данные классы можно использовать для стилизации элементов: ```scss .for-desktop { .is-device-mobile & { display: none; } } ``` ## Правила написания кода ### Короткие именна переменных Не следует сокращать имена переменных. **Неправильно:** ```js $('.elements').each((i, e) => { // ... }); ``` **Правильно:** ```js $('.elements').each((index, element) => { // ... }); ``` Исключение могут составить имена счетчиков в цикле (`i`, `j`, `k`): ```js for (let i = 0; i < 10; i++) { // ... } ``` ### Именование jQuery-переменных Название переменных, являющихся jQuery-объектами, следует начинать с `$`. **Неправильно:** ```js let element = $('.element'); ``` **Правильно:** ```js let $element = $('.element'); ``` ### jQuery-селекторы Следует избегать дублирования jQuery-селекторов. Если обращение к элементу происходит многократно, то jQuery-объект можно сохранить в отдельную переменную, либо переписать код так, чтобы избежать дублирования. **Неправильно:** ```js $('.element').on('click', () => { // ... }); $('.element').on('mouseenter', () => { // ... }); ``` **Правильно:** ```js let $element = $('.element'); $element.on('click', () => { // ... }); $element.on('mouseenter', () => { // ... }); ``` Или так: ```js $('.element') .on('click', () => { // ... }) .on('mouseenter', () => { // ... }); ``` ### Обработка событий с помощью jQuery Для создания обработчика событий следует использовать функцию [`.on()`](http://api.jquery.com/on/). **Неправильно:** ```js $('button').click(() => { // ... }); $('form').submit(() => { // ... }); ``` **Правильно:** ```js $('button').on('click', () => { // ... }); $('form').on('submit', () => { // ... }); ``` ## Использование линтера В сборку интегрирован линтер [ESLint](http://eslint.org/). Файл настроек — `.eslintrc`. Данный линтер позволяет поддерживать JavaScript-код в соответствии с заданным регламентом. Проверка осуществляется с помощью задачи `lint:js`: ```bash gulp lint:js ``` Пример использования: ```js var $form = $('.form') $form.on("submit", function () { $.post('ajax.php', function (data) { $(".result").html(data); }) }) ``` Результаты проверки: ```text 1:1 error Expected blank line after variable declarations newline-after-var 1:1 error Unexpected var, use let or const instead no-var 1:23 error Missing semicolon semi 2:10 error Strings must use singlequote quotes 2:20 error Unexpected function expression prefer-arrow-callback 2:20 warning Unexpected unnamed function func-names 3:1 error Expected indentation of 1 tab but found 2 spaces indent 3:22 warning Unexpected unnamed function func-names 3:22 error Unexpected function expression prefer-arrow-callback 4:1 error Expected indentation of 2 tabs but found 4 spaces indent 4:7 error Strings must use singlequote quotes 5:1 error Expected indentation of 1 tab but found 2 spaces indent 5:5 error Missing semicolon semi 6:3 error Missing semicolon semi ✖ 14 problems (12 errors, 2 warnings) 12 errors, 0 warnings potentially fixable with the `--fix` option. ``` ESlint сообщает о 14 найденных ошибках, причем большая часть из них может быть исправлена автоматически. За это отвечает ключ `--fix`, который можно указать при запуске задачи `lint:js`: ```bash gulp lint:js --fix ``` Исправленный код: ```js let $form = $('.form'); $form.on('submit', () => { $.post('ajax.php', (data) => { $('.result').html(data); }); }); ``` ================================================ FILE: 11_resources.md ================================================ # Работа с дополнительными ресурсами Дополнительными ресурсами считается все то, что не попадает ни под одну из предыдущих категорий файлов. Это могут быть различные favicon, шрифты, аудио, видео, документы и прочее. Подобные файлы следует хранить в папке `src/resources`. ```text ninelines-template └── src    └── resources       └── ... ``` Задача `copy` копирует содержимое папки `src/resources` в `build`: ```bash gulp copy ``` ## Работа со шрифтами Подключить шрифт можно двумя способами: * CDN ([Google Fonts](https://fonts.google.com/)) * Конвертировать и подключить с помощью [`@font-face`](https://developer.mozilla.org/ru/docs/Web/CSS/@font-face). ### Подключение шрифта с помощью Google Fonts Если шрифт и его требуемая языковая версия имеются в Google Fonts, то данный вариант подключения является приоритетным. Порядок подключения шрифта на примере Roboto: 1. Находим шрифт — [Roboto](https://fonts.google.com/specimen/Roboto) 2. Нажимаем `Select this font`. 3. Открываем появившееся снизу экрана окно. 4. Во вкладке `Customize` выбираем нужное начертание и языковую версию. 5. Во вкладке `Embed` переключаемся в `@import` и копируем содержимое тега `<style>`. 6. В файл `src/scss/_fonts.scss` вставляем скопированный `@import`. 7. В файл `src/scss/_variables.scss` добавляем переменную `$font-family-roboto`. 8. Используем переменную в стилях. ### Конвертирование шрифта и подключение с помощью `@font-face` Если шрифт отсутствует в Google Fonts, или нет подходящей языковой версии (отсутствует кириллица), то необходимо получить файл шрифта. Требуемые форматы — `.woff` (обязательно) и `.woff2` (опционально). Файлы `.ttf`, `.otf` или `.eot` можно сконвертировать в `.woff` и `.woff2` с помощью онлайн сервисов: * [onlinefontconverter.com](https://onlinefontconverter.com/) * [everythingfonts.com](https://everythingfonts.com/) Полученные файлы следует хранить в папке `src/resources/fonts`: ```text ninelines-template └── src    └── resources       └── fonts       └── ... ``` Подключение шрифтов происходит в файле `src/scss/_fonts.scss`: ```scss @font-face { src: url("../fonts/my-font-regular.woff2") format("woff2"), url("../fonts/my-font-regular.woff") format("woff"); font-family: "My Font"; font-weight: 400; font-style: normal; } ``` При наличии множества начертаний шрифты следует подключать в следующем порядке: 1. 100 normal (thin) 2. 100 italic (thin italic) 3. 200 normal (extra light) 4. 200 italic (extra light italic) 5. 300 normal (light) 6. 300 italic (light italic) 7. 400 normal (regular) 8. 400 italic (regular italic) 9. 500 normal (medium) 10. 500 italic (medium italic) 11. 600 normal (semi bold) 12. 600 italic (semi bold italic) 13. 700 normal (bold) 14. 700 italic (bold italic) 15. 800 normal (extra bold) 16. 800 italic (extra bold italic) 17. 900 normal (heavy) 18. 900 italic (heavy italic) Пример: ```scss @font-face { src: url("../fonts/my-font-light.woff") format("woff"); font-family: "My Font"; font-weight: 300; font-style: normal; } @font-face { src: url("../fonts/my-font-light-italic.woff") format("woff"); font-family: "My Font"; font-weight: 300; font-style: italic; } @font-face { src: url("../fonts/my-font-regular.woff") format("woff"); font-family: "My Font"; font-weight: 400; font-style: normal; } @font-face { src: url("../fonts/my-font-regular-italic.woff") format("woff"); font-family: "My Font"; font-weight: 400; font-style: italic; } @font-face { src: url("../fonts/my-font-bold.woff") format("woff"); font-family: "My Font"; font-weight: 700; font-style: normal; } @font-face { src: url("../fonts/my-font-bold-italic.woff") format("woff"); font-family: "My Font"; font-weight: 700; font-style: italic; } ``` После подключения шрифта в файл `src/scss/_variables.scss` следует добавить переменную в формате `$font-family-[name]`. Пример: ```scss $font-family-my-font: "My Font", sans-serif; ``` Затем переменную можно использовать в любом SCSS-файле: ```scss body { font-family: $font-family-my-font; } ``` ================================================ FILE: 13_pixel-perfect.md ================================================ # Pixel-perfect или верстка в соответствии с макетом Pixel-perfect — техника верстки, при которой результат максимально совпадает с исходным макетом. Под этим понимается соответствие: * положения элементов; * размеров; * отступов; * цветов; * шрифтов; * размеров текста; * межстрочного и межбуквенного интервалов. Что не входит в pixel-perfect: * Аболютное соответствие макету. Несмотря на свое название, техника pixel-perfect не подразумевает стопроцентного совпадения с макетом. Различия могут быть и будут. Дизайнер при создании макета может сделать что-то «на глаз» или просто ошибиться: сбиться с сетки, выровнять слой немного не по центру, использовать не тот шрифт или размер текста, подобрать цвет не по палитре и так далее. Не нужно повторять того же в верстке: * Если положение элемента отклоняется от сетки или других подобных элементов, то его следует выровнять. * Если элемент почти по центру, то вероятно он должен быть строго по центру. * Если есть подозрение, что в каком-то месте макета сбился шрифт, то скорее всего он действительно сбился, и вместо него должен использоваться другой. * Если несколько цветов, используемых в макете, незначительно отличаются HEX-кодом и неотличимы на глаз, то вероятно это должен быть один цвет. Также стоит учесть различный рендеринг страницы в различных средах. Операционные системы по-разному отображают текст. Графические редакторы и браузеры по-разному отображают текст. Браузеры по-разному интерпретируют дробные значения. Все эти факторы в сумме не позволяют достичь абсолютного соответствия макету. Погрешности от нескольких пикселей до нескольких десятков пикселей вполне допустимы. * Соответствие макету на всех возможных разрешениях. Нарисовать макет для всех возможных разрешений крайне затруднительно. И уж тем более затруднительно сверстать все это многообразие. На деле верстальщик получает лишь несколько основных макетов: * десктопная версия; * планшетная версия (очень редко); * мобильная версия. На эти имеющиеся макеты и следует ориентироваться. Применение pixel-perfect к промежуточным размерам сайта недопустимо. ## Инструменты для работы с pixel-perfect Одним из лучших инструментов для pixel-perfect верстки является плагин [PerfectPixel](http://www.welldonecode.com/perfectpixel/). Преимущества плагина: * Поддержка основными браузерами: Chrome, Firefox, Opera (в планах поддержка Safari, IE и Edge). * Нет необходимости ставить дополнительный код на сайт. * Удобный интерфейс. * Возможность позиционировать и масштабировать слой, изменять прозрачность. * Функция блокировки слоя. * Режим инверсии (позволяет проще всего увидеть отличия от макета). Нет необходимости устанавливать данный плагин во все браузеры одновременно. В основном при разработке используется Chrome, поэтому достаточно установить плагин только в него, а остальные браузеры проверить визуально на глаз. Также необходимо учесть, что на разных операционных системах сайт может отображаться с небольшыми различиями. Например проверив верстку на соответствие макету в ОС Windows, используя Chrome вы получили идеальное соответствие с макетом. Далее, вы открываете сайт в Mac OS, используя Chrome и видите, что есть небольшие расхождения. Если данные рахождения как-то влияют на визуальное восприятие и изменяют структуру сайта, то их необходимо поправить, в других случаях - это считается нормой. ================================================ FILE: 15_metatags.md ================================================ # Работа с метатегами В данном разделе приведен список основных метатегов `<meta>` и `<link>` с примерами. Менее значащие или устаревшие метатеги намеренно не указаны. ## Базовые метатеги Базовые метатеги задают основную информацию о сайте. ### `charset` Задает кодировку страницы. > Значение по умолчанию в сборке — `utf-8`. Пример: ```jade append vars - meta.charset = 'utf-8' ``` ### `description` Задает описание страницы. > Оптимальная длинна описания — не больше 160 символов. Пример: ```jade append vars - meta.description = 'Описание страницы' ``` Или так: ```jade prepend vars - description = 'Описание страницы' ``` При этом также будут определены значения `og:description` и `twitter:description`. ### `keywords` Задает список ключевых слов. Пример: ```jade append vars - meta.keywords = ['список', 'ключевых', 'слов'] ``` Или так: ```jade append vars - meta.keywords = 'список, ключевых, слов' ``` ### `title` Задает заголовок страницы (`<title>`). Пример: ```jade prepend vars - title = 'Заголовок страницы' ``` При этом также будут определены значения `og:title` и `twitter:title`. ### `viewport` Позволяет управлять отображением страницы на мобильных устройствах. > Значение по умолчанию в сборке — `width=device-width`. Пример: ```jade append vars - meta.viewport = 'width=device-width, initial-scale=1' ``` ### `icon` Задает favicon сайта. Пример: ```jade append vars - link.icon = 'favicon.ico' ``` Также можно задать иконки разных размеров: ```jade append vars - link.icon16x16 = 'favicon-16x16.png' - link.icon32x32 = 'favicon-32x32.png' - link.icon96x96 = 'favicon-96x96.png' - link.icon128x128 = 'favicon-128x128.png' - link.icon196x196 = 'favicon-196x196.png' ``` ### `manifest` Задает ссылку на `manifest.json`. Пример: ```jade append vars - link.manifest = '/manifest.json' ``` Манифест веб-приложения предоставляет информацию о приложении (такую как имя, авторство, иконку и описание) в формате JSON-файла. Цель манифеста — установить веб-приложение на рабочий стол устройства, предоставляя более быстрый доступ. ### `format-detection` Позволяет отключить определение номера телефона, адреса, даты или почты. ```jade append vars - meta.formatDetection.telephone = false ``` Или так: ```jade append vars - meta.formatDetection = 'telephone=no' ``` ### `theme-color` Задает цвет вкладки мобильного браузера. Пример: ```jade append vars - meta.themeColor = '#4285f4' ``` ## Метатеги Apple Метатеги Apple задают дополнительную информацию для устройств на iOS (iPhone, iPad). ### `apple-mobile-web-app-capable` Включает режим полноэкранного приложения iOS, в котором скрывается адресная строка и панель навигации. Пример: ```jade append vars - meta.appleMobileWebAppCapable = 'on' ``` ### `apple-mobile-web-app-status-bar-style` Задает стиль строки состояния в полноэкранном режиме iOS. Пример: ```jade append vars - meta.appleMobileWebAppCapable = 'on' - meta.appleMobileWebAppStatusBarStyle = 'black' ``` ### `apple-mobile-web-app-title` Задает заголовок для иконки запуска в iOS. > Если не указано, то будет использоваться значение тега `<title>`. Пример: ```jade append vars - meta.appleMobileWebAppTitle = 'Название сайта' ``` ### `apple-touch-icon` Задает иконку сайта в iOS. Пример: ```jade append vars - link.appleTouchIcon = 'images/touch-icon.png' ``` Также можно задать иконки разных размеров: ```jade append vars - link.appleTouchIcon40x40 = 'images/touch-icon-40x40.png' - link.appleTouchIcon57x57 = 'images/touch-icon-57x57.png' - link.appleTouchIcon58x58 = 'images/touch-icon-58x58.png' - link.appleTouchIcon60x60 = 'images/touch-icon-60x60.png' - link.appleTouchIcon72x72 = 'images/touch-icon-72x72.png' - link.appleTouchIcon76x76 = 'images/touch-icon-76x76.png' - link.appleTouchIcon80x80 = 'images/touch-icon-80x80.png' - link.appleTouchIcon87x87 = 'images/touch-icon-87x87.png' - link.appleTouchIcon114x114 = 'images/touch-icon-114x114.png' - link.appleTouchIcon120x120 = 'images/touch-icon-120x120.png' - link.appleTouchIcon144x144 = 'images/touch-icon-144x144.png' - link.appleTouchIcon152x152 = 'images/touch-icon-152x152.png' - link.appleTouchIcon167x167 = 'images/touch-icon-167x167.png' - link.appleTouchIcon180x180 = 'images/touch-icon-180x180.png' - link.appleTouchIcon1024x1024 = 'images/touch-icon-1024x1024.png' ``` ### `apple-touch-icon-precomposed` Задает precomposed-иконку в iOS. Используется в том случае, если к иконке не нужно применять системные стили. Пример: ```jade append vars - link.appleTouchIconPrecomposed = 'images/touch-icon-precomposed.png' ``` Также можно задать иконки разных размеров: ```jade append vars - link.appleTouchIconPrecomposed40x40 = 'images/touch-icon-precomposed-40x40.png' - link.appleTouchIconPrecomposed57x57 = 'images/touch-icon-precomposed-57x57.png' - link.appleTouchIconPrecomposed58x58 = 'images/touch-icon-precomposed-58x58.png' - link.appleTouchIconPrecomposed60x60 = 'images/touch-icon-precomposed-60x60.png' - link.appleTouchIconPrecomposed72x72 = 'images/touch-icon-precomposed-72x72.png' - link.appleTouchIconPrecomposed76x76 = 'images/touch-icon-precomposed-76x76.png' - link.appleTouchIconPrecomposed80x80 = 'images/touch-icon-precomposed-80x80.png' - link.appleTouchIconPrecomposed87x87 = 'images/touch-icon-precomposed-87x87.png' - link.appleTouchIconPrecomposed114x114 = 'images/touch-icon-precomposed-114x114.png' - link.appleTouchIconPrecomposed120x120 = 'images/touch-icon-precomposed-120x120.png' - link.appleTouchIconPrecomposed144x144 = 'images/touch-icon-precomposed-144x144.png' - link.appleTouchIconPrecomposed152x152 = 'images/touch-icon-precomposed-152x152.png' - link.appleTouchIconPrecomposed167x167 = 'images/touch-icon-precomposed-167x167.png' - link.appleTouchIconPrecomposed180x180 = 'images/touch-icon-precomposed-180x180.png' - link.appleTouchIconPrecomposed1024x1024 = 'images/touch-icon-precomposed-1024x1024.png' ``` ### `mask-icon` Задает маску для иконки закрепленного сайта в iOS. Пример: ```jade append vars - link.maskIcon.href = 'mask-icon.svg' - link.maskIcon.color = 'red' ``` ## Метатеги Microsoft Метатеги Microsoft задают дополнительную информацию для IE и Edge на Windows. ### `application-name` Задает заголовок закрепленного сайта в IE11 и Edge. > Если не указано, то будет использоваться значение тега `<title>`. Пример: ```jade append vars - meta.applicationName = 'Название сайта' ``` ### `msapplication-TileColor` Задает цвет плитки в Windows 10. Пример: ```jade append vars - meta.msapplicationTileColor = '#FF3300' ``` ### `msapplication-TileImage` Задает фоновое изображение плитки в Windows 10. Пример: ```jade append vars - meta.msapplicationTileImage = 'images/tile-image.png' ``` ### `msapplication-square70x70logo` Задает иконку малой плитки в Windows 10. Пример: ```jade append vars - meta.msapplicationSquare70x70logo = 'images/ms-logo-70x70.png' ``` ### `msapplication-square150x150logo` Задает иконку средней плитки в Windows 10. Пример: ```jade append vars - meta.msapplicationSquare150x150logo = 'images/ms-logo-150x150.png' ``` ### `msapplication-wide310x150logo` Задает иконку широкой плитки в Windows 10. Пример: ```jade append vars - meta.msapplicationSquare310x150logo = 'images/ms-logo-310x150.png' ``` ### `msapplication-square310x310logo` Задает иконку большой плитки в Windows 10. Пример: ```jade append vars - meta.msapplicationSquare310x310logo = 'images/ms-logo-310x310.png' ``` ### `msapplication-notification` Позволяет определить до пяти XML-файлов, которые будут опрашиваться с определенным интервалом. Эти файлы могут содержать уведомления, отображаемые на плитке. Пример: ```jade append vars - meta.msapplicationNotification = 'frequency=30; polling-uri=notifications/feed-1.xml; polling-uri2=notifications/feed-2.xml' ``` ### `X-UA-Compatible` Позволяет управлять режимом совместимости браузеров IE8+. > Значение по умолчанию в сборке — `IE=edge`. Пример: ```jade append vars - meta.XUACompatible = 'IE=edge' ``` ## Метатеги [Open Graph](http://ogp.me/) Метатеги Open Graph задают дополнительную информацию, используемую социальными сетями (VK, Twitter, Google Plus, Одноклассники и так далее) для оформления публикаций. ### `og:url` Задает адрес страницы в Open Graph. > Требуется указывать полный URL. Пример: ```jade append vars - meta.ogUrl = 'http://example.com/' ``` ### `og:locale` Задает локаль страницы в Open Graph. > Значение по умолчанию в сборке — `ru_RU`. Пример: ```jade append vars - meta.ogLocale = 'ru_RU' ``` ### `og:title` Задает заголовок страницы в Open Graph. Пример: ```jade append vars - meta.ogTitle = 'Заголовок страницы' ``` Или так: ```jade append vars - title = 'Заголовок страницы' ``` ### `og:description` Задает описание страницы в Open Graph. Пример: ```jade prepend vars - meta.ogDescription = 'Описание страницы' ``` Или так: ```jade prepend vars - description = 'Описание страницы' ``` ### `og:image` Задает изображение страницы в Open Graph. > Требуется указывать полный URL. Пример: ```jade append vars - meta.ogImage = 'http://example.com/images/og-image.png' ``` Или так: ```jade prepend vars - image = 'http://example.com/images/og-image.png' ``` ### `og:image:type` Задает MIME-тип изображения в Open Graph. Пример: ```jade append vars - meta.ogImageType = 'image/jpeg' ``` ### `og:image:width` Задает ширину изображения в Open Graph. Пример: ```jade append vars - meta.ogImageWidth = 1200 ``` ### `og:image:height` Задает высоту изображения в Open Graph. Пример: ```jade append vars - meta.ogImageHeight = 600 ``` ### `og:image:alt` Задает описание изображения в Open Graph. Пример: ```jade append vars - meta.ogImageAlt = 'Описание изображения' ``` ## Метатеги Twitter Метатеги Twitter задают дополнительную информацию, используемую для оформления Twitter-публикаций. ### `twitter:card` Задает тип карточки Twitter. > Значение по умолчанию в сборке — `summary_large_image`. Пример: ```jade append vars - meta.twitterCard = 'summary_large_image' ``` ### `twitter:site` Задает `@username` сайта в Twitter. Пример: ```jade append vars - meta.twitterSite = '@username' ``` ### `twitter:creator` Задает `@username` автора контента в Twitter. Пример: ```jade append vars - meta.twitterCreator = '@username' ``` ### `twitter:title` Задает заголовок страницы в Twitter. Пример: ```jade append vars - meta.twitterSite = 'Заголовок страницы' ``` Или так: ```jade prepend vars - title = 'Заголовок страницы' ``` ### `twitter:description` Задает описание страницы в Twitter. Пример: ```jade append vars - meta.twitterDescription = 'Описание страницы' ``` Или так: ```jade prepend vars - description = 'Описание страницы' ``` ### `twitter:image` Задает изображение страницы в Twitter. > Требуется указывать полный URL. Пример: ```jade append vars - meta.twitterImage = 'http://example.com/images/twitter-image.png' ``` Или так: ```jade prepend vars - image = 'http://example.com/images/twitter-image.png' ``` ================================================ FILE: 16_codestyle-pug.md ================================================ # Синтаксис и форматирование * Для отступов используется символ табуляции `\t`. * Для переноса строк используется символ `\n` (LF). * Не должно быть пробелов в конце строк. * Максимальная длина строки — 120 символов. * Между большими блоками кода следует оставлять одну пустую строку. **Неправильно** (нет пустых строк): ```jade .products .product img.product__image(src="images/products/1.png" alt="") .product__title | Название товара .product__description | Описание товара .product__price | 12345 .product img.product__image(src="images/products/2.png" alt="") .product__title | Название товара .product__description | Описание товара .product__price | 45678 .product img.product__image(src="images/products/3.png" alt="") .product__title | Название товара .product__description | Описание товара .product__price | 90123 .navigation a.navigation__link(href="/catalog?page=2") | Следующая страница ``` **Неправильно** (слишком много пустых строк): ```jade .products .product img.product__image(src="images/products/1.png" alt="") .product__title | Название товара .product__description | Описание товара .product__price | 12345 .product img.product__image(src="images/products/2.png" alt="") .product__title | Название товара .product__description | Описание товара .product__price | 45678 .product img.product__image(src="images/products/3.png" alt="") .product__title | Название товара .product__description | Описание товара .product__price | 90123 .navigation a.navigation__link(href="/catalog?page=2") | Следующая страница ``` **Правильно:** ```jade .products .product img.product__image(src="images/products/1.png" alt="") .product__title | Название товара .product__description | Описание товара .product__price | 12345 .product img.product__image(src="images/products/2.png" alt="") .product__title | Название товара .product__description | Описание товара .product__price | 45678 .product img.product__image(src="images/products/3.png" alt="") .product__title | Название товара .product__description | Описание товара .product__price | 90123 .navigation a.navigation__link(href="/catalog?page=2") | Следующая страница ``` # Однострочная вложенность Как правило теги вкладываются друг на разных строках: ```jade ul li a(href="/page/1") | 1 li a(href="/page/2") | 2 li a(href="/page/3") | 3 ``` Но в случае, **если** строк в файле довольно много и их сокращение **увеличивает читаемость**, то допустимо использовать однострочную запись: ```jade ul li: a(href="/page/1") 1 li: a(href="/page/2") 2 li: a(href="/page/3") 3 ``` В иных случаях не следует использовать такой способ записи. # Самозакрывающиеся теги Самозакрывающиеся теги (`img`, `meta`, `link`) определяются автоматически, поэтому не следует указывать это явно. # Классы Классы записываются сразу после тега: ```jade ul.navigation li.navigation__item ``` Если у элемента не указан тег, то он принимается за `div` (по этой причине не следует явно указывать тег `div`): ```jade .container .block ``` Будет преобразовано в: ```html <div class="container"> <div class="block"></div> </div> ``` Атрибут `class` используется только в том случае, если он задается с помощью переменной или через условие: ```jade - page = 'index' .menu a.menu__item(class={'menu__item--active': page === 'index'} href='/') | Главная страница a.menu__item(class={'menu__item--active': page === 'catalog'} href='/catalog') | Каталог a.menu__item(class={'menu__item--active': page === 'contacts'} href='/contacts') | Контакты ``` # Идентификаторы Идентификатор записывается после класса или тега. **Неправильно:** ```jade ul#top-menu.menu ``` **Правильно:** ```jade ul.menu#top-menu // или так .menu#top-menu // или так #top-menu ``` Атрибут `id` используется только в случае, если он задается с помощью переменной. # Атрибуты Атрибуты записываются в круглых скобках после указания тега, класса и/или идентификатора. ```jade form(action="/login.php" method="post") input(type="email" name="email" placeholder="E-mail") input(type="password" name="password" placeholder="Пароль") button(type="submit") | Войти ``` Не следует дублировать запись атрибутов. **Неправильно:** ```jade img(src="images/photo.jpg")(alt="") ``` **Правильно:** ```jade img(src="images/photo.jpg" alt="") ``` # Кавычки Для указания значения атрибутов используются двойные кавычки. **Неправильно:** ```jade input(type='text' name='name') ``` **Правильно:** ```jade input(type="text" name="name") ``` # Разделение атрибутов Атрибуты разделяются одним пробелом. **Неправильно:** ```jade img(src="images/logo.png", srcset="images/logo@2x.png 2x", alt="") ``` **Правильно:** ```jade img(src="images/logo.png" srcset="images/logo@2x.png 2x" alt="") ``` # Нестандартные атрибуты Атрибуты, в названии которых используются нестандартные символы (`(`, `)`, `[`, `]`, `:`, `@`) записываются в одинарных кавычках. **Неправильно:** ```jade // Не скомпилируется button(type="button" (click)="send()") | Отправить ``` ```jade button(type="button" v-on:click="send()") | Отправить ``` ```jade button(type="button" @click="send()") | Отправить ``` **Правильно:** ```jade // Не скомпилируется button(type="button" '(click)'="send()") | Отправить ``` ```jade button(type="button" 'v-on:click'="send()") | Отправить ``` ```jade button(type="button" '@click'="send()") | Отправить ``` # Многострочная запись атрибутов Если атрибутов слишком много и/или длина строки превышает установленный предел, то следует записать каждый атрибут в отдельной строке: ```jade input( type="text" name="name" placeholder="Имя" maxlength="20" required autocomplete="on" ) ``` # Использование переменных в атбирутах В значение атрибута можно передать переменную или любое JS-выражение: ```jade img(src=`images/image-${image.number}.jpg` alt=image.description) ``` По умолчанию значения атрибутов экранируются. ```jade .pagination(v-if="items.length > 5") ``` Будет преобразовано в: ```html <div class="pagination" v-if="items.length > 5"></div> ``` Отключить экранирование значения атрибута можно заменив `=` на `!=`. ```jade .pagination(v-if!="items.length > 5") ``` Будет преобразовано в: ```html <div class="pagination" v-if="items.length > 5"></div> ``` # JSON JSON в атрибуте записывается в виде JS-объекта, а не строки. **Неправильно:** ```jade .slider(data-options="{autoplay: true, arrows: false, fade: true}") ``` **Правильно:** ```jade .slider(data-options={ autoplay: true, arrows: false, fade: true }) // или так .slider( data-options={ autoplay: true, arrows: false, fade: true } ) ``` # Атрибут style Атрибут style также при необходимости можно записать в виде JS-объекта. ```jade .element( style={ '-webkit-transform': 'rotate(45deg)', 'transform': 'rotate(45deg)' } ) ``` # Использование `&attributes` `&attributes` либо в миксинах, либо в том случае, если необходимо задать множество атрибутов элемента с помощью переменной. **Неправильно:** ```jade a(href="/")&attributes({target: '_blank'}) | На главную ``` **Правильно:** ```jade a(href="/" target="_blank") | На главную ``` ```jade - attrs = {class: 'no-js', lang: 'ru'} doctype html html&attributes(attrs) head body ``` ```jade mixin button() button.button&attributes(attributes) block +button().button--large(type="submit") | Войти ``` # Вывод текста Текст следует выводить на следующей строке с помощью символа `|`. **Неправильно:** ```jade .product img.product__image(src="images/products/1.png" alt="") .product__title Название товара .product__description Описание товара .product__price 12345 ``` **Правильно:** ```jade .product img.product__image(src="images/products/1.png" alt="") .product__title | Название товара .product__description | Описание товара .product__price | 12345 ``` В случае, **если** строк в файле довольно много и их сокращение **увеличивает читаемость**, то допустимо использовать однострочную запись. # Использование JS в Pug Pug позволяет использовать JS в шаблонах: ```jade // Однострочная запись - title = 'Title' - description = 'Description' // Многострочная запись - attrs = { class: 'no-js', lang: 'ru' } ``` Не следует использовать эту возможность для создания условий и циклов, так как в Pug уже имеются специальные конструкции. # Вывод переменных Существует два варианта вывода переменных — экранированный и неэкранированный: ```jade // Экранированный .product .product__title= title .product__description= description .product__price= price // Неэкранированный .product .product__title!= title .product__description!= description .product__price!= price ``` # Вывод переменных в тексте При необходимости вывести переменную в тексте следует воспользоваться конструкцией `#{}` (для экранированного вывода) или `!{}` (для неэкранированного вывода): ```jade .product .product__title | Название товара: #{title} .product__description | Описание: #{description} .product__price | Цена: !{price}р. ``` # Вывод элементов в тексте Элементы в тексте записываются с помощью конструкции `#[]`. **Неправильно:** ```jade .product .product__title | <b>Название товара:</b><br> | <a href="/product/1">#{title}</a> .product__description | <b>Описание:</b><br> | #{description} .product__price | <b>Цена:</b><br> | !{price}р. ``` ```jade .product .product__title b Название товара: br a(href="/product/1") #{title} .product__description b Описание: br | #{description} .product__price b Цена: br | !{price}р. ``` **Правильно:** ```jade .product .product__title | #[b Название товара:]#[br] | #[a(href="/product/1") #{title}] .product__description | #[b Описание:]#[br] | #{description} .product__price | #[b Цена:]#[br] | !{price}р. ``` После `#[br]` следует ставить перенос строки. # Вывод кода При необходимости записать `<script>` или `<style>` непосредственно в Pug, то можно воспользоваться следующей конструкцией: ```jade script. (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); ga('create', 'UA-123456789-00', 'auto'); ga('send', 'pageview'); style. img[href*="tns-counter"] { position: absolute; left: -9999px; } ``` # Управляющие конструкции * `if` — используется для условий. **Неправильно:** ```jade - if (products.length) { .products // ... - } ``` **Правильно:** ```jade if products.length .products // ... ``` * `unless` — предназначена для условий, но не используется. Вместо нее следует использовать `if`. * `case` — предназначена для условий. Практически не используется. * `each` — предназначена для создания циклов. **Неправильно:** ```jade - for (var i = 0; i < products.length; i++) { - var product = products[i]; .product .product__title= product.title .product__description= product.description .product__price= product.price - } ``` **Правильно:** ```jade each product in products .product .product__title= product.title .product__description= product.description .product__price= product.price ``` ```jade each i in Array.from(Array(10).keys()) img(src=`images/frame-${i}.jpg` alt="") ``` * `while` — не используется. # Наследование Базовый шаблон (`base.pug`) не предназначен для редактирования. Не стоит в нем задавать значения переменных, подключать какие-либо скрипты или стили, и уж тем более писать код страницы. На то он и базовый. В нем описывается самый основной код страницы. Допустимо лишь подключать миксины в этом файле. Если возникает необходимость изменить этот файл (подключить какие-то стили или скрипты, добавить шапку и подвал сайта), то лучше создать другой базовый шаблон (например `custom-base.pug`) унаследованный от `base.pug` (а то и вовсе написанный с нуля), и уже работать с этим файлом и наследовать страницы от него. При переопределении блоков можно использовать сокращенную запись. **Неправильно:** ```jade block append content // ... ``` **Правильно:** ```jade append content // ... ``` # Подключение файлов Если страница состоит из нескольких независимых блоков, то каждый следует вынести в отдельный файл. **Неправильно:** ```jade .header // ... .banner // ... .container .sidebar // ... .content .filter // ... .products // ... .pagination // ... .footer // ... ``` **Правильно:** ```jade include pug/header include pug/banner .container include pug/sidebar .content include pug/filter include pug/products include pug/pagination ``` Если на странице используются какие-либо счетчики (Google Analytics, Яндекс Метрика и тому подобноее), то этот также следует выносить в отдельный файл: ```jade prepend scripts include pug/counters ``` При подключении Pug-файлов не нужно указывать расширение. **Неправильно:** ```jade include pug/header.pug ``` **Правильно:** ```jade include pug/header ``` Подключать можно не только Pug, но и любые другие текстовые файлы. Например svg: ```jade .header a.header__logo(href="/") include ../images/logo.svg ``` # Миксины Если на сайте имеется какой-либо шаблонный блок или компонент (кнопка, карточка, статья), который встречается более одного раза, то его следует вынести в отдельный миксин. **Неправильно:** ```jade .products .product a.product__image(href="/product/1") img(src="/images/image-1.jpg" alt="") a.product__title(href="/product/1") | Название товара .product__description | Описание товара .product__price | 12345 a.product__link(href="/product/1") | Подробнее .product a.product__image(href="/product/2") img(src="/images/image-2.jpg" alt="") a.product__title(href="/product/2") | Название товара .product__description | Описание товара .product__price | 67890 a.product__link(href="/product/2") | Подробнее ``` **Правильно:** Создаем миксин в файле src/pug/mixins/product.pug ```jade mixin product(options) .product&attributes(attributes) a.product__image(href=options.href) img(src=options.image alt="") a.product__title(href=options.href)= options.title .product__description= options.description .product__price= options.price a.product__link(href=options.href) | Подробнее ``` Подключаем миксин в `base.pug`: ```jade include mixins/svg include mixins/product ``` После можно использовать созданный миксин на любой странице: ```jade .products +product({ href: '/product/1', image: '/images/image-1.jpg', title: 'Название товара', description: 'Описание товара', price: 12345 }) +product({ href: '/product/2', image: '/images/image-2.jpg', title: 'Название товара', description: 'Описание товара', price: 67890 }) ``` В миксин также можно передать содержимое (в основном используется при передаче большого количества контента, например в статьях): ```jade mixin article(title) article.article h1.article__title= title .article__content block +article('Заголовок статьи') | Содержимое статьи ``` # Комментарии Для комментариаев используется синтаксис `//` и `//-`: ```jade // Однострочный комментарий, который попадет в html //- Однострочный комментарий, который не попадет в html // Многострочный комментарий, который попадет в html //- Многострочный комментарий, который не попадет в html ``` ================================================ FILE: 17_codestyle-scss.md ================================================ # Синтаксис и форматирование * Для отступов используется символ табуляции `\t`. * Для переноса строк используется символ `\n` (LF). * Не должно быть пробелов в конце строк. * Максимальная длина строки — 120 символов. * `!important` используется только для того, чтобы перебить значение `style`-атрибута. * В коде не должно быть вендорных префиксов. (есть исключения) **Неправильно:** ```scss a { -webkit-transition: color 0.3s; -moz-transition: color 0.3s; transition: color 0.3s; } ``` **Правильно:** ```scss a { transition: color 0.3s; } ``` Вендорные префиксы допустимы только в том случае, если вариант без префикса отсутствует. ```scss @mixin retina { @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { @content; } } ``` * Селекторы, свойства, `@`-правила, названия переменных, миксин, функций, `#`-цвета — все записывается в нижнем регистре. **Неправильно:** ```scss $Color-Cod-Gray: #1A1A1A; .Form { INPUT { color: $Color-Cod-Gray; } } ``` **Правильно:** ```scss $color-cod-gray: #1a1a1a; .form { input { color: $color-cod-gray; } } ``` * CSS-правила отделяются друг от друга пустой строкой. **Неправильно:** ```scss .header { position: relative; margin: 0 auto; padding: 15px; max-width: 1000px; background: url("../images/header.jpg") 50% 50% no-repeat; @include retina { background-image: url("../images/header@2x.jpg"); } @media (min-width: 1000px) { padding-top: 30px; padding-bottom: 30px; } } ``` **Правильно:** ```scss .header { position: relative; margin: 0 auto; padding: 15px; max-width: 1000px; background: url("../images/header.jpg") 50% 50% no-repeat; @include retina { background-image: url("../images/header@2x.jpg"); } @media (min-width: 1000px) { padding-top: 30px; padding-bottom: 30px; } } ``` * Не должно быть более одной пустой строки подряд. **Неправильно:** ```scss .button { display: inline-block; &::before { content: ""; } } ``` **Правильно:** ```scss .button { display: inline-block; &::before { content: ""; } } ``` * Между свойствами не должно быть пустых строк. **Неправильно:** ```scss .button { display: inline-block; border: solid 1px $color-black; border-radius: 4px; padding: 5px 10px; font-family: $font-family-roboto; font-weight: 300; font-size: 16px; line-height: 1.5; text-align: center; text-decoration: none; cursor: pointer; } ``` **Правильно:** ```scss .button { display: inline-block; border: solid 1px $color-black; border-radius: 4px; padding: 5px 10px; font-family: $font-family-roboto; font-weight: 300; font-size: 16px; line-height: 1.5; text-align: center; text-decoration: none; cursor: pointer; } ``` * При перечислении селекторов каждый записывается на отдельной строке. **Неправильно:** ```scss .prev, .next { position: absolute; top: 50%; } ``` **Правильно:** ```scss .prev, .next { position: absolute; top: 50%; } ``` * Перед `{` ставится один пробел, а после — один перенос строки. **Неправильно:** ```scss .header{ position: relative; } .banner { max-width: 1000px; } .footer { margin-top: 15px; } .container { margin: 0 auto; } ``` **Правильно:** ```scss .header { position: relative; } .banner { max-width: 1000px; } .footer { margin-top: 15px; } .container { margin: 0 auto; } ``` * Перед `}` не должно быть пустых строк. **Неправильно:** ```scss a { text-decoration: none; } p { margin: 15px 0; } ``` **Правильно:** ```scss a { text-decoration: none; } p { margin: 15px 0; } ``` * Перед `:` и `,` не должно быть пробелов, а после ставится один пробел. **Неправильно:** ```scss .container { padding : 0 15px; max-width: 1000px; background: none; } .button { box-shadow: inset 0 0 2px 1px $color-black ,0 5px 4px 0 rgba($color-black, 0.25); transition: color 0.3s , opacity 0.3s; } ``` **Правильно:** ```scss .container { padding: 0 15px; max-width: 1000px; background: none; } .button { box-shadow: inset 0 0 2px 1px $color-black, 0 5px 4px 0 rgba($color-black, 0.25); transition: color 0.3s, opacity 0.3s; } ``` * После открывающей и до закрывающей скобки не должно быть пробелов. **Неправильно:** ```scss a[ href ] { color: darken( $color-blue, 10% ); cursor: pointer; } ``` **Правильно:** ```scss a[href] { color: darken($color-blue, 10%); cursor: pointer; } ``` * Перед и после операторов (`+`, `-`, `*`, `/`) и комбинаторов (`+`, `>`, `~`) ставится один пробел. **Неправильно:** ```scss .sidebar { width: calc((100%- 30px)/2); >a { display: block; } } ``` **Правильно:** ```scss .sidebar { width: calc((100% - 30px) / 2); > a { display: block; } } ``` * Строки записываются в двойных кавычках. **Неправильно:** ```scss li { list-style: none; &::before { content: ''; display: inline-block; vertical-align: middle; width: 10px; height: 10px; background-image: url(../images/point.png); } } ``` **Правильно:** ```scss li { list-style: none; &::before { content: ""; display: inline-block; vertical-align: middle; width: 10px; height: 10px; background-image: url("../images/point.png"); } } ``` * Пути записываются в двойных кавычках. **Неправильно:** ```scss @import 'vendor/player'; .button-play { background: url(../images/play.png) 50% 50% no-repeat; } ``` **Правильно:** ```scss @import "vendor/player"; .button-play { background: url("../images/play.png") 50% 50% no-repeat; } ``` * Значения атрибутов записываются в двойных кавычках. **Неправильно:** ```scss a[target=_blank]::after { content: ""; display: inline-block; width: 15px; height: 15px; background-image: url("../images/new-page.png"); } ``` **Правильно:** ```scss a[target="_blank"]::after { content: ""; display: inline-block; width: 15px; height: 15px; background-image: url("../images/new-page.png"); } ``` * Названия шрифтов записываются в двойных кавычках, за исключением ключевых слов. **Неправильно:** ```scss $font-family-roboto: Roboto, sans-serif; .button { font-family: $font-family-roboto; } ``` **Правильно:** ```scss $font-family-roboto: "Roboto", sans-serif; .button { font-family: $font-family-roboto; } ``` * Псевдоэлементы записываются с помощью `::`. **Неправильно:** ```scss .list { &__item { &:before { content: ""; display: inline-block; vertical-align: middle; border-radius: 50%; width: 8px; height: 8px; background-color: currentColor; } } } ``` **Правильно:** ```scss .list { &__item { &::before { content: ""; display: inline-block; vertical-align: middle; border-radius: 50%; width: 8px; height: 8px; background-color: currentColor; } } } ``` * Не должно быть дублирующихся свойств. **Неправильно:** ```scss .button { display: inline-block; border: solid 1px $color-black; border-radius: 4px; padding: 5px; font-family: $font-family-roboto; font-weight: 300; font-size: 16px; line-height: 1.5; text-align: center; text-decoration: none; cursor: pointer; border: none; } ``` **Правильно:** ```scss .button { display: inline-block; border: none; border-radius: 4px; padding: 5px; font-family: $font-family-roboto; font-weight: 300; font-size: 16px; line-height: 1.5; text-align: center; text-decoration: none; cursor: pointer; } ``` * `font-weight` записывается в числовом формате. **Неправильно:** ```scss b { font-weight: bold; } ``` **Правильно:** ```scss b { font-weight: 700; } ``` * У нулевых значений не указываются единицы измерений (кроме времени). **Неправильно:** ```scss .button { margin: 0px auto; padding: 5px 0px 6px; } ``` **Правильно:** ```scss .button { margin: 0 auto; padding: 5px 0 6px; } ``` * Для чисел в интервале `(-1, 1)` указывается ведущий ноль. **Неправильно:** ```scss a { transition: color .3s; } ``` **Правильно:** ```scss a { transition: color 0.3s; } ``` * Вместо именованных цветов используется `#`-запись. **Неправильно:** ```scss $color-black: black; ``` **Правильно:** ```scss $color-black: #000; ``` * Для записи `#`-цвета следует использовать сокращенный вариант, если это возможно. **Неправильно:** ```scss $color-black: #000000; ``` **Правильно:** ```scss $color-black: #000; ``` * При записи `rgba`-цвета задается `#`-значением или переменной. **Неправильно:** ```scss .overlay { background-color: rgba(0, 0, 0, 0.5); } ``` **Правильно:** ```scss .overlay { background-color: rgba($color-black, 0.5); } ``` # `@extend` Вместо директивы `@extend` следует использовать `@mixin`. # Порядок правил * CSS-переменные. * `$`-переменные. * `@include` без контента * Свойства * `&::before` * `&::after` * Различные селекторы * `&:link` * `&:visited` * `&:focus` * `&:hover` * `&:active` * `&:first-child` * `&:last-child` * `&:nth-child()` * `&[attr]` * `&.modifier` * `&--modifier` * `@include` с контентом * `@media` # Порядок свойств * `all` * `print-color-adjust` * `appearance` * `counter-increment` * `counter-reset` * `content` * `quotes` * `position` * `left` * `right` * `top` * `bottom` * `z-index` * `display` * `columns` * `column-width` * `column-count` * `column-fill` * `column-gap` * `column-rule` * `column-rule-style` * `column-rule-width` * `column-rule-color` * `column-span` * `break-after` * `break-before` * `break-inside` * `page-break-after` * `page-break-before` * `page-break-inside` * `orphans` * `widows` * `flex` * `flex-grow` * `flex-shrink` * `flex-basis` * `flex-flow` * `flex-direction` * `flex-wrap` * `place-content` * `place-items` * `place-self` * `align-content` * `align-items` * `align-self` * `justify-content` * `justify-items` * `justify-self` * `order` * `clear` * `float` * `grid` * `grid-area` * `grid-auto-columns` * `grid-auto-flow` * `grid-auto-rows` * `grid-column` * `grid-column-end` * `grid-column-gap` * `grid-column-start` * `grid-gap` * `grid-row` * `grid-row-end` * `grid-row-gap` * `grid-row-start` * `grid-template` * `grid-template-areas` * `grid-template-columns` * `grid-template-rows` * `list-style` * `list-style-type` * `list-style-position` * `list-style-image` * `caption-side` * `empty-cells` * `table-layout` * `vertical-align` * `clip-path` * `mask` * `mask-clip` * `mask-composite` * `mask-image` * `mask-mode` * `mask-origin` * `mask-position` * `mask-position-x` * `mask-position-y` * `mask-repeat` * `mask-repeat-x` * `mask-repeat-y` * `mask-size` * `mask-type` * `shape-image-threshold` * `shape-margin` * `shape-outside` * `contain` * `overflow` * `overflow-x` * `overflow-y` * `overflow-anchor` * `overflow-wrap` * `margin` * `margin-top` * `margin-right` * `margin-bottom` * `margin-left` * `margin-before` * `margin-end` * `margin-after` * `margin-start` * `margin-collapse` * `margin-top-collapse` * `margin-bottom-collapse` * `margin-before-collapse` * `margin-after-collapse` * `outline` * `outline-style` * `outline-width` * `outline-color` * `outline-offset` * `outline-radius` * `outline-radius-topleft` * `outline-radius-topright` * `outline-radius-bottomright` * `outline-radius-bottomleft` * `border` * `border-style` * `border-width` * `border-color` * `border-top` * `border-top-style` * `border-top-width` * `border-top-color` * `border-right` * `border-right-style` * `border-right-width` * `border-right-color` * `border-bottom` * `border-bottom-style` * `border-bottom-width` * `border-bottom-color` * `border-left` * `border-left-style` * `border-left-width` * `border-left-color` * `border-before` * `border-before-style` * `border-before-width` * `border-before-color` * `border-end` * `border-end-style` * `border-end-width` * `border-end-color` * `border-after` * `border-after-style` * `border-after-width` * `border-after-color` * `border-start` * `border-start-style` * `border-start-width` * `border-start-color` * `border-collapse` * `border-image` * `border-image-source` * `border-image-slice` * `border-image-width` * `border-image-outset` * `border-image-repeat` * `border-radius` * `border-top-left-radius` * `border-top-right-radius` * `border-bottom-right-radius` * `border-bottom-left-radius` * `border-spacing` * `padding` * `padding-top` * `padding-right` * `padding-bottom` * `padding-left` * `padding-before` * `padding-end` * `padding-after` * `padding-start` * `width` * `height` * `min-width` * `min-height` * `max-width` * `max-height` * `box-decoration-break` * `box-shadow` * `box-sizing` * `src` * `font` * `font-family` * `font-weight` * `font-style` * `font-display` * `font-feature-settings` * `font-kerning` * `font-smoothing` * `font-stretch` * `font-synthesis` * `font-variant` * `font-variant-alternates` * `font-variant-caps` * `font-variant-east-asian` * `font-variant-ligatures` * `font-variant-numeric` * `font-variant-position` * `font-size` * `font-size-adjust` * `unicode-bidi` * `unicode-range` * `line-break` * `line-height` * `letter-spacing` * `word-break` * `word-spacing` * `word-wrap` * `white-space` * `hyphens` * `tab-size` * `text-align` * `text-align-last` * `text-combine-upright` * `text-decoration` * `text-decoration-style` * `text-decoration-line` * `text-decoration-color` * `text-decoration-skip` * `text-emphasis` * `text-emphasis-style` * `text-emphasis-color` * `text-emphasis-position` * `text-fill-color` * `text-indent` * `text-justify` * `text-orientation` * `text-overflow` * `text-rendering` * `text-security` * `text-shadow` * `text-size-adjust` * `text-stroke` * `text-stroke-width` * `text-stroke-color` * `text-transform` * `text-underline-position` * `direction` * `writing-mode` * `ruby-align` * `ruby-position` * `color` * `caret-color` * `tap-highlight-color` * `d` * `x` * `y` * `cx` * `cy` * `r` * `rx` * `ry` * `fill` * `fill-opacity` * `fill-rule` * `stroke` * `stroke-dasharray` * `stroke-dashoffset` * `stroke-linecap` * `stroke-linejoin` * `stroke-miterlimit` * `stroke-opacity` * `stroke-width` * `alignment-baseline` * `baseline-shift` * `dominant-baseline` * `clip-rule` * `color-interpolation` * `color-interpolation-filters` * `color-rendering` * `flood-color` * `flood-opacity` * `lighting-color` * `marker` * `marker-end` * `marker-mid` * `marker-start` * `paint-order` * `shape-rendering` * `stop-color` * `stop-opacity` * `text-anchor` * `offset` * `offset-position` * `offset-path` * `offset-distance` * `offset-anchor` * `offset-rotate` * `background` * `background-image` * `background-position` * `background-position-x` * `background-position-y` * `background-size` * `background-repeat` * `background-repeat-x` * `background-repeat-y` * `background-origin` * `background-clip` * `background-attachment` * `background-color` * `background-blend-mode` * `image-orientation` * `image-rendering` * `object-fit` * `object-position` * `opacity` * `visibility` * `filter` * `isolation` * `mix-blend-mode` * `zoom` * `backface-visibility` * `perspective` * `perspective-origin` * `perspective-origin-x` * `perspective-origin-y` * `transform` * `transform-box` * `transform-origin` * `transform-origin-x` * `transform-origin-y` * `transform-origin-z` * `transform-style` * `transition` * `transition-property` * `transition-duration` * `transition-delay` * `transition-timing-function` * `animation` * `animation-name` * `animation-duration` * `animation-delay` * `animation-timing-function` * `animation-iteration-count` * `animation-direction` * `animation-fill-mode` * `animation-play-state` * `will-change` * `cursor` * `pointer-events` * `touch-action` * `user-drag` * `user-focus` * `user-select` * `user-zoom` * `resize` * `scroll-behavior` * `scroll-snap-coordinate` * `scroll-snap-destination` * `scroll-snap-type` * `scroll-snap-type-x` * `scroll-snap-type-y` ================================================ FILE: 18_codestyle-javascript.md ================================================ # Синтаксис и форматирование * Для отступов используется символ табуляции `\t`. * Для переноса строк используется символ `\n` (LF). * Не должно быть пробелов в конце строк. * Максимальная длина строки — 120 символов. * Всегда ставится `;`. **Неправильно:** ```js $('form').on('submit', (event) => { let $form = $(event.currentTarget) event.preventDefault() if (validate($form)) { $.post($form.attr('action')) .done(() => { showSuccessModal() }) .fail(() => { showErrorModal() }) } }) ``` **Правильно:** ```js $('form').on('submit', (event) => { let $form = $(event.currentTarget); event.preventDefault(); if (validate($form)) { $.post($form.attr('action')) .done(() => { showSuccessModal(); }) .fail(() => { showErrorModal(); }); } }) ``` * Используется строгое сравнение `===` и `!==` (вместо `==` и `!=`). **Неправильно:** ```js $('.test__button').on('click', () => { if (test.currentQuestionIndex == test.questions.length - 1) { test.finish(); } test.answer(); }); ``` **Правильно:** ```js $('.test__button').on('click', () => { if (test.currentQuestionIndex === test.questions.length - 1) { test.finish(); } test.answer(); }); ``` * Не должно быть неиспользуемых переменных. **Неправильно:** ```js let dx = event.clientX - start.clientX; let dy = event.clientY - start.clientY; $slider.css({ '-webkit-transform': `translate(${dx}px, 0)`, 'transform': `translate(${dx}px, 0)`, }); ``` **Правильно:** ```js let dx = event.clientX - start.clientX; $slider.css({ '-webkit-transform': `translate(${dx}px, 0)`, 'transform': `translate(${dx}px, 0)`, }); ``` * Переменные объявляются на отдельных строках. **Неправильно:** ```js function showPage($page) { let $currentPage = $('.page--current'), $header = $('.header'), $footer = $('.footer'); new TimelineMax() .to([ $header, $footer, $currentPage, ], 1, { opacity: 0, onComplete() { $currentPage.removeClass('page--current'); }, }) .from([ $header, $footer, $page, ], 1, { clearProps: 'all', onStart() { $page.addClass('page--current'); }, }); } ``` **Правильно:** ```js function showPage($page) { let $currentPage = $('.page--current'); let $header = $('.header'); let $footer = $('.footer'); new TimelineMax() .to([ $header, $footer, $currentPage, ], 1, { opacity: 0, onComplete() { $currentPage.removeClass('page--current'); }, }) .from([ $header, $footer, $page, ], 1, { clearProps: 'all', onStart() { $page.addClass('page--current'); }, }); } ``` * Не следует сокращать названия переменных и функций. **Неправильно:** ```js $('.js-scroll').on('click', (e) => { let $l = $(e.currentTarget); let h = $l.attr('href'); let t = $(h).offset().top; $('html, body').animate({ scrollTop: t, }, 500); }); ``` **Правильно:** ```js $('.js-scroll').on('click', (event) => { let $link = $(event.currentTarget); let href = $link.attr('href'); let top = $(href).offset().top; $('html, body').animate({ scrollTop: top, }, 500); }); ``` * После определения переменных следует оставлять пустую строку. **Неправильно:** ```js $('form').on('submit', (event) => { let $form = $(event.currentTarget); let $button = $form.find('submit'); event.preventDefault(); $button.prop('disabled', true); submitForm($form); }); ``` **Правильно:** ```js $('form').on('submit', (event) => { let $form = $(event.currentTarget); let $button = $form.find('submit'); event.preventDefault(); $button.prop('disabled', true); submitForm($form); }); ``` * Операторы в выражениях отделяются одним пробелом. **Неправильно:** ```js function sum(a, b) { return a+b; } ``` **Правильно:** ```js function sum(a, b) { return a + b; } ``` * Перед `return` следует оставлять пустую строку. **Неправильно:** ```js function getQueryParam(key) { let params = location.search.slice(1).split('&'); for (let param of params) { param = param.split('&'); if (param[0] === key) { return param[1]; } } return null; } ``` **Правильно:** ```js function getQueryParam(key) { let params = location.search.slice(1).split('&'); for (let param of params) { param = param.split('&'); if (param[0] === key) { return param[1]; } } return null; } ``` * Многострочные конструкции и выражения отделяются пустой строкой. **Неправильно:** ```js function f() { console.log('f'); } function g() { console.log('g'); } let timer = { start() { this.stop(); this.intervalId = setInterval(() => { console.log('Tick'); }, 1000); }, stop() { clearInterval(this.intervalId); }, }; if (x > 5) { x = 0; } if (!items.length) { items.push(42); } switch (number) { case 4: console.log('a'); break; case 5: console.log('b'); break; case 1: console.log('c'); break; default: console.log('d'); break; } for (let i = 0; i < 10; i++) { y *= i; } $('.button').on('click', (event) => { event.preventDefault(); run(); }); ``` **Правильно:** ```js function f() { console.log('f'); } function g() { console.log('g'); } let timer = { start() { this.stop(); this.intervalId = setInterval(() => { console.log('Tick'); }, 1000); }, stop() { clearInterval(this.intervalId); }, }; if (x > 5) { x = 0; } if (!items.length) { items.push(42); } switch (number) { case 4: console.log('a'); break; case 5: console.log('b'); break; case 1: console.log('c'); break; default: console.log('d'); break; } for (let i = 0; i < 10; i++) { y *= i; } $('.button').on('click', (event) => { event.preventDefault(); run(); }); ``` * Не более одной пустой строки подряд. **Неправильно:** ```js $('.js-scroll-to').on('click', (event) => { event.preventDefault(); scrollTo(event.currentTarget.href); }); $('.js-close').on('click', (event) => { $(event.currentTarget).parent().removeClass('is-open'); }); ``` **Правильно:** ```js $('.js-scroll-to').on('click', (event) => { event.preventDefault(); scrollTo(event.currentTarget.href); }); $('.js-close').on('click', (event) => { $(event.currentTarget).parent().removeClass('is-open'); }); ``` * Не следует использовать однострочную запись `if` без фигурных скобок. **Неправильно:** ```js function resizeItems() { if (!items.length) return; items.forEach((item) => { item.resize(); }); } ``` **Правильно:** ```js function resizeItems() { if (!items.length) { return; } items.forEach((item) => { item.resize(); }); } ``` * Для строк используются одинарные кавычки. **Неправильно:** ```js let name = "John Doe" ``` **Правильно:** ```js let name = 'John Doe'; ``` # ES6 возможности * Используются шаблонные строки вместо конкатенации. **Неправильно:** ```js function getDateTime() { let now = new Date(); let year = now.getFullYear().toString().padStart(2, '0'); let month = (now.getMonth() + 1).toString().padStart(2, '0'); let day = now.getDate().toString().padStart(2, '0'); let hours = now.getHours().toString().padStart(2, '0'); let minutes = now.getMinutes().toString().padStart(2, '0'); return year + '.' + month + '.' + day + ' ' + hours + ':' + minutes; } ``` **Правильно:** ```js function getDateTime() { let now = new Date(); let year = now.getFullYear().toString().padStart(2, '0'); let month = (now.getMonth() + 1).toString().padStart(2, '0'); let day = now.getDate().toString().padStart(2, '0'); let hours = now.getHours().toString().padStart(2, '0'); let minutes = now.getMinutes().toString().padStart(2, '0'); return `${year}.${month}.${day} ${hours}:${minutes}`; } ``` * Используется `let` вместо `var`. **Неправильно:** ```js var name = 'John'; var surname = 'Doe'; var fullname = `${name} ${surname}`; ``` **Правильно:** ```js let name = 'John'; let surname = 'Doe'; let fullname = `${name} ${surname}`; ``` **Правильно:** ```js ``` * Используются стрелочные функции вместо анонимных. **Неправильно:** ```js $('form').on('submit', function (event) { event.preventDefault(); submitForm(this); }); ``` **Правильно:** ```js $('form').on('submit', (event) => { event.preventDefault(); submitForm(event.currentTarget); }); ``` * Следует использовать сокращенную запись ключей объекта. **Неправильно:** ```js function getObjectPosition(object) { let $offset = $(object).offset(); let x = $offset.top; let y = $offset.left; return { x: x, y: y, }; } ``` **Правильно:** ```js function getObjectPosition(object) { let $offset = $(object).offset(); let x = $offset.top; let y = $offset.left; return { x, y, }; } ``` * Следует использовать сокращенную запись методов объекта. **Неправильно:** ```js TweenMax.from($element, 1, { opacity: 0, clearProps: 'all', onStart: () => { $element.removeClass('is-hidden'); }, }); ``` **Правильно:** ```js TweenMax.from($element, 1, { opacity: 0, clearProps: 'all', onStart() { $element.removeClass('is-hidden'); }, }); ``` ================================================ FILE: 19_video-js.md ================================================ # Video.js [Video.js](https://github.com/videojs/video.js) - это библиотека с открытым исходным кодом, предназначенная для создания видео плеера. Сама по себе библиотека очень проста. Дополнительная функциональность поставляется в плагинах(плейлисты, аналитика, реклама, и расширенные форматы видео - `HLS` или `DASH`). ## Установка ### NPM ```bash npm install --save video.js ``` ### CDN ```html <script src="http://vjs.zencdn.net/6.9.0/video.js"></script> ``` ```html <link href="http://vjs.zencdn.net/6.9.0/video-js.css" rel="stylesheet"> ``` ## Инициализация На странице должен присутствовать тег: ```html <video class="video-js"></video> ``` Передаем строку содержащую `id` элемента: ```js let video = videojs('id'); ``` Или `DOM` элемент ```js let video = videojs(document.querySelector('.video-js')); ``` ## Опции Опции передаются вторым параметром: ```js let video = videojs('my-video', { autoplay: false, }); ``` **Основные опции:** * `autoplay: boolean` - автоматическое воспроизведение; * `controls: boolean` - отображать ли интерфейс плеера; * `loop: boolean` - зацикливание воспроизведения видео; * `muted: boolean` - приглушение звука; * `poster: string` - ссылка на превью видео; * `width: string|number` - ширина видео; * `height: string|number` - высота видео; **Дополнительные опции:** * `fluid: boolean` - подгонять ли видео под размер контейнера; * `aspectRatio: string` - соотношение сторон видео (16:9, 4:3); ## Методы * `src(string|array)` - позволяет задать источник видео; ```js video.src('/path/to/video.mp4'); video.src([ { type: 'video/mp4', src: '/path/to/video.mp4', }, { type: 'video/webm', src: '/path/to/video.webm', }, { type: 'video/ogg', src: '/path/to/video.ogg', }, ]); ``` * `poster(string)` - позволяет задать превью видео; * `play()` - воспроизводит видео; * `pause()` - ставит видео на паузу; * `paused()` - возвращает `true`, если видео стоит на паузе, иначе `false`; * `dispose()` - полностью удаляет плеер (вызывает событие `dispose`, удаляет все обработчики событий, удаляет `DOM` элементы); * `volume(number)` - задает горомкость звука (число от `0` до `1`); если вызвать без параметра - возвращает текущее значение; * `muted(bolean)` - возвращае `true`, если звук выключен, иначе `false`; если передано `true` - выключает звук. * `requestFullscreen()` - вход в полноэкранный режим; * `exitFullscreen()` - выход из полноэкранного режима; * `isFullscreen()` - возвращает `true` если видео находится в полноэкранном режиме, иначе `false`; * `currentTime(number)` - возвращает текущее место воспроизведения (в секундах); если передать число - устанавливает текущее место; * `duration()` - возвращает длину видео; * `remainingTime()` - возвращает оставшееся время; **Пример:** ```js let video = videojs('video', { controls: true, autoplay: false, loop: false, poster: '/video/cover.jpg', }); video.src({ src: '/video/video.mp4', withCredentials: true, }); video.on('ready', () => { // ... }); ``` ## События События те же, что у нативного элемента `video`. Полный список [тут](https://developer.mozilla.org/ru/docs/Web/Guide/Events/Media_events). **Пример:** ```js video.on('dispose', () => { // ... }); video.on('play', () => { // ... }); video.on('ended', () => { // ... }); ``` ## Стриминг (HLS) Для стриминга `video.js` использует плагин [videojs-contrib-hls](https://github.com/videojs/videojs-contrib-hls). Как подготовить видео - описано [тут](20_hls.md) ### Установка ```bash npm install --save videojs-contrib-hls ``` ### Инициализация ```js let video = videojs('my-video', { controls: true, autoplay: true, loop: false, poster: '/video/cover.jpg', html5: { nativeAudioTracks: false, nativeVideoTracks: false, nativeTextTracks: false, hls: { overrideNative: true, }, }, }); video.src({ src: 'video-name.m3u8', type: 'application/x-mpegURL', withCredentials: true, }); ``` Для воспроизведения стриминга с других серверов, необходимо установить свойство `withCredentials: false`. ## Выбор качества воспроизводимого видео Для ручного выбора качества видео, необходимо установить 2 плагина: ```bash npm install --save videojs-contrib-quality-levels videojs-hls-quality-selector ``` После указания источника видео, нужно вызвать метод `.hlsQualitySelector()` для экземпляра видео. **Пример** ```js video.src({ src: `video.m3u8`, type: 'application/x-mpegURL', withCredentials: false, }); video.hlsQualitySelector(); ``` ## Субтитры Для субтриров используются файлы в формате `.vtt`. **Пример** ``` WEBVTT 00:01.000 --> 00:04.000 Never drink liquid nitrogen. 00:05.000 --> 00:09.000 - It will perforate your stomach. ``` Что бы подключить субтитры к видео - нужно использовать метод `.addRemoteTextTrack()`. **Пример** ```js video.addRemoteTextTrack({ label: 'Russian', kind: 'captions', src: 'subtitles.vtt', default: true, }, false); ``` Задать стили субтитрам можно так (здесь используется `!important`, так как video.js задает стили инлайн): ```scss .vjs-text-track-display { display: block; pointer-events: none; div { font-family: Arial, sans-serif !important; font-size: 40px !important; background-color: transparent !important; } } ``` ## Стилизация По умолчанию используется стандартный скин. ```html <link href="http://vjs.zencdn.net/6.7.1/video-js.css" rel="stylesheet"> ``` **Стандартный скин:** ![Стандартная стилизация](images/19/image-1.jpg) **Отображение без стилей:** ![Отображение без стилей](images/19/image-2.jpg) Для кастомной стилизаци, нужно использовать свои стили. **Пример своей стилизации:** ![Своя стилизация](images/19/image-3.jpg) - Контейнер Вписываем контейнер в блок, у которого заданы размеры требуемые по макету. ```html <div id="video" class="video-js"></div> ``` ```scss .video-js { width: 100%; height: 100%; } ``` - Элемент `<video>` Подгоняем формат, для предотвращения деформирования видео. ```scss .vjs-tech { width: auto; height: 100vh; min-width: 100vw; object-fit: cover; @media (min-aspect-ratio: 1920 / 1080) { width: 100vw; height: auto; min-height: 100vh; } } ``` - Обложка `.vjs-poster` - Спинер `.vjs-loading-spinner` - Кнопка воспроизведение\пауза `.vjs-play-control` - Кнопка вкл\выкл звук `.vjs-mute-control` - Полоса громкости `.vjs-volume-bar` - Текущее время видео `.vjs-current-time` - Длительность видео `.vjs-duration` - Оставшееся время `.vjs-remaining-time` - Прогресс бар `.vjs-progress-control` - Кнопка полноэкранного режима `.vjs-fullscreen-control` **Пример:** ```scss .vjs-progress-control { display: block; } .vjs-progress-holder { position: relative; height: 4px; cursor: pointer; &::before { content: ""; position: absolute; left: 0; width: 100%; height: 4px; background-color: #fff; } } .vjs-play-progress { position: relative; display: block; width: 0; height: 4px; background-color: #000; .vjs-control-text { display: none; } .vjs-time-tooltip { display: none; } } ``` ================================================ FILE: 20_hls.md ================================================ # HTTP Live Streaming (HLS) `HLS` — протокол для потоковой передачи медиа (аудио/видео), на основе `HTTP`. В основе работы лежит принцип разбиения цельного потока на небольшие фрагменты, последовательно скачиваемые по `HTTP`. В начале сессии скачивается плей-лист в формате `.m3u8` (обычные текстовый файл), содержащий метаданные об имеющихся вложенных потоках. Сами потоки находятся в файлах с расширением `.ts`. Видео обычно кодируется с помощью `H264/h265`, аудио `AAC`. [Ссылка на статью](https://developer.apple.com/library/content/referencelibrary/GettingStarted/AboutHTTPLiveStreaming/about/about.html) ## Конвертация (подготовка файлов для стриминга) `.mp4` в `.m3u8` и `.ts` ### FFMPEG 1. Скачиваем и устанавливаем [FFMPEG](http://www.ffmpeg.org/download.html). У многих возникает вопрос как установить FFMPEG на Windows, поэтому ответ на этот вопрос можно найти [по ссылке](https://windowsloop.com/install-ffmpeg-windows-10/). 2. Прописываем `FFMPEG` в `PATH`. 3. Переходим в папку с видео: ```bash cd video_folder_name/ ``` 4. Создаем папки под видео: ```bash mkdir 1080 720 406 270 180 ``` 5. Нарезаем видео: ```bash ffmpeg \ -i video_name.mp4 \ -s 1920x1080 -c:a aac -c:v libx264 -map 0 -f segment -segment_time 10 -segment_format mpegts -segment_list 1080/playlist-1080.m3u8 "1080/video_name-1080-%d.ts" \ -s 1280x720 -c:a aac -c:v libx264 -map 0 -f segment -segment_time 10 -segment_format mpegts -segment_list 720/playlist-720.m3u8 "720/video_name-720-%d.ts" \ -s 720x406 -c:a aac -c:v libx264 -map 0 -f segment -segment_time 10 -segment_format mpegts -segment_list 406/playlist-406.m3u8 "406/video_name-406-%d.ts" \ -s 480x270 -c:a aac -c:v libx264 -map 0 -f segment -segment_time 10 -segment_format mpegts -segment_list 270/playlist-270.m3u8 "270/video_name-270-%d.ts" \ -s 320x180 -c:a aac -c:v libx264 -map 0 -f segment -segment_time 10 -segment_format mpegts -segment_list 180/playlist-180.m3u8 "180/video_name-180-%d.ts" ``` Аналогичная команда для cmd: ```bash ffmpeg ^ -i video_name.mp4 ^ -s 1920x1080 -c:a aac -c:v libx264 -map 0 -f segment -segment_time 10 -segment_format mpegts -segment_list 1080/playlist-1080.m3u8 "1080/video_name-1080-%d.ts" ^ -s 1280x720 -c:a aac -c:v libx264 -map 0 -f segment -segment_time 10 -segment_format mpegts -segment_list 720/playlist-720.m3u8 "720/video_name-720-%d.ts" ^ -s 720x406 -c:a aac -c:v libx264 -map 0 -f segment -segment_time 10 -segment_format mpegts -segment_list 406/playlist-406.m3u8 "406/video_name-406-%d.ts" ^ -s 480x270 -c:a aac -c:v libx264 -map 0 -f segment -segment_time 10 -segment_format mpegts -segment_list 270/playlist-270.m3u8 "270/video_name-270-%d.ts" ^ -s 320x180 -c:a aac -c:v libx264 -map 0 -f segment -segment_time 10 -segment_format mpegts -segment_list 180/playlist-180.m3u8 "180/video_name-180-%d.ts" ``` 6. Делаем обложку (если обложка не предоставлялась дизайнером): ```bash ffmpeg -i video_name.mp4 -ss 00:00:00 -vframes 1 cover.jpg ``` <details> <summary>Удобнее это делать, используя переменную с названием видео:</summary> ```bash video_name="название_видео" mkdir 1080 720 406 270 180 ffmpeg -i $video_name.mp4 \ -s 1920x1080 -c:a aac -c:v libx264 -map 0 -f segment -segment_time 10 -segment_format mpegts -segment_list 1080/playlist-1080.m3u8 "1080/$video_name-1080-%d.ts" -s 1280x720 -c:a aac -c:v libx264 -map 0 -f segment -segment_time 10 -segment_format mpegts -segment_list 720/playlist-720.m3u8 "720/$video_name-720-%d.ts" -s 720x406 -c:a aac -c:v libx264 -map 0 -f segment -segment_time 10 -segment_format mpegts -segment_list 406/playlist-406.m3u8 "406/$video_name-406-%d.ts" -s 480x270 -c:a aac -c:v libx264 -map 0 -f segment -segment_time 10 -segment_format mpegts -segment_list 270/playlist-270.m3u8 "270/$video_name-270-%d.ts" -s 320x180 -c:a aac -c:v libx264 -map 0 -f segment -segment_time 10 -segment_format mpegts -segment_list 180/playlist-180.m3u8 "180/$video_name-180-%d.ts" ffmpeg -i $video_name.mp4 -ss 00:00:00 -vframes 1 cover.jpg ``` Аналогичная команда для cmd: ```bash SET video_name="название_видео" mkdir 1080 720 406 270 180 ffmpeg -i %video_name%.mp4 ^ -s 1920x1080 -c:a aac -c:v libx264 -map 0 -f segment -segment_time 10 -segment_format mpegts -segment_list 1080/playlist-1080.m3u8 "1080/%video_name%-1080-%d.ts" ^ -s 1280x720 -c:a aac -c:v libx264 -map 0 -f segment -segment_time 10 -segment_format mpegts -segment_list 720/playlist-720.m3u8 "720/%video_name%-720-%d.ts" ^ -s 720x406 -c:a aac -c:v libx264 -map 0 -f segment -segment_time 10 -segment_format mpegts -segment_list 406/playlist-406.m3u8 "406/%video_name%-406-%d.ts" ^ -s 480x270 -c:a aac -c:v libx264 -map 0 -f segment -segment_time 10 -segment_format mpegts -segment_list 270/playlist-270.m3u8 "270/%video_name%-270-%d.ts" ^ -s 320x180 -c:a aac -c:v libx264 -map 0 -f segment -segment_time 10 -segment_format mpegts -segment_list 180/playlist-180.m3u8 "180/%video_name%-180-%d.ts" ffmpeg -i %video_name%.mp4 -ss 00:00:00 -vframes 1 cover.jpg ``` </details> На выходе получаем видео, раскиданное по папкам с нужным размером и разбитое на файлы `.ts`, манифесты `.m3u8` (содержащие метаинформацию о файлах для каждого размера) и обложку `cover.jpg`. 7. Создаем мастер файл для объединения всех манифестов `video-name.m3u8`: ``` #EXTM3U #EXT-X-VERSION:3 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=3880000,RESOLUTION=1920x1080,CODECS="mp4a.40.2,avc1.77.30",CLOSED-CAPTIONS=NONE 1080/playlist-1080.m3u8 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1810000,RESOLUTION=1280x720,CODECS="mp4a.40.2,avc1.77.30",CLOSED-CAPTIONS=NONE 720/playlist-720.m3u8 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=800000,RESOLUTION=720x406,CODECS="mp4a.40.2,avc1.77.30",CLOSED-CAPTIONS=NONE 406/playlist-406.m3u8 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=600000,RESOLUTION=480x270,CODECS="mp4a.40.2,avc1.77.30",CLOSED-CAPTIONS=NONE 270/playlist-270.m3u8 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=410000,RESOLUTION=320x180,CODECS="mp4a.40.2,avc1.77.30",CLOSED-CAPTIONS=NONE 180/playlist-180.m3u8 ``` ## Стандартные размеры видео для HLS * **180p** — 320x180px * **270p** — 480x270px * **406p** — 720x406px * **720p** — 1280x720px * **1080p** — 1920x1080px Точки `p` — определяющие название файла, берутся от высоты видео. ================================================ FILE: 21_bem.md ================================================ # Использование БЭМ методологии Что такое БЭМ и всю информацио о методологии можно прочесть [здесь](https://ru.bem.info/methodology/). * Используется стиль именования [«Two Dashes»](https://ru.bem.info/methodology/naming-convention/#%D1%81%D1%82%D0%B8%D0%BB%D1%8C-two-dashes). * Название блока должно быть логически понятным: header, footer, nav, sidebar, content, list и т.п. * Элемент - составная часть блока. Не должен использоваться вне блока и не должен являться частью другого элемента. * Модификатор не должен использоваться самостоятельно. * Допустимы модификаторы со значениями, но желательно использовать в очень редких случаях. * Допустимо использование миксов. * В идеале, каждый блок должен представлять из себя независимый компонент. Такой компонент позволяет без труда использовать его много раз в повторяющихся местах сайта, быстро и безболезненно редактировать и дополнять его, а также переносить из проекта в проект (универсальный компонент). **Неправильно:** ```jade .header--fixed .header__top button.header__top__burger a.header__top__logo(href="#") .header__bottom .header__nav .header__nav__item a.header__nav__item__link(href="#") .header__nav__item--active a.header__nav__item--active__link(href="#") .header__nav__item a.header__nav__item__link(href="#") ``` **Правильно:** ```jade header.header.header--fixed .header__top button.header__burger(type="button") a.header__logo(href="#") .header__bottom nav.nav .nav__item a.nav__link(href="#") .nav__item.nav__item--active a.nav__link(href="#") .nav__item a.nav__link(href="#") ``` **Правильно:** ```jade header.header.is-fixed .header__top button.header__burger(type="button") a.header__logo(href="#") .header__bottom nav.nav .nav__item a.nav__link(href="#") .nav__item.is-active a.nav__link(href="#") .nav__item a.nav__link(href="#") ``` ================================================ FILE: 22_crossbrowser-adaptive.md ================================================ # Кроссбраузерность Исходя из нашей статистики, основная часть сайтов должна поддерживаться в последних версиях таких браузеров, как: ## Десктоп * Chrome * Safari * Firefox * Internet Explorer 11 * Edge * Yandex Browser (русскоязычный сегмент) * Opera (русскоязычный сегмент) ## Мобильные устройства и планшеты * Chrome * Safari В определенных случаях данный список может меняться. # Адаптивность Для основной части сайтов используются два базовых брейкпоинта (в SCSS созданы для этого миксины mobile и desktop): * с 320 до 1024 (мобильные устройства и планшеты) * с 1025 и выше (десктоп) Остальные брейкпоинты добавляются по мере надобности, либо в зависимости от предоставленных макетов. Сайт должен быть полностю адаптирован по ширине начиная от 320 и по высоте начиная от 550 пикселей. ================================================ FILE: 23_perfomance.md ================================================ # Советы для оптимизации производительности сайта В данном разделе документации собраны некоторые советы для разработки, которые помогут улучшить производительность сайта. # 1. Оптимизация растровых изображений Формат изображений выбираем отталкиваясь от прозрачности, если прозрачность присутствует - png, если ее нет - jpg. Картинки формата jpg получаются более легкими и лучше сжимаются. Конечно есть случаи, где требуется использовать тот или иной формат по мере надобности. Используйте **srcset** для выбора необходимого изображения под нужное разрешение. ```html <img src="/images/example.jpg" srcset="/images/example-retina.jpg 2x" alt="image"> ``` Картинки должны быть сжаты, для этого можно использовать любой онлайн сервис или ПО для сжатия картинок. Конкретного предпочтения по сервисам нет, главное чтобы после сжатия картинки не слишком теряли качество. Вот небольшой список онлайн сервисов: + <https://tinypng.com> + <https://imagecompressor.com> + <https://compressor.io> + <https://imagify.io> + <https://kraken.io> # 2. Оптимизация JavaScript кода Некоторые советы, которые помогут оптимизировать работу скриптов. ## 2.1 Кеширование выборки и вычислений Кеширование позволяет сократить кол-во обращений к дом-узлам или сократить кол-во вычислений, сделав это всего один раз и записав в память. **неправильно** ```js let $header = $('.header'); let $headerNav = $('.header__nav'); let $headerItem = $('.header__item') function getItemTopPosition() { $headerItem.each((index, item) => { let itemTopPosition = $header.height() / 2 - $(item).offset().top; console.log(itemTopPosition); }); } ``` **правильно** ```js let $header = $('.header'); let $headerNav = $header.find('.header__nav'); let $headerItem = $headerNav.find('.header__item'); let headerHalfHeight = $header.height() / 2; function getItemTopPosition() { $headerItem.each((index, item) => { let itemTopPosition = headerHalfHeight - $(item).offset().top; console.log(itemTopPosition); }); } ``` ## 2.2 Чистка обработчиков событий В наших проектах мы часто используем роутер, поэтому создаем функции для инициализации страницы на которую переходим. В функции инициализации вешаются обработчики на элементы и часто их забывают отключать. Получается такая ситуация - перешли на страницу первый раз, повесились обработчики. Ушли со страницы и вернулись еще раз, произошла инициализация и обработчики повесились еще раз и т.д. В итоге при выполнении какого-то события оно будет отработано несколько раз. Для предотвращения такой ситуации можно сделать так: ```js function initPage() { $('.element') .off('.some-event') .on('click.some-event', () => { // do smth }); } ``` Или создать функцию сброса параметров после ухода со страницы, в которой можно не только сбрасывать обработчики но и обнулять какие-то вычисления. ```js let currentPage = null; let pageHeight = null; function initPage(page) { let $page = $(page); currentPage = $page; pageHeight = $page.height(); $page.on('scroll.page', () => { // do smth }); } function resetPage() { currentPage.off('.page'); currentPage = null; pageHeight = null; } ``` Ситуации бывают разные, поэтому необходимо следить за тем, чтобы обработчики не дублировались, иначе двойные, тройные … n-ые вычисления могут привести к значительному снижению производительности. ## 2.3 Использование throttle Существуют ситуации когда нам необходимо делать какие-то ресурсоемкие вычисления например на движение мыши или скролл. Для того, чтобы снизить нагрузку мы можем использовать **throttle** - функция, которая позволяет выполнить какое-то действие с задержкой в заданное кол-во времени. Подробнее можно почитать [здесь](https://learn.javascript.ru/task/throttle) или “покурить” гугл. Приведу небольшой пример. Здесь функция **getScrollPercentProgress** будет выполняться каждый раз при скролле как только браузер сможет обработать этот код. ```js function getScrollPercentProgress() { return (pageYOffset + innerHeight) / document.body.clientHeight * 100; } window.onscroll = () => { console.log(getScrollPercentProgress()); }; ``` Здесь же **getScrollPercentProgress** будет вызываться каждые 100мс, тем самым снижая кол-во выполнений. Также для уменьшения вычислений создаем сразу две переменные, которые будут хранить высоту окна и высоту документа. ```js let windowHeight = innerHeight; let documentHeight = document.body.clientHeight; function getScrollPercentProgress() { return (pageYOffset + windowHeight ) / documentHeight * 100; } window.onscroll = throttle(() => { console.log(getScrollPercentProgress()); }, 100); ``` ## 2.4 Использование debounce **Debounce** позволяет отложить вызов функции, пока не пройдет заданный промежуток времени с момента последнего вызова, например: В данном случае функция **rebuildSmth** будет выполняться непрерывно, пока мы меняем размер окна. ```js function rebuildSmth() { $('.elements').height(innerHeight); // здесь выполняется какой-то супер сложный код } window.onresize = rebuildSmth; ``` А здесь она выполнится один раз, через 500 мс с момента последнего изменения размера. ```js let windowHeight = innerHeight; let $elements = $('.elements'); function rebuildSmth() { $elements.height(innerHeight); // здесь выполняется какой-то супер сложный код } window.onresize = debounce(rebuildSmth, 500); ``` # 3. Оптимизация CSS-кода Касаемо производительности css все намного проще. Главное, что стоит запомнить - браузер читает селекторы справа налево и порядок селекторов по производительности (от более производительных к менее): 1. идентификатор (#block) 2. класс (.block) 3. тип (div) 4. сосед по уровню (h1 + p) 5. дочерний элемент (div > ul) 6. вложенный элемент (div a) 7. общий селектор (*) 8. атрибут ([type=”button”]) 9. псевдоклассы/псевдоэлементы (a:hover) Выбор элементов в css влияет на производительность, в том числе, на то, как быстро отображается страница. Однако в реальном использовании разница совсем невелика и большого прироста производительности можно не ожидать. Используйте простые css-селекторы, меньше вложений и короче цепочки. Идеальный вариант - прямые селекторы только по классу (.element), благо БЭМ это позволяет. Подобное правильно также хорошо подходит для выбора дом-узла в js. **плохо** ```css div ul li[data-type="link"] a { color: red; } div.article { display: block; } .article li a { text-decoration: none; } .article:last-child ul * { background: none; } ``` **хорошо** ```css .link { color: red; } .article { display: block; } .article__link { text-decoration: none; } .article__item { background: none; } ``` # 4. Оптимизация анимации ## 4.1 Не анимируйте сложные свойства На данный момент браузеры хорошо оптимизируют анимацию свойств **opacity** и **transform**. Анимирование остальных свойств лучше избегать или стараться использовать по минимуму, особенно это касается мобильных устройств. **плохо** ```css .menu { position: fixed; left: -100%; top: 0; background: transparent; transition: all 0.3s; } .menu.is-opened { left: 0; box-shadow: 10px 0 30px rgba(255, 255, 255, 0.3); background: #fff; } ``` **хорошо** ```css .menu { position: fixed; left: 0; top: 0; z-index: 1; transform: translate3d(-100%, 0, 0); transition: transform 0.3s; } .menu::before { content: ""; position: absolute; left: 0; top: 0; z-index: -1; display: block; width: 100%; height: 100%; box-shadow: 10px 0 30px rgba(0, 0, 0, 0.3); background: #fff; opacity: 0; transition: opacity 0.3s; } .menu.is-opened { transform: none; } .menu.is-opened::before { opacity: 1; } ``` Объем кода получается больше, однако производительность, особенно на мобильных и слабых устройствах, будет заметна. Также обратите внимание на свойство transition, всегда указывайте какие свойства элемента вам нужны для анимации, чтобы не анимировать сразу все. ## 4.2 Используйте requestAnimationFrame вместо setInterval **RequestAnimationFrame (RAF)** лучше справляется с анимацией, чем интервал. Он хорошо оптимизирован браузерами и лучше экономит ресурсы, что немаловажно для мобильных и слабых устройств. **плохо** ```js let intervalId = setInterval(() => { // код вашей анимации if (i > 1000) { clearInterval(intervalId); } }, 1000 / 60); ``` **хорошо** ```js let rafId; function animate() { rafId = requestAnimationFrame(animate); // код вашей анимации if (i > 1000) { cancelAnimationFrame(rafId); } } requestAnimationFrame(animate); ``` Поворот по оси Z помогает убрать лишние дергания анимации, особенно заметно в IE и Edge при анимации scale. # 5. Другие решения ## 5.1 Lazy loading (ленивая загрузка) В интернете много пояснений о том, что такое ленивая загрузка и как ее реализовать, если коротко - это способ загрузки данных по мере надобности. Например в нашем случае, когда мы разрабатываем SPA, содержимое сайта загружается все и сразу т.е. мы ждем пока загрузятся все картинки, видео, фреймы и т.п. (со всех страниц), а их может быть очень много и вкупе они имеют большой вес. Благодаря ленивой загрузке можно сделать так, чтобы изначально загружались ресурсы только одной страницы или только те, которые пользователь увидит изначально на экране (плюс взять небольшой запас). Далее пользователь переходит по страницам, открывает попапы, переходит по секциям и т.д., и по мере надобности подгружаются недостающие ресурсы. Т.е. смысл в том, что мы не отдаем пользователю сразу все, а только то, что ему необходимо на данный момент. ================================================ FILE: 24_git.md ================================================ # Работа с Git Для работы с репозиториями мы используем [Bitbucket](https://bitbucket.org). ## Работа с ветками * master - основная ветка * production - ветка для боевого сайта Для работы каждый разработчик должен создавать свою ветку с именем **dev-[Ваше имя]**, например dev-vasya. Т.к. у нас настроен автодеплой, то после выполнения очередной задачи свою ветку необходимо сливать с веткой **master**, чтобы изменения попали на тестовый сервер. Пушить в ветку **master** нельзя, для объединения необходимо выполнить **pull request (далее PR)** из вашей ветки в ветку мастер. PR в мастер может принимать любой разработчик. Для отправки наработок на боевой сайт (продакшн), необходимо сделать PR в ветку **production**. В данном случае запрос на объединение сможет принять только администратор репозитория. Для новичков делать PR в мастер необходимо минимум 3 раза в день. Для этого можно разбить основную задачу на 3 логические части. В остальных случаях PR нужно делать по мере необходимости или выполнения задачи. **Обязательно!** Перед началом работы выполняйте **git pull**, чтобы стянуть последние наработки по проекту. ## Коммиты К коммитам строгих требований нет. Сообщения коммитов должны быть корткие и доносить основной смысл выполненного объема работ. Желательно, чтобы сообщение коммита было на английском языке. ## Конфликты При возникновении конфликтов обязательно свяжитесь с разработчиком, который вносил изменения, чтобы вместе решить конфликты и не затереть актуальные наработки. Если такой возможности нет, внимательно изучите конфликты и саму задачу, чтобы понять какая часть является актуальной. После разрешения конфликтов и отправки наработок в ветку, необходимо сообщить в чат проекта, менеджеру о произошедшем и попросить перепроверить актуальность изменений. ================================================ FILE: 25_checklist.md ================================================ ## Проверка переходов по ссылкам с передачей GET-параметров GET-параметр - параметр, который передается серверу при помощи URL. Данные параметры находятся в URL сразу после знака вопроса `?` и состоят из пары - `ключ = значение`. Несколько GET-параметров разделяются между собой знаком амперсанда `&`. Пример ссылки с GET-параметрами: `https://site.com?param1=value1¶m2=value2`. Здесь можно выделить два параметра - `param1`, который имеет значение `value1` и `param2`, который имеет значение `value2`. При проверке переходов необходимо подставить любой GET-параметр (или несколько) в URL сайта, произвести переход по полученной ссылке и посмотреть не пропадут ли параметры из URL. Например есть ссылка на сайт - `site.com`, добавляем к ней GET-параметры - `?test_param=value`, в результате получим подобную ссылку - `site.com?test_param=value`, производим переход и смотрим не изменился ли URL. Часто, причиной пропажи параметров является манипуляция URL, в каком-то из участков кода. Например меняется адрес при входе на страницу при помощи `window.location`, `history.pushState`, `history.replaceState` и т.п. В таком случае необходимо обязательно предусмотреть возможность сохранения передаваемых параметров. Также, если на сайте используются якоря `(site.com#section)`, необходимо проверять их работоспособность с передачей параметров `(site.com?test_param=value#section)`. ## Проверка на соответствие макету Результат верстки должен соответствовать макету. Проверка осуществляется с помощью расширения [PerfectPixel](https://chrome.google.com/webstore/detail/perfectpixel-by-welldonec/dkaagdgjmgdmbnecmcefdhjekcoceebi?hl=ru), в браузере Chrome под ОС Windows, Linux или Mac. Допустимы незначительные отличия, связанные с: - различием в рендеринге шрифтов - ошибками в макете (различные отступы или размеры у однотипных элементов, погрешности в цветах) - заменой контента (текст, изображения, видео) Иные отличия недопустимы. Если верстка по тем или иным причинам расходится с макетом, то об этом следует сообщить менеджеру проекта. ## Проверка кода линтером Следует проверять код линтером. Если при проверке кода линтером выявлены ошибки, то их следует исправить, либо сообщить об этом разработчику, ответственному за данный код. Если при работе с линтером появляются подозрения на некорреткную настройку или баги, то об этом следует сообщить разработчику, ответственному за настройку линтера ([@beliarh](https://github.com/beliarh)). ## Проверка кода валидатором Следует проверять код [валидатором](https://validator.w3.org/unicorn/). ## Проверка фавиконки На сайте должна быть фавиконка. При отсутствии фавиконки следует запросить ее у менеджера проекта. ## Шрифты На сайте должны использоваться корректные шрифты. Обязательный формат - `woff`. Опциональный формат - `woff2`. Недопустимо использовать `woff2`, без использования `woff`. Допустимо использовать только `woff`. Крайне желательно использовать и `woff` и `woff2`. В используемом шрифте не должно быть битых символов. ## Проверка на типографирование Текст на сайте должен быть типографирован, т.е. прогнан через [Типограф](https://www.artlebedev.ru/typograf/). Если в тексте присутствуют лишние и битые символы, от них необходимо избавиться. ## Проверка выделения текста Содержимое текстовых блоков должно корректно выделяться. ## Проверка иконок и изображений Иконки на сайте должны быть сделаны с помощью SVG, либо PNG + @2x PNG (если иконка имеет сложные растровые эффекты, SVG отсутствует или невозможно экспортировать иконку в формате SVG). Для контентных изображений должна быть указана @2x-версия. SVG-иконки должны иметь внутренние отступы, чтобы избежать обрезанных краев в разных браузерах. ## Проверка интерактивных элементов Следует проверять корректность работы интерактивных элементов: * Слайдеры * Всплывающие окна * Аудио и видеоплееры * Табы * Тесты и опросы * Элементы форм * Валидация полей * Отправка форм * Прочие элементы, обладающие сложной логикой и неуказанные в данном списке ## Проверка свайпа и drag'n'drop Если в каком либо блоке сайта допустимо и логично реагировать на свайп или drag'n'drop, и это поведение не реализовано, то об этом следует сообщить менеджеру проекта и разработчику. Свайп можно использоват в мобильной и планшетной версии для перехода между слайдами или для постраничной навигации (если таковая имеется). Drag'n'drop можно использовать в слайдерах и элементах, поведение которых предполагает (или допускает) перетаскивание. ## Проверка навигации по сайту с помощью клавиатуры На сайте должна корректно работать навигация с помощью клавиатуры. В частности следует проверять работу следующих клавиш (и комбинаций клавиш): * Стрелки влево, вправо, вверх и вниз * Tab * Shift + Tab * Enter * Esc * Пробел * Shift + Пробел * Page Up * Page Down * Home * End ## Проверка кнопок Кнопки, клик по которым не ведет на другую страницу, а лишь выполняет какое-либо действие, должны быть сделаны тегом `<button>`. У тега `<button>` должен быть указан атрибут `type`. ## Проверка ссылок Ссылки - интерактивные элементы, при клике на которые происходит переход на другую страницу или внешний ресурс. Ссылки-якоря - интерактивные элементы, при клике на которые происходит скролл к нужному месту страницы. Все ссылки должны быть сделаны тегом `<a>`. У всех ссылок должен быть указан атрибут `href`. У внутренних ссылок атрибут `href` должен начинаться с `/`. У внешних ссылок должен быть указан атрибут `target="_blank"`. У ссылок-якорей в атрибуте `href` должен быть указан хеш. Ссылки должны быть корректными и вести на соответствующие страницы. ## Проверка кликабельной области элементов Если у кнопки или отдельностоящей ссылки нет явно ограниченной кликабельной области, то за такую следует принять размер элемента плюс поля 5-15px. Кликабельная область должна иметь размер минимум 20-30px по высоте и ширине (если нет явной границы). Содержимое кнопки или ссылки должно быть по центру кликабельной области. Для ссылок в тексте размер кликабельной области не контролируется. ## Проверка анимаций Любая анимация на сайте (переходы, смена слайдов, ховеры) должна работать плавно, без рывков и мерцаний. Не должно быть зависаний или долгих пауз во время анимации. Анимация однотипных элементов должна быть одинаковой на всех блоках и страницах. На всех интерактивных элементах должен быть плавный ховер. ## Проверка верстки стресстестом **TODO** ## Проверка сайта с включенным блокировщиком рекламы Сайт должен проверяться с включенным блокировщиком рекламы. Элементы сайта не должны блокироваться. ## Проверка переходов на страницы по прямой ссылке Следует проверять корректность загрузки всех страниц сайта. Также следует проверять корректность загрузки страницы с дополнительными GET-параметрами и хешем. Заданные GET-параметры и хеш при этом не должны пропадать. ## Проверка переходов между страницами Следует проверять корректность перехода между всеми страницами сайта. Например, если на сайте несколько страниц: * `/a` * `/b` * `/c` То следует проверять переходы: * `/a` => `/a` * `/a` => `/b` * `/a` => `/c` * `/b` => `/a` * `/b` => `/b` * `/b` => `/c` * `/c` => `/a` * `/c` => `/b` * `/c` => `/c` Если какой-либо переход невозможен, то его следует игнорировать. Также следует проверять корректность перехода по несуществующему адресу. На сайте должна быть предусмотрена страница с 404 ошибкой, либо должен срабатывать редирект на главную. ## Проверка навигации с помощью истории браузера На сайте должны корректно работать переходы между страницами с помощью истории браузера (стрелки влево и вправо рядом с адресной строкой). ## Кроссбраузерность и кроссплатформенность На проектах поддерживаются последние версии браузеров (если не указано иного). Сайт следует проверять в следующих системах: * Windows * Chrome * Firefox * Edge * IE 11 * Yandex (русскоязычный сегмент) * Opera (русскоязычный сегмент) * macOS * Safari * Chrome * Android * Chrome * iOS * Safari * Chrome ## Проверка адаптивности (десктоп) Основные размеры мониторов: * 2560x1440 * 2560x1080 * 1920x1200 * 1920x1080 * 1650x1050 * 1600x900 * 1440x900 * 1366x768 * 1280x1024 * 1280x720 При проверке адаптивности следует проверять не только указанные размеры, но также и промежуточные. Причина в том, что размер окна сайта не равен разрешению монитора (за исключением полноэкранного режима). Часть пространства занимает системная панель, часть - верхняя панель браузера. К тому же иногда пользователи открывают окно браузера не на весь экран. Проверка осуществляется в панели разработчика в режиме `Responsive` и начинается с наибольшего экрана (2560x1440). Минимальные размеры до которых стоит проверять адаптивность десктопной версии - 1025px по ширине, и 550px по высоте. Проверка адаптивности проходит по следующему алгоритму: 1. Задается размер окна 2560x1440. 2. Путем перетаскивания и постепенного уменьшения высоты (до минимальной - 550px) проверяется корректность отображения сайта. 3. Далее ширина окна немного уменьшается (на 25-50px). 4. Пункты 2 и 3 повторяются до тех пор, пока ширина окна не достигнет минимальной - 1025px. ## Проверка адаптивности (планшеты и мобильные устройства) Для начала сайт нужно проверить в мобильном эмуляторе браузера. Как и в случае с десктопной версией проверяются не только размеры, соответствующие разрешению экрана, но также и промежуточные. Минимальная ширина экрана до которой следует проверять адаптивность - 320px. Проверять следует как портретную, так и альбомную ориентацию. В альбомной ориентации размер элементов не должен сильно отличаться от портретной. Не должно быть такого, что в альбомной ориентации элементы слишком мелкие или большие. Обязательно проверять сайт на реальных устройствах (либо в [BrowserStack](https://www.browserstack.com/)): * Android * Любое мобильное устройство и планшет с актуальной версией системы (6+) * Chrome * iOS * iPhone SE/7/8/X и iPad Mini/Air/Pro * Safari * Chrome При проверке сайта на реальных устройствах страница должна корректно отображаться и скроллиться. ## Проверка работоспособности сайта при ресайзе Необходимо проверять и дорабатывать логику сайта при ресайзе, чтобы сайт при изменении размеров окна не ломался. Например на сайте имеется слайдер, который отображается на мобильной версии, но при этом на десктопе отсутствует, либо отключен. В данном случае необходимо предусмотреть логику создания / удаления (отключения) слайдера при переходе с мобильной версии на десктоп и обратно, по ресайзу, без перезагрузки сайта. В качестве дополнительных примеров можно взять уникальные случаи - `"кирпичная" сетка, параллакс элементов, анимация движения элементов по скроллу` и т.д. Подобные вещи часто "ломаются" именно при изменении размеров окна, поэтому нужно не забывать предусматривать корректировку логики по ресайзу. Для чего это необходимо: * исключается некорректное отображение сайта при изменении ориентации на устройствах * исключается некорректное отображение сайта при масштабировании или случайном изменении размеров окна браузера * удобство при тестировании, нет необходимости перезагружать сайт Для оптимизации нагрузки, всю логику выполнения по ресайзу можно делать с задержкой. Например изменился размер окна, а все корректировки логики выполнились только спустя 1 секунду. В этом могут помочь техники `throttling` и `debouncing`. Также необходимо следить за тем, чтобы ресайз не препятствовал отображению сайта, например на мобильных устройствах при скролле скрываются / открываются навигационные панели браузера, это вызывает событие ресайза. В этом случае на сайте могут происходить корректировки размеров, положения и т.д. чего-либо, однако, это может быть лишним т.к. данные корретировки необходимы будут только при изменении ширины окна, а не высоты. ## Проверка метатегов На сайте должны быть указаны метатеги (`title`, `description`, `image`). Если сайт многостраничный, то метатеги должны быть указаны на каждой странице. Если сайт одностраничный и использует `shareSettings.php` для шаринга, то метатеги должны быть указаны именно в этом файле. При этом необходимо подставить переменные в pug-файле. ## Проверка шеринга На сайте должен работать шеринг (при наличии). Если сайт многостраничный, то должна шариться ссылка на страницу с основными параметрами (при наличии таковых). Если сайт многостраничный, то должна шариться ссылка на `share.php` с требуемыми параметрами. При шаринге в ссылку не должны попасть лишние параметры (например, UTM-метки). Проверка и отладка шаринга осуществляется с помощью следующих инструментов: * https://developers.facebook.com/tools/debug/ * https://cards-dev.twitter.com/validator * https://vk.com/dev/pages.clearCache * https://search.google.com/structured-data/testing-tool * https://telegram.me/webpagebot При шаринге сайта в соц. сети должны корректно отобразиться: * Заголовок (соответствующий заданному метатегу, необрезанный, без искажений кодировки и битых символов) * Описание (соответствующее заданному метатегу, необрезанное, без искажений кодировки и битых символов) * Изображение (соответствующее заданному метатегу) Ссылка шаринга должна вести на ту же страницу, которую шарили, либо на страницу, соответствующую заданной логике. В случае использования `share.php` ссылка должна перенаправлять на нужную страницу. Если расшариваемая страница недоступна по прямой ссылке (т.е. переход на нее происходит по внутренней логике сайта), то следует шарить главную страницу. ## Проверка аналитики Для проверки аналитики используется расширение [Google Analytics Debugger](https://chrome.google.com/webstore/detail/google-analytics-debugger/jnkmfdileelhofjcijamephohjechhna). После установки в панели расширений появится иконка GA Debug (в виде письма). На проверяемом сайте следует открыть панель разработчика и включить GA Debug. На иконке появится надпись `ON`. После чего страница перезагрузится и в консоли появится множество сообщений от расширения. Избавиться от лишних сообщений можно, если вписать в поле `Filter` консоли значение `Running command` (все сообщения от данного расширения помещается данным префиксом). При фильтрации сообщений консоли следует быть внимательным, так как фильтруются абсолютно все сообщения, в том числе ошибки и собственные вызовы `console.log`. После этого следует поочередно проверить указанные в ТЗ события. ## Проверка контента На сайте должен быть актуальный контент: * Текст * Ссылки * Изображения * Видео * Аудио Запросить актуальный контент можно у менеджера проекта. При наличии ошибок в тексте (орфографических, пунктуационных, логических) следует сообщить об этом менеджеру проекта. При наличии недоступных ресурсов (недоступные изображения, видео или аудио, 404 ошибки) следует сообщить об этом менеджеру проекта. При выявлении несоответствия контента (перепутан текст, изображение, видео или аудио) следует сообщить об этом менеджеру проекта. ## Проверка размера загружаемых ресурсов На сайте не должно быть чрезмерно больших файлов. Размер изображений должен соответствовать размеру на сайте (например, для элемента размером 300x300 не должно использоваться изображение размером 2000x2000). Не должно быть мегабайтных JPG. Такие файлы следует оптимизировать вручную с ограничением максимального качества. Размер видео не должен быть больше 100 мегабайт (за исключением продолжительных видео и адаптивного стриминга). ## Проверка производительности сайта **TODO** ================================================ FILE: 26_short-checklist.md ================================================ 1. Переходы по ссылкам с передачей GET-параметров. 2. Соответствие макету. 3. Проверка кода линтером. 4. Проверка всех страниц сайта HTML-валидатором (https://html5.validator.nu/). 5. Имеется фавиконка. 6. Корректные шрифты (woff + woff2). 7. Типографированный текст. Отсутствуют битые символы. 8. Текст на сайте корректно выделяется. 9. Корректные svg-иконки. 10. Интерактивные элементы работают корректно (слайдеры, всплывающие окна, аудио и видеоплееры, табы, тесты, опросы, формы, выпадающие списки и прочее). 11. В формах реализована валидация полей. 12. Если на сайте присутствует кастомный скролл, то навигация с помощью клавиатуры должна работать корректно (Space, Shift + Space, Page Up, Page Down, Home, End, стрелки) 13. Кликабельные элементы, не ведущие на другие страницы, сделаны кнопками (`<button type="button">`). 14. Кликабельные элементы, ведущие на другие страницы (за исключением кнопок для отправки форм), сделаны ссылками с корректным href (`<a>`). 15. У всех кликабельных элементов достаточная кликабельная область. 16. Анимации на сайте работают корректно, плавно, без задержек и подвисаний. 17. На всех интерактивных элементах должен быть ховер. 18. Стресстест верстки (в местах с динамическим контентом прописать много текста с очень длинными словами и проверить не ломается ли верстка, также проверить не ломается ли при малом количестве контента). 19. Сайт не ломается блокировщиком рекламы. 20. Страницы открываются по прямой ссылке (для SPA). 21. Переходы между страницами работают корректно (для SPA). 22. Навигация с помощью истории браузера работает корректно (для SPA). 23. Проверка сайта в Windows + Chrome. 24. Проверка сайта в Windows + Firefox. 25. Проверка сайта в Windows + Edge. 26. Проверка сайта в Windows + IE 11. 27. Проверка сайта в Windows + Yandex. (русскоязычный сегмент) 28. Проверка сайта в Windows + Opera. (русскоязычный сегмент) 29. Проверка сайта в macOS + Chrome. 30. Проверка сайта в macOS + Safari. 31. Проверка сайта в Android + Chrome (планшет). 32. Проверка сайта в iOS + Safari (планшет). 33. Проверка сайта в Android + Chrome (мобильное устройство). 34. Проверка сайта в iOS + Safari (мобильное устройство). 35. Сайт корректно адаптируется в десктопной версии (от 1025x550 до 2560x1440). 36. Сайт корректно адаптируется в мобильной и планшетной версии (от 320 до 1024). 37. Сайт корректно отображается в альбомной ориентации (мобильная и планшетная версия). 38. Сайт не ломается при ресайзе окна. По нeoбxoдимocти происходит переинициализация логики сайта при смене десктоп - мобайл и наоборт (мобайл - десктоп). 39. Указаны метатеги title, description и image (при наличии). 40. Корректная работа шеринга. Сброс кэша (если нужно): - https://developers.facebook.com/tools/debug/ - https://cards-dev.twitter.com/validator - https://vk.com/dev/pages.clearCache - https://search.google.com/structured-data/testing-tool - https://telegram.me/webpagebot 41. Корректная работа аналитики. 42. Актуальный контент (текста, ссылки, картинки, видео, аудио). ================================================ FILE: 27_validation.md ================================================ # Базовая валидация форм Рассмотрим требования только к часто встречающимся полям. Валидация остальных полей выполняется индивидуально. ### поле для ввода ФИО или только имени: * ограничение минимального кол-ва символов * ограничение максимального кол-ва символов * ввод только букв и пробелов * удаление пробелов в начале и конце строки перед отправкой ### поле для ввода email: * удаление пробелов в начале и конце строки перед отправкой * маска ввода ```regex ^([a-z0-9_-]+\.)*[a-z0-9_-]+@[a-z0-9_-]+(\.[a-z0-9_-]+)*\.[a-z]{2,6}$ ``` ```regex ^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.[A-Za-z] ``` ### поле для ввода телефона: * ввод только цифр и некоторых символов (плюс, пробел, тире, круглые скобки) * удаление пробелов в начале и конце строки перед отправкой * маска ввода ```regex ^((8|\+7)[\- ]?)?(\(?\d{3}\)?[\- ]?)?[\d\- ]{7,10}$ ``` ```regex /^(\s*)?(\+)?([- _():=+]?\d[- _():=+]?){10,14}(\s*)?$/ ``` --- Требования к этим полям также могут меняться на разных проектах. Регулярные выражения даны для примера, вы можете использовать свои. ================================================ FILE: 28_dynamic-share-for-spa.md ================================================ # Настройка динамических шерингов для SPA С недавнего времени в сборку был добавлен новый файл `shareSettings.php`, в папку `resources`. В нем необходимо указывать все данные по шерингам. Во избежание ошибок при сборке, данный файл нельзя удалять из проекта. Он автоматически удаляется в конечном билде. Теперь нет необходимости добавлять логику с корректировкой URL для `share.js`, создавать отдельный файл `share.php` и в нескольких местах менять данные шеринга. Достаточно один раз прописать все данные в `shareSettings.php`. ### базовый код файла shareSettings.php: ```php $protocol = $_SERVER['PROTOCOL'] = isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) ? 'https' : 'http'; $host = $protocol . '://' . $_SERVER['HTTP_HOST']; $title = ''; $description = ''; $image = $host . '/images/'; // Uncomment the code below and fill in the pages if necessary // $pages = [ // '/page/1' => [ // 'title' => '', // 'description' => '', // 'image' => '/images/', // ], // ]; $page = @$pages[$_SERVER['REQUEST_URI']]; if ($page) { $title = !is_null(@$page['title']) ? $page['title'] : $title; $description = !is_null(@$page['description']) ? $page['description'] : $description; $image = !is_null(@$page['image']) ? $host . $page['image'] : $image; } ``` ### пример кода с указанными данными для шеринга: ```php $protocol = $_SERVER['PROTOCOL'] = isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) ? 'https' : 'http'; $host = $protocol . '://' . $_SERVER['HTTP_HOST']; $title = 'Базовый заголовок страницы'; $description = 'Базовое описание страницы'; $image = $host . '/images/share/main.jpg'; $pages = [ '/article' => [ 'title' => 'Заголовок статьи', 'description' => 'Описание статьи', 'image' => '/images/share/article.jpg', ], '/test' => [ 'title' => 'Заголовок теста', 'description' => 'Описание теста', 'image' => '/images/share/test.jpg', ], '/test?result=1' => [ 'title' => 'Заголовок результатов теста №1', 'description' => 'Описание результатов теста №1', 'image' => '/images/share/test.jpg', ], '/test?result=2' => [ 'title' => 'Заголовок результатов теста №2', 'description' => 'Описание результатов теста №2', 'image' => '/images/share/test.jpg', ], '/product' => [ 'title' => 'Заголовок продуктовой страницы', 'description' => 'Описание продуктовой страницы', 'image' => '/images/share/product.jpg', ], ]; $page = @$pages[$_SERVER['REQUEST_URI']]; if ($page) { $title = !is_null(@$page['title']) ? $page['title'] : $title; $description = !is_null(@$page['description']) ? $page['description'] : $description; $image = !is_null(@$page['image']) ? $host . $page['image'] : $image; } ``` --- Чтобы все данные из этого файла попали в разметку страницы при входе, необходимо обязательно внести небольшие изменения в корневой файл `index.pug`. ### пример кода для index.pug: ```jade extends pug/base prepend vars - title = '<?= htmlspecialchars($title) ?>' - description = '<?= htmlspecialchars($description) ?>' - image = '<?= htmlspecialchars($image) ?>' append vars //- ... block content //- ... ``` Теперь на локальном сервере во вкладке будет отображаться подобное - `<?= htmlspecialchars($title) ?>`. Не стоит пугаться, корректный заголовок (а также description и image) будут работать на тестовом, а также боевом серверах. Это происходит по той причине, что `index.html`, который создается в результате обычного билда, просто не умеет читать php-код. А на сервер вместо `index.html` будет добавляться `index.php`, который прекрасно распознает все эти переменные. --- Также в папке `resources` теперь хранится файл `.htaccess`, который также нельзя удалять и для него тоже настроено автоудаление, если сборка не запущена в режиме SPA. Этот файл содержит настройки для корректной работы сайта в режиме SPA. ================================================ FILE: 29_helpers.md ================================================ # Всппомогательные функции Для облегчения некоторой части базовых настроек в сборку добавленны вспомогательные функций для js и scss. Их использование не обяхательно, но оно облегчит некоторую часть работы. ## Вспомогательные функции PUG #### Миксины `lazyElements` #### Миксин `svg` Подгрузка свг элемента из файла спрайтов ## Вспомогательные функции JS - `lastPageYOffset` - содержит позицию скролла после использования `saveScrollPosition`. - `debounce` - полезен для функций, которые получают/обновляют данные, и мы знаем, что повторный вызов в течение короткого промежутка времени не даст ничего нового, синтаксис похож на `setTimeout`. - `saveScrollPosition` - полезен при открытии модальных окон, используется для сохранения текущей позиции горизонтального скролла. - `restoreScrollPosition` - полезен при закрытии модальных окон, используется для сброса сохраненной позиции горизонтального скролла. - `scrollTo` - плавный скролл до элемента, принимает 3 параметра, элемент до которого нужно скроллить / время анимации / смещение (+/-). - `getScrollbarWidth` - полез при открытии модальных окон, чтобы контент не прыгал, возвращает ширину скролл-бара. - `hasHoverSupport` - определяет доступность ховера, на различных устройствах, помогает избежать ховеров на мобилках и планшетах. - `actualYear` - определяет актуальных год (в основном нужно для копирайтов в футере) - `backToTop` - Кнопка возврат наверх, есть возможность показывать и скрывать эту кнопку через класс - `counter` - Счетчик значений больше/меньше + ввод через input - `dropEye` - Переключатель глаза и показ символов в поле ввода пароля - `lazyLoading` - Отложенная загрузка изображений + тригер для ручного вызова - `numberFormat` - Форматирование числа, добавляет тысячный разделитель - `scrollToAnchor` - Плавный переход к якорю - `transchoice` - Плюрализация (множественность) текста Остальные переменные добавленны по мере популярнисти использования на проектах, для каждого проекта этот список можно индивидуально донастроить ## Вспомогательные функции SCSS - `max / min` - вспомогательные функции для `supports-safe-area-insets` - `hover / active-hover / active / disabled` - вспомогательные миксины для корректного доступа к ховеру, так же небольшие хелперы для активного/неактивного состояния элементов и ховера при активном состоянии #### Миксин `supports-safe-area-insets` Вспомогательный миксин для использования всего пространства экрана на iOs устройствах начиная с iPhone X Сейчас на большинстве сайтов можно наблюдать такую картину ![safe-area error](images/29/safe-areas-error.png) Визуально выглядит это плохо и для решения таких проблем появилась возмоность использовать переменные среды агента пользователя (safe-area), которые выглядит следующим образом ![safe-area true](images/29/safe-areas.png) Начнём сначала, для правильности работы в шапке сайта нужно указывать специальный viewport. ```html <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, viewport-fit=cover"> ``` ##### Пример использования safe-area ```scss body { padding: 12px; @include supports-safe-area-insets { body { padding-top: max(12px, env(safe-area-inset-top)) padding-right: max(12px, env(safe-area-inset-right)) padding-bottom: max(12px, env(safe-area-inset-bottom)) padding-left: max(12px, env(safe-area-inset-left)); } } } ``` Разумеется любое из этих 4-х значений можно использовать по отдельности и с любым css свойством, главное знать и понимать когда конкретно это необходимо. В итоге, при правильном использовании safe-area мы получаем такую картину. ![safe-area success](images/29/safe-areas-success.png) #### Миксин `text-border` `text-border($color, $borderColor, $ieColor, $width: 1px, $ieWidth: 1px)` - Вспомогательный миксин для корректного использования окантовки у текста, предусмотрен полифилл для старых IE. Первые 3 свойства обязательны для заполнения, `$color`: цвет текста в основной массе браузеров, `$borderColor` - цвет окантовки для всех браузеров, `$ieColor` - цвет текста для IE, так в нём недоступно свойство `transparent`, остальные свойства используются редко, поэтому в них добавленны базовые значения ##### Пример использования text-border ```scss @include text-border(transparent, #000, #fff); ``` #### Миксин `font-face` `font-face($url, $font-family, $font-weight, $font-style)` - Вспомогательный миксин для задания шрифтов, все поля обязательны для заполнения ##### Пример использования font-face ```scss @include font-face("../fonts/GraphikRBCLC/GraphikRBCLC-Regular", "GraphikRBCLC", 400, normal); ``` #### Миксин `retina` Вспомогательный миксин для добавления `background-images` для дисплеев с 2х экранами #### Миксин `placeholder` Вспомогательный миксин для стилизации подсказок у полей ввода #### Миксины в папке `libs` Вспомогательные функции для работы некоторых других функций, так же их можно использовать любом другом месте #### Миксин `image-rendering` Качество рендеринга изображений, при использовании `background-size` #### Миксин `svg-to-data-url` Преобразует SVG в URL-адрес данных, чтобы этот SVG можно было использовать в качестве фонового изображения #### Миксин `object-fit` Миксин добавляет `object-fit` и надстройки для полифилов, так же можно задать позицию #### Миксин `triangle` Миксин создаёт треугольник в любую из сторон, можно менять цвет, подходит для кнопок плей ================================================ FILE: README.md ================================================ # Оглавление * [Основные возможности и используемые технологии](01_technologies.md) * [Минимальные требования](02_requirements.md) * [Начало работы](03_installation.md) * [Gulp-задачи](04_tasks.md) * [Структура папок и файлов](05_structure.md) * [Подключение сторонних библиотек](06_libraries.md) * [Работа с изображениями](07_images.md) * [Работа с PNG-спрайтами](07_images.md#Работа-с-png-спрайтами) * [Работа с SVG-спрайтами](07_images.md#Работа-с-svg-спрайтами) * [Избавляемся от обрезанных краев SVG-иконок](07_images.md#избавляемся-от-обрезанных-краев-svg-иконок) * [Работа с шаблонизатором Pug](08_templates.md) * [Работа со стилями](09_styles.md) * [Работа со скриптами](10_scripts.md) * [Работа с дополнительными ресурсами](11_resources.md) * [Работа со шрифтами](11_resources.md#Работа-со-шрифтами) * [Pixel-perfect или верстка в соответствии с макетом](13_pixel-perfect.md) * [Работа с метатегами](15_metatags.md) * [Базовые метатеги](15_metatags.md#Базовые-метатеги) * [Метатеги Apple](15_metatags.md#Метатеги-apple) * [Метатеги Microsoft](15_metatags.md#Метатеги-microsoft) * [Метатеги Open Graph](15_metatags.md#метатеги-open-graph) * [Метатеги Twitter](15_metatags.md#Метатеги-twitter) * [Использование БЭМ методологии](21_bem.md) * [Оформление Pug-кода](16_codestyle-pug.md) * [Оформление SCSS-кода](17_codestyle-scss.md) * [Оформление JavaScript-кода](18_codestyle-javascript.md) * [Кроссбраузерность и адаптивность](22_crossbrowser-adaptive.md) * Дополнительная информация * [Работа с video.js](19_video-js.md) * [Работа с HLS](20_hls.md) * [Базовая валидация форм](27_validation.md) * [Производительность](23_perfomance.md) * [Работа с Git](24_git.md) * [Настройка динамических шерингов для SPA](28_dynamic-share-for-spa.md) * [Вспомогательные функции и миксины для упрощения работы](29_helpers.md) * [Подробный чеклист по тестированию сайтов](25_checklist.md) * [Короткий чеклист по тестированию сайтов](26_short-checklist.md)