【d3js】d3.drag扩展 自己实现一个drag-灵析社区

JOHO

前言

本篇文章主要讲怎么一块拖动多个小方块的, 和实现一个自定义的drag

上篇drag基础代码

效果图:

code

<!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>
    #container {
      width: 500px;
      margin: 50px auto 0;
    }
  </style>
</head>

<body>
  <div id="container">

  </div>
</body>

</html>
<script src="https://d3js.org/d3.v5.min.js"></script>

<script>
  const svg = d3.select('#container')
    .append('svg')
    .attr('width', 500)
    .attr('height', 500);

  const data = [
    { id: 1, fill: 'black', x: 10, y: 10 },
    { id: 2, fill: 'black', x: 50, y: 50 },
    { id: 3, fill: 'black', x: 100, y: 70 },
    { id: 4, fill: 'black', x: 20, y: 100 }
  ];

  draw(); // 绘制
  bindDrag(); //绑定drag
  function draw() {
    const update = svg.selectAll('rect')
      .data(data, d => d.id);

    //修改层
    update.attr('x', (d, idx) => d.x)
      .attr('y', (d, idx) => d.y)
      .attr('fill', (d) => d.fill)

    //渲染层
    const enter = update.enter();

    //删除层
    const exit = update.exit();

    enter.append('rect')
      .attr('width', 20)
      .attr('height', 20)
      .attr('id', d => `rect-${d.id}`) //不能绑定数字咱们给拼接一个字符串 error: Uncaught DOMException: Failed to execute 'querySelector' on 'Document': '#4' is not a valid selector.
      .attr('x', (d, idx) => d.x)
      .attr('y', (d, idx) => d.y)
      .attr('fill', (d) => d.fill)
      .attr('stroke', 'blue')
      .attr('strokeWidth', 1)

    exit.remove()
  }

  function bindDrag() { // 绑定drag
    svg.selectAll('rect').call(drag()); // 使用.call 给每个rect绑定drag事件
  }

  function drag() {
    return d3.drag()
      .on('start', dragStarted) // 开始
      .on('drag', dragged)  // 执行中
      .on('end', dragEnded); // 结束
  };

  let currentMoveElement = null;

  function dragStarted(d) { // drag start
    console.log(d, 'start log....'); // 获取到的是当前拖拽的item 就是之前绑定的值。如:{id: 1, fill: 'black', x: 10, y: 10}
    currentMoveElement = d3.select(`#rect-${d.id}`); // 🎈优化点: 提前获取到元素 不用再drag的时候一直用d3.select获取的
  };

  function dragged(d) { // drag 
    console.log(d, 'drag log....');

    const currentX = d3.event.x; // 使用d3.event 能获取到当前拖拽的x、y
    const currentY = d3.event.y;
    if (currentMoveElement) {
      currentMoveElement.attr('x', currentX) // 一直修改其位置
        .attr('y', currentY);
    }
  };

  function dragEnded(d) { // drag end
    console.log(d, 'end log....')
    const currentX = d3.event.x; // 使用d3.event 能获取到当前拖拽的x、y
    const currentY = d3.event.y;

    d.x = currentX; // 再最后更改下绑定的数据,不然就会再下次drag的时候还是获取到之前的坐标,会发现尼拖拽节点跑到初始位置了
    d.y = currentY;  // 如果有疑问可以注释掉这个函数的代码尝试下


    //对应的data原始数据也要更新,假如你有个保存的场景 那尼获取到的坐标就不是最新的了
    data.forEach((item) => {
      if (item.id === d.id) {
        item.x = currentX;
        item.y = currentY;
      }
    })
    console.log(data, 'data')
  };
</script>

选中多个小方块一块拖动

怎么实现选中多个小方块一块拖动?
  • 1、需要些选中事件(对事件不了解的可以往之前篇幅翻翻)。
  • 2、如果有选中的小方块正好有我拖拽的的小方块那就要把选中的一块拖动(反之就还是单个拖拽)。

