<!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>拖拽、回弹和吸附的成语匹配</title>
<script>
document.documentElement.style.fontSize = '10px'
</script>
<style>
body {
margin: 0;
background-color: #114b5f;
}
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: 0.5rem;
box-sizing: border-box;
}
.blank-cell-group .cell-item .wrapper,
.char-cell-group .cell-item .wrapper {
width: 100%;
height: 100%;
border: 0.5rem solid #456990;
box-sizing: border-box;
border-radius: 1rem;
}
.char-cell-group {
flex-wrap: wrap;
margin-top: 5rem;
}
.char-cell-group .cell-item .wrapper {
justify-content: center;
align-items: center;
font-size: 3rem;
color: #e4fde1;
border: none;
background-color: #456990;
}
</style>
</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 = ['诗情画意', '南来北往', '一团和气', '落花流水'],
oCharCellGroup = document.querySelector('.char-cell-group'),
oBlanks = document.querySelectorAll('.blank-cell-group .wrapper')
let charCollection = [],
charAreas = [],
blankAreas = [],
blankStates = [undefined, undefined, undefined, undefined],
oChars = null,
startX = 0,
startY = 0,
cellX = 0,
cellY = 0,
cellH = 0,
cellW = 0,
mouseX = 0,
mouseY = 0
const init = () => {
getAreas(oBlanks, blankAreas)
charCollection = formatCharsArr()
render()
oChars = oCharCellGroup.querySelectorAll('.cell-item .wrapper')
getAreas(oChars, charAreas)
bindEvents()
}
init()
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 getAreas(domCollection, arrWrapper) {
let startX = 0,
startY = 0,
oItem = null
for (let i = 0; i < domCollection.length; i++) {
oItem = domCollection[i]
startX = oItem.offsetLeft
startY = oItem.offsetTop
arrWrapper.push({
startX,
startY,
})
}
}
function checkAnwser() {
let idiom = ''
blankStates.forEach((item) => {
idiom += item.char
})
setTimeout(() => {
if (idioms.includes(idiom)) {
alert('正确')
} else {
alert('错误')
}
blankStates.forEach((item) => {
const charEl = item.el
const index = charEl.dataset.index
setPosition(charEl, [
charAreas[index].startX,
charAreas[index].startY,
])
})
for (let i = 0; i < blankStates.length; i++) {
blankStates[i] = undefined
}
}, 500)
}
function pxToRem(item) {
return item / 10 + 'rem'
}
function handleAdsorbed(charEl) {
const blankArea = oBlanks[0].offsetWidth * oBlanks[0].offsetHeight
for (let i = 0; i < oBlanks.length; i++) {
if (blankStates[i] !== undefined) {
continue
}
const oBlank = oBlanks[i]
let occupiedArea = 0
const verticalLength =
oBlank.offsetHeight - (charEl.offsetTop - oBlank.offsetTop)
let horizontalLength = 0
if (charEl.offsetLeft >= oBlank.offsetLeft) {
horizontalLength =
oBlank.offsetLeft + oBlank.offsetWidth - charEl.offsetLeft
} else {
horizontalLength =
charEl.offsetLeft + charEl.offsetWidth - oBlank.offsetLeft
}
occupiedArea =
horizontalLength > 0 && verticalLength > 0
? horizontalLength * verticalLength
: 0
if (occupiedArea / blankArea >= 0.5) {
setPosition(charEl, [oBlank.offsetLeft, oBlank.offsetTop])
blankStates[i] = {
char: charEl.innerText,
el: charEl,
}
return true
}
}
}
function handleReBound(charEl) {
const _index = parseInt(charEl.dataset.index),
charArea = charAreas[_index]
setPosition(charEl, [charArea.startX, charArea.startY])
}
function setPosition(dom, position) {
dom.style.left = pxToRem(position[0])
dom.style.top = pxToRem(position[1])
}
function charCellTpl(char, index) {
return `
<div class="cell-item">
<div class="wrapper" data-index="${index}">${char}</div>
</div>
`
}
function render() {
let list = ''
charCollection.forEach((char, index) => {
list += charCellTpl(char, index)
})
oCharCellGroup.innerHTML = list
}
function bindEvents() {
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 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.position = 'fixed'
this.style.transition = ''
this.style.width = pxToRem(cellW)
this.style.height = pxToRem(cellH)
setPosition(this, [cellX, cellY])
}
function handleTouchMove(e) {
const moveX = e.touches[0].clientX
const moveY = e.touches[0].clientY
cellX = moveX - mouseX
cellY = moveY - mouseY
setPosition(this, [cellX, cellY])
}
function handleTouchEnd(e) {
const transitionDuration = '.5s',
transitionTimeFunction = 'ease'
this.style.transition = `top ${transitionDuration} ${transitionTimeFunction}, left ${transitionDuration} ${transitionTimeFunction}`
let isAdsorbed = handleAdsorbed(this)
if (!blankStates.includes(undefined)) checkAnwser()
if (!isAdsorbed) handleReBound(this)
}
})()
</script>
</body>
</html>