плагинvue-dragridАналогично по функциямvue-gridlayout, просмотреть эффекткликните сюда. Далее подробно объясняется каждая фиксация.
Готов к работе
- Первый клон проекта локально.
-
git reset --hard commit
Команда может сделать текущий заголовок точкой фиксации.
Завершить базовую верстку html
Нажмите кнопку «Копировать», чтобы скопировать весь идентификатор коммита. Затем запустите по корневому пути проектаgit reset
. Откройте index.html в браузере, чтобы просмотреть эффект. Основные HTML-результаты плагина следующие:
<!-- 节点容器 -->
<div class="dragrid">
<!-- 可拖拽的节点,使用translate控制位移 -->
<div class="dragrid-item" style="transform: translate(0px, 0px)">
<!-- 通过slot可以插入动态内容 -->
<div class="dragrid-item-content">
</div>
<!-- 拖拽句柄 -->
<div class="dragrid-drag-bar"></div>
<!-- 缩放句柄 -->
<div class="dragrid-resize-bar"></div>
</div>
</div>
Используйте vue для простой верстки узлов
Сначала переключите коммит, установите необходимые пакеты и выполните следующую команду:
git reset --hard 83842ea107e7d819761f25bf06bfc545102b2944
npm install
<!-- 启动,端口为7777,在package.json中可以修改 -->
npm start
Одним из шагов на этом шаге является создание среды, это можно сделать непосредственно, просмотрев файл конфигурации webpack.config.js.
Другой - расположение узлов.Основная идея состоит в том, чтобы рассматривать контейнер узла как сетку, и каждый узел может управлять положением узла через абсциссу (x) и ординату (y), а координата верхнего левого угла равна ( 0, 0); размер узла определяется шириной (w) и высотой (h); каждый узел также должен иметь уникальный идентификатор. Таким образом, структура данных узла узла:
{
id: "uuid",
x: 0,
y: 0,
w: 6,
h: 8
}
Значения w и h — это количество занятых ячеек сетки.Например, контейнер — это 24 ячейки, а ширина — 960 пикселей, а ширина каждой ячейки — 40 пикселей, тогда указанный выше узел отображается как 240 пикселей * 320 пикселей. , и он находится в верхнем левом углу контейнера.
Давайте посмотрим на логику, соответствующую dragrid.vue:
computed: {
cfg() {
let cfg = Object.assign({}, config);
cfg.cellW = Math.floor(this.containerWidth / cfg.col);
cfg.cellH = cfg.cellW; // 1:1
return cfg;
}
},
methods: {
getStyle(node) {
return {
width: node.w * this.cfg.cellW + 'px',
height: node.h * this.cfg.cellH + 'px',
transform: "translate("+ node.x * this.cfg.cellW +"px, "+ node.y * this.cfg.cellH +"px)"
};
}
}
Среди них cellW и cellH — это ширина и высота каждой сетки, так что легко вычислить ширину, высоту и смещение узлов.
Завершите перетаскивание одного узла
событие перетаскивания
- Используйте mousedown, mousemove, mouseup для реализации перетаскивания.
- Эти события привязаны к документу, и их нужно привязать только один раз.
Процесс выполнения примерно такой:
Мышь находится на ручке перетаскивания,onMouseDown
Метод срабатывает, после сохранения некоторых значений в eventHandler срабатывает движение мышиonMouseMove
метод при входе в первый разeventHandler.drag
Если это ложь, метод isDrag будет судить, является ли это поведением перетаскивания (перемещение на 5 пикселей по горизонтали или вертикали) в соответствии со смещением.Если это поведение перетаскивания, установите для атрибута перетаскивания значение true и выполните одновременноdragdrop.dragStart
метод (поведение перетаскивания будет выполнено только один раз), а затем мышь продолжает двигаться, и она начинает выполнятьсяdragdrop.drag
метод. После последнего отпускания мыши он выполнитonMouseUp
метод для сброса некоторого состояния обратно в исходное состояние при выполненииdragdrop.dragEnd
метод.
узел перетаскивания
Логика перетаскивания узлов инкапсулирована в файл dragdrop.js, основной метод —dragStart
,drag
,dragEnd
.
dragStart
В поведении перетаскивания этот метод выполняется только один раз, поэтому он подходит для выполнения некоторой работы по инициализации.На данный момент код выглядит следующим образом:
dragStart(el, offsetX, offsetY) {
// 要拖拽的节点
const dragNode = utils.searchUp(el, 'dragrid-item');
// 容器
const dragContainer = utils.searchUp(el, 'dragrid');
// 拖拽实例
const instance = cache.get(dragContainer.getAttribute('name'));
// 拖拽节点
const dragdrop = dragContainer.querySelector('.dragrid-dragdrop');
// 拖拽节点id
const dragNodeId = dragNode.getAttribute('dg-id');
// 设置拖拽节点
dragdrop.setAttribute('style', dragNode.getAttribute('style'));
dragdrop.innerHTML = dragNode.innerHTML;
instance.current = dragNodeId;
const offset = utils.getOffset(el, dragNode, {offsetX, offsetY});
// 容器偏移
const containerOffset = dragContainer.getBoundingClientRect();
// 缓存数据
this.offsetX = offset.offsetX;
this.offsetY = offset.offsetY;
this.dragrid = instance;
this.dragElement = dragdrop;
this.dragContainer = dragContainer;
this.containerOffset = containerOffset;
}
- Параметр el — это элемент маркера перетаскивания, offsetX — горизонтальное смещение мыши от маркера перетаскивания, а offsetY — вертикальное смещение мыши от маркера перетаскивания.
- Через el можно рекурсивно найти узел перетаскивания (dragNode) и контейнер перетаскивания (dragContainer).
- Элемент dragdrop — это реальный узел перетаскивания, управляемый мышью, а соответствующий узел макета станет заполнителем, который визуально отображается как эффект тени.
- Установка узла перетаскивания фактически устанавливает innerHTML щелкнутого узла перетаскивания на перетаскивание, а также применяет стиль к прошлому.
- Примеры сопротивления, на самом деле Dragrid.Vue пример, он создается в примерах крючковых функций в буфере кэша, здесь в этом примере можно получить из кэша в соответствии с именем, так что метод в этом примере может быть называется.
-
instance.current = dragNodeId;
После установки применяются стили узла перетаскивания и узла-заполнителя. - Смещение X и смещение Y в кэшированных данных — это смещения маркера перетаскивания относительно левого верхнего угла узла.
drag
После того, как поведение перетаскивания произойдет, движение мыши выполнит этот метод, и узел будет двигаться, постоянно обновляя стиль перетаскиваемого узла.
drag(event) {
const pageX = event.pageX, pageY = event.pageY;
const x = pageX - this.containerOffset.left - this.offsetX,
y = pageY - this.containerOffset.top - this.offsetY;
this.dragElement.style.cssText += ';transform:translate('+ x +'px, '+ y +'px)';
}
В основном это вычисление смещения узла относительно контейнера: расстояние между мышью и страницей — смещение контейнера — расстояние между мышью и перетаскиваемым узлом — это расстояние между узлом и контейнером.
dragEnd
В основном для сброса состояния. Логика относительно проста, поэтому я не буду вдаваться в подробности.
В этот момент одиночный узел уже может перемещаться с помощью мыши.
Заполнитель может следовать движению узла сопротивления
В этом разделе речь пойдет об узле-заполнителе (теневая часть заполнителя), перемещающемся вместе с перетаскиваемым узлом. Основная идея:
- Перетаскивая смещение узла из контейнера (x, y в методе перетаскивания), его можно преобразовать в координаты соответствующей сетки.
- Если преобразованные координаты изменяются, обновите координаты узла-заполнителя.
Код, добавленный к методу перетаскивания, выглядит следующим образом:
// 坐标转换
const nodeX = Math.round(x / opt.cellW);
const nodeY = Math.round(y / opt.cellH);
let currentNode = this.dragrid.currentNode;
// 发生移动
if(currentNode.x !== nodeX || currentNode.y !== nodeY) {
currentNode.x = nodeX;
currentNode.y = nodeY;
}
Переставить и переместить узлы вверх
В этом разделе есть два основных момента:
- Двумерный массив используется для представления сетки, так что информация о положении узлов может быть отмечена в этом двумерном массиве.
- Пока узел меняется в узлах, он должен быть переставлен, и каждый узел должен быть максимально перемещен вверх.
Построение двумерных массивов
getArea(nodes) {
let area = [];
nodes.forEach(n => {
for(let row = n.y; row < n.y + n.h; row++){
let rowArr = area[row];
if(rowArr === undefined){
area[row] = new Array();
}
for(let col = n.x; col < n.x + n.w; col++){
area[row][col] = n.id;
}
}
});
return area;
}
Двумерные данные могут быть динамически расширены по мере необходимости.Если в строке нет заполнения узлов, фактически сохраняется неопределенное значение. В противном случае значение идентификатора узла сохраняется.
метод компоновки
Узлы просматриваются в dragird.vue, а после изменения будет вызываться метод компоновки Код выглядит следующим образом:
/**
* 重新布局
* 只要有一个节点发生变化,就要重新进行排版布局
*/
layout() {
this.nodes.forEach(n => {
const y = this.moveup(n);
if(y < n.y){
n.y = y;
}
});
},
// 向上查找节点可以冒泡到的位置
moveup(node) {
let area = this.area;
for(let row = node.y - 1; row > 0; row--){
// 如果一整行都为空,则直接继续往上找
if(area[row] === undefined) continue;
for(let col = node.x; col < node.x + node.w; col++){
// 改行如果有内容,则直接返回下一行
if(area[row][col] !== undefined){
return row + 1;
}
}
}
return 0;
}
В макете метода компоновки обходятся все узлы, а метод moveup возвращает координату положения, до которой узел может подняться по вертикали, если она меньше фактической координаты, он будет двигаться вверх. Метод moveup по умолчанию начинает поиск с предыдущей строки, пока не обнаружит, что значение хранится в двумерном массиве (в новой строке уже есть элементы), после чего возвращает количество строк на данный момент плюс 1.
В этот момент при перетаскивании узла для перемещения узел-заполнитель будет перемещаться максимально вверх.Если есть только один узел, узел-заполнитель всегда будет перемещаться сверху.
Нисходящее движение связанных узлов
Когда перетаскиваемый узел перемещается, узел, который сталкивается с перетаскиваемым узлом и узлом, который он отправляет, сначала перемещается вниз на определенное расстояние, так что перетаскиваемый узел может быть перемещен в соответствующее положение, и, наконец, узел поднимается, как указано в предыдущий раздел.
См. метод перекрытия в dragrid.vue:
overlap(node) {
// 下移节点
this.nodes.forEach(n => {
if(node !== n && n.y + n.h > node.y) {
n.y += node.h;
}
});
}
n.y + n.h > node.y
Представляет узлы, которые могут столкнуться с перетаскиваемыми узлами, а также узлы под перетаскиваемыми узлами.
Этот метод вызывается в dragdrop.drag.
Обратите внимание, что настоящий метод имеет проблему, не учитывает, если узел столкновения относительно высокий,n.y += node.h
Узел не опускается к нижнему перетаскиванию узла, поэтому перетаскивание узлов будет складываться. Мы вернемся к решению.
зум
После понимания вышеизложенных идей, масштабирование на самом деле то же самое.Главное выполнить преобразование координат.При изменении координат будет вызываться метод перекрытия.
resize(event) {
const opt = this.dragrid.cfg;
// 之前
const x1 = this.currentNode.x * opt.cellW + this.offsetX,
y1 = this.currentNode.y * opt.cellH + this.offsetY;
// 之后
const x2 = event.pageX - this.containerOffset.left,
y2 = event.pageY - this.containerOffset.top;
// 偏移
const dx = x2 - x1, dy = y2 - y1;
// 新的节点宽和高
const w = this.currentNode.w * opt.cellW + dx,
h = this.currentNode.h * opt.cellH + dy;
// 样式设置
this.dragElement.style.cssText += ';width:' + w + 'px;height:' + h + 'px;';
// 坐标转换
const nodeW = Math.round(w / opt.cellW);
const nodeH = Math.round(h / opt.cellH);
let currentNode = this.dragrid.currentNode;
// 发生移动
if(currentNode.w !== nodeW || currentNode.h !== nodeH) {
currentNode.w = nodeW;
currentNode.h = nodeH;
this.dragrid.overlap(currentNode);
}
}
Измените размер (ширину и высоту) узла в соответствии со смещением расстояния между мышью и перетаскиваемым контейнером, где x1 — расстояние от контейнера после щелчка мышью, а x2 — расстояние от контейнера после перемещения расстояние, то разница dx равна расстоянию, на которое перемещается мышь, а dy — тому же самому.
На данный момент основная логика плагина в основном завершена.
[исправлено] Решить проблему, из-за которой большой блок в верхней части положения столкновения не двигался вниз
перекрытие изменено следующим образом:
overlap(node) {
let offsetUpY = 0;
// 碰撞检测,查找一起碰撞节点里面,位置最靠上的那个
this.nodes.forEach(n => {
if(node !== n && this.checkHit(node, n)){
const value = node.y - n.y;
offsetUpY = value > offsetUpY ? value : offsetUpY;
}
});
// 下移节点
this.nodes.forEach(n => {
if(node !== n && n.y + n.h > node.y) {
n.y += (node.h + offsetUpY);
}
});
}
Offsetupy Наконец-то хранит расстояние между верхним узлом и ущепленным узлом между всеми узлами, которые сталкиваются с перетаскиваемым узлом. Тогда значение OFFSETUPY будет добавлено в процесс перемещения вниз, чтобы убедиться, что все узлы перемещаются ниже притащенного узла.
Основная логика этого плагина здесь, читатели могут самостоятельно решить следующие проблемы:
- Предел масштабирования, когда достигается минимальная ширина, он не может продолжать масштабирование.
- Перетащите полосу прокрутки управления.
- Ограничения на перетаскивание границ.
- Перетаскивание вниз и перемещение происходит, когда он достигает 1/2 высоты узла столкновения.