Рубрикатор

Здесь можно найти крутой материал на любую тему из digital : через поиск, рубрику и даже #тег.

{{ errors.first('search') }}

Выберите категорию

Все Все Разработка Агентство Разработка Дизайн Разработка Новости Разработка Образование Разработка Продвижение Разработка Разработка

Облако #тегов

#digital news #SMM #TAGREE #TikTok #бизнес #вдохновение #веб-дизайн #дизайн #кейс #маркетинг #мир #программирование #продвижение #разработка #реклама #сайты #социальные сети #таргетинг #типографика #фронтенд
Школа 05.01.2022
Курс: Junior frontend-разработчик

Оптимизация и автоматизация

Изображение

1. Проблема

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

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

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

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

Помимо автоматизации разработки огромнейшее значение имеют производительность конечного кода, оптимизация и минификация всех ресурсов. Чем быстрее загрузиться наш сайт, тем быстрее потенциальный пользователь получит доступ к нашему контенту. А ещё это напрямую влияет на SEO (Search Engine Optimization). Быстрые сайты получают приоритет в поисковой выдаче.

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

2. Автоматизация разработки

Структура проекта

Первое, что нам нужно сделать, изменить структуру нашего проекта. Создайте в корне учебного проекта каталог src и все вложенные каталоги, как показано в примере ниже:

my_project/
  src/
    assets/
      fonts/
      icons/
      images/
      svg-sprite/
    html/
      blocks/
    scripts/
      dev/
      vendor/
    styles/
      blocks/
      global/
      vendor/

Каталог src будет содержать наш исходный код и ресурсы.

В подкаталоге assets мы разместим наши шрифты, иконки, изображения.

В подкаталоге html будут лежать html-файлы всех наших страничек. Переместите сюда наши файлы index.html, events.html и event.html. В подкаталоге html/blocks/ в дальнейшем мы будем размещать переиспользуемые HTML-блоки, такие как шапка и футер сайта, карточки листингов и т.п.

В подкаталоге scripts/dev/ будет располагаться наш js-код, а в подкаталоге scripts/vendor/ — js-код сторонних разработчиков.

В подкаталоге styles разместим наши стили. Пока переместите сюда имеющийся файл styles.css, а что с ним делать дальше, разберёмся чуть позже.

Node.js

Далее нам понадобится Node.js. Это среда выполнения JavaScript-кода, построенная на основе JavaScript-движка V8 с открытым исходным кодом, который в свою очередь был создан компанией Google. Node.js прежде всего предназначена для создания серверных приложений на языке JavaScript.

Перейдите по предложенной выше ссылке и установите последнюю рекомендованную версию. Желательно работать именно с LTS-версией (Long-Term Support), имеющей максимальное время технической поддержки. Версия «Current» имеет все последние наработки и обновления платформы, но также она может содержать в себе ошибки.

Чтобы удостовериться, что установка Node.js прошла успешно, выполните в командной строке следующее:

node -v

В консоли отобразится номер установленной версии Node.js.

npm

npm (Node Package Manager) — это менеджер пакетов, который управляет модулями и зависимостями проекта. В Node.js npm имеется по умолчанию, его отдельная установка не требуется.

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

Внимание: прежде чем выполнить следующие шаги, в корне проекта создайте файл .gitignore со следующим содержимым:

node_modules

Таким образом, при последующих коммитах в удалённый репозиторий установленные пакеты npm не попадут в ваш git.

Для описания модулей, от которых зависит наше приложение, в npm используется файл package.json. Создайте такой файл в корне учебного проекта, рядом с каталогом src, откройте текстовым редактором и добавьте следующее содержимое:

{
  "name": "course_front",
  "version": "1.0.0",
  "description": "course junior frontend-developer",
  "author": "si@tagree.ru",
  "devDependencies": {
    "autoprefixer": "^10.4.2",
    "browser-sync": "^2.27.7",
    "del": "^6.0.0",
    "gulp-csso": "^4.0.1",
    "gulp-file-include": "^2.3.0",
    "gulp-format-html": "^1.2.5",
    "gulp-imagemin": "^8.0.0",
    "gulp-less": "^5.0.0",
    "gulp-plumber": "^1.2.1",
    "gulp-postcss": "^9.0.1",
    "gulp-rename": "^2.0.0",
    "gulp-svgmin": "^4.1.0",
    "gulp-svgstore": "^9.0.0",
    "gulp-terser": "^2.1.0",
    "imagemin-gifsicle": "^7.0.0",
    "imagemin-mozjpeg": "^10.0.0",
    "imagemin-optipng": "^8.0.0",
    "postcss": "^8.4.5",
    "postcss-sort-media-queries": "^4.2.1"
  },
  "scripts": {
    "build": "gulp build",
    "serve": "gulp serve",
    "start": "gulp start"
  }
}

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

Чтобы установить новый пакет в devDependencies, нужно открыть терминал, перейти в рабочую папку проекта и набрать следующую команду:

npm install [package-name] --save-dev

Если установить пакет без флага --save-dev, или с флагом --save, то пакет попадёт в запись dependencies. В этой записи должны содержаться те пакеты, от которых зависит финальный код приложения. В рамках данного учебного проекта нам это не понадобится.

Так как все необходимые пакеты уже прописаны в нашем package.json, то ставить их по отдельности нет никакой необходимости. Поэтому откройте терминал, перейдите в папку проекта и просто выполните команду:

npm install

Все необходимые пакеты установятся автоматически.

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

