1

If that possible to change the Rect element stroke(border) to wave style like below image. I will let user to draw Rect by mouse moving.

I know how to change border to Dashed but how about wave style?

enter image description here

This is the Rect sample code for Konva.js and API doc https://konvajs.org/api/Konva.Rect.html

<!DOCTYPE html>
<html>
  <head>
    <script src="https://unpkg.com/konva@7.0.3/konva.min.js"></script>
    <meta charset="utf-8" />
    <title>Konva Rect Demo</title>
    <style>
      body {
        margin: 0;
        padding: 0;
        overflow: hidden;
        background-color: #f0f0f0;
      }
    </style>
  </head>
  <body>
    <div id="container"></div>
    <script>
      var width = window.innerWidth;
      var height = window.innerHeight;

      var stage = new Konva.Stage({
        container: "container",
        width: width,
        height: height,
      });

      var layer = new Konva.Layer();

      var rect1 = new Konva.Rect({
        x: 20,
        y: 20,
        width: 100,
        height: 50,
        dash: [10, 10],
        stroke: "black",
        strokeWidth: 4,
      });
      layer.add(rect1);
      stage.add(layer);
    </script>
  </body>
</html>
MichaelMao
  • 2,596
  • 2
  • 23
  • 54
  • On first reading I thought I might down-vote because the question is unclear and has no 'what-I-tried' code. However, on reflection, the question is pitched perfectly. It gives the image example of the target, and asks if it is possible meaning the answers can be similarly conceptual then provide an example to show the way. +1 from me. I guess the answer could be some kind of path but I've no idea how to define it! – Vanquished Wombat Jul 28 '20 at 08:57
  • Thank you. I provide a sample code for people easy to try. But about 'what-I-try', I will say I only study the document and Konva.js official website also search on SO and can't find anything. Oh..I also ask this on Konva.js official website. And I only find the dashed border sample. – MichaelMao Jul 28 '20 at 09:38
  • @VanquishedWombat You say 'some kind of path' but if I know how to write that where to put it? The problem is I can't find how can I change the border style – MichaelMao Jul 28 '20 at 09:43
  • I think the border style is fixed as a line, but it can have a pattern though I think the pattern is more about defining dashes or dots that a 'curly' path as per your image. I am sure @lavrton will be along with a simple answer shortly when the sun rises on his location. – Vanquished Wombat Jul 28 '20 at 11:09
  • Where to set the pattern? I can try that but the problem is I don't know where to set it – MichaelMao Jul 28 '20 at 11:20
  • One long-winded approach would be to create a SVG image of on squiggle, then add 'border' rectangle shapes to each side of the main rect and repeat-draw the SVG image into each using appropriate orientation to achieve the result you seek. The key would be to draw the SVG so that the line seemed continuous. – Vanquished Wombat Jul 28 '20 at 11:58
  • Interesting overlapping question here https://stackoverflow.com/questions/42441472/draw-a-squiggly-line-in-svg – Vanquished Wombat Jul 28 '20 at 13:27

1 Answers1

5

So thanks to the excellent answer to this similar question, I give you this. The practical approach by @Paul LeBeau in that answer, converted to Konvajs.

enter image description here

Here is the working snippet - perhaps best viewed full screen. Also a codepen here so you can play.

// Set up a stage
let stage = new Konva.Stage({
        container: 'container',
        width: window.innerWidth,
        height: window.innerHeight,
      }),

  // add a layer to draw on
  layer = new Konva.Layer(),

  // Add a rect to give us a path as we draw
  rubberRect = new Konva.Rect({ X:0, Y:0, width: 0, height: 0, stroke: 'transparent', visible: false}),
  
  // and a path to provide the squiggles
  rubberSquiggles  = new Konva.Path({
        x: rubberRect.position().x,
        y: rubberRect.position().y,
        data: '',
        stroke: 'cyan',
        visible: false
      });

// Stick it all into the canvas.
layer.add(rubberRect);
layer.add(rubberSquiggles);
stage.add(layer);

// Now we need a primitive state machine and position tracking so that 
// we can follow the users mouse when they are scribing a rectangle and
// clone the final rect and path when they mouseup/

// Handy object to store state and rect position drawn 
let rectPos = {state: 'waiting', x1:0, y1: 0, x2: 0, y2: 0};

