Search code examples
cssalgorithmsvgpolygonclip-path

Algorithm to convert SVG path to CSS clip path polygon


I'm experimenting with using SVGs as clip-path.

I know you can pass a url to an SVG path, but I've had issues with it, especially when it comes to consider absolute vs. relative path (to my knowledge, Figma and Illustrator export absolute paths).

I found this really cool which can convert SVGs to clip-path as polygon definitions. It even handles curves really well.

https://www.plantcss.com/css-clip-path-converter

I'd love to learn more about how it works, but can't find my on the web about how to do it.

I did find this: https://gist.github.com/lostfictions/65570a178fb095829637da2b4742fa11#file-svg-to-css-clip-path-polygon-js-L17

You'll notice it doesn't work for paths with letter commands though (M, C, A, etc.).

Anyways, I've been exploring my own algorithm for a while now, but haven't been able to make much progress.

Can anyone help me try to figure this out?

Ideal behavior is:

  1. Take a path from an SVG like this:

<svg  viewBox="-238.3 0.7 1138.1 1138.9">
<path d="M33.9131 258.815L750.625 719.529H705.429L733.14 747.257C791.424 805.721 791.424 900.16 733.14 958.481C703.998 987.641 665.898 1002.29 627.656 1002.29C589.413 1002.29 551.17 987.641 522.028 958.481L479.961 916.387V990.22C479.961 1072.73 413.149 1139.56 330.713 1139.56C248.276 1139.56 181.464 1072.71 181.464 990.22V916.531L139.54 958.481C110.398 987.641 72.1558 1002.29 33.9131 1002.29C-4.32946 1002.29 -42.4289 987.641 -71.571 958.481C-129.855 900.16 -129.855 805.721 -71.571 747.257L-43.8604 719.529H-89.0563C-171.513 719.529 -238.305 652.532 -238.305 570.187C-238.305 487.842 -171.493 420.845 -89.0563 420.845H-15.2705L-71.571 364.509C-129.855 306.188 -129.855 211.606 -71.571 153.285C-13.2868 94.9645 81.2359 94.9645 139.52 153.285L181.444 195.256V150.031C181.444 67.523 248.399 0.689453 330.692 0.689453C412.986 0.689453 479.941 67.5434 479.941 150.031V195.399L522.008 153.306C580.292 94.9849 674.815 94.9849 733.099 153.306C791.383 211.626 791.383 306.208 733.099 364.529L676.798 420.865H750.584C833.041 420.865 899.833 487.719 899.833 570.207C899.833 652.695 833.021 719.549 750.584 719.549L33.9131 258.815Z" />
</svg>

and have it output a polygon like this:

