Стили, скрипты и картинки темы
Как подключать свои файлы, что магазин добавляет сам и почему порядок подключения важен.
На этой странице
- Три файла, которые магазин просит всегда
- Подключение своих файлов
- Вывод собранного списка
- Склейка: один файл вместо двадцати
- Что попадает в склейку, а что нет
- Как сделать свой файл пригодным для склейки
- Если тема обновляет часть страницы сама
- Как проверить
- Как выключить склейку
- Что магазин подключает сам
- Сторонние библиотеки — файлами, а не ссылкой
- Кэш браузера: две ловушки
- Картинки
- Шрифты
- Цвета настройками
- Светлый фон или тёмный
- Пустая папка не доедет до клиента
Файлы оформления лежат в папке assets/ внутри темы и отдаются браузеру по
адресу /theme-assets/имя-темы/…. Этот адрес всегда доступен в шаблоне:
<img src="{$theme.assets}/img/logo.svg" alt="{$storefront.name}">
Файлы за пределами assets/ браузеру не отдаются. Шаблоны, манифест и
переводы через этот адрес не прочитать — это защита, а не недоработка.
Три файла, которые магазин просит всегда
На каждой странице магазин подключает три файла вашей темы:
assets/vendor/fontawesome/all.min.css
assets/css/theme.css
assets/js/theme.js
Их нет — страница не сломается, но в консоли браузера будет три ошибки 404. Заведите их сразу, хотя бы пустыми: набор значков вам, скорее всего, понадобится другой, но файл по этому пути магазин попросит в любом случае.
Подключение своих файлов
{block name="page_assets"}
{asset_css file="css/product.css"}
{asset_js file="js/product.js" defer=true}
{/block}
| Параметр | Что делает |
|---|---|
file |
путь внутри assets/ вашей темы |
inline |
вставить содержимое прямо в страницу, а не ссылкой |
key |
имя, по которому файл не подключится дважды |
head / footer |
куда выводить |
| остальные | уезжают атрибутами в <link> или <script> |
Всё, что не названо, становится атрибутом: defer=true, async=true,
media="print", type="module".
Один и тот же файл, подключённый дважды, выводится один раз. Если один и тот же
файл лежит под разными именами — задайте им общий key, и подключится только
первый.
Вывод собранного списка
{assets_head} в <head>
{assets_footer} перед </body>
В эти два места выводятся и ваши файлы, и те, что магазин подключает сам, и те, что добавляют дополнения. Без них витрина останется без оформления и без поведения.
Подключать файл можно в любом месте шаблона — в шапке, в подвале, внутри
{include} карточки товара. Магазин печатает оба списка последними, когда
страница собрана целиком, поэтому стиль, объявленный в подвале, всё равно
окажется в <head>. Так же подключают свои файлы дополнения: чат в подвале,
отметка акции в карточке, маска телефона в шапке.
До ядра 2.86.0 было иначе: файл, объявленный после {assets_head}, в шапку не
попадал, и блок page_assets обязан был стоять раньше. Старые каркасы с таким
порядком работают как прежде — переставлять ничего не нужно.
Склейка: один файл вместо двадцати
Магазин склеивает стили и скрипты страницы в сжатые файлы и отдаёт их из
/storage/bundles/. Покупатель с телефона платит за каждый запрос к серверу, а
страница темы с дополнениями просит полтора-два десятка файлов. На главной
штатной темы CartiX вместо 23 файлов теперь грузятся 4.
Склеек два набора — так же, как Битрикс отделяет файлы ядра и шаблона от файлов страницы:
| Набор | Что в нём | Как кешируется |
|---|---|---|
| общий | стили и скрипт темы, значки, корзина, живой поиск | одинаковый на всех страницах — браузер скачивает его один раз |
| страницы | файлы этой страницы и дополнений на ней | свой у каждого вида страницы, но маленький |
Без наборов каждая страница получала бы свою склейку со всей темой внутри, и покупатель скачивал бы стиль темы заново на главной, в каталоге и в карточке.
Скрипты дополнений склеиваются отдельно от скриптов темы и магазина. В одном файле ошибка в чужом скрипте остановила бы всё, что идёт за ним, и кривое дополнение выключило бы оформление заказа. Стили так не ломаются, поэтому их клеят вместе.
Ничего включать не нужно: склейка работает для любой темы. Правите файл — меняется имя склейки, и браузер берёт свежую версию.
Что попадает в склейку, а что нет
Склеиваются соседние файлы вашего магазина, подключённые через
{asset_css} и {asset_js}:
- стиль без атрибутов (или с
media="all"); - скрипт ровно с
defer=true.
Остальные остаются отдельными и рвут цепочку своего вида, чтобы не поменялся порядок:
| Файл | Почему отдельно |
|---|---|
чужой адрес (https://…) |
его содержимого у магазина нет |
стиль с @import |
@import обязан стоять в начале файла, в середине склейки браузер его выбросит |
стиль с media="print" и т. п. |
склейка применилась бы ко всем экранам |
скрипт без defer, с async, type="module" |
он выполняется в другой момент, и порядок поменялся бы |
скрипт со своими атрибутами (data-…) |
он читает их из своего тега, а в склейке тега нет |
Стиль между двумя скриптами (и скрипт между стилями) цепочку не рвёт: на
порядок стилей скрипты не влияют, а defer-скрипты выполняются после разбора
страницы, когда бы их ни объявили.
Как сделать свой файл пригодным для склейки
Настройки скрипту передавайте объектом, а не атрибутами тега. Такой файл склеить нельзя:
{asset_js file="js/slider.js" defer=true data-speed="500"}
А такой можно — настройки встроенным скриптом, файл отдельно:
{asset_js inline="window.MySlider = { speed: 500 };" key="my-slider" head=true}
{asset_js file="js/slider.js" defer=true}
// js/slider.js
var speed = (window.MySlider || {}).speed || 300;
@import шрифтов вынесите в отдельный маленький файл — остальное
оформление склеится.
Скрипт, которому нужен другой скрипт, подключайте после него, оба с
defer. Внутри склейки порядок тот же, что в шаблоне.
Если тема обновляет часть страницы сама
Часто тема обновляет кусок страницы, перечитав её целиком: корзину после «+»,
список после фильтра или «Показать ещё». Стили блоков, которых на странице до
этого не было (появился подарок, пришёл товар в акции), лежат в <head>
перечитанной страницы, а тема берёт из неё только кусок. Перенесите их
помощником магазина, и новый блок придёт оформленным:
fetch(url, { headers: { 'X-Requested-With': 'XMLHttpRequest' } })
.then(function (r) { return r.text(); })
.then(function (html) {
var doc = new DOMParser().parseFromString(html, 'text/html');
if (window.CartiX && CartiX.adoptStyles) CartiX.adoptStyles(doc);
document.querySelector('[data-cart-page]').innerHTML =
doc.querySelector('[data-cart-page]').innerHTML;
});
Помощник добавляет только стили, которых на странице ещё нет, и ставит их сразу за последним стилем страницы. Скрипты он не переносит: в подменённом куске они не выполняются, а повторный запуск скриптов страницы задвоил бы обработчики. Так устроены корзина и списки штатной темы CartiX и каталог магазина.
Как проверить
Откройте код страницы (Ctrl+U): в <head> будут ссылки вида
/storage/bundles/471ac0cc5ff814eb3afe.css. Если файл остался отдельной
ссылкой, значит, он не подошёл под правила выше, и по таблице видно почему.
Склейке нужна ссылка public/storage (php artisan storage:link; при установке
она создаётся сама). Если ссылки нет, магазин не ломает страницу, а отдаёт
файлы по отдельности, как раньше.
Как выключить склейку
Склейку выключают в панели: Настройки магазина → Основные настройки → Скорость витрины, переключатель «Склеивать стили и скрипты». Это пригодится, когда вы ищете ошибку в своём скрипте и хотите видеть его отдельным файлом, или когда своя тема или чужое дополнение не уживаются со склейкой.
⚠️ Будьте осторожны с этим переключателем. Он меняет то, как загружается каждая страница витрины, и после переключения магазин может работать неадекватно. Без склейки витрина открывается медленнее, а тема или дополнение, рассчитанные на склейку, могут оставить блоки без оформления или кнопки без ответа. После переключения откройте витрину и проверьте главную, карточку товара, корзину и оформление заказа, а когда закончите проверку, включите склейку обратно.
Если панель недоступна, склейку выключает строка CARTIX_BUNDLE_ASSETS=false в
файле .env магазина. Она сильнее переключателя: пока строка стоит, панель
показывает, что склейка выключена на сервере, и переключатель не действует.
Что магазин подключает сам
Тема этого не делает и не должна дублировать.
| Где | Что |
|---|---|
| везде | cart-sync.js — счётчик корзины и пометки «в корзине» |
| везде | search.css, search.js — живой поиск |
| страницы оформления | checkout.css, checkout.js |
| страница оплаты | pay.js |
| страницы товара | accessories.css, accessories.js — комплекты |
| страницы товара | packaging.css, packaging.js — счёт упаковками |
Свой checkout.css темы переопределять вид можно и нужно — он подключается
после магазинного. А вот свой скрипт оформления писать не стоит: см.
Атрибуты data-*.
Сторонние библиотеки — файлами, а не ссылкой
Слайдер, ползунок цены, набор значков кладутся в assets/vendor/ и подключаются
оттуда:
assets/vendor/
swiper/
nouislider/
fontawesome/
Почему не ссылкой на чужой сайт: внешний адрес — это чужая доступность, чужая скорость и чужие правила про данные покупателей. Упал внешний источник — у магазина рассыпалась вёрстка, и владелец не может ни починить, ни объяснить это покупателю. Плюс свой файл отдаётся с правильным типом и с длинным кэшем.
Единственное исключение — reCAPTCHA: её скрипт приходит с сайта Google, и это выбор владельца магазина, а не темы.
Кэш браузера: две ловушки
К каждому файлу, подключённому через {asset_css} и {asset_js}, магазин
дописывает метку версии: время изменения и размер файла. Поменяли файл —
поменялась метка, и браузер скачивает новый. Не меняли — браузер берёт файл из
своего кэша и не качает его заново на каждой странице. Сами файлы отдаются с
длинным кэшем: месяц и «не перепроверять».
Отсюда две ловушки, на которые наступают все.
Первая: страница пришла из кэша — в ней заморожена старая метка. Правка в редакторе «Дизайна» сбрасывает страницы витрины сама. Если вы меняли файл по FTP или из консоли, сбросьте страницы витрины вручную, иначе покупатель увидит новый стиль, только когда страница обновится в кэше.
⚠️ Вторая, и она хуже: файл, подключённый вручную, метки не получает вовсе.
{* так делать нельзя — файл залипнет у покупателя на месяц *}
<link rel="stylesheet" href="{$theme.assets}/css/my.css">
{* так правильно *}
{asset_css file="css/my.css"}
Через {$theme.assets} подключают картинки и шрифты, а стили и скрипты —
только тегами. Файл, написанный вручную, к тому же не попадает в склейку и
грузится отдельным запросом.
Картинки
<img src="{$theme.assets}/img/no-photo.svg" alt="">
Картинки темы — это заглушки, значки, фон: то, что принадлежит оформлению.
Фотографии товаров приходят в данных и режутся тегом {thumb}:
<img src="{thumb src=$product.image size=320}" alt="{$product.name}">
Подгрузку картинок при прокрутке владелец включает настройкой темы — она
приходит в листинге как image_lazy:
<img src="{thumb src=$product.image size=320}"
{if $image_lazy}loading="lazy"{/if} alt="{$product.name}">
Шрифты
Кладутся в assets/ и подключаются из своего же CSS:
@font-face {
font-family: 'Inter';
src: url('../fonts/inter.woff2') format('woff2');
font-display: swap;
}
Пути внутри CSS считаются от самого файла стилей — {$theme.assets} там не
работает.
Шрифт можно сделать настройкой темы ("type": "font"), тогда владелец выбирает
его в панели, а тема читает выбор:
<style>:root { --font: {$theme.font_family}, system-ui, sans-serif; }</style>
Цвета настройками
Удобный приём: цвета из настроек темы выводятся переменными CSS в каркасе, а всё остальное оформление опирается на них.
<style>
:root {
--primary: {$theme.colors.primary|default:'#4f46e5'};
--accent: {$theme.colors.accent|default:'#f59e0b'};
--bg: {$theme.colors.bg|default:'#ffffff'};
--text: {$theme.colors.text|default:'#1f2937'};
--container: {$theme.container_width|default:1240}px;
}
</style>
Так владелец меняет вид магазина из панели, а вам не нужно ни пересобирать стили, ни держать по файлу на каждую цветовую схему.
Светлый фон или тёмный
Когда владелец задаёт свой цвет фона, теме нужно понять, что на нём читается:
светлый логотип или тёмный, белые надписи или чёрные. Для этого есть
обработчик color_tone: он отдаёт light для светлого цвета, dark для
тёмного и пустую строку, если цвет не разобран.
<html data-header-tone="{$theme.color_header|color_tone}">
html[data-header-tone="light"] .logo__img--on-dark { display: none; }
html[data-header-tone="light"] .logo__img--on-light { display: block; }
Обработчик понимает те же записи цвета, что и поле color: #hex, rgb() и
hsl(). Граница светлого и тёмного совпадает с приёмом для надписей
oklch(from … clamp(0, (0.68 - l) * 999, 1) 0 0), поэтому логотип и текст на
одном фоне переключаются одновременно.
Пустая папка не доедет до клиента
⚠️ В пакет темы складываются файлы. Папка без единого файла в архив не
попадёт: завели assets/css/ пустой — у клиента её не будет.
Что ещё не попадает в пакет и почему — на странице Упаковка и публикация.