拖拽交换
效果图如下
静态效果图
动态效果图
CSS样式
css
复制代码
.container {
background-color: #d4d4d4;
/* 修改背景色 */
border: 2px solid black;
height: 320px;
width: 240px;
position: relative;
overflow: hidden;
/* 防止内容溢出 */
}
.block {
position: absolute;
width: 50px;
height: 32px;
border: 2px solid black;
border-radius: 5px;
text-align: center;
font-weight: bolder;
cursor: move;
user-select: none;
box-sizing: border-box;
/* 确保边框不增加元素尺寸 */
}
.filled {
background-color: #00ff00;
}
.empty {
background-color: #ffffff;
}
.dragging {
opacity: 0.8;
z-index: 100;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
}
.title {
font-size: 20px;
color: red;
margin-bottom: 10px;
}
HTML页面
html
复制代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>拖拽交换</title>
</head>
<body>
<div id="app">
<div class="title">拖拽交换1</div>
<div class="container" id="dropzone">
<div v-for="(block, index) in blocks" :key="index" :id="'block-'+index"
:class="['block', block.content ? 'filled' : 'empty']"
:style="{ top: block.top + 'px', left: block.left + 'px' }">
{{ block.content }}
</div>
</div>
</div>
<!-- 引入拖拽库 -->
<script src="js/interact.js"></script>
<!-- 引入Vue库 -->
<script src="js/vue.min.js"></script>
</body>
</html>
JavaScript
js
复制代码
<script>
let app = new Vue({
el: '#app',
data: {
blocks: [],
blockSize: {
width: 45,
height: 25
},
margin: {
x: 5,
y: 10
},
cols: 4,
containerRect: null
},
created() {},
mounted() {
this.initBlocks();
this.$nextTick(() => {
this.containerRect = document.getElementById('dropzone').getBoundingClientRect();
this.setupDragAndDrop();
});
},
methods: {
// 初始化块及其区域
initBlocks() {
const blocks = [];
const blockWidth = this.blockSize.width + this.margin.x * 2;
const blockHeight = this.blockSize.height + this.margin.y * 2;
for (let i = 0; i < 24; i++) {
const row = Math.floor(i / this.cols);
const col = i % this.cols;
let cont = '';
if (i === 0) cont = '3';
if (i == 13) cont = '127';
blocks.push({
id: i,
content: cont,
top: row * blockHeight + this.margin.y,
left: col * blockWidth + this.margin.x,
originalTop: row * blockHeight + this.margin.y,
originalLeft: col * blockWidth + this.margin.x
});
}
this.blocks = blocks;
},
// 设置拖拽与交换
setupDragAndDrop() {
const self = this;
const container = document.getElementById('dropzone');
interact('.block.filled').draggable({
inertia: false,
autoScroll: false,
restrict: {
restriction: container,
endOnly: false,
elementRect: {
top: 0,
left: 0,
bottom: 1,
right: 1
}
},
onstart: function(event) {
event.target.classList.add('dragging');
// 更新容器尺寸(防止窗口大小改变导致限制不准确)
self.containerRect = container.getBoundingClientRect();
},
onmove: function(event) {
const target = event.target;
const x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx;
const y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy;
target.style.transform = `translate(${x}px, ${y}px)`;
target.setAttribute('data-x', x);
target.setAttribute('data-y', y);
},
onend: function(event) {
const target = event.target;
target.classList.remove('dragging');
const dragIndex = parseInt(target.id.split('-')[1]);
// 重置位置
target.style.transform = '';
target.removeAttribute('data-x');
target.removeAttribute('data-y');
// 获取鼠标释放位置(相对于容器)
const dropX = event.clientX - self.containerRect.left;
const dropY = event.clientY - self.containerRect.top;
// 找出释放位置下方的方块
const dropBlock = document.elementFromPoint(event.clientX, event.clientY);
if (dropBlock && dropBlock.classList.contains('block') &&
dropBlock !== target &&
!dropBlock.classList.contains('filled')) {
const dropIndex = parseInt(dropBlock.id.split('-')[1]);
// 交换内容
const temp = self.blocks[dragIndex].content;
self.$set(self.blocks, dragIndex, {
...self.blocks[dragIndex],
content: ''
});
self.$set(self.blocks, dropIndex, {
...self.blocks[dropIndex],
content: temp
});
}
}
});
}
}
});
完整代码
html
复制代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>拖拽交换</title>
<style>
.container {
background-color: #d4d4d4;
/* 修改背景色 */
border: 2px solid black;
height: 320px;
width: 240px;
position: relative;
overflow: hidden;
/* 防止内容溢出 */
}
.block {
position: absolute;
width: 50px;
height: 32px;
border: 2px solid black;
border-radius: 5px;
text-align: center;
font-weight: bolder;
cursor: move;
user-select: none;
box-sizing: border-box;
/* 确保边框不增加元素尺寸 */
}
.filled {
background-color: #00ff00;
}
.empty {
background-color: #ffffff;
}
.dragging {
opacity: 0.8;
z-index: 100;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
}
.title {
font-size: 20px;
color: red;
margin-bottom: 10px;
}
</style>
</head>
<body>
<div id="app">
<div class="title">拖拽交换1</div>
<div class="container" id="dropzone">
<div v-for="(block, index) in blocks" :key="index" :id="'block-'+index"
:class="['block', block.content ? 'filled' : 'empty']"
:style="{ top: block.top + 'px', left: block.left + 'px' }">
{{ block.content }}
</div>
</div>
</div>
<script src="js/interact.js"></script>
<script src="js/vue.min.js"></script>
<script>
new Vue({
el: '#app',
data: {
blocks: [],
blockSize: {
width: 45,
height: 25
},
margin: {
x: 5,
y: 10
},
cols: 4,
containerRect: null
},
created() {},
mounted() {
this.initBlocks();
this.$nextTick(() => {
this.containerRect = document.getElementById('dropzone').getBoundingClientRect();
this.setupDragAndDrop();
});
},
methods: {
initBlocks() {
const blocks = [];
const blockWidth = this.blockSize.width + this.margin.x * 2;
const blockHeight = this.blockSize.height + this.margin.y * 2;
for (let i = 0; i < 24; i++) {
const row = Math.floor(i / this.cols);
const col = i % this.cols;
let cont = '';
if (i === 0) cont = '3';
if (i == 13) cont = '127';
blocks.push({
id: i,
content: cont,
top: row * blockHeight + this.margin.y,
left: col * blockWidth + this.margin.x,
originalTop: row * blockHeight + this.margin.y,
originalLeft: col * blockWidth + this.margin.x
});
}
this.blocks = blocks;
},
setupDragAndDrop() {
const self = this;
const container = document.getElementById('dropzone');
interact('.block.filled').draggable({
inertia: false,
autoScroll: false,
restrict: {
restriction: container,
endOnly: false,
elementRect: {
top: 0,
left: 0,
bottom: 1,
right: 1
}
},
onstart: function(event) {
event.target.classList.add('dragging');
// 更新容器尺寸(防止窗口大小改变导致限制不准确)
self.containerRect = container.getBoundingClientRect();
},
onmove: function(event) {
const target = event.target;
const x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx;
const y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy;
target.style.transform = `translate(${x}px, ${y}px)`;
target.setAttribute('data-x', x);
target.setAttribute('data-y', y);
},
onend: function(event) {
const target = event.target;
target.classList.remove('dragging');
const dragIndex = parseInt(target.id.split('-')[1]);
// 重置位置
target.style.transform = '';
target.removeAttribute('data-x');
target.removeAttribute('data-y');
// 获取鼠标释放位置(相对于容器)
const dropX = event.clientX - self.containerRect.left;
const dropY = event.clientY - self.containerRect.top;
// 找出释放位置下方的方块
const dropBlock = document.elementFromPoint(event.clientX, event.clientY);
if (dropBlock && dropBlock.classList.contains('block') &&
dropBlock !== target &&
!dropBlock.classList.contains('filled')) {
const dropIndex = parseInt(dropBlock.id.split('-')[1]);
// 交换内容
const temp = self.blocks[dragIndex].content;
self.$set(self.blocks, dragIndex, {
...self.blocks[dragIndex],
content: ''
});
self.$set(self.blocks, dropIndex, {
...self.blocks[dropIndex],
content: temp
});
}
}
});
}
}
});
</script>
</body>
</html>