【d3js】d3.drag 让你的元素能拖动起来-灵析社区

JOHO

前言

经过前几篇大家对d3js相信有了一个新得认识,那今天就带大家领略下drag的魅力。d3.drag方法是给指定元素绑定一个drag事件, 它内部有start、drag、end几个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(); // 绘制
  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 => d.id)
      .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()
  }
</script>

使用d3.drag

使用d3.drag对小方块实现拖动事件,让根据选中的小方块跟着鼠标拖动 效果图:

code

bindDrag()// 给小方块绑定drag
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')
  };
其实认真看代码d3.drag, 还是有几个点需要主要到的:1、可以提前获取到目标元素,避免每次再drag的时候一直获取。2、使用d3.event可以获取到元素拖拽到的新坐标,值x、y实时更改位置。3、再拖拽完成需要更改绑定的值,以及同步原数据。

源码

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

总结

相信大家也看到d3.drag的能力了吧!!那么我上面实现了单个小方块的拖拽,如何实现多个同时拖拽的?再或者怎么不适用d3.drag自己实现一个drag呢? 下期d3.drag扩展咱们一期来看下如何实现我上面那些能力


阅读量:1015

点赞量:0

收藏量:0