在网格中拖动进行布局-vue-grid-layout

写在开头

哈喽,各位UU们好吖!😋

当前,2024年08月01日上午,七月,翻篇了,新的一个月开始啦,八月,希望百事从欢,顺顺利利哈。👻

在七月份,满勤打卡了一个月的运动,也算是有一个小收获吧。

小编大概是每天晚上抽一个小时左右时间出去散散步🚶,到处走一走,瞧一瞧😁。都是为了健康,希望八月继续坚持吧。💪

回到正题,这次要分享的是如何在网格中拖动进行布局相关的内容,具体效果如下,请诸君按需食用。

(难受,封面图好像自动压缩了,有些网格线都没显示出来,看着像是对不齐🙈,还好上面动图不会😋)

起因

在公司业务中,有个功能使用到了 vue-grid-layout 插件,它是一个"网格布局系统"。😲

具体可以看看如下的官网与演示案例了解哈:

官网:传送门

演示案例:传送门

本次,咱们是要来研究一下它的拖动行为,它是如何做到按网格布局来拖动的❓或者说是有点磁贴效果的拖动❓

这种在网格中拖动的行为对于自定义页面布局是挺有帮助的,它能让元素精确对齐,小编的业务也是用来它对可视化页面进行自定义布局,效果嘎嘎好。👻

接下来,我们就一起来看看要如何实现这么一个功能。😋

网格的绘制

vue-grid-layout 插件中,是没有显示网格线的,它也没有提供相关属性来支持,得自己去调整样式,挺麻烦的。😕

感觉网格线的显示能改善用户体验并能使布局看起来更有条理一点,也不知道为啥子不支持,或许有其他考虑。。。

由于,咱们要自己从头来动手实现,那就先来画画这个网格线吧。😋

且看:

html 复制代码
<!DOCTYPE html>
<html>
<head>
  <style>
    body {
      --color: #cbd5e1;
    }
    .container {
      width: 800px;
      height: 400px;
      box-sizing: border-box;
      border: 1px solid var(--color);
      background-image: linear-gradient(to right, var(--color) 1px, transparent 1px), 
                        linear-gradient(to bottom, var(--color) 1px, transparent 1px);
      background-size: 40px 40px;
      position: relative;
    }
  </style>
</head>
<body>
  <div id="container" class="container"></div>
</body>
</html>

效果:

主要通过了 background-image 属性的渐变行为来实现,从上到下(to bottom)与从左到右(to right)在容器背景各绘制了一条1px的线条。

又因为使用了 background-size 属性控制了背景只有40px,而背景的 background-repeat 属性默认是会平铺满的,所以最终小格子会平铺满整个容器的背景。

linear-gradient() 函数的作用可以自己瞅瞅:传送门。😋

网格拖动

接下来,咱们来看看如何实现元素在网格中的拖动行为,由于已经把代码很精简了,直接来瞧瞧:

javascript 复制代码
<!DOCTYPE html>
<html>
<head>
  <style>
    /* ... */
    .draggable {
      background-color: var(--color);
      height: 80px;
      width: 120px;
      position: absolute;
      top: 0;
      left: 0;
      align-items: center;
      display: flex;
      justify-content: center;
      cursor: move;
      user-select: none;
      touch-action: none;
    }
  </style>
</head>
<body>
  <div id="container" class="container">
    <div id="draggable" class="draggable">橙某人</div>
  </div>
  <script>
    document.addEventListener("DOMContentLoaded", () => {
      const draggable = document.getElementById("draggable");
      
      // 网格的大小
      let gridSize = 40;
      // 拖动元素的移动位置
      let dx = 0;
      let dy = 0;
      
      // 监听鼠标按下事件
      draggable.addEventListener("mousedown", handleMouseDown);
      
      function handleMouseDown(e) {
        // 每次拖动都需要减掉原来已有的移动位置,否则第二次拖动又会从初始位置开始了
        const startPos = {
          x: e.clientX - dx,
          y: e.clientY - dy,
        };
        function handleMouseMove(e) {
          // 计算拖动距离
          const dxTemp = e.clientX - startPos.x;
          const dyTemp = e.clientY - startPos.y;
          // 将拖动距离限制成格子大小的倍数
          const snappedX = Math.round(dxTemp / gridSize) * gridSize;
          const snappedY = Math.round(dyTemp / gridSize) * gridSize;
          // 记录拖动元素的移动位置
          dx = snappedX;
          dy = snappedY;
          // 更新拖动元素位置
          draggable.style.transform = `translate3d(${snappedX}px, ${snappedY}px, 0)`;
        }
        function handleMouseUp() {
          document.removeEventListener("mousemove", handleMouseMove);
          document.removeEventListener("mouseup", handleMouseUp);
        };
        
        // 监听鼠标移动与抬起事件
        document.addEventListener("mousemove", handleMouseMove);
        document.addEventListener("mouseup", handleMouseUp);
      }
    });
  </script>
</body>
</html>

效果如下:

