JS 实战: Drag 点击拖曳效果

JS 实战: Drag 点击拖曳效果

简介

一直以来都觉得网页中的点击拖曳效果很酷,本篇就来尝试看看实现使用原生 JS 来实现点击拖曳元素的效果。

参考

DIV 点击拖动,纯JS 实现https://blog.csdn.net/qq_32214375/article/details/82387482
鼠标事件以及clientX、offsetX、screenX、pageX、x的区别https://blog.csdn.net/weixin_41342585/article/details/80659736
Window.getComputedStyle()-MDNhttps://developer.mozilla.org/zh-CN/docs/Web/API/Window/getComputedStyle
js原生获取元素的css属性https://www.cnblogs.com/leaf930814/p/6985017.html?utm_source=itdadao&utm_medium=referral
CSS z-index 属性https://www.w3school.com.cn/cssref/pr_pos_z-index.asp

正文

项目结构 & 静态模版

在开始编写 js 程序之前,我们先来创建一个接下来要操作的元素模版,同时给出我们的项目结构:

/js_drag
|- index.html
|- index.css
|- index.js

添加元素

首先我们先定义好 html 和 css 文件的内容:

  • index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="index.css">
</head>
<body>
    <div class="container">
        <div id="box1" class="box"></div>
        <div id="box2" class="box"></div>
        <div id="box3" class="box"></div>
        <div id="box4" class="box"></div>
    </div>
</body>
</html>
  • index.css
body {
    margin: 0;
    background-color: gray;
}

.container {
    background-color: #fff;
    margin: 40px auto 0;
    width: 800px;
    height: 800px;
}

.box {
    width: 100px;
    height: 100px;
}

#box1 { background-color: blue; }
#box2 { background-color: red; }
#box3 { background-color: green; }
#box4 { background-color: lightgray; }

效果如下:

添加 position

然而这样的元素其实是不能拖曳的,我们将 .container 元素作为我们拖动的背景,而里面的每个 .box 作为我们想要拖动的元素,就需要使用 position 属性的 relativeabsolute 值,使得 box 相对于 container 进行移动

  • index.css
body {
    margin: 0;
    background-color: gray;
}

.container {
    background-color: #fff;
    margin: 40px auto 0;
    width: 800px;
    height: 800px;
    position: relative;
}

.box {
    width: 100px;
    height: 100px;
}

#box1 {
    background-color: blue;
    position: absolute;
    left: 200px;
    top: 100px;
}

#box2 {
    background-color: red;
    position: absolute;
    top: 200px;
    right: 100px;
}

#box3 {
    background-color: green;
    position: absolute;
    right: 200px;
    bottom: 100px;
}

#box4 {
    background-color: lightgray;
    position: absolute;
    bottom: 200px;
    left: 100px;
}

效果如下:

主要逻辑片段

完成好我们的静态元素模版后,我们就可以开始来编写拖曳元素的逻辑代码了

事件响应结构

首先我们先建立整个拖曳事件的状态转移图:

我们判断鼠标落下(mousedown)时记录当前鼠标坐标(clientX、clientY),若在放开前进行拖曳则根据鼠标偏移量改变元素位置,放开时则取消对移动事件的监听,我们首先给出事件响应结构的代码:

  • index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="index.css">
</head>
<body>
    <div class="container">
        <!-- 在 mousedown 事件绑定拖曳方法,传入 event 和 id 作为参数 -->
        <div onmousedown="dragStart(event, this.id)" id="box1" class="box"></div>
        <div onmousedown="dragStart(event, this.id)" id="box2" class="box"></div>
        <div onmousedown="dragStart(event, this.id)" id="box3" class="box"></div>
        <div onmousedown="dragStart(event, this.id)" id="box4" class="box"></div>
    </div>

    <script src="index.js"></script>
</body>
</html>
  • index.js
function dragStart (e, id) {
  // mousedown 事件,拖曳开始
  console.log('mouse down -> drag start')
  const el = document.getElementById(id)
  console.log(this)
  console.log(el)
  
  const getMove = function (e) {
    // mousemove 事件,表示拖曳移动阶段
    console.log('mouse move')
  }
  this.addEventListener('mousemove', getMove)
  this.onmouseup = function (e) {
    // mouseup 事件,表示结束拖曳,移除 mousemove 的监听函数
    console.log('mouse up -> drag over')
    this.removeEventListener('mousemove', getMove)
  }
}

点击、移动、并放开鼠标的示例如下

移动元素

接下来,为了要移动元素,我们首先要先拿到元素原本的位置(由于使用 position 属性,所以需要拿到 topleft 的值)

window.getComputedStyle

这边需要注意的一点在于,当我们直接使用 el.style.attr 只能拿到透过 style 设置的值。为了拿到一开始的初始值我们需要使用 window.getComputedStyle 方法来动态的取值,为此我们需要抽象出 getStyleVal 方法来封装 getComputedStyle 方法:

  • getComputedStyle
function getStyleVal (el, attr) {
  const s = getComputedStyle(el)[attr]
  return Number(s.substring(0, s.indexOf('p')))
}

同时我们在每次的 mousemove 事件拿到鼠标的新位置并计算偏移量之后,改变元素的位置:

  • index.js
// 获取目标元素的目标值(封装 getComputedStyle 方法)
function getStyleVal (el, attr) {
  const s = getComputedStyle(el)[attr]
  return Number(s.substring(0, s.indexOf('p')))
}