// listen for mousedown
stage.on('mousedown', function(e){

  // Set state and note the mouse starting pos. 
  rectPos = {state: 'drawing', x1: e.evt.layerX , y1: e.evt.layerY, x2: e.evt.layerX, y2: e.evt.layerY};  
  
  // ANd position the rubber rect and squiggle line there.
  rubberRect.position({x: rectPos.x1, y: rectPos.y1})
  rubberSquiggles.position({x: rectPos.x1, y: rectPos.y1});
    
  // better make sure the rubber shapes are on show.
  rubberRect.show();
  rubberSquiggles.show();
    
})

// listen for mouse move
stage.on('mousemove', function(e){
  
  
  // only draw if the mouse state is 'drawing'... 
  if (rectPos.state === 'drawing'){ 
    
    // In which case note the mouse pos...
    rectPos.x2 = e.evt.layerX;
    rectPos.y2 = e.evt.layerY;   
    
    // ...and apply to the rubber rect
    rubberRect.size({
      width: rectPos.x2 - rectPos.x1,
      height: rectPos.y2 - rectPos.y1,
    });

    // and re-calc the squiggles path, then apply it direct to the shape.    
    rubberSquiggles.data(konvaSquigglePath(rubberRect, 20, 8));
    
    layer.batchDraw();
    
  }
})

// Listen for mouseup
stage.on('mouseup', function(e){
  
  // only draw if the mouse state is 'drawing'... 
  if (rectPos.state === 'drawing'){

    // switch drawing mode off
    rectPos.state = 'waiting';

    // clone the rubber shapes
    let newRect = rubberRect.clone();
    let newPath = rubberSquiggles.clone();
    layer.add(newRect)
    layer.add(newPath)

    // hide the rubber shapes until the next draw.
    rubberRect.hide();
    rubberSquiggles.hide();
  }
})
  
/* this is the func that calculates the path.
  Props to the answer by @Paul LeBeau to https://stackoverflow.com/questions/42441472/draw-a-squiggly-line-in-svg which I have shamefully adapted here to work with Konvjs.

We pass in the konva rect from which we get the simple path. The clever part is how the 
function breaks the path into small lengths and draws beziers alternating along the points.

Play with the squiggleStep and squiggleAmplitude param values to find a pleasent effect.

*/
function konvaSquigglePath(rect, squiggleStep, squiggleAmplitude){
  
  let r = {x1: 0, y1: 0, x2: rect.width(), y2: rect.height() },

      // make a simple path around the given rect - is there an easier way ? 
     p = new Konva.Path({
      data: 'M 0 0 H ' + r.x2 + ' V ' + r.y2 + ' H 0 L 0 0'
      }),

    length = p.getLength(),
    pathLen = p.getLength(),
 
    // Adjust step so that there are a whole number of steps along the path
    numSteps = Math.round(pathLen / squiggleStep),
    pos = p.getPointAtLength(0),
    newPath = "M" + [pos.x, pos.y].join(','),
    side = -1;
  
  for (let i=1; i<=numSteps; i++)
  {
    let last = pos;
    pos = p.getPointAtLength(i * pathLen / numSteps);
    
    // Find a point halfway between last and pos. Then find the point that is
    // perpendicular to that line segment, and is squiggleAmplitude away from
    // it on the side of the line designated by 'side' (-1 or +1).
    // This point will be the control point of the quadratic curve forming the
    // squiggle step.
    
    // The vector from the last point to this one
    let vector = {x: (pos.x - last.x),
                  y: (pos.y - last.y)};
    // The length of this vector
    let vectorLen = Math.sqrt(vector.x * vector.x + vector.y * vector.y);
    // The point halfway between last point and this one
    let half = {x: (last.x + vector.x/2),
                y: (last.y + vector.y/2)};
    // The vector that is perpendicular to 'vector'
    let perpVector = {x: -(squiggleAmplitude * vector.y / vectorLen),
                      y: (squiggleAmplitude * vector.x / vectorLen)};
    // No calculate the control point position
    let controlPoint = {x: (half.x + perpVector.x * side),
                        y: (half.y + perpVector.y * side)};
    newPath += ("Q" + [controlPoint.x, controlPoint.y, pos.x, pos.y].join(','));
    // Switch the side (for next step)
    side = -side;
    
  }
  return newPath;
}
body {
  margin: 10;
  padding: 10;
  overflow: hidden;
  background-color: #f0f0f0;
}
p {
border-bottom: 1px solid #333;
}
<script src="https://unpkg.com/konva@^3/konva.min.js"></script>
<p>Draw a rectangle on the white surface.
</p>
<div id="container"></div>
Vanquished Wombat
  • 9,075
  • 5
  • 28
  • 67