Кнопка «наверх» в Django: почему в проде это уже не три строки JavaScript
Практический разбор создания и подключения кнопки «наверх» для Django-проектов. Показываю, почему простая scroll-to-top кнопка в проде требует учитывать Django Admin, мобильные интерфейсы, доступность, Content Security Policy, cookie-баннеры и другие фиксированные виджеты. Также разбираю пакет django-scroll-to-top: подключение, настройку через админку, ревизии, откат изменений и безопасную работу с SVG-иконками.
Как сделать scroll-to-top для сайта и Django Admin, не забыв про мобильные устройства, CSP, доступность, плавающие виджеты и нормальную настройку без правки шаблонов.
На первый взгляд кнопка «наверх» — задача на пять минут. Добавил position: fixed, обработчик window.scrollTo() — готово.
Но стоит этой кнопке появиться в живом проекте, как выясняется, что она пересекается с cookie-баннером, мешает чату поддержки, выглядит иначе в мобильной версии, не дружит со строгим CSP или пропадает из Django Admin.
В итоге маленькая UI-деталь начинает обрастать условиями. Я решил собрать их в отдельный Django-пакет — django-scroll-to-top
Когда трёх строк JavaScript достаточно
Для небольшого сайта, где нет сложной верстки, админки, CSP и требований к повторному использованию, самый простой вариант действительно выглядит примерно так:
Это нормальное решение. Не всегда стоит тянуть пакет ради одной кнопки.
Но в реальном Django-проекте быстро появляются дополнительные вопросы:
- когда именно показывать кнопку: после 300 пикселей, одного экрана или только при прокрутке вверх;
- что делать на коротких страницах;
- как не перекрыть cookie-баннер, чат, toast-уведомления или нижнюю мобильную навигацию;
- как дать пользователю закрыть кнопку;
- как не сломать клавиатурную навигацию и режим reduced motion;
- как сделать отдельное оформление для сайта и Django Admin;
- как не заставлять проект добавлять unsafe-inline в Content Security Policy;
- как позволить редактору или администратору изменить цвет, положение и иконку без нового деплоя.
Именно в этот момент «три строки JavaScript» превращаются в отдельный компонент.
Что я хотел получить
Цель была не в том, чтобы сделать ещё одну стрелку в правом нижнем углу. Хотелось собрать переиспользуемый компонент со следующими свойствами:
- Подключение сайта одной template-тегом.
- Отдельная поддержка обычных страниц и стандартного Django Admin.
- Настройка внешнего вида через админку, а не через постоянную правку CSS.
- Без jQuery, CDN, фронтенд-фреймворка и обязательной сборки.
- Безопасная работа при строгой CSP.
- Прогрессивное улучшение: без JavaScript остаётся обычная ссылка в начало страницы.
- Возможность жить рядом с другими фиксированными элементами интерфейса.
Пакет в итоге хранит обычные настройки установки в settings.py, а визуальное поведение — в базе данных. Это позволяет менять кнопку через Django Admin, публиковать новую версию настроек и при необходимости откатываться на предыдущую. В проекте есть отдельные профили для публичного сайта и Django Admin, а ревизии могут быть черновыми, опубликованными или архивными.
Быстрое подключение
Базовый сценарий начинается с установки:
В settings.py добавляем приложение. Если нужна поддержка стандартной админки, пакет должен идти раньше django.contrib.admin:
Включаем области, где должна работать кнопка:
Для публичной части добавляем URLConf пакета:
А в общий шаблон сайта — один тег:
На стандартном Django Admin ничего дополнительно вставлять не нужно: пакет использует обычный механизм разрешения шаблонов Django. Если же в проекте переопределён admin/base_site.html тег можно добавить вручную в блок footer
Настройка без превращения админки в редактор CSS
Мне не хотелось хранить в базе шаблоны, произвольный CSS или JavaScript. Это неудобно для сопровождения и создаёт лишнюю поверхность для ошибок.
Поэтому визуальная часть собрана из контролируемых вариантов:
- круг, квадрат, скруглённый квадрат или pill;
- заливка solid, outline, soft, ghost, glass или gradient;
- положение в любом углу экрана;
- отдельные размеры для desktop и mobile;
- светлая и тёмная тема;
- встроенные иконки, иконки от разработчика или загружаемые SVG;
- настройки тени, границы, opacity и focus ring.
Что происходит, когда рядом есть cookie-баннер или чат
Нижний правый угол страницы редко бывает свободен. Там часто живут:
- cookie-баннер;
- компактная кнопка после закрытия баннера;
- чат поддержки;
- кнопка обратного звонка;
- мобильная навигация;
- toast-уведомления.
Пакет умеет рассматривать такие элементы как препятствия. Для этого можно пометить элемент атрибутом:
Дальше для кнопки можно выбрать поведение: игнорировать препятствия, сдвинуться вдоль края, попробовать другой угол или скрыться, если безопасного места не осталось.
Для сложных виджетов есть отдельный адаптер: он может отслеживать появление и исчезновение элементов, например компактного launcher после закрытия cookie-баннера. При этом ни cookie-пакет, ни чат не становятся зависимостями django-scroll-to-top
Доступность — не отдельная галочка в конце
У кнопки есть понятное имя для screen reader, поддержка клавиатуры, видимый focus-visible, минимальный размер области нажатия и режим prefers-reduced-motion.
Если пользователь отключил анимации на уровне системы, плавная прокрутка не будет навязываться. Если JavaScript не загрузился, кнопка остаётся обычной ссылкой на начало документа.
Полный независимый аудит WCAG 2.2 AA и тестирование масштабирования 200% и 400% пока находятся в roadmap, поэтому называть компонент полностью сертифицированным по WCAG было бы неправильно. Но структурные требования — клавиатурная доступность, фокус, reduced motion, forced-colors и безопасная работа без JavaScript — уже заложены в компонент и покрываются тестами.
CSP и загружаемые SVG
В корпоративных проектах часто нельзя просто добавить inline-скрипт и включить unsafe-inline ради одной кнопки.
По умолчанию компонент использует same-origin CSS и JavaScript. Для него подходит политика такого вида:
Настраиваемые цвета и размеры отдаются не через inline-стили, а через версионированный stylesheet endpoint. Это позволяет сохранить простой контракт с одним template-тегом и не ослаблять CSP.
Отдельно пришлось подумать о загружаемых SVG. Админ не рендерит исходный файл как есть: SVG проходит санитарную обработку. Скрипты, обработчики событий, внешние ресурсы, встроенные документы и небезопасные namespace отклоняются. Для загружаемых иконок также хранится информация об авторе, источнике и лицензии.
Ревизии, публикация и откат
Одна из самых полезных вещей в пакете — не сама кнопка, а жизненный цикл её настроек.
Можно создать черновик, посмотреть результат в live preview, опубликовать изменения или вернуться к предыдущей версии. Это особенно удобно, когда кнопку настраивает не разработчик, а контент-менеджер или дизайнер.
У ревизий есть три состояния:
- draft — редактируемый черновик;
- published — текущая активная конфигурация;
- archived — сохранённая версия для отката.
Где пакет уместен, а где нет
django-scroll-to-top имеет смысл, когда кнопка нужна в нескольких проектах, должна работать в Django Admin, настраиваться без деплоя или жить в окружении со строгими требованиями к CSP и интерфейсу.
Для лендинга на одну страницу проще и правильнее написать несколько строк самостоятельно. Это будет быстрее, понятнее и дешевле в сопровождении.
Но если такая маленькая деталь начинает повторяться в нескольких продуктах, появляется необходимость поддерживать мобильную версию, доступность, независимые настройки для сайтов и админки, то отдельный компонент уже перестаёт быть избыточным.
Сейчас пакет выпущен как beta-версия 0.2.0, требует Python 3.10+ и поддерживает Django 4.2 LTS, 5.x и 6.0. Лицензия — MIT.
Исходный код, документация и примеры использования доступны в GitHub-репозитории проекта.
Пакет опубликован в PyPI под именем django-scroll-to-top.
Обратная связь, баг-репорты и предложения по интеграции с кастомными Django Admin-темами приветствуются в Issues.