После установки хотя бы одного npm-модуля рядом с package.json будет автоматически создан файл package-lock.json, который будет обновляться каждый раз при добавлении новой зависимости.

В package-lock.json содержится описание текущей иерархии используемых в приложении модулей: их точные версии и точные версии используемых в свою очередь ими модулей. При развертывании приложения в новой среде наличие файла package-lock.json гарантирует, что будут установлены версии тех пакетов, которые использовались в разработке в последний раз.

Описание файла package-lock.json имеет более высокий приоритет в сравнении с описанием зависимостей package.json.

Иногда всё же возникает необходимость обновить пакеты. Чтобы обновить все пакеты, описанные в package.json, можно использовать следующую команду:

npm update

Команда для обновления конкретного пакета:

npm update [package-name]

Команда для удаления пакета (может быть выполнена с флагами --save и --save-dev):

npm uninstall [package-name]

Gulp

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

Для наших целей в рамках курса (а очень часто и в коммерческой разработке) нет смысла использовать сложно-конфигурируемые системы сборки проектов вроде webpack. Нам идеально подойдёт быстрый таск-менеджер для автоматического выполнения часто используемых задач. Одним из них является Gulp.

Gulp — это инструмент, который помогает автоматизировать такие рутинные задачи веб-разработки как:

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

Давайте установим Gulp.js. Сначала нам понадобится установить саму утилиту. Для этого в командной строке наберите:

npm install --global gulp-cli

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

Теперь, находясь в директории нашего проекта, нужно установить зависимость в devDependencies, которой нам не хватало:

npm install --save-dev gulp

Проверьте версию Gulp с помощью команды:

gulp --version

Должно получиться примерно следующее:

$ gulp --version
CLI version: 2.3.0
Local version: 4.0.2

В вашем package.json также должна появиться соответствующая запись.

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

Конфигурация Gulp

Создайте в корне проекта файл gulpfile.mjs, добавьте в него следующее содержимое:

import gulp from "gulp";

import del from "del";
import include from "gulp-file-include";
import formatHtml from "gulp-format-html";

import less from "gulp-less";
import plumber from "gulp-plumber";
import postcss from "gulp-postcss";
import autoprefixer from "autoprefixer";
import sortMediaQueries from "postcss-sort-media-queries";
import minify from "gulp-csso";
import rename from "gulp-rename";

import terser from "gulp-terser";

import imagemin from "gulp-imagemin";
import imagemin_gifsicle from "imagemin-gifsicle";
import imagemin_mozjpeg from "imagemin-mozjpeg";
import imagemin_optipng from "imagemin-optipng";

import svgmin from "gulp-svgmin";
import svgstore from "gulp-svgstore";

import server from "browser-sync";

const resources = {
  html: "src/html/**/*.html",
  jsDev: "src/scripts/dev/**/*.js",
  jsVendor: "src/scripts/vendor/**/*.js",
  images: "src/assets/images/**/*.{png,jpg,jpeg,webp,gif,svg}",
  less: "src/styles/**/*.less",
  svgSprite: "src/assets/svg-sprite/*.svg",
  static: [
    "src/assets/icons/**/*.*",
    "src/assets/favicons/**/*.*",
    "src/assets/fonts/**/*.{woff,woff2}",
    "src/assets/video/**/*.{mp4,webm}",
    "src/assets/audio/**/*.{mp3,ogg,wav,aac}",
    "src/json/**/*.json",
    "src/php/**/*.php"
  ]
};

// Gulp Tasks:

function clean() {
  return del("dist");
}

function includeHtml() {
  return gulp
    .src("src/html/*.html")
    .pipe(plumber())
    .pipe(
      include({
        prefix: "@@",
        basepath: "@file"
      })
    )
    .pipe(formatHtml())
    .pipe(gulp.dest("dist"));
}

function style() {
  return gulp
    .src("src/styles/styles.less")
    .pipe(plumber())
    .pipe(less())
    .pipe(
      postcss([
        autoprefixer({ overrideBrowserslist: ["last 4 version"] }),
        sortMediaQueries({
          sort: "desktop-first"
        })
      ])
    )
    .pipe(gulp.dest("dist/styles"))
    .pipe(minify())
    .pipe(rename("styles.min.css"))
    .pipe(gulp.dest("dist/styles"));
}

function js() {
  return gulp
    .src("src/scripts/dev/*.js")
    .pipe(plumber())
    .pipe(
      include({
        prefix: "//@@",
        basepath: "@file"
      })
    )
    .pipe(gulp.dest("dist/scripts"))
    .pipe(terser())
    .pipe(
      rename(function (path) {
        path.basename += ".min";
      })
    )
    .pipe(gulp.dest("dist/scripts"));
}

function jsCopy() {
  return gulp
    .src(resources.jsVendor)
    .pipe(plumber())
    .pipe(gulp.dest("dist/scripts"));
}

function copy() {
  return gulp
    .src(resources.static, {
      base: "src"
    })
    .pipe(gulp.dest("dist/"));
}

function images() {
  return gulp
    .src(resources.images)
    .pipe(
      imagemin([
        imagemin_gifsicle({ interlaced: true }),
        imagemin_mozjpeg({ quality: 100, progressive: true }),
        imagemin_optipng({ optimizationLevel: 3 })
      ])
    )
    .pipe(gulp.dest("dist/assets/images"));
}

function svgSprite() {
  return gulp
    .src(resources.svgSprite)
    .pipe(
      svgmin({
        js2svg: {
          pretty: true
        }
      })
    )
    .pipe(
      svgstore({
        inlineSvg: true
      })
    )
    .pipe(rename("symbols.svg"))
    .pipe(gulp.dest("dist/assets/icons"));
}

