├── .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 └── images ├── 14 ├── 960-grid-system.jpg ├── example-1.jpg ├── example-10.jpg ├── example-11.jpg ├── example-12.jpg ├── example-2.jpg ├── example-3.jpg ├── example-4.jpg ├── example-5.jpg ├── example-6.jpg ├── example-7.jpg ├── example-8.jpg └── example-9.jpg ├── 19 ├── image-1.jpg ├── image-2.jpg └── image-3.jpg └── 29 ├── safe-areas-error.png ├── safe-areas-success.png └── safe-areas.png /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /01_technologies.md: -------------------------------------------------------------------------------- 1 | # Основные возможности и используемые технологии 2 | 3 | * Система сборки [Gulp](https://gulpjs.com/) 4 | * Оптимизация изображений. 5 | * Генерация PNG- и SVG-спрайтов. 6 | * Шаблонизация с помощью [Pug](https://pugjs.org/). 7 | * CSS-препроцессор [SCSS](http://sass-lang.com/) и [Autoprefixer](https://autoprefixer.github.io/ru/). 8 | * ES6 и [jQuery](https://jquery.com/). 9 | * Встроенное определение устройства, браузера и операционной системы пользователя. 10 | * Проверка кода линтерами ([pug-lint](https://www.npmjs.com/package/pug-lint), [stylelint](https://stylelint.io/), [ESLint](http://eslint.org/)). 11 | * [Browsersync](https://www.browsersync.io/), автоматическое обновление страницы при разработке. 12 | * Возможность быстро создать архив проекта. 13 | * Множество дополнительных параметров сборки. 14 | -------------------------------------------------------------------------------- /02_requirements.md: -------------------------------------------------------------------------------- 1 | # Минимальные требования 2 | 3 | * node >= 9.5.0 4 | * npm >= 5.6.0 5 | * gulp >= 4.0.0 6 | * gulp-cli >= 2.0.1 7 | 8 | [Ссылка на инструкцию по переходу с gulp 3 на gulp 4](https://demisx.github.io/gulp4/2015/01/15/install-gulp4.html). 9 | -------------------------------------------------------------------------------- /03_installation.md: -------------------------------------------------------------------------------- 1 | # Начало работы 2 | 3 | Для установки рекомендуется использовать [Yeoman](http://yeoman.io/): 4 | 5 | ```bash 6 | npm install -g yo 7 | ``` 8 | 9 | После yeoman, необходимо установить шаблон самой сборки: 10 | 11 | ```bash 12 | npm install -g generator-ninelines-template 13 | ``` 14 | 15 | Теперь находясь в пустой папке с проектом выполняем команду: 16 | 17 | ```bash 18 | yo ninelines-template 19 | ``` 20 | 21 | Генератор задаст несколько вопросов: 22 | 23 | - Название проекта (по умолчанию — название папки проекта). 24 | - Описание проекта. 25 | - Запустить ли `npm install` (по умолчанию — да). Если пропустить установку npm-пакетов, то перед началом работы над проектом необходимо самостоятельно запустить `npm install`. 26 | 27 | Теперь можно запустить `gulp` и приступить к работе. 28 | -------------------------------------------------------------------------------- /04_tasks.md: -------------------------------------------------------------------------------- 1 | # Gulp-задачи 2 | 3 | * `default` — основная задача, запускает `build`, `watch` и `serve`. 4 | * `build` — сборка всех файлов, запускает задачи `copy`, `images`, `sprites:png`, `sprites:svg`, `pug`, `scss`, `js`. 5 | * `watch` — запускает слежение за файлами, так что при изменении они автоматически пересобираются. 6 | * `serve` — запускает сервер Browsersync. 7 | * `pug` — запускает сборку Pug-шаблонов. 8 | * `images` — запускает сборку изображений. 9 | * `sprites:png` — запускает генерацию PNG-спрайтов. 10 | * `sprites:svg` — запускает генерацию SVG-спрайтов. 11 | * `scss` — запускает сборку стилей. 12 | * `js` — запускает сборку скриптов. 13 | * `copy` — запускает сборку дополнительных ресурсов. 14 | * `lint` — последовательно запускает линтеры `lint:js`, `lint:pug`, `lint:scss`. 15 | * `lint:js` — проверяет JavaScript-файлы линтером [ESLint](http://eslint.org/). 16 | * `lint:pug` — проверяет Pug-файлы линтером [pug-lint](https://github.com/pugjs/pug-lint). 17 | * `lint:scss` — проверяет SCSS-файлы линтером [stylelint](https://stylelint.io/). 18 | * `optimize:svg` — оптимизирует и форматирует код SVG-файлов в папке `src/images`. 19 | * `optimize:images` — оптимизирует изображения в папке `src/images`. 20 | * `zip` — создает архив проекта. 21 | 22 | ## Дополнительные параметры: 23 | 24 | * `--ci` — включает режим CI (`--no-cache --no-notify --no-open --throw-errors`). 25 | * `--fix` — автоматически исправляет ошибки при проверке кода линтером (только для `lint:js`). 26 | * `--minify` — включает минификацию файлов (только для `sprites:svg`, `pug`, `scss` и `js`). 27 | * `--minify-html` — включает минификацию HTML-файлов (имеет приоритет перед `--minify`). 28 | * `--minify-css` — включает минификацию CSS-файлов (имеет приоритет перед `--minify`). 29 | * `--minify-js` — включает минификацию JS-файлов (имеет приоритет перед `--minify`). 30 | * `--minify-svg` — включает минификацию SVG-файлов (имеет приоритет перед `--minify`). 31 | * `--no-cache` — отключает кэширование (только для `copy`, `images` и `pug`). 32 | * `--no-debug` — отключает отладочный вывод списка обрабатываемых файлов. 33 | * `--no-notify` — отключает уведомления об ошибках. 34 | * `--no-open` — отключает автоматический запуск браузера (только для `serve`). 35 | * `--port` — задает порт сервера (только для `serve`). 36 | * `--spa` — включает режим одностраничного приложения (только для `serve`). 37 | * `--throw-errors` — прерывает сборку при возникновении ошибки. 38 | -------------------------------------------------------------------------------- /05_structure.md: -------------------------------------------------------------------------------- 1 | # Структура папок и файлов 2 | 3 | ```text 4 | ninelines-template 5 | ├── src 6 | │   ├── images 7 | │   │   └── sprites 8 | │   │   ├── png 9 | │   │   │   └── .keep 10 | │   │   └── svg 11 | │   │   └── .keep 12 | │   ├── js 13 | │   │   ├── vendor 14 | │   │   │   └── .keep 15 | │   │   ├── main.js 16 | │   │   └── vendor.js 17 | │   ├── pug 18 | │   │   ├── mixins 19 | │   │   │   └── svg.pug 20 | │   │   ├── base.pug 21 | │   │   └── mixins.pug 22 | │   ├── resources 23 | │   │   └── fonts 24 | │   │   └── .keep 25 | │   ├── scss 26 | │   │   ├── functions 27 | │   │   │   └── _sprites.scss 28 | │   │   ├── mixins 29 | │   │   │   ├── _clearfix.scss 30 | │   │   │   ├── _retina.scss 31 | │   │   │   ├── _sprites.scss 32 | │   │   │   ├── _triangle.scss 33 | │   │   │   └── _visually-hidden.scss 34 | │   │   ├── vendor 35 | │   │   │   └── .keep 36 | │   │   ├── _base.scss 37 | │   │   ├── _fonts.scss 38 | │   │   ├── _functions.scss 39 | │   │   ├── _mixins.scss 40 | │   │   ├── _sprites.hbs 41 | │   │   ├── _sprites.scss 42 | │   │   ├── _variables.scss 43 | │   │   ├── _vendor.scss 44 | │   │   └── main.scss 45 | │   └── index.pug 46 | ├── .babelrc 47 | ├── .editorconfig 48 | ├── .eslintignore 49 | ├── .eslintrc 50 | ├── .gitignore 51 | ├── .npmrc 52 | ├── .pug-lintrc.json 53 | ├── .stylelintignore 54 | ├── .stylelintrc 55 | ├── gulpfile.js 56 | ├── package.json 57 | ├── README.md 58 | └── webpack.config.js 59 | ``` 60 | 61 | ## `src` 62 | 63 | В папке `src` хранятся исходные файлы проекта. 64 | 65 | ## `src/images` 66 | 67 | Папка `images` предназначена для хранения изображений. 68 | При сборке файлы из данной папки попадают в `build/images`. 69 | 70 | ## `src/images/sprites` 71 | 72 | Папка `src/images/sprites` предназначена для хранения векторных (SVG) и растровых (PNG) иконок. 73 | 74 | ## `src/images/sprites/png` 75 | 76 | Папка `src/images/sprites/png` предназначена для хранения растровых иконок. 77 | При сборке файлы из данной папки объединяются в два спрайта: `build/images/sprites.png` и `build/images/sprites@2x.png`. 78 | 79 | ## `src/images/sprites/svg` 80 | 81 | Папка `src/images/sprites/svg` предназначена для хранения векторных иконок. 82 | При сборке файлы из данной папки объединяются в один спрайт: `build/images/sprites.svg`. 83 | 84 | ## `src/js` 85 | 86 | Папка `src/js` предназначена для хранения скриптов. 87 | 88 | ## `src/js/vendor` 89 | 90 | Папка `src/js/vendor` предназначена для хранения скриптов сторонних библиотек, которых нет в репозитории npm. 91 | 92 | ## `src/js/main.js` 93 | 94 | Файл `src/js/main.js` предназначен для хранения основной логики сайта. 95 | При сборке данный файл попадает в `build/js`. 96 | 97 | ## `src/js/vendor.js` 98 | 99 | Файл `src/js/vendor.js` предназначен для подключения сторонних библиотек. 100 | 101 | При сборке данный файл попадет в `build/js`. 102 | 103 | ## `src/pug` 104 | 105 | Папка `src/pug` предназначена для хранения шаблонов. 106 | 107 | ## `src/pug/mixins` 108 | 109 | Папка `src/pug/mixins` предназначена для хранения Pug-миксин. 110 | 111 | ## `src/pug/base.pug` 112 | 113 | В файле `src/pug/base.pug` хранится базовый шаблон страниц сайта. 114 | 115 | ## `src/pug/mixins.pug` 116 | 117 | Файл `src/pug/mixins.pug` предназначен для подключения Pug-миксин из папки `src/pug/mixins`. 118 | 119 | ## `src/resources` 120 | 121 | Папка `src/resources` предназначена для хранения различных файлов проекта. 122 | При сборке файлы из данной папки попадают в `build`. 123 | 124 | ## `src/resources/fonts` 125 | 126 | Папка `src/resources/fonts` предназначена для хранения шрифтов. 127 | При сборке файлы из данной папки попадают в `build/fonts`. 128 | 129 | ## `src/scss` 130 | 131 | Папка `src/scss` предназначена для хранения стилей. 132 | 133 | ## `src/scss/functions` 134 | 135 | Папка `src/scss/functions` предназначена для хранения SCSS-функций. 136 | 137 | ## `src/scss/mixins` 138 | 139 | Папка `src/scss/mixins` предназначена для хранения SCSS-миксин. 140 | 141 | ## `src/scss/vendor` 142 | 143 | Папка `src/scss/vendor` предназначена для хранения стилей сторонних библиотек, которых нет в репозитории npm. 144 | 145 | ## `src/scss/_base.scss` 146 | 147 | Файл `src/scss/_base.scss` предназначен для хранения базовых стилей. 148 | 149 | ## `src/scss/_fonts.scss` 150 | 151 | Файл `src/scss/_fonts.scss` предназначен для подключения шрифтов. 152 | 153 | ## `src/scss/_functions.scss` 154 | 155 | Файл `src/scss/_functions.scss` предназначен для подключения функций из папки `src/scss/functions`. 156 | 157 | ## `src/scss/_mixins.scss` 158 | 159 | Файл `src/scss/_mixins.scss` предназначен для подключения миксин из папки `src/scss/mixins`. 160 | 161 | ## `src/scss/_sprites.hbs` 162 | 163 | `src/scss/_sprites.hbs` — шаблон, на основе которого генерируется содержимое файла `src/scss/_sprites.scss`. 164 | 165 | ## `src/scss/_sprites.scss` 166 | 167 | Файл `src/scss/_sprites.scss` предназначен для работы с PNG-спрайтами. 168 | Содержимое данного файла автоматически генерируется на основе шаблона `src/scss/_sprites.hbs` и иконок из папки `src/images/sprites/png`. 169 | 170 | ## `src/scss/_variables.scss` 171 | 172 | Файл `src/scss/_variables.scss` предназначен для хранения SCSS-переменных. 173 | 174 | ## `src/scss/_vendor.scss` 175 | 176 | Файл `src/scss/_vendor.scss` предназначен для подключения стилей сторонних библиотек. 177 | 178 | ## `src/scss/main.scss` 179 | 180 | Файл `src/scss/main.scss` предназначен для хранения основных стилей сайта. 181 | При сборке данный файл преобразуется в CSS и сохраняется в `build/css` вместе с файлом `main.css.map`. 182 | 183 | ## `src/index.pug` 184 | 185 | `src/index.pug` — шаблон главной страницы. 186 | При сборке все Pug-файлы из папки `src` преобразуются в HTML и сохраняются в `build`. 187 | 188 | ## `.babelrc` 189 | 190 | `.babelrc` — файл настроек JavaScript-транспайлера Babel. 191 | 192 | ## `.editorconfig` 193 | 194 | `.editorconfig` — файл настроек редактора. 195 | 196 | ## `.eslintignore` 197 | 198 | `.eslintignore` — файл настроек ESLint для игнорирования файлов. 199 | 200 | ## `.eslintrc` 201 | 202 | `.eslintrc` — файл настроек ESLint. 203 | 204 | ## `.gitignore` 205 | 206 | `.gitignore` — файл настроек Git для игнорирования файлов. 207 | 208 | ## `.npmrc` 209 | 210 | `.npmrc` — файл настроек npm. 211 | 212 | ## `.pug-lintrc.json` 213 | 214 | `.pug-lintrc.json` — файл настроек pug-lint. 215 | 216 | ## `.stylelintignore` 217 | 218 | `.stylelintignore` — файл настроек stylelint для игнорирования файлов. 219 | 220 | ## `.stylelintrc` 221 | 222 | `.stylelintrc` — файл настроек stylelint. 223 | 224 | ## `gulpfile.js` 225 | 226 | `gulpfile.js` — основной файл сборки, содержащий Gulp-задачи. 227 | 228 | ## `package.json` 229 | 230 | `package.json` — файл, содержащий базовую информацию о проекте и список требуемых библиотек. 231 | 232 | ## `README.md` 233 | 234 | `README.md` — описание проекта. 235 | 236 | ## `webpack.config.js` 237 | 238 | `webpack.config.js` — файл настроек webpack. 239 | -------------------------------------------------------------------------------- /06_libraries.md: -------------------------------------------------------------------------------- 1 | # Подключение сторонних библиотек 2 | 3 | Библиотеки подключаются с помощью npm. 4 | При установке следует указывать ключ `--save` или `--save-dev`. 5 | 6 | Пример: 7 | 8 | ```bash 9 | npm install --save jquery 10 | npm install --save-dev gulp 11 | ``` 12 | 13 | `--save` указывается для библиотек, код которых попадает в итоговую сборку (папку `build`) и будет использоваться на сайте. 14 | 15 | `--save-dev` указывается для библиотек, которые используются только для сборки. 16 | 17 | После установки необходимо подключить нужные файлы библиотеки: 18 | 19 | * скрипты — в `src/js/vendor.js` или `src/js/main.js`. 20 | * стили — в `src/scss/_vendor.scss`. 21 | * изображения — в `src/images`. 22 | * любые другие файлы — в `src/resources`. 23 | 24 | Полный пример, описывающий установку библиотеки fancybox: 25 | 26 | 1. Установка: 27 | 28 | ```bash 29 | npm install --save fancybox 30 | ``` 31 | 32 | 2. Подключение скриптов в файл `src/js/vendor.js`: 33 | 34 | ```js 35 | import 'fancybox'; 36 | ``` 37 | 38 | 3. Подключение стилей в файл `src/scss/_vendor.scss`: 39 | 40 | ```scss 41 | $fancybox-image-url: "../images"; 42 | 43 | @import "../../node_modules/fancybox/dist/scss/jquery.fancybox"; 44 | ``` 45 | 46 | 4. Копирование изображений в `src/images`: 47 | 48 | ```text 49 | ninelines-template 50 | └── src 51 | ├── images 52 | │   ├── blank.gif 53 | │   ├── fancybox_loading.gif 54 | │   ├── fancybox_loading@2x.gif 55 | │   ├── fancybox_overlay.png 56 | │   ├── fancybox_sprite.png 57 | │   ├── fancybox_sprite@2x.png 58 | │   └── ... 59 | └── ... 60 | ``` 61 | 62 | Если библиотека отсутствует в npm, либо её нужно модифицировать, то файлы следует скачать и закинуть в папки `src/js/vendor` и `src/scss/vendor`. 63 | -------------------------------------------------------------------------------- /07_images.md: -------------------------------------------------------------------------------- 1 | # Работа с изображениями 2 | 3 | Изображения следует хранить в папке `src/images`. 4 | При запуске задачи `images` файлы из папки `src/images` копируются в `build/images`. 5 | 6 | ```text 7 | ninelines-template 8 | ├── build 9 | │   └── images 10 | └── src 11 | └── images 12 | ``` 13 | 14 | Для оптимизации изображений можно использовать задачу `optimize:images`. 15 | 16 | > `optimize:images` оптимизирует только исходные файлы из папки `src/images`! 17 | 18 | Предварительно оптимизированные изображения рекомендуется хранить в папке `src/resources/images`. 19 | В таком случае при запуске задачи `optimize:images` данные файлы не будут затронуты. 20 | 21 | ```text 22 | ninelines-template 23 | └── src 24 | └── resources 25 | └── images 26 | ``` 27 | 28 | ## Работа с PNG-спрайтами 29 | 30 | Работа с PNG-спрайтами строится следующим образом: 31 | 32 | 1. Берем две версии иконки — обычную и retina (увеличенную в два раза). 33 | Сохраняем в `src/images/sprites/png`: 34 | 35 | ```text 36 | ninelines-template 37 | └── src 38 |    └── images 39 |       └── sprites 40 |       └── png 41 |          ├── phone.png 42 |          └── phone@2x.png 43 | ``` 44 | 45 | 2. Запускаем задачу `sprites:png` (если уже запущен `gulp watch` или `gulp`, то данный шаг можно пропустить): 46 | 47 | ```bash 48 | gulp sprites:png 49 | ``` 50 | 51 | 3. Генератор оптимизирует и объединяет иконки в спрайты: 52 | 53 | ```text 54 | ninelines-template 55 | └── build 56 |    └── images 57 | ├── sprites.png 58 | └── sprites@2x.png 59 | ``` 60 | 61 | На основе предзаданного шаблона `src/scss/_sprites.hbs` генерируется файл `src/scss/_sprites.scss`, содержащий вспомогательную информацию о получившихся спрайтах: 62 | 63 | ```text 64 | ninelines-template 65 | └── src 66 |    └── scss 67 | ├── _sprites.hbs 68 | └── _sprites.scss 69 | ``` 70 | 71 | Для каждой иконки создается CSS-класс в формате `.sprite-[name]`. 72 | В нашем случае получим класс `.sprite-phone`. 73 | 74 | В сборке также содержится ряд SCSS-функций и миксин для работы со спрайтами. 75 | 76 | `src/scss/functions/_sprites.scss`: 77 | 78 | ```scss 79 | @function sprite($name, $size: normal) { /* ... */ } 80 | @function sprite-width($name, $size: normal) { /* ... */ } 81 | @function sprite-height($name, $size: normal) { /* ... */ } 82 | @function sprite-image($name, $size: normal) { /* ... */ } 83 | @function sprite-x($name, $size: normal) { /* ... */ } 84 | @function sprite-y($name, $size: normal) { /* ... */ } 85 | @function sprite-total-width($name, $size: normal) { /* ... */ } 86 | @function sprite-total-height($name, $size: normal) { /* ... */ } 87 | ``` 88 | 89 | `src/scss/mixins/_srites.scss`: 90 | 91 | ```scss 92 | @mixin sprite-width($name, $size: normal) { /* ... */ } 93 | @mixin sprite-height($name, $size: normal) { /* ... */ } 94 | @mixin sprite-background-image($name, $size: normal) { /* ... */ } 95 | @mixin sprite-background-position($name, $size: normal) { /* ... */ } 96 | @mixin sprite-background-size($name, $size: normal) { /* ... */ } 97 | @mixin sprite-background($name, $size: normal) { /* ... */ } 98 | @mixin sprite($name) { /* ... */ } 99 | ``` 100 | 101 | 4. Полученные спрайты можно использовать в Pug (с помощью классов): 102 | 103 | ```jade 104 | footer 105 | a(href="tel:+71234567890") 106 | span.sprite-phone 107 | | +7 (123) 456-78-90 108 | ``` 109 | 110 | Или в SCSS (с помощью миксин): 111 | 112 | ```scss 113 | footer { 114 | a { 115 | &::before { 116 | @include sprite("phone"); 117 | 118 | content: ""; 119 | } 120 | } 121 | } 122 | ``` 123 | 124 | ## Работа с SVG-спрайтами 125 | 126 | Принцип работы с SVG-спрайтами: 127 | 128 | 1. Получаем векторные иконки в формате `.svg` (либо заранее подготовленные, либо экспортируем с помощью редактора). 129 | Сохраняем в папку `src/images/sprites/svg`: 130 | 131 | ```text 132 | ninelines-template 133 | └── src 134 |    └── images 135 |       └── sprites 136 |       └── svg 137 |          └── phone.svg 138 | ``` 139 | 140 | 2. Запускаем задачу `sprites:svg` (если уже запущен `gulp watch` или `gulp`, то данный шаг можно пропустить): 141 | 142 | ```bash 143 | gulp sprites:svg 144 | ``` 145 | 146 | 3. Генератор оптимизирует и объединяет иконки в один спрайт: 147 | 148 | ```text 149 | ninelines-template 150 | └── build 151 |    └── images 152 | └── sprites.svg 153 | ``` 154 | 155 | В сборке содержится Pug-миксин для подключения SVG-спрайтов.
156 | `src/pug/mixins/svg.pug`: 157 | 158 | ```jade 159 | mixin svg(name) 160 | svg&attributes(attributes) 161 | use(xlink:href=`${baseDir}images/sprites.svg#${name}`) 162 | ``` 163 | 164 | 4. Подключаем иконку в Pug: 165 | 166 | ```jade 167 | footer 168 | a(href="tel:+71234567890") 169 | +svg("phone") 170 | | +7 (123) 456-78-90 171 | ``` 172 | 173 | При необходимости иконку можно стилизовать: 174 | 175 | ```scss 176 | footer { 177 | a { 178 | svg { 179 | display: inline-block; 180 | vertical-align: middle; 181 | width: 30px; 182 | height: 30px; 183 | fill: $color-black; 184 | } 185 | } 186 | } 187 | ``` 188 | 189 | Если цвет заливки или обводки не удается изменить с помощью CSS, то необходимо открыть SVG-файл иконки в редакторе и удалить соответствующие атрибуты (`fill`, `stroke`) из кода. 190 | 191 | ## Избавляемся от обрезанных краев SVG-иконок 192 | 193 | 1. Общий пример: 194 | 195 | Шаг 1: исходная иконка без полей 196 | ```html 197 | 198 | 199 | 200 | 201 | 202 | ``` 203 | 204 | Шаг 2: добавляем поле размером {padding} 205 | ```html 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | ``` 214 | 215 | Шаг 3: запускаем optimize:svg и получаем иконку без лишних трансформаций 216 | ```html 217 | 218 | 219 | 220 | 221 | 222 | ``` 223 | 224 | 2. Конкретный пример: 225 | 226 | Шаг 1: исходная иконка без полей 227 | ```html 228 | 229 | 230 | 231 | 232 | 233 | ``` 234 | 235 | Шаг 2: добавляем поле размером 1px 236 | ```html 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | ``` 245 | 246 | Шаг 3: запускаем optimize:svg и получаем иконку без лишних трансформаций 247 | ```html 248 | 249 | 250 | 251 | 252 | 253 | ``` 254 | -------------------------------------------------------------------------------- /08_templates.md: -------------------------------------------------------------------------------- 1 | # Работа с шаблонизатором Pug 2 | 3 | > При работе с шаблонизатором **важно** придерживаться установленных [правил по оформлению кода](16_codestyle-pug.md). 4 | 5 | В сборке используется шаблонизатор [Pug](https://pugjs.org/) (ранее назывался Jade). 6 | 7 | Pug предоставляет множество возможностей, упрощающих работу с шаблонами: 8 | 9 | * Переменные. 10 | * Циклы. 11 | * Условия. 12 | * Фильтры. 13 | * Наследование шаблонов. 14 | * Миксины. 15 | 16 | Шаблоны страниц размещаются в `src`, а дополнительные файлы и миксины в `src/pug`: 17 | 18 | ```text 19 | ninelines-template 20 | └── src 21 |    ├── pug 22 |    │   ├── mixins 23 |    │   │   └── svg.pug 24 |    │   ├── base.pug 25 |    │   └── mixins.pug 26 |    └── index.pug 27 | ``` 28 | 29 | За сборку и преобразование Pug в HTML отвечает задача `pug`: 30 | 31 | ```bash 32 | gulp pug 33 | ``` 34 | 35 | После выполнения команды в папке `build` появятся HTML-файлы: 36 | 37 | ```text 38 | ninelines-template 39 | └── build 40 |    └── index.html 41 | ``` 42 | 43 | ## Базовый шаблон и создание страниц 44 | 45 | В качестве базового шаблона используется `src/pug/base.pug`. 46 | 47 | Пример наследования и использования шаблона: 48 | 49 | ```jade 50 | extends pug/base 51 | 52 | block content 53 | // Содержимое страницы 54 | ``` 55 | 56 | Базовый шаблон определяет блоки (участки кода или место в шаблоне), которые можно изменять и дополнять при наследовании. 57 | 58 | ### `vars` 59 | 60 | Блок `vars` хранит основные настройки шаблона: 61 | 62 | * `baseDir` — корневая директория сайта (по умолчанию `/`). 63 | 64 | * `title` — заголовок страницы (используется в `` и метатегах). 65 | 66 | * `description` — описание страницы (используется в метатегах). 67 | 68 | * `image` — изображение страницы (используется в метатегах). 69 | 70 | * `html` — настройки тега `<html>`: 71 | * `html.attrs` — объект для задания дополнительных атрибутов. 72 | * `html.classList` — массив классов. 73 | 74 | * `body` — настройки тега `<body>`: 75 | * `body.attrs` — объект для задания дополнительных атрибутов. 76 | * `body.classList` — массив классов. 77 | 78 | * `meta` — значения метатегов. 79 | 80 | * `link` — значения тегов `<link>`. 81 | 82 | Пример использования: 83 | 84 | ```jade 85 | prepend vars 86 | - title = 'Заголовок' 87 | - description = 'Описание' 88 | - image = 'http://example.com/images/image.png' 89 | 90 | append vars 91 | - link.icon16x16 = '/favicon_16x16.png' 92 | - link.icon32x32 = '/favicon_32x32.png' 93 | ``` 94 | 95 | ### `head-start` 96 | 97 | Блок `head-start` является альтернативой `prepend meta`. 98 | 99 | ### `meta` 100 | 101 | В блоке `meta` подключаются метатеги. 102 | 103 | Пример использования: 104 | 105 | ```jade 106 | append meta 107 | meta(name="referrer" content="none") 108 | ``` 109 | 110 | ### `links` 111 | 112 | В блоке `links` подключаются внешние ресурсы. 113 | 114 | Пример использования: 115 | 116 | ```jade 117 | append links 118 | link(rel="prefetch" href="/images/background.jpg") 119 | ``` 120 | 121 | ### `styles` 122 | 123 | В блоке `styles` подключаются стили. 124 | 125 | Пример использования: 126 | 127 | ```jade 128 | append styles 129 | link(rel="stylesheet" href="/css/custom.css") 130 | ``` 131 | 132 | ### `head-end` 133 | 134 | Блок `head-end` является альтернативой `append links`. 135 | 136 | ### `body-start` 137 | 138 | Блок `body-start` является альтернативой `prepend content`. 139 | 140 | ### `content` 141 | 142 | Блок `content` предназначен для хранения содержимого страницы. 143 | 144 | Пример использования: 145 | 146 | ```jade 147 | block content 148 | .container 149 | h1 150 | | Заголовок страницы 151 | ``` 152 | 153 | ### `scripts` 154 | 155 | В блоке `scripts` подключаются скрипты. 156 | 157 | Пример использования: 158 | 159 | ```jade 160 | append scripts 161 | script(src="/js/custom.js") 162 | ``` 163 | 164 | ### `body-end` 165 | 166 | Блок `body-end` является альтернативой `append scripts`. 167 | 168 | ## Правила написания кода и использование линтера 169 | 170 | В сборку интегрирован линтер [pug-lint](https://www.npmjs.com/package/pug-lint). 171 | Файл настроек — `.pug-lintrc.json`. 172 | Данный линтер позволяет поддерживать Pug-код в соответствии с заданным регламентом. 173 | 174 | Проверка осуществляется с помощью задачи `lint:pug`. 175 | 176 | Пример использования (`src/index.pug`): 177 | 178 | ```jade 179 | extends pug/base 180 | 181 | append vars 182 | - html.classList.push('page-index') 183 | 184 | block content 185 | a(href='#').link Ссылка 186 | ``` 187 | 188 | Результаты проверки: 189 | 190 | ```text 191 | ninelines-template/src/index.pug:7:14 192 | 5| 193 | 6| block content 194 | > 7| a(href='#').link Ссылка 195 | --------------------^ 196 | 8| 197 | 198 | All class literals must be written before any attribute blocks 199 | 200 | ninelines-template/src/index.pug:7:5 201 | 5| 202 | 6| block content 203 | > 7| a(href='#').link Ссылка 204 | -----------^ 205 | 8| 206 | 207 | Invalid attribute quote mark found 208 | 209 | ninelines-template/src/index.pug:4:1 210 | 2| 211 | 3| append vars 212 | > 4| - html.classList.push('page-index') 213 | -------^ 214 | 5| 215 | 6| block content 216 | 7| a(href='#').link Ссылка 217 | 218 | Invalid indentation 219 | 220 | ninelines-template/src/index.pug:7:1 221 | 5| 222 | 6| block content 223 | > 7| a(href='#').link Ссылка 224 | -------^ 225 | 8| 226 | 227 | Invalid indentation 228 | ``` 229 | 230 | Исправленный код: 231 | 232 | ```jade 233 | extends pug/base 234 | 235 | append vars 236 | - html.classList.push('page-index') 237 | 238 | block content 239 | a.link(href="#") 240 | | Ссылка 241 | ``` 242 | 243 | В дополнение к проверкам кода линтером следует придерживаться следующих правил: 244 | 245 | * Повторяющиеся участки кода по возможности выносить в отдельные миксины. 246 | * Схожие по структуре страницы выносить в отдельный шаблон и наследоваться от него. 247 | -------------------------------------------------------------------------------- /09_styles.md: -------------------------------------------------------------------------------- 1 | # Работа со стилями 2 | 3 | > При работе со стилями **важно** придерживаться установленных [правил по оформлению кода](17_codestyle-scss.md). 4 | 5 | В сборке используется препроцессор [SCSS](http://sass-lang.com/) и PostCSS-плагин [Autoprefixer](https://autoprefixer.github.io/ru/). 6 | 7 | Стили размещаются в папке `src/scss`: 8 | 9 | ```text 10 | ninelines-template 11 | └── src 12 |    └── scss 13 |       ├── functions 14 |       │   ├── _responsive.scss 15 | │   └── _sprites.scss 16 |       ├── mixins 17 |       │   ├── _breakpoint.scss 18 |       │   ├── _clearfix.scss 19 |       │   ├── _retina.scss 20 | │   ├── _sprites.scss 21 | │   ├── _triangle.scss 22 | │   └── _visually-hidden.scss 23 |       ├── vendor 24 |       │   └── .keep 25 |       ├── _base.scss 26 |       ├── _fonts.scss 27 |       ├── _functions.scss 28 |       ├── _mixins.scss 29 |       ├── _sprites.hbs 30 |       ├── _sprites.scss 31 |       ├── _variables.scss 32 |       ├── _vendor.scss 33 |       └── main.scss 34 | ``` 35 | 36 | За сборку и преобразование SCSS в CSS отвечает задача `scss`: 37 | 38 | ```bash 39 | gulp scss 40 | ``` 41 | 42 | После выполнения команды в папке `build/css` появятся файлы `main.css` и `main.css.map`: 43 | 44 | ```text 45 | ninelines-template 46 | └── build 47 |    └── css 48 |       ├── main.css 49 |       └── main.css.map 50 | ``` 51 | 52 | ## Правила написания кода 53 | 54 | ### БЭМ 55 | 56 | Для именования классов рекомендуется использовать [БЭМ-нотацию](https://ru.bem.info/methodology/naming-convention/). 57 | 58 | ```scss 59 | .block { 60 | &__element { 61 | &--modificator { 62 | // ... 63 | } 64 | } 65 | } 66 | ``` 67 | 68 | ### Классы состояний 69 | 70 | Классы состояний рекомендуется записывать кратко: 71 | 72 | ```scss 73 | .is-active { 74 | // ... 75 | } 76 | 77 | .is-current { 78 | // ... 79 | } 80 | 81 | .is-open { 82 | // ... 83 | } 84 | 85 | .is-hidden { 86 | // ... 87 | } 88 | ``` 89 | 90 | ### Порядок CSS-свойств 91 | 92 | CSS-свойства следует записывать в определенном порядке. Порядок задан в файле `.stylelintrc` (ключ `order/properties-order`). 93 | Проверить правильность порядка свойств можно с помощью линтера: 94 | 95 | ```bash 96 | gulp lint:scss 97 | ``` 98 | 99 | ### Переменные 100 | 101 | В файл `src/scss/_variables.scss` следует выносить лишь основные переменные: 102 | 103 | * `font-family` для шрифтов. Пример: 104 | 105 | ```scss 106 | $font-family-roboto: Roboto, sans-serif; 107 | $font-family-pt-serif: PT Serif, serif; 108 | ``` 109 | 110 | * Цвета. Пример: 111 | 112 | ```scss 113 | $color-aqua-deep: #005741; 114 | $color-black: #000; 115 | $color-white: #fff; 116 | ``` 117 | 118 | Для именования цветов можно пользоваться [данным сервисом](http://chir.ag/projects/name-that-color/). 119 | 120 | Переменные, используемые лишь в одном блоке или компоненте следует записывать в том же файле, где они используются. 121 | 122 | ### `@mixin` и `@extend` 123 | 124 | Повторяющиеся участки кода (20-30 строк и более), отличающиеся лишь значениями, следует выносить в отдельные миксины. 125 | 126 | Не рекомендуется использовать директиву `@extend`. Вместо неё следует воспользоваться `@mixin`. 127 | 128 | ### Вендорные префиксы 129 | 130 | В SCSS-коде не должно присутствовать вендорных префиксов. Они автоматически расставляются в процессе сборки. Однако существуют исключения и некоторые префиксы необходимо добавлять вручную. 131 | 132 | **Неправильно:** 133 | 134 | ```scss 135 | input { 136 | -webkit-transition: border-color 0.3s; 137 | transition: border-color 0.3s; 138 | 139 | &::-webkit-input-placeholder { 140 | color: #000; 141 | } 142 | 143 | &:-moz-placeholder { 144 | color: #000; 145 | } 146 | 147 | &::-moz-placeholder { 148 | color: #000; 149 | } 150 | 151 | &:-ms-input-placeholder { 152 | color: #000; 153 | } 154 | 155 | &::placeholder { 156 | color: #000; 157 | } 158 | } 159 | ``` 160 | 161 | **Правильно:** 162 | 163 | ```scss 164 | input { 165 | transition: border-color 0.3s; 166 | 167 | &::placeholder { 168 | color: #000; 169 | } 170 | } 171 | ``` 172 | 173 | ## Использование линтера 174 | 175 | В сборку интегрирован линтер [stylelint](https://stylelint.io/). 176 | Файл настроек — `.stylelintrc`. 177 | Данный линтер позволяет поддерживать SCSS-код в соответствии с заданным регламентом. 178 | 179 | Проверка осуществляется с помощью задачи `lint:scss`: 180 | 181 | ```bash 182 | gulp lint:scss 183 | ``` 184 | 185 | Пример использования: 186 | 187 | ```scss 188 | .block { 189 | &__element { 190 | display: inline-block 191 | } 192 | border-radius: 0px; 193 | height: 30px; 194 | width:30px; 195 | } 196 | ``` 197 | 198 | Результаты проверки: 199 | 200 | ```text 201 | 2:3 ⚠ Expected indentation of 1 tab (indentation) [stylelint] 202 | 3:5 ⚠ Expected indentation of 2 tabs (indentation) [stylelint] 203 | 3:25 ⚠ Expected a trailing semicolon (declaration-block-trailing-semicolon) [stylelint] 204 | 4:3 ⚠ Expected indentation of 1 tab (indentation) [stylelint] 205 | 5:3 ⚠ Expected indentation of 1 tab (indentation) [stylelint] 206 | 5:3 ⚠ Expected declaration to come before rule (order/order) [stylelint] 207 | 5:3 ⚠ Expected empty line before declaration (declaration-empty-line-before) [stylelint] 208 | 5:19 ⚠ Unexpected unit (length-zero-no-unit) [stylelint] 209 | 6:3 ⚠ Expected indentation of 1 tab (indentation) [stylelint] 210 | 7:3 ⚠ Expected indentation of 1 tab (indentation) [stylelint] 211 | 7:3 ⚠ Expected "width" to come before "height" (order/properties-order) [stylelint] 212 | 7:9 ⚠ Expected single space after ":" with a single-line declaration (declaration-colon-space-after) [stylelint] 213 | ``` 214 | 215 | Исправленный код: 216 | 217 | ```scss 218 | .block { 219 | border-radius: 0; 220 | width:30px; 221 | height: 30px; 222 | 223 | &__element { 224 | display: inline-block 225 | } 226 | } 227 | ``` 228 | -------------------------------------------------------------------------------- /10_scripts.md: -------------------------------------------------------------------------------- 1 | # Работа со скриптами 2 | 3 | > При работе со скриптами **важно** придерживаться установленных [правил по оформлению кода](18_codestyle-javascript.md). 4 | 5 | Скрипты размещаются в папке `src/js`: 6 | 7 | ```text 8 | ninelines-template 9 | └── src 10 |    └── js 11 |       ├── vendor 12 |       │   └── .keep 13 |       ├── main.js 14 |       └── vendor.js 15 | ``` 16 | 17 | За сборку и преобразование JS отвечает задача `js`: 18 | 19 | ```bash 20 | gulp js 21 | ``` 22 | 23 | После выполнения команды в папке `build/js` появятся файлы `main.js` и `vendor.js`: 24 | 25 | ```text 26 | ninelines-template 27 | └── build 28 |    └── js 29 |       ├── main.js 30 |       └── vendor.js 31 | ``` 32 | 33 | Также дополнительно подключены библиотеки: 34 | 35 | * [jQuery](https://jquery.com/) 36 | * [ninelines-ua-parser](https://github.com/ninelines-team/ninelines-ua-parser) 37 | 38 | [ninelines-ua-parser](https://github.com/ninelines-team/ninelines-ua-parser) основана на 39 | [ua-parser-js](https://github.com/faisalman/ua-parser-js) и отвечает за определение устройства, браузера и операционной 40 | системы пользователя, а также автоматически проставляет классы `<html>` элементу: 41 | 42 | * `.is-os-mac-os` 43 | * `.is-os-windows` 44 | * `.is-os-linux` 45 | * `.is-os-android` 46 | * `.is-os-ios` 47 | * `.is-device-mobile` 48 | * `.is-device-tablet` 49 | * `.is-device-desktop` 50 | * `.is-engine-webkit` 51 | * `.is-engine-gecko` 52 | * `.is-browser-chrome` 53 | * `.is-browser-firefox` 54 | * `.is-browser-ie` 55 | * `.is-browser-safari` 56 | 57 | Данные классы можно использовать для стилизации элементов: 58 | 59 | ```scss 60 | .for-desktop { 61 | .is-device-mobile & { 62 | display: none; 63 | } 64 | } 65 | ``` 66 | 67 | ## Правила написания кода 68 | 69 | ### Короткие именна переменных 70 | 71 | Не следует сокращать имена переменных. 72 | 73 | **Неправильно:** 74 | 75 | ```js 76 | $('.elements').each((i, e) => { 77 | // ... 78 | }); 79 | ``` 80 | 81 | **Правильно:** 82 | 83 | ```js 84 | $('.elements').each((index, element) => { 85 | // ... 86 | }); 87 | ``` 88 | 89 | Исключение могут составить имена счетчиков в цикле (`i`, `j`, `k`): 90 | 91 | ```js 92 | for (let i = 0; i < 10; i++) { 93 | // ... 94 | } 95 | ``` 96 | 97 | ### Именование jQuery-переменных 98 | 99 | Название переменных, являющихся jQuery-объектами, следует начинать с `$`. 100 | 101 | **Неправильно:** 102 | 103 | ```js 104 | let element = $('.element'); 105 | ``` 106 | 107 | **Правильно:** 108 | 109 | ```js 110 | let $element = $('.element'); 111 | ``` 112 | 113 | ### jQuery-селекторы 114 | 115 | Следует избегать дублирования jQuery-селекторов. 116 | Если обращение к элементу происходит многократно, то jQuery-объект можно сохранить в отдельную переменную, либо переписать код так, чтобы избежать дублирования. 117 | 118 | **Неправильно:** 119 | 120 | ```js 121 | $('.element').on('click', () => { 122 | // ... 123 | }); 124 | 125 | $('.element').on('mouseenter', () => { 126 | // ... 127 | }); 128 | ``` 129 | 130 | **Правильно:** 131 | 132 | ```js 133 | let $element = $('.element'); 134 | 135 | $element.on('click', () => { 136 | // ... 137 | }); 138 | 139 | $element.on('mouseenter', () => { 140 | // ... 141 | }); 142 | ``` 143 | 144 | Или так: 145 | 146 | ```js 147 | $('.element') 148 | .on('click', () => { 149 | // ... 150 | }) 151 | .on('mouseenter', () => { 152 | // ... 153 | }); 154 | ``` 155 | 156 | ### Обработка событий с помощью jQuery 157 | 158 | Для создания обработчика событий следует использовать функцию [`.on()`](http://api.jquery.com/on/). 159 | 160 | **Неправильно:** 161 | 162 | ```js 163 | $('button').click(() => { 164 | // ... 165 | }); 166 | 167 | $('form').submit(() => { 168 | // ... 169 | }); 170 | ``` 171 | 172 | **Правильно:** 173 | 174 | ```js 175 | $('button').on('click', () => { 176 | // ... 177 | }); 178 | 179 | $('form').on('submit', () => { 180 | // ... 181 | }); 182 | ``` 183 | 184 | ## Использование линтера 185 | 186 | В сборку интегрирован линтер [ESLint](http://eslint.org/). 187 | Файл настроек — `.eslintrc`. 188 | Данный линтер позволяет поддерживать JavaScript-код в соответствии с заданным регламентом. 189 | 190 | Проверка осуществляется с помощью задачи `lint:js`: 191 | 192 | ```bash 193 | gulp lint:js 194 | ``` 195 | 196 | Пример использования: 197 | 198 | ```js 199 | var $form = $('.form') 200 | $form.on("submit", function () { 201 | $.post('ajax.php', function (data) { 202 | $(".result").html(data); 203 | }) 204 | }) 205 | ``` 206 | 207 | Результаты проверки: 208 | 209 | ```text 210 | 1:1 error Expected blank line after variable declarations newline-after-var 211 | 1:1 error Unexpected var, use let or const instead no-var 212 | 1:23 error Missing semicolon semi 213 | 2:10 error Strings must use singlequote quotes 214 | 2:20 error Unexpected function expression prefer-arrow-callback 215 | 2:20 warning Unexpected unnamed function func-names 216 | 3:1 error Expected indentation of 1 tab but found 2 spaces indent 217 | 3:22 warning Unexpected unnamed function func-names 218 | 3:22 error Unexpected function expression prefer-arrow-callback 219 | 4:1 error Expected indentation of 2 tabs but found 4 spaces indent 220 | 4:7 error Strings must use singlequote quotes 221 | 5:1 error Expected indentation of 1 tab but found 2 spaces indent 222 | 5:5 error Missing semicolon semi 223 | 6:3 error Missing semicolon semi 224 | 225 | ✖ 14 problems (12 errors, 2 warnings) 226 | 12 errors, 0 warnings potentially fixable with the `--fix` option. 227 | ``` 228 | 229 | ESlint сообщает о 14 найденных ошибках, причем большая часть из них может быть исправлена автоматически. 230 | За это отвечает ключ `--fix`, который можно указать при запуске задачи `lint:js`: 231 | 232 | ```bash 233 | gulp lint:js --fix 234 | ``` 235 | 236 | Исправленный код: 237 | 238 | ```js 239 | let $form = $('.form'); 240 | 241 | $form.on('submit', () => { 242 | $.post('ajax.php', (data) => { 243 | $('.result').html(data); 244 | }); 245 | }); 246 | ``` 247 | -------------------------------------------------------------------------------- /11_resources.md: -------------------------------------------------------------------------------- 1 | # Работа с дополнительными ресурсами 2 | 3 | Дополнительными ресурсами считается все то, что не попадает ни под одну из предыдущих категорий файлов. 4 | Это могут быть различные favicon, шрифты, аудио, видео, документы и прочее. 5 | 6 | Подобные файлы следует хранить в папке `src/resources`. 7 | 8 | ```text 9 | ninelines-template 10 | └── src 11 |    └── resources 12 |       └── ... 13 | ``` 14 | 15 | Задача `copy` копирует содержимое папки `src/resources` в `build`: 16 | 17 | ```bash 18 | gulp copy 19 | ``` 20 | 21 | ## Работа со шрифтами 22 | 23 | Подключить шрифт можно двумя способами: 24 | 25 | * CDN ([Google Fonts](https://fonts.google.com/)) 26 | * Конвертировать и подключить с помощью [`@font-face`](https://developer.mozilla.org/ru/docs/Web/CSS/@font-face). 27 | 28 | ### Подключение шрифта с помощью Google Fonts 29 | 30 | Если шрифт и его требуемая языковая версия имеются в Google Fonts, то данный вариант подключения является приоритетным. 31 | 32 | Порядок подключения шрифта на примере Roboto: 33 | 34 | 1. Находим шрифт — [Roboto](https://fonts.google.com/specimen/Roboto) 35 | 2. Нажимаем `Select this font`. 36 | 3. Открываем появившееся снизу экрана окно. 37 | 4. Во вкладке `Customize` выбираем нужное начертание и языковую версию. 38 | 5. Во вкладке `Embed` переключаемся в `@import` и копируем содержимое тега `<style>`. 39 | 6. В файл `src/scss/_fonts.scss` вставляем скопированный `@import`. 40 | 7. В файл `src/scss/_variables.scss` добавляем переменную `$font-family-roboto`. 41 | 8. Используем переменную в стилях. 42 | 43 | ### Конвертирование шрифта и подключение с помощью `@font-face` 44 | 45 | Если шрифт отсутствует в Google Fonts, или нет подходящей языковой версии (отсутствует кириллица), то необходимо получить файл шрифта. 46 | 47 | Требуемые форматы — `.woff` (обязательно) и `.woff2` (опционально). 48 | 49 | Файлы `.ttf`, `.otf` или `.eot` можно сконвертировать в `.woff` и `.woff2` с помощью онлайн сервисов: 50 | 51 | * [onlinefontconverter.com](https://onlinefontconverter.com/) 52 | * [everythingfonts.com](https://everythingfonts.com/) 53 | 54 | Полученные файлы следует хранить в папке `src/resources/fonts`: 55 | 56 | ```text 57 | ninelines-template 58 | └── src 59 |    └── resources 60 |       └── fonts 61 |       └── ... 62 | ``` 63 | 64 | Подключение шрифтов происходит в файле `src/scss/_fonts.scss`: 65 | 66 | ```scss 67 | @font-face { 68 | src: url("../fonts/my-font-regular.woff2") format("woff2"), url("../fonts/my-font-regular.woff") format("woff"); 69 | font-family: "My Font"; 70 | font-weight: 400; 71 | font-style: normal; 72 | } 73 | ``` 74 | 75 | При наличии множества начертаний шрифты следует подключать в следующем порядке: 76 | 77 | 1. 100 normal (thin) 78 | 2. 100 italic (thin italic) 79 | 3. 200 normal (extra light) 80 | 4. 200 italic (extra light italic) 81 | 5. 300 normal (light) 82 | 6. 300 italic (light italic) 83 | 7. 400 normal (regular) 84 | 8. 400 italic (regular italic) 85 | 9. 500 normal (medium) 86 | 10. 500 italic (medium italic) 87 | 11. 600 normal (semi bold) 88 | 12. 600 italic (semi bold italic) 89 | 13. 700 normal (bold) 90 | 14. 700 italic (bold italic) 91 | 15. 800 normal (extra bold) 92 | 16. 800 italic (extra bold italic) 93 | 17. 900 normal (heavy) 94 | 18. 900 italic (heavy italic) 95 | 96 | Пример: 97 | 98 | ```scss 99 | @font-face { 100 | src: url("../fonts/my-font-light.woff") format("woff"); 101 | font-family: "My Font"; 102 | font-weight: 300; 103 | font-style: normal; 104 | } 105 | 106 | @font-face { 107 | src: url("../fonts/my-font-light-italic.woff") format("woff"); 108 | font-family: "My Font"; 109 | font-weight: 300; 110 | font-style: italic; 111 | } 112 | 113 | @font-face { 114 | src: url("../fonts/my-font-regular.woff") format("woff"); 115 | font-family: "My Font"; 116 | font-weight: 400; 117 | font-style: normal; 118 | } 119 | 120 | @font-face { 121 | src: url("../fonts/my-font-regular-italic.woff") format("woff"); 122 | font-family: "My Font"; 123 | font-weight: 400; 124 | font-style: italic; 125 | } 126 | 127 | @font-face { 128 | src: url("../fonts/my-font-bold.woff") format("woff"); 129 | font-family: "My Font"; 130 | font-weight: 700; 131 | font-style: normal; 132 | } 133 | 134 | @font-face { 135 | src: url("../fonts/my-font-bold-italic.woff") format("woff"); 136 | font-family: "My Font"; 137 | font-weight: 700; 138 | font-style: italic; 139 | } 140 | ``` 141 | 142 | После подключения шрифта в файл `src/scss/_variables.scss` следует добавить переменную в формате `$font-family-[name]`. 143 | Пример: 144 | 145 | ```scss 146 | $font-family-my-font: "My Font", sans-serif; 147 | ``` 148 | 149 | Затем переменную можно использовать в любом SCSS-файле: 150 | 151 | ```scss 152 | body { 153 | font-family: $font-family-my-font; 154 | } 155 | ``` 156 | -------------------------------------------------------------------------------- /13_pixel-perfect.md: -------------------------------------------------------------------------------- 1 | # Pixel-perfect или верстка в соответствии с макетом 2 | 3 | Pixel-perfect — техника верстки, при которой результат максимально совпадает с исходным макетом. 4 | 5 | Под этим понимается соответствие: 6 | 7 | * положения элементов; 8 | * размеров; 9 | * отступов; 10 | * цветов; 11 | * шрифтов; 12 | * размеров текста; 13 | * межстрочного и межбуквенного интервалов. 14 | 15 | Что не входит в pixel-perfect: 16 | 17 | * Аболютное соответствие макету. 18 | 19 | Несмотря на свое название, техника pixel-perfect не подразумевает стопроцентного совпадения с макетом. 20 | Различия могут быть и будут. 21 | 22 | Дизайнер при создании макета может сделать что-то «на глаз» или просто ошибиться: сбиться с сетки, выровнять слой немного не по центру, использовать не тот шрифт или размер текста, подобрать цвет не по палитре и так далее. 23 | 24 | Не нужно повторять того же в верстке: 25 | 26 | * Если положение элемента отклоняется от сетки или других подобных элементов, то его следует выровнять. 27 | * Если элемент почти по центру, то вероятно он должен быть строго по центру. 28 | * Если есть подозрение, что в каком-то месте макета сбился шрифт, то скорее всего он действительно сбился, и вместо него должен использоваться другой. 29 | * Если несколько цветов, используемых в макете, незначительно отличаются HEX-кодом и неотличимы на глаз, то вероятно это должен быть один цвет. 30 | 31 | Также стоит учесть различный рендеринг страницы в различных средах. 32 | 33 | Операционные системы по-разному отображают текст. 34 | Графические редакторы и браузеры по-разному отображают текст. 35 | Браузеры по-разному интерпретируют дробные значения. 36 | 37 | Все эти факторы в сумме не позволяют достичь абсолютного соответствия макету. 38 | 39 | Погрешности от нескольких пикселей до нескольких десятков пикселей вполне допустимы. 40 | 41 | * Соответствие макету на всех возможных разрешениях. 42 | 43 | Нарисовать макет для всех возможных разрешений крайне затруднительно. 44 | И уж тем более затруднительно сверстать все это многообразие. 45 | 46 | На деле верстальщик получает лишь несколько основных макетов: 47 | 48 | * десктопная версия; 49 | * планшетная версия (очень редко); 50 | * мобильная версия. 51 | 52 | На эти имеющиеся макеты и следует ориентироваться. 53 | Применение pixel-perfect к промежуточным размерам сайта недопустимо. 54 | 55 | ## Инструменты для работы с pixel-perfect 56 | 57 | Одним из лучших инструментов для pixel-perfect верстки является плагин [PerfectPixel](http://www.welldonecode.com/perfectpixel/). 58 | 59 | Преимущества плагина: 60 | 61 | * Поддержка основными браузерами: Chrome, Firefox, Opera (в планах поддержка Safari, IE и Edge). 62 | * Нет необходимости ставить дополнительный код на сайт. 63 | * Удобный интерфейс. 64 | * Возможность позиционировать и масштабировать слой, изменять прозрачность. 65 | * Функция блокировки слоя. 66 | * Режим инверсии (позволяет проще всего увидеть отличия от макета). 67 | 68 | Нет необходимости устанавливать данный плагин во все браузеры одновременно. В основном при разработке используется Chrome, поэтому достаточно установить плагин только в него, а остальные браузеры проверить визуально на глаз. 69 | 70 | Также необходимо учесть, что на разных операционных системах сайт может отображаться с небольшыми различиями. Например проверив верстку на соответствие макету в ОС Windows, используя Chrome вы получили идеальное соответствие с макетом. Далее, вы открываете сайт в Mac OS, используя Chrome и видите, что есть небольшие расхождения. Если данные рахождения как-то влияют на визуальное восприятие и изменяют структуру сайта, то их необходимо поправить, в других случаях - это считается нормой. 71 | -------------------------------------------------------------------------------- /15_metatags.md: -------------------------------------------------------------------------------- 1 | # Работа с метатегами 2 | 3 | В данном разделе приведен список основных метатегов `<meta>` и `<link>` с примерами. 4 | Менее значащие или устаревшие метатеги намеренно не указаны. 5 | 6 | ## Базовые метатеги 7 | 8 | Базовые метатеги задают основную информацию о сайте. 9 | 10 | ### `charset` 11 | 12 | Задает кодировку страницы. 13 | 14 | > Значение по умолчанию в сборке — `utf-8`. 15 | 16 | Пример: 17 | 18 | ```jade 19 | append vars 20 | - meta.charset = 'utf-8' 21 | ``` 22 | 23 | ### `description` 24 | 25 | Задает описание страницы. 26 | 27 | > Оптимальная длинна описания — не больше 160 символов. 28 | 29 | Пример: 30 | 31 | ```jade 32 | append vars 33 | - meta.description = 'Описание страницы' 34 | ``` 35 | 36 | Или так: 37 | 38 | ```jade 39 | prepend vars 40 | - description = 'Описание страницы' 41 | ``` 42 | 43 | При этом также будут определены значения `og:description` и `twitter:description`. 44 | 45 | ### `keywords` 46 | 47 | Задает список ключевых слов. 48 | 49 | Пример: 50 | 51 | ```jade 52 | append vars 53 | - meta.keywords = ['список', 'ключевых', 'слов'] 54 | ``` 55 | 56 | Или так: 57 | 58 | ```jade 59 | append vars 60 | - meta.keywords = 'список, ключевых, слов' 61 | ``` 62 | 63 | ### `title` 64 | 65 | Задает заголовок страницы (`<title>`). 66 | 67 | Пример: 68 | 69 | ```jade 70 | prepend vars 71 | - title = 'Заголовок страницы' 72 | ``` 73 | 74 | При этом также будут определены значения `og:title` и `twitter:title`. 75 | 76 | ### `viewport` 77 | 78 | Позволяет управлять отображением страницы на мобильных устройствах. 79 | 80 | > Значение по умолчанию в сборке — `width=device-width`. 81 | 82 | Пример: 83 | 84 | ```jade 85 | append vars 86 | - meta.viewport = 'width=device-width, initial-scale=1' 87 | ``` 88 | 89 | ### `icon` 90 | 91 | Задает favicon сайта. 92 | 93 | Пример: 94 | 95 | ```jade 96 | append vars 97 | - link.icon = 'favicon.ico' 98 | ``` 99 | 100 | Также можно задать иконки разных размеров: 101 | 102 | ```jade 103 | append vars 104 | - link.icon16x16 = 'favicon-16x16.png' 105 | - link.icon32x32 = 'favicon-32x32.png' 106 | - link.icon96x96 = 'favicon-96x96.png' 107 | - link.icon128x128 = 'favicon-128x128.png' 108 | - link.icon196x196 = 'favicon-196x196.png' 109 | ``` 110 | 111 | ### `manifest` 112 | 113 | Задает ссылку на `manifest.json`. 114 | 115 | Пример: 116 | 117 | ```jade 118 | append vars 119 | - link.manifest = '/manifest.json' 120 | ``` 121 | 122 | Манифест веб-приложения предоставляет информацию о приложении (такую как имя, авторство, иконку и описание) в формате JSON-файла. 123 | Цель манифеста — установить веб-приложение на рабочий стол устройства, предоставляя более быстрый доступ. 124 | 125 | ### `format-detection` 126 | 127 | Позволяет отключить определение номера телефона, адреса, даты или почты. 128 | 129 | ```jade 130 | append vars 131 | - meta.formatDetection.telephone = false 132 | ``` 133 | 134 | Или так: 135 | 136 | ```jade 137 | append vars 138 | - meta.formatDetection = 'telephone=no' 139 | ``` 140 | 141 | ### `theme-color` 142 | 143 | Задает цвет вкладки мобильного браузера. 144 | 145 | Пример: 146 | 147 | ```jade 148 | append vars 149 | - meta.themeColor = '#4285f4' 150 | ``` 151 | 152 | ## Метатеги Apple 153 | 154 | Метатеги Apple задают дополнительную информацию для устройств на iOS (iPhone, iPad). 155 | 156 | ### `apple-mobile-web-app-capable` 157 | 158 | Включает режим полноэкранного приложения iOS, в котором скрывается адресная строка и панель навигации. 159 | 160 | Пример: 161 | 162 | ```jade 163 | append vars 164 | - meta.appleMobileWebAppCapable = 'on' 165 | ``` 166 | 167 | ### `apple-mobile-web-app-status-bar-style` 168 | 169 | Задает стиль строки состояния в полноэкранном режиме iOS. 170 | 171 | Пример: 172 | 173 | ```jade 174 | append vars 175 | - meta.appleMobileWebAppCapable = 'on' 176 | - meta.appleMobileWebAppStatusBarStyle = 'black' 177 | ``` 178 | 179 | ### `apple-mobile-web-app-title` 180 | 181 | Задает заголовок для иконки запуска в iOS. 182 | 183 | > Если не указано, то будет использоваться значение тега `<title>`. 184 | 185 | Пример: 186 | 187 | ```jade 188 | append vars 189 | - meta.appleMobileWebAppTitle = 'Название сайта' 190 | ``` 191 | 192 | ### `apple-touch-icon` 193 | 194 | Задает иконку сайта в iOS. 195 | 196 | Пример: 197 | 198 | ```jade 199 | append vars 200 | - link.appleTouchIcon = 'images/touch-icon.png' 201 | ``` 202 | 203 | Также можно задать иконки разных размеров: 204 | 205 | ```jade 206 | append vars 207 | - link.appleTouchIcon40x40 = 'images/touch-icon-40x40.png' 208 | - link.appleTouchIcon57x57 = 'images/touch-icon-57x57.png' 209 | - link.appleTouchIcon58x58 = 'images/touch-icon-58x58.png' 210 | - link.appleTouchIcon60x60 = 'images/touch-icon-60x60.png' 211 | - link.appleTouchIcon72x72 = 'images/touch-icon-72x72.png' 212 | - link.appleTouchIcon76x76 = 'images/touch-icon-76x76.png' 213 | - link.appleTouchIcon80x80 = 'images/touch-icon-80x80.png' 214 | - link.appleTouchIcon87x87 = 'images/touch-icon-87x87.png' 215 | - link.appleTouchIcon114x114 = 'images/touch-icon-114x114.png' 216 | - link.appleTouchIcon120x120 = 'images/touch-icon-120x120.png' 217 | - link.appleTouchIcon144x144 = 'images/touch-icon-144x144.png' 218 | - link.appleTouchIcon152x152 = 'images/touch-icon-152x152.png' 219 | - link.appleTouchIcon167x167 = 'images/touch-icon-167x167.png' 220 | - link.appleTouchIcon180x180 = 'images/touch-icon-180x180.png' 221 | - link.appleTouchIcon1024x1024 = 'images/touch-icon-1024x1024.png' 222 | ``` 223 | 224 | ### `apple-touch-icon-precomposed` 225 | 226 | Задает precomposed-иконку в iOS. 227 | Используется в том случае, если к иконке не нужно применять системные стили. 228 | 229 | Пример: 230 | 231 | ```jade 232 | append vars 233 | - link.appleTouchIconPrecomposed = 'images/touch-icon-precomposed.png' 234 | ``` 235 | 236 | Также можно задать иконки разных размеров: 237 | 238 | ```jade 239 | append vars 240 | - link.appleTouchIconPrecomposed40x40 = 'images/touch-icon-precomposed-40x40.png' 241 | - link.appleTouchIconPrecomposed57x57 = 'images/touch-icon-precomposed-57x57.png' 242 | - link.appleTouchIconPrecomposed58x58 = 'images/touch-icon-precomposed-58x58.png' 243 | - link.appleTouchIconPrecomposed60x60 = 'images/touch-icon-precomposed-60x60.png' 244 | - link.appleTouchIconPrecomposed72x72 = 'images/touch-icon-precomposed-72x72.png' 245 | - link.appleTouchIconPrecomposed76x76 = 'images/touch-icon-precomposed-76x76.png' 246 | - link.appleTouchIconPrecomposed80x80 = 'images/touch-icon-precomposed-80x80.png' 247 | - link.appleTouchIconPrecomposed87x87 = 'images/touch-icon-precomposed-87x87.png' 248 | - link.appleTouchIconPrecomposed114x114 = 'images/touch-icon-precomposed-114x114.png' 249 | - link.appleTouchIconPrecomposed120x120 = 'images/touch-icon-precomposed-120x120.png' 250 | - link.appleTouchIconPrecomposed144x144 = 'images/touch-icon-precomposed-144x144.png' 251 | - link.appleTouchIconPrecomposed152x152 = 'images/touch-icon-precomposed-152x152.png' 252 | - link.appleTouchIconPrecomposed167x167 = 'images/touch-icon-precomposed-167x167.png' 253 | - link.appleTouchIconPrecomposed180x180 = 'images/touch-icon-precomposed-180x180.png' 254 | - link.appleTouchIconPrecomposed1024x1024 = 'images/touch-icon-precomposed-1024x1024.png' 255 | ``` 256 | 257 | ### `mask-icon` 258 | 259 | Задает маску для иконки закрепленного сайта в iOS. 260 | 261 | Пример: 262 | 263 | ```jade 264 | append vars 265 | - link.maskIcon.href = 'mask-icon.svg' 266 | - link.maskIcon.color = 'red' 267 | ``` 268 | 269 | ## Метатеги Microsoft 270 | 271 | Метатеги Microsoft задают дополнительную информацию для IE и Edge на Windows. 272 | 273 | ### `application-name` 274 | 275 | Задает заголовок закрепленного сайта в IE11 и Edge. 276 | 277 | > Если не указано, то будет использоваться значение тега `<title>`. 278 | 279 | Пример: 280 | 281 | ```jade 282 | append vars 283 | - meta.applicationName = 'Название сайта' 284 | ``` 285 | 286 | ### `msapplication-TileColor` 287 | 288 | Задает цвет плитки в Windows 10. 289 | 290 | Пример: 291 | 292 | ```jade 293 | append vars 294 | - meta.msapplicationTileColor = '#FF3300' 295 | ``` 296 | 297 | ### `msapplication-TileImage` 298 | 299 | Задает фоновое изображение плитки в Windows 10. 300 | 301 | Пример: 302 | 303 | ```jade 304 | append vars 305 | - meta.msapplicationTileImage = 'images/tile-image.png' 306 | ``` 307 | 308 | ### `msapplication-square70x70logo` 309 | 310 | Задает иконку малой плитки в Windows 10. 311 | 312 | Пример: 313 | 314 | ```jade 315 | append vars 316 | - meta.msapplicationSquare70x70logo = 'images/ms-logo-70x70.png' 317 | ``` 318 | 319 | ### `msapplication-square150x150logo` 320 | 321 | Задает иконку средней плитки в Windows 10. 322 | 323 | Пример: 324 | 325 | ```jade 326 | append vars 327 | - meta.msapplicationSquare150x150logo = 'images/ms-logo-150x150.png' 328 | ``` 329 | 330 | ### `msapplication-wide310x150logo` 331 | 332 | Задает иконку широкой плитки в Windows 10. 333 | 334 | Пример: 335 | 336 | ```jade 337 | append vars 338 | - meta.msapplicationSquare310x150logo = 'images/ms-logo-310x150.png' 339 | ``` 340 | 341 | ### `msapplication-square310x310logo` 342 | 343 | Задает иконку большой плитки в Windows 10. 344 | 345 | Пример: 346 | 347 | ```jade 348 | append vars 349 | - meta.msapplicationSquare310x310logo = 'images/ms-logo-310x310.png' 350 | ``` 351 | 352 | ### `msapplication-notification` 353 | 354 | Позволяет определить до пяти XML-файлов, которые будут опрашиваться с определенным интервалом. 355 | Эти файлы могут содержать уведомления, отображаемые на плитке. 356 | 357 | Пример: 358 | 359 | ```jade 360 | append vars 361 | - meta.msapplicationNotification = 'frequency=30; polling-uri=notifications/feed-1.xml; polling-uri2=notifications/feed-2.xml' 362 | ``` 363 | 364 | ### `X-UA-Compatible` 365 | 366 | Позволяет управлять режимом совместимости браузеров IE8+. 367 | 368 | > Значение по умолчанию в сборке — `IE=edge`. 369 | 370 | Пример: 371 | 372 | ```jade 373 | append vars 374 | - meta.XUACompatible = 'IE=edge' 375 | ``` 376 | 377 | ## Метатеги [Open Graph](http://ogp.me/) 378 | 379 | Метатеги Open Graph задают дополнительную информацию, используемую социальными сетями (VK, Twitter, Google Plus, Одноклассники и так далее) для оформления публикаций. 380 | 381 | ### `og:url` 382 | 383 | Задает адрес страницы в Open Graph. 384 | 385 | > Требуется указывать полный URL. 386 | 387 | Пример: 388 | 389 | ```jade 390 | append vars 391 | - meta.ogUrl = 'http://example.com/' 392 | ``` 393 | 394 | ### `og:locale` 395 | 396 | Задает локаль страницы в Open Graph. 397 | 398 | > Значение по умолчанию в сборке — `ru_RU`. 399 | 400 | Пример: 401 | 402 | ```jade 403 | append vars 404 | - meta.ogLocale = 'ru_RU' 405 | ``` 406 | 407 | ### `og:title` 408 | 409 | Задает заголовок страницы в Open Graph. 410 | 411 | Пример: 412 | 413 | ```jade 414 | append vars 415 | - meta.ogTitle = 'Заголовок страницы' 416 | ``` 417 | 418 | Или так: 419 | 420 | ```jade 421 | append vars 422 | - title = 'Заголовок страницы' 423 | ``` 424 | 425 | ### `og:description` 426 | 427 | Задает описание страницы в Open Graph. 428 | 429 | Пример: 430 | 431 | ```jade 432 | prepend vars 433 | - meta.ogDescription = 'Описание страницы' 434 | ``` 435 | 436 | Или так: 437 | 438 | ```jade 439 | prepend vars 440 | - description = 'Описание страницы' 441 | ``` 442 | 443 | ### `og:image` 444 | 445 | Задает изображение страницы в Open Graph. 446 | 447 | > Требуется указывать полный URL. 448 | 449 | Пример: 450 | 451 | ```jade 452 | append vars 453 | - meta.ogImage = 'http://example.com/images/og-image.png' 454 | ``` 455 | 456 | Или так: 457 | 458 | ```jade 459 | prepend vars 460 | - image = 'http://example.com/images/og-image.png' 461 | ``` 462 | 463 | ### `og:image:type` 464 | 465 | Задает MIME-тип изображения в Open Graph. 466 | 467 | Пример: 468 | 469 | ```jade 470 | append vars 471 | - meta.ogImageType = 'image/jpeg' 472 | ``` 473 | 474 | ### `og:image:width` 475 | 476 | Задает ширину изображения в Open Graph. 477 | 478 | Пример: 479 | 480 | ```jade 481 | append vars 482 | - meta.ogImageWidth = 1200 483 | ``` 484 | 485 | ### `og:image:height` 486 | 487 | Задает высоту изображения в Open Graph. 488 | 489 | Пример: 490 | 491 | ```jade 492 | append vars 493 | - meta.ogImageHeight = 600 494 | ``` 495 | 496 | ### `og:image:alt` 497 | 498 | Задает описание изображения в Open Graph. 499 | 500 | Пример: 501 | 502 | ```jade 503 | append vars 504 | - meta.ogImageAlt = 'Описание изображения' 505 | ``` 506 | 507 | ## Метатеги Twitter 508 | 509 | Метатеги Twitter задают дополнительную информацию, используемую для оформления Twitter-публикаций. 510 | 511 | ### `twitter:card` 512 | 513 | Задает тип карточки Twitter. 514 | 515 | > Значение по умолчанию в сборке — `summary_large_image`. 516 | 517 | Пример: 518 | 519 | ```jade 520 | append vars 521 | - meta.twitterCard = 'summary_large_image' 522 | ``` 523 | 524 | ### `twitter:site` 525 | 526 | Задает `@username` сайта в Twitter. 527 | 528 | Пример: 529 | 530 | ```jade 531 | append vars 532 | - meta.twitterSite = '@username' 533 | ``` 534 | 535 | ### `twitter:creator` 536 | 537 | Задает `@username` автора контента в Twitter. 538 | 539 | Пример: 540 | 541 | ```jade 542 | append vars 543 | - meta.twitterCreator = '@username' 544 | ``` 545 | 546 | ### `twitter:title` 547 | 548 | Задает заголовок страницы в Twitter. 549 | 550 | Пример: 551 | 552 | ```jade 553 | append vars 554 | - meta.twitterSite = 'Заголовок страницы' 555 | ``` 556 | 557 | Или так: 558 | 559 | ```jade 560 | prepend vars 561 | - title = 'Заголовок страницы' 562 | ``` 563 | 564 | ### `twitter:description` 565 | 566 | Задает описание страницы в Twitter. 567 | 568 | Пример: 569 | 570 | ```jade 571 | append vars 572 | - meta.twitterDescription = 'Описание страницы' 573 | ``` 574 | 575 | Или так: 576 | 577 | ```jade 578 | prepend vars 579 | - description = 'Описание страницы' 580 | ``` 581 | 582 | ### `twitter:image` 583 | 584 | Задает изображение страницы в Twitter. 585 | 586 | > Требуется указывать полный URL. 587 | 588 | Пример: 589 | 590 | ```jade 591 | append vars 592 | - meta.twitterImage = 'http://example.com/images/twitter-image.png' 593 | ``` 594 | 595 | Или так: 596 | 597 | ```jade 598 | prepend vars 599 | - image = 'http://example.com/images/twitter-image.png' 600 | ``` 601 | -------------------------------------------------------------------------------- /16_codestyle-pug.md: -------------------------------------------------------------------------------- 1 | # Синтаксис и форматирование 2 | 3 | * Для отступов используется символ табуляции `\t`. 4 | 5 | * Для переноса строк используется символ `\n` (LF). 6 | 7 | * Не должно быть пробелов в конце строк. 8 | 9 | * Максимальная длина строки — 120 символов. 10 | 11 | * Между большими блоками кода следует оставлять одну пустую строку. 12 | 13 | **Неправильно** (нет пустых строк): 14 | 15 | ```jade 16 | .products 17 | .product 18 | img.product__image(src="images/products/1.png" alt="") 19 | .product__title 20 | | Название товара 21 | .product__description 22 | | Описание товара 23 | .product__price 24 | | 12345 25 | .product 26 | img.product__image(src="images/products/2.png" alt="") 27 | .product__title 28 | | Название товара 29 | .product__description 30 | | Описание товара 31 | .product__price 32 | | 45678 33 | .product 34 | img.product__image(src="images/products/3.png" alt="") 35 | .product__title 36 | | Название товара 37 | .product__description 38 | | Описание товара 39 | .product__price 40 | | 90123 41 | .navigation 42 | a.navigation__link(href="/catalog?page=2") 43 | | Следующая страница 44 | ``` 45 | 46 | **Неправильно** (слишком много пустых строк): 47 | 48 | ```jade 49 | .products 50 | 51 | .product 52 | img.product__image(src="images/products/1.png" alt="") 53 | 54 | .product__title 55 | | Название товара 56 | 57 | .product__description 58 | | Описание товара 59 | 60 | .product__price 61 | | 12345 62 | 63 | 64 | .product 65 | img.product__image(src="images/products/2.png" alt="") 66 | 67 | .product__title 68 | | Название товара 69 | 70 | .product__description 71 | | Описание товара 72 | 73 | .product__price 74 | | 45678 75 | 76 | 77 | .product 78 | img.product__image(src="images/products/3.png" alt="") 79 | 80 | .product__title 81 | | Название товара 82 | 83 | .product__description 84 | | Описание товара 85 | 86 | .product__price 87 | | 90123 88 | 89 | 90 | .navigation 91 | a.navigation__link(href="/catalog?page=2") 92 | | Следующая страница 93 | ``` 94 | 95 | **Правильно:** 96 | 97 | ```jade 98 | .products 99 | .product 100 | img.product__image(src="images/products/1.png" alt="") 101 | .product__title 102 | | Название товара 103 | .product__description 104 | | Описание товара 105 | .product__price 106 | | 12345 107 | 108 | .product 109 | img.product__image(src="images/products/2.png" alt="") 110 | .product__title 111 | | Название товара 112 | .product__description 113 | | Описание товара 114 | .product__price 115 | | 45678 116 | 117 | .product 118 | img.product__image(src="images/products/3.png" alt="") 119 | .product__title 120 | | Название товара 121 | .product__description 122 | | Описание товара 123 | .product__price 124 | | 90123 125 | 126 | .navigation 127 | a.navigation__link(href="/catalog?page=2") 128 | | Следующая страница 129 | ``` 130 | 131 | # Однострочная вложенность 132 | 133 | Как правило теги вкладываются друг на разных строках: 134 | 135 | ```jade 136 | ul 137 | li 138 | a(href="/page/1") 139 | | 1 140 | li 141 | a(href="/page/2") 142 | | 2 143 | li 144 | a(href="/page/3") 145 | | 3 146 | ``` 147 | 148 | Но в случае, **если** строк в файле довольно много и их сокращение **увеличивает читаемость**, то допустимо 149 | использовать однострочную запись: 150 | 151 | ```jade 152 | ul 153 | li: a(href="/page/1") 1 154 | li: a(href="/page/2") 2 155 | li: a(href="/page/3") 3 156 | ``` 157 | 158 | В иных случаях не следует использовать такой способ записи. 159 | 160 | # Самозакрывающиеся теги 161 | 162 | Самозакрывающиеся теги (`img`, `meta`, `link`) определяются автоматически, поэтому не следует указывать это явно. 163 | 164 | # Классы 165 | 166 | Классы записываются сразу после тега: 167 | 168 | ```jade 169 | ul.navigation 170 | li.navigation__item 171 | ``` 172 | 173 | Если у элемента не указан тег, то он принимается за `div` (по этой причине не следует явно указывать тег `div`): 174 | 175 | ```jade 176 | .container 177 | .block 178 | ``` 179 | 180 | Будет преобразовано в: 181 | 182 | ```html 183 | <div class="container"> 184 | <div class="block"></div> 185 | </div> 186 | ``` 187 | 188 | Атрибут `class` используется только в том случае, если он задается с помощью переменной или через условие: 189 | 190 | ```jade 191 | - page = 'index' 192 | 193 | .menu 194 | a.menu__item(class={'menu__item--active': page === 'index'} href='/') 195 | | Главная страница 196 | a.menu__item(class={'menu__item--active': page === 'catalog'} href='/catalog') 197 | | Каталог 198 | a.menu__item(class={'menu__item--active': page === 'contacts'} href='/contacts') 199 | | Контакты 200 | ``` 201 | 202 | # Идентификаторы 203 | 204 | Идентификатор записывается после класса или тега. 205 | 206 | **Неправильно:** 207 | 208 | ```jade 209 | ul#top-menu.menu 210 | ``` 211 | 212 | **Правильно:** 213 | 214 | ```jade 215 | ul.menu#top-menu 216 | 217 | // или так 218 | .menu#top-menu 219 | 220 | // или так 221 | #top-menu 222 | ``` 223 | 224 | Атрибут `id` используется только в случае, если он задается с помощью переменной. 225 | 226 | # Атрибуты 227 | 228 | Атрибуты записываются в круглых скобках после указания тега, класса и/или идентификатора. 229 | 230 | ```jade 231 | form(action="/login.php" method="post") 232 | input(type="email" name="email" placeholder="E-mail") 233 | input(type="password" name="password" placeholder="Пароль") 234 | button(type="submit") 235 | | Войти 236 | ``` 237 | 238 | Не следует дублировать запись атрибутов. 239 | 240 | **Неправильно:** 241 | 242 | ```jade 243 | img(src="images/photo.jpg")(alt="") 244 | ``` 245 | 246 | **Правильно:** 247 | 248 | ```jade 249 | img(src="images/photo.jpg" alt="") 250 | ``` 251 | 252 | # Кавычки 253 | 254 | Для указания значения атрибутов используются двойные кавычки. 255 | 256 | **Неправильно:** 257 | 258 | ```jade 259 | input(type='text' name='name') 260 | ``` 261 | 262 | **Правильно:** 263 | 264 | ```jade 265 | input(type="text" name="name") 266 | ``` 267 | 268 | # Разделение атрибутов 269 | 270 | Атрибуты разделяются одним пробелом. 271 | 272 | **Неправильно:** 273 | 274 | ```jade 275 | img(src="images/logo.png", srcset="images/logo@2x.png 2x", alt="") 276 | ``` 277 | 278 | **Правильно:** 279 | 280 | ```jade 281 | img(src="images/logo.png" srcset="images/logo@2x.png 2x" alt="") 282 | ``` 283 | 284 | # Нестандартные атрибуты 285 | 286 | Атрибуты, в названии которых используются нестандартные символы (`(`, `)`, `[`, `]`, `:`, `@`) записываются в одинарных 287 | кавычках. 288 | 289 | **Неправильно:** 290 | 291 | ```jade 292 | // Не скомпилируется 293 | button(type="button" (click)="send()") 294 | | Отправить 295 | ``` 296 | 297 | ```jade 298 | button(type="button" v-on:click="send()") 299 | | Отправить 300 | ``` 301 | 302 | ```jade 303 | button(type="button" @click="send()") 304 | | Отправить 305 | ``` 306 | 307 | **Правильно:** 308 | 309 | ```jade 310 | // Не скомпилируется 311 | button(type="button" '(click)'="send()") 312 | | Отправить 313 | ``` 314 | 315 | ```jade 316 | button(type="button" 'v-on:click'="send()") 317 | | Отправить 318 | ``` 319 | 320 | ```jade 321 | button(type="button" '@click'="send()") 322 | | Отправить 323 | ``` 324 | 325 | # Многострочная запись атрибутов 326 | 327 | Если атрибутов слишком много и/или длина строки превышает установленный предел, то следует записать каждый атрибут 328 | в отдельной строке: 329 | 330 | ```jade 331 | input( 332 | type="text" 333 | name="name" 334 | placeholder="Имя" 335 | maxlength="20" 336 | required 337 | autocomplete="on" 338 | ) 339 | ``` 340 | 341 | # Использование переменных в атбирутах 342 | 343 | В значение атрибута можно передать переменную или любое JS-выражение: 344 | 345 | ```jade 346 | img(src=`images/image-${image.number}.jpg` alt=image.description) 347 | ``` 348 | 349 | По умолчанию значения атрибутов экранируются. 350 | 351 | ```jade 352 | .pagination(v-if="items.length > 5") 353 | ``` 354 | 355 | Будет преобразовано в: 356 | 357 | ```html 358 | <div class="pagination" v-if="items.length > 5"></div> 359 | ``` 360 | 361 | Отключить экранирование значения атрибута можно заменив `=` на `!=`. 362 | 363 | ```jade 364 | .pagination(v-if!="items.length > 5") 365 | ``` 366 | 367 | Будет преобразовано в: 368 | 369 | ```html 370 | <div class="pagination" v-if="items.length > 5"></div> 371 | ``` 372 | 373 | # JSON 374 | 375 | JSON в атрибуте записывается в виде JS-объекта, а не строки. 376 | 377 | **Неправильно:** 378 | 379 | ```jade 380 | .slider(data-options="{autoplay: true, arrows: false, fade: true}") 381 | ``` 382 | 383 | **Правильно:** 384 | 385 | ```jade 386 | .slider(data-options={ 387 | autoplay: true, 388 | arrows: false, 389 | fade: true 390 | }) 391 | 392 | // или так 393 | .slider( 394 | data-options={ 395 | autoplay: true, 396 | arrows: false, 397 | fade: true 398 | } 399 | ) 400 | ``` 401 | 402 | # Атрибут style 403 | 404 | Атрибут style также при необходимости можно записать в виде JS-объекта. 405 | 406 | ```jade 407 | .element( 408 | style={ 409 | '-webkit-transform': 'rotate(45deg)', 410 | 'transform': 'rotate(45deg)' 411 | } 412 | ) 413 | ``` 414 | 415 | # Использование `&attributes` 416 | 417 | `&attributes` либо в миксинах, либо в том случае, если необходимо задать множество атрибутов элемента с помощью 418 | переменной. 419 | 420 | **Неправильно:** 421 | 422 | ```jade 423 | a(href="/")&attributes({target: '_blank'}) 424 | | На главную 425 | ``` 426 | 427 | **Правильно:** 428 | 429 | ```jade 430 | a(href="/" target="_blank") 431 | | На главную 432 | ``` 433 | 434 | ```jade 435 | - attrs = {class: 'no-js', lang: 'ru'} 436 | 437 | doctype html 438 | html&attributes(attrs) 439 | head 440 | body 441 | ``` 442 | 443 | ```jade 444 | mixin button() 445 | button.button&attributes(attributes) 446 | block 447 | 448 | +button().button--large(type="submit") 449 | | Войти 450 | ``` 451 | 452 | # Вывод текста 453 | 454 | Текст следует выводить на следующей строке с помощью символа `|`. 455 | 456 | **Неправильно:** 457 | 458 | ```jade 459 | .product 460 | img.product__image(src="images/products/1.png" alt="") 461 | .product__title Название товара 462 | .product__description Описание товара 463 | .product__price 12345 464 | ``` 465 | 466 | **Правильно:** 467 | 468 | ```jade 469 | .product 470 | img.product__image(src="images/products/1.png" alt="") 471 | .product__title 472 | | Название товара 473 | .product__description 474 | | Описание товара 475 | .product__price 476 | | 12345 477 | ``` 478 | 479 | В случае, **если** строк в файле довольно много и их сокращение **увеличивает читаемость**, то допустимо использовать 480 | однострочную запись. 481 | 482 | # Использование JS в Pug 483 | 484 | Pug позволяет использовать JS в шаблонах: 485 | 486 | ```jade 487 | // Однострочная запись 488 | - title = 'Title' 489 | - description = 'Description' 490 | 491 | // Многострочная запись 492 | - 493 | attrs = { 494 | class: 'no-js', 495 | lang: 'ru' 496 | } 497 | ``` 498 | 499 | Не следует использовать эту возможность для создания условий и циклов, так как в Pug уже имеются специальные 500 | конструкции. 501 | 502 | # Вывод переменных 503 | 504 | Существует два варианта вывода переменных — экранированный и неэкранированный: 505 | 506 | ```jade 507 | // Экранированный 508 | .product 509 | .product__title= title 510 | .product__description= description 511 | .product__price= price 512 | 513 | // Неэкранированный 514 | .product 515 | .product__title!= title 516 | .product__description!= description 517 | .product__price!= price 518 | ``` 519 | 520 | # Вывод переменных в тексте 521 | 522 | При необходимости вывести переменную в тексте следует воспользоваться конструкцией `#{}` (для экранированного вывода) 523 | или `!{}` (для неэкранированного вывода): 524 | 525 | ```jade 526 | .product 527 | .product__title 528 | | Название товара: #{title} 529 | .product__description 530 | | Описание: #{description} 531 | .product__price 532 | | Цена: !{price}р. 533 | ``` 534 | 535 | # Вывод элементов в тексте 536 | 537 | Элементы в тексте записываются с помощью конструкции `#[]`. 538 | 539 | **Неправильно:** 540 | 541 | ```jade 542 | .product 543 | .product__title 544 | | <b>Название товара:</b><br> 545 | | <a href="/product/1">#{title}</a> 546 | .product__description 547 | | <b>Описание:</b><br> 548 | | #{description} 549 | .product__price 550 | | <b>Цена:</b><br> 551 | | !{price}р. 552 | ``` 553 | 554 | ```jade 555 | .product 556 | .product__title 557 | b Название товара: 558 | br 559 | a(href="/product/1") #{title} 560 | .product__description 561 | b Описание: 562 | br 563 | | #{description} 564 | .product__price 565 | b Цена: 566 | br 567 | | !{price}р. 568 | ``` 569 | 570 | **Правильно:** 571 | 572 | ```jade 573 | .product 574 | .product__title 575 | | #[b Название товара:]#[br] 576 | | #[a(href="/product/1") #{title}] 577 | .product__description 578 | | #[b Описание:]#[br] 579 | | #{description} 580 | .product__price 581 | | #[b Цена:]#[br] 582 | | !{price}р. 583 | ``` 584 | 585 | После `#[br]` следует ставить перенос строки. 586 | 587 | # Вывод кода 588 | 589 | При необходимости записать `<script>` или `<style>` непосредственно в Pug, то можно воспользоваться следующей 590 | конструкцией: 591 | 592 | ```jade 593 | script. 594 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 595 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 596 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 597 | })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); 598 | 599 | ga('create', 'UA-123456789-00', 'auto'); 600 | ga('send', 'pageview'); 601 | 602 | style. 603 | img[href*="tns-counter"] { 604 | position: absolute; 605 | left: -9999px; 606 | } 607 | ``` 608 | 609 | # Управляющие конструкции 610 | 611 | * `if` — используется для условий. 612 | 613 | **Неправильно:** 614 | 615 | ```jade 616 | - if (products.length) { 617 | .products 618 | // ... 619 | - } 620 | ``` 621 | 622 | **Правильно:** 623 | 624 | ```jade 625 | if products.length 626 | .products 627 | // ... 628 | ``` 629 | 630 | * `unless` — предназначена для условий, но не используется. Вместо нее следует использовать `if`. 631 | 632 | * `case` — предназначена для условий. Практически не используется. 633 | 634 | * `each` — предназначена для создания циклов. 635 | 636 | **Неправильно:** 637 | 638 | ```jade 639 | - for (var i = 0; i < products.length; i++) { 640 | - var product = products[i]; 641 | .product 642 | .product__title= product.title 643 | .product__description= product.description 644 | .product__price= product.price 645 | - } 646 | ``` 647 | 648 | **Правильно:** 649 | 650 | ```jade 651 | each product in products 652 | .product 653 | .product__title= product.title 654 | .product__description= product.description 655 | .product__price= product.price 656 | ``` 657 | 658 | ```jade 659 | each i in Array.from(Array(10).keys()) 660 | img(src=`images/frame-${i}.jpg` alt="") 661 | ``` 662 | 663 | * `while` — не используется. 664 | 665 | # Наследование 666 | 667 | Базовый шаблон (`base.pug`) не предназначен для редактирования. Не стоит в нем задавать значения переменных, подключать 668 | какие-либо скрипты или стили, и уж тем более писать код страницы. На то он и базовый. В нем описывается самый основной 669 | код страницы. Допустимо лишь подключать миксины в этом файле. 670 | 671 | Если возникает необходимость изменить этот файл (подключить какие-то стили или скрипты, добавить шапку и подвал сайта), 672 | то лучше создать другой базовый шаблон (например `custom-base.pug`) унаследованный от `base.pug` (а то и вовсе 673 | написанный с нуля), и уже работать с этим файлом и наследовать страницы от него. 674 | 675 | При переопределении блоков можно использовать сокращенную запись. 676 | 677 | **Неправильно:** 678 | 679 | ```jade 680 | block append content 681 | // ... 682 | ``` 683 | 684 | **Правильно:** 685 | 686 | ```jade 687 | append content 688 | // ... 689 | ``` 690 | 691 | # Подключение файлов 692 | 693 | Если страница состоит из нескольких независимых блоков, то каждый следует вынести в отдельный файл. 694 | 695 | **Неправильно:** 696 | 697 | ```jade 698 | .header 699 | // ... 700 | .banner 701 | // ... 702 | .container 703 | .sidebar 704 | // ... 705 | .content 706 | .filter 707 | // ... 708 | .products 709 | // ... 710 | .pagination 711 | // ... 712 | .footer 713 | // ... 714 | ``` 715 | 716 | **Правильно:** 717 | 718 | ```jade 719 | include pug/header 720 | include pug/banner 721 | .container 722 | include pug/sidebar 723 | .content 724 | include pug/filter 725 | include pug/products 726 | include pug/pagination 727 | ``` 728 | 729 | Если на странице используются какие-либо счетчики (Google Analytics, Яндекс Метрика и тому подобноее), то этот также 730 | следует выносить в отдельный файл: 731 | 732 | ```jade 733 | prepend scripts 734 | include pug/counters 735 | ``` 736 | 737 | При подключении Pug-файлов не нужно указывать расширение. 738 | 739 | **Неправильно:** 740 | 741 | ```jade 742 | include pug/header.pug 743 | ``` 744 | 745 | **Правильно:** 746 | 747 | ```jade 748 | include pug/header 749 | ``` 750 | 751 | Подключать можно не только Pug, но и любые другие текстовые файлы. Например svg: 752 | 753 | ```jade 754 | .header 755 | a.header__logo(href="/") 756 | include ../images/logo.svg 757 | ``` 758 | 759 | # Миксины 760 | 761 | Если на сайте имеется какой-либо шаблонный блок или компонент (кнопка, карточка, статья), который встречается более 762 | одного раза, то его следует вынести в отдельный миксин. 763 | 764 | **Неправильно:** 765 | 766 | ```jade 767 | .products 768 | .product 769 | a.product__image(href="/product/1") 770 | img(src="/images/image-1.jpg" alt="") 771 | a.product__title(href="/product/1") 772 | | Название товара 773 | .product__description 774 | | Описание товара 775 | .product__price 776 | | 12345 777 | a.product__link(href="/product/1") 778 | | Подробнее 779 | 780 | .product 781 | a.product__image(href="/product/2") 782 | img(src="/images/image-2.jpg" alt="") 783 | a.product__title(href="/product/2") 784 | | Название товара 785 | .product__description 786 | | Описание товара 787 | .product__price 788 | | 67890 789 | a.product__link(href="/product/2") 790 | | Подробнее 791 | ``` 792 | 793 | **Правильно:** 794 | 795 | Создаем миксин в файле src/pug/mixins/product.pug 796 | 797 | ```jade 798 | mixin product(options) 799 | .product&attributes(attributes) 800 | a.product__image(href=options.href) 801 | img(src=options.image alt="") 802 | a.product__title(href=options.href)= options.title 803 | .product__description= options.description 804 | .product__price= options.price 805 | a.product__link(href=options.href) 806 | | Подробнее 807 | ``` 808 | 809 | Подключаем миксин в `base.pug`: 810 | 811 | ```jade 812 | include mixins/svg 813 | include mixins/product 814 | ``` 815 | 816 | После можно использовать созданный миксин на любой странице: 817 | 818 | ```jade 819 | .products 820 | +product({ 821 | href: '/product/1', 822 | image: '/images/image-1.jpg', 823 | title: 'Название товара', 824 | description: 'Описание товара', 825 | price: 12345 826 | }) 827 | 828 | +product({ 829 | href: '/product/2', 830 | image: '/images/image-2.jpg', 831 | title: 'Название товара', 832 | description: 'Описание товара', 833 | price: 67890 834 | }) 835 | ``` 836 | 837 | В миксин также можно передать содержимое (в основном используется при передаче большого количества контента, например 838 | в статьях): 839 | 840 | ```jade 841 | mixin article(title) 842 | article.article 843 | h1.article__title= title 844 | .article__content 845 | block 846 | 847 | +article('Заголовок статьи') 848 | | Содержимое статьи 849 | ``` 850 | 851 | # Комментарии 852 | 853 | Для комментариаев используется синтаксис `//` и `//-`: 854 | 855 | ```jade 856 | // Однострочный комментарий, который попадет в html 857 | 858 | //- Однострочный комментарий, который не попадет в html 859 | 860 | // 861 | Многострочный комментарий, 862 | который попадет в html 863 | 864 | //- 865 | Многострочный комментарий, 866 | который не попадет в html 867 | ``` 868 | -------------------------------------------------------------------------------- /17_codestyle-scss.md: -------------------------------------------------------------------------------- 1 | # Синтаксис и форматирование 2 | 3 | * Для отступов используется символ табуляции `\t`. 4 | 5 | * Для переноса строк используется символ `\n` (LF). 6 | 7 | * Не должно быть пробелов в конце строк. 8 | 9 | * Максимальная длина строки — 120 символов. 10 | 11 | * `!important` используется только для того, чтобы перебить значение `style`-атрибута. 12 | 13 | * В коде не должно быть вендорных префиксов. (есть исключения) 14 | 15 | **Неправильно:** 16 | 17 | ```scss 18 | a { 19 | -webkit-transition: color 0.3s; 20 | -moz-transition: color 0.3s; 21 | transition: color 0.3s; 22 | } 23 | ``` 24 | 25 | **Правильно:** 26 | 27 | ```scss 28 | a { 29 | transition: color 0.3s; 30 | } 31 | ``` 32 | 33 | Вендорные префиксы допустимы только в том случае, если вариант без префикса отсутствует. 34 | 35 | ```scss 36 | @mixin retina { 37 | @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { 38 | @content; 39 | } 40 | } 41 | ``` 42 | 43 | * Селекторы, свойства, `@`-правила, названия переменных, миксин, функций, `#`-цвета — все записывается в нижнем 44 | регистре. 45 | 46 | **Неправильно:** 47 | 48 | ```scss 49 | $Color-Cod-Gray: #1A1A1A; 50 | 51 | .Form { 52 | INPUT { 53 | color: $Color-Cod-Gray; 54 | } 55 | } 56 | ``` 57 | 58 | **Правильно:** 59 | 60 | ```scss 61 | $color-cod-gray: #1a1a1a; 62 | 63 | .form { 64 | input { 65 | color: $color-cod-gray; 66 | } 67 | } 68 | ``` 69 | 70 | * CSS-правила отделяются друг от друга пустой строкой. 71 | 72 | **Неправильно:** 73 | 74 | ```scss 75 | .header { 76 | position: relative; 77 | margin: 0 auto; 78 | padding: 15px; 79 | max-width: 1000px; 80 | background: url("../images/header.jpg") 50% 50% no-repeat; 81 | @include retina { 82 | background-image: url("../images/header@2x.jpg"); 83 | } 84 | @media (min-width: 1000px) { 85 | padding-top: 30px; 86 | padding-bottom: 30px; 87 | } 88 | } 89 | ``` 90 | 91 | **Правильно:** 92 | 93 | ```scss 94 | .header { 95 | position: relative; 96 | margin: 0 auto; 97 | padding: 15px; 98 | max-width: 1000px; 99 | background: url("../images/header.jpg") 50% 50% no-repeat; 100 | 101 | @include retina { 102 | background-image: url("../images/header@2x.jpg"); 103 | } 104 | 105 | @media (min-width: 1000px) { 106 | padding-top: 30px; 107 | padding-bottom: 30px; 108 | } 109 | } 110 | ``` 111 | 112 | * Не должно быть более одной пустой строки подряд. 113 | 114 | **Неправильно:** 115 | 116 | ```scss 117 | .button { 118 | display: inline-block; 119 | 120 | 121 | &::before { 122 | content: ""; 123 | } 124 | } 125 | ``` 126 | 127 | **Правильно:** 128 | 129 | ```scss 130 | .button { 131 | display: inline-block; 132 | 133 | &::before { 134 | content: ""; 135 | } 136 | } 137 | ``` 138 | 139 | * Между свойствами не должно быть пустых строк. 140 | 141 | **Неправильно:** 142 | 143 | ```scss 144 | .button { 145 | display: inline-block; 146 | 147 | border: solid 1px $color-black; 148 | border-radius: 4px; 149 | 150 | padding: 5px 10px; 151 | 152 | font-family: $font-family-roboto; 153 | font-weight: 300; 154 | font-size: 16px; 155 | line-height: 1.5; 156 | 157 | text-align: center; 158 | text-decoration: none; 159 | 160 | cursor: pointer; 161 | } 162 | ``` 163 | 164 | **Правильно:** 165 | 166 | ```scss 167 | .button { 168 | display: inline-block; 169 | border: solid 1px $color-black; 170 | border-radius: 4px; 171 | padding: 5px 10px; 172 | font-family: $font-family-roboto; 173 | font-weight: 300; 174 | font-size: 16px; 175 | line-height: 1.5; 176 | text-align: center; 177 | text-decoration: none; 178 | cursor: pointer; 179 | } 180 | ``` 181 | 182 | * При перечислении селекторов каждый записывается на отдельной строке. 183 | 184 | **Неправильно:** 185 | 186 | ```scss 187 | .prev, .next { 188 | position: absolute; 189 | top: 50%; 190 | } 191 | ``` 192 | 193 | **Правильно:** 194 | 195 | ```scss 196 | .prev, 197 | .next { 198 | position: absolute; 199 | top: 50%; 200 | } 201 | ``` 202 | 203 | * Перед `{` ставится один пробел, а после — один перенос строки. 204 | 205 | **Неправильно:** 206 | 207 | ```scss 208 | .header{ 209 | position: relative; 210 | } 211 | 212 | .banner { 213 | max-width: 1000px; 214 | } 215 | 216 | .footer 217 | { 218 | margin-top: 15px; 219 | } 220 | 221 | .container { 222 | 223 | margin: 0 auto; 224 | } 225 | ``` 226 | 227 | **Правильно:** 228 | 229 | ```scss 230 | .header { 231 | position: relative; 232 | } 233 | 234 | .banner { 235 | max-width: 1000px; 236 | } 237 | 238 | .footer { 239 | margin-top: 15px; 240 | } 241 | 242 | .container { 243 | margin: 0 auto; 244 | } 245 | ``` 246 | 247 | * Перед `}` не должно быть пустых строк. 248 | 249 | **Неправильно:** 250 | 251 | ```scss 252 | a { 253 | text-decoration: none; } 254 | 255 | p { 256 | margin: 15px 0; 257 | 258 | } 259 | ``` 260 | 261 | **Правильно:** 262 | 263 | ```scss 264 | a { 265 | text-decoration: none; 266 | } 267 | 268 | p { 269 | margin: 15px 0; 270 | } 271 | ``` 272 | 273 | * Перед `:` и `,` не должно быть пробелов, а после ставится один пробел. 274 | 275 | **Неправильно:** 276 | 277 | ```scss 278 | .container { 279 | padding : 0 15px; 280 | max-width: 1000px; 281 | background: none; 282 | } 283 | 284 | .button { 285 | box-shadow: inset 0 0 2px 1px $color-black ,0 5px 4px 0 rgba($color-black, 0.25); 286 | transition: color 0.3s , opacity 0.3s; 287 | } 288 | ``` 289 | 290 | **Правильно:** 291 | 292 | ```scss 293 | .container { 294 | padding: 0 15px; 295 | max-width: 1000px; 296 | background: none; 297 | } 298 | 299 | .button { 300 | box-shadow: inset 0 0 2px 1px $color-black, 0 5px 4px 0 rgba($color-black, 0.25); 301 | transition: color 0.3s, opacity 0.3s; 302 | } 303 | ``` 304 | 305 | * После открывающей и до закрывающей скобки не должно быть пробелов. 306 | 307 | **Неправильно:** 308 | 309 | ```scss 310 | a[ href ] { 311 | color: darken( $color-blue, 10% ); 312 | cursor: pointer; 313 | } 314 | ``` 315 | 316 | **Правильно:** 317 | 318 | ```scss 319 | a[href] { 320 | color: darken($color-blue, 10%); 321 | cursor: pointer; 322 | } 323 | ``` 324 | 325 | * Перед и после операторов (`+`, `-`, `*`, `/`) и комбинаторов (`+`, `>`, `~`) ставится один пробел. 326 | 327 | **Неправильно:** 328 | 329 | ```scss 330 | .sidebar { 331 | width: calc((100%- 30px)/2); 332 | 333 | >a { 334 | display: block; 335 | } 336 | } 337 | ``` 338 | 339 | **Правильно:** 340 | 341 | ```scss 342 | .sidebar { 343 | width: calc((100% - 30px) / 2); 344 | 345 | > a { 346 | display: block; 347 | } 348 | } 349 | ``` 350 | 351 | * Строки записываются в двойных кавычках. 352 | 353 | **Неправильно:** 354 | 355 | ```scss 356 | li { 357 | list-style: none; 358 | 359 | &::before { 360 | content: ''; 361 | display: inline-block; 362 | vertical-align: middle; 363 | width: 10px; 364 | height: 10px; 365 | background-image: url(../images/point.png); 366 | } 367 | } 368 | ``` 369 | 370 | **Правильно:** 371 | 372 | ```scss 373 | li { 374 | list-style: none; 375 | 376 | &::before { 377 | content: ""; 378 | display: inline-block; 379 | vertical-align: middle; 380 | width: 10px; 381 | height: 10px; 382 | background-image: url("../images/point.png"); 383 | } 384 | } 385 | ``` 386 | 387 | * Пути записываются в двойных кавычках. 388 | 389 | **Неправильно:** 390 | 391 | ```scss 392 | @import 'vendor/player'; 393 | 394 | .button-play { 395 | background: url(../images/play.png) 50% 50% no-repeat; 396 | } 397 | ``` 398 | 399 | **Правильно:** 400 | 401 | ```scss 402 | @import "vendor/player"; 403 | 404 | .button-play { 405 | background: url("../images/play.png") 50% 50% no-repeat; 406 | } 407 | ``` 408 | 409 | * Значения атрибутов записываются в двойных кавычках. 410 | 411 | **Неправильно:** 412 | 413 | ```scss 414 | a[target=_blank]::after { 415 | content: ""; 416 | display: inline-block; 417 | width: 15px; 418 | height: 15px; 419 | background-image: url("../images/new-page.png"); 420 | } 421 | ``` 422 | 423 | **Правильно:** 424 | 425 | ```scss 426 | a[target="_blank"]::after { 427 | content: ""; 428 | display: inline-block; 429 | width: 15px; 430 | height: 15px; 431 | background-image: url("../images/new-page.png"); 432 | } 433 | ``` 434 | 435 | * Названия шрифтов записываются в двойных кавычках, за исключением ключевых слов. 436 | 437 | **Неправильно:** 438 | 439 | ```scss 440 | $font-family-roboto: Roboto, sans-serif; 441 | 442 | .button { 443 | font-family: $font-family-roboto; 444 | } 445 | ``` 446 | 447 | **Правильно:** 448 | 449 | ```scss 450 | $font-family-roboto: "Roboto", sans-serif; 451 | 452 | .button { 453 | font-family: $font-family-roboto; 454 | } 455 | ``` 456 | 457 | * Псевдоэлементы записываются с помощью `::`. 458 | 459 | **Неправильно:** 460 | 461 | ```scss 462 | .list { 463 | &__item { 464 | &:before { 465 | content: ""; 466 | display: inline-block; 467 | vertical-align: middle; 468 | border-radius: 50%; 469 | width: 8px; 470 | height: 8px; 471 | background-color: currentColor; 472 | } 473 | } 474 | } 475 | ``` 476 | 477 | **Правильно:** 478 | 479 | ```scss 480 | .list { 481 | &__item { 482 | &::before { 483 | content: ""; 484 | display: inline-block; 485 | vertical-align: middle; 486 | border-radius: 50%; 487 | width: 8px; 488 | height: 8px; 489 | background-color: currentColor; 490 | } 491 | } 492 | } 493 | ``` 494 | 495 | * Не должно быть дублирующихся свойств. 496 | 497 | **Неправильно:** 498 | 499 | ```scss 500 | .button { 501 | display: inline-block; 502 | border: solid 1px $color-black; 503 | border-radius: 4px; 504 | padding: 5px; 505 | font-family: $font-family-roboto; 506 | font-weight: 300; 507 | font-size: 16px; 508 | line-height: 1.5; 509 | text-align: center; 510 | text-decoration: none; 511 | cursor: pointer; 512 | border: none; 513 | } 514 | ``` 515 | 516 | **Правильно:** 517 | 518 | ```scss 519 | .button { 520 | display: inline-block; 521 | border: none; 522 | border-radius: 4px; 523 | padding: 5px; 524 | font-family: $font-family-roboto; 525 | font-weight: 300; 526 | font-size: 16px; 527 | line-height: 1.5; 528 | text-align: center; 529 | text-decoration: none; 530 | cursor: pointer; 531 | } 532 | ``` 533 | 534 | * `font-weight` записывается в числовом формате. 535 | 536 | **Неправильно:** 537 | 538 | ```scss 539 | b { 540 | font-weight: bold; 541 | } 542 | ``` 543 | 544 | **Правильно:** 545 | 546 | ```scss 547 | b { 548 | font-weight: 700; 549 | } 550 | ``` 551 | 552 | * У нулевых значений не указываются единицы измерений (кроме времени). 553 | 554 | **Неправильно:** 555 | 556 | ```scss 557 | .button { 558 | margin: 0px auto; 559 | padding: 5px 0px 6px; 560 | } 561 | ``` 562 | 563 | **Правильно:** 564 | 565 | ```scss 566 | .button { 567 | margin: 0 auto; 568 | padding: 5px 0 6px; 569 | } 570 | ``` 571 | 572 | * Для чисел в интервале `(-1, 1)` указывается ведущий ноль. 573 | 574 | **Неправильно:** 575 | 576 | ```scss 577 | a { 578 | transition: color .3s; 579 | } 580 | ``` 581 | 582 | **Правильно:** 583 | 584 | ```scss 585 | a { 586 | transition: color 0.3s; 587 | } 588 | ``` 589 | 590 | * Вместо именованных цветов используется `#`-запись. 591 | 592 | **Неправильно:** 593 | 594 | ```scss 595 | $color-black: black; 596 | ``` 597 | 598 | **Правильно:** 599 | 600 | ```scss 601 | $color-black: #000; 602 | ``` 603 | 604 | * Для записи `#`-цвета следует использовать сокращенный вариант, если это возможно. 605 | 606 | **Неправильно:** 607 | 608 | ```scss 609 | $color-black: #000000; 610 | ``` 611 | 612 | **Правильно:** 613 | 614 | ```scss 615 | $color-black: #000; 616 | ``` 617 | 618 | * При записи `rgba`-цвета задается `#`-значением или переменной. 619 | 620 | **Неправильно:** 621 | 622 | ```scss 623 | .overlay { 624 | background-color: rgba(0, 0, 0, 0.5); 625 | } 626 | ``` 627 | 628 | **Правильно:** 629 | 630 | ```scss 631 | .overlay { 632 | background-color: rgba($color-black, 0.5); 633 | } 634 | ``` 635 | 636 | # `@extend` 637 | 638 | Вместо директивы `@extend` следует использовать `@mixin`. 639 | 640 | # Порядок правил 641 | 642 | * CSS-переменные. 643 | * `$`-переменные. 644 | * `@include` без контента 645 | * Свойства 646 | * `&::before` 647 | * `&::after` 648 | * Различные селекторы 649 | * `&:link` 650 | * `&:visited` 651 | * `&:focus` 652 | * `&:hover` 653 | * `&:active` 654 | * `&:first-child` 655 | * `&:last-child` 656 | * `&:nth-child()` 657 | * `&[attr]` 658 | * `&.modifier` 659 | * `&--modifier` 660 | * `@include` с контентом 661 | * `@media` 662 | 663 | # Порядок свойств 664 | 665 | * `all` 666 | * `print-color-adjust` 667 | * `appearance` 668 | * `counter-increment` 669 | * `counter-reset` 670 | * `content` 671 | * `quotes` 672 | * `position` 673 | * `left` 674 | * `right` 675 | * `top` 676 | * `bottom` 677 | * `z-index` 678 | * `display` 679 | * `columns` 680 | * `column-width` 681 | * `column-count` 682 | * `column-fill` 683 | * `column-gap` 684 | * `column-rule` 685 | * `column-rule-style` 686 | * `column-rule-width` 687 | * `column-rule-color` 688 | * `column-span` 689 | * `break-after` 690 | * `break-before` 691 | * `break-inside` 692 | * `page-break-after` 693 | * `page-break-before` 694 | * `page-break-inside` 695 | * `orphans` 696 | * `widows` 697 | * `flex` 698 | * `flex-grow` 699 | * `flex-shrink` 700 | * `flex-basis` 701 | * `flex-flow` 702 | * `flex-direction` 703 | * `flex-wrap` 704 | * `place-content` 705 | * `place-items` 706 | * `place-self` 707 | * `align-content` 708 | * `align-items` 709 | * `align-self` 710 | * `justify-content` 711 | * `justify-items` 712 | * `justify-self` 713 | * `order` 714 | * `clear` 715 | * `float` 716 | * `grid` 717 | * `grid-area` 718 | * `grid-auto-columns` 719 | * `grid-auto-flow` 720 | * `grid-auto-rows` 721 | * `grid-column` 722 | * `grid-column-end` 723 | * `grid-column-gap` 724 | * `grid-column-start` 725 | * `grid-gap` 726 | * `grid-row` 727 | * `grid-row-end` 728 | * `grid-row-gap` 729 | * `grid-row-start` 730 | * `grid-template` 731 | * `grid-template-areas` 732 | * `grid-template-columns` 733 | * `grid-template-rows` 734 | * `list-style` 735 | * `list-style-type` 736 | * `list-style-position` 737 | * `list-style-image` 738 | * `caption-side` 739 | * `empty-cells` 740 | * `table-layout` 741 | * `vertical-align` 742 | * `clip-path` 743 | * `mask` 744 | * `mask-clip` 745 | * `mask-composite` 746 | * `mask-image` 747 | * `mask-mode` 748 | * `mask-origin` 749 | * `mask-position` 750 | * `mask-position-x` 751 | * `mask-position-y` 752 | * `mask-repeat` 753 | * `mask-repeat-x` 754 | * `mask-repeat-y` 755 | * `mask-size` 756 | * `mask-type` 757 | * `shape-image-threshold` 758 | * `shape-margin` 759 | * `shape-outside` 760 | * `contain` 761 | * `overflow` 762 | * `overflow-x` 763 | * `overflow-y` 764 | * `overflow-anchor` 765 | * `overflow-wrap` 766 | * `margin` 767 | * `margin-top` 768 | * `margin-right` 769 | * `margin-bottom` 770 | * `margin-left` 771 | * `margin-before` 772 | * `margin-end` 773 | * `margin-after` 774 | * `margin-start` 775 | * `margin-collapse` 776 | * `margin-top-collapse` 777 | * `margin-bottom-collapse` 778 | * `margin-before-collapse` 779 | * `margin-after-collapse` 780 | * `outline` 781 | * `outline-style` 782 | * `outline-width` 783 | * `outline-color` 784 | * `outline-offset` 785 | * `outline-radius` 786 | * `outline-radius-topleft` 787 | * `outline-radius-topright` 788 | * `outline-radius-bottomright` 789 | * `outline-radius-bottomleft` 790 | * `border` 791 | * `border-style` 792 | * `border-width` 793 | * `border-color` 794 | * `border-top` 795 | * `border-top-style` 796 | * `border-top-width` 797 | * `border-top-color` 798 | * `border-right` 799 | * `border-right-style` 800 | * `border-right-width` 801 | * `border-right-color` 802 | * `border-bottom` 803 | * `border-bottom-style` 804 | * `border-bottom-width` 805 | * `border-bottom-color` 806 | * `border-left` 807 | * `border-left-style` 808 | * `border-left-width` 809 | * `border-left-color` 810 | * `border-before` 811 | * `border-before-style` 812 | * `border-before-width` 813 | * `border-before-color` 814 | * `border-end` 815 | * `border-end-style` 816 | * `border-end-width` 817 | * `border-end-color` 818 | * `border-after` 819 | * `border-after-style` 820 | * `border-after-width` 821 | * `border-after-color` 822 | * `border-start` 823 | * `border-start-style` 824 | * `border-start-width` 825 | * `border-start-color` 826 | * `border-collapse` 827 | * `border-image` 828 | * `border-image-source` 829 | * `border-image-slice` 830 | * `border-image-width` 831 | * `border-image-outset` 832 | * `border-image-repeat` 833 | * `border-radius` 834 | * `border-top-left-radius` 835 | * `border-top-right-radius` 836 | * `border-bottom-right-radius` 837 | * `border-bottom-left-radius` 838 | * `border-spacing` 839 | * `padding` 840 | * `padding-top` 841 | * `padding-right` 842 | * `padding-bottom` 843 | * `padding-left` 844 | * `padding-before` 845 | * `padding-end` 846 | * `padding-after` 847 | * `padding-start` 848 | * `width` 849 | * `height` 850 | * `min-width` 851 | * `min-height` 852 | * `max-width` 853 | * `max-height` 854 | * `box-decoration-break` 855 | * `box-shadow` 856 | * `box-sizing` 857 | * `src` 858 | * `font` 859 | * `font-family` 860 | * `font-weight` 861 | * `font-style` 862 | * `font-display` 863 | * `font-feature-settings` 864 | * `font-kerning` 865 | * `font-smoothing` 866 | * `font-stretch` 867 | * `font-synthesis` 868 | * `font-variant` 869 | * `font-variant-alternates` 870 | * `font-variant-caps` 871 | * `font-variant-east-asian` 872 | * `font-variant-ligatures` 873 | * `font-variant-numeric` 874 | * `font-variant-position` 875 | * `font-size` 876 | * `font-size-adjust` 877 | * `unicode-bidi` 878 | * `unicode-range` 879 | * `line-break` 880 | * `line-height` 881 | * `letter-spacing` 882 | * `word-break` 883 | * `word-spacing` 884 | * `word-wrap` 885 | * `white-space` 886 | * `hyphens` 887 | * `tab-size` 888 | * `text-align` 889 | * `text-align-last` 890 | * `text-combine-upright` 891 | * `text-decoration` 892 | * `text-decoration-style` 893 | * `text-decoration-line` 894 | * `text-decoration-color` 895 | * `text-decoration-skip` 896 | * `text-emphasis` 897 | * `text-emphasis-style` 898 | * `text-emphasis-color` 899 | * `text-emphasis-position` 900 | * `text-fill-color` 901 | * `text-indent` 902 | * `text-justify` 903 | * `text-orientation` 904 | * `text-overflow` 905 | * `text-rendering` 906 | * `text-security` 907 | * `text-shadow` 908 | * `text-size-adjust` 909 | * `text-stroke` 910 | * `text-stroke-width` 911 | * `text-stroke-color` 912 | * `text-transform` 913 | * `text-underline-position` 914 | * `direction` 915 | * `writing-mode` 916 | * `ruby-align` 917 | * `ruby-position` 918 | * `color` 919 | * `caret-color` 920 | * `tap-highlight-color` 921 | * `d` 922 | * `x` 923 | * `y` 924 | * `cx` 925 | * `cy` 926 | * `r` 927 | * `rx` 928 | * `ry` 929 | * `fill` 930 | * `fill-opacity` 931 | * `fill-rule` 932 | * `stroke` 933 | * `stroke-dasharray` 934 | * `stroke-dashoffset` 935 | * `stroke-linecap` 936 | * `stroke-linejoin` 937 | * `stroke-miterlimit` 938 | * `stroke-opacity` 939 | * `stroke-width` 940 | * `alignment-baseline` 941 | * `baseline-shift` 942 | * `dominant-baseline` 943 | * `clip-rule` 944 | * `color-interpolation` 945 | * `color-interpolation-filters` 946 | * `color-rendering` 947 | * `flood-color` 948 | * `flood-opacity` 949 | * `lighting-color` 950 | * `marker` 951 | * `marker-end` 952 | * `marker-mid` 953 | * `marker-start` 954 | * `paint-order` 955 | * `shape-rendering` 956 | * `stop-color` 957 | * `stop-opacity` 958 | * `text-anchor` 959 | * `offset` 960 | * `offset-position` 961 | * `offset-path` 962 | * `offset-distance` 963 | * `offset-anchor` 964 | * `offset-rotate` 965 | * `background` 966 | * `background-image` 967 | * `background-position` 968 | * `background-position-x` 969 | * `background-position-y` 970 | * `background-size` 971 | * `background-repeat` 972 | * `background-repeat-x` 973 | * `background-repeat-y` 974 | * `background-origin` 975 | * `background-clip` 976 | * `background-attachment` 977 | * `background-color` 978 | * `background-blend-mode` 979 | * `image-orientation` 980 | * `image-rendering` 981 | * `object-fit` 982 | * `object-position` 983 | * `opacity` 984 | * `visibility` 985 | * `filter` 986 | * `isolation` 987 | * `mix-blend-mode` 988 | * `zoom` 989 | * `backface-visibility` 990 | * `perspective` 991 | * `perspective-origin` 992 | * `perspective-origin-x` 993 | * `perspective-origin-y` 994 | * `transform` 995 | * `transform-box` 996 | * `transform-origin` 997 | * `transform-origin-x` 998 | * `transform-origin-y` 999 | * `transform-origin-z` 1000 | * `transform-style` 1001 | * `transition` 1002 | * `transition-property` 1003 | * `transition-duration` 1004 | * `transition-delay` 1005 | * `transition-timing-function` 1006 | * `animation` 1007 | * `animation-name` 1008 | * `animation-duration` 1009 | * `animation-delay` 1010 | * `animation-timing-function` 1011 | * `animation-iteration-count` 1012 | * `animation-direction` 1013 | * `animation-fill-mode` 1014 | * `animation-play-state` 1015 | * `will-change` 1016 | * `cursor` 1017 | * `pointer-events` 1018 | * `touch-action` 1019 | * `user-drag` 1020 | * `user-focus` 1021 | * `user-select` 1022 | * `user-zoom` 1023 | * `resize` 1024 | * `scroll-behavior` 1025 | * `scroll-snap-coordinate` 1026 | * `scroll-snap-destination` 1027 | * `scroll-snap-type` 1028 | * `scroll-snap-type-x` 1029 | * `scroll-snap-type-y` 1030 | -------------------------------------------------------------------------------- /18_codestyle-javascript.md: -------------------------------------------------------------------------------- 1 | # Синтаксис и форматирование 2 | 3 | * Для отступов используется символ табуляции `\t`. 4 | 5 | * Для переноса строк используется символ `\n` (LF). 6 | 7 | * Не должно быть пробелов в конце строк. 8 | 9 | * Максимальная длина строки — 120 символов. 10 | 11 | * Всегда ставится `;`. 12 | 13 | **Неправильно:** 14 | 15 | ```js 16 | $('form').on('submit', (event) => { 17 | let $form = $(event.currentTarget) 18 | 19 | event.preventDefault() 20 | 21 | if (validate($form)) { 22 | $.post($form.attr('action')) 23 | .done(() => { 24 | showSuccessModal() 25 | }) 26 | .fail(() => { 27 | showErrorModal() 28 | }) 29 | } 30 | }) 31 | ``` 32 | 33 | **Правильно:** 34 | 35 | ```js 36 | $('form').on('submit', (event) => { 37 | let $form = $(event.currentTarget); 38 | 39 | event.preventDefault(); 40 | 41 | if (validate($form)) { 42 | $.post($form.attr('action')) 43 | .done(() => { 44 | showSuccessModal(); 45 | }) 46 | .fail(() => { 47 | showErrorModal(); 48 | }); 49 | } 50 | }) 51 | ``` 52 | 53 | * Используется строгое сравнение `===` и `!==` (вместо `==` и `!=`). 54 | 55 | **Неправильно:** 56 | 57 | ```js 58 | $('.test__button').on('click', () => { 59 | if (test.currentQuestionIndex == test.questions.length - 1) { 60 | test.finish(); 61 | } 62 | 63 | test.answer(); 64 | }); 65 | ``` 66 | 67 | **Правильно:** 68 | 69 | ```js 70 | $('.test__button').on('click', () => { 71 | if (test.currentQuestionIndex === test.questions.length - 1) { 72 | test.finish(); 73 | } 74 | 75 | test.answer(); 76 | }); 77 | ``` 78 | 79 | * Не должно быть неиспользуемых переменных. 80 | 81 | **Неправильно:** 82 | 83 | ```js 84 | let dx = event.clientX - start.clientX; 85 | let dy = event.clientY - start.clientY; 86 | 87 | $slider.css({ 88 | '-webkit-transform': `translate(${dx}px, 0)`, 89 | 'transform': `translate(${dx}px, 0)`, 90 | }); 91 | ``` 92 | 93 | **Правильно:** 94 | 95 | ```js 96 | let dx = event.clientX - start.clientX; 97 | 98 | $slider.css({ 99 | '-webkit-transform': `translate(${dx}px, 0)`, 100 | 'transform': `translate(${dx}px, 0)`, 101 | }); 102 | ``` 103 | 104 | * Переменные объявляются на отдельных строках. 105 | 106 | **Неправильно:** 107 | 108 | ```js 109 | function showPage($page) { 110 | let $currentPage = $('.page--current'), 111 | $header = $('.header'), 112 | $footer = $('.footer'); 113 | 114 | new TimelineMax() 115 | .to([ 116 | $header, 117 | $footer, 118 | $currentPage, 119 | ], 1, { 120 | opacity: 0, 121 | onComplete() { 122 | $currentPage.removeClass('page--current'); 123 | }, 124 | }) 125 | .from([ 126 | $header, 127 | $footer, 128 | $page, 129 | ], 1, { 130 | clearProps: 'all', 131 | onStart() { 132 | $page.addClass('page--current'); 133 | }, 134 | }); 135 | } 136 | ``` 137 | 138 | **Правильно:** 139 | 140 | ```js 141 | function showPage($page) { 142 | let $currentPage = $('.page--current'); 143 | let $header = $('.header'); 144 | let $footer = $('.footer'); 145 | 146 | new TimelineMax() 147 | .to([ 148 | $header, 149 | $footer, 150 | $currentPage, 151 | ], 1, { 152 | opacity: 0, 153 | onComplete() { 154 | $currentPage.removeClass('page--current'); 155 | }, 156 | }) 157 | .from([ 158 | $header, 159 | $footer, 160 | $page, 161 | ], 1, { 162 | clearProps: 'all', 163 | onStart() { 164 | $page.addClass('page--current'); 165 | }, 166 | }); 167 | } 168 | ``` 169 | 170 | * Не следует сокращать названия переменных и функций. 171 | 172 | **Неправильно:** 173 | 174 | ```js 175 | $('.js-scroll').on('click', (e) => { 176 | let $l = $(e.currentTarget); 177 | let h = $l.attr('href'); 178 | let t = $(h).offset().top; 179 | 180 | $('html, body').animate({ 181 | scrollTop: t, 182 | }, 500); 183 | }); 184 | ``` 185 | 186 | **Правильно:** 187 | 188 | ```js 189 | $('.js-scroll').on('click', (event) => { 190 | let $link = $(event.currentTarget); 191 | let href = $link.attr('href'); 192 | let top = $(href).offset().top; 193 | 194 | $('html, body').animate({ 195 | scrollTop: top, 196 | }, 500); 197 | }); 198 | ``` 199 | 200 | * После определения переменных следует оставлять пустую строку. 201 | 202 | **Неправильно:** 203 | 204 | ```js 205 | $('form').on('submit', (event) => { 206 | let $form = $(event.currentTarget); 207 | let $button = $form.find('submit'); 208 | event.preventDefault(); 209 | $button.prop('disabled', true); 210 | submitForm($form); 211 | }); 212 | ``` 213 | 214 | **Правильно:** 215 | 216 | ```js 217 | $('form').on('submit', (event) => { 218 | let $form = $(event.currentTarget); 219 | let $button = $form.find('submit'); 220 | 221 | event.preventDefault(); 222 | $button.prop('disabled', true); 223 | submitForm($form); 224 | }); 225 | ``` 226 | 227 | * Операторы в выражениях отделяются одним пробелом. 228 | 229 | **Неправильно:** 230 | 231 | ```js 232 | function sum(a, b) { 233 | return a+b; 234 | } 235 | ``` 236 | 237 | **Правильно:** 238 | 239 | ```js 240 | function sum(a, b) { 241 | return a + b; 242 | } 243 | ``` 244 | 245 | * Перед `return` следует оставлять пустую строку. 246 | 247 | **Неправильно:** 248 | 249 | ```js 250 | function getQueryParam(key) { 251 | let params = location.search.slice(1).split('&'); 252 | 253 | for (let param of params) { 254 | param = param.split('&'); 255 | 256 | if (param[0] === key) { 257 | return param[1]; 258 | } 259 | } 260 | return null; 261 | } 262 | ``` 263 | 264 | **Правильно:** 265 | 266 | ```js 267 | function getQueryParam(key) { 268 | let params = location.search.slice(1).split('&'); 269 | 270 | for (let param of params) { 271 | param = param.split('&'); 272 | 273 | if (param[0] === key) { 274 | return param[1]; 275 | } 276 | } 277 | 278 | return null; 279 | } 280 | ``` 281 | 282 | * Многострочные конструкции и выражения отделяются пустой строкой. 283 | 284 | **Неправильно:** 285 | 286 | ```js 287 | function f() { 288 | console.log('f'); 289 | } 290 | function g() { 291 | console.log('g'); 292 | } 293 | let timer = { 294 | start() { 295 | this.stop(); 296 | this.intervalId = setInterval(() => { 297 | console.log('Tick'); 298 | }, 1000); 299 | }, 300 | stop() { 301 | clearInterval(this.intervalId); 302 | }, 303 | }; 304 | if (x > 5) { 305 | x = 0; 306 | } 307 | if (!items.length) { 308 | items.push(42); 309 | } 310 | switch (number) { 311 | case 4: 312 | console.log('a'); 313 | break; 314 | case 5: 315 | console.log('b'); 316 | break; 317 | case 1: 318 | console.log('c'); 319 | break; 320 | default: 321 | console.log('d'); 322 | break; 323 | } 324 | for (let i = 0; i < 10; i++) { 325 | y *= i; 326 | } 327 | $('.button').on('click', (event) => { 328 | event.preventDefault(); 329 | run(); 330 | }); 331 | ``` 332 | 333 | **Правильно:** 334 | 335 | ```js 336 | function f() { 337 | console.log('f'); 338 | } 339 | 340 | function g() { 341 | console.log('g'); 342 | } 343 | 344 | let timer = { 345 | start() { 346 | this.stop(); 347 | 348 | this.intervalId = setInterval(() => { 349 | console.log('Tick'); 350 | }, 1000); 351 | }, 352 | 353 | stop() { 354 | clearInterval(this.intervalId); 355 | }, 356 | }; 357 | 358 | if (x > 5) { 359 | x = 0; 360 | } 361 | 362 | if (!items.length) { 363 | items.push(42); 364 | } 365 | 366 | switch (number) { 367 | case 4: 368 | console.log('a'); 369 | break; 370 | 371 | case 5: 372 | console.log('b'); 373 | break; 374 | 375 | case 1: 376 | console.log('c'); 377 | break; 378 | 379 | default: 380 | console.log('d'); 381 | break; 382 | } 383 | 384 | for (let i = 0; i < 10; i++) { 385 | y *= i; 386 | } 387 | 388 | $('.button').on('click', (event) => { 389 | event.preventDefault(); 390 | run(); 391 | }); 392 | ``` 393 | 394 | * Не более одной пустой строки подряд. 395 | 396 | **Неправильно:** 397 | 398 | ```js 399 | $('.js-scroll-to').on('click', (event) => { 400 | event.preventDefault(); 401 | scrollTo(event.currentTarget.href); 402 | }); 403 | 404 | 405 | $('.js-close').on('click', (event) => { 406 | $(event.currentTarget).parent().removeClass('is-open'); 407 | }); 408 | ``` 409 | 410 | **Правильно:** 411 | 412 | ```js 413 | $('.js-scroll-to').on('click', (event) => { 414 | event.preventDefault(); 415 | scrollTo(event.currentTarget.href); 416 | }); 417 | 418 | $('.js-close').on('click', (event) => { 419 | $(event.currentTarget).parent().removeClass('is-open'); 420 | }); 421 | ``` 422 | 423 | * Не следует использовать однострочную запись `if` без фигурных скобок. 424 | 425 | **Неправильно:** 426 | 427 | ```js 428 | function resizeItems() { 429 | if (!items.length) return; 430 | 431 | items.forEach((item) => { 432 | item.resize(); 433 | }); 434 | } 435 | ``` 436 | 437 | **Правильно:** 438 | 439 | ```js 440 | function resizeItems() { 441 | if (!items.length) { 442 | return; 443 | } 444 | 445 | items.forEach((item) => { 446 | item.resize(); 447 | }); 448 | } 449 | ``` 450 | 451 | * Для строк используются одинарные кавычки. 452 | 453 | **Неправильно:** 454 | 455 | ```js 456 | let name = "John Doe" 457 | ``` 458 | 459 | **Правильно:** 460 | 461 | ```js 462 | let name = 'John Doe'; 463 | ``` 464 | 465 | # ES6 возможности 466 | 467 | * Используются шаблонные строки вместо конкатенации. 468 | 469 | **Неправильно:** 470 | 471 | ```js 472 | function getDateTime() { 473 | let now = new Date(); 474 | let year = now.getFullYear().toString().padStart(2, '0'); 475 | let month = (now.getMonth() + 1).toString().padStart(2, '0'); 476 | let day = now.getDate().toString().padStart(2, '0'); 477 | let hours = now.getHours().toString().padStart(2, '0'); 478 | let minutes = now.getMinutes().toString().padStart(2, '0'); 479 | 480 | return year + '.' + month + '.' + day + ' ' + hours + ':' + minutes; 481 | } 482 | ``` 483 | 484 | **Правильно:** 485 | 486 | ```js 487 | function getDateTime() { 488 | let now = new Date(); 489 | let year = now.getFullYear().toString().padStart(2, '0'); 490 | let month = (now.getMonth() + 1).toString().padStart(2, '0'); 491 | let day = now.getDate().toString().padStart(2, '0'); 492 | let hours = now.getHours().toString().padStart(2, '0'); 493 | let minutes = now.getMinutes().toString().padStart(2, '0'); 494 | 495 | return `${year}.${month}.${day} ${hours}:${minutes}`; 496 | } 497 | ``` 498 | 499 | * Используется `let` вместо `var`. 500 | 501 | **Неправильно:** 502 | 503 | ```js 504 | var name = 'John'; 505 | var surname = 'Doe'; 506 | var fullname = `${name} ${surname}`; 507 | ``` 508 | 509 | **Правильно:** 510 | 511 | ```js 512 | let name = 'John'; 513 | let surname = 'Doe'; 514 | let fullname = `${name} ${surname}`; 515 | ``` 516 | 517 | **Правильно:** 518 | 519 | ```js 520 | ``` 521 | 522 | * Используются стрелочные функции вместо анонимных. 523 | 524 | **Неправильно:** 525 | 526 | ```js 527 | $('form').on('submit', function (event) { 528 | event.preventDefault(); 529 | submitForm(this); 530 | }); 531 | ``` 532 | 533 | **Правильно:** 534 | 535 | ```js 536 | $('form').on('submit', (event) => { 537 | event.preventDefault(); 538 | submitForm(event.currentTarget); 539 | }); 540 | ``` 541 | 542 | * Следует использовать сокращенную запись ключей объекта. 543 | 544 | **Неправильно:** 545 | 546 | ```js 547 | function getObjectPosition(object) { 548 | let $offset = $(object).offset(); 549 | let x = $offset.top; 550 | let y = $offset.left; 551 | 552 | return { 553 | x: x, 554 | y: y, 555 | }; 556 | } 557 | ``` 558 | 559 | **Правильно:** 560 | 561 | ```js 562 | function getObjectPosition(object) { 563 | let $offset = $(object).offset(); 564 | let x = $offset.top; 565 | let y = $offset.left; 566 | 567 | return { 568 | x, 569 | y, 570 | }; 571 | } 572 | ``` 573 | 574 | * Следует использовать сокращенную запись методов объекта. 575 | 576 | **Неправильно:** 577 | 578 | ```js 579 | TweenMax.from($element, 1, { 580 | opacity: 0, 581 | clearProps: 'all', 582 | onStart: () => { 583 | $element.removeClass('is-hidden'); 584 | }, 585 | }); 586 | ``` 587 | 588 | **Правильно:** 589 | 590 | ```js 591 | TweenMax.from($element, 1, { 592 | opacity: 0, 593 | clearProps: 'all', 594 | onStart() { 595 | $element.removeClass('is-hidden'); 596 | }, 597 | }); 598 | ``` 599 | -------------------------------------------------------------------------------- /19_video-js.md: -------------------------------------------------------------------------------- 1 | # Video.js 2 | 3 | [Video.js](https://github.com/videojs/video.js) - это библиотека с открытым исходным кодом, предназначенная для 4 | создания видео плеера. 5 | 6 | Сама по себе библиотека очень проста. Дополнительная функциональность поставляется в плагинах(плейлисты, аналитика, 7 | реклама, и расширенные форматы видео - `HLS` или `DASH`). 8 | 9 | ## Установка 10 | 11 | ### NPM 12 | 13 | ```bash 14 | npm install --save video.js 15 | ``` 16 | 17 | ### CDN 18 | 19 | ```html 20 | <script src="http://vjs.zencdn.net/6.9.0/video.js"></script> 21 | ``` 22 | 23 | ```html 24 | <link href="http://vjs.zencdn.net/6.9.0/video-js.css" rel="stylesheet"> 25 | ``` 26 | 27 | ## Инициализация 28 | 29 | На странице должен присутствовать тег: 30 | 31 | ```html 32 | <video class="video-js"></video> 33 | ``` 34 | 35 | Передаем строку содержащую `id` элемента: 36 | 37 | ```js 38 | let video = videojs('id'); 39 | ``` 40 | 41 | Или `DOM` элемент 42 | 43 | ```js 44 | let video = videojs(document.querySelector('.video-js')); 45 | ``` 46 | 47 | ## Опции 48 | 49 | Опции передаются вторым параметром: 50 | 51 | ```js 52 | let video = videojs('my-video', { 53 | autoplay: false, 54 | }); 55 | ``` 56 | 57 | **Основные опции:** 58 | 59 | * `autoplay: boolean` - автоматическое воспроизведение; 60 | * `controls: boolean` - отображать ли интерфейс плеера; 61 | * `loop: boolean` - зацикливание воспроизведения видео; 62 | * `muted: boolean` - приглушение звука; 63 | * `poster: string` - ссылка на превью видео; 64 | * `width: string|number` - ширина видео; 65 | * `height: string|number` - высота видео; 66 | 67 | **Дополнительные опции:** 68 | 69 | * `fluid: boolean` - подгонять ли видео под размер контейнера; 70 | * `aspectRatio: string` - соотношение сторон видео (16:9, 4:3); 71 | 72 | ## Методы 73 | 74 | * `src(string|array)` - позволяет задать источник видео; 75 | 76 | ```js 77 | video.src('/path/to/video.mp4'); 78 | 79 | video.src([ 80 | { 81 | type: 'video/mp4', 82 | src: '/path/to/video.mp4', 83 | }, 84 | { 85 | type: 'video/webm', 86 | src: '/path/to/video.webm', 87 | }, 88 | { 89 | type: 'video/ogg', 90 | src: '/path/to/video.ogg', 91 | }, 92 | ]); 93 | ``` 94 | 95 | * `poster(string)` - позволяет задать превью видео; 96 | 97 | * `play()` - воспроизводит видео; 98 | 99 | * `pause()` - ставит видео на паузу; 100 | 101 | * `paused()` - возвращает `true`, если видео стоит на паузе, иначе `false`; 102 | 103 | * `dispose()` - полностью удаляет плеер (вызывает событие `dispose`, удаляет все обработчики событий, удаляет `DOM` элементы); 104 | 105 | * `volume(number)` - задает горомкость звука (число от `0` до `1`); если вызвать без параметра - возвращает текущее значение; 106 | 107 | * `muted(bolean)` - возвращае `true`, если звук выключен, иначе `false`; если передано `true` - выключает звук. 108 | 109 | * `requestFullscreen()` - вход в полноэкранный режим; 110 | 111 | * `exitFullscreen()` - выход из полноэкранного режима; 112 | 113 | * `isFullscreen()` - возвращает `true` если видео находится в полноэкранном режиме, иначе `false`; 114 | 115 | * `currentTime(number)` - возвращает текущее место воспроизведения (в секундах); если передать число - устанавливает текущее место; 116 | 117 | * `duration()` - возвращает длину видео; 118 | 119 | * `remainingTime()` - возвращает оставшееся время; 120 | 121 | **Пример:** 122 | 123 | ```js 124 | let video = videojs('video', { 125 | controls: true, 126 | autoplay: false, 127 | loop: false, 128 | poster: '/video/cover.jpg', 129 | }); 130 | 131 | video.src({ 132 | src: '/video/video.mp4', 133 | withCredentials: true, 134 | }); 135 | 136 | video.on('ready', () => { 137 | // ... 138 | }); 139 | 140 | ``` 141 | 142 | ## События 143 | 144 | События те же, что у нативного элемента `video`. 145 | Полный список [тут](https://developer.mozilla.org/ru/docs/Web/Guide/Events/Media_events). 146 | 147 | **Пример:** 148 | 149 | ```js 150 | video.on('dispose', () => { 151 | // ... 152 | }); 153 | 154 | video.on('play', () => { 155 | // ... 156 | }); 157 | 158 | video.on('ended', () => { 159 | // ... 160 | }); 161 | ``` 162 | 163 | ## Стриминг (HLS) 164 | 165 | Для стриминга `video.js` использует плагин [videojs-contrib-hls](https://github.com/videojs/videojs-contrib-hls). 166 | 167 | Как подготовить видео - описано [тут](20_hls.md) 168 | 169 | ### Установка 170 | 171 | ```bash 172 | npm install --save videojs-contrib-hls 173 | ``` 174 | 175 | ### Инициализация 176 | 177 | ```js 178 | let video = videojs('my-video', { 179 | controls: true, 180 | autoplay: true, 181 | loop: false, 182 | poster: '/video/cover.jpg', 183 | html5: { 184 | nativeAudioTracks: false, 185 | nativeVideoTracks: false, 186 | nativeTextTracks: false, 187 | hls: { 188 | overrideNative: true, 189 | }, 190 | }, 191 | }); 192 | 193 | video.src({ 194 | src: 'video-name.m3u8', 195 | type: 'application/x-mpegURL', 196 | withCredentials: true, 197 | }); 198 | ``` 199 | 200 | Для воспроизведения стриминга с других серверов, необходимо установить свойство `withCredentials: false`. 201 | 202 | ## Выбор качества воспроизводимого видео 203 | 204 | Для ручного выбора качества видео, необходимо установить 2 плагина: 205 | 206 | ```bash 207 | npm install --save videojs-contrib-quality-levels videojs-hls-quality-selector 208 | ``` 209 | 210 | После указания источника видео, нужно вызвать метод `.hlsQualitySelector()` для экземпляра видео. 211 | 212 | **Пример** 213 | 214 | ```js 215 | video.src({ 216 | src: `video.m3u8`, 217 | type: 'application/x-mpegURL', 218 | withCredentials: false, 219 | }); 220 | 221 | video.hlsQualitySelector(); 222 | ``` 223 | 224 | ## Субтитры 225 | 226 | Для субтриров используются файлы в формате `.vtt`. 227 | 228 | **Пример** 229 | 230 | ``` 231 | WEBVTT 232 | 233 | 00:01.000 --> 00:04.000 234 | Never drink liquid nitrogen. 235 | 236 | 00:05.000 --> 00:09.000 237 | - It will perforate your stomach. 238 | ``` 239 | 240 | Что бы подключить субтитры к видео - нужно использовать метод `.addRemoteTextTrack()`. 241 | 242 | **Пример** 243 | 244 | ```js 245 | video.addRemoteTextTrack({ 246 | label: 'Russian', 247 | kind: 'captions', 248 | src: 'subtitles.vtt', 249 | default: true, 250 | }, false); 251 | ``` 252 | 253 | Задать стили субтитрам можно так (здесь используется `!important`, так как video.js задает стили инлайн): 254 | 255 | ```scss 256 | .vjs-text-track-display { 257 | display: block; 258 | pointer-events: none; 259 | 260 | div { 261 | font-family: Arial, sans-serif !important; 262 | font-size: 40px !important; 263 | background-color: transparent !important; 264 | } 265 | } 266 | ``` 267 | 268 | ## Стилизация 269 | 270 | По умолчанию используется стандартный скин. 271 | 272 | ```html 273 | <link href="http://vjs.zencdn.net/6.7.1/video-js.css" rel="stylesheet"> 274 | ``` 275 | 276 | **Стандартный скин:** 277 | 278 | ![Стандартная стилизация](images/19/image-1.jpg) 279 | 280 | **Отображение без стилей:** 281 | 282 | ![Отображение без стилей](images/19/image-2.jpg) 283 | 284 | Для кастомной стилизаци, нужно использовать свои стили. 285 | 286 | **Пример своей стилизации:** 287 | 288 | ![Своя стилизация](images/19/image-3.jpg) 289 | 290 | - Контейнер 291 | 292 | Вписываем контейнер в блок, у которого заданы размеры требуемые 293 | по макету. 294 | 295 | ```html 296 | <div id="video" class="video-js"></div> 297 | ``` 298 | 299 | ```scss 300 | .video-js { 301 | width: 100%; 302 | height: 100%; 303 | } 304 | ``` 305 | 306 | - Элемент `<video>` 307 | 308 | Подгоняем формат, для предотвращения деформирования видео. 309 | 310 | ```scss 311 | .vjs-tech { 312 | width: auto; 313 | height: 100vh; 314 | min-width: 100vw; 315 | object-fit: cover; 316 | 317 | @media (min-aspect-ratio: 1920 / 1080) { 318 | width: 100vw; 319 | height: auto; 320 | min-height: 100vh; 321 | } 322 | } 323 | 324 | ``` 325 | 326 | - Обложка `.vjs-poster` 327 | 328 | - Спинер `.vjs-loading-spinner` 329 | 330 | - Кнопка воспроизведение\пауза `.vjs-play-control` 331 | 332 | - Кнопка вкл\выкл звук `.vjs-mute-control` 333 | 334 | - Полоса громкости `.vjs-volume-bar` 335 | 336 | - Текущее время видео `.vjs-current-time` 337 | 338 | - Длительность видео `.vjs-duration` 339 | 340 | - Оставшееся время `.vjs-remaining-time` 341 | 342 | - Прогресс бар `.vjs-progress-control` 343 | 344 | - Кнопка полноэкранного режима `.vjs-fullscreen-control` 345 | 346 | **Пример:** 347 | 348 | ```scss 349 | .vjs-progress-control { 350 | display: block; 351 | } 352 | 353 | .vjs-progress-holder { 354 | position: relative; 355 | height: 4px; 356 | cursor: pointer; 357 | 358 | &::before { 359 | content: ""; 360 | position: absolute; 361 | left: 0; 362 | width: 100%; 363 | height: 4px; 364 | background-color: #fff; 365 | } 366 | } 367 | 368 | .vjs-play-progress { 369 | position: relative; 370 | display: block; 371 | width: 0; 372 | height: 4px; 373 | background-color: #000; 374 | 375 | .vjs-control-text { 376 | display: none; 377 | } 378 | 379 | .vjs-time-tooltip { 380 | display: none; 381 | } 382 | } 383 | ``` 384 | -------------------------------------------------------------------------------- /20_hls.md: -------------------------------------------------------------------------------- 1 | # HTTP Live Streaming (HLS) 2 | 3 | `HLS` — протокол для потоковой передачи медиа (аудио/видео), на основе `HTTP`. 4 | 5 | В основе работы лежит принцип разбиения цельного потока на небольшие фрагменты, последовательно скачиваемые по `HTTP`. 6 | 7 | В начале сессии скачивается плей-лист в формате `.m3u8` (обычные текстовый файл), содержащий метаданные об имеющихся вложенных потоках. 8 | 9 | Сами потоки находятся в файлах с расширением `.ts`. 10 | 11 | Видео обычно кодируется с помощью `H264/h265`, аудио `AAC`. 12 | 13 | [Ссылка на статью](https://developer.apple.com/library/content/referencelibrary/GettingStarted/AboutHTTPLiveStreaming/about/about.html) 14 | 15 | 16 | ## Конвертация (подготовка файлов для стриминга) `.mp4` в `.m3u8` и `.ts` 17 | 18 | ### FFMPEG 19 | 20 | 1. Скачиваем и устанавливаем [FFMPEG](http://www.ffmpeg.org/download.html). 21 | У многих возникает вопрос как установить FFMPEG на Windows, поэтому ответ на этот вопрос можно найти [по ссылке](https://windowsloop.com/install-ffmpeg-windows-10/). 22 | 2. Прописываем `FFMPEG` в `PATH`. 23 | 3. Переходим в папку с видео: 24 | 25 | ```bash 26 | cd video_folder_name/ 27 | ``` 28 | 29 | 4. Создаем папки под видео: 30 | 31 | ```bash 32 | mkdir 1080 720 406 270 180 33 | ``` 34 | 35 | 5. Нарезаем видео: 36 | 37 | ```bash 38 | ffmpeg \ 39 | -i video_name.mp4 \ 40 | -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" \ 41 | -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" \ 42 | -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" \ 43 | -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" \ 44 | -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" 45 | ``` 46 | 47 | Аналогичная команда для cmd: 48 | 49 | ```bash 50 | ffmpeg ^ 51 | -i video_name.mp4 ^ 52 | -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" ^ 53 | -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" ^ 54 | -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" ^ 55 | -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" ^ 56 | -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" 57 | ``` 58 | 59 | 6. Делаем обложку (если обложка не предоставлялась дизайнером): 60 | 61 | ```bash 62 | ffmpeg -i video_name.mp4 -ss 00:00:00 -vframes 1 cover.jpg 63 | ``` 64 | 65 | <details> 66 | <summary>Удобнее это делать, используя переменную с названием видео:</summary> 67 | 68 | ```bash 69 | video_name="название_видео" 70 | 71 | mkdir 1080 720 406 270 180 72 | 73 | ffmpeg 74 | -i $video_name.mp4 \ 75 | -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" 76 | -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" 77 | -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" 78 | -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" 79 | -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" 80 | 81 | ffmpeg -i $video_name.mp4 -ss 00:00:00 -vframes 1 cover.jpg 82 | ``` 83 | 84 | Аналогичная команда для cmd: 85 | 86 | ```bash 87 | SET video_name="название_видео" 88 | 89 | mkdir 1080 720 406 270 180 90 | 91 | ffmpeg 92 | -i %video_name%.mp4 ^ 93 | -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" ^ 94 | -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" ^ 95 | -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" ^ 96 | -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" ^ 97 | -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" 98 | 99 | ffmpeg -i %video_name%.mp4 -ss 00:00:00 -vframes 1 cover.jpg 100 | ``` 101 | </details> 102 | 103 | На выходе получаем видео, раскиданное по папкам с нужным размером и разбитое на файлы `.ts`, манифесты `.m3u8` (содержащие метаинформацию о файлах для каждого размера) и обложку `cover.jpg`. 104 | 105 | 7. Создаем мастер файл для объединения всех манифестов `video-name.m3u8`: 106 | 107 | ``` 108 | #EXTM3U 109 | #EXT-X-VERSION:3 110 | #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=3880000,RESOLUTION=1920x1080,CODECS="mp4a.40.2,avc1.77.30",CLOSED-CAPTIONS=NONE 111 | 1080/playlist-1080.m3u8 112 | #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1810000,RESOLUTION=1280x720,CODECS="mp4a.40.2,avc1.77.30",CLOSED-CAPTIONS=NONE 113 | 720/playlist-720.m3u8 114 | #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=800000,RESOLUTION=720x406,CODECS="mp4a.40.2,avc1.77.30",CLOSED-CAPTIONS=NONE 115 | 406/playlist-406.m3u8 116 | #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=600000,RESOLUTION=480x270,CODECS="mp4a.40.2,avc1.77.30",CLOSED-CAPTIONS=NONE 117 | 270/playlist-270.m3u8 118 | #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=410000,RESOLUTION=320x180,CODECS="mp4a.40.2,avc1.77.30",CLOSED-CAPTIONS=NONE 119 | 180/playlist-180.m3u8 120 | ``` 121 | 122 | ## Стандартные размеры видео для HLS 123 | 124 | * **180p** — 320x180px 125 | * **270p** — 480x270px 126 | * **406p** — 720x406px 127 | * **720p** — 1280x720px 128 | * **1080p** — 1920x1080px 129 | 130 | Точки `p` — определяющие название файла, берутся от высоты видео. 131 | -------------------------------------------------------------------------------- /21_bem.md: -------------------------------------------------------------------------------- 1 | # Использование БЭМ методологии 2 | 3 | Что такое БЭМ и всю информацио о методологии можно прочесть [здесь](https://ru.bem.info/methodology/). 4 | 5 | * Используется стиль именования [«Two Dashes»](https://ru.bem.info/methodology/naming-convention/#%D1%81%D1%82%D0%B8%D0%BB%D1%8C-two-dashes). 6 | 7 | * Название блока должно быть логически понятным: header, footer, nav, sidebar, content, list и т.п. 8 | 9 | * Элемент - составная часть блока. Не должен использоваться вне блока и не должен являться частью другого элемента. 10 | 11 | * Модификатор не должен использоваться самостоятельно. 12 | 13 | * Допустимы модификаторы со значениями, но желательно использовать в очень редких случаях. 14 | 15 | * Допустимо использование миксов. 16 | 17 | * В идеале, каждый блок должен представлять из себя независимый компонент. Такой компонент позволяет без труда использовать его много раз в повторяющихся местах сайта, быстро и безболезненно редактировать и дополнять его, а также переносить из проекта в проект (универсальный компонент). 18 | 19 | **Неправильно:** 20 | 21 | ```jade 22 | .header--fixed 23 | .header__top 24 | button.header__top__burger 25 | a.header__top__logo(href="#") 26 | 27 | .header__bottom 28 | .header__nav 29 | .header__nav__item 30 | a.header__nav__item__link(href="#") 31 | .header__nav__item--active 32 | a.header__nav__item--active__link(href="#") 33 | .header__nav__item 34 | a.header__nav__item__link(href="#") 35 | ``` 36 | 37 | **Правильно:** 38 | 39 | ```jade 40 | header.header.header--fixed 41 | .header__top 42 | button.header__burger(type="button") 43 | a.header__logo(href="#") 44 | 45 | .header__bottom 46 | nav.nav 47 | .nav__item 48 | a.nav__link(href="#") 49 | .nav__item.nav__item--active 50 | a.nav__link(href="#") 51 | .nav__item 52 | a.nav__link(href="#") 53 | ``` 54 | 55 | **Правильно:** 56 | 57 | ```jade 58 | header.header.is-fixed 59 | .header__top 60 | button.header__burger(type="button") 61 | a.header__logo(href="#") 62 | 63 | .header__bottom 64 | nav.nav 65 | .nav__item 66 | a.nav__link(href="#") 67 | .nav__item.is-active 68 | a.nav__link(href="#") 69 | .nav__item 70 | a.nav__link(href="#") 71 | ``` 72 | -------------------------------------------------------------------------------- /22_crossbrowser-adaptive.md: -------------------------------------------------------------------------------- 1 | # Кроссбраузерность 2 | 3 | Исходя из нашей статистики, основная часть сайтов должна поддерживаться в последних версиях таких браузеров, как: 4 | 5 | ## Десктоп 6 | 7 | * Chrome 8 | * Safari 9 | * Firefox 10 | * Internet Explorer 11 11 | * Edge 12 | * Yandex Browser (русскоязычный сегмент) 13 | * Opera (русскоязычный сегмент) 14 | 15 | ## Мобильные устройства и планшеты 16 | 17 | * Chrome 18 | * Safari 19 | 20 | В определенных случаях данный список может меняться. 21 | 22 | # Адаптивность 23 | 24 | Для основной части сайтов используются два базовых брейкпоинта (в SCSS созданы для этого миксины mobile и desktop): 25 | 26 | * с 320 до 1024 (мобильные устройства и планшеты) 27 | * с 1025 и выше (десктоп) 28 | 29 | Остальные брейкпоинты добавляются по мере надобности, либо в зависимости от предоставленных макетов. 30 | 31 | Сайт должен быть полностю адаптирован по ширине начиная от 320 и по высоте начиная от 550 пикселей. 32 | -------------------------------------------------------------------------------- /23_perfomance.md: -------------------------------------------------------------------------------- 1 | # Советы для оптимизации производительности сайта 2 | 3 | В данном разделе документации собраны некоторые советы для разработки, которые помогут улучшить производительность сайта. 4 | 5 | # 1. Оптимизация растровых изображений 6 | 7 | Формат изображений выбираем отталкиваясь от прозрачности, если прозрачность присутствует - png, если ее нет - jpg. Картинки формата jpg получаются более легкими и лучше сжимаются. Конечно есть случаи, где требуется использовать тот или иной формат по мере надобности. 8 | 9 | Используйте **srcset** для выбора необходимого изображения под нужное разрешение. 10 | 11 | ```html 12 | <img src="/images/example.jpg" srcset="/images/example-retina.jpg 2x" alt="image"> 13 | ``` 14 | 15 | Картинки должны быть сжаты, для этого можно использовать любой онлайн сервис или ПО для сжатия картинок. Конкретного предпочтения по сервисам нет, главное чтобы после сжатия картинки не слишком теряли качество. Вот небольшой список онлайн сервисов: 16 | 17 | + <https://tinypng.com> 18 | + <https://imagecompressor.com> 19 | + <https://compressor.io> 20 | + <https://imagify.io> 21 | + <https://kraken.io> 22 | 23 | # 2. Оптимизация JavaScript кода 24 | 25 | Некоторые советы, которые помогут оптимизировать работу скриптов. 26 | 27 | ## 2.1 Кеширование выборки и вычислений 28 | 29 | Кеширование позволяет сократить кол-во обращений к дом-узлам или сократить кол-во вычислений, сделав это всего один раз и записав в память. 30 | 31 | **неправильно** 32 | 33 | ```js 34 | let $header = $('.header'); 35 | let $headerNav = $('.header__nav'); 36 | let $headerItem = $('.header__item') 37 | 38 | function getItemTopPosition() { 39 | $headerItem.each((index, item) => { 40 | let itemTopPosition = $header.height() / 2 - $(item).offset().top; 41 | 42 | console.log(itemTopPosition); 43 | }); 44 | } 45 | ``` 46 | 47 | **правильно** 48 | 49 | ```js 50 | let $header = $('.header'); 51 | let $headerNav = $header.find('.header__nav'); 52 | let $headerItem = $headerNav.find('.header__item'); 53 | 54 | let headerHalfHeight = $header.height() / 2; 55 | 56 | function getItemTopPosition() { 57 | $headerItem.each((index, item) => { 58 | let itemTopPosition = headerHalfHeight - $(item).offset().top; 59 | 60 | console.log(itemTopPosition); 61 | }); 62 | } 63 | ``` 64 | 65 | ## 2.2 Чистка обработчиков событий 66 | 67 | В наших проектах мы часто используем роутер, поэтому создаем функции для инициализации страницы на которую переходим. В функции инициализации вешаются обработчики на элементы и часто их забывают отключать. Получается такая ситуация - перешли на страницу первый раз, повесились обработчики. Ушли со страницы и вернулись еще раз, произошла инициализация и обработчики повесились еще раз и т.д. В итоге при выполнении какого-то события оно будет отработано несколько раз. Для предотвращения такой ситуации можно сделать так: 68 | 69 | ```js 70 | function initPage() { 71 | $('.element') 72 | .off('.some-event') 73 | .on('click.some-event', () => { 74 | // do smth 75 | }); 76 | } 77 | ``` 78 | 79 | Или создать функцию сброса параметров после ухода со страницы, в которой можно не только сбрасывать обработчики но и обнулять какие-то вычисления. 80 | 81 | ```js 82 | let currentPage = null; 83 | let pageHeight = null; 84 | 85 | function initPage(page) { 86 | let $page = $(page); 87 | 88 | currentPage = $page; 89 | pageHeight = $page.height(); 90 | 91 | $page.on('scroll.page', () => { 92 | // do smth 93 | }); 94 | } 95 | 96 | function resetPage() { 97 | currentPage.off('.page'); 98 | 99 | currentPage = null; 100 | pageHeight = null; 101 | } 102 | ``` 103 | 104 | Ситуации бывают разные, поэтому необходимо следить за тем, чтобы обработчики не дублировались, иначе двойные, тройные … n-ые вычисления могут привести к значительному снижению производительности. 105 | 106 | ## 2.3 Использование throttle 107 | 108 | Существуют ситуации когда нам необходимо делать какие-то ресурсоемкие вычисления например на движение мыши или скролл. Для того, чтобы снизить нагрузку мы можем использовать **throttle** - функция, которая позволяет выполнить какое-то действие с задержкой в заданное кол-во времени. Подробнее можно почитать [здесь](https://learn.javascript.ru/task/throttle) или “покурить” гугл. Приведу небольшой пример. 109 | Здесь функция **getScrollPercentProgress** будет выполняться каждый раз при скролле как только браузер сможет обработать этот код. 110 | 111 | ```js 112 | function getScrollPercentProgress() { 113 | return (pageYOffset + innerHeight) / document.body.clientHeight * 100; 114 | } 115 | 116 | window.onscroll = () => { 117 | console.log(getScrollPercentProgress()); 118 | }; 119 | ``` 120 | 121 | Здесь же **getScrollPercentProgress** будет вызываться каждые 100мс, тем самым снижая кол-во выполнений. Также для уменьшения вычислений создаем сразу две переменные, которые будут хранить высоту окна и высоту документа. 122 | 123 | ```js 124 | let windowHeight = innerHeight; 125 | let documentHeight = document.body.clientHeight; 126 | 127 | function getScrollPercentProgress() { 128 | return (pageYOffset + windowHeight ) / documentHeight * 100; 129 | } 130 | 131 | window.onscroll = throttle(() => { 132 | console.log(getScrollPercentProgress()); 133 | }, 100); 134 | ``` 135 | 136 | ## 2.4 Использование debounce 137 | 138 | **Debounce** позволяет отложить вызов функции, пока не пройдет заданный промежуток времени с момента последнего вызова, например: 139 | 140 | В данном случае функция **rebuildSmth** будет выполняться непрерывно, пока мы меняем размер окна. 141 | 142 | ```js 143 | function rebuildSmth() { 144 | $('.elements').height(innerHeight); 145 | // здесь выполняется какой-то супер сложный код 146 | } 147 | 148 | window.onresize = rebuildSmth; 149 | ``` 150 | 151 | А здесь она выполнится один раз, через 500 мс с момента последнего изменения размера. 152 | 153 | ```js 154 | let windowHeight = innerHeight; 155 | let $elements = $('.elements'); 156 | 157 | function rebuildSmth() { 158 | $elements.height(innerHeight); 159 | // здесь выполняется какой-то супер сложный код 160 | } 161 | 162 | window.onresize = debounce(rebuildSmth, 500); 163 | ``` 164 | 165 | # 3. Оптимизация CSS-кода 166 | 167 | Касаемо производительности css все намного проще. Главное, что стоит запомнить - браузер читает селекторы справа налево и порядок селекторов по производительности (от более производительных к менее): 168 | 169 | 1. идентификатор (#block) 170 | 2. класс (.block) 171 | 3. тип (div) 172 | 4. сосед по уровню (h1 + p) 173 | 5. дочерний элемент (div > ul) 174 | 6. вложенный элемент (div a) 175 | 7. общий селектор (*) 176 | 8. атрибут ([type=”button”]) 177 | 9. псевдоклассы/псевдоэлементы (a:hover) 178 | 179 | Выбор элементов в css влияет на производительность, в том числе, на то, как быстро отображается страница. Однако в реальном использовании разница совсем невелика и большого прироста производительности можно не ожидать. 180 | Используйте простые css-селекторы, меньше вложений и короче цепочки. Идеальный вариант - прямые селекторы только по классу (.element), благо БЭМ это позволяет. Подобное правильно также хорошо подходит для выбора дом-узла в js. 181 | 182 | **плохо** 183 | 184 | ```css 185 | div ul li[data-type="link"] a { 186 | color: red; 187 | } 188 | 189 | div.article { 190 | display: block; 191 | } 192 | 193 | .article li a { 194 | text-decoration: none; 195 | } 196 | 197 | .article:last-child ul * { 198 | background: none; 199 | } 200 | ``` 201 | 202 | **хорошо** 203 | 204 | ```css 205 | .link { 206 | color: red; 207 | } 208 | 209 | .article { 210 | display: block; 211 | } 212 | 213 | .article__link { 214 | text-decoration: none; 215 | } 216 | 217 | .article__item { 218 | background: none; 219 | } 220 | ``` 221 | 222 | # 4. Оптимизация анимации 223 | 224 | ## 4.1 Не анимируйте сложные свойства 225 | 226 | На данный момент браузеры хорошо оптимизируют анимацию свойств **opacity** и **transform**. Анимирование остальных свойств лучше избегать или стараться использовать по минимуму, особенно это касается мобильных устройств. 227 | 228 | **плохо** 229 | 230 | ```css 231 | .menu { 232 | position: fixed; 233 | left: -100%; 234 | top: 0; 235 | background: transparent; 236 | transition: all 0.3s; 237 | } 238 | 239 | .menu.is-opened { 240 | left: 0; 241 | box-shadow: 10px 0 30px rgba(255, 255, 255, 0.3); 242 | background: #fff; 243 | } 244 | ``` 245 | 246 | **хорошо** 247 | 248 | ```css 249 | .menu { 250 | position: fixed; 251 | left: 0; 252 | top: 0; 253 | z-index: 1; 254 | transform: translate3d(-100%, 0, 0); 255 | transition: transform 0.3s; 256 | } 257 | 258 | .menu::before { 259 | content: ""; 260 | position: absolute; 261 | left: 0; 262 | top: 0; 263 | z-index: -1; 264 | display: block; 265 | width: 100%; 266 | height: 100%; 267 | box-shadow: 10px 0 30px rgba(0, 0, 0, 0.3); 268 | background: #fff; 269 | opacity: 0; 270 | transition: opacity 0.3s; 271 | } 272 | 273 | .menu.is-opened { 274 | transform: none; 275 | } 276 | 277 | .menu.is-opened::before { 278 | opacity: 1; 279 | } 280 | ``` 281 | 282 | Объем кода получается больше, однако производительность, особенно на мобильных и слабых устройствах, будет заметна. Также обратите внимание на свойство transition, всегда указывайте какие свойства элемента вам нужны для анимации, чтобы не анимировать сразу все. 283 | 284 | ## 4.2 Используйте requestAnimationFrame вместо setInterval 285 | 286 | **RequestAnimationFrame (RAF)** лучше справляется с анимацией, чем интервал. Он хорошо оптимизирован браузерами и лучше экономит ресурсы, что немаловажно для мобильных и слабых устройств. 287 | 288 | **плохо** 289 | 290 | ```js 291 | let intervalId = setInterval(() => { 292 | // код вашей анимации 293 | 294 | if (i > 1000) { 295 | clearInterval(intervalId); 296 | } 297 | }, 1000 / 60); 298 | ``` 299 | 300 | **хорошо** 301 | 302 | ```js 303 | let rafId; 304 | 305 | function animate() { 306 | rafId = requestAnimationFrame(animate); 307 | 308 | // код вашей анимации 309 | 310 | if (i > 1000) { 311 | cancelAnimationFrame(rafId); 312 | } 313 | } 314 | 315 | requestAnimationFrame(animate); 316 | ``` 317 | 318 | Поворот по оси Z помогает убрать лишние дергания анимации, особенно заметно в IE и Edge при анимации scale. 319 | 320 | # 5. Другие решения 321 | 322 | ## 5.1 Lazy loading (ленивая загрузка) 323 | 324 | В интернете много пояснений о том, что такое ленивая загрузка и как ее реализовать, если коротко - это способ загрузки данных по мере надобности. 325 | 326 | Например в нашем случае, когда мы разрабатываем SPA, содержимое сайта загружается все и сразу т.е. мы ждем пока загрузятся все картинки, видео, фреймы и т.п. (со всех страниц), а их может быть очень много и вкупе они имеют большой вес. Благодаря ленивой загрузке можно сделать так, чтобы изначально загружались ресурсы только одной страницы или только те, которые пользователь увидит изначально на экране (плюс взять небольшой запас). Далее пользователь переходит по страницам, открывает попапы, переходит по секциям и т.д., и по мере надобности подгружаются недостающие ресурсы. Т.е. смысл в том, что мы не отдаем пользователю сразу все, а только то, что ему необходимо на данный момент. 327 | -------------------------------------------------------------------------------- /24_git.md: -------------------------------------------------------------------------------- 1 | # Работа с Git 2 | 3 | Для работы с репозиториями мы используем [Bitbucket](https://bitbucket.org). 4 | 5 | ## Работа с ветками 6 | 7 | * master - основная ветка 8 | * production - ветка для боевого сайта 9 | 10 | Для работы каждый разработчик должен создавать свою ветку с именем **dev-[Ваше имя]**, например dev-vasya. 11 | 12 | Т.к. у нас настроен автодеплой, то после выполнения очередной задачи свою ветку необходимо сливать с веткой **master**, чтобы изменения попали на тестовый сервер. Пушить в ветку **master** нельзя, для объединения необходимо выполнить **pull request (далее PR)** из вашей ветки в ветку мастер. PR в мастер может принимать любой разработчик. 13 | 14 | Для отправки наработок на боевой сайт (продакшн), необходимо сделать PR в ветку **production**. В данном случае запрос на объединение сможет принять только администратор репозитория. 15 | 16 | Для новичков делать PR в мастер необходимо минимум 3 раза в день. Для этого можно разбить основную задачу на 3 логические части. В остальных случаях PR нужно делать по мере необходимости или выполнения задачи. 17 | 18 | **Обязательно!** Перед началом работы выполняйте **git pull**, чтобы стянуть последние наработки по проекту. 19 | 20 | ## Коммиты 21 | 22 | К коммитам строгих требований нет. Сообщения коммитов должны быть корткие и доносить основной смысл выполненного объема работ. Желательно, чтобы сообщение коммита было на английском языке. 23 | 24 | ## Конфликты 25 | 26 | При возникновении конфликтов обязательно свяжитесь с разработчиком, который вносил изменения, чтобы вместе решить конфликты и не затереть актуальные наработки. 27 | 28 | Если такой возможности нет, внимательно изучите конфликты и саму задачу, чтобы понять какая часть является актуальной. После разрешения конфликтов и отправки наработок в ветку, необходимо сообщить в чат проекта, менеджеру о произошедшем и попросить перепроверить актуальность изменений. 29 | -------------------------------------------------------------------------------- /25_checklist.md: -------------------------------------------------------------------------------- 1 | ## Проверка переходов по ссылкам с передачей GET-параметров 2 | 3 | GET-параметр - параметр, который передается серверу при помощи URL. Данные параметры находятся в URL сразу после знака вопроса `?` и состоят из пары - `ключ = значение`. Несколько GET-параметров разделяются между собой знаком амперсанда `&`. 4 | 5 | Пример ссылки с GET-параметрами: `https://site.com?param1=value1¶m2=value2`. Здесь можно выделить два параметра - `param1`, который имеет значение `value1` и `param2`, который имеет значение `value2`. 6 | 7 | При проверке переходов необходимо подставить любой GET-параметр (или несколько) в URL сайта, произвести переход по полученной ссылке и посмотреть не пропадут ли параметры из URL. 8 | Например есть ссылка на сайт - `site.com`, добавляем к ней GET-параметры - `?test_param=value`, в результате получим подобную ссылку - `site.com?test_param=value`, производим переход и смотрим не изменился ли URL. 9 | 10 | Часто, причиной пропажи параметров является манипуляция URL, в каком-то из участков кода. Например меняется адрес при входе на страницу при помощи `window.location`, `history.pushState`, `history.replaceState` и т.п. В таком случае необходимо обязательно предусмотреть возможность сохранения передаваемых параметров. 11 | 12 | Также, если на сайте используются якоря `(site.com#section)`, необходимо проверять их работоспособность с передачей параметров `(site.com?test_param=value#section)`. 13 | 14 | ## Проверка на соответствие макету 15 | 16 | Результат верстки должен соответствовать макету. 17 | 18 | Проверка осуществляется с помощью расширения [PerfectPixel](https://chrome.google.com/webstore/detail/perfectpixel-by-welldonec/dkaagdgjmgdmbnecmcefdhjekcoceebi?hl=ru), в браузере Chrome под ОС Windows, Linux или Mac. 19 | 20 | Допустимы незначительные отличия, связанные с: 21 | - различием в рендеринге шрифтов 22 | - ошибками в макете (различные отступы или размеры у однотипных элементов, погрешности в цветах) 23 | - заменой контента (текст, изображения, видео) 24 | 25 | Иные отличия недопустимы. 26 | 27 | Если верстка по тем или иным причинам расходится с макетом, то об этом следует сообщить менеджеру проекта. 28 | 29 | ## Проверка кода линтером 30 | 31 | Следует проверять код линтером. 32 | 33 | Если при проверке кода линтером выявлены ошибки, то их следует исправить, либо сообщить об этом разработчику, ответственному за данный код. 34 | 35 | Если при работе с линтером появляются подозрения на некорреткную настройку или баги, то об этом следует сообщить разработчику, ответственному за настройку линтера ([@beliarh](https://github.com/beliarh)). 36 | 37 | ## Проверка кода валидатором 38 | 39 | Следует проверять код [валидатором](https://validator.w3.org/unicorn/). 40 | 41 | ## Проверка фавиконки 42 | 43 | На сайте должна быть фавиконка. 44 | 45 | При отсутствии фавиконки следует запросить ее у менеджера проекта. 46 | 47 | ## Шрифты 48 | 49 | На сайте должны использоваться корректные шрифты. 50 | 51 | Обязательный формат - `woff`. 52 | Опциональный формат - `woff2`. 53 | 54 | Недопустимо использовать `woff2`, без использования `woff`. 55 | 56 | Допустимо использовать только `woff`. 57 | 58 | Крайне желательно использовать и `woff` и `woff2`. 59 | 60 | В используемом шрифте не должно быть битых символов. 61 | 62 | ## Проверка на типографирование 63 | 64 | Текст на сайте должен быть типографирован, т.е. прогнан через [Типограф](https://www.artlebedev.ru/typograf/). Если в тексте присутствуют лишние и битые символы, от них необходимо избавиться. 65 | 66 | ## Проверка выделения текста 67 | 68 | Содержимое текстовых блоков должно корректно выделяться. 69 | 70 | ## Проверка иконок и изображений 71 | 72 | Иконки на сайте должны быть сделаны с помощью SVG, либо PNG + @2x PNG (если иконка имеет сложные растровые эффекты, SVG отсутствует или невозможно экспортировать иконку в формате SVG). 73 | 74 | Для контентных изображений должна быть указана @2x-версия. 75 | 76 | SVG-иконки должны иметь внутренние отступы, чтобы избежать обрезанных краев в разных браузерах. 77 | 78 | ## Проверка интерактивных элементов 79 | 80 | Следует проверять корректность работы интерактивных элементов: 81 | 82 | * Слайдеры 83 | * Всплывающие окна 84 | * Аудио и видеоплееры 85 | * Табы 86 | * Тесты и опросы 87 | * Элементы форм 88 | * Валидация полей 89 | * Отправка форм 90 | * Прочие элементы, обладающие сложной логикой и неуказанные в данном списке 91 | 92 | ## Проверка свайпа и drag'n'drop 93 | 94 | Если в каком либо блоке сайта допустимо и логично реагировать на свайп или drag'n'drop, и это поведение не реализовано, то об этом следует сообщить менеджеру проекта и разработчику. 95 | 96 | Свайп можно использоват в мобильной и планшетной версии для перехода между слайдами или для постраничной навигации (если таковая имеется). 97 | 98 | Drag'n'drop можно использовать в слайдерах и элементах, поведение которых предполагает (или допускает) перетаскивание. 99 | 100 | ## Проверка навигации по сайту с помощью клавиатуры 101 | 102 | На сайте должна корректно работать навигация с помощью клавиатуры. В частности следует проверять работу следующих клавиш (и комбинаций клавиш): 103 | 104 | * Стрелки влево, вправо, вверх и вниз 105 | * Tab 106 | * Shift + Tab 107 | * Enter 108 | * Esc 109 | * Пробел 110 | * Shift + Пробел 111 | * Page Up 112 | * Page Down 113 | * Home 114 | * End 115 | 116 | ## Проверка кнопок 117 | 118 | Кнопки, клик по которым не ведет на другую страницу, а лишь выполняет какое-либо действие, должны быть сделаны тегом `<button>`. 119 | 120 | У тега `<button>` должен быть указан атрибут `type`. 121 | 122 | ## Проверка ссылок 123 | 124 | Ссылки - интерактивные элементы, при клике на которые происходит переход на другую страницу или внешний ресурс. 125 | 126 | Ссылки-якоря - интерактивные элементы, при клике на которые происходит скролл к нужному месту страницы. 127 | 128 | Все ссылки должны быть сделаны тегом `<a>`. 129 | 130 | У всех ссылок должен быть указан атрибут `href`. 131 | 132 | У внутренних ссылок атрибут `href` должен начинаться с `/`. 133 | 134 | У внешних ссылок должен быть указан атрибут `target="_blank"`. 135 | 136 | У ссылок-якорей в атрибуте `href` должен быть указан хеш. 137 | 138 | Ссылки должны быть корректными и вести на соответствующие страницы. 139 | 140 | ## Проверка кликабельной области элементов 141 | 142 | Если у кнопки или отдельностоящей ссылки нет явно ограниченной кликабельной области, то за такую следует принять размер элемента плюс поля 5-15px. 143 | 144 | Кликабельная область должна иметь размер минимум 20-30px по высоте и ширине (если нет явной границы). 145 | 146 | Содержимое кнопки или ссылки должно быть по центру кликабельной области. 147 | 148 | Для ссылок в тексте размер кликабельной области не контролируется. 149 | 150 | ## Проверка анимаций 151 | 152 | Любая анимация на сайте (переходы, смена слайдов, ховеры) должна работать плавно, без рывков и мерцаний. 153 | 154 | Не должно быть зависаний или долгих пауз во время анимации. 155 | 156 | Анимация однотипных элементов должна быть одинаковой на всех блоках и страницах. 157 | 158 | На всех интерактивных элементах должен быть плавный ховер. 159 | 160 | ## Проверка верстки стресстестом 161 | 162 | **TODO** 163 | 164 | ## Проверка сайта с включенным блокировщиком рекламы 165 | 166 | Сайт должен проверяться с включенным блокировщиком рекламы. 167 | 168 | Элементы сайта не должны блокироваться. 169 | 170 | ## Проверка переходов на страницы по прямой ссылке 171 | 172 | Следует проверять корректность загрузки всех страниц сайта. 173 | 174 | Также следует проверять корректность загрузки страницы с дополнительными GET-параметрами и хешем. 175 | Заданные GET-параметры и хеш при этом не должны пропадать. 176 | 177 | ## Проверка переходов между страницами 178 | 179 | Следует проверять корректность перехода между всеми страницами сайта. 180 | 181 | Например, если на сайте несколько страниц: 182 | 183 | * `/a` 184 | * `/b` 185 | * `/c` 186 | 187 | То следует проверять переходы: 188 | 189 | * `/a` => `/a` 190 | * `/a` => `/b` 191 | * `/a` => `/c` 192 | * `/b` => `/a` 193 | * `/b` => `/b` 194 | * `/b` => `/c` 195 | * `/c` => `/a` 196 | * `/c` => `/b` 197 | * `/c` => `/c` 198 | 199 | Если какой-либо переход невозможен, то его следует игнорировать. 200 | 201 | Также следует проверять корректность перехода по несуществующему адресу. 202 | На сайте должна быть предусмотрена страница с 404 ошибкой, либо должен срабатывать редирект на главную. 203 | 204 | ## Проверка навигации с помощью истории браузера 205 | 206 | На сайте должны корректно работать переходы между страницами с помощью истории браузера (стрелки влево и вправо рядом с адресной строкой). 207 | 208 | ## Кроссбраузерность и кроссплатформенность 209 | 210 | На проектах поддерживаются последние версии браузеров (если не указано иного). 211 | 212 | Сайт следует проверять в следующих системах: 213 | 214 | * Windows 215 | * Chrome 216 | * Firefox 217 | * Edge 218 | * IE 11 219 | * Yandex (русскоязычный сегмент) 220 | * Opera (русскоязычный сегмент) 221 | * macOS 222 | * Safari 223 | * Chrome 224 | * Android 225 | * Chrome 226 | * iOS 227 | * Safari 228 | * Chrome 229 | 230 | ## Проверка адаптивности (десктоп) 231 | 232 | Основные размеры мониторов: 233 | 234 | * 2560x1440 235 | * 2560x1080 236 | * 1920x1200 237 | * 1920x1080 238 | * 1650x1050 239 | * 1600x900 240 | * 1440x900 241 | * 1366x768 242 | * 1280x1024 243 | * 1280x720 244 | 245 | При проверке адаптивности следует проверять не только указанные размеры, но также и промежуточные. 246 | Причина в том, что размер окна сайта не равен разрешению монитора (за исключением полноэкранного режима). 247 | Часть пространства занимает системная панель, часть - верхняя панель браузера. 248 | К тому же иногда пользователи открывают окно браузера не на весь экран. 249 | 250 | Проверка осуществляется в панели разработчика в режиме `Responsive` и начинается с наибольшего экрана (2560x1440). 251 | 252 | Минимальные размеры до которых стоит проверять адаптивность десктопной версии - 1025px по ширине, и 550px по высоте. 253 | 254 | Проверка адаптивности проходит по следующему алгоритму: 255 | 256 | 1. Задается размер окна 2560x1440. 257 | 2. Путем перетаскивания и постепенного уменьшения высоты (до минимальной - 550px) проверяется корректность отображения сайта. 258 | 3. Далее ширина окна немного уменьшается (на 25-50px). 259 | 4. Пункты 2 и 3 повторяются до тех пор, пока ширина окна не достигнет минимальной - 1025px. 260 | 261 | ## Проверка адаптивности (планшеты и мобильные устройства) 262 | 263 | Для начала сайт нужно проверить в мобильном эмуляторе браузера. 264 | 265 | Как и в случае с десктопной версией проверяются не только размеры, соответствующие разрешению экрана, но также и промежуточные. 266 | 267 | Минимальная ширина экрана до которой следует проверять адаптивность - 320px. 268 | 269 | Проверять следует как портретную, так и альбомную ориентацию. 270 | 271 | В альбомной ориентации размер элементов не должен сильно отличаться от портретной. 272 | Не должно быть такого, что в альбомной ориентации элементы слишком мелкие или большие. 273 | 274 | Обязательно проверять сайт на реальных устройствах (либо в [BrowserStack](https://www.browserstack.com/)): 275 | 276 | * Android 277 | * Любое мобильное устройство и планшет с актуальной версией системы (6+) 278 | * Chrome 279 | * iOS 280 | * iPhone SE/7/8/X и iPad Mini/Air/Pro 281 | * Safari 282 | * Chrome 283 | 284 | При проверке сайта на реальных устройствах страница должна корректно отображаться и скроллиться. 285 | 286 | ## Проверка работоспособности сайта при ресайзе 287 | 288 | Необходимо проверять и дорабатывать логику сайта при ресайзе, чтобы сайт при изменении размеров окна не ломался. 289 | 290 | Например на сайте имеется слайдер, который отображается на мобильной версии, но при этом на десктопе отсутствует, либо отключен. В данном случае необходимо предусмотреть логику создания / удаления (отключения) слайдера при переходе с мобильной версии на десктоп и обратно, по ресайзу, без перезагрузки сайта. 291 | В качестве дополнительных примеров можно взять уникальные случаи - `"кирпичная" сетка, параллакс элементов, анимация движения элементов по скроллу` и т.д. Подобные вещи часто "ломаются" именно при изменении размеров окна, поэтому нужно не забывать предусматривать корректировку логики по ресайзу. 292 | 293 | Для чего это необходимо: 294 | * исключается некорректное отображение сайта при изменении ориентации на устройствах 295 | * исключается некорректное отображение сайта при масштабировании или случайном изменении размеров окна браузера 296 | * удобство при тестировании, нет необходимости перезагружать сайт 297 | 298 | Для оптимизации нагрузки, всю логику выполнения по ресайзу можно делать с задержкой. Например изменился размер окна, а все корректировки логики выполнились только спустя 1 секунду. В этом могут помочь техники `throttling` и `debouncing`. 299 | 300 | Также необходимо следить за тем, чтобы ресайз не препятствовал отображению сайта, например на мобильных устройствах при скролле скрываются / открываются навигационные панели браузера, это вызывает событие ресайза. В этом случае на сайте могут происходить корректировки размеров, положения и т.д. чего-либо, однако, это может быть лишним т.к. данные корретировки необходимы будут только при изменении ширины окна, а не высоты. 301 | 302 | ## Проверка метатегов 303 | 304 | На сайте должны быть указаны метатеги (`title`, `description`, `image`). 305 | 306 | Если сайт многостраничный, то метатеги должны быть указаны на каждой странице. 307 | 308 | Если сайт одностраничный и использует `shareSettings.php` для шаринга, то метатеги должны быть указаны именно в этом файле. При этом необходимо подставить переменные в pug-файле. 309 | 310 | ## Проверка шеринга 311 | 312 | На сайте должен работать шеринг (при наличии). 313 | 314 | Если сайт многостраничный, то должна шариться ссылка на страницу с основными параметрами (при наличии таковых). 315 | 316 | Если сайт многостраничный, то должна шариться ссылка на `share.php` с требуемыми параметрами. 317 | 318 | При шаринге в ссылку не должны попасть лишние параметры (например, UTM-метки). 319 | 320 | Проверка и отладка шаринга осуществляется с помощью следующих инструментов: 321 | 322 | * https://developers.facebook.com/tools/debug/ 323 | * https://cards-dev.twitter.com/validator 324 | * https://vk.com/dev/pages.clearCache 325 | * https://search.google.com/structured-data/testing-tool 326 | * https://telegram.me/webpagebot 327 | 328 | При шаринге сайта в соц. сети должны корректно отобразиться: 329 | 330 | * Заголовок (соответствующий заданному метатегу, необрезанный, без искажений кодировки и битых символов) 331 | * Описание (соответствующее заданному метатегу, необрезанное, без искажений кодировки и битых символов) 332 | * Изображение (соответствующее заданному метатегу) 333 | 334 | Ссылка шаринга должна вести на ту же страницу, которую шарили, либо на страницу, соответствующую заданной логике. 335 | В случае использования `share.php` ссылка должна перенаправлять на нужную страницу. 336 | 337 | Если расшариваемая страница недоступна по прямой ссылке (т.е. переход на нее происходит по внутренней логике сайта), то следует шарить главную страницу. 338 | 339 | ## Проверка аналитики 340 | 341 | Для проверки аналитики используется расширение [Google Analytics Debugger](https://chrome.google.com/webstore/detail/google-analytics-debugger/jnkmfdileelhofjcijamephohjechhna). 342 | 343 | После установки в панели расширений появится иконка GA Debug (в виде письма). 344 | 345 | На проверяемом сайте следует открыть панель разработчика и включить GA Debug. На иконке появится надпись `ON`. 346 | 347 | После чего страница перезагрузится и в консоли появится множество сообщений от расширения. 348 | 349 | Избавиться от лишних сообщений можно, если вписать в поле `Filter` консоли значение `Running command` (все сообщения от данного расширения помещается данным префиксом). 350 | При фильтрации сообщений консоли следует быть внимательным, так как фильтруются абсолютно все сообщения, в том числе ошибки и собственные вызовы `console.log`. 351 | 352 | После этого следует поочередно проверить указанные в ТЗ события. 353 | 354 | ## Проверка контента 355 | 356 | На сайте должен быть актуальный контент: 357 | 358 | * Текст 359 | * Ссылки 360 | * Изображения 361 | * Видео 362 | * Аудио 363 | 364 | Запросить актуальный контент можно у менеджера проекта. 365 | 366 | При наличии ошибок в тексте (орфографических, пунктуационных, логических) следует сообщить об этом менеджеру проекта. 367 | 368 | При наличии недоступных ресурсов (недоступные изображения, видео или аудио, 404 ошибки) следует сообщить об этом менеджеру проекта. 369 | 370 | При выявлении несоответствия контента (перепутан текст, изображение, видео или аудио) следует сообщить об этом менеджеру проекта. 371 | 372 | ## Проверка размера загружаемых ресурсов 373 | 374 | На сайте не должно быть чрезмерно больших файлов. 375 | 376 | Размер изображений должен соответствовать размеру на сайте (например, для элемента размером 300x300 не должно использоваться изображение размером 2000x2000). 377 | 378 | Не должно быть мегабайтных JPG. Такие файлы следует оптимизировать вручную с ограничением максимального качества. 379 | 380 | Размер видео не должен быть больше 100 мегабайт (за исключением продолжительных видео и адаптивного стриминга). 381 | 382 | ## Проверка производительности сайта 383 | 384 | **TODO** 385 | -------------------------------------------------------------------------------- /26_short-checklist.md: -------------------------------------------------------------------------------- 1 | 1. Переходы по ссылкам с передачей GET-параметров. 2 | 2. Соответствие макету. 3 | 3. Проверка кода линтером. 4 | 4. Проверка всех страниц сайта HTML-валидатором (https://html5.validator.nu/). 5 | 5. Имеется фавиконка. 6 | 6. Корректные шрифты (woff + woff2). 7 | 7. Типографированный текст. Отсутствуют битые символы. 8 | 8. Текст на сайте корректно выделяется. 9 | 9. Корректные svg-иконки. 10 | 10. Интерактивные элементы работают корректно (слайдеры, всплывающие окна, аудио и видеоплееры, табы, тесты, опросы, формы, выпадающие списки и прочее). 11 | 11. В формах реализована валидация полей. 12 | 12. Если на сайте присутствует кастомный скролл, то навигация с помощью клавиатуры должна работать корректно (Space, Shift + Space, Page Up, Page Down, Home, End, стрелки) 13 | 13. Кликабельные элементы, не ведущие на другие страницы, сделаны кнопками (`<button type="button">`). 14 | 14. Кликабельные элементы, ведущие на другие страницы (за исключением кнопок для отправки форм), сделаны ссылками с корректным href (`<a>`). 15 | 15. У всех кликабельных элементов достаточная кликабельная область. 16 | 16. Анимации на сайте работают корректно, плавно, без задержек и подвисаний. 17 | 17. На всех интерактивных элементах должен быть ховер. 18 | 18. Стресстест верстки (в местах с динамическим контентом прописать много текста с очень длинными словами и проверить не ломается ли верстка, также проверить не ломается ли при малом количестве контента). 19 | 19. Сайт не ломается блокировщиком рекламы. 20 | 20. Страницы открываются по прямой ссылке (для SPA). 21 | 21. Переходы между страницами работают корректно (для SPA). 22 | 22. Навигация с помощью истории браузера работает корректно (для SPA). 23 | 23. Проверка сайта в Windows + Chrome. 24 | 24. Проверка сайта в Windows + Firefox. 25 | 25. Проверка сайта в Windows + Edge. 26 | 26. Проверка сайта в Windows + IE 11. 27 | 27. Проверка сайта в Windows + Yandex. (русскоязычный сегмент) 28 | 28. Проверка сайта в Windows + Opera. (русскоязычный сегмент) 29 | 29. Проверка сайта в macOS + Chrome. 30 | 30. Проверка сайта в macOS + Safari. 31 | 31. Проверка сайта в Android + Chrome (планшет). 32 | 32. Проверка сайта в iOS + Safari (планшет). 33 | 33. Проверка сайта в Android + Chrome (мобильное устройство). 34 | 34. Проверка сайта в iOS + Safari (мобильное устройство). 35 | 35. Сайт корректно адаптируется в десктопной версии (от 1025x550 до 2560x1440). 36 | 36. Сайт корректно адаптируется в мобильной и планшетной версии (от 320 до 1024). 37 | 37. Сайт корректно отображается в альбомной ориентации (мобильная и планшетная версия). 38 | 38. Сайт не ломается при ресайзе окна. По нeoбxoдимocти происходит переинициализация логики сайта при смене десктоп - мобайл и наоборт (мобайл - десктоп). 39 | 39. Указаны метатеги title, description и image (при наличии). 40 | 40. Корректная работа шеринга. Сброс кэша (если нужно): 41 | - https://developers.facebook.com/tools/debug/ 42 | - https://cards-dev.twitter.com/validator 43 | - https://vk.com/dev/pages.clearCache 44 | - https://search.google.com/structured-data/testing-tool 45 | - https://telegram.me/webpagebot 46 | 41. Корректная работа аналитики. 47 | 42. Актуальный контент (текста, ссылки, картинки, видео, аудио). 48 | -------------------------------------------------------------------------------- /27_validation.md: -------------------------------------------------------------------------------- 1 | # Базовая валидация форм 2 | 3 | Рассмотрим требования только к часто встречающимся полям. Валидация остальных полей выполняется индивидуально. 4 | 5 | ### поле для ввода ФИО или только имени: 6 | 7 | * ограничение минимального кол-ва символов 8 | * ограничение максимального кол-ва символов 9 | * ввод только букв и пробелов 10 | * удаление пробелов в начале и конце строки перед отправкой 11 | 12 | ### поле для ввода email: 13 | 14 | * удаление пробелов в начале и конце строки перед отправкой 15 | * маска ввода 16 | 17 | ```regex 18 | ^([a-z0-9_-]+\.)*[a-z0-9_-]+@[a-z0-9_-]+(\.[a-z0-9_-]+)*\.[a-z]{2,6}$ 19 | ``` 20 | 21 | ```regex 22 | ^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.[A-Za-z] 23 | ``` 24 | 25 | ### поле для ввода телефона: 26 | 27 | * ввод только цифр и некоторых символов (плюс, пробел, тире, круглые скобки) 28 | * удаление пробелов в начале и конце строки перед отправкой 29 | * маска ввода 30 | 31 | ```regex 32 | ^((8|\+7)[\- ]?)?(\(?\d{3}\)?[\- ]?)?[\d\- ]{7,10}$ 33 | ``` 34 | 35 | ```regex 36 | /^(\s*)?(\+)?([- _():=+]?\d[- _():=+]?){10,14}(\s*)?$/ 37 | ``` 38 | 39 | --- 40 | 41 | Требования к этим полям также могут меняться на разных проектах. 42 | 43 | Регулярные выражения даны для примера, вы можете использовать свои. 44 | -------------------------------------------------------------------------------- /28_dynamic-share-for-spa.md: -------------------------------------------------------------------------------- 1 | # Настройка динамических шерингов для SPA 2 | 3 | С недавнего времени в сборку был добавлен новый файл `shareSettings.php`, в папку `resources`. В нем необходимо указывать все данные по шерингам. Во избежание ошибок при сборке, данный файл нельзя удалять из проекта. Он автоматически удаляется в конечном билде. 4 | 5 | Теперь нет необходимости добавлять логику с корректировкой URL для `share.js`, создавать отдельный файл `share.php` и в нескольких местах менять данные шеринга. Достаточно один раз прописать все данные в `shareSettings.php`. 6 | 7 | ### базовый код файла shareSettings.php: 8 | 9 | ```php 10 | $protocol = $_SERVER['PROTOCOL'] = isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) ? 'https' : 'http'; 11 | $host = $protocol . '://' . $_SERVER['HTTP_HOST']; 12 | $title = ''; 13 | $description = ''; 14 | $image = $host . '/images/'; 15 | 16 | // Uncomment the code below and fill in the pages if necessary 17 | // $pages = [ 18 | // '/page/1' => [ 19 | // 'title' => '', 20 | // 'description' => '', 21 | // 'image' => '/images/', 22 | // ], 23 | // ]; 24 | 25 | $page = @$pages[$_SERVER['REQUEST_URI']]; 26 | 27 | if ($page) { 28 | $title = !is_null(@$page['title']) ? $page['title'] : $title; 29 | $description = !is_null(@$page['description']) ? $page['description'] : $description; 30 | $image = !is_null(@$page['image']) ? $host . $page['image'] : $image; 31 | } 32 | ``` 33 | 34 | ### пример кода с указанными данными для шеринга: 35 | 36 | ```php 37 | $protocol = $_SERVER['PROTOCOL'] = isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) ? 'https' : 'http'; 38 | $host = $protocol . '://' . $_SERVER['HTTP_HOST']; 39 | $title = 'Базовый заголовок страницы'; 40 | $description = 'Базовое описание страницы'; 41 | $image = $host . '/images/share/main.jpg'; 42 | 43 | $pages = [ 44 | '/article' => [ 45 | 'title' => 'Заголовок статьи', 46 | 'description' => 'Описание статьи', 47 | 'image' => '/images/share/article.jpg', 48 | ], 49 | '/test' => [ 50 | 'title' => 'Заголовок теста', 51 | 'description' => 'Описание теста', 52 | 'image' => '/images/share/test.jpg', 53 | ], 54 | '/test?result=1' => [ 55 | 'title' => 'Заголовок результатов теста №1', 56 | 'description' => 'Описание результатов теста №1', 57 | 'image' => '/images/share/test.jpg', 58 | ], 59 | '/test?result=2' => [ 60 | 'title' => 'Заголовок результатов теста №2', 61 | 'description' => 'Описание результатов теста №2', 62 | 'image' => '/images/share/test.jpg', 63 | ], 64 | '/product' => [ 65 | 'title' => 'Заголовок продуктовой страницы', 66 | 'description' => 'Описание продуктовой страницы', 67 | 'image' => '/images/share/product.jpg', 68 | ], 69 | ]; 70 | 71 | $page = @$pages[$_SERVER['REQUEST_URI']]; 72 | 73 | if ($page) { 74 | $title = !is_null(@$page['title']) ? $page['title'] : $title; 75 | $description = !is_null(@$page['description']) ? $page['description'] : $description; 76 | $image = !is_null(@$page['image']) ? $host . $page['image'] : $image; 77 | } 78 | ``` 79 | 80 | --- 81 | 82 | Чтобы все данные из этого файла попали в разметку страницы при входе, необходимо обязательно внести небольшие изменения в корневой файл `index.pug`. 83 | 84 | ### пример кода для index.pug: 85 | 86 | ```jade 87 | extends pug/base 88 | 89 | prepend vars 90 | - title = '<?= htmlspecialchars($title) ?>' 91 | - description = '<?= htmlspecialchars($description) ?>' 92 | - image = '<?= htmlspecialchars($image) ?>' 93 | 94 | append vars 95 | //- ... 96 | 97 | block content 98 | //- ... 99 | ``` 100 | 101 | Теперь на локальном сервере во вкладке будет отображаться подобное - `<?= htmlspecialchars($title) ?>`. Не стоит пугаться, корректный заголовок (а также description и image) будут работать на тестовом, а также боевом серверах. 102 | 103 | Это происходит по той причине, что `index.html`, который создается в результате обычного билда, просто не умеет читать php-код. А на сервер вместо `index.html` будет добавляться `index.php`, который прекрасно распознает все эти переменные. 104 | 105 | --- 106 | 107 | Также в папке `resources` теперь хранится файл `.htaccess`, который также нельзя удалять и для него тоже настроено автоудаление, если сборка не запущена в режиме SPA. Этот файл содержит настройки для корректной работы сайта в режиме SPA. 108 | -------------------------------------------------------------------------------- /29_helpers.md: -------------------------------------------------------------------------------- 1 | # Всппомогательные функции 2 | 3 | Для облегчения некоторой части базовых настроек в сборку добавленны вспомогательные функций для js и scss. 4 | 5 | Их использование не обяхательно, но оно облегчит некоторую часть работы. 6 | 7 | ## Вспомогательные функции PUG 8 | 9 | #### Миксины `lazyElements` 10 | 11 | 12 | #### Миксин `svg` 13 | 14 | Подгрузка свг элемента из файла спрайтов 15 | 16 | ## Вспомогательные функции JS 17 | 18 | - `lastPageYOffset` - содержит позицию скролла после использования `saveScrollPosition`. 19 | 20 | - `debounce` - полезен для функций, которые получают/обновляют данные, и мы знаем, что повторный вызов в течение короткого промежутка времени не даст ничего нового, синтаксис похож на `setTimeout`. 21 | 22 | - `saveScrollPosition` - полезен при открытии модальных окон, используется для сохранения текущей позиции горизонтального скролла. 23 | 24 | - `restoreScrollPosition` - полезен при закрытии модальных окон, используется для сброса сохраненной позиции горизонтального скролла. 25 | 26 | - `scrollTo` - плавный скролл до элемента, принимает 3 параметра, элемент до которого нужно скроллить / время анимации / смещение (+/-). 27 | 28 | - `getScrollbarWidth` - полез при открытии модальных окон, чтобы контент не прыгал, возвращает ширину скролл-бара. 29 | 30 | - `hasHoverSupport` - определяет доступность ховера, на различных устройствах, помогает избежать ховеров на мобилках и планшетах. 31 | 32 | - `actualYear` - определяет актуальных год (в основном нужно для копирайтов в футере) 33 | 34 | - `backToTop` - Кнопка возврат наверх, есть возможность показывать и скрывать эту кнопку через класс 35 | 36 | - `counter` - Счетчик значений больше/меньше + ввод через input 37 | 38 | - `dropEye` - Переключатель глаза и показ символов в поле ввода пароля 39 | 40 | - `lazyLoading` - Отложенная загрузка изображений + тригер для ручного вызова 41 | 42 | - `numberFormat` - Форматирование числа, добавляет тысячный разделитель 43 | 44 | - `scrollToAnchor` - Плавный переход к якорю 45 | 46 | - `transchoice` - Плюрализация (множественность) текста 47 | 48 | Остальные переменные добавленны по мере популярнисти использования на проектах, для каждого проекта этот список можно индивидуально донастроить 49 | 50 | ## Вспомогательные функции SCSS 51 | 52 | - `max / min` - вспомогательные функции для `supports-safe-area-insets` 53 | 54 | - `hover / active-hover / active / disabled` - вспомогательные миксины для корректного доступа к ховеру, так же небольшие хелперы для активного/неактивного состояния элементов и ховера при активном состоянии 55 | 56 | #### Миксин `supports-safe-area-insets` 57 | 58 | Вспомогательный миксин для использования всего пространства экрана на iOs устройствах начиная с iPhone X 59 | 60 | Сейчас на большинстве сайтов можно наблюдать такую картину 61 | 62 | ![safe-area error](images/29/safe-areas-error.png) 63 | 64 | Визуально выглядит это плохо и для решения таких проблем появилась возмоность использовать переменные среды агента пользователя (safe-area), которые выглядит следующим образом 65 | 66 | ![safe-area true](images/29/safe-areas.png) 67 | 68 | Начнём сначала, для правильности работы в шапке сайта нужно указывать специальный viewport. 69 | 70 | ```html 71 | <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, viewport-fit=cover"> 72 | ``` 73 | 74 | ##### Пример использования safe-area 75 | ```scss 76 | body { 77 | padding: 12px; 78 | 79 | @include supports-safe-area-insets { 80 | body { 81 | padding-top: max(12px, env(safe-area-inset-top)) 82 | padding-right: max(12px, env(safe-area-inset-right)) 83 | padding-bottom: max(12px, env(safe-area-inset-bottom)) 84 | padding-left: max(12px, env(safe-area-inset-left)); 85 | } 86 | } 87 | } 88 | ``` 89 | 90 | Разумеется любое из этих 4-х значений можно использовать по отдельности и с любым css свойством, главное знать и понимать когда конкретно это необходимо. 91 | В итоге, при правильном использовании safe-area мы получаем такую картину. 92 | 93 | ![safe-area success](images/29/safe-areas-success.png) 94 | 95 | #### Миксин `text-border` 96 | 97 | `text-border($color, $borderColor, $ieColor, $width: 1px, $ieWidth: 1px)` - Вспомогательный миксин для корректного использования окантовки у текста, предусмотрен полифилл для старых IE. 98 | 99 | Первые 3 свойства обязательны для заполнения, `$color`: цвет текста в основной массе браузеров, `$borderColor` - цвет окантовки для всех браузеров, `$ieColor` - цвет текста для IE, так в нём недоступно свойство `transparent`, остальные свойства используются редко, поэтому в них добавленны базовые значения 100 | 101 | ##### Пример использования text-border 102 | ```scss 103 | @include text-border(transparent, #000, #fff); 104 | ``` 105 | 106 | #### Миксин `font-face` 107 | 108 | `font-face($url, $font-family, $font-weight, $font-style)` - Вспомогательный миксин для задания шрифтов, все поля обязательны для заполнения 109 | 110 | ##### Пример использования font-face 111 | ```scss 112 | @include font-face("../fonts/GraphikRBCLC/GraphikRBCLC-Regular", "GraphikRBCLC", 400, normal); 113 | ``` 114 | 115 | #### Миксин `retina` 116 | 117 | Вспомогательный миксин для добавления `background-images` для дисплеев с 2х экранами 118 | 119 | #### Миксин `placeholder` 120 | 121 | Вспомогательный миксин для стилизации подсказок у полей ввода 122 | 123 | #### Миксины в папке `libs` 124 | 125 | Вспомогательные функции для работы некоторых других функций, так же их можно использовать любом другом месте 126 | 127 | #### Миксин `image-rendering` 128 | 129 | Качество рендеринга изображений, при использовании `background-size` 130 | 131 | #### Миксин `svg-to-data-url` 132 | 133 | Преобразует SVG в URL-адрес данных, чтобы этот SVG можно было использовать в качестве фонового изображения 134 | 135 | #### Миксин `object-fit` 136 | 137 | Миксин добавляет `object-fit` и надстройки для полифилов, так же можно задать позицию 138 | 139 | #### Миксин `triangle` 140 | 141 | Миксин создаёт треугольник в любую из сторон, можно менять цвет, подходит для кнопок плей 142 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Оглавление 2 | 3 | * [Основные возможности и используемые технологии](01_technologies.md) 4 | * [Минимальные требования](02_requirements.md) 5 | * [Начало работы](03_installation.md) 6 | * [Gulp-задачи](04_tasks.md) 7 | * [Структура папок и файлов](05_structure.md) 8 | * [Подключение сторонних библиотек](06_libraries.md) 9 | * [Работа с изображениями](07_images.md) 10 | * [Работа с PNG-спрайтами](07_images.md#Работа-с-png-спрайтами) 11 | * [Работа с SVG-спрайтами](07_images.md#Работа-с-svg-спрайтами) 12 | * [Избавляемся от обрезанных краев SVG-иконок](07_images.md#избавляемся-от-обрезанных-краев-svg-иконок) 13 | * [Работа с шаблонизатором Pug](08_templates.md) 14 | * [Работа со стилями](09_styles.md) 15 | * [Работа со скриптами](10_scripts.md) 16 | * [Работа с дополнительными ресурсами](11_resources.md) 17 | * [Работа со шрифтами](11_resources.md#Работа-со-шрифтами) 18 | * [Pixel-perfect или верстка в соответствии с макетом](13_pixel-perfect.md) 19 | * [Работа с метатегами](15_metatags.md) 20 | * [Базовые метатеги](15_metatags.md#Базовые-метатеги) 21 | * [Метатеги Apple](15_metatags.md#Метатеги-apple) 22 | * [Метатеги Microsoft](15_metatags.md#Метатеги-microsoft) 23 | * [Метатеги Open Graph](15_metatags.md#метатеги-open-graph) 24 | * [Метатеги Twitter](15_metatags.md#Метатеги-twitter) 25 | * [Использование БЭМ методологии](21_bem.md) 26 | * [Оформление Pug-кода](16_codestyle-pug.md) 27 | * [Оформление SCSS-кода](17_codestyle-scss.md) 28 | * [Оформление JavaScript-кода](18_codestyle-javascript.md) 29 | * [Кроссбраузерность и адаптивность](22_crossbrowser-adaptive.md) 30 | * Дополнительная информация 31 | * [Работа с video.js](19_video-js.md) 32 | * [Работа с HLS](20_hls.md) 33 | * [Базовая валидация форм](27_validation.md) 34 | * [Производительность](23_perfomance.md) 35 | * [Работа с Git](24_git.md) 36 | * [Настройка динамических шерингов для SPA](28_dynamic-share-for-spa.md) 37 | * [Вспомогательные функции и миксины для упрощения работы](29_helpers.md) 38 | * [Подробный чеклист по тестированию сайтов](25_checklist.md) 39 | * [Короткий чеклист по тестированию сайтов](26_short-checklist.md) 40 | -------------------------------------------------------------------------------- /images/14/960-grid-system.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ninelines-team/ninelines-docs/2bcd745c733b570685c5e2c73a12b70dbe6a4aab/images/14/960-grid-system.jpg -------------------------------------------------------------------------------- /images/14/example-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ninelines-team/ninelines-docs/2bcd745c733b570685c5e2c73a12b70dbe6a4aab/images/14/example-1.jpg -------------------------------------------------------------------------------- /images/14/example-10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ninelines-team/ninelines-docs/2bcd745c733b570685c5e2c73a12b70dbe6a4aab/images/14/example-10.jpg -------------------------------------------------------------------------------- /images/14/example-11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ninelines-team/ninelines-docs/2bcd745c733b570685c5e2c73a12b70dbe6a4aab/images/14/example-11.jpg -------------------------------------------------------------------------------- /images/14/example-12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ninelines-team/ninelines-docs/2bcd745c733b570685c5e2c73a12b70dbe6a4aab/images/14/example-12.jpg -------------------------------------------------------------------------------- /images/14/example-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ninelines-team/ninelines-docs/2bcd745c733b570685c5e2c73a12b70dbe6a4aab/images/14/example-2.jpg -------------------------------------------------------------------------------- /images/14/example-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ninelines-team/ninelines-docs/2bcd745c733b570685c5e2c73a12b70dbe6a4aab/images/14/example-3.jpg -------------------------------------------------------------------------------- /images/14/example-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ninelines-team/ninelines-docs/2bcd745c733b570685c5e2c73a12b70dbe6a4aab/images/14/example-4.jpg -------------------------------------------------------------------------------- /images/14/example-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ninelines-team/ninelines-docs/2bcd745c733b570685c5e2c73a12b70dbe6a4aab/images/14/example-5.jpg -------------------------------------------------------------------------------- /images/14/example-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ninelines-team/ninelines-docs/2bcd745c733b570685c5e2c73a12b70dbe6a4aab/images/14/example-6.jpg -------------------------------------------------------------------------------- /images/14/example-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ninelines-team/ninelines-docs/2bcd745c733b570685c5e2c73a12b70dbe6a4aab/images/14/example-7.jpg -------------------------------------------------------------------------------- /images/14/example-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ninelines-team/ninelines-docs/2bcd745c733b570685c5e2c73a12b70dbe6a4aab/images/14/example-8.jpg -------------------------------------------------------------------------------- /images/14/example-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ninelines-team/ninelines-docs/2bcd745c733b570685c5e2c73a12b70dbe6a4aab/images/14/example-9.jpg -------------------------------------------------------------------------------- /images/19/image-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ninelines-team/ninelines-docs/2bcd745c733b570685c5e2c73a12b70dbe6a4aab/images/19/image-1.jpg -------------------------------------------------------------------------------- /images/19/image-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ninelines-team/ninelines-docs/2bcd745c733b570685c5e2c73a12b70dbe6a4aab/images/19/image-2.jpg -------------------------------------------------------------------------------- /images/19/image-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ninelines-team/ninelines-docs/2bcd745c733b570685c5e2c73a12b70dbe6a4aab/images/19/image-3.jpg -------------------------------------------------------------------------------- /images/29/safe-areas-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ninelines-team/ninelines-docs/2bcd745c733b570685c5e2c73a12b70dbe6a4aab/images/29/safe-areas-error.png -------------------------------------------------------------------------------- /images/29/safe-areas-success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ninelines-team/ninelines-docs/2bcd745c733b570685c5e2c73a12b70dbe6a4aab/images/29/safe-areas-success.png -------------------------------------------------------------------------------- /images/29/safe-areas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ninelines-team/ninelines-docs/2bcd745c733b570685c5e2c73a12b70dbe6a4aab/images/29/safe-areas.png --------------------------------------------------------------------------------