JS拖动、吸附、回弹(简单触屏版组成成语)

837 阅读1分钟

我完成功能的是时候,是参照的b站前端小野森森老师的思路来的。比如点击div时再设置div为绝对定位;吸附的判断条件是重叠部分要超过宽高的一半等。 完成效果图如下:

成语.png

<!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>