const build = gulp.series(
  clean,
  copy,
  includeHtml,
  style,
  js,
  jsCopy,
  images,
  svgSprite
);

function reloadServer(done) {
  server.reload();
  done();
}

function serve() {
  server.init({
    server: "dist"
  });
  gulp.watch(resources.html, gulp.series(includeHtml, reloadServer));
  gulp.watch(resources.less, gulp.series(style, reloadServer));
  gulp.watch(resources.jsDev, gulp.series(js, reloadServer));
  gulp.watch(resources.jsVendor, gulp.series(jsCopy, reloadServer));
  gulp.watch(resources.static, { delay: 500 }, gulp.series(copy, reloadServer));
  gulp.watch(resources.images, { delay: 500 }, gulp.series(images, reloadServer));
  gulp.watch(resources.svgSprite, gulp.series(svgSprite, reloadServer));
}

const start = gulp.series(build, serve);

export {
  clean,
  copy,
  includeHtml,
  style,
  js,
  jsCopy,
  images,
  svgSprite,
  build,
  serve,
  start
};

Объём кода может немного испугать, но давайте поэтапно разберём, что здесь происходит.

В начале мы видим серию строк, начинающихся словом import:

import gulp from "gulp";

Это всего-навсего автоматическое подключение установленных нами npm-модулей из папки node_modules. Используются они ниже.

Далее идёт const resources. Это константа, то есть тип данных, который не подлежит дальнейшему изменению. Её значением является объект, представляющий из себя набор свойств, состоящих из пар «ключ: значение»:

const resources = {
  html: "src/html/**/*.html",
};

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

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

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

Очистка сборки

Первая задача, которую мы видим — clean.

function clean() {
  return del("dist");
}

Задача «возвращает» вызов функции del с текстовым параметром "dist".

del является одной из наших зависимостей, импорт которой можно видеть на второй строке нашего конфига gulpfile.mjs.

Всё, что делает данный пакет, — удаляет указанную нами директорию, в данном случае dist. Эта директория будет автоматически создаваться при сборке проекта и содержать готовую сборку проекта. Нам нужно регулярно её чистить, чтобы не накапливать «мусор».

Обработка HTML

Следующая задача — includeHtml. Здесь мы обрабатываем все наши HTML-файлы.

function includeHtml() {
  return gulp
    .src("src/html/*.html")
    .pipe(plumber())
    .pipe(
      include({
        prefix: "@@",
        basepath: "@file"
      })
    )
    .pipe(formatHtml())
    .pipe(gulp.dest("dist"));
}

Вначале в src мы обращаемся ко всем HTML-файлам, лежащим непосредственно в src/html/. Как мы помним, здесь у нас расположены наши страницы.

Далее по цепочке благодаря встроенному Gulp-методу pipe выполняется следующая подзадача — вызов плагина plumber. Этот плагин предотвращает прерывание исполняемой функции в том случае, если мы совершили какую-то ошибку и наш код нельзя правильно обработать. В консоль будет выведено соответствующее уведомление.

Далее вызывается include. Этот плагин позволяет нам включать HTML-блоки, вынесенные в отдельные файлы.

Пора сделать первую оптимизацию процесса разработки.

Теперь разметка всех наших страниц в src/html/ будет выглядеть так:

<!DOCTYPE html>
<html lang="ru">
  @@include('blocks/head.html')

  <body class="body">
    @@include('blocks/page-header.html')

    <main class="main">
      <!-- содержимое страницы -->
    </main>

    @@include('blocks/page-footer.html')
  </body>
</html>

Соответственно в подкаталоге src/html/blocks/ нам необходимо создать файлы:

  • head.html
  • page-header.html
  • page-footer.html

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

Следующая подзадача — вызов formatHtml. Плагин просто автоматически форматирует нашу разметку.

Наконец, вызывается gulp.dest("dist") — обработанные и собранные HTML-файлы сохраняются в каталог dist в корне нашего проекта.

Обработка стилей

За обработку стилей отвечает задача style.

function style() {
  return gulp
    .src("src/styles/styles.less")
    .pipe(plumber())
    .pipe(less())
    .pipe(
      postcss([
        autoprefixer({ overrideBrowserslist: ["last 4 version"] }),
        sortMediaQueries({
          sort: "desktop-first"
        })
      ])
    )
    .pipe(gulp.dest("dist/styles"))
    .pipe(minify())
    .pipe(rename("styles.min.css"))
    .pipe(gulp.dest("dist/styles"));
}

Работать мы будем не с обычным CSS, а использовать CSS-препроцессор Less. Подробно об этом поговорим в следующем разделе данного урока. Пока в каталоге src/styles/ создайте пустой файл styles.less.

Благодаря плагину less мы обрабатываем наш less-файл и автоматически конвертируем его в обычный CSS, после чего обрабатываем код CSS-процессором PostCSS.

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

В качестве примера, наш исходный CSS-код:

.element {
  display: flex;
}

Скомпилированный CSS:

.element {
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
}

Далее sortMediaQueries автоматически сортирует медиа-выражения. Пока опустим этот момент, но подробно к нему вернёмся, когда будем изучать адаптивную вёрстку.

Сохраняем наши стили в каталог dist/styles/, тут же производим очередную оптимизацию — минифицируем наш CSS-файл плагином minify, переименовываем в styles.min.css и также сохраняем в dist/styles/.

