Update SVG clip path to dynamically reveal parts of an image

I’m attempting to create an effect that allows users to drag an image of glasses over another blurred, grayscale image to reveal the unfiltered version of that image behind the glasses. I have a PNG of the glasses with transparency where the lenses would be. I have an SVG of the same glasses to be used as the clip path. The PNG appears over the SVG clip path (giving the appearance of glasses).

My knowledge of JavaScript is limited. I tried getting help from ChatGPT but I can’t seem to explain the desired effect well enough for a useful response. This is as close as I was able to get.

I think the HTML and CSS are working (see below), but I am having trouble getting the JavaScript function to update the clip path using the SVG coordinates. I honestly don’t know what I’m doing. I’d be grateful for some pointers or help in making it work.

Here is the PNG image:

the PNG image

(The SVG file is the same image but without the lens cutouts.)

The HTML file:

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>A New Lens</title>
    <link rel="stylesheet" href="styles-svg.css">
</head>
<body>
    <div class="image-container">
        <img src="photo.png" class="filtered-image">
        <img src="photo.png" class="unfiltered-image">
        <div class="mask-container">
            <svg width="0" height="0">
                <defs>
                    <clipPath id="svgClipPath">
                        <path id="clipPathPath" d="M1066.69,2338.82C1079.01,2292.93 1134.78,2273.13 1222,2274.97C1298.25,2277.93 1364.37,2286.81 1380.78,2336.21C1389.51,2332.48 1399.29,2328.34 1410.22,2323.68C1453.06,2305.42 1491.5,2269.63 1542.85,2249.12C1631.21,2213.83 1712.43,2194.8 1786,2192C1832.41,2190.23 1923.09,2195.09 1987.5,2204.12C2093.33,2218.96 2175.63,2245.04 2236.67,2271.44C2269.83,2285.79 2308.24,2291.69 2313.66,2313.01C2319.36,2335.47 2325.11,2374.19 2313.66,2404.66C2308.11,2419.43 2288.17,2420.74 2273.87,2437.41C2250.63,2464.5 2242,2508.09 2236.67,2536.67C2225.41,2596.96 2189.26,2693.34 2159.49,2744.86C2085.85,2872.29 2008.79,2951.81 1812.67,2962.67C1717.17,2967.95 1619.32,2944.42 1542.85,2900.01C1505.14,2878.11 1451.05,2807.22 1418.06,2748.86C1367.84,2660.03 1340.95,2557.61 1336.67,2511.33C1328.67,2424.83 1306.26,2444.63 1315.2,2378.7C1316.47,2369.34 1324.17,2362.56 1343.18,2353.09C1331.75,2324.09 1287.59,2310.49 1222,2306.97C1153.69,2308.01 1119.84,2327.29 1105.81,2357.07C1119.33,2364.59 1125.11,2370.7 1126.19,2378.7C1135.13,2444.63 1112.72,2424.83 1104.72,2511.33C1100.44,2557.61 1073.55,2660.03 1023.33,2748.86C990.338,2807.22 936.243,2878.11 898.534,2900.01C822.064,2944.42 724.215,2967.95 628.72,2962.67C432.598,2951.81 355.538,2872.29 281.896,2744.86C252.124,2693.34 215.973,2596.96 204.72,2536.67C199.387,2508.09 190.759,2464.5 167.521,2437.41C153.218,2420.74 133.281,2419.43 127.731,2404.66C116.277,2374.19 122.03,2335.47 127.731,2313.01C133.142,2291.69 171.559,2285.79 204.72,2271.44C265.754,2245.04 348.054,2218.96 453.884,2204.12C518.296,2195.09 608.977,2190.23 655.387,2192C728.958,2194.8 810.177,2213.83 898.534,2249.12C949.883,2269.63 988.328,2305.42 1031.16,2323.68C1044.72,2329.45 1056.5,2334.43 1066.69,2338.82ZM653.72,2224C557.233,2214.7 447.731,2236.13 372.938,2289.09C307.601,2335.36 275.305,2406.45 270.72,2493.67C264.356,2614.72 286.251,2707.45 358.091,2793.3C437.89,2888.67 578.686,2915.33 653.72,2920.67C747.406,2927.33 875.269,2894.61 949.573,2793.3C1019.42,2698.08 1048.72,2558.93 1030.05,2475C1009.16,2381.06 935.174,2295.28 812.027,2255.51C764.732,2240.23 700.216,2228.48 653.72,2224ZM1787.67,2224C1741.17,2228.48 1676.65,2240.23 1629.36,2255.51C1506.21,2295.28 1432.23,2381.06 1411.33,2475C1392.67,2558.93 1421.97,2698.08 1491.81,2793.3C1566.12,2894.61 1693.98,2927.33 1787.67,2920.67C1862.7,2915.33 2003.5,2888.67 2083.3,2793.3C2155.14,2707.45 2177.03,2614.72 2170.67,2493.67C2166.08,2406.45 2133.79,2335.36 2068.45,2289.09C1993.66,2236.13 1884.15,2214.7 1787.67,2224Z"/>
                    </clipPath>
                </defs>
            </svg>
            <img src="glasses.png" alt="the overlay glasses image" class="overlay-mask">
        </div>
    </div>
    <script src="script-svg.js"></script>
