在网格中拖动进行布局-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>

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


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

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

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

相关推荐
web1478621072313 分钟前
C# .Net Web 路由相关配置
前端·c#·.net
m0_7482478014 分钟前
Flutter Intl包使用指南:实现国际化和本地化
前端·javascript·flutter
飞的肖17 分钟前
前端使用 Element Plus架构vue3.0实现图片拖拉拽,后等比压缩,上传到Spring Boot后端
前端·spring boot·架构
青灯文案125 分钟前
前端 HTTP 请求由 Nginx 反向代理和 API 网关到后端服务的流程
前端·nginx·http
m0_7482548829 分钟前
DataX3.0+DataX-Web部署分布式可视化ETL系统
前端·分布式·etl
ZJ_.41 分钟前
WPSJS:让 WPS 办公与 JavaScript 完美联动
开发语言·前端·javascript·vscode·ecmascript·wps
GIS开发特训营1 小时前
Vue零基础教程|从前端框架到GIS开发系列课程(七)响应式系统介绍
前端·vue.js·前端框架·gis开发·webgis·三维gis
Cachel wood1 小时前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
学代码的小前端1 小时前
0基础学前端-----CSS DAY9
前端·css
joan_851 小时前
layui表格templet图片渲染--模板字符串和字符串拼接
前端·javascript·layui