Обработка JavaScript

За обработку наших js-файлов отвечает задача js.

function js() {
  return gulp
    .src("src/scripts/dev/*.js")
    .pipe(plumber())
    .pipe(
      include({
        prefix: "//@@",
        basepath: "@file"
      })
    )
    .pipe(gulp.dest("dist/scripts"))
    .pipe(terser())
    .pipe(
      rename(function (path) {
        path.basename += ".min";
      })
    )
    .pipe(gulp.dest("dist/scripts"));
}

Мы ещё не изучали, JavaScript, однако давайте в общих чертах разберём что тут происходит.

Мы обрабатываем все js-файлы, лежащие непосредственно в src/scripts/dev/, инклудим js-блоки по аналогии с HTML, если в этом есть острая необходимость (в случае с JavaScript делать это крайне не рекомендуется), сохраняем всё в dist/scripts/, а затем с помощью плагина terser минифицируем наш js-код.

Ниже мы видим задачу jsCopy.

function jsCopy() {
  return gulp
    .src(resources.jsVendor)
    .pipe(plumber())
    .pipe(gulp.dest("dist/scripts"));
}

Всё, что она делает — копирует без какой либо обработки js-файлы, лежащие в resources.jsVendor и сохраняет в dist/scripts/.

Обработка статичных файлов

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

С помощью задачи copy все файлы с соответствующими путями сохраняются в папку dist.

function copy() {
  return gulp
    .src(resources.static, {
      base: "src"
    })
    .pipe(gulp.dest("dist/"));
}

Так, шрифты, лежащие в подкаталоге src/assets/fonts/ будут скопированы в dist/assets/fonts/.

Обработка изображений

С помощью задачи images мы обрабатываем изображения.

function images() {
  return gulp
    .src(resources.images)
    .pipe(
      imagemin([
        imagemin_gifsicle({ interlaced: true }),
        imagemin_mozjpeg({ quality: 100, progressive: true }),
        imagemin_optipng({ optimizationLevel: 3 })
      ])
    )
    .pipe(gulp.dest("dist/assets/images"));
}

Берём, всё, что указано в resources.images, оптимизируем и сжимаем в соответствии с указанными параметрами плагином imagemin и далее сохраняем в dist/assets/images/.

При этом в папке src у нас остаются исходные изображения.

SVG-спрайт.

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

Скопируйте в каталог src/assets/svg-sprite/ ранее экспортированный из макета шапки логотип logo.svg. Откройте файл текстовым редактором.

Если в нём имеются атрибуты ширины и высоты (width и height), удалите их. Должно остаться следующее:

<svg viewBox="0 0 202 147" fill="none" xmlns="http://www.w3.org/2000/svg">
  <!-- остальное содержимое файла -->
</svg>

Теперь надо изменить разметку нашего логотипа в шапке на следующую:

<a href="#" class="page-header__logo">
  <svg class="page-header__logo-icon" width="202" height="147">
    <use xlink:href="assets/icons/symbols.svg#logo"></use>
  </svg>
</a>

Обратите внимание, насколько наша разметка стала меньше и чище. Мы обращаемся к внешнему файлу symbols.svg, лежащему в каталоге dist/assets/icons/, и к конкретной его части — символу logo.

Сам SVG-спрайт генерируется благодаря задаче svgSprite.

function svgSprite() {
  return gulp
    .src(resources.svgSprite)
    .pipe(
      svgmin({
        js2svg: {
          pretty: true
        }
      })
    )
    .pipe(
      svgstore({
        inlineSvg: true
      })
    )
    .pipe(rename("symbols.svg"))
    .pipe(gulp.dest("dist/assets/icons"));
}

Мы берём все SVG-файлы, расположенные в resources.svgSprite, объединяем в один символьный спрайт, оптимизируем его и сохраняем по указанному пути.

Наш сгенерированный файл будет иметь примерно следующее содержимое:

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
  <symbol id="logo" viewBox="0 0 202 147">
    <!-- код логотипа -->
  </symbol>
  
  <!-- прочие символы -->
</svg>

Сборка проекта и отслеживание изменений

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

const build = gulp.series(
  clean,
  copy,
  includeHtml,
  style,
  js,
  jsCopy,
  images,
  svgSprite
);

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

Осталось совсем чуть-чуть. Развернём локальный сервер, используя директорию dist, плагин server и новую задачу serve.

function serve() {
  server.init({
    server: "dist"
  });
  gulp.watch(resources.html, gulp.series(includeHtml, reloadServer));
  gulp.watch(resources.less, gulp.series(style, reloadServer));
  gulp.watch(resources.jsDev, gulp.series(js, reloadServer));
  gulp.watch(resources.jsVendor, gulp.series(jsCopy, reloadServer));
  gulp.watch(resources.static, { delay: 500 }, gulp.series(copy, reloadServer));
  gulp.watch(resources.images, { delay: 500 }, gulp.series(images, reloadServer));
  gulp.watch(resources.svgSprite, gulp.series(svgSprite, reloadServer));
}

При выполнении команды gulp serve в консоли по адресу http://localhost:3000/ запустится наше приложение.

Благодаря встроенному функционалу gulp.watch мы будем отслеживать все изменения по указанным путям, выполнять указанные задачи, а затем автоматически перезагружать наш сервер с помощью reloadServer.

function reloadServer(done) {
  server.reload();
  done();
}

Создадим ещё одну задачу, которая объединит в себе сборку проекта и запуск сервера:

