<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
This Pen doesn't use any external CSS resources.