<div>
  <p>Input your absolute SVG path to be used with <code>clipPathUnits="userSpaceOnUse"</code></p>
  <textarea id="input-path">M15 0v0c8.284 0 15 5.435 15 12.139s-6.716 12.139-15 12.139c-0.796 0-1.576-0.051-2.339-0.147-3.222 3.209-6.943 3.785-10.661 3.869v-0.785c2.008-0.98 3.625-2.765 3.625-4.804 0-0.285-0.022-0.564-0.063-0.837-3.392-2.225-5.562-5.625-5.562-9.434 0-6.704 6.716-12.139 15-12.139zM31.125 27.209c0 1.748 1.135 3.278 2.875 4.118v0.673c-3.223-0.072-6.181-0.566-8.973-3.316-0.661 0.083-1.337 0.126-2.027 0.126-2.983 0-5.732-0.805-7.925-2.157 4.521-0.016 8.789-1.464 12.026-4.084 1.631-1.32 2.919-2.87 3.825-4.605 0.961-1.84 1.449-3.799 1.449-5.825 0-0.326-0.014-0.651-0.039-0.974 2.268 1.873 3.664 4.426 3.664 7.24 0 3.265-1.88 6.179-4.82 8.086-0.036 0.234-0.055 0.474-0.055 0.718z</textarea>
</div>

<div class="m-t-4">
  <p>SVG path to be used with <code>clipPathUnits="objectBoundingBox"</code></p>
  <textarea id="output-path"></textarea>
</div>

<div class="m-t-2">
  <h3>Preview</h3>
  <div class="grid">
    <div class="">
      <h4>Original SVG</h4>
      <div id="svg-original"></div>
    </div>
    <div class="">
      <h4>Image with original SVG clip-path</h4>
      <div id="svg-with-crop"></div>
    </div>
    <div class="">
      <h4>Image with relative SVG clip-path</h4>
      <div id="svg-relative"></div>
    </div>
  </div>
</div>


<div class="m-t-2">
  <h3>Final result</h3>
  <div class="grid-2">
    <div>
      <h4>CSS</h4>
      <textarea id="output-css"></textarea>
      <h4 class="m-t-2">HTML</h4>
      <textarea id="output-html"></textarea>
    </div>
    <div class="grid-right">
      <div id="output-full"></div>
    </div>
  </div>