const start = gulp.series(build, serve);

Последнее, что нам осталось — сделать экспорт всех наших задач, которые мы хотим выполнять:

export {
  clean,
  copy,
  includeHtml,
  style,
  js,
  jsCopy,
  images,
  svgSprite,
  build,
  serve,
  start
};

Три основные задачи мы также прописали в package.json:

"scripts": {
  "build": "gulp build",
  "serve": "gulp serve",
  "start": "gulp start"
}

На этом с автоматизацией всё. Теперь, находясь в директории проекта, выполняем в консоли команду:

gulp start

Ждём сборки проекта, запуска сервера и работаем дальше уже в автоматическом режиме.

Остановить обработку и отслеживание можно сочетанием клавиш ctrl + c.

Примечание: на данный момент у нас не прописаны стили, так как файл styles.less пустой. Скоро мы это исправим.

3. CSS-препроцессор Less

О препроцессорах

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

  • импорт файлов;
  • вложенные CSS-правила;
  • переменные;
  • функции;
  • математические операции;
  • операции с цветом;
  • циклы.

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

Следует помнить, что CSS-препроцессор — это всего лишь инструмент разработки, и если вы не умеете писать CSS-код, то он вам не поможет.

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

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

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

Структура файлов

В каталоге проекта src/styles/ у нас лежит файл styles.less. Это основной файл, в котором будут подключаться все наши стили. Он не должен содержать какой-либо CSS-код, здесь будут только импорты прочих файлов.

Порядок импорта важен. В начале мы будем подключать общие стили, которые нельзя отнести к какому-либо выделенному БЭМ-блоку, локальные шрифты, переменные и прочее. Такие файлы будут лежать у нас в каталоге src/styles/global/. Давайте их создадим:

  • common.less
  • fonts.less
  • variables.less

В каталоге src/styles/vendor/ мы будем хранить стили сторонних плагинов. Например стили слайдера, который у нас есть на главной странице. Пока оставим этот каталог пустым, но запомним, что такие файлы лучше подключать следующими, т.е. после общих стилей.

Наконец, в каталоге src/styles/blocks/ будут располагаться стили наших БЭМ-блоков. Правило простое: название БЭМ-блока всегда должно соответствовать названию файла.

Таким образом, в данном каталоге у нас будут лежать:

  • btn.less
  • event-card.less
  • event.less
  • events.less
  • listing.less
  • page-footer.less
  • page-header.less
  • reserve.less
  • section-title.less
  • и все прочие блоки.

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

Подключаются файлы с помощью правила @import. Содержание нашего файла styles.less на данный момент должен быть примерно таким:

@import "global/variables.less";
@import "global/fonts.less";
@import "global/common.less";

// @import "vendor/swiper.less";

@import "blocks/btn.less";
@import "blocks/section-title.less";

@import "blocks/event-card.less";

@import "blocks/listing.less";
@import "blocks/reserve.less";

@import "blocks/events.less";
@import "blocks/event.less";

@import "blocks/page-header.less";
@import "blocks/page-footer.less";

Препроцессор Less хорош тем, что внутри less-файлов мы можем писать обычный CSS-код. А это значит, что всё, что находится у нас в файле styles.css, можно распределить по только что созданным less-файлам, а сам CSS-файл можно удалить, он нам больше не нужен.

С БЭМ-блоками должно быть всё более-менее понятно, variables.less пока оставим пустым, в fonts.less локально подключим ещё один шрифт, который нам нужен, но отсутствует на Google Fonts:

@font-face {
  font-family: "mak";
  src: local("MAK-bold"), url("../assets/fonts/MAK-bold.woff") format("woff");
  font-weight: 700;
  font-style: normal;
}

@font-face {
  font-family: "mak";
  src: local("MAK-light"), url("../assets/fonts/MAK-light.woff") format("woff");
  font-weight: 300;
  font-style: normal;
}

Скачайте файлы шрифтов и разместите их в каталоге src/assets/fonts/.

Содержание common.less может быть примерно таким:

html,body {
  width: 100%; 
  margin: 0;
  padding: 0;
}

body {
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  background-color: #dedede;
  background-image: url(../assets/images/noise.png), url(../assets/images/bg.png);
  background-position: 0 0, 0 0;
  background-size: auto, 100% auto;
  background-repeat: repeat, repeat-y;
  color: #1F1E1E;
  font-family: "Open Sans", sans-serif;
  font-size: 16px;
  font-weight: 400;
  font-style: normal;
  line-height: 1.375;
  overflow-x: hidden;
  padding-top: 147px;
}

.main {
  flex: 1 0 auto;
}

.page-footer {
  flex: 0 0 auto;
}

a {
  color: #1F1E1E;
  outline: none;
}

img {
  max-width: 100%;
  height: auto;
  object-fit: cover;
}

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

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

Теперь наш учебный проект можно собрать и запустить с помощью команды gulp start. Если вы всё сделали правильно, запустится локальный сервер, а в браузере по адресу http://localhost:3000/ можно будет увидеть итог вашей работы в текущем состоянии.

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

Переменные

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

@black: #1f1e1e;
@black_80: fade(@black, 80%);

@font: "Roboto", sans-serif;
@font_size: 16px;
@fonts_size_s: (@font_size / 1.25);

Теперь заданные переменные мы можем использовать в качестве значений для любых CSS-правил:

body {
  color: @black;
  font-family: @font;
  font-size: @font_size;
}

figcaption {
  color: @black_80;
  font-size: @fonts_size_s;
}

