gordie
gordie

Reputation: 1955

How can I animate the tracing a SVG rectangle using JavaScript?

For a game I'm working on,

I would like to be able to draw a SVG rectangle; using a percentage value (50% would draw half the rectangle stroke).
I need to do this in Javascript since I'll update the value quite often.

<svg id="rectangle-timer" style="width:100%;height:100%;">
    <rect width="100%" height="100%"/>
</svg>

I saw quite nice JS libraries like drawSVG or Vivus, but it seems that they work with paths, not with basic shapes like rectangles.

Can anyone help ?

Thanks.

Upvotes: 2

Views: 1325

Answers (2)

somethinghere
somethinghere

Reputation: 17340

The reason most libraries will use path elements is because of their inheritance from the SVGGeometryElement prototype, which gives you handy functions for computing the path length. So if we swap out this rectangle for a path like this:

<path d="M 0 0 L 1 0 L 1 1 L 0 1 z" />

We get exactly the same output, but its much more controllable. After that, we can just adjust the strokeDasharray value in the style to extend and remove some stroke. For this property we just need two values: initial dash size and initial empty space. So when our progress is 0, we want the first value to be 0 and the second to be the path length , and as we approach 1 we want the second value to 0 and the first one to increase to the path length.

function update( amount ){
  
  const total = rect.getTotalLength();
  const filled = total * amount;
  const none = total - filled;
  
  rect.style.strokeDasharray = `${filled} ${none}`;
  
}

const rect = document.getElementById( 'rectangle' );
const input = document.getElementById( 'input' );

input.addEventListener( 'mousemove', event => update( input.value ));
update( input.value );
<svg width="200px" height="200px" viewBox="0 0 200 200">
    <path d="M 20 20 L 180 20 L 180 180 L 20 180 z" id="rectangle" fill="none" stroke="black" stroke-width="10" />
</svg>

<input id="input" type="range" min="0" max="1" step=".01" />

If you insist on using a rect, you could get a rectangle's path length by taking its width and height twice, which would look something like this:

function update( amount ){
  
  const total = rect.width.baseVal.value * 2 + rect.height.baseVal.value * 2;
  const filled = total * amount;
  const none = total - filled;
  
  rect.style.strokeDasharray = `${filled} ${none}`;
  
}

const rect = document.getElementById( 'rectangle' );
const input = document.getElementById( 'input' );

input.addEventListener( 'mousemove', event => update( input.value ));
update( input.value );
<svg width="200px" height="200px" viewBox="0 0 200 200">
    <rect x="20" y="20" width="160" height="160" id="rectangle" fill="none" stroke="black" stroke-width="10" />
</svg>

<input id="input" type="range" min="0" max="1" step=".01" />

In the long run, however, this would mean less versatility, so I would suggest switching to path.

Upvotes: 4

enxaneta
enxaneta

Reputation: 33044

This is my solution: The SVG has preserveAspectRatio ="none" style="width:100%;height:100vh;" The total length of the path is 2*window.innerWidth + 2*window.innerHeight; Both stroke-dasharray and stroke-dashoffset are igual to the total length of the path.

I'm using an input type="range" to animate the stroke-dashoffset. In order to preserve the stroke width and avoid stretching I'm using vector-effect="non-scaling-stroke"

I hope this is what you need.

function timer(){
let totalLength = 2*window.innerWidth + 2*window.innerHeight;
thePath.setAttributeNS(null, "style", `stroke-dashoffset:${totalLength * (1-range.value)}`)
}
timer()

range.addEventListener("input",timer);


setTimeout(function() {
		timer()
		addEventListener('resize', timer, false);
}, 15);
*{margin:0; padding:0;}
#thePath {
  stroke-dasharray: calc(2 * 100vw + 2* 100vh);
  stroke-dashoffset: calc(2 * 100vw + 2* 100vh);
}
#rectangle-timer{background:#dfdfdf}
[type="range"] {
  position: absolute;
  display: block;
  width: 200px;
  height: 20px;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  margin: auto;
}
<svg id="rectangle-timer" viewBox="0 0 100 100" preserveAspectRatio ="none" style="width:100%;height:100vh;">

    <path id="thePath" d="M0,0L100,0 100,100 0,100 0,0" fill="none" stroke="skyBlue" stroke-width="25" vector-effect="non-scaling-stroke" />
</svg>

<input type="range" id="range" value=".5" min="0" max="1" step=".01"  />

Upvotes: 3

Related Questions