Skip to content
Advertisement

svg stroke-dashoffset animation with percentage values

I have been successful in animating stroke-dashoffset with value as <length> as folllowing

const svg = document.querySelector("svg");

// variable for the namespace 
const svgns = "http://www.w3.org/2000/svg"

//assigning svg element attribute 
svg.setAttribute('class', 'layer1');
svg.setAttribute('xmlns', svgns);
svg.setAttribute('viewBox', '0 0 400 200');

//make background
var fill1 = '#e6e6e6';

let bg = document.createElementNS(svgns, 'rect');
bg.setAttribute('class', 'bg');
bg.setAttribute('id', 'bg');
bg.setAttribute('width', '400');
bg.setAttribute('height', '200');
bg.setAttribute('fill', fill1);

svg.appendChild(bg);

//wrapper dimension
var x = 20;
var y = 50;
var width = 200;
var height = 50;

let wrapper = document.createElementNS(svgns, 'rect');
wrapper.setAttribute('class', 'wrapper');
wrapper.setAttribute('id', 'wrapper');
wrapper.setAttribute('x', x);
wrapper.setAttribute('y', y);
wrapper.setAttribute('width', width);
wrapper.setAttribute('height', height);
wrapper.setAttribute('fill', 'none');
wrapper.setAttribute('stroke', 'black');
wrapper.setAttribute('stroke-width', '1');

svg.appendChild(wrapper);

let tube = document.createElementNS(svgns, 'line');
tube.setAttribute('class', 'tube');
tube.setAttribute('id', 'tube');
tube.setAttribute('x1', x);
tube.setAttribute('x2', x + width);
tube.setAttribute('y1', y + height / 2);
tube.setAttribute('y2', y + height / 2);
tube.setAttribute('stroke', 'brown');
tube.setAttribute('stroke-width', '1');

svg.appendChild(tube);

var ln = document.querySelector("#tube");
var path = ln.getTotalLength();

ln.style.setProperty("--off", path + 'px');

var pct = 0.60;
var pxl = path - (path * pct);

ln.style.setProperty('--offset', pxl);

ln.style.setProperty('--v1', (path / svg.viewBox.baseVal.width) * 100 + '%');
ln.style.setProperty('--v2', (pxl / svg.viewBox.baseVal.width) * 100 + '%');

var gridNum = 10;
var gridStart = width / gridNum;
var counter = gridStart;

for (var i = 0; i < gridNum; i++) {
    let ln = document.createElementNS(svgns, 'line');
    ln.setAttribute('class', 'axisLines' + i);
    ln.setAttribute('id', 'axisLines' + i);
    var x1 = x + counter;
    ln.setAttribute('x1', x1);
    ln.setAttribute('x2', x + counter);
    ln.setAttribute('y1', y);
    ln.setAttribute('y2', y + height);
    ((gridNum / 2) - 1) == i ? ln.setAttribute('stroke', 'red') : ln.setAttribute('stroke', 'green');
    ln.setAttribute('stroke-width', '1.5');
    svg.appendChild(ln);
    counter = counter + gridStart;

    let tl = document.createElementNS(svgns, 'title');
    ((gridNum / 2) - 1) == i ? tl.textContent = `Middle Coordinate ${x1-x},${y}` : tl.textContent = `Coordinate ${x1-x},${y}`;
    svg.appendChild(tl);
    ln.appendChild(tl);


}
.tube {
    stroke-dasharray: var(--off);
    stroke-dashoffset: var(--off);
    animation: effect 4s ease-out infinite forwards;
}

@keyframes effect {
    100% {
        stroke-dashoffset: var(--offset);
    }
}
<!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>
    <link rel="stylesheet" href="style.css">
    </link>
    <svg>
    <script href="index.js"></script>
</svg>
</body>

</html>

However, I am trying to figure out how can I calculate the exact % value in css if I decide to use <percentage> instead of value.

I came to know from this post that percentages are calculated in respect to the current viewport, which I tried as ln.style.setProperty('--v1', (path / svg.viewBox.baseVal.width) * 100 + '%');

and

ln.style.setProperty('--v2', (pxl / svg.viewBox.baseVal.width) * 100 + '%');

