写在开头
哈喽,各位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>
好了,就这么简单!这就能实现开头看到的完整效果了,就唠到这里完结。😋
至此,本篇文章就写完啦,撒花撒花。
希望本文对你有所帮助,如有任何疑问,期待你的留言哦。
老样子,点赞+评论=你会了,收藏=你精通了。