Bend Hover Line
Hover
SVG
JS
Preview Links
Custom JS
Enable custom code in preview or view on published site.
Enable custom code?
Last Updated: Mar 3, 2026
<script>
document.addEventListener("DOMContentLoaded", function () {
const useCSSPath = CSS.supports('d', 'path("M0,0")');
document.querySelectorAll(".hover-7_wrap").forEach((component) => {
if (component.hasAttribute("data-hover-7")) return;
component.setAttribute("data-hover-7", "");
const path = component.querySelector("path");
let targetY = 80;
let currentY = 80;
let velocityY = 0;
let targetX = 500;
let currentX = 500;
let isHovering = false;
let animating = false;
let releaseDirection = 0;
let lastClientX = 0;
let lastClientY = 0;
let touchActive = false;
const MID_Y = 80;
const LERP_SPEED = 0.21;
function updatePath() {
const cx = Math.round(currentX * 10) / 10;
const cy = Math.round(currentY * 10) / 10;
const d = `M0,80 Q${cx},${cy} 1000,80`;
if (useCSSPath) {
path.style.d = `path("${d}")`;
} else {
path.setAttribute("d", d);
}
}
function animate() {
if (isHovering) {
currentX += (targetX - currentX) * LERP_SPEED;
currentY += (targetY - currentY) * LERP_SPEED;
velocityY = currentY - MID_Y;
} else {
const displacement = currentY - MID_Y;
const crossed = releaseDirection !== 0 && Math.sign(displacement) !== releaseDirection;
const springForce = -0.028 * displacement;
velocityY += springForce;
velocityY *= crossed ? 0.83 : 0.965;
currentY += velocityY;
currentX += (500 - currentX) * 0.056;
}
updatePath();
const settled = !isHovering && Math.abs(currentY - MID_Y) < 0.05 && Math.abs(velocityY) < 0.05 && Math.abs(currentX - 500) < 0.5;
if (settled) {
currentY = MID_Y;
currentX = 500;
velocityY = 0;
updatePath();
animating = false;
return;
}
animating = true;
requestAnimationFrame(animate);
}
function startAnim() {
if (!animating) {
animating = true;
animate();
}
}
function hitTest(clientX, clientY) {
const rect = component.getBoundingClientRect();
const relX = (clientX - rect.left) / rect.width;
const relY = (clientY - rect.top) / rect.height;
if (relX >= 0 && relX <= 1 && relY >= 0 && relY <= 1) {
targetX = relX * 1000;
targetY = relY * 240 - 40;
isHovering = true;
startAnim();
} else if (isHovering) {
isHovering = false;
releaseDirection = Math.sign(currentY - MID_Y);
velocityY = 0;
startAnim();
}
}
function releaseAll() {
isHovering = false;
touchActive = false;
if (Math.abs(currentY - MID_Y) > 0.05 || Math.abs(velocityY) > 0.05) {
releaseDirection = Math.sign(currentY - MID_Y);
velocityY = 0;
startAnim();
}
}
let isTouchDevice = false;
document.addEventListener("touchstart", () => { isTouchDevice = true; }, { once: true, passive: true });
document.addEventListener("mousemove", (e) => {
lastClientX = e.clientX;
lastClientY = e.clientY;
hitTest(e.clientX, e.clientY);
});
document.addEventListener("mouseleave", () => { releaseAll(); });
document.addEventListener("touchstart", (e) => {
const touch = e.touches[0];
if (touch) {
touchActive = true;
lastClientX = touch.clientX;
lastClientY = touch.clientY;
hitTest(touch.clientX, touch.clientY);
}
}, { passive: true });
document.addEventListener("touchmove", (e) => {
const touch = e.touches[0];
if (touch) {
lastClientX = touch.clientX;
lastClientY = touch.clientY;
if (touchActive) hitTest(touch.clientX, touch.clientY);
}
}, { passive: true });
document.addEventListener("touchend", () => { releaseAll(); }, { passive: true });
document.addEventListener("touchcancel", () => { releaseAll(); }, { passive: true });
let scrollTicking = false;
window.addEventListener("scroll", () => {
if (!isTouchDevice && (lastClientX || lastClientY)) {
if (!scrollTicking) {
scrollTicking = true;
requestAnimationFrame(() => {
hitTest(lastClientX, lastClientY);
scrollTicking = false;
});
}
}
}, { passive: true });
});
});
</script>









