实现可视化设计工具已成为前端开发的重要挑战之一。本文将介绍如何使用 Vue.js 配合 Interact.js 库创建一个功能完整的橱柜设计器,兼容PC和移动终端。
核心功能
网格系统:基于 CSS 网格实现精准定位
拖拽功能:实现单元格的自由移动
缩放控制:8个方向的手柄缩放支持
智能吸附:移动和缩放时自动吸附到网格
支持拖拽、缩放、网格吸附等核心功能。
技术实现
1. 网格系统设计
.cabinet {
background-image: linear-gradient(#ddd 1px, transparent 1px),
linear-gradient(90deg, #ddd 1px, transparent 1px);
background-size: 10px 10px;
}
通过 CSS 线性渐变创建网格背景,提供视觉参考,设置 GRID = 10
作为基础吸附粒度。
2. 数据结构设计
data() {
return {
cols: 40, // 横向格数
rows: 30, // 纵向格数
cells: [ // 单元格数据
{ id: uid(), x: 0, y: 0, w: 100, h: 100 },
{ id: uid(), x: 110, y: 0, w: 100, h: 100 }
],
handles: ["tl", "tm", "tr", "ml", "mr", "bl", "bm", "br"], // 8个手柄
curCell: null // 当前选中单元格
};
}
3. Interact.js 集成
记得先install interact.js
拖拽实现
bindDrag() {
const vm = this; // <-- 缓存 this
interact(".cell").draggable({
modifiers: [
interact.modifiers.snap({
targets: [interact.createSnapGrid({ x: 10, y: 10 })]
}),
interact.modifiers.restrictRect({
restriction: "parent",
elementRect: { left: 0, right: 1, top: 0, bottom: 1 }
})
],
listeners: {
move(e) {
const cell = vm.cells.find(c => c.id === e.target.dataset.id);
cell.x = Math.round((cell.x + e.dx) / 10) * 10;
cell.y = Math.round((cell.y + e.dy) / 10) * 10;
}
}
});
}
缩放实现
bindResize() {
const vm = this; // <-- 缓存 this
interact(".cell").resizable({
edges: {
left: ".resize-handle.tl, .resize-handle.ml, .resize-handle.bl",
right: ".resize-handle.tr, .resize-handle.mr, .resize-handle.br",
top: ".resize-handle.tl, .resize-handle.tm, .resize-handle.tr",
bottom: ".resize-handle.bl, .resize-handle.bm, .resize-handle.br"
},
modifiers: [
interact.modifiers.snapSize({
targets: [interact.createSnapGrid({ x: 10, y: 10 })]
}),
interact.modifiers.restrictRect({ restriction: "parent" })
],
listeners: {
move(e) {
const cell = vm.cells.find(c => c.id === e.target.dataset.id);
cell.x = Math.round(e.rect.left / 10) * 10;
cell.y = Math.round(e.rect.top / 10) * 10;
cell.w = Math.round(e.rect.width / 10) * 10;
cell.h = Math.round(e.rect.height / 10) * 10;
}
}
});
}
4. 手势控制优化
为防止移动端和桌面端的默认行为干扰交互体验,添加了以下优化:
.cabinet {
touch-action: none; /* 新标准 */
-webkit-user-select: none; /* 旧 webkit */
-webkit-touch-callout: none;
user-select: none;
}
5. 响应式设计
通过计算属性动态计算画布尺寸:
computed: {
cabStyle() {
return {
width: this.cols * GRID + "px",
height: this.rows * GRID + "px",
backgroundSize: `${GRID}px ${GRID}px`
};
}
}
关键问题与解决方案
1. 事件冒泡处理
@click.stop="curCell = cell.id"
使用 Vue 的 .stop
修饰符阻止事件冒泡,确保点击单元格时不会触发画布的点击事件。
2. 精准定位
cellStyle(c) {
return {
transform: `translate(${c.x}px, ${c.y}px)`,
width: c.w + "px",
height: c.h + "px"
};
}
使用 transform: translate()
而非 top/left
实现更流畅的定位效果。
总结
通过 Vue.js 和 Interact.js 的组合,我们实现了一个功能完整的柜子设计器。这种方案不仅适用于柜子设计,还可以扩展到其他可视化设计场景,如室内设计、UI 布局等。关键在于合理的数据结构设计和与第三方库的有效整合。
Interact.js 提供了强大的底层交互支持,而 Vue.js 则负责数据管理和界面渲染,两者结合可以快速构建出高性能的可视化编辑工具。