</body>
</html>

The CSS file:

    body {
    margin: 0;
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    background-color: #f0f0f0;
}

.image-container {
    position: relative;
    width: 1800px; 
    height: 1200px; 
    display: inline-block;
}

.filtered-image,
.unfiltered-image {
    width: 100%;
    height: 100%;
}

.filtered-image {
    filter: blur(5px) grayscale(100%);
}

.unfiltered-image {
    position: absolute;
    top: 0;
    left: 0;
}

.mask-container {
    position: absolute;
    top: 500px; 
    left: 500px; 
    width: 500px; 
    height: 176px; 
    cursor: pointer;
}

.overlay-mask {
    width: 100%;
    height: 100%;
    pointer-events: none; 
}

And the JavaScript file (which does not work):

const maskContainer = document.querySelector('.mask-container');
const imageContainer = document.querySelector('.image-container');
const unfilteredImage = document.querySelector('.unfiltered-image');
const svgClipPath = document.querySelector('#clipPathPath');

let isDragging = false;
let offsetX, offsetY;

maskContainer.addEventListener('mousedown', (e) => {
    isDragging = true;
    offsetX = e.offsetX;
    offsetY = e.offsetY;
});

document.addEventListener('mouseup', () => {
    isDragging = false;
});

document.addEventListener('mousemove', (e) => {
    if (isDragging) {
        const rect = imageContainer.getBoundingClientRect();
        let x = e.clientX - rect.left - offsetX;
        let y = e.clientY - rect.top - offsetY;

        x = Math.max(0, Math.min(x, rect.width - maskContainer.clientWidth));
        y = Math.max(0, Math.min(y, rect.height - maskContainer.clientHeight));

        maskContainer.style.left = `${x}px`;
        maskContainer.style.top = `${y}px`;

        updateClipPath(x, y);
    }
});

function updateClipPath(x, y) {
    const maskRect = maskContainer.getBoundingClientRect();
    const imageRect = imageContainer.getBoundingClientRect();

    const offsetX = (maskRect.left - imageRect.left) / imageRect.width;
    const offsetY = (maskRect.top - imageRect.top) / imageRect.height;

    const clipPathData = svgClipPath.getAttribute('d');
    const translatedClipPathData = translatePath(clipPathData, offsetX, offsetY);

    unfilteredImage.style.clipPath = `path('${translatedClipPathData}')`;
}

function translatePath(pathData, offsetX, offsetY) {
    const scaleFactorX = 1800; 
    const scaleFactorY = 1200; 

    return pathData.replace(/([d.]+),([d.]+)/g, (match, p1, p2) => {
        const x = parseFloat(p1) + offsetX * scaleFactorX;
        const y = parseFloat(p2) + offsetY * scaleFactorY;
        return `${x},${y}`;
    });
}

I was able to get this to work using a rudimentary set of coordinates (a polygon) roughly outlining the glasses, but it was not suitable for use in a client website. Below is the JavaScript code that actually worked with the bad polygon version of the glasses. If I understood JavaScript better, this might be sufficient for me to correct the SVG version, but I’m a newb. (I did enroll in a Udemy course so if I figure this out, maybe I can post the answer here in a few months.)

const polygon = document.querySelector('.polygon');
const imageContainer = document.querySelector('.image-container');
const unfilteredImage = document.querySelector('.unfiltered-image');

let isDragging = false;
let offsetX, offsetY;

polygon.addEventListener('mousedown', (e) => {
    isDragging = true;
    offsetX = e.offsetX;
    offsetY = e.offsetY;
});

document.addEventListener('mouseup', () => {
    isDragging = false;
});

document.addEventListener('mousemove', (e) => {
    if (isDragging) {
        const rect = imageContainer.getBoundingClientRect();
        let x = e.clientX - rect.left - offsetX;
        let y = e.clientY - rect.top - offsetY;

        // Ensure the polygon stays within the bounds of the image
        x = Math.max(0, Math.min(x, rect.width - polygon.clientWidth));
        y = Math.max(0, Math.min(y, rect.height - polygon.clientHeight));

        polygon.style.left = `${x}px`;
        polygon.style.top = `${y}px`;

        updateClipPath(x, y);
    }
});

function updateClipPath(x, y) {
    const polygonWidth = polygon.clientWidth;
    const polygonHeight = polygon.clientHeight;

    const relativePoints = `
        0 24%, 5% 0, 43% 0%, 50% 17%, 56% 0, 96% 0, 100% 24%, 
        100% 68%, 91% 100%, 68% 100%, 56% 80%, 50% 34%, 43% 80%, 
        30% 100%, 9% 100%, 0% 68%`;

    const absolutePoints = relativePoints.split(',').map(point => {
        const [xRel, yRel] = point.trim().split(' ');
        const xAbs = x + (parseFloat(xRel) / 100) * polygonWidth;
        const yAbs = y + (parseFloat(yRel) / 100) * polygonHeight;
        return `${(xAbs / 1800) * 100}% ${(yAbs / 1200) * 100}%`;
    }).join(', ');

    const polygonPath = `polygon(${absolutePoints})`;

    unfilteredImage.style.clipPath = polygonPath;
}