Используйте vue для реализации функции grid-layout

Node.js внешний интерфейс JavaScript Vue.js

плагинvue-dragridАналогично по функциямvue-gridlayout, просмотреть эффекткликните сюда. Далее подробно объясняется каждая фиксация.

Готов к работе

  1. Первый клон проекта локально.
  2. 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 — это ширина и высота каждой сетки, так что легко вычислить ширину, высоту и смещение узлов.

Завершите перетаскивание одного узла

событие перетаскивания

  1. Используйте mousedown, mousemove, mouseup для реализации перетаскивания.
  2. Эти события привязаны к документу, и их нужно привязать только один раз.

Процесс выполнения примерно такой:

Мышь находится на ручке перетаскивания,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;
}
  1. Параметр el — это элемент маркера перетаскивания, offsetX — горизонтальное смещение мыши от маркера перетаскивания, а offsetY — вертикальное смещение мыши от маркера перетаскивания.
  2. Через el можно рекурсивно найти узел перетаскивания (dragNode) и контейнер перетаскивания (dragContainer).
  3. Элемент dragdrop — это реальный узел перетаскивания, управляемый мышью, а соответствующий узел макета станет заполнителем, который визуально отображается как эффект тени.
  4. Установка узла перетаскивания фактически устанавливает innerHTML щелкнутого узла перетаскивания на перетаскивание, а также применяет стиль к прошлому.
  5. Примеры сопротивления, на самом деле Dragrid.Vue пример, он создается в примерах крючковых функций в буфере кэша, здесь в этом примере можно получить из кэша в соответствии с именем, так что метод в этом примере может быть называется.
  6. instance.current = dragNodeId;После установки применяются стили узла перетаскивания и узла-заполнителя.
  7. Смещение 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

В основном для сброса состояния. Логика относительно проста, поэтому я не буду вдаваться в подробности.

В этот момент одиночный узел уже может перемещаться с помощью мыши.

Заполнитель может следовать движению узла сопротивления

В этом разделе речь пойдет об узле-заполнителе (теневая часть заполнителя), перемещающемся вместе с перетаскиваемым узлом. Основная идея:

  1. Перетаскивая смещение узла из контейнера (x, y в методе перетаскивания), его можно преобразовать в координаты соответствующей сетки.
  2. Если преобразованные координаты изменяются, обновите координаты узла-заполнителя.

Код, добавленный к методу перетаскивания, выглядит следующим образом:

// 坐标转换
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;
}

Переставить и переместить узлы вверх

В этом разделе есть два основных момента:

  1. Двумерный массив используется для представления сетки, так что информация о положении узлов может быть отмечена в этом двумерном массиве.
  2. Пока узел меняется в узлах, он должен быть переставлен, и каждый узел должен быть максимально перемещен вверх.

Построение двумерных массивов

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. Перетащите полосу прокрутки управления.
  3. Ограничения на перетаскивание границ.
  4. Перетаскивание вниз и перемещение происходит, когда он достигает 1/2 высоты узла столкновения.