Issue with SVG path not animating as expected

960 views Asked by At

I am using this video tutorial about inline SVGs as a reference. I am trying to replicate the effect of making an SVG line appear to draw itself in from its middle point out. The only difference between the approach from the video and mine is that in the video, the SVG line has a predefined length, where as mine is of variable length.

The idea behind the approach is pretty simple. You make an SVG line of whatever size you want, then set its stroke-dasharray property to be '0, length/2' and its stroke-dashoffset property '-length/2', so that the line is not drawn at all at first and its 'starting point' is set to be at its middle point. Then when the relevant input field is focused, you change the dasharray property to be 'length, 0' and the dashoffset property to 0. This makes the dashes be equal to the length of the line. This should make the line appear to draw itself in from the middle point, and indeed this does happen if you know the length of the line from the start. However, this is not what happens when I try to implement this approach using lines that don't have predetermined lengths. My line appears to draw itself in from almost the very beginning of the line, instead of from the middle point. I am confused as to why this is happening. I am using JavaScript to calculate the length of the line. Below is a snippet of my code.

function animateLine() {

  const input = document.querySelector('.input');
  const line = document.querySelector('.focus');
  
  const length = line.getTotalLength();
  
  line.style.strokeDasharray = `0, ${length/2}`;
  line.style.strokeDashoffset = `-${length/2}`;
  
  input.addEventListener('focus', function() {
    line.style.strokeDasharray = `${length}, 0`;
    line.style.strokeDashoffset = `0`;
  });
};


animateLine();
/* Input module */

.input {
  background: none;
  border: none;
  width: 100 %;
  padding: 0.5em;
  padding-left: 0;
  color: white;
  font-family: inherit;
  font-size: 0.85em;
}

.input:focus {
  outline: none;
}

.line {
  width: 100%;
  height: 2px;
  padding: 0;
  stroke: grey;
}

.focus {
  stroke: black;
  transition: all 5s;
  stroke-dasharray: 0, 10000;
}
<!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>
</head>

<body>
  <input class="input" type="text" name="review[body]" placeholder="Leave a review..." required>
  <svg xmlns="http://www.w3.org/2000/svg" class="line" viewBox="0 0 40 2" preserveAspectRatio="none">
                        <path d="M0 1 L40 1"></path>
                        <path d="M0 1 L40 1" class="focus"></path>
</svg>
</body>

</html>

I tried playing around with different percentages, and just using a value of '20' as my half-length because my viewport is of length '40', but none of this worked. Does anyone know what could be the problem here?

1

There are 1 answers

0
Christian Bernal On

I figured out the problem. It's an embarrassing mistake but I'll leave it up here regardless in case anyone ever comes across this problem too.

I was using "transition: all 5s" to better see where the middle point was, when I animated my SVG line in. The problem was that at the start, I was setting stroke-dasharray to "0, 10000" to make the line invisible, and I was not setting the stroke-dashoffset property because I thought I needed to first figure out what the middle point was. I was then setting these 2 properties to what they should be using JavaScript. This triggered a transition to initiate, which took 5 seconds. So when I focused on the relevant input element, I was NOT starting the animation from the middle point like I wanted to; I wasn't waiting 5 seconds when I loaded to the page, which didn't give the initial animation enough time to finish getting to the middle point of the line.

All of this was due to a misunderstanding of mine. My understanding was that the length of the SVG line was proportional to the size of the div containing it (since I was setting the width of the 'line' class to be 100%). However, SVG sizes are actually calculated using viewport units, not pixels or other absolute units. Knowing this, I realized that I could achieve the effect that I was looking using a much simpler approach with no JavaScript involved. We can simply ignore what the actual displayed size of the SVG is going to be, and only focus on the viewport units we initially set. The browser will actually make this viewport units be proportional to whatever space is available to the SVG by default. So instead of calculating what the length of the SVG path is, we can simply use a width of 20 viewport units as the middle point of our SVG line, since we know that these viewport units will be proportional to the space that's available to the SVG. I'd like to emphasize that this will work for any line lengths, as long as you are using a viewport of 40.

/* Input module */

.input {
  background: none;
  border: none;
  width: 100%;
  padding: 0.5em;
  padding-left: 0;
  color: black;
  font-family: inherit;
  font-size: 0.85em;
}

.input:focus {
  outline: none;
}


/* SVGs */

.line {
  width: 100%;
  height: 2px;
  padding: 0;
  stroke: grey;
}

.focus {
  stroke: black;
  transition: all 2s;
  stroke-dasharray: 0, 20;
  stroke-dashoffset: -20;
}

.input:focus~.line .focus {
  stroke-dasharray: 40;
  stroke-dashoffset: 0;
}
<!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>
</head>

<body>
  <input class="input" type="text" name="review[body]" placeholder="Leave a review..." required>
  <svg xmlns="http://www.w3.org/2000/svg" class="line" viewBox="0 0 40 2" preserveAspectRatio="none">
                        <path d="M0 1 L40 1"></path>
                        <path d="M0 1 L40 1" class="focus">
                        </path>
                    </svg>
</body>

</html>

And that is pretty much it. Really basic mistake of mine.