实现效果图:

新增点击事件部分代码

 const selected = [];
 bindEvent();
  function bindEvent() { // 绑定点击事件(看不懂的去前面d3event事件绑定去看看)
    svg.selectAll('rect').on('click', (d) => {
      const index = selected.indexOf(d.id);
      if (index !== -1) {
        selected.splice(index, 1);
      } else {
        selected.push(d.id);
      }
      
      updateColor();
    });
  };

  function updateColor() { // 更新color 
    data.forEach((item) => {
      if (selected.includes(item.id)) {
        item.fill = 'red';
      }
    });
    draw(); // 重新绘制,更新需要高亮的点(d3数据绑定函数,不理解怎么更新的可以去看看d3数据绑定篇幅)
  }

改动drag code

function bindDrag() { // 绑定drag
    svg.selectAll('rect').call(drag()); // 使用.call 给每个rect绑定drag事件
  }

  function drag() {
    return d3.drag()
      .on('start', dragStarted) // 开始
      .on('drag', dragged)  // 执行中
      .on('end', dragEnded); // 结束
  };

  let diffX = 0; diffY = 0;

  // 🎈优化点: 提前获取到元素 不用再drag的时候一直用d3.select获取的
  let currentMoveElement = null; // 这个数据要变成数组的形式了存放多个[selectDom、selectDom...]

  function dragStarted(d) { // drag start
    console.log(d, 'start log....'); // 获取到的是当前拖拽的item 就是之前绑定的值。如:{id: 1, fill: 'black', x: 10, y: 10}

    if (selected.includes(d.id)) { // 判断是不是拖拽的是高亮选中的节点(ps: 多个节点拖拽)
      currentMoveElement = selected.map(item => d3.select(`#rect-${item}`));
    } else { // 当前选中的节点拖拽
      currentMoveElement = [d3.select(`#rect-${d.id}`)];
    }
  };

  function dragged(d) { // drag 
    console.log(d, 'drag log....');

    const currentX = d3.event.x; // 使用d3.event 能获取到当前拖拽的x、y
    const currentY = d3.event.y;

    const { x: originX, y: originY } = d3.select(`#rect-${d.id}`).datum() || {}; // datum 获取到之前绑定的值


    diffX = currentX - originX; // 计算出元素位置跟拖拽位置的diff(x、y)
    diffY = currentY - originY;

    if (currentMoveElement) {
      currentMoveElement.forEach((item) => { // 统一更改坐标
        const { x, y } = item.datum();
        item.attr('x', x + diffX) // 一直修改其位置
          .attr('y', y + diffY);
      })
    }
  };

  function dragEnded(d) { // drag end
    console.log(d, 'end log....')
    const dragIdMap = new Map;
    currentMoveElement.forEach(item => { // 统一更改绑定数据的值
      const originData = item.datum();
      dragIdMap.set(originData.id, true);
      item.datum({ // 重新绑定拖拽后的新坐标
        ...originData,
        x: originData.x + diffX,
        y: originData.y + diffY,
      })
    });

    //对应的data原始数据也要更新,假如你有个保存的场景 那尼获取到的坐标就不是最新的了
    data.forEach((item) => {
      if (dragIdMap.has(item.id)) {
        item.x = item.x + diffX;
        item.y = item.y + diffY;
      }
    })
    console.log(data, 'data')
  };
总结:关键点判断是不是拖拽的是高亮选中的节点 如果拖拽的是选中的高亮节点就多个拖拽。如果拖拽的不是高亮的就是单个拖拽。其他都是跟单个拖拽一样(需要改变绑定的数据、需要更改原始数据)。

自定义drag

自定义drag跟自定义brush多少有点像,都是利用鼠标move事件来达到咱们期望的效果。

效果图:

基础代码在这上面改动