.clipped {
  clip-path: polygon( 3.769% 22.712%, 83.418% 63.141%, 78.396% 63.141%, 81.475% 65.574%, 81.475% 65.574%, 83.224% 67.202%, 84.584% 68.981%, 85.556% 70.873%, 86.139% 72.841%, 86.333% 74.847%, 86.139% 76.852%, 85.556% 78.819%, 84.584% 80.709%, 83.224% 82.486%, 81.475% 84.11%, 81.475% 84.11%, 80.475% 84.839%, 79.42% 85.492%, 78.318% 86.069%, 77.174% 86.568%, 75.993% 86.992%, 74.782% 87.338%, 73.547% 87.607%, 72.293% 87.8%, 71.026% 87.916%, 69.752% 87.954%, 69.752% 87.954%, 68.479% 87.916%, 67.211% 87.8%, 65.955% 87.607%, 64.717% 87.338%, 63.504% 86.992%, 62.321% 86.568%, 61.174% 86.069%, 60.07% 85.492%, 59.015% 84.839%, 58.014% 84.11%, 53.339% 80.416%, 53.339% 86.895%, 53.339% 86.895%, 53.122% 89.021%, 52.493% 91.038%, 51.488% 92.919%, 50.139% 94.636%, 48.481% 96.163%, 46.549% 97.472%, 44.375% 98.538%, 41.996% 99.332%, 39.443% 99.829%, 36.753% 100%, 36.753% 100%, 34.062% 99.829%, 31.51% 99.332%, 29.13% 98.537%, 26.957% 97.472%, 25.024% 96.162%, 23.366% 94.635%, 22.018% 92.918%, 21.012% 91.038%, 20.383% 89.021%, 20.166% 86.895%, 20.166% 80.428%, 15.507% 84.11%, 15.507% 84.11%, 14.506% 84.839%, 13.451% 85.492%, 12.347% 86.069%, 11.2% 86.568%, 10.017% 86.992%, 8.804% 87.338%, 7.566% 87.607%, 6.311% 87.8%, 5.043% 87.916%, 3.769% 87.954%, 3.769% 87.954%, 2.495% 87.916%, 1.229% 87.8%, -0.025% 87.607%, -1.261% 87.338%, -2.472% 86.992%, -3.652% 86.568%, -4.797% 86.069%, -5.899% 85.492%, -6.953% 84.839%, -7.954% 84.11%, -7.954% 84.11%, -9.703% 82.486%, -11.063% 80.709%, -12.034% 78.819%, -12.617% 76.852%, -12.812% 74.847%, -12.617% 72.841%, -12.034% 70.873%, -11.063% 68.981%, -9.703% 67.202%, -7.954% 65.574%, -4.874% 63.141%, -9.897% 63.141%, -9.897% 63.141%, -12.588% 62.969%, -15.141% 62.472%, -17.521% 61.676%, -19.694% 60.609%, -21.626% 59.298%, -23.284% 57.77%, -24.633% 56.053%, -25.638% 54.173%, -26.266% 52.159%, -26.483% 50.036%, -26.483% 50.036%, -26.266% 47.913%, -25.638% 45.898%, -24.632% 44.018%, -23.283% 42.301%, -21.626% 40.773%, -19.693% 39.462%, -17.52% 38.395%, -15.14% 37.6%, -12.588% 37.102%, -9.897% 36.93%, -1.697% 36.93%, -7.954% 31.987%, -7.954% 31.987%, -9.703% 30.362%, -11.063% 28.585%, -12.034% 26.693%, -12.617% 24.725%, -12.812% 22.719%, -12.617% 20.713%, -12.034% 18.745%, -11.063% 16.853%, -9.703% 15.076%, -7.954% 13.451%, -7.954% 13.451%, -5.898% 12.069%, -3.649% 10.995%, -1.254% 10.227%, 1.236% 9.766%, 3.776% 9.613%, 6.315% 9.766%, 8.806% 10.227%, 11.2% 10.995%, 13.449% 12.069%, 15.505% 13.451%, 20.164% 17.134%, 20.164% 13.166%, 20.164% 13.166%, 20.382% 11.039%, 21.011% 9.022%, 22.018% 7.142%, 23.369% 5.425%, 25.028% 3.898%, 26.961% 2.588%, 29.135% 1.523%, 31.514% 0.728%, 34.064% 0.232%, 36.75% 0.061%, 36.75% 0.061%, 39.437% 0.232%, 41.987% 0.729%, 44.366% 1.523%, 46.54% 2.589%, 48.473% 3.899%, 50.132% 5.426%, 51.483% 7.143%, 52.49% 9.023%, 53.119% 11.04%, 53.337% 13.166%, 53.337% 17.147%, 58.012% 13.453%, 58.012% 13.453%, 60.068% 12.071%, 62.317% 10.997%, 64.711% 10.229%, 67.202% 9.768%, 69.741% 9.615%, 72.28% 9.768%, 74.771% 10.229%, 77.165% 10.997%, 79.415% 12.071%, 81.471% 13.453%, 81.471% 13.453%, 83.219% 15.078%, 84.58% 16.855%, 85.551% 18.746%, 86.134% 20.715%, 86.328% 22.721%, 86.134% 24.727%, 85.551% 26.695%, 84.58% 28.587%, 83.219% 30.364%, 81.471% 31.989%, 75.214% 36.932%, 83.414% 36.932%, 83.414% 36.932%, 86.105% 37.104%, 88.657% 37.6%, 91.037% 38.395%, 93.211% 39.461%, 95.143% 40.77%, 96.801% 42.297%, 98.149% 44.015%, 99.155% 45.895%, 99.783% 47.912%, 100% 50.037%, 100% 50.037%, 99.783% 52.163%, 99.155% 54.18%, 98.149% 56.06%, 96.8% 57.778%, 95.142% 59.305%, 93.21% 60.614%, 91.037% 61.68%, 88.657% 62.475%, 86.104% 62.971%, 83.414% 63.143%, 3.769% 22.712%)
}
<div class="clipped" style="background-color: black; width:300px;height:350px"></div>

