css-magic-gradient — генерация CSS-градиентов с реактивностью для Vue 3

В веб-интерфейсах градиенты то и дело нужны и «по месту»: от фона карточки до кнопок и декоративных полос. Писать вручную linear-gradient(to right, #aabbcc, #ddeeff) и подставлять цвета из темы или из формы — неудобно, легко ошибиться в формате, а при смене цвета (пользователь выбрал другой акцент, переключилась тема) приходится заново собирать строку и обновлять стиль. Пакет css-magic-gradient как раз про то, чтобы один раз описать градиент (линейный, радиальный или конический), получить готовую CSS-строку и во Vue 3 — реактивно пересчитывать её при изменении базового цвета или опций, без ручных подписок и watchers.
В этом посте — зачем выносить генерацию градиентов в отдельный слой, что поддерживается (линейные, радиальные, конические градиенты, кастомные остановки, многослойность, радужный конический), как пользоваться ядром без фреймворка и как подключить реактивные хуки для Vue 3. Упор на реактивность: передаёте ref цвета и опций — градиент обновляется сам при любом изменении.
Зачем вообще реактивная генерация градиентов
На практике с градиентами часто получается так: в одном компоненте подставляем цвет из пропса в linear-gradient(...), в другом — из темы или из формы выбора цвета, в третьем — снова та же сборка строки, но с другими направлениями или количеством шагов. Типичные ситуации:
- Единый источник правды: хочется один раз описать «как строится градиент от базового цвета» (смещение яркости, направление, количество шагов) и во всём приложении получать валидную CSS-строку. Иначе в одном месте градиент «светлее вверху», в другом — «светлее справа», и визуал расходится.
- Реактивность без костылей: при изменении базового цвета (тема, выбор пользователя, проп) строка градиента должна пересчитываться сама. Вручную в
watchили при каждом рендере вызывать генератор и вешать результат вstyle— дублирование и риск забыть обновить при смене опций. - Гибкие сценарии: не только «один цвет + авто-осветление», но и кастомные остановки цветов, многошаговые линейные градиенты, радиальные слои, конические с поворотом оттенка или готовый радужный конус. Собирать такие строки вручную — неудобно и легко ошибиться в синтаксисе.
css-magic-gradient даёт единый слой: вы вызываете createLinearGradient, createRadialGradient или createConicGradient с базовым цветом и опциями (или с массивом остановок), получаете готовую CSS-строку. Во Vue 3 композиции useLinearGradient, useRadialGradient, useConicGradient принимают ref цвета и опций и возвращают computed-строку: при изменении ref градиент пересчитывается автоматически, компонент перерисовывается — никакой ручной подписки. В итоге и дизайн-систему держать проще (одни правила генерации), и динамические темы/формы с выбором цвета работают «из коробки».
Что внутри: ядро и плагин для Vue 3
Пакет небольшой: ядро с чистыми функциями генерации (без зависимостей от фреймворка), плюс экспорт плагина и композиций для Vue 3. Подключаете только то, чем пользуетесь.
| Что подключать | Назначение |
|---|---|
| css-magic-gradient (ядро) | createLinearGradient, createMultiStepLinearGradient, createRadialGradient, createConicGradient, createRainbowConicGradient. Работает в любом окружении (Node, браузер). |
| Vue 3 | VueGradientPlugin (опционально, для глобальных свойств) и useLinearGradient, useRadialGradient, useConicGradient — реактивные ComputedRef<string> при передаче ref'ов. |
Установка — одна команда:
bash
npm install css-magic-gradient
Для Vue-проекта нужна peer-зависимость vue (^3.5.27) — как правило, она уже есть. Типы поставляются из коробки (TypeScript). Внутри для работы с цветом используется пакет color-value-tools (нормализация hex, яркость, hue rotation, rgba и т.д.).
Как устроено ядро: линейный градиент
Всё строится вокруг вызова функции с базовым цветом (или массивом остановок) и опциональными опциями. Результат — готовая строка вида linear-gradient(...), radial-gradient(...) или conic-gradient(...), которую можно подставить в style.background или в CSS.
createLinearGradient
Два режима:
- От одного цвета: передаёте строку (hex или CSS-переменную) и опции — градиент строится из автоматически осветлённого цвета в начало и базового в конец.
- Кастомные остановки: передаёте массив
{ color, position?, opacity? }и опции направления — строка собирается как есть.
| Поле | Описание |
|---|---|
| baseColor (или массив остановок) | Цвет в формате hex, CSS-переменная var(--accent) и т.д.; либо массив LinearGradientColorStop[]. |
| options.offsetPercent | Смещение яркости для начального цвета, -100..100 (по умолчанию 15). |
| options.direction | Направление: 'to bottom', 'to top', 'to right', 'to left' или своя строка (по умолчанию 'to bottom'). |
| options.angle | Угол в градусах (перекрывает direction). |
| options.fallbackColor | Цвет подстановки, если база — CSS-переменная и она не задана (по умолчанию '#f5e477'). |
Пример от одного цвета:
ts
import { createLinearGradient } from 'css-magic-gradient';
const gradient = createLinearGradient('#3b82f6', {
direction: 'to right',
offsetPercent: 20,
});
// linear-gradient(to right, осветлённый синий, #3b82f6)
Пример с кастомными остановками:
ts
const gradient = createLinearGradient(
[
{ color: '#00f', position: '0%' },
{ color: '#fff', opacity: 0.5, position: '50%' },
{ color: '#f00', position: '100%' },
],
{ direction: 'to right' }
);
createMultiStepLinearGradient
Строит линейный градиент из нескольких шагов по яркости от одного базового цвета — удобно для полос, разделителей, фонов с мягкими переходами.
ts
import { createMultiStepLinearGradient } from 'css-magic-gradient';
const gradient = createMultiStepLinearGradient('#10b981', 5, {
direction: 'to bottom',
offsetPercent: 25,
});
Радиальный и конический градиенты
createRadialGradient
Принимает базовый цвет и опции: форма (circle/ellipse), размер, позиция центра. Можно задать свои остановки через useCustomColors и colors или собрать несколько слоёв через layers — в итоге получится строка из нескольких radial-gradient(...) через запятую (как в CSS).
| Опция | Описание |
|---|---|
| shape | 'circle' | 'ellipse' (по умолчанию 'ellipse'). |
| size | Строка ('farthest-corner', '50% 50%') или объект { width, height }. |
| position | Позиция центра, например 'center', '50% 50%'. |
| useCustomColors + colors | Свой массив остановок { color, opacity?, position? }. |
| layers | Массив слоёв, у каждого — shape, size, position, colors. |
Пример с кастомными цветами:
ts
import { createRadialGradient } from 'css-magic-gradient';
createRadialGradient('#00f', {
useCustomColors: true,
colors: [
{ color: '#00f', position: '0%' },
{ color: '#fff', opacity: 0.5, position: '100%' },
],
});
createConicGradient
Конический градиент от базового цвета: либо автоматические шаги по яркости (или по hue при hueRotation: true), либо полностью свои остановки через colors.
| Опция | Описание |
|---|---|
| fromAngle | Начальный угол в градусах (по умолчанию 0). |
| position | Позиция центра (по умолчанию '50% 50%'). |
| colors | Кастомные остановки { color, opacity?, position? }. |
| hueRotation | true — шаги по кругу оттенков от базового цвета. |
| steps | Количество шагов при авто-генерации (по умолчанию 8). |
| offsetPercent | Смещение яркости для шагов (по умолчанию 20). |
createRainbowConicGradient
Готовый радужный конический градиент (HSL по кругу). Удобно для спиннеров, декоративных элементов.
ts
import { createRainbowConicGradient } from 'css-magic-gradient';
const rainbow = createRainbowConicGradient({
fromAngle: 0,
position: '50% 50%',
saturation: 80,
lightness: 60,
steps: 12,
});
Использование без Vue: только ядро
В «голом» JS или в любом фреймворке без композиций достаточно импортировать функции и подставлять результат в стили:
ts
import {
createLinearGradient,
createRadialGradient,
createConicGradient,
createRainbowConicGradient,
} from 'css-magic-gradient';
const linear = createLinearGradient('#ef4444', { direction: 'to right' });
const radial = createRadialGradient('#22c55e', { shape: 'circle', position: '30% 70%' });
const conic = createConicGradient('#8b5cf6', { hueRotation: true, steps: 6 });
const rainbow = createRainbowConicGradient({ steps: 8 });
document.querySelector('.panel').style.background = linear;
При смене цвета или опций вызываете функцию заново и снова присваиваете строку — реактивность в таком сценарии вы реализуете сами (например, через подписку на тему или обработчик формы).
Реактивность во Vue 3: главный акцент
Во Vue-приложении не хочется в каждом компоненте вручную вызывать генератор в watch или при каждом обновлении пропа. Композиции useLinearGradient, useRadialGradient, useConicGradient принимают реактивные аргументы: базовый цвет и опции могут быть как обычными значениями, так и ref (или reactive). Возвращают они ComputedRef<string> — то есть при изменении любого из ref'ов градиент пересчитывается автоматически, Vue перерисовывает шаблон. Никакой ручной подписки и отписки.
Внутри используется одна и та же логика: computed(() => createXxxGradient(resolve(baseColor), resolve(options))), где resolve разворачивает ref. Поэтому:
- передали ref цвета из темы или из формы — градиент обновляется при смене цвета;
- передали ref опций (направление, угол, количество шагов) — при смене опций строка пересчитывается сама.
Регистрация плагина (опционально)
Плагин вешает на app.config.globalProperties методы $useLinearGradient, $useRadialGradient, $useConicGradient — удобно, если хотите вызывать из Options API. Для реактивности в <script setup> плагин не обязателен: достаточно импортировать хуки.
ts
import { createApp } from 'vue';
import { VueGradientPlugin } from 'css-magic-gradient';
import App from './App.vue';
const app = createApp(App);
app.use(VueGradientPlugin);
app.mount('#app');
Использование в компоненте: ref и computed
В любом компоненте подключаете композицию и передаёте ref цвета и при необходимости ref опций — получаете реактивную строку градиента:
vue
<script setup lang="ts">
import { ref } from 'vue';
import { useLinearGradient, useRadialGradient, useConicGradient } from 'css-magic-gradient';
const color = ref('#3b82f6');
const direction = ref<'to right' | 'to bottom'>('to right');
const linearGradient = useLinearGradient(color, {
direction,
offsetPercent: 15,
});
const radialGradient = useRadialGradient(color, { shape: 'circle' });
const conicGradient = useConicGradient(color, { hueRotation: true, steps: 6 });
</script>
<template>
<div
class="card"
:style="{ background: linearGradient }"
/>
<div
class="spot"
:style="{ background: radialGradient }"
/>
<div
class="wheel"
:style="{ background: conicGradient }"
/>
</template>
Как только пользователь или тема меняет color или direction, linearGradient, radialGradient и conicGradient пересчитываются, Vue обновляет DOM — реактивность «из коробки».
Опции тоже реактивные
Опции можно передать целиком из ref — например, настройки градиента приходят из store или из формы:
vue
<script setup lang="ts">
import { ref, computed } from 'vue';
import { useLinearGradient } from 'css-magic-gradient';
const color = ref('#f59e0b');
const options = ref({
direction: 'to bottom' as const,
offsetPercent: 20,
angle: undefined as number | undefined,
});
const gradient = useLinearGradient(color, options);
// Меняем направление по клику — градиент обновится сам
function setDirection(dir: 'to right' | 'to bottom') {
options.value = { ...options.value, direction: dir };
}
</script>
<template>
<div :style="{ background: gradient }">
<button @click="setDirection('to right')">To right</button>
<button @click="setDirection('to bottom')">To bottom</button>
</div>
</template>
Никаких watch на color или options: композиция внутри использует computed, поэтому зависимости собираются автоматически.
Композables и производные значения
Удобно комбинировать градиент с другими вычислениями — например, два варианта (обычный и для hover) оба реактивны от одного цвета:
vue
<script setup lang="ts">
import { ref } from 'vue';
import { useLinearGradient } from 'css-magic-gradient';
const color = ref('#6366f1');
const baseGradient = useLinearGradient(color, { direction: 'to right', offsetPercent: 15 });
const hoverGradient = useLinearGradient(color, { direction: 'to left', offsetPercent: 25 });
</script>
<template>
<div
class="card"
:style="{ background: baseGradient }"
@mouseenter="($event.currentTarget as HTMLElement).style.background = hoverGradient.value"
@mouseleave="($event.currentTarget as HTMLElement).style.background = baseGradient.value"
/>
</template>
На практике hover лучше реализовать через CSS-переменную или класс; здесь важно то, что оба градиента реактивны и пересчитываются при смене color.
Примеры под реальные задачи
1. Градиент от темы (CSS-переменная или ref)
Задача: фон блока должен быть линейным градиентом от акцентного цвета темы. Тема меняется — градиент обновляется сам.
vue
<script setup lang="ts">
import { computed } from 'vue';
import { useLinearGradient } from 'css-magic-gradient';
const themeColor = computed(() => {
return getComputedStyle(document.documentElement).getPropertyValue('--color-accent').trim() || '#3b82f6';
});
const gradient = useLinearGradient(themeColor, { angle: 135, offsetPercent: 20 });
</script>
<template>
<header :style="{ background: gradient }">...</header>
</template>
Если акцент хранится в ref (например, из Pinia или provide/inject), передаёте этот ref — при смене темы ref обновляется, градиент пересчитывается.
2. Выбор цвета в форме
Задача: пользователь выбирает цвет в <input type="color"> или в пикере — карточка сразу показывает градиент от этого цвета.
vue
<script setup lang="ts">
import { ref } from 'vue';
import { useLinearGradient } from 'css-magic-gradient';
const pickedColor = ref('#8b5cf6');
const gradient = useLinearGradient(pickedColor, {
direction: 'to bottom',
offsetPercent: 25,
});
</script>
<template>
<div>
<input v-model="pickedColor" type="color" />
<div class="preview" :style="{ background: gradient }" />
</div>
</template>
Реактивность обеспечивает связку v-model → ref → computed внутри useLinearGradient: при смене цвета градиент обновляется без единого watch.
3. Список карточек с разными градиентами
Задача: у каждой карточки свой акцентный цвет, градиент строится от него по одному правилу. Выносим карточку в отдельный компонент — тогда у каждого экземпляра свой реактивный градиент от своего ref.
vue
<!-- CardItem.vue -->
<script setup lang="ts">
import { toRef } from 'vue';
import { useLinearGradient } from 'css-magic-gradient';
const props = defineProps<{ color: string; title: string }>();
const gradient = useLinearGradient(toRef(props, 'color'), { direction: 'to right', offsetPercent: 15 });
</script>
<template>
<div class="card">
<div class="card-bg" :style="{ background: gradient }" />
<h3>{{ title }}</h3>
</div>
</template>
В родителе передаёте color и title; при смене color градиент в карточке обновится сам. Для списка с фиксированными цветами можно и без ref — передать просто item.color, тогда градиент пересчитается при смене пропа.
4. Радужный спиннер и декоративный конус
Задача: индикатор загрузки или декоративный круг с радужным коническим градиентом — без Vue достаточно один раз вызвать createRainbowConicGradient; если параметры (steps, fromAngle) должны меняться по UI — оборачиваете в ref и используете хук (для радужного градиента своего хука в пакете нет, но опции можно держать в ref и вызывать createRainbowConicGradient внутри computed).
ts
import { createRainbowConicGradient } from 'css-magic-gradient';
const rainbow = createRainbowConicGradient({ steps: 8, fromAngle: 0 });
document.querySelector('.spinner').style.background = rainbow;
Краткий итог
css-magic-gradient закрывает типичные задачи при работе с градиентами: один раз описать правило (линейный от цвета с offset, многошаговый, радиальный с слоями, конический с hue rotation или кастомные остановки), получать валидную CSS-строку из ядра и во Vue 3 — реактивно через useLinearGradient, useRadialGradient, useConicGradient. Передаёте ref цвета и при необходимости ref опций — градиент пересчитывается при любом изменении, без ручных подписок и watchers. Кастомизация через полный набор опций (направление, угол, форма, размер, позиция, свои остановки, слои), при необходимости — готовый радужный конический градиент через createRainbowConicGradient. В итоге логика «как строится градиент» живёт в одном месте, код остаётся предсказуемым, а реактивность во Vue берёт на себя пакет.
Пакет на npm: css-magic-gradient
Репозиторий: macrulezru/css-magic-gradient