Шаблон для сайта
11-06-2024Время чтения ~ 20 мин.Albireo Framework / CMS 435
Шаблон для сайта это не просто комбинированный вывод HTML-кода, а целая система взаимодействия между «ядром» CMS и многочисленными разрозненными файлами. Это как кубики Лего, которые никогда не сложатся в единую фигуру, если не предусмотреть понятные алгоритмы их взаимодействия.
Среди большинства CMS встречается две крайности в реализации механизма шаблонов. Первый — это примитивное понимание шаблона как такового. Система сформировала данные для вывода, а дальше вебмастер (тот, кто делает шаблон сайта) должен сам решать что с этим делать.
Проблема такого подхода в том, что качество шаблонов будет сильно зависеть от компетентности вебмастера и его знаний особенностей системы. Например, если нужно вывести несколько блоков на главной, а в CMS нет какого-то внятного алгоритма для этого, то вебмастер вынужден сам придумывать код, который может оказаться проблемным. Поскольку автор CMS не предусмотрел полный цикл создания шаблона, то вебмастера будут изобретать свои велосипеды кто на что горазд.
Вторая крайность — это излишний контроль за шаблоном со стороны CMS. Например есть системы, где нужно описывать весь шаблон в yaml-файлах конфигурации. Скажем для создания формы нужно её описать в yaml-файле, вместо того, чтобы использовать обычный html-код. Или невозможность выполнения php-кода в файлах шаблона. Или жесткая завязка на какой-то конкретный css-фреймворк. Есть системы, где вообще нет возможности поменять модульную сетку, а всю кастомизацию можно сделать в предопределённом css-файле.
Понятно, что такой подход сильно ограничивает возможности и потребности реальных пользователей.
С моей точки зрения, шаблон сайта для Albireo CMS должен решать сразу несколько задач, поэтому подход должен быть комплексным. Попробую кратко описать что я имею ввиду.
Со стороны конечного пользователя
Для обычного пользователя система должна предложить способы быстрого переключения как самого шаблона, так и его частей — шапки, подвалы, сайдбары, виджеты.
Для смены дизайна пользователю будет достаточно просто выбрать некий css-файл, который и подключит нужный цветовой дизайн.
У пользователя должна быть возможность подключения произвольного css/js/html-кода для любой страницы или для всего сайта. Например он нашёл в Сети какой-то интересный блок (код) и ему не нужно что либо программировать в шаблоне, чтобы его подключить. Вместо этого используется что-то на уровне copy/paste.
То есть для конечного пользователя шаблон должен предлагать сразу много готовых решений с минимальным количеством телодвижений.
Со стороны вебмастера
Когда вебмастер делает новый шаблон, то ему нужна какая-то основа. В идеале он должен сделать копию default-шаблона и внести свои правки в некоторых файлах.
Также возможен вариант, когда новый шаблон не обязательно создавать, а достаточно расширить существующий. Например, если нужно подключить какой-то шрифт, css-стиль или даже php-код, то система должна предусмотреть механизм расширения default-шаблона. То есть вебмастер не занимается всем кодингом, а только тем, который требуется непосредственно для решения конкретной задачи.
Поэтому для вебмастера шаблон должен быть как можно проще, но при этом иметь готовые решения для большинства задач.
Со стороны системы
Для CMS буден намного проще, если шаблон будет иметь какую-то жесткую структуру каталогов и файлов. С другой стороны, при необходимости, система должна работать с любым указанным путём. Поэтому шаблон имеет некую структуру, которая предопределена разработчиком как «дефолтная» (рекомендуемая), но если вебмастер знает, что делать, то легко может её поменять.
Система должна передавать управление шаблону полностью и окончательно. То есть шаблон — это конечная точка вывода контента. CMS подготавливает минимально необходимые данные для шаблона, но их использование целиком ложится на код шаблона.
Со стороны разработчика другого проекта, который ничего не знает о CMS
Современный WEB представляет собой смесь огромного количества готовых разработок. Многие программисты делают хорошие проекты, которые никак не связаны с CMS или любыми другими проектами. Например подсветка кода highlight.js может интегрироваться для любого сайта. Гугл-шрифты, иконочные шрифты, css-эффекты, анимация, слайдеры, php-библиотеки для работы с изображениями и т.д., и т.п.
Все эти проекты могут иметь свою структуру файлов и каталогов, а также разные механизмы интеграции в другие проекты. Поэтому CMS просто обязана иметь возможность безболезненной интеграции с другими проектами.
Очень важный нюанс в том, что сторонние проекты/библиотеки должны подключаться к шаблону в разных частях. Как минимум есть секция HEAD и конец BODY (т.н. lazy). При этом многие библиотеки требуют код начальной инициализации и это тоже важно учитывать.
Как работает шаблон в Albireo CMS
Прежде чем двигаться дальше, нужно объяснить как именно осуществляется вывод данных в Albireo.
После загрузки ядра системы и определения какой именно php-файл (файл страницы) отвечает за текущий URL, система передаёт управление в layout-файл. По сути это обычный php-файл, который подключает файл страницы. Например layout-файл empty.php
состоит из одной строчки:
require getVal('pageFile');
Этот layout-файл используется для того, чтобы формирование html-кода 100% целиком ложилось на файл страницы.
То есть система всегда требует layout-файл: система просто его подключает, а что именно делать дальше решает уже сам layout-файл. Такой подход позволяет создавать совершенно произвольные форматы вывода, с любыми модульными сетками, подключениями и т.д.
Понятно, что формировать в каждой странице полный html-код не самое удобное занятие, поэтому есть layout-файл default.php
, который разделяет шаблон на структуры start, content и end по отдельным файлам.
require __DIR__ . '/_start.php'; echo '<article class="layout-center container-content">'; require __DIR__ . '/_content.php'; echo '</article>'; require __DIR__ . '/_end.php';
По сути это уже полноценная модульная сетка, где контент (файл страницы) подключается в рамках тэга ARTICLE.
Если нужен сайдбар, то его layout-файл sidebar.php
будет чуть сложнее:
require __DIR__ . '/_start.php'; ?> <div class="layout-center-wrap"><div class="layout-wrap container-content"> <div class="flex flex-wrap-tablet"> <article class="w70 w100-tablet"><?php require __DIR__ . '/_content.php'; ?></article> <div class="w25 w100-tablet container-sidebar"><?= implodeWrap( data: widgets(CONFIG_DIR . 'widgets.php'), before: getConfig('widget-before'), after: getConfig('widget-after') ) ?></div> </div> </div></div> <?php require __DIR__ . '/_end.php';
То есть каждый layout-файл выполняет роль модульной сетки, в рамках которой происходят все подключения. При этом файл _start.php
используется для начальной части HTML-страницы, _content.php
отвечает за контентную часть и именно в нём подключается файл страницы, а _end.php
содержит заключительную часть HTML.
Выбор layout-файла для страницы происходит прямо в настройках страницы:
layout: sidebar-left.php
Либо в конфигурационном файле. При этом у пользователя есть возможность выбрать любой другой шаблон для страницы (или всех):
template: taurus
Каталог layout
хранит модульные сетки шаблона, где происходит подключение остальных блоков.
Раздельное подключение прочих блоков
Как я уже отметил, у любой страницы структурно есть блоки для HEAD, блок для конца BODY (называем это lazy), а также возможны какие-то блоки до и после вывода контента (content и content-start).
Например если требуется подключить Alpine.js, то это нужно сделать в секции HEAD. Сам html-код подключения (SCRIPT) удобно разместить в отдельном файле и именно в нём организовать все нюансы подключения.
То же самое касается и подключения любых других библиотек. Каталоги
-
parts/head
подключает файлы в секцию HEAD -
parts/lazy
подключает файлы в конце BODY -
parts/content
подключает файл после вывода контента -
parts/content-start
подключает вывод перед контентом
В layout-файлах происходит автоматическое подключение нужных файлов из parts
.
Может возникнуть вопрос о том, что сторонние библиотеки могут состоять из нескольких файлов или даже каталогов, как именно организовать их расположение?
Покажу на примере Alpine.js. Его подключение организовано в файле parts/head/alpine.php
:
if (checkStr(getPageData('use.alpine', false)) === true) echo '<script src="' . RESOURCES_URL . 'alpine/alpine.min.js" defer></script>';
Этот код проверяет отметку в настройках страницы use.alpine: +
, что означает «включено» и, соответственно, будет подключен файл alpine.min.js
из каталога ресурсов.
То есть в parts-файле происходит только подключение нужных файлов и, если нужно их инициализация. Но сами файлы хранятся отдельно в каталоге ресурсов.
Каталог ресурсов
Идея такого каталога у меня возникла после создания многочисленных шаблонов для MaxSite CMS. Многие шаблоны имели одни и те же файлы. Скажем гугл-шрифты. В своей практике я использую примерно 20 одних и тех же шрифтов в зависимости от шаблона. Общий их размер примерно 5Мб, на которые увеличивается каждый шаблон.
Поэтому первая причина выделения каталога ресурсов — это удаления дублирующихся файлов: каждый шаблон может использовать общие ресурсы. Сам же каталог ресурсов (RESOURCES_URL) располагается уровнем выше, чем каталог шаблонов сайта.
Вторая причина — это организация файлов сторонней библиотеки.
Например есть Bootstrap Icons, который в своём дистрибутиве имеет отдельные каталоги для файла шрифта и css/sass-файлов. Но про факту нам нужны только bootstrap-icons.css
и два woff-файла. Таким образом, когда мы создаём Bootstrap Icons для каталога ресурсов, нам просто не нужны все те файлы, что идут в комплекте дистрибутива. Мы оставляем только те, которые подключаются в файлах шаблона.
Когда у вебмастера стоит задача добавить новую библиотеку, например слайдер, то файлы дистрибутива располагаются в каталоге ресурсов, а в parts-файле организуется его подключение.
И третья причина — это обновление сторонних библиотек. Например выйдет новая версия иконок Bootstrap, то всё что нужно сделать, так это обновить его файлы в ресурсах. Все остальные шаблоны автоматом получат это обновление.
TPL-шаблонизация
Файлы tpl — это файлы, который используются для формирования html из php, но в упрощенном синтаксисе. Я об этом рассказывал ранее, не буду повторяться. Примеры Blade, Twig, Smarty.
Каталог tpl
является частью шаблона, хотя формально tpl-файлы могут использоваться совершенно в других частях сайта, например вывод комментариев, пагинация, вывод рубрик, меток и т.п.
Но tpl-файлы как правило сильно завязаны на css-классы шаблона, поэтому если два шаблона будут использовать разные css-фреймворки, то придётся переписывать и tpl-файлы. Поэтому tpl-файлы сразу определяются как часть шаблона.
Extras-файлы
Эти файлы (каталог extras
) обрабатываются точно также как и tpl-файлы, но для tpl-файлов нужно организовывать цикл вывода, а extras-файлы работают в рамках текущей страницы вывода. Более того, extras-файлы можно подключить через настройки страницы, например так:
# перед началом контента extras.start[]: info1.php extras.start[]: page-data.php # в конце контента extras.end[]: page-tags.php extras.end[]: page-author.php
По своей сути extras-файлы близки к info-top-файлам MaxSite CMS, когда с их помощью можно организовать разный вывод заголовков записи. Здесь нечто похожее, только в более удобной форме. Например для вывода у этой страницы информации о записи (рубрики, дата и т.д.) используется простой код в тексте страницы:
_(t-gray t90) <?= extras('info1.php') ?>
Сам же info1.php
представляет собой обычный tpl-файл.
Сниппеты
Сниппеты — это небольшие фрагменты кода, которые я придумал ещё во времена LPF. Например счетчики посещений можно разместить в сниппете и подключить одной строчкой:
<?= snippet('counter') ?>
В шаблоне располагаются (каталог snippets
) те сниппеты, которые используют css-классы шаблона. Но если сниппет «общий» (тот же счётчик), то его можно разместить в общем каталоге website/snippets
— система сама определит какой сниппет подключить.
Модули
Для всех остальных блоков используется каталог modules
. Например modules/headers
хранит файлы шапок, а modules/footers
— файлы подвалов. Организация модулей произвольная, то есть файл можно подключить обычным require
, но для того, чтобы обеспечить настраиваемость модулей, используется функция, которая смотрит опции страницы, где и указываются подключаемые модули.
По своей сути это близко к компонентам MaxSite CMS, только несколько проще.
Если нужно указать модули шапки или подвала, то делается это через опции страницы (или в файле конфигурации):
# файлы шапки headers: headers/header1.php, headers/header2.php # или так headers: headers/header3.php # файлы подвала footers: footers/footer1.php # или так footers: footers/footer2.php
Сам вывод подвала и шапки организован в layout-файле: он смотрит опции headers
и footers
и подключает указанные в них файлы.
Итак, что получается на данный момент.
- Есть layout-файл, который подключает файл страницы.
- Также layout-файл организует подключение parts-файлов в нужных частях итогового html-кода.
- Файлы tpl, зависят о css-классов, поэтому располагаются в каталоге шаблона.
- Файлы extras нужны для вывода различной информации о текущей странице.
- Файлы modules хранят файлы шапки, подвала и вообще любых других блоков.
- Сниппеты подключаются как есть.
- Все сторонние библиотеки хранятся в общем каталоге ресурсов.
Такой подход к организации, позволяет охватить весь цикл создания и обслуживания шаблона.
Организация CSS и JS
Теперь к самому интересному.
Традиционно для css и js используется каталог assets
. Внутри размещается каталоги css
, js
и sass
. Какой-то жесткой схемы здесь нет, поэтому можно разместить еще и шрифты, изображения, в общем всё что угодно.
Подключение файлов здесь происходит через опции страниц. Например в дефолтном шаблоне это так:
css.theme: berry.css css.fstyle: my1.css js.lazy[]: my.js
Первая строчка указывает подключить файл assets/css/themes/berry.css
. Вторая строчка указывает подключить файл assets/css/my1.css
как обычное содержимое STYLE. Третья подключает файл assets/js/my.js
.
То есть смысл в том, что в Albireo CMS предусмотрено самые разные подключения css/js/файлов.
Например, если бы мне нужно было подключить шрифт Exo2 и сделать его основным:
css.fonts.lazy[]: exo2.css css.root[body-font-family]: exo2
Файл шрифта при этом будет располагаться в assets/css/fonts/exo2.css
.
Получается достаточно простая схема организации CSS:
-
assets/css/
— произвольные css-файлы -
assets/css/themes/
— файлы «тем» -
assets/css/fonts/
— файлы шрифтов, при этом сами woff- файлы размещены в каталоге ресурсов (хотя это не обязательно). -
assets/sass/
— исходники Sass
Уверен, что для любого верстальщика такая структура будет привычной и понятной.
Что касается каталога images
, то можно его создать в рамках шаблона, но при условии, что эти изображения не будут меняться пользователем. Например логотип логичней разместить в общий uploads
сайта, поскольку он всё-равно будет меняться.
Подходы к вёрстке
Теперь немного абстрагируемся. Пусть у нас есть некий css-фреймворк, который мы хотим использовать для сайта (или отдельной страницы, без разницы).
В 99% случаев потребуется его подключить в виде одного css-файла. Сделать это можно разными способами:
# в секцию HEAD - файл assets/css/framework.css css.head[]: framework.css # в конце BODY - файл assets/css/framework.css css.lazy[]: framework.css # в секцию HEAD - файл assets/css/themes/framework.css css.theme: framework.css
То есть принципиально нет вообще никакой разницы как именно подключается css-файл. Тонкость здесь в том, что файл фреймворка скорее всего потребуется изменить. И сделать это можно разными способами.
Первый — это правка исходного файла. Причём не важно будет ли это sass-компиляция или ручная корректировка css-кода. Суть её в том, что на выходе мы получаем тот же самый framework.css
, который и содержит нужный нам дизайн.
Такой подход используется в шаблонах MF и Default для MaxSite CMS. Вы просто подключаете другой css-файл и шаблон сразу меняет своё оформление.
В шаблоне MF есть 15 разных цветовых схем.
Но есть ещё один способ. Исходный framework.css
подключаем как есть, но потом подключаем ещё один css-файл, который содержит только то, что нужно изменить.
Строго говоря, этот вопрос лежит в плоскости методологии css-верстки. Каждый верстальщик выбирает свой путь, поэтому я не думаю, что есть какой-то один абсолютно правильный. Но, лично для меня, вопрос удобства верстки стоит на первом месте. Возможно из-за того, что мне приходится делать много шаблонов, поэтому я ищу оптимальный вариант.
Верстка с Berry CSS
Эту идею я вынашивал уже пару лет, но из-за войны просто не смог довести её до логического завершения. Версия 4 Berry CSS подразумевала, что каждый дизайн нужно скомпилировать в отдельный css-файл. После этого он подключается и всё прекрасно работает.
Но, если посмотреть на изменения между файлами, то они будут касаться только цветовых палитр. Когда-то у меня была задумка вынести из Berry CSS отдельным файлом только цвета, но это оказалось неудобно. Поэтому я остановился на sass-компиляции, благо это несложно.
Моя новая идея в том, что все цвета можно задать в виде css-переменных, а в самих классах их использовать без указания жесткого дефолтного цвета.
Эту версию Berry CSS пока не выкладывал.
Чтобы было понятно. Например есть класс t-red
, который задаётся так:
.t-red { color: var(--red); }
Сам же цвет задаётся отдельно (там же в Berry CSS):
:root { --red: #f00; }
Теперь, если мне нужно изменить этот цвет на другой оттенок, то в отдельном файле (например my.css
) мне достаточно переопределить css-переменную:
:root { --red: #f71030; }
Все те классы, где используется var(--red)
автоматом сменят свой цвет на новый.
Таким образом отпадает необходимость sass-компиляции и наличия огромного количества однотипных css-файлов.
Чтобы понять насколько это круто, понажимайте на кнопки, которые меняют цветовой дизайн этой страницы. Верстка выполнена в Material Design, поэтому достаточно просто переопределить primary палитру.
Это демо эквивалентно следующему css-коду:
:root { --primary-grad: 340deg; --primary-saturat: 60%; --primary950: hsl(var(--primary-grad) var(--primary-saturat) 7%); --primary900: hsl(var(--primary-grad) var(--primary-saturat) 10%); --primary850: hsl(var(--primary-grad) var(--primary-saturat) 15%); --primary800: hsl(var(--primary-grad) var(--primary-saturat) 20%); --primary750: hsl(var(--primary-grad) var(--primary-saturat) 25%); --primary700: hsl(var(--primary-grad) var(--primary-saturat) 30%); --primary650: hsl(var(--primary-grad) var(--primary-saturat) 35%); --primary600: hsl(var(--primary-grad) var(--primary-saturat) 40%); --primary550: hsl(var(--primary-grad) var(--primary-saturat) 45%); --primary500: hsl(var(--primary-grad) var(--primary-saturat) 50%); --primary450: hsl(var(--primary-grad) var(--primary-saturat) 55%); --primary400: hsl(var(--primary-grad) var(--primary-saturat) 60%); --primary350: hsl(var(--primary-grad) var(--primary-saturat) 65%); --primary300: hsl(var(--primary-grad) var(--primary-saturat) 70%); --primary250: hsl(var(--primary-grad) var(--primary-saturat) 75%); --primary200: hsl(var(--primary-grad) var(--primary-saturat) 80%); --primary150: hsl(var(--primary-grad) var(--primary-saturat) 85%); --primary100: hsl(var(--primary-grad) var(--primary-saturat) 90%); --primary50: hsl(var(--primary-grad) var(--primary-saturat) 94%); }
Вторая кнопка делает тоже самое, только используется оттенок 100deg и 45% насыщенность, третья 180deg и 20%.
Посмотрите страницу тестирования дизайна, который использует классы Berry CSS. В ней 17 цветовых палитр и 11 фиксированных цветов. Их хватит, чтобы охватить все потребности при создании дизайна шаблона.
Теперь же есть ещё и возможность изменить цвет каждой палитры. На практике достаточно поменять primary и secondary.
Почему это важно?
Как бы ни старались разработчики css-фреймворков или дизайнеры сайтов, самая вариативная часть — это основные цвета. Даже если дизайнер будет семь пядей во лбу, его «идеальный» цвет может не понравится клиенту. Лично у меня были клиенты, которым нравились сочетания, вроде коричневый + зелёный, причём в таких оттенках, что я бы в жизни не подумал, что это может быть основным цветом сайта. То есть мы должны исходить из предпосылки, что абсолютно любой цвет может быть изменён клиентом.
Поэтому механизм смены цвета так важен.
При использовании Berry CSS у вебмастера (и клиента) есть сразу несколько вариантов.
Первый — использовать встроенные дефолтные палитры. Если не предполагается последующая смена цвета, то вполне себе легально можно использовать классы вида t-ingigo600
, bg-teal200
и т.п. Сами по себе все эти палитры используются как вторичные цвета и часто применяются по месту, особенно когда нужно использовать цвет в тексте страницы. Сам по себе цвет может быть примерным: зеленый, красный — без явного уточнения.
Второй вариант — использование фиксированных цветов, которые задаются как классы t-color1..9
, bg-color1..9
. Они удобны тем, что иногда клиент хочет видеть конкретный цвет, например фон шапки, подвала, цвет окантовки виджета и т.п. В этом случае можно просто задать точный цвет в css-переменной и использовать на странице. Опыт показывает, что 9 цветов перекрывают практически любую задачу по сайту.
Третий вариант — использование Material Design. Здесь три палитры, которые настраиваются либо фиксировано (например с помощью color.adobe.com), либо как оттенок hsl/hsb. Это самый гибкий вариант, поскольку смена цвета возможна в любой момент, но при этом доступна вся яркостная палитра цвета.
Berry CSS, пожалуй, единственный css-фреймворк, который изначально содержит такое количество готовых палитр. В остальных, например Tailwind CSS в последних версиях я насчитал 22 палитры. Но из них 5 — это оттенки серого, а остальные цвета слишком близки друг к другу по оттенкам. Фактически Tailwind CSS предлагает наборы фиксированных цветов, а не палитры как таковые и не оперирует Material Design. В Berry CSS палитра же задаётся как оттенок (градус) и насыщенность, а яркость рассчитывается автоматом (при sass-компиляции). Tailwind CSS оперирует обычными hex-цветами — в них нет hsl-модели.
Я когда-то использовал такой же подход (задолго до Tailwind CSS), но перешел на hsl/hsb, потому что эта модель используется профессиональными дизайнерами. И они составляют палитры либо как набор фиксированных цветов, либо как как яркостные цвета одного оттенка.
Суть всего это в том, что дизайн сайта будет определяться профессионализмом дизайнера. Даже скорее его художественными способностями и умением видеть и понимать цвета при рисовании акрилом или гуашью. Но, таких дизайнеров, я думаю, не так много. Подавляющее большинство просто тыкают пипеткой в фотошопе, подбирая что-то приятное. И как раз для такого большинства и годится предлагаемая схема, когда используется одна основная палитра. В ней не нужно подбирать цвет (оттенок и насыщенность), а вся верстка происходит в рамках яркостных оттенков.
Эти оттенки доступны в виде классов t-primaryXXX
, bg-primaryXXX
от 50 до 950 с шагом 50. Здесь ошибиться очень сложно, потому что монохромная палитра допускает абсолютно любые цветовые оттенки при соблюдении яркостного контраста. Думаю, каждый поймёт, что оттенок 100 и 200 будут иметь малый контраст, а 350 и 600 сильный. И практически не важно какой это будет цвет: хоть красный, хоть малиновый.
Итого по шаблонам в Albireo CMS
Albireo CMS предлагает не просто комплексный подход, а полностью готовые решения для создания и редактирования шаблонов. Это не какая-то абстрактная «рыба», а нормальная общепонятная схема для любого мало-мальски грамотного верстальщика.
Я хочу, чтобы Albireo CMS максимально эффективно могла учесть современные подходы к вёрстке сайтов. При этом я не хочу загонять верстальщика в лоно одного css-фреймворка. Поэтому моя идея проста: предоставить полностью готовый к использованию вариант шаблона на Berry CSS и показать как он работает, а верстальщик уже сам подумает как ему использовать свою любимый фреймворк.
Слава Украине! Смерть рашистам!