<svg id="svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 400">
<path id="path" fill="#673ab7" d=""/>
</svg>
svg {
width: 300px;
outline: 1px dashed gray;
}
const svgEl = document.querySelector("#svg");
const pathEl = document.querySelector("#path");
const w = 300;
const h = 400;
const SVGRect = svgEl.getBoundingClientRect();
const maxDist = Math.hypot(w, h);
const corners = {
tl: { x: 0, y: 0, sx: 0, sy: 0, r: 0, minr: 0, maxr: 50, shift: 50 },
tr: { x: w, y: 0, sx: w, sy: 0, r: 0, minr: 0, maxr: 50, shift: 50 },
br: { x: w, y: h, sx: w, sy: h, r: 0, minr: 0, maxr: 50, shift: 50 },
bl: { x: 0, y: h, sx: 0, sy: h, r: 0, minr: 0, maxr: 50, shift: 50 }
};
const sides = {
t: { s: 0, mins: 0, maxs: 20 },
r: { s: 0, mins: 0, maxs: 20 },
b: { s: 0, mins: 0, maxs: 20 },
l: { s: 0, mins: 0, maxs: 20 }
};
function updateShape(corners, sides) {
return [
{
type: "M",
x: corners.tl.x,
y: corners.tl.y + corners.tl.r
},
{
type: "A",
rx: corners.tl.r,
ry: corners.tl.r,
x: corners.tl.x + corners.tl.r,
y: corners.tl.y
},
{
type: "Q",
x1: (corners.tl.x + corners.tr.x) / 2,
y1: (corners.tl.y + corners.tr.y) / 2 + sides.t.s,
x: corners.tr.x - corners.tr.r,
y: corners.tr.y
},
{
type: "A",
rx: corners.tr.r,
ry: corners.tr.r,
x: corners.tr.x,
y: corners.tr.y + corners.tr.r
},
{
type: "Q",
x1: (corners.tr.x + corners.br.x) / 2 - sides.r.s,
y1: (corners.tr.y + corners.br.y) / 2,
x: corners.br.x,
y: corners.br.y - corners.br.r
},
{
type: "A",
rx: corners.br.r,
ry: corners.br.r,
x: corners.br.x - corners.br.r,
y: corners.br.y
},
{
type: "Q",
x1: (corners.bl.x + corners.br.x) / 2,
y1: (corners.bl.y + corners.br.y) / 2 - sides.b.s,
x: corners.bl.x + corners.bl.r,
y: corners.bl.y
},
{
type: "A",
rx: corners.bl.r,
ry: corners.bl.r,
x: corners.bl.x,
y: corners.bl.y - corners.bl.r
},
{
type: "Q",
x1: (corners.bl.x + corners.tl.x) / 2 + sides.l.s,
y1: (corners.bl.y + corners.tl.y) / 2,
x: corners.tl.x,
y: corners.tl.y + corners.tl.r
}
];
}
function pointsToD(points) {
let d = "";
for (let p of points) {
switch (p.type) {
case "M":
d += `M ${p.x} ${p.y}`;
break;
case "A":
d += `, A ${p.rx} ${p.ry} 0 0 1 ${p.x} ${p.y}`;
break;
case "Q":
d += `, Q ${p.x1} ${p.y1} ${p.x} ${p.y}`;
break;
}
}
return d;
}
svgEl.addEventListener("mousemove", onMouseMove);
onMouseMove({
clientX: SVGRect.x + SVGRect.width / 2,
clientY: SVGRect.y + SVGRect.height / 2
});
function onMouseMove(event) {
let { clientX: mx, clientY: my } = event;
let x = mx - SVGRect.x;
let y = my - SVGRect.y;
let dist, vec, radius;
for (let cor of Object.values(corners)) {
dist = 1 - Math.hypot(cor.sx - x, cor.sy - y) / maxDist;
dist = clamp(dist, 0, 1);
vec = normalize(x - cor.sx, y - cor.sy);
cor.x = cor.sx + vec.x * cor.shift * dist;
cor.y = cor.sy + vec.y * cor.shift * dist;
cor.r = lerp(cor.minr, cor.maxr, dist);
}
sides.t.s = sides.t.mins + sides.t.maxs * (1 - y / h);
sides.b.s = sides.b.mins + (sides.b.maxs * y) / h;
sides.l.s = sides.l.mins + sides.l.maxs * (1 - x / w);
sides.r.s = sides.r.mins + (sides.r.maxs * x) / w;
const points = updateShape(corners, sides);
pathEl.setAttribute("d", pointsToD(points));
}
function normalize(x, y) {
let len = x * x + y * y;
if (len > 0) {
len = 1 / Math.sqrt(len);
}
x = x * len;
y = y * len;
return { x, y };
}
function clamp(number, min, max) {
return Math.max(min, Math.min(number, max));
}
function lerp(v0, v1, t) {
return v0 * (1 - t) + v1 * t;
}
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.