【问题标题】:Konvajs: Create a draggable area with some constraints on one of the childsKonvajs:创建一个对其中一个孩子有一些约束的可拖动区域
【发布时间】:2020-01-27 19:08:34
【问题描述】:

我正在使用 Konva 创建一个时间轴,整个时间轴(舞台)可以在各个方向拖动,但我有一个轴,其中包含时间轴(Konva 组)的所有年份,我想限制它的移动,以便它只水平移动。

我不能使用 dragBoundFunc,因为它会限制时间轴所有节点上的移动。

我尝试使用 dragmove 事件更改元素的位置:

stage.on("dragmove", function(evt) {
  xaxis.y(0);
});

但在拖动舞台时,xaxis 仍会在所有方向上移动。

我也可以为轴和时间线本身使用不同的可拖动层,但是当我拖动轴时,它不会移动时间线,如果我移动时间线也是一样的。

【问题讨论】:

    标签: konvajs


    【解决方案1】:

    作为最简单的解决方案,您只需确保您的时间组的绝对位置相同即可:

    stage.on("dragmove", function(evt) {
      // read absolute position
      const oldAbs = xaxis.absolutePosition();
    
      // set new absolute position, but make sure x = 0
      xaxis.absolutePosition({
       x: oldAbs.x,
       y: 0
      });
    });
    

    【讨论】:

    • 谢谢拉夫顿。那工作得很好。我还添加了一些边界,以便时间线不会越过 x 轴。
    【解决方案2】:

    这是一个功能稍强的版本,它允许垂直拖动事件层,同时保持时间线轴可见以供参考。这使用了两层 - 一层用作包含时间线和网格的背景,而第二层则显示事件。

    这里的关键技术是使用可拖动事件层上的dragMove 事件侦听器来水平而不是垂直同步移动背景层。同时事件层也被一个dragBound函数约束来阻止愚蠢的用户体验。

    一项改进是向事件层添加剪辑,这样当向下拖动时它不会遮挡时间线。

    var stageWidth = 800,
      stageHeight = 300,
      timeFrom = 1960,
      timeTo = 2060,
      timeRange = timeTo - timeFrom,
      timeLineWidth = 1000,
      timeSteps = 20, // over 100 yrs = 5 year intervals
      timeInt = timeRange / timeSteps,
      timeLineStep = timeLineWidth / timeSteps,
      yearWidth = timeLineWidth / timeRange,
      plotHeight = 500,
      events = [{
          date: 1964,
          desc: 'Born',
          dist: 10
        },
        {
          date: 1966,
          desc: 'England win world cup - still celebrating !',
          dist: 20
        },    
        {
          date: 1968,
          desc: 'Infant school',
          dist: 30
        },
        {
          date: 1975,
          desc: 'Secondary school',
          dist: 50
        },
        {
          date: 1981,
          desc: 'Sixth form',
          dist: 7
        },
        {
          date: 1983,
          desc: 'University',
          dist: 30
        },
        {
          date: 1986,
          desc: 'Degree, entered IT career',
          dist: 50
        },
        {
          date: 1990,
          desc: 'Marriage #1',
          dist: 0
        },
        {
          date: 1996,
          desc: 'Divorce #1',
          dist: 0
        },
        {
          date: 1998,
          desc: 'Marriage #2 & Son born',
          dist: 90
        },
        {
          date: 2000,
          desc: 'World did not end',
          dist: 20
        },    
        {
          date: 2025,
          desc: 'Retired ?',
          dist: 0
        },
        {
          date: 2044,
          desc: 'Enters Duncodin - retirement home for IT workers',
          dist: 0
        },
        {
          date: 2054,
          desc: 'Star dust',
          dist: 0
        }
      ]
    
    
    function setup() {
    
      // Set up a stage and a shape
      stage = new Konva.Stage({
        container: 'konva-stage',
        width: stageWidth,
        height: stageHeight
      });
    
      // bgLayer is the background with the grid, timeline and date text.
      var bgLayer = new Konva.Layer({
        draggable: false
      })
    
      stage.add(bgLayer);
    
      for (var i = 0, max = timeSteps; i < max; i = i + 1) {
        bgLayer.add(new Konva.Line({
          points: [(i * timeLineStep) + 0.5, 0, (i * timeLineStep) + .5, plotHeight],
          stroke: 'cyan',
          strokeWidth: 1
        }))
    
        bgLayer.add(new Konva.Text({
          x: (i * timeLineStep) + 4,
          y: 260,
          text: timeFrom + (timeInt * i),
          fontSize: 12,
          fontFamily: 'Calibri',
          fill: 'magenta',
          rotation: 90,
          listening: false
        }));
    
      }
    
      for (var i = 0, max = plotHeight; i < max; i = i + timeLineStep) {
        bgLayer.add(new Konva.Line({
          points: [0, i + 0.5, timeLineWidth, i + .5],
          stroke: 'cyan',
          strokeWidth: 1
        }))
      }
    
      // add timeline
      var timeLine = new Konva.Rect({
        x: 0,
        y: 245,
        height: 1,
        width: timeLineWidth,
        fill: 'magenta',
        listening: false
      });
      bgLayer.add(timeLine)
    
      // eventLayer contains only the event link line and text.
      var eventLayer = new Konva.Layer({
        draggable: true,
    
        // the dragBoundFunc returns an object as {x: val, y: val} in which the x is constricted to stop
        // the user dragging out of sight, and the y is not allowed to change.
        // ! position of bgLayer is moved in x axis in sync with eventLayer via dragMove event 
        dragBoundFunc: function(pos) {
          return {
    
            x: function() {
              var retX = pos.x;
              if (retX > 20) { // if the left exceeds 20px from left edge of stage
                retX = 20;
              } else if (retX < (stageWidth - (timeLineWidth + 50))) { // if the right exceeds 50 px from right edge of stage
                retX = stageWidth - (timeLineWidth + 50);
              }
              return retX;
            }(),
    
            y: function() {
              var retY = pos.y;
              if (retY < 0) {
                retY = 0;
              } else if (retY > 200) {
                retY = 200;
              }
              return retY;
            }()
    
          };
        }
      });
      stage.add(eventLayer);
    
      // ! position of bgLayer is moved in x axis in sync with eventLayer via dragMove event of eventLayer.
      eventLayer.on('dragmove', function() {
        var pos = eventLayer.position();
        var bgPos = bgLayer.position();
        bgLayer.position({
          x: pos.x,
          y: bgPos.y
        }); // <--- move the bgLayer in sync with the event eventLayer.
        stage.draw()
      });
    
      for (var i = 0, max = events.length; i < max; i = i + 1) {
        var event = events[i];
        var link = new Konva.Rect({
          x: yearWidth * (event.date - timeFrom),
          y: 200 - event.dist,
          width: 1,
          height: 55 + event.dist,
          fill: 'magenta',
          listening: false
        });
        eventLayer.add(link)
    
        var eventLabel = new Konva.Text({
          x: yearWidth * (event.date - timeFrom) - 5,
          y: 190 - event.dist,
          text: event.date + ' - ' + event.desc,
          fontSize: 16,
          fontFamily: 'Calibri',
          fill: 'magenta',
          rotation: -90,
          listening: false
        });
        eventLayer.add(eventLabel);
    
        var dragRect = new Konva.Rect({
          x: 0,
          y: 0,
          width: timeLineWidth,
          height: 500,
          opacity: 0,
          fill: 'cyan',
          listening: true
        });
        eventLayer.add(dragRect);
    
        dragRect.moveToTop()
      }
    
      stage.draw()
    
    }
    
    var stage, eventLayer;
    
    setup()
    .konva-stage {
      width: 100%;
      height: 100%;
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/konva/4.0.13/konva.js"></script>
    <p>Drag the timeline left & right AND up & down...</p>
    <div id="konva-stage"></div>

    【讨论】:

    • 感谢您的详细解释和提供的演示,它帮助我更好地理解了它是如何工作的。最后,我使用了另一个较小的解决方案,但这很有帮助。
    【解决方案3】:

    只是为了好玩,我的答案的精简版显示了 ondrag() 函数,没有所有的时间线装饰。

    var stage;
    
    function setup() {
    
      // Set up a stage and a shape
      stage = new Konva.Stage({
        container: 'konva-stage',
        width: 600,
        height: 300
      });
    
      // layer1.
      var layer1 = new Konva.Layer({
        draggable: false
      })
    
      stage.add(layer1);
    
      var ln1 = new Konva.Line({
        points: [10, 0, 10, 20, 10, 10, 0, 10, 20, 10],
        stroke: 'cyan',
        strokeWidth: 4
      });
      layer1.add(ln1);
    
    
    
      var layer2 = new Konva.Layer({
        draggable: true,
      });
      stage.add(layer2);
    
    
      var ln2 = new Konva.Line({
        points: [10, 0, 10, 20, 10, 10, 0, 10, 20, 10],
        stroke: 'magenta',
        strokeWidth: 4
      });
      layer2.add(ln2);
    
      // position the crosses on the canvas
      ln1.position({
        x: 100,
        y: 80
      });
      ln2.position({
        x: 100,
        y: 40
      });
    
      // ! position of layer1 is moved in x axis in sync with layer2 via dragMove event of layer2.
      layer2.on('dragmove', function() {
        var pos = layer2.position();
        var bgPos = layer1.position();
        layer1.position({
          x: pos.x,
          y: bgPos.y
        }); // <--- move  layer1 in sync with layer2.
        stage.draw()
      });
    
      stage.draw()
    }
    
    
    setup()
      .konva-stage {
      width: 100%;
      height: 100%;
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/konva/4.0.13/konva.js"></script>
    <p>Drag the upper cross - only one moves vertically whilst the other is contrained in the y-axis. Both move in sync on the x-axis</p>
    <div id="konva-stage"></div>

    【讨论】:

      【解决方案4】:

      您的要求并不完全清楚,但我假设您想要限制时间线的拖动,以便提供良好的用户体验。请参阅下面的工作 sn-p。大部分代码是时间线的设置。重要的是

      1. 包括一个覆盖整个时间线的矩形,它的不透明度为零并且正在侦听鼠标事件。

      2. 为 layer 提供一个 dragBoundFunc,它返回一个对象为 {x: val, y: val},其中 x 被限制以阻止用户水平拖出视线,并且 y 不允许改变.如果您将矩形和舞台视为矩形,那么数学并不难理解。如果您的时间线是垂直的,请交换 x 和 y 行为。

      var stageWidth = 800,
        timeFrom = 1960,
        timeTo = 2060,
        range = timeTo - timeFrom,
        timeLineWidth = 1000;
      yearWidth = timeLineWidth / range,
        events = [{
            date: 1964,
            desc: 'Born'
          },
          {
            date: 1968,
            desc: 'Infant school'
          },
          {
            date: 1975,
            desc: 'Secondary school'
          },
          {
            date: 1981,
            desc: 'Sixth form'
          },
          {
            date: 1983,
            desc: 'University'
          },
          {
            date: 1986,
            desc: 'Degree, entered IT career'
          },
          {
            date: 1990,
            desc: 'Marriage #1'
          },
          {
            date: 1998,
            desc: 'Marriage #2'
          },
          {
            date: 1999,
            desc: 'Son born'
          },
          {
            date: 2025,
            desc: 'Retired ?'
          },
          {
            date: 2044,
            desc: 'Enters Duncodin - retirement home for IT workers'
          },
          {
            date: 2054,
            desc: 'Star dust'
          }
        ]
      
      
      function setup() {
      
        // Set up a stage and a shape
        stage = new Konva.Stage({
          container: 'konva-stage',
          width: stageWidth,
          height: 500
        });
      
      
        layer = new Konva.Layer({
          draggable: true,
          
          // the dragBoundFunc returns an object as {x: val, y: val} in which the x is constricted to stop
          // the user dragging out of sight, and the y is not allowed to change.
          dragBoundFunc: function(pos) {
            return {
              x: function() {
                retX = pos.x;
                if (retX > 20) {
                  retX = 20;
                } else if (retX < (stageWidth - (timeLineWidth + 50))) {
                  retX = stageWidth - (timeLineWidth + 50);
                }
                return retX;
              }(),
              y: this.absolutePosition().y
            };
          }
        });
        stage.add(layer);
      
        // add timeline
        var timeLine = new Konva.Rect({
          x: 0,
          y: 245,
          height: 10,
          width: timeLineWidth,
          fill: 'magenta',
          listening: false
        });
        layer.add(timeLine)
      
        for (var i = 0, max = events.length; i < max; i = i + 1) {
      
          var event = events[i];
          var link = new Konva.Rect({
            x: yearWidth * (event.date - timeFrom),
            y: 200,
            width: 5,
            height: 55,
            fill: 'magenta',
            listening: false
          });
          layer.add(link)
      
          var timeLabel = new Konva.Text({
            x: yearWidth * (event.date - timeFrom) + 10,
            y: 265,
            text: event.date,
            fontSize: 16,
            fontFamily: 'Calibri',
            fill: 'magenta',
            rotation: 90,
            listening: false
          });
          layer.add(timeLabel);
      
          var eventLabel = new Konva.Text({
            x: yearWidth * (event.date - timeFrom) - 5,
            y: 190,
            text: event.desc,
            fontSize: 16,
            fontFamily: 'Calibri',
            fill: 'magenta',
            rotation: -90,
            listening: false
          });
          layer.add(eventLabel);
      
          var dragRect = new Konva.Rect({
            x: 0,
            y: 0,
            width: timeLineWidth,
            height: 500,
            opacity: 0,
            fill: 'cyan',
            listening: true
          });
          layer.add(dragRect);
      
          dragRect.moveToTop()
        }
      
        stage.draw()
      
      }
      
      var stage, layer;
      
      setup()
      .konva-stage {
        width: 100%;
        height: 100%;
      <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/konva/4.0.13/konva.js"></script>
      <p>Drag the timeline...</p>
      <div id="konva-stage"></div>

      【讨论】:

      • 谢谢@vanquiished-wombat,我可能没有正确解释。我的意思是舞台/时间线可以水平和垂直完全拖动,但线/x 轴的年份只能水平移动。所以,我可以向各个方向拖动,但 x 轴将始终停留在舞台的底部。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-09
      • 1970-01-01
      • 1970-01-01
      • 2017-11-19
      相关资源
      最近更新 更多