Thank you!


Solution

  • You can use getPointAtLength() to find the coordinates for points on the path and turn it into at percentage based on the width and the height of the path.

    (I moves the original path so that it doesn't have any negative points).

    let path = document.querySelector('path');
    
    let pathLength = Math.floor(path.getTotalLength());
    let steps = 10;
    let scaled = Math.floor(pathLength / steps);
    let bbox = path.getBBox();
    
    let points = Object.keys([...new Array(scaled)]).map(num => {
      let point = path.getPointAtLength(num * steps);
      let x = (point.x / bbox.width * 100).toFixed(2);
      let y = (point.y / bbox.height * 100).toFixed(2);
      return `${x}% ${y}%`;
    }).join(',');
    
    document.querySelector('style[title="s1"]').innerHTML = `.clipped
    {clip-path: polygon(${points});}`;
    body {
      display: flex;
    }
    <style title="s1"></style>
    
    <svg height="200" viewBox="0 0 1138.1 1138.9">
    <path d="M 272.2181 258.1255 L 988.93 718.8395 H 943.734 L 971.445 746.5675 C 1029.729 805.0315 1029.729 899.4705 971.445 957.7915 C 942.303 986.9515 904.203 1001.6005 865.961 1001.6005 C 827.718 1001.6005 789.475 986.9515 760.333 957.7915 L 718.266 915.6975 V 989.5305 C 718.266 1072.0405 651.454 1138.8705 569.018 1138.8705 C 486.581 1138.8705 419.769 1072.0205 419.769 989.5305 V 915.8415 L 377.845 957.7915 C 348.703 986.9515 310.4608 1001.6005 272.2181 1001.6005 C 233.9755 1001.6005 195.8761 986.9515 166.734 957.7915 C 108.45 899.4705 108.45 805.0315 166.734 746.5675 L 194.4446 718.8395 H 149.2487 C 66.792 718.8395 -0 651.8425 -0 569.4975 C -0 487.1525 66.812 420.1555 149.2487 420.1555 H 223.0345 L 166.734 363.8195 C 108.45 305.4985 108.45 210.9165 166.734 152.5955 C 225.0182 94.275 319.5409 94.275 377.825 152.5955 L 419.749 194.5665 V 149.3415 C 419.749 66.8335 486.704 0 568.997 0 C 651.291 0 718.246 66.8539 718.246 149.3415 V 194.7095 L 760.313 152.6165 C 818.597 94.2954 913.12 94.2954 971.404 152.6165 C 1029.688 210.9365 1029.688 305.5185 971.404 363.8395 L 915.103 420.1755 H 988.889 C 1071.346 420.1755 1138.138 487.0295 1138.138 569.5175 C 1138.138 652.0055 1071.326 718.8595 988.889 718.8595 L 272.2181 258.1255 Z" />
    </svg>
    
    <div class="clipped" style="background-color: orange; width:200px;height:200px"></div>