<!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>
    #container {
      width: 500px;
      margin: 50px auto 0;
    }
  </style>
</head>

<body>
  <div id="container">

  </div>
</body>

</html>
<script src="https://d3js.org/d3.v5.min.js"></script>

<script>
  const svg = d3.select('#container')
    .append('svg')
    .attr('width', 500)
    .attr('height', 500);

  const data = [
    { id: 1, fill: 'black', x: 10, y: 10 },
    { id: 2, fill: 'black', x: 50, y: 50 },
    { id: 3, fill: 'black', x: 100, y: 70 },
    { id: 4, fill: 'black', x: 20, y: 100 }
  ];

  draw(); // 绘制
  function draw() {
    const update = svg.selectAll('rect')
      .data(data, d => d.id);

    //修改层
    update.attr('x', (d, idx) => d.x)
      .attr('y', (d, idx) => d.y)
      .attr('fill', (d) => d.fill)

    //渲染层
    const enter = update.enter();

    //删除层
    const exit = update.exit();

    enter.append('rect')
      .attr('width', 20)
      .attr('height', 20)
      .attr('id', d => `rect-${d.id}`) //不能绑定数字咱们给拼接一个字符串 error: Uncaught DOMException: Failed to execute 'querySelector' on 'Document': '#4' is not a valid selector.
      .attr('x', (d, idx) => d.x)
      .attr('y', (d, idx) => d.y)
      .attr('fill', (d) => d.fill)
      .attr('stroke', 'blue')
      .attr('strokeWidth', 1)

    exit.remove()
  };

  const selected = [];
  bindEvent(); // 绑定点击事件

  function bindEvent() { // 绑定点击事件(看不懂的去前面d3event事件绑定去看看)
    svg.selectAll('rect').on('click', (d) => {

      const index = selected.indexOf(d.id);
      if (index !== -1) {
        selected.splice(index, 1);
      } else {
        selected.push(d.id);
      }
      d3.event.preventDefault();
      updateColor();
    })
  };

  function updateColor() { // 更新color 
    console.log(selected, 'selected')
    data.forEach((item) => {
      if (selected.includes(item.id)) {
        item.fill = 'red';
      }
    });
    draw(); // 重新绘制,更新需要高亮的点(d3数据绑定函数,不理解怎么更新的可以去看看d3数据绑定篇幅)
  }
</script>

custom drag code
下面代码定义了bindDrag方法,其他就跟之前的drag一样了。可以用上面多个小方块代码往里面套就好了。

  bindDrag(); //绑定drag
  function bindDrag() {
    let diffX; let diffY
    svg.selectAll('rect')
      .on('mousedown', function (d) {
        // start drag
        const { offsetX: x1, offsetY: y1 } = d3.event; // 使用d3.event获取当前的x、y
        console.log(d3.event, 'd3.event down');

        svg.on('mousemove', function () {
          // move drag
          const { offsetX: x2, offsetY: y2 } = d3.event;
          diffX = x2 - x1;
          diffY = y2 - y1;

          console.log(d3.event, 'd3.event move');
        });

        svg.on('mouseup', function () {
          // end drag
          console.log(d3.event, 'd3.event up');
          svg.on('mousemove', null);
          svg.on('mouseup', null);
        });
      });
  };

自定义drag完整代码

<!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>
    #container {
      width: 500px;
      margin: 50px auto 0;
    }
  </style>
</head>

<body>
  <div id="container">

  </div>
</body>

</html>
<script src="https://d3js.org/d3.v5.min.js"></script>