function dragStart (e, id) {
  console.log('mouse down -> drag start')
  const el = document.getElementById(id)
  // 获取当前位置
  const [top, left] = [getStyleVal(el, 'top'), getStyleVal(el, 'left')]
  // 获取鼠标落下时的坐标
  const [cursorX, cursorY] = [e.clientX, e.clientY]

  const getMove = function (e) {
    console.log('mouse move')
    // 鼠标新坐标
    const [x, y] = [e.clientX, e.clientY]
    // 鼠标相对偏移量
    const [offsetX, offsetY] = [x - cursorX, y - cursorY]
    // 元素新位置
    let [nextLeft, nextTop] = [left + offsetX, top + offsetY]
    // 移动元素
    el.style.left = `${nextLeft}px`
    el.style.top = `${nextTop}px`
  }
  this.addEventListener('mousemove', getMove)
  this.onmouseup = function (e) {
    console.log('mouse up -> drag over')
    this.removeEventListener('mousemove', getMove)
  }
}

限制可移动范围

我们可以看到,上面的例子已经可以自由的拖动元素了,但是却飞出了白色的范围。

接下来我们需要限制元素可以移动的范围,避免里面的 box 拖一拖飞出 container 的范围,首先我们将获取可移动范围封装成一个方法:

function maxOffset (el) {
  const p = el.parentNode
  const outerW = getStyleVal(p, 'width')
  const outerH = getStyleVal(p, 'height')
  const innerW = getStyleVal(el, 'width')
  const innerH = getStyleVal(el, 'height')
  // return [maxW, maxH]
  return [outerW - innerW, outerH - innerH]
}

并在 mousemove 事件中限制元素的目标位置

  • index.js
function getStyleVal (el, attr) {
  const s = getComputedStyle(el)[attr]
  return Number(s.substring(0, s.indexOf('p')))
}

function maxOffset (el) {
  const p = el.parentNode
  const outerW = getStyleVal(p, 'width')
  const outerH = getStyleVal(p, 'height')
  const innerW = getStyleVal(el, 'width')
  const innerH = getStyleVal(el, 'height')
  return [outerW - innerW, outerH - innerH]
}

function dragStart (e, id) {
  console.log('mouse down -> drag start')
  const el = document.getElementById(id)

  const [top, left] = [getStyleVal(el, 'top'), getStyleVal(el, 'left')]
  const [cursorX, cursorY] = [e.clientX, e.clientY]
  const [maxW, maxH] = maxOffset(el)

  const getMove = function (e) {
    console.log('mouse move')
    const [x, y] = [e.clientX, e.clientY]
    const [offsetX, offsetY] = [x - cursorX, y - cursorY]
    let [nextLeft, nextTop] = [left + offsetX, top + offsetY]
    // left 和 top 必须分别限制在 [0, maxW], [0, maxH] 的范围之内
    nextLeft = nextLeft < 0 ? 0 : nextLeft > maxW ? maxW : nextLeft
    nextTop = nextTop < 0 ? 0 : nextTop > maxH ? maxH : nextTop
    el.style.left = `${nextLeft}px`
    el.style.top = `${nextTop}px`
  }
  this.addEventListener('mousemove', getMove)
  this.onmouseup = function (e) {
    console.log('mouse up -> drag over')
    this.removeEventListener('mousemove', getMove)
  }
}

效果如下:

最终版本

最后我们加上一个小功能,每次点击元素时将其提到最上方(使用 z-index 属性实现),代码如下:

  • index.js
function getStyleVal (el, attr) {
  const s = getComputedStyle(el)[attr]
  return Number(s.substring(0, s.indexOf('p')))
}

function maxOffset (el) {
  const p = el.parentNode
  const outerW = getStyleVal(p, 'width')
  const outerH = getStyleVal(p, 'height')
  const innerW = getStyleVal(el, 'width')
  const innerH = getStyleVal(el, 'height')
  return [outerW - innerW, outerH - innerH]
}

const data = {
  zIndex: 0
}

function dragStart (e, id) {
  const el = document.getElementById(id)
  const [top, left] = [getStyleVal(el, 'top'), getStyleVal(el, 'left')]
  const [cursorX, cursorY] = [e.clientX, e.clientY]
  const [maxW, maxH] = maxOffset(el)

  // 每次 mousedown 将该元素的 zIndex 设为全局最大
  el.style.zIndex = ++data.zIndex
  
  const getMove = function (e) {
    const [x, y] = [e.clientX, e.clientY]
    const [offsetX, offsetY] = [x - cursorX, y - cursorY]
    let [nextTop, nextLeft] = [top + offsetY, left + offsetX]
    nextLeft = nextLeft < 0 ? 0 : nextLeft > maxW ? maxW : nextLeft
    nextTop = nextTop < 0 ? 0 : nextTop > maxH ? maxH : nextTop
    el.style.left = `${nextLeft}px`
    el.style.top = `${nextTop}px`
  }
  this.addEventListener('mousemove', getMove)
  this.onmouseup = function (e) {
    this.removeEventListener('mousemove', getMove)
  }
}

最终版本的效果如下:

结语

其实很简单吧,核心功能实现在于:根据鼠标偏移量改变元素当前位置,使用到的相关属性和方法注意点如下:

  • 监听 mousedownmousemovemouseup 实现状态的轮转
  • 使用 getComputedStyle 动态获取 css 属性值
  • clientXclientY 表示相对于可视区域的偏移量(参考链接二)
  • 直接设置 el.style.attr 改变元素样式

本篇就到此为止啦(终于写出来了hh)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值