В конечном итоге наш код будет скомпилирован в обычный CSS:

body {
  color: #1f1e1e;
  font-family: "Roboto", sans-serif;
  font-size: 16px;
}

figcaption {
  color: rgba(31,30,30,.8);
  font-size: 12.8px;
}

Обратите внимание, благодаря функции fade мы преобразовали цвет для figcaption с HEX в RGBA, добавив 20% прозрачности.

Хранить переменные мы будем в файле src/styles/global/variables.less. Наш файл на данный момент может выглядеть примерно так:

// Цветовая палитра
@white: #ffffff;
@black: #1f1t1t;

@red: #bc3324;
@red_dark: #a51505;

@gray: #dedede;
@gray_dark: #bfbfbf;

// Шрифты
@font1: "Open Sans", sans-serif;
@font2: "Lora", serif;
@font3: "mak", serif;

Дело осталось за малым: пройтись автозаменой по всем прочим less-файлам и заменить совпадающие значения на наши переменные. Так, common.less к данному моменту будет выглядеть следующим образом:

html,body {
  width: 100%; 
  margin: 0;
  padding: 0;
}

body {
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  background-color: @gray;
  background-image: url(../assets/images/noise.png), url(../assets/images/bg.png);
  background-position: 0 0, 0 0;
  background-size: auto, 100% auto;
  background-repeat: repeat, repeat-y;
  color: @black;
  font-family: @font1;
  font-size: 16px;
  font-weight: 400;
  font-style: normal;
  line-height: 1.375;
  overflow-x: hidden;
  padding-top: 147px;
}

.main {
  flex: 1 0 auto;
}

.page-footer {
  flex: 0 0 auto;
}

a {
  color: @black;
  outline: none;
}

img {
  max-width: 100%;
  height: auto;
  object-fit: cover;
}

Комментарии

В обычном CSS единственный способ оставить комментарий выглядит так:

/* Комментарий */

/*
Многострочный
комментарий
*/

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

// Шрифты
@font1: "Open Sans", sans-serif;

Закомментированные строки не попадут в конечный CSS-код. Однако при необходимости (например нам нужно указать авторские права) мы можем сохранить комментарий в скомпилированном файле:

/*! Этот комментарий будет сохранён. !*/

Вложенные CSS-правила

Обычный CSS-код писать не очень удобно, нет никакой группировки правил, а в случае с БЭМ мы постоянно пишем одинаковые части названий элементов блоков. Давайте вспомним, как выглядели наши для шапки сайта:

.page-header {
  /* Стили блока шапки */ 
}

.page-header__container {
  /* Стили контейнера шапки */ 
}

.page-header__nav-link {
  /* Стили ссылок навигации */
}

.page-header__nav-link:hover {
  /* Стили ссылок по наведению мыши */
}

В less мы будем писать код, объединяя всё внутри БЭМ-блоков:

.page-header {
  /* Стили блока шапки */

  &__container {
    /* Стили контейнера шапки */
  }

  &__nav-link {
    /* Стили ссылок навигации */
    
    &:hover {
      /* Стили ссылок по наведению мыши */
    }
  }
}

В данном случае амперсанд & будет соединять наименования всех уровней вложенности CSS-правил. Специфичность правил, о которой мы говорили на втором уроке, останется той же. Таким образом, мы не только сохраняем все преимущества БЭМ, но и объединяем весь CSS-код внутри каждого БЭМ-блока.

Для псевдоэлементов и модификаторов работает всё точно так же:

.element {
  /* CSS-стили */
  
  &::before,
  &::after {
    /* CSS-стили псевдоэлемента */
  }

  &--red {
    /* CSS-стили модификатора*/

    &::before,
    &::after {
      /* псевдоэлемент с модификатором*/
    }
  }
}

Аналогично мы можем прописывать CSS-правила для вложенных элементов, повышая специфичность правил.

CSS-код:

.element {
  /* CSS-стили */
}

.element section {
  /* CSS-стили */
}

.element section.child {
  /* CSS-стили */
}

Соответствующий less-код:

.element {
  /* CSS-стили элемента */

  section {
    /* CSS-стили секции внутри .element */

    &.child {
      /* CSS-стили секции с классом .child */
    }
  }
}

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

Примеси

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

Давайте вспомним, как мы задавали контейнер для шапки сайта:

.page-header {
  // Стили блока шапки

  &__container {
    box-sizing: border-box;
    display: flex;
    align-items: center;
    width: 100%;
    max-width: 1414px;
    margin: 0 auto;
    padding-left: 40px;
    padding-right: 40px;
  }
}

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

В каталоге src/styles/global создайте файл mixins.less со следующим содержанием:

.container() {
  box-sizing: border-box;
  width: 100%;
  max-width: 1414px;
  margin-left: auto;
  margin-right: auto;
  padding-left: 40px;
  padding-right: 40px;
}

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

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

.page-header {
  /* Стили блока шапки */

  &__container {
    .container();

    display: flex;
    align-items: center;
  }
}

В конечном CSS-коде стили миксина будут объединены со стилями элемента, к которому мы его применили, при этом сам миксин не попадёт в CSS-файл.

Однако, Less позволяет нам использовать любое CSS-правило в качестве миксина или же БЭМ- микса (вспоминаем второй урок).

Мы можем задать стили в нашем mixins.less, к примеру, следующим образом:

.container {
  box-sizing: border-box;
  width: 100%;
  max-width: 1414px;
  margin-left: auto;
  margin-right: auto;
  padding-left: 40px;
  padding-right: 40px;
}