</div>
@import url(https://unpkg.com/normalize.css) layer(normalize);
@import url(https://unpkg.com/open-props/normalize.min.css) layer(open-props);
@import url(https://unpkg.com/open-props/open-props.min.css) layer(open-props);
@import url(https://rodydavis.github.io/material-design-lite/css/mdl.min.css) layer(mdl);
@import url(https://rodydavis.github.io/material-design-lite/css/themes/baseline.css) layer(mdl);
@import url(https://code.getmdl.io/1.3.0/material.indigo-pink.min.css) layer(mdl);

@layer base {

  *,
  *:after,
  *:before {
    box-sizing: border-box;
  }

  body {
    padding: var(--size-3) var(--size-4);
    min-height: 100vh;
    background: var(--gradient-7);
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
  }

  h1 {
    text-align: center;
    min-width: 100%;
    color: rgb(var(--md-ref-palette-primary98-rgb));
  }

  code {
    background-color: rgb(var(--md-ref-palette-secondary30-rgb));
  }

  button, p, h1, h2, h3, h4 {
    font-family: inherit;
  }
  
  p, h1, h2, h3, h4 {
    color: rgb(var(--md-ref-palette-primary98-rgb));
    font-weight: bold;
    letter-spacing: -0.2px;
  }
  
  p {
    margin-bottom: 0
  }
  
  h3 {
    margin-bottom: 20px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.2);
  }
  
  h4 {
    margin-top: 0;
    margin-bottom: 0;
    padding-top: 0;
  }
  
  textarea {
    width: 100%;
    max-width: 100%;
    min-height: 120px;
    margin-top: 10px;
  }
  
  a {
    color: rgb(var(--md-ref-palette-primary98-rgb));
  }

}

@layer demo {

  .mdl-grid {
    padding: 0 !important;
    margin-left: -8px;
    margin-right: -8px;
  }
  
  .m-t-2 {
    margin-top: var(--size-2);
  }
  .m-t-4 {
    margin-top: var(--size-4);
  }
  
  .grid {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
  }
  .grid-2 {
    display: grid;
    grid-template-columns: 2fr 1fr;    
  }
  .grid-right {
    padding-left: 20px;
  }
  
  #svg-original svg path {
    fill: white;
    stroke: none;
  }
  
  #svg-with-crop svg, #svg-relative svg {
    position: absolute;
    width: 0;
    height: 0;
    overflow: hidden;
  }
  #svg-with-crop .preview {
    background: url('https://unsplash.com/photos/O6N9RV2rzX8/download?ixid=MnwxMjA3fDB8MXxzZWFyY2h8M3x8YXV0dW1ufGVufDB8fHx8MTY2NDk5NDExMQ&w=640');
    background-repeat: no-repeat;
    background-size: cover;
    clip-path: url('#svg-clippath-before');
    height: 0;
    padding-bottom: 100%;
  }
  
  #svg-relative, #svg-with-crop {
    max-width: 200px;
    max-height: 200px;
  }
  #svg-relative .preview {
    background: url('https://unsplash.com/photos/O6N9RV2rzX8/download?ixid=MnwxMjA3fDB8MXxzZWFyY2h8M3x8YXV0dW1ufGVufDB8fHx8MTY2NDk5NDExMQ&w=640');
    background-repeat: no-repeat;
    background-size: cover;
    clip-path: url('#svg-clippath-after');
    height: 0;
    padding-bottom: 100%;
  }
  
  #output-full svg {
    position: absolute;
    width: 0;
    height: 0;
  }
  
  .clipped {
    width: 100%;
    height: 0;
    padding-bottom: 100%;
    background: url(https://unsplash.com/photos/O6N9RV2rzX8/download?ixid=MnwxMjA3fDB8MXxzZWFyY2h8M3x8YXV0dW1ufGVufDB8fHx8MTY2NDk5NDExMQ&w=640);
    background-size: cover;
    -webkit-clip-path: url(#my-clip-path);
    clip-path: url(#my-clip-path);
  }
}
const updateOriginalSvg = function() {
  const svg_original = $('<svg><path></path></svg>');
  svg_original.find('path').attr('d', $('#input-path').val());
  $('#svg-original').empty().append(svg_original);
}

const updateCropSvg = function() {
  const svg_crop = $('<svg><clipPath id="svg-clippath-before" clipPathUnits="userSpaceOnUse"><path></path></clipPath></svg>');
  svg_crop.find('path').attr('d', $('#input-path').val());
  $('#svg-with-crop').empty().append(svg_crop);
  $('#svg-with-crop').append('<div class="preview"></div>');
}

const updateRelativeSvg = function() {
  const svg_crop = $('<svg><clipPath id="svg-clippath-after" clipPathUnits="objectBoundingBox"><path></path></clipPath></svg>');
  svg_crop.find('path').attr('d', $('#output-path').val());
  $('#svg-relative').empty().append(svg_crop);
  $('#svg-relative').append('<div class="preview"></div>');
}

const keysListByCommand = {
  'm': ['x', 'y'],
  'l': ['x', 'y'],

  'h': ['x'],
  'v': ['y'],

  'c': ['x', 'y', 'x1', 'y1', 'x2', 'y2'],

  's': ['x', 'y', 'x1', 'y1'],
  'q': ['x', 'y', 'x1', 'y1'],
  't': ['x', 'y', 'x1', 'y1'],

  'a': ['rx', 'ry', 'x-axis-rotation', 'large-arc-flag', 'sweep-flag', 'x', 'y']
};

const parseCoordsItem = function(item) {
  const commandSrc = item.substring(0,1);
  const command = commandSrc.toLowerCase();
  const isCommandUpperCase = command !== commandSrc;
  const keysList = keysListByCommand[command];
  let coordsList = item
    .substring(1)
    .replace(/,$/,'')
    .split(',')
    .map(item => +item);

  return {
    commandSrc,
    command,
    isCommandUpperCase,
    keysList,
    coordsList
  }
}

// ---------------------------------------------

const normalizePathCoords = function(coords) {
  let result = coords
    .replace(/([a-z]) /gi, '$1')
    .replace(/([a-z])/gi, ' $1')
    .trim()

    .replace(/(\d{1,})(-)/gi, '$1 $2')
    .replace(/\s00/gi, ' 0 0 ')
    .replace(/z/gi, ' ')

    .replace(/,\s{1,}/gi, ',')
    .replace(/\s{1,},/gi, ',')
    .replace(/\s{1,}/gi, ',');

  // .345.279
  while(result.match(/\.\d{1,}\.\d{1,}/gi)) {
    result = result.replace(/(\.\d{1,})(\.\d{1,})/gi, '$1,$2');
  }

  return result;
}

// ---------------------------------------------

const round = function(num) {
  return Math.round(num * 1000) / 1000;
}
const addOmittedCommands = function (srcCoordsList) {
  srcCoordsList = srcCoordsList.slice();
  const coordsFixed = [];
  const max = 5000;
  let counter = 0;
  const handledCommands = {
    'a': true,
    't': true,
    'c': true,
    's': true,
    'q': true,
  }

  while(srcCoordsList.length > 0 && counter < max) {
    let value = srcCoordsList.shift();
    let {commandSrc, command, coordsList, keysList} = parseCoordsItem(value);

    if(keysList) {
      let coords;

      if(handledCommands[command] && coordsList.length > keysList.length) {
        // Fix problem with long commands like A
        const cuttedTail = coordsList.splice(keysList.length);
        coords = coordsList.join(',');

        if(cuttedTail.length % keysList.length === 0) {
          // Move part of command to the next item
          cuttedTail[0] = `${commandSrc}${cuttedTail[0]}`;
          srcCoordsList.unshift(cuttedTail.join(','));
        }
        else {
          console.log('\nCommand is broken, check params:', coordsList);
        }
      }
      else {
        coords = coordsList.join(',');
      }

      value = `${commandSrc}${coords}`;
    }
    else {
      console.log('Unrecognized command: ', command);
    }

    coordsFixed.push(value);
    counter++;
  }

  return coordsFixed;
}
const transformValuesByKeys = function (keysList, coordsList, itemCommand) {
  const valuesList = coordsList;
  const path = $('#svg-original path')[0];
  const pathSizes = path.getBBox();
  const transformedValuesList = valuesList.map((item, index) => {
    if(!keysList[index] && itemCommand !== 'a') {
      // L lets use more than two coords
      if(index % 2 == 0) {
        return getTransformedByKey(pathSizes, 'width', item);
      }
      else {
        return getTransformedByKey(pathSizes, 'height', item);
      }
    }

    if(keysList[index].includes('rotation')|| keysList[index].includes('flag')) {
      return item;
    }

    if(keysList[index].includes('x')) {
      return getTransformedByKey(pathSizes, 'width', item);
    }

    if(keysList[index].includes('y')) {
      return getTransformedByKey(pathSizes, 'height', item);
    }

    return item;
  });

  return transformedValuesList;
}
const getTransformedByKey = function (pathSizes, key = 'height', value) {
  let result = 0;
  if(key === 'width') {
    result = round(value / pathSizes.width);
  }
  else {
    result = round(value / pathSizes.height);
  }

  // Reduce of maximum coordinates to 1
  if(result > 1) {
    result = Math.floor(result);
  }

  return result;
}
const transformCoords = function (srcCoordsList) {
  srcCoordsList = srcCoordsList.slice();
  const coordsTransformed = [];
  const max = 5000;
  let counter = 0;

  while(srcCoordsList.length > 0 && counter < max) {
    let value = srcCoordsList.shift();
    let {commandSrc, command, coordsList, keysList} = parseCoordsItem(value);

    if(keysList) {
      const transformedValsList = transformValuesByKeys(keysList, coordsList, command)
      value = `${commandSrc}${transformedValsList.join(',')}`;
    }
    else {
      console.log('Unrecognized command: ', command);
    }

    coordsTransformed.push(value);
    counter++;
  }

  return coordsTransformed;
}
const transformPath = function(coords) {
  // Normalize coordinates list formating
  const coordsNormalized = normalizePathCoords(coords);
  // Collect all cordinates set from char to next char (next char not includes)
  const coordsListSrc = [...coordsNormalized.matchAll(/[a-z][^(a-z)]{1,}/gi)]
    .map(item => item[0]);
  let coordsList = coordsListSrc.slice();
  // Add omitted commands for more correct parsing
  coordsList = addOmittedCommands(coordsListSrc.slice());

  // if(this.isRemoveOffset) {
  //   // Remove path offset
  //   coordsList = this.removeOffset(coordsList);
  // }

  // Convert coordinates to relative
  const coordsTransformed = transformCoords(coordsList);

  let resultPath = coordsTransformed.join(' ');
  
  if (resultPath.includes('Infinity')) {
    return null;
  }

  return resultPath;
}

const run = function() {
  updateOriginalSvg();
  updateCropSvg();
  const coords = transformPath($('#input-path').val());
  if (coords === null) {
    $('#output-path').val('Invalid SVG path supplied.');
    $('#output-css').val('');
    $('#output-html').val('');
  } else {
    $('#output-path').val(coords);
    $('#output-css').val(`.svg {
  position: absolute;
  width: 0;
  height: 0;
}
.clipped {
  width: 100%;
  height: 0;
  padding-bottom: 100%;
  background: url(https://unsplash.com/photos/O6N9RV2rzX8/download?ixid=MnwxMjA3fDB8MXxzZWFyY2h8M3x8YXV0dW1ufGVufDB8fHx8MTY2NDk5NDExMQ&w=640);
  background-size: cover;
  -webkit-clip-path: url(#my-clip-path);
  clip-path: url(#my-clip-path);
}`);
    $('#output-html').val(`<svg class="svg">
  <clipPath id="my-clip-path" clipPathUnits="objectBoundingBox"><path d="${coords}"></path></clipPath>
</svg>

<div class="clipped"></div>`);
    $('#output-full').html($('#output-html').val());
  }
  updateRelativeSvg();
}

$(function() {
  run();
  $('#input-path').on('change', function() {
    run();
  });
});
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/cash/8.1.1/cash.min.js