Мир всем, на связи ShADAMoV!
Сегодня я бы хотел поведать вам о своём опыте взаимодействия с библиотекой GridStack. Расскажу о странностях и сложностях, с которыми столкнулся в ходе её интеграции во Vue 3 проект.
Прежде чем приступим, дисклеймер: автор данной статьи не претендует на истину в последней инстанции и так же не рассказывает про саму технологию, а лишь делится опытом взаимодействия с ней. Прежде чем читать дальше, настоятельно рекомендую прочесть документацию по данной библиотеке от автора (это займет не больше 10 минут).
Итак, погнали!
Для начала, надо данную библиотеку - установить. Я использовал для этого npm
npm install --save gridstack
Следующий шаг - подключение библиотеки в компонент.
import { GridStack } from 'gridstack'; import 'gridstack/dist/gridstack.min.css';
Далее, нам надо определиться со способом, которым мы будем загружать данные для отображения на сетке. Их всего два: js или html.
Допустим у вас есть некий массив blocks, в котором лежат объекты с полями, необходимыми для установки размера и позиции элемента.const blocks = [ { w: 2, h: 2, x: 0, y: 0, id: 'Unic1', type: 'Standart', }, { w: 2, h: 2, x: 0, y: 0, id: 'Unic2', type: 'Image', }, { w: 2, h: 2, x: 0, y: 0, id: 'Unic3', type: 'Text', }, ]
Поле type нам может понадобиться в том случае, если добавляемые блоки отличны друг от друга (например: разные компоненты).
Первый способ. Отобразим данный массив при помощи js.
<template> <div class="grid-stack"></div> </template> <script setup lang="ts"> import { onMounted } from 'vue'; import { GridStack } from 'gridstack'; import 'gridstack/dist/gridstack.min.css'; const blocks = [ { w: 2, h: 2, x: 0, y: 0, id: 'Unic1', type: 'Standart', }, { w: 2, h: 2, x: 0, y: 0, id: 'Unic2', type: 'Image', }, { w: 2, h: 2, x: 0, y: 0, id: 'Unic3', type: 'Text', }, ] // Тут будут лежать минимальные настройки для GridStack const gridStackOptions = { animate: true, cellHeight: '180px', float: true, column: 12, margin: 4, resizable: { handles: 'e, se, s, sw, w, nw, n, ne', }, } // Так как данная библиотека рабоатет с уже имеющимся HTML, выполняем всё в onMounted onMounted(() => { const grid = GridStack.init(gridStackOptions, 'grid-stack') // Загружаем данные в сетку blocks.map((block) => { grid.addWidget({ x: block.x, y: block.y, w: block.w, h: block.h, id: block.id, }) }) }) </script> <style lang=scss> .grid-stack-item { z-index: 1; border: 2px solid gray; user-select: none; border-radius: 12px; &:hover { box-shadow: 0 4px 14px 0 rgb(0 0 0 / 10%); } } </style>
На данном этапе, сетка уже должна иметь 3 элемента, которые вы можете свободно перемещать и изменять их размер. Минус такого подхода в том, что при добавлении не просто блока, а некоего компонента, начинаются танцы с бубнами, где рискуешь потерять реактивность, так как в этом случае, можно передавать содержимое widget'а только в виде html.
Второй способ (которым я и воспользовался), больше подходит под большинство задач, так как автор данной библиотеки, пользуется фрэймворком Angular, из‑за чего в фундамент заложено декларативное встраивание контента.
<template> <div class="grid-stack"> <div v-for="block in blocks" :key="block.id" :gs-x="block.x" :gs-y="block.y" :gs-w="block.w" :gs-h="block.h" :gs-id="block.id" class="grid-stack-item" > <div class="grid-stack-item-content"> <standart v-if="block.type === 'standart'" /> <text v-if="block.type === 'text'" /> </div> </div> </div> </template> <script setup lang="ts"> import { onMounted } from 'vue'; import { GridStack } from 'gridstack'; import 'gridstack/dist/gridstack.min.css'; import Standart from '../standart/index.vue'; import LineChart from '../text/index.vue'; const blocks = [ { w: 2, h: 2, x: 0, y: 0, id: 'Unic1', type: 'standart', }, { w: 2, h: 2, x: 0, y: 0, id: 'Unic2', type: 'image', }, { w: 2, h: 2, x: 0, y: 0, id: 'Unic3', type: 'text', }, ] // Тут будут лежать минимальные настройки для GridStack const gridStackOptions = { animate: true, cellHeight: '180px', float: true, column: 12, margin: 4, resizable: { handles: 'e, se, s, sw, w, nw, n, ne', }, } // Так как данная библиотека рабоатет с уже имеющимся HTML, выполняем всё в onMounted onMounted(() => { GridStack.init(gridStackOptions, 'grid-stack') }) </script> <style lang=scss> .grid-stack-item-content { z-index: 1; border: 2px solid gray; user-select: none; border-radius: 12px; &:hover { box-shadow: 0 4px 14px 0 rgb(0 0 0 / 10%); } } </style>
По сути, вы сделали всё, что нужно было для того, чтобы начать пользоваться GridStack. "Мои поздравления!" хотел бы я сказать, однако далее я расскажу о нервотрёпке, с которой я столкнулся...
Проблема
По идее, GridStack - это сетка, на которой можно размещать, перемещать и изменять блоки, которые мы на ней отобразим, однако какого было моё удивление, когда я узнал, что отобразить сетку background'ом - НЕЛЬЗЯ!
Сказать, что я был расстроен - ничего не сказать... Мне пришлось искать способы отображения сетки человеческим способом. Я пробовал и background-image, и liner-gradient, и через absolute размещенный позади сетки html-элемент. Скажу сразу, остановился на последнем способе, так как у меня была не просто сетка, а сетка, элементы которой прямоугольники, с закругленными углами...
Если ваша цель - просто сетка, без закруглений и прочей вундердизайнерской чепухи - ваш выбор linear-gradient.
<style lang="scss">
.grid-stack {
background: #f7f7f7;
background-image: linear-gradient(#fff 8px, transparent 8px),
linear-gradient(90deg, #fff 8px, transparent 8px);
background-size: 8.33% 180px, 8.33%;
background-position: 0 -4px, -4px 0;
}
.grid-stack-item-content {
z-index: 1;
border: 2px solid gray;
user-select: none;
border-radius: 12px;
background-color: white;
&:hover {
box-shadow: 0 4px 14px 0 rgb(0 0 0 / 10%);
}
}
</style>
Но если же вы из числа мазохистов, то я лишу вас удовольствия получить всю ту боль, что я пережил и покажу, как можно отрисовать красивые прямоугольники с закруглёнными углами.
<template>
<div class="grid-stack">
<div
v-for="block in blocks"
:key="block.id"
:gs-x="block.x"
:gs-y="block.y"
:gs-w="block.w"
:gs-h="block.h"
:gs-id="block.id"
class="grid-stack-item"
>
<div class="grid-stack-item-content">
<standart v-if="block.type === 'standart'" />
<text v-if="block.type === 'text'" />
</div>
</div>
<div class="background">
<!-- 4 - количество строк сетки, если сетка пуста -->
<div v-for="n in blocks.length ? gridRowCount : 4" :key="n" class="background__row">
<div class="background__row-item"></div>
<div class="background__row-item"></div>
<div class="background__row-item"></div>
<div class="background__row-item"></div>
<div class="background__row-item"></div>
<div class="background__row-item"></div>
<div class="background__row-item"></div>
<div class="background__row-item"></div>
<div class="background__row-item"></div>
<div class="background__row-item"></div>
<div class="background__row-item"></div>
<div class="background__row-item"></div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, computed, ref } from 'vue';
import { GridStack } from 'gridstack';
import 'gridstack/dist/gridstack.min.css';
const gridRowCount = ref<number>();
const blocks = [
{
w: 2,
h: 2,
x: 0,
y: 0,
id: 'Unic1',
type: 'standart',
},
{
w: 2,
h: 2,
x: 0,
y: 0,
id: 'Unic2',
type: 'image',
},
{
w: 2,
h: 2,
x: 0,
y: 0,
id: 'Unic3',
type: 'text',
},
];
// Тут будут лежать минимальные настройки для GridStack
const gridStackOptions = {
animate: true,
cellHeight: '188px',
float: true,
column: 12,
margin: 4,
resizable: {
handles: 'e, se, s, sw, w, nw, n, ne',
},
};
// Так как данная библиотека рабоатет с уже имеющимся HTML, выполняем всё в onMounted
onMounted(() => {
const grid = GridStack.init(gridStackOptions, 'grid-stack');
// Так как grid инницилизируется какое-то время, сразу получить данные о количестве строк - не удастся, поэтому ждём какое-то время
setTimeout(() => {
gridRowCount.value = grid.getRow();
console.log(gridRowCount);
}, 200);
});
</script>
<style lang="scss">
.grid-stack-item {
position: relative;
}
.grid-stack-item-content {
z-index: 1;
border: 2px solid #e5e7ee;
user-select: none;
border-radius: 12px;
background-color: white;
&:hover {
box-shadow: 0 4px 14px 0 rgb(0 0 0 / 10%);
}
}
.background {
position: absolute;
width: 100%;
height: 100%;
&__row {
display: flex;
width: 100%;
height: 188px;
column-gap: 10px;
padding: 4px;
&-item {
width: 8.3333%;
width: calc((100% - 110px) / 12);
height: 100%;
border: 1px solid #e5e7ee;
border-radius: 13px;
}
}
}
</style>
Я бы наверное еще написал про проблемы, возникающие при адаптации и работе данной библиотеке в связке с данными с бэка, однако написание статьи и так уже затанулось на 2 часа, а спать как бы хочется)
Надеюсь, что данная статья была для вас полезна! Всегда рад комментариям и обоснованной критике.
Всем мира <( ̄︶ ̄)>