.display-none {
  display: none;
}

.color-red {
  color: @red;
}

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

Такие примеси-классы нужны, чтобы точечно перебить какие-либо свойства элементов. Например, нам может понадобиться временно скрыть какой-либо блок с помощью JavaScript. Для этого мы просто добавим данному элементу класс display-none. Именно поэтому подключать mixins.less следует самым последним. Это позволит перебить родные стили элемента, но сохранить специфичность на том же уровне.

Примеси с параметром

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

.mixin(@width: 100px, @height: 50px) {
  width: @width;
  height: @height;

  /* прочие свойства */
}

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

.element_1 {
  .mixin();
}

.element_2 {
  .mixin(80px);
}

.element_3 {
  .mixin(80px 70px);
}

Скомпилированный CSS-код:

.element_1 {
  width: 100px;
  height: 50px;
}

.element_2 {
  width: 80px;
  height: 50px;
}

.element_3 {
  width: 80px;
  height: 70px;
}

Расширения

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

Рассмотрим less-код:

.inline {
  color: red;
}

.nav {
  &:extend(.inline);
  background: blue;

  width: 100%;
}

Конечный CSS-код будет выглядеть так:

.inline,
.nav {
  color: red;
}

.nav {
  background: blue;
  width: 100%;
}

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

Примечание: Задумка отличная. Однако уменьшение кода в конечном счёте оказывается мнимым. Дело в том, что при сборке проекта мы автоматически сжимаем, минифицируем наш CSS с помощью gulp-postcss, а все правила объединяются наиболее оптимальным образом. Таким образом, никакого смысла использовать less-расширения для нас нет, следует применять именно примеси.

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

4. Оптимизация HTML-разметки

Листинг мероприятий

Мы уже научились создавать и включать отдельные HTML-блоки с помощью задачи includeHtml для Gulp. Теперь на примере листинга мероприятий разберёмся, как ещё сильнее оптимизировать нашу вёрстку.

Используемый нами Gulp-плагин позволяет передавать переменные. Воспользуемся этим.

В каталоге src/html/blocks/ создайте файл event-card.html, скопируйте разметку карточки мероприятия из events.html и измените её следующим образом:

<article class="event-card  @@class">
  <a href="#" class="event-card__link">
    <picture class="event-card__picture">
      <img src="@@img" alt="@@alt" class="event-card__img" />
    </picture>

    <h4 class="event-card__title">@@title</h4>

    <p class="event-card__description">@@descr</p>

    <time datetime="@@datetime" class="event-card__time">@@time</time>
  </a>
</article>

Мы вынесли все необходимые значения в переменные, которые теперь зададим непосредственно в листинге, перечислив все карточки через метод loop() следующим образом:

<div class="events">
  <section class="listing events__listing">
    <h2 class="listing__title section-title">Мероприятия</h2>

    <div class="listing__events-list">
      <!-- prettier-ignore -->
      @@loop('blocks/event-card.html', [
        {
          "class": "listing__event-card",
          "img": "assets/images/events/nirvana.jpg",
          "alt": "nirvana",
          "title": "Собираемся и слушаем альбом Nirvana",
          "descr": "Это третий альбом группы...",
          "time": "07.12.2022 | начало 18.00",
          "datetime": "2022-12-07T18:00"
        },
        {
          "class": "listing__event-card",
          "img": "assets/images/events/stuff.jpg",
          "alt": "stuff",
          "title": "Есть тема, нужно обсудить с HowStuffWorks",
          "descr": "Один из самых популярных подкастов в мире...",
          "time": "14.12.2022 | начало 18.30",
          "datetime": "2022-12-14T18:30"
        }
      ])
    </div>

    <!-- Кнопка "Показать всё" -->
  </section>

  <!-- Остальная разметка -->
</div>

Комментарий <!-- prettier-ignore --> вам понадобится в том случае, если вы используете автоматическое форматирование разметки с помощью расширения prettier для вашего редактора кода.

Теперь HTML-код карточки мы можем править в одном единственном месте. Переменные для оставшихся карточек пропишите самостоятельно.

Листинг статей блога

На главной странице у нас имеется листинг статей блога, сверстаем его.

Чтобы разметка самой страницы осталась максимально простой, создадим в каталоге src/html/blocks новый файл blog-listing.html и подключим его:

<!DOCTYPE html>
<html lang="ru">
  @@include('blocks/head.html')

  <body class="body">
    @@include('blocks/page-header.html')

    <main class="main">
      <div class="home">
        <!-- прочая разметка -->

        <!-- prettier-ignore -->
        @@include('blocks/blog-listing.html', {"class": "home__listing"})

        <!-- прочая разметка -->
      </div>
    </main>

    @@include('blocks/page-footer.html')
  </body>
</html>

БЭМ-блок home нам нужен, чтобы задать внутренние отступы на странице, а БЭМ-элементу home__listing зададим внешний нижний отступ в соответствии с дизайном:

.home {
  padding: 100px 0;
  
  &__listing {
    margin-bottom: 95px;
  }
}

В качестве листинга мы используем уже имеющийся у нас после вёрстки списка мероприятий БЭМ-блок listing.

А для самих карточек необходимо создать новый БЭМ-блок article-card и соответствующий HTML-файл. По аналогии с мероприятиями сразу же сделаем цикл по карточкам:

