前言

在写一个简单的H5页面时,需要实现如下的一个拖拽效果,找了半天未能找到符合要求的,含泪手写
先来看一下我们要是实现一个怎样的效果

基本思路

  1. 鼠标摁下,记录元素的初始位置以及宽高
  2. 监听鼠标的移动,根据鼠标的移动不断改变自己的位置/宽高
  3. 是否存在边界限制

    拖拽实现

    我们常见的改变元素位置的方式有

  4. 定位

  5. 使用translate进行偏移

那我们现在用那种方式那实现拖拽呢?
从功能实现上来看,这两个方式都能很好的实现我们的需求
从性能上来看,translate天生就是用来制作动画效果的,所以translate的性能以及流畅度都是要优于定位的。
开始操作!!

  1. <style>
  2. .box{
  3. margin: 50px;
  4. width: 500px;
  5. height: 300px;
  6. border: 1px solid black;
  7. position: relative;
  8. }
  9. .drag{
  10. height: 100px;
  11. width: 100px;
  12. background-color: #cbd;
  13. }
  14. </style>
  15. <div class="box">
  16. <div class="drag"></div>
  17. </div>
  18. <script>
  19. let dragEl = document.querySelector(".drag")
  20. let container = document.querySelector(".box")
  21. let width, height, maxWidth, maxHeight, tx, ty, startX, startY
  22. // 初始化
  23. function init() {
  24. // 为目标元素设置初始的偏移,避免在第一次获取偏移时为空的问题
  25. dragEl.style.transform = "translate(0px,0px)"
  26. // 获取父元素宽高
  27. maxWidth = container.clientWidth
  28. maxHeight = container.clientHeight
  29. }
  30. function getInfo(e) {
  31. // 获取元素当前的宽高
  32. width = dragEl.clientWidth
  33. height = dragEl.clientHeight
  34. // 获取元素当前的偏移量
  35. let translateStr = dragEl.style.transform
  36. const reg = /\d+/g
  37. let translateArr = translateStr.match(reg)
  38. tx = Number(translateArr[0])
  39. ty = Number(translateArr[1])
  40. // 记录鼠标的起始位置
  41. startX = e.clientX
  42. startY = e.clientY
  43. }
  44. function drag() {
  45. dragEl.addEventListener("mousedown", (e) => {
  46. getInfo(e)
  47. document.onmousemove = (e) => {
  48. let distanceX = tx + e.clientX - startX
  49. let distanceY = ty + e.clientY - startY
  50. dragEl.style.transform = `translate(${distanceX}px, ${distanceY}px)`
  51. }
  52. document.onmouseup = () => {
  53. document.onmousemove = null
  54. }
  55. })
  56. }
  57. init()
  58. drag()
  59. </script>

通过上述代码,我们已经完成了元素的拖动,接下来需要考虑的就是,如果有边界限制,我们又该如何实现
从上诉例子中,我们可以观察出,元素偏移的最小值为0,最大值为父元素的宽高 - 目标元素的宽高
所以在有边界限制的情形下偏移量的计算方式为

  1. let distanceX = Math.max(0, Math.min(tx + e.clientX - startX, maxWidth - width))
  2. let distanceY = Math.max(0, Math.min(ty + e.clientY - startY, maxHeight - height))

缩放实现

这里我们以向左缩放为例

  1. 首先我们需要为目标元素添加一个边框,用来进行缩放的操作

    1. <style>
    2. .box{
    3. margin: 50px;
    4. width: 500px;
    5. height: 300px;
    6. border: 1px solid black;
    7. position: relative;
    8. }
    9. .drag{
    10. height: 100px;
    11. width: 100px;
    12. background-color: #cbd;
    13. }
    14. .left{
    15. width: 10px;
    16. height: calc(100% - 14px);
    17. margin: 7px 0px;
    18. position: absolute;
    19. background-color: #000;
    20. cursor: col-resize;
    21. top: 0;
    22. left: -5px;
    23. }
    24. </style>
    25. <script>
    26. function addLeft() {
    27. left = document.createElement("div")
    28. left.className = "left"
    29. dragEl.append(left)
    30. }
    31. init()
    32. drag()
    33. addLeft()
    34. </script>

    2.为左侧的边框添加缩放功能,因为是左侧的缩放,所以在宽度变化的同时,需要不断调整元素的位置,令其符合视觉效果

    1. function leftZoom() {
    2. left.addEventListener("mousedown", (e) => {
    3. e.stopPropagation()
    4. getInfo(e)
    5. document.onmousemove = (e) => {
    6. // 注意这里是➖
    7. newWidth = width - (e.clientX - startX)
    8. let distanceX = tx + (e.clientX - startX)
    9. dragEl.style.width = `${newWidth}px`
    10. dragEl.style.transform = `translate(${distanceX}px, ${ty}px)`
    11. }
    12. document.onmouseup = () => {
    13. document.onmousemove = null
    14. }
    15. })
    16. }
    17. init()
    18. drag()
    19. addLeft()
    20. leftZoom()
  2. 限制元素缩放的最小值

    1. let minWidth = 30
    2. newWidth = Math.max(minWidth, width - (e.clientX - startX))
  3. 现在我们已经完成了缩放,但是当元素向右缩小到最小值时,元素会向右移动,显然这是不符合逻辑的,所以我们需要对偏移进行限制

    1. // 最大偏移为已经偏移的距离 + 目标元素的宽度 - 最小宽度
    2. let distanceX = Math.min(tx + width - minWidth, tx + (e.clientX - startX))

    4.如果缩放的尺寸需要限制在父元素内,我们需要继续完善代码

    1. // 最大宽度为元素当前偏移量 + 最初的宽度,最小宽度为minWidth
    2. newWidth = Math.min(tx + width, Math.max(minWidth, width - (e.clientX - startX)))
    3. // 最大偏移为已经偏移的距离 + 目标元素的宽度 - 最小宽度,最小偏移为0
    4. let distanceX = Math.max(0,Math.min(tx + width - minWidth, tx + (e.clientX - startX)))

    其他是三边以及四个角的实现方式基本相同,就不在这里一一赘述了

    最后

    虽然我们完成了元素的拖拽与缩放,但是我们在使用时,还是存在许多的限制,比如

  4. 目标元素不能被定位,如果使用定位对元素进行了偏移,我们所做的限制就会不生效

  5. 同理目标元素也不能存在边距

虽然存在限制,但是我们可以根据自己的实际需求进行调整