我完成功能的是时候,是参照的b站前端小野森森老师的思路来的。比如点击div时再设置div为绝对定位;吸附的判断条件是重叠部分要超过宽高的一半等。 完成效果图如下:
<!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>Document</title>
<style>
html {
width: 375px;
margin: 0 auto;
position: relative;
}
.wrap {
display: flex;
flex-wrap: wrap;
box-sizing: border-box;
}
.wrap .item {
flex: 0 0 25%;
box-sizing: border-box;
padding: 8px;
height: 91px;
width: 90px;
}
.wrap .item .box {
border: 1px solid #ccc;
height: 75px;
width: 73.75px;
box-sizing: border-box;
}
.bottom-wrap {
margin-top: 80px;
}
.bottom-wrap .item .box {
border: none;
background-color: orange;
color: white;
font-size: 36px;
align-items: center;
display: flex;
justify-content: center;
}
</style>
</head>
<body>
<div class="top-wrap wrap">
<div class="item">
<div class="box"></div>
</div>
<div class="item">
<div class="box"></div>
</div>
<div class="item">
<div class="box"></div>
</div>
<div class="item">
<div class="box"></div>
</div>
</div>
<div class="bottom-wrap wrap">
</div>
<script>
(() => {
const idiomArr = ['行尸走肉', '金蝉脱壳', '百里挑一', '金玉满堂', '背水一战', '霸王别姬', '天上人间', '不吐不快', '海阔天空', '情非得已', '满腹经纶',
'兵临城下', '春暖花开', '插翅难逃', '黄道吉日', '天下无双', '偷天换日', '两小无猜', '卧虎藏龙', '珠光宝气', '簪缨世族', '花花公子', '绘声绘影', '国色天香',
'相亲相爱', '八仙过海', '金玉良缘', '掌上明珠', '皆大欢喜', '逍遥法外', '生财有道', '极乐世界', '愚公移山', '魑魅魍魉', '龙生九子', '精卫填海', '海市蜃楼',
'高山流水', '卧薪尝胆', '壮志凌云', '金枝玉叶', '四海一家', '穿针引线', '无忧无虑', '无地自容', '三位一体', '落叶归根', '相见恨晚', '惊天动地', '滔滔不绝',
'相濡以沫', '长生不死', '原来如此', '女娲补天', '三皇五帝', '万箭穿心', '水木清华', '窈窕淑女', '破釜沉舟', '叶公好龙', '后会无期', '守株待兔', '凤凰于飞',
'一生一世', '花好月圆', '世外桃源', '韬光养晦', '画蛇添足', '青梅竹马', '风花雪月', '滥竽充数', '没完没了', '总而言之', '欣欣向荣', '时光荏苒', '差强人意',
'好好先生', '无懈可击', '随波逐流', '袖手旁观', '群雄逐鹿', '血战到底', '买椟还珠', '一见钟情', '喜闻乐见', '负荆请罪', '三人成虎'
]
const bttomDiv = document.querySelector('.bottom-wrap')
let fail = 0 //错误数
let success = 0 //正确数
let chatArr //选择的16个汉字
let bottomPosition //选择区16个框的位置
let topPosition //填充区4个框的位置
let currBottomOffset //移动时当前框的坐标
let currBottomClient //移动时当前鼠标的位置
let diff // 当前框与当前鼠标的坐标差
let rsArr //填充区个框的汉字集合, 例如 ['一', '生', '一', '世']
function init() {
chatArr = []
bottomPosition = []
topPosition = []
currBottomOffset = {}
currBottomClient = {}
diff = {}
rsArr = new Array(4)
getCharArr() //获取并保存字符数数组
getTpl() // 填充选择区
getBottomPostion() //获取并保存选择区的坐标位置
getTopPostion() //获取并保存填空区的坐标位置
bindBottomEvent() //绑定事件
}
/**
* 给选择区的16个框绑定事件
*/
function bindBottomEvent() {
let bottomItem = document.querySelectorAll('.bottom-wrap .item .box')
const len = bottomItem.length
for (let i = 0; i < len; i++) {
bottomItem[i].addEventListener('touchstart', handleTouchStart)
bottomItem[i].addEventListener('touchmove', handleTouchMove)
bottomItem[i].addEventListener('touchend', handleTouchEnd)
}
}
function handleTouchStart(e) {
currBottomClient = {
x: e.touches ? e.touches[0].clientX : e.clientX,
y: e.touches ? e.touches[0].clientY : e.clientY
}
currBottomOffset = bottomPosition[this.dataset.index]
diff = {
x: currBottomClient.x - currBottomOffset.left,
y: currBottomClient.y - currBottomOffset.top
}
this.style.position = 'absolute'
this.style.top = currBottomOffset.top + 'px'
this.style.left = currBottomOffset.left + 'px'
}
function handleTouchMove(e) {
currBottomClient = {
x: e.touches ? e.touches[0].clientX : e.clientX,
y: e.touches ? e.touches[0].clientY : e.clientY
}
this.style.left = currBottomClient.x - diff.x + 'px'
this.style.top = currBottomClient.y - diff.y + 'px'
}
function handleTouchEnd(e) {
if (this.style.left === currBottomOffset.left + 'px' && this.style.top === currBottomOffset.top + 'px') {
// 没有移动, 单纯的点击事件
if (currBottomClient.y < 100) {
// 点击的是填充区的,点击填充区的,需要把对应的词也清空
rsArr.map((item, index) => {
if (item === this.innerHTML) {
rsArr[index] = null
}
})
} else {
// 点击的是选择区的,这里不用做处理
}
return false
}
let top, left
let len = topPosition.length
let flag = false
let index
for (let i = 0; i < len; i++) {
if (rsArr[i]) {
// 当前已有文字
continue
}
top = parseFloat(this.style.top)
left = parseFloat(this.style.left)
if (
// 从左上角放置文字,要求宽过一半,高过一半
(
left >= topPosition[i].left &&
left < topPosition[i].left + 74 / 2 &&
top >= topPosition[i].top &&
top <= topPosition[i].top + 74 / 2
) ||
// 从右上角放置文字,要求宽过一半,高过一半
(
left + 73.75 >= topPosition[i].left + 74 / 2 &&
left < topPosition[i].left + 74 &&
top >= topPosition[i].top &&
top <= topPosition[i].top + 74 / 2
)
) {
index = i
flag = true
break
}
}
if (!flag) {
this.style.position = 'relative'
this.style.left = 0
this.style.top = 0
} else {
this.style.position = 'absolute'
this.style.left = topPosition[index].left + 'px'
this.style.top = topPosition[index].top + 'px'
rsArr[index] = this.innerHTML
let rs = rsArr.join('')
if (rs.length === 4) {
setTimeout(() => {
if (idiomArr.indexOf(rs) !== -1) {
success++
alert(`成语组成正确!您正确了${success}道题, 错误了${fail}道题`)
} else {
fail++
alert(`成语组成错误!您正确了${success}道题, 错误了${fail}道题`)
}
setTimeout(() => {
init() //重置
}, 1200);
}, 800)
return
}
}
}
/**
* 保证一个正确的成语,其余随机,共16个汉字
*/
function getCharArr() {
let len = idiomArr.length
let rand = Math.floor(Math.random() * (len - 1))
idiomArr.map(item => {
if (item !== idiomArr[rand]) {
chatArr = chatArr.concat(item.split(''))
}
})
chatArr = chatArr.sort(compare).splice(0, 12) //随机取12个汉字
chatArr = chatArr.concat(idiomArr[rand].split('')) //加上正确的成语4个汉字
chatArr.sort(compare) //16个汉字随机排序
}
/**
* 获取填充区域的4个框的位置坐标
*/
function getTopPostion() {
let topItem = document.querySelectorAll('.top-wrap .item .box')
topItem = [...topItem] //伪数组转数组
topItem.map(item => {
topPosition.push({
top: item.offsetTop,
left: item.offsetLeft
})
})
}
/**
* 获取选择区16个框的位置坐标
*/
function getBottomPostion() {
let bottomItem = document.querySelectorAll('.bottom-wrap .item .box')
bottomItem = [...bottomItem] //伪数组转数组
bottomItem.map(item => {
bottomPosition.push({
top: item.offsetTop,
left: item.offsetLeft
})
})
}
/**
* 选择区的模板
*/
function getTpl() {
let str = ''
chatArr.map((item, index) => {
str += `
<div class="item">
<div class="box" data-index="${index}">${item}</div>
</div>
`
})
bttomDiv.innerHTML = str
}
/**
* 对数组进行随机排序
*/
function compare() {
return Math.random() > 0.5 ? 1 : -1
}
init()
})()
</script>
</body>
</html>