<section class="listing @@class">
  <h2 class="listing__title section-title">Блог</h2>

  <div class="listing__article-list">
    <!-- prettier-ignore -->
    @@loop('article-card.html', [
      {
        "class": "listing__article-card",
        "img": "assets/images/blog/theatre.jpg",
        "alt": "Theatre Photosession",
        "title": "Фотосессия в стенах Театра драмы",
        "descr": "Организовывая фотосессию для...",
        "time": "26.11.2021",
        "datetime": "2021-11-26T00:00"
      }
    ])
  </div>

  <a href="#" class="listing__link link">Смотреть все</a>
</section>

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

Кнопка «Смотреть всё» вынесена в отдельный БЭМ-блок, так как используется повторно.

Карточка статьи блога

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

В отдельных случаях стрелка может быть ссылкой или кнопкой, но в данном случае она уже находится внутри ссылки, а значит мы должны использовать <div> или <span>.

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

Таким образом в карточке мы имеем два выделенных блока — ссылку-изображение и прочую информацию:

<article class="article-card @@class">
  <a href="#" class="article-card__link">
    <picture class="article-card__picture">
      <img src="@@img" alt="@@alt" class="article-card__img" />
    </picture>
  </a>

  <div class="article-card__info">
    <time datetime="@@datetime" class="article-card__time">@@time</time>

    <a href="#" class="article-card__title-link">
      <h4 class="article-card__title">@@title</h4>

      <div class="article-card__arrow arrow">
        <svg class="arrow__icon" width="92" height="62">
          <use xlink:href="assets/icons/symbols.svg#arrow"></use>
        </svg>
      </div>
    </a>

    <p class="article-card__description">@@descr</p>
  </div>
</article>

Если обратить внимание на UI-kit проекта в Figma, то по наведению на карточку мыши вокруг изображения должна появляться тень, подсвечивая таким образом карточку.

.article-card {
  box-sizing: border-box;

  display: flex;
  width: 100%;

  &:hover {
    .article-card__picture {
      box-shadow: 0 0 30px fade(@black, 55%);
    }
  }
  &__link {
    flex-shrink: 0;
    align-self: flex-start;
    display: block;
    width: 467px;
    margin-right: 60px;
    font-size: 0;
    text-decoration: none;
  }
  &__picture {
    display: block;
    position: relative;
    width: 100%;
    transition: box-shadow 0.2s;
    overflow: hidden;
    &::after {
      content: "";
      display: block;
      width: 100%;
      padding-top: 62.3126%;
    }
  }
  &__img {
    display: block;
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
}

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

.article-card {
  &__info {
    box-sizing: border-box;
    position: relative;
    display: flex;
    flex-direction: column;
    width: 100%;
    padding-top: 30px;
    padding-bottom: 30px;
    padding: 30px 0;
    padding-right: 150px;
  }
  &__time {
    margin: 0 0 40px;
    font-weight: 300;
    font-size: 16px;
    line-height: 22px;
  }
  &__title-link,
  &__description {
    max-width: 470px;
  }
  &__title-link {
    margin: auto 0 40px;
    color: @black;
    text-decoration-color: transparent;
    transition: color 0.2s, text-decoration-color 0.2s;
    &:hover {
      color: @red;
      text-decoration-color: @red;
    }
  }
  &__title {
    margin: 0;
    color: inherit;
    font-weight: 600;
    font-size: 24px;
    line-height: 33px;
  }
  &__description {
    margin: 0;
    font-size: 16px;
    line-height: 22px;
  }
  &__arrow {
    position: absolute;
    right: 0;
    bottom: 30px;
    color: inherit;
    transition: none;
  }
}

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

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

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

.article-card {
  /* прочие стили */

  &__link {
  /* прочие стили */

    &:hover {
      & ~ .article-card__info {
        .article-card__title-link {
          color: @red;
          text-decoration-color: @red;
        }
      }
    }
  }
}

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

.article-card {
  position: relative;
  /* прочие стили */

  & + .article-card {
    margin-top: 120px;
    &::before {
      content: "";
      display: block;
      position: absolute;
      margin: 0 auto 60px;
      bottom: 100%;
      left: 0;
      right: 0;
      width: 100%;
      max-width: 1254px;
      height: 1px;
      background-color: @black;
      pointer-events: none;
    }
  }
}

Резюме

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

5. Материалы для самостоятельного изучения

  1. Руководство по Node.js:
    https://nodejs.org/ru/docs/guides/
  2. Документация npm:
    https://docs.npmjs.com/
  3. Документация Gulp:
    https://gulpjs.com/docs/en/getting-started/quick-start
  4. Документация Less:
    https://lesscss.org/features/

{{ netItem.title }}

Меню

Список уроков

1. Введение. Знакомство с HTML 2. Основы CSS и методологии 3. Блочная модель и позиционирование 4. Flexbox и Grid Layout 5. Оптимизация и автоматизация 6. Формы, таблицы и текстовый контент 7. Адаптивная вёрстка 8. Графика 9. Введение в JavaScript 10. Введение в JavaScript. Часть 2 11. Введение в JavaScript. Часть 3

Навигация по странице

Email

hi@tagree.ru

Телефон

+7 499 350 0730

Telegram

t.me/tagree_ru

Москва

+7 499 350 0730

Долгоруковская 7, БЦ «Садовая Плаза», 4 эт.

Томск

+7 382 270 0368

Белая 8, БЦ «Tagree»

Петербург

+7 812 509-31-09

​Большая Монетная, 16

vkyt
Политика конфиденциальности Пользовательское соглашение Cookies

Пользуясь сайтом, соглашаюсь с политикой использования cookies

© 2022, tagree digital agency.