<script>
  const svg = d3.select('#container')
    .append('svg')
    .attr('width', 500)
    .attr('height', 500);

  const data = [
    { id: 1, fill: 'black', x: 10, y: 10 },
    { id: 2, fill: 'black', x: 50, y: 50 },
    { id: 3, fill: 'black', x: 100, y: 70 },
    { id: 4, fill: 'black', x: 20, y: 100 }
  ];

  draw(); // 绘制
  function draw() {
    const update = svg.selectAll('rect')
      .data(data, d => d.id);

    //修改层
    update.attr('x', (d, idx) => d.x)
      .attr('y', (d, idx) => d.y)
      .attr('fill', (d) => d.fill)

    //渲染层
    const enter = update.enter();

    //删除层
    const exit = update.exit();

    enter.append('rect')
      .attr('width', 20)
      .attr('height', 20)
      .attr('id', d => `rect-${d.id}`) //不能绑定数字咱们给拼接一个字符串 error: Uncaught DOMException: Failed to execute 'querySelector' on 'Document': '#4' is not a valid selector.
      .attr('x', (d, idx) => d.x)
      .attr('y', (d, idx) => d.y)
      .attr('fill', (d) => d.fill)
      .attr('stroke', 'blue')
      .attr('strokeWidth', 1)

    exit.remove()
  }



  const selected = [];
  bindEvent(); // 绑定点击事件

  function bindEvent() { // 绑定点击事件(看不懂的去前面d3event事件绑定去看看)
    svg.selectAll('rect').on('click', (d) => {

      const index = selected.indexOf(d.id);
      if (index !== -1) {
        selected.splice(index, 1);
      } else {
        selected.push(d.id);
      }
      d3.event.preventDefault();
      updateColor();
    });
  };

  function updateColor() { // 更新color 
    console.log(selected, 'selected')
    data.forEach((item) => {
      if (selected.includes(item.id)) {
        item.fill = 'red';
      }
    });
    draw(); // 重新绘制,更新需要高亮的点(d3数据绑定函数,不理解怎么更新的可以去看看d3数据绑定篇幅)
  };


  bindDrag(); //绑定drag
  function bindDrag() {
    let diffX; let diffY; let currentMoveElement;
    svg.selectAll('rect')
      .on('mousedown', function (d) {
        // start drag
        const { offsetX: x1, offsetY: y1 } = d3.event; // 使用d3.event获取当前的x、y
        console.log(d3.event, 'd3.event down');

        if (selected.includes(d.id)) { // 判断是不是拖拽的是高亮选中的节点(ps: 多个节点拖拽)
          currentMoveElement = selected.map(item => d3.select(`#rect-${item}`));
        } else { // 当前选中的节点拖拽
          currentMoveElement = [d3.select(`#rect-${d.id}`)];
        }

        svg.on('mousemove', function () {
          // move drag
          const { offsetX: x2, offsetY: y2 } = d3.event;
          diffX = x2 - x1;
          diffY = y2 - y1;

          if (currentMoveElement) {
            currentMoveElement.forEach((item) => { // 统一更改坐标
              const { x, y } = item.datum();
              item.attr('x', x + diffX) // 一直修改其位置
                .attr('y', y + diffY);
            })
          }

          console.log(d3.event, 'd3.event move');
        });

        svg.on('mouseup', function () {
          // end drag
          console.log(d3.event, 'd3.event up');
          svg.on('mousemove', null);
          svg.on('mouseup', null);


          const dragIdMap = new Map;
          currentMoveElement.forEach(item => { // 统一更改绑定数据的值
            const originData = item.datum();
            dragIdMap.set(originData.id, true);
            item.datum({ // 重新绑定拖拽后的新坐标
              ...originData,
              x: originData.x + diffX,
              y: originData.y + diffY,
            })
          });

          //对应的data原始数据也要更新,假如你有个保存的场景 那尼获取到的坐标就不是最新的了
          data.forEach((item) => {
            if (dragIdMap.has(item.id)) {
              item.x = item.x + diffX;
              item.y = item.y + diffY;
            }
          })
        });
      });
  };
</script>

结束语

相信大家经过多个小方块一块拖动,和自定义拖拽对drag有了一个很深的认识了

阅读量:426

点赞量:0

收藏量:0