My issues
Currently, I have positioned the pathIcon along the 'd' path of the SVG path. However, if I change the size of my SVG, the 'd' attribute must also change.
How can I define a new 'd' that resembles the old SVG path with the new size? I don't know how to define a new 'd' in JavaScript.
document.addEventListener("DOMContentLoaded", function() {
let svg = document.querySelector(".svg-path");
let mPath = document.getElementById("Path_440");
let strokePath = document.getElementById("theFill");
let pathIcon = document.getElementById("pathIcon");
const svgRect = svg.getBBox();
const width = svgRect.width;
const height = svgRect.height;
svg.setAttribute("viewBox", `0 0 ${width} ${height}`)
function defineNewOffsetPath()
{
/**my issues : defineNewOffsetPath
mPath.setAttribute("d", newPath)
how to define a path look like old path with new size
**/
pathIcon.style.offsetPath = `path('${mPath.getAttribute("d")}')`;
}
defineNewOffsetPath();
})
.svg-path {
overflow: visible;
width: 100%;
height: auto;
}
#pathIcon {
position: absolute;
inset: 0;
width: 100px;
height: 200px;
background-size: 25px;
offset-rotate: 0rad;
transition: 0.2s;
offset-distance: 0%;
}
#Path_440 {
stroke-width: 2;
stroke: #001d36;
}
<div style="height: 175px"></div>
<div id="scrollDiv" style="position: relative">
<svg class="svg-path" viewBox="0 0 0 0" fill="none">
<defs>
<path id="Path_440"
d="M1293 2S1277 76.47 1197 93.5C1105.55 112.97 888.33 91.07 772.5 100.5 545.5 100.5 302.61 125.94 279 295.5 268 374.5 265.11 419.83 268 503S269.9 645.65 305 741C346.77 854.46 770 838.5 1094.5 832 1366 842.5 1676.02 792 1766 1011 1803.18 1101.5 1766 1457.5 1766 1493.5 1766 1561 1696 1613.5 1618 1627.5 1465 1627.5 1188.11 1632.5 1003.5 1632.5 732.5 1632.5 369.53 1605.69 312 1717.5 271.61 1796 276 1920 276 1982 277.12 2074.28 272.55 2144.17 312 2258 339.86 2338.39 721.15 2324.5 981 2324.5 1297 2324.5 1677.34 2307.5 1739.5 2403.5 1793.57 2487 1772.73 2616.18 1772.73 2765 1772.73 2893 1770.73 2997.5 1652 3032 1612.67 3043.43 1237 3032 893 3032 405.5 3032 2 3030 2 3030"
stroke="#020878" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke-dasharray="20 20" />
</defs>
<use href="#Path_440" stroke-width="10" stroke-dasharray="20 10"></use>
<use id="theFill" href="#Path_440" stroke-dasharray="1991.82, 9259.88" stroke-width="10" stroke="#4cacff"></use>
</svg>
<svg id="pathIcon" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect width="100" height="200" fill="url(#pattern0)" />
<defs>
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image0_873_8619" transform="scale(0.00353357 0.00176678)" />
</pattern>
<image id="image0_873_8619" width="283" height="566" xlink:href="data:image/png;base64,iVBORw0KGgoAAAA" />
</defs>
</svg>
</div>
Provided you only need to scale your path data proportional – it is pretty straight forward:
d
attribute)a
arc command values like x-axis-rotation, largeArc, and sweep flag)d
attributelet svg = document.querySelector(".svg-path");
let mPath = document.getElementById("Path_440");
let strokePath = document.getElementById("theFill");
let pathIcon = document.getElementById("pathIcon");
document.addEventListener("DOMContentLoaded", function () {
// auto adjust viewBox
let svgRect = svg.getBBox();
let width = svgRect.width;
let height = svgRect.height;
svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
// update offset path
defineNewOffsetPath();
});
function defineNewOffsetPath() {
// retrieve the current scale from SVG transformation matrix
let matrix = svg.getCTM();
let scale = matrix.a;
// parse path data
let d = mPath.getAttribute("d");
let pathData = parsePathData(d);
//scale pathdata
pathData = scalePathData(pathData, scale);
// apply scaled pathdata as stringified d attribute value
d = pathDataToD(pathData);
pathIcon.style.offsetPath = `path('${d}')`;
}
// recalculate offset path on resize
window.addEventListener("resize", (e) => {
defineNewOffsetPath();
});
// just for illustration
resizeObserver()
function resizeObserver() {
defineNewOffsetPath();
}
new ResizeObserver(resizeObserver).observe(scrollDiv)
/**
* sclae path data proportional
*/
function scalePathData(pathData, scale = 1) {
let pathDataScaled = [];
pathData.forEach((com, i) => {
let { type, values } = com;
let comT = {
type: type,
values: []
};
switch (type.toLowerCase()) {
// lineto shorthands
case "h":
comT.values = [values[0] * scale]; // horizontal - x-only
break;
case "v":
comT.values = [values[0] * scale]; // vertical - x-only
break;
// arcto
case "a":
comT.values = [
values[0] * scale, // rx: scale
values[1] * scale, // ry: scale
values[2], // x-axis-rotation: keep it
values[3], // largeArc: dito
values[4], // sweep: dito
values[5] * scale, // final x: scale
values[6] * scale // final y: scale
];
break;
/**
* Other point based commands: L, C, S, Q, T
* scale all values
*/
default:
if (values.length) {
comT.values = values.map((val, i) => {
return val * scale;
});
}
}
pathDataScaled.push(comT);
});
return pathDataScaled;
}
/**
* parse stringified path data used in d attribute
* to an array of computable command data
*/
function parsePathData(d) {
d = d
// remove new lines, tabs an comma with whitespace
.replace(/[\n\r\t|,]/g, " ")
// pre trim left and right whitespace
.trim()
// add space before minus sign
.replace(/(\d)-/g, "$1 -")
// decompose multiple adjacent decimal delimiters like 0.5.5.5 => 0.5 0.5 0.5
.replace(/(\.)(?=(\d+\.\d+)+)(\d+)/g, "$1$3 ");
let pathData = [];
let cmdRegEx = /([mlcqazvhst])([^mlcqazvhst]*)/gi;
let commands = d.match(cmdRegEx);
// valid command value lengths
let comLengths = {
m: 2,
a: 7,
c: 6,
h: 1,
l: 2,
q: 4,
s: 4,
t: 2,
v: 1,
z: 0
};
commands.forEach((com) => {
let type = com.substring(0, 1);
let typeRel = type.toLowerCase();
let isRel = type === typeRel;
let chunkSize = comLengths[typeRel];
// split values to array
let values = com.substring(1, com.length).trim().split(" ").filter(Boolean);
/**
* A - Arc commands
* large arc and sweep flags
* are boolean and can be concatenated like
* 11 or 01
* or be concatenated with the final on path points like
* 1110 10 => 1 1 10 10
*/
if (typeRel === "a" && values.length != comLengths.a) {
let n = 0,
arcValues = [];
for (let i = 0; i < values.length; i++) {
let value = values[i];
// reset counter
if (n >= chunkSize) {
n = 0;
}
// if 3. or 4. parameter longer than 1
if ((n === 3 || n === 4) && value.length > 1) {
let largeArc = n === 3 ? value.substring(0, 1) : "";
let sweep = n === 3 ? value.substring(1, 2) : value.substring(0, 1);
let finalX = n === 3 ? value.substring(2) : value.substring(1);
let comN = [largeArc, sweep, finalX].filter(Boolean);
arcValues.push(comN);
n += comN.length;
} else {
// regular
arcValues.push(value);
n++;
}
}
values = arcValues.flat().filter(Boolean);
}
// string to number
values = values.map(Number);
// if string contains repeated shorthand commands - split them
let hasMultiple = values.length > chunkSize;
let chunk = hasMultiple ? values.slice(0, chunkSize) : values;
let comChunks = [
{
type: type,
values: chunk
}
];
// has implicit or repeated commands – split into chunks
if (hasMultiple) {
let typeImplicit = typeRel === "m" ? (isRel ? "l" : "L") : type;
for (let i = chunkSize; i < values.length; i += chunkSize) {
let chunk = values.slice(i, i + chunkSize);
comChunks.push({
type: typeImplicit,
values: chunk
});
}
}
comChunks.forEach((com) => {
pathData.push(com);
});
});
/**
* first M is always absolute/uppercase -
* unless it adds relative linetos
* (facilitates d concatenating)
*/
pathData[0].type = "M";
return pathData;
}
/**
* serialize pathData array to
* d attribute string
*/
function pathDataToD(pathData, decimals = 3) {
let d = ``;
pathData.forEach((com) => {
d += `${com.type}${com.values
.map((val) => {
return +val.toFixed(decimals);
})
.join(" ")}`;
});
return d;
}
html{
margin:0;
padding:0;
}
.svg-path {
overflow: visible;
width: 100%;
}
#pathIcon {
position: absolute;
inset: 0;
width: 5vw;
height: 5vw;
offset-rotate: 0deg;
offset-distance: 10%;
}
#scrollDiv{
resize:both;
overflow:auto;
border: 1px solid #ccc;
margin:10px;
}
<div id="scrollDiv" style="position: relative">
<svg class="svg-path" viewBox="0 0 0 0" fill="none">
<defs>
<path id="Path_440"
d="M1293 2 s-16 74.47-96 91.5c-91.45 19.47-308.67-2.43-424.5 7-227 0-469.89 25.44-493.5 195-11 79-13.89 124.33-11 207.5s1.9 142.65 37 238c41.77 113.46 465 97.5 789.5 91 271.5 10.5 581.52-40 671.5 179 37.18 90.5 0 446.5 0 482.5 0 67.5-70 120-148 134-153 0-429.89 5-614.5 5-271 0-633.97-26.81-691.5 85-40.39 78.5-36 202.5-36 264.5 1.12 92.28-3.45 162.17 36 276 27.86 80.39 409.15 66.5 669 66.5 316 0 696.34-17 758.5 79 54.07 83.5 33.23 212.68 33.23 361.5 0 128-2 232.5-120.73 267-39.33 11.43-415 0-759 0-487.5 0-891-2-891-2"
/>
</defs>
<use class="stroke" href="#Path_440" stroke="#ccc" stroke-width="10" stroke-dasharray="20 10"></use>
<use class="stroke" id="theFill" href="#Path_440" stroke-dasharray="925.988 9259.88" stroke-width="10" stroke="#4cacff"></use>
</svg>
<svg id="pathIcon" fill="none">
<rect width="100" height="100" fill="red" fill-opacity="0.5"/>
</svg>
</div>