But passing on the above to CSS as following does not produce the same animation

.tube {
    stroke-dasharray: var(--v1);
    stroke-dashoffset: var(--v1);
    animation: effect 4s ease-out infinite forwards;
}

@keyframes effect {
    100% {
        stroke-dashoffset: var(--v2);
    }
}

Answer

You need to calculate the normalised hypotenuse of the width and the height.

const svg = document.querySelector("svg");

// variable for the namespace 
const svgns = "http://www.w3.org/2000/svg"

//assigning svg element attribute 
svg.setAttribute('class', 'layer1');
svg.setAttribute('xmlns', svgns);
svg.setAttribute('viewBox', '0 0 400 200');

//make background
var fill1 = '#e6e6e6';

let bg = document.createElementNS(svgns, 'rect');
bg.setAttribute('class', 'bg');
bg.setAttribute('id', 'bg');
bg.setAttribute('width', '400');
bg.setAttribute('height', '200');
bg.setAttribute('fill', fill1);

svg.appendChild(bg);

//wrapper dimension
var x = 20;
var y = 50;
var width = 200;
var height = 50;

let wrapper = document.createElementNS(svgns, 'rect');
wrapper.setAttribute('class', 'wrapper');
wrapper.setAttribute('id', 'wrapper');
wrapper.setAttribute('x', x);
wrapper.setAttribute('y', y);
wrapper.setAttribute('width', width);
wrapper.setAttribute('height', height);
wrapper.setAttribute('fill', 'none');
wrapper.setAttribute('stroke', 'black');
wrapper.setAttribute('stroke-width', '1');

svg.appendChild(wrapper);

let tube = document.createElementNS(svgns, 'line');
tube.setAttribute('class', 'tube');
tube.setAttribute('id', 'tube');
tube.setAttribute('x1', x);
tube.setAttribute('x2', x + width);
tube.setAttribute('y1', y + height / 2);
tube.setAttribute('y2', y + height / 2);
tube.setAttribute('stroke', 'brown');
tube.setAttribute('stroke-width', '1');

svg.appendChild(tube);

var ln = document.querySelector("#tube");
var path = ln.getTotalLength();

ln.style.setProperty("--off", path + 'px');

var pct = 0.60;
var pxl = path - (path * pct);

ln.style.setProperty('--offset', pxl);

let normalised_hypotenuse = Math.sqrt(svg.viewBox.baseVal.width ** 2 + svg.viewBox.baseVal.height ** 2) / Math.sqrt(2);
ln.style.setProperty('--v1', (path / normalised_hypotenuse) * 100 + '%');
ln.style.setProperty('--v2', (pxl / normalised_hypotenuse) * 100 + '%');

var gridNum = 10;
var gridStart = width / gridNum;
var counter = gridStart;

for (var i = 0; i < gridNum; i++) {
    let ln = document.createElementNS(svgns, 'line');
    ln.setAttribute('class', 'axisLines' + i);
    ln.setAttribute('id', 'axisLines' + i);
    var x1 = x + counter;
    ln.setAttribute('x1', x1);
    ln.setAttribute('x2', x + counter);
    ln.setAttribute('y1', y);
    ln.setAttribute('y2', y + height);
    ((gridNum / 2) - 1) == i ? ln.setAttribute('stroke', 'red') : ln.setAttribute('stroke', 'green');
    ln.setAttribute('stroke-width', '1.5');
    svg.appendChild(ln);
    counter = counter + gridStart;

    let tl = document.createElementNS(svgns, 'title');
    ((gridNum / 2) - 1) == i ? tl.textContent = `Middle Coordinate ${x1-x},${y}` : tl.textContent = `Coordinate ${x1-x},${y}`;
    svg.appendChild(tl);
    ln.appendChild(tl);


}
.tube {
    stroke-dasharray: var(--v1);
    stroke-dashoffset: var(--v1);
    animation: effect 4s ease-out infinite forwards;
}

@keyframes effect {
    100% {
        stroke-dashoffset: var(--v2);
    }
}
<!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>
    <link rel="stylesheet" href="style.css">
    </link>
    <svg>
    <script href="index.js"></script>
</svg>
</body>

</html>
Advertisement