呃...怎么来解释这个代码呢?好像也没几行代码,已经删得不能再删了。😶

唠一唠整体思路:

首先,咱把"拖动元素"变成 absolute 布局,为了不影响页面布局,然后通过 translate 来控制其移动的位置,这里你也能选择用 top/left 属性,或者是 margin 属性都行呢。

其次,我们通过监听拖动元素身上的鼠标三兄弟事件(mousedown/mousemove/mouseup),一阵捣鼓,是能计算出鼠标从按下到松开之后的"移动距离",把这个移动距离给到拖动元素身上,拖动元素就能随心所欲的拖动了。

计算鼠标的移动距离,或者说是元素的拖动距离,方式有很多,还不会的UU们可以瞧瞧小编的另一篇拖动相关的文章:拖动❓元素拖动、列表拖动、表格拖动(列与行)🍊🍊🍊

最后,咱们通过公式来限制一下这个"移动距离"的大小,让它按格子大小的倍数变化,再去给到拖动元素身上。

格子大小的倍数 = Math.round(移动距离 / 格子大小) * 格子大小

限制容器中拖动

由于现在拖动元素能随心所欲的拖动,所以也能跑到容器外,这不是我们所期待❗

咱们需要把它限制在容器中拖动,其实也挺简单,做一下四周的限制,且看:

javascript 复制代码
const container = document.getElementById("container");
const draggable = document.getElementById("draggable");
const { width: containerWidth, height: containerHeight } = container.getBoundingClientRect();
const { width: draggableWidth, height: draggableHeight } = draggable.getBoundingClientRect();

function handleMouseMove(e) {
  // ...
  const snappedY = Math.round(dyTemp / gridSize) * gridSize;
  // 限制容器中拖动
  if (snappedX < 0 || snappedY < 0 || snappedX > (containerWidth - draggableWidth) || snappedY > (containerHeight - draggableHeight)) {
    return;
  }
  dx = snappedX;
  // ...
}

仅需要在鼠标移动事件(handleMouseMove)中添加一个判断即可。

随意改变网格大小

最后,网格的大小肯定是因业务而定的,需要能随意动态改变其大小,来看看如何做:

html 复制代码
<!DOCTYPE html>
<html>
<body>
  <!-- ... -->
  <div id="radioBox">
    <input type="radio" name="size" id="radio_10" value="10" />
    <label for="radio_10">10*10</label>

    <input type="radio" name="size" id="radio_20" value="20" />
    <label for="radio_20">20*20</label>

    <input type="radio" name="size" id="radio_30" value="30" />
    <label for="radio_30">30*30</label>

    <input type="radio" name="size" id="radio_40" value="40" checked />
    <label for="radio_40">40*40</label>

    <input type="radio" name="size" id="radio_50" value="50" />
    <label for="radio_50">50*50</label>
  </div>
  <script>
    document.addEventListener("DOMContentLoaded", () => {
      // ...
      
      const radioBox = document.getElementById("radioBox");
      const radioMap = document.querySelectorAll("input[type='radio']");
      
      // 事件委托
      radioBox.addEventListener("click", (e) => {
        if (e.target.type === "radio") radioChange();
      });
      
      // 处理单选框的变化
      function radioChange() {
        for (let i = 0; i < radioMap.length; i++) {
          if (radioMap[i].checked) {
            gridSize = radioMap[i].value;
            updateBackgroundSize();
          }
        }
      }
      
      function updateBackgroundSize() {
        // 改变网格的绘制
        container.style.backgroundSize = `${gridSize}px ${gridSize}px`;
        // 重置拖动元素的移动位置
        dx = 0;
        dy = 0;
        draggable.style.transform = `translate3d(${0}px, ${0}px, 0)`;
      }
    });
  </script>
</body>
</html>

好了,就这么简单!这就能实现开头看到的完整效果了,就唠到这里完结。😋


至此,本篇文章就写完啦,撒花撒花。

希望本文对你有所帮助,如有任何疑问,期待你的留言哦。

老样子,点赞+评论=你会了,收藏=你精通了。

相关推荐
GIS程序媛—椰子23 分钟前
【Vue 全家桶】7、Vue UI组件库(更新中)
前端·vue.js
DogEgg_00129 分钟前
前端八股文(一)HTML 持续更新中。。。
前端·html
ZL不懂前端32 分钟前
Content Security Policy (CSP)
前端·javascript·面试
乐闻x35 分钟前
ESLint 使用教程(一):从零配置 ESLint
javascript·eslint
木舟100936 分钟前
ffmpeg重复回听音频流,时长叠加问题
前端
王大锤43911 小时前
golang通用后台管理系统07(后台与若依前端对接)
开发语言·前端·golang
我血条子呢1 小时前
[Vue]防止路由重复跳转
前端·javascript·vue.js
黎金安1 小时前
前端第二次作业
前端·css·css3
啦啦右一1 小时前
前端 | MYTED单篇TED词汇学习功能优化
前端·学习