使用JS操作DOM实现拖拽、吸附功能的DEMO
使用原生DOM实现拖拽功能的DEMO
实现方式有很多可以优化的地方,这个只是用来练练手,随便写写的
- 可以将字体块拖到上方空白区域,如果是一个成语则提示成功,失败则提示失败
- 如果元素没有拖到空白块区域则还原
- 在成功或失败后恢复元素位置
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>移动端拖拽demo</title>
<style>
body {
margin: 0;
}
div {
display: flex;
flex-direction: column;
}
.blank-cell-group,
.char-cell-group {
width: 100%;
flex-direction: row;
}
.blank-cell-group .cell-item,
.char-cell-group .cell-item {
width: 25%;
height: 25vw;
padding: .5rem;
box-sizing: border-box;
}
.char-cell-group {
flex-wrap: wrap;
margin-top: 5rem;
}
.blank-cell-group .cell-item .wrapper,
.char-cell-group .cell-item .wrapper {
width: 100%;
height: 100%;
border: .1rem solid #ddd;
box-sizing: border-box;
}
.char-cell-group .cell-item .wrapper {
border: none;
background-color: rgb(189, 28, 189);
font-size: 3rem;
justify-content: center;
align-items: center;
color: #fff;
}
</style>
<script>
document.documentElement.style.fontSize = '10px';
</script>
</head>
<body>
<div id="app">
<div class="container">
<div class="blank-cell-group">
<div class="cell-item">
<div class="wrapper"></div>
</div>
<div class="cell-item">
<div class="wrapper"></div>
</div>
<div class="cell-item">
<div class="wrapper"></div>
</div>
<div class="cell-item">
<div class="wrapper"></div>
</div>
</div>
<div class="char-cell-group">
</div>
</div>
</div>
<script>
(() => {
const idioms = ['诗情画意', '南来北往', '一团和气', '落花流水'];
const oCharCellGroup = document.querySelector('.char-cell-group')
const oBlanks = document.querySelectorAll('.blank-cell-group .wrapper');
let charCollection = [];
let charAreas = [];
let blankAreas = [];
let resArr = [];
let oChars = null;
// 鼠标距左距上的距离
let startX = 0;
let startY = 0;
// 单元格距左距上的距离
let cellX = 0;
let cellY = 0;
// 单元格的高度与宽度
let cellH = 0;
let cellW = 0;
// 鼠标距离元素边框的距离 (固定)
let mouseX = 0;
let mouseY = 0;
const init = () => {
charCollection = formatCharsArr();
// 渲染所有的单元格
render();
oChars = oCharCellGroup.querySelectorAll('.cell-item .wrapper');
getAreas(oBlanks, blankAreas);
// 获取每一个单元格的坐标
getAreas(oChars, charAreas)
// 绑定事件
bindEvent();
}
function render() {
let list = '';
charCollection.forEach((char, index) => {
list += charCellTpl(char, index);
});
oCharCellGroup.innerHTML = list;
}
function bindEvent () {
let oChar = null;
for (let i = 0; i < oChars.length; i ++) {
oChar = oChars[i];
oChar.addEventListener('touchstart', handleTouchStart, false);
oChar.addEventListener('touchmove', handleTouchMove, false);
oChar.addEventListener('touchend', handleTouchEnd, false);
}
}
function remHandler(value) {
return value / 10 + 'rem';
}
function handleTouchStart (e) {
cellW = this.offsetWidth;
cellH = this.offsetHeight;
cellX = this.offsetLeft;
cellY = this.offsetTop;
startX = e.touches[0].clientX;
startY = e.touches[0].clientY;
mouseX = startX - cellX;
mouseY = startY - cellY;
this.style.width = remHandler(cellW);
this.style.height = remHandler(cellH);
this.style.position = 'fixed';
setPosition(this, { x: cellX, y: cellY })
}
function handleTouchMove(e) {
e.preventDefault();
const moveX = e.touches[0].clientX,
moveY = e.touches[0].clientY;
cellY = moveY - mouseY;
cellX = moveX - mouseX;
setPosition(this, { x: cellX, y: cellY })
}
function handleTouchEnd(e) {
const blankWidth = oBlanks[0].offsetWidth;
const blankHeight = oBlanks[0].offsetHeight;
for(let i = 0; i < blankAreas.length; i ++) {
if (resArr[i] !== undefined) {
continue;
}
// 空白处的距左距上距离
let { startX, startY } = blankAreas[i];
// 判断单元格左、右顶点超过空白元素半个的条件
if (
(
cellX >= startX &&
cellX < startX + blankWidth / 2 &&
cellY < startY + blankHeight / 2
)
||
(
cellX + cellW > startX + blankWidth / 2 &&
cellX + cellW <= startX + blankWidth &&
cellY < startY + blankHeight / 2
)
) {
setPosition(this, { x: startX, y: startY });
setResArr(this, i);
if (!resArr.includes(undefined) && resArr.length === 4) {
setTimeout(() => {
if (!checkResult()) {
alert('错了');
} else {
alert('正确了');
}
resetPosition();
}, 1000);
}
return;
}
}
// 获取属性data-index
const _index = Number(this.dataset.index);
const charArea = charAreas[_index];
setPosition(this, { x: charArea.startX, y: charArea.startY })
}
function getAreas(domCollection, itemWrapper) {
let startX = 0,
stratY = 0,
temp = null;
for (let i = 0; i < domCollection.length; i ++) {
temp = domCollection[i];
startX = temp.offsetLeft;
startY = temp.offsetTop;
itemWrapper.push({
startY,
startX,
})
}
}
function formatCharsArr() {
let _arr = [];
idioms.forEach(item => {
_arr = _arr.concat(item.split(''));
})
return _arr.sort(randomSort);
}
function randomSort(a, b) {
return Math.random() > 0.5 ? 1 : -1;
}
function charCellTpl(char, index) {
return (
`<div class="cell-item">
<div class="wrapper" data-index="${ index }">${ char }</div>
</div>`
);
}
function setPosition(el, { x, y}) {
el.style.left = remHandler(x);
el.style.top = remHandler(y);
}
function setResArr(el, index) {
resArr[index] = {
char: el.innerText,
el,
}
}
function resetPosition() {
resArr.forEach(item => {
const el = item.el,
index = Number(el.dataset.index),
{ startX, startY } = charAreas[index];
setPosition(el, { x: startX, y: startY });
})
resArr = [];
startX = 0;
startY = 0;
cellX = 0;
cellY = 0;
cellH = 0;
cellW = 0;
mouseX = 0;
mouseY = 0;
}
function checkResult() {
let idiom = '';
resArr.forEach(item => {
idiom += item.char;
})
return idioms.find(item => item === idiom);
}
init();
})();
</script>
</body>
</html>
实现效果如图, 使用移动端调试食用
总结:
有几段可以记录下来的代码:
- 乱序排序:
return _arr.sort(randomSort);
function randomSort(a, b) {
return Math.random() > 0.5 ? 1 : -1;
}
- 取值标签中 data-index (data-*)
this.dataset.index
上述设计的代码以及功能都十分的基础,老油条就可以不用参考了,小白可以自己写一个试试锻炼一下dom的操作、代码风格以及编程思维。
转载自:https://juejin.cn/post/7046300306237292557