20

Is it possible to create an inset circle clip path so that the clip path would effectively cut a hole through the div in the center opposed to only showing the center?

The div should all be shown apart from a hole cut out in the center to create something like this:

cut out circle

I would like to use clip path or something similar so that I can have stuff (images and content) behind the div and the clip path will be used to reveal this. So the div (blue div from my jsfiddle) will disappear from the center using a transition to show the content behind it.

div {
  background: blue;
  width: 200px;
  height: 200px;
  -webkit-clip-path: circle(50px at center);
}
<div></div>

https://jsfiddle.net/pm4yvbxn/

4
  • 2
    could you attach an image to show exactly what you want?
    – Shayan
    Commented May 3, 2016 at 9:38
  • 1
    What exacly is your aim? Is there a reason you want to use the clip-path property?
    – web-tiki
    Commented May 3, 2016 at 9:53
  • 1
    @web-tiki I would like to use clip path or something similar so that I can have stuff (images and content) behind the div and the clip path will be used to reveal this. So the div (blue div from my jsfiddle) will disappear from the center using a transition to show the content behind it.
    – Rafty
    Commented May 3, 2016 at 9:57
  • 1
    Hi - were you able to figure out a way to do this using clip-path?
    – AshD
    Commented Sep 15, 2021 at 23:49

7 Answers 7

17

I don't think you can achieve this with clip-path but you can certainly cut a hole in a div using the radial-gradient background images. This has much better browser support than clip-path too.

Note: This approach (and box-shadow ) will work only when the element that is covering the content below has a colored fill. If instead of sandybrown color, there needs to be another image on top then these approaches will not work because they don't actually cut a hole, they just mimic that effect.

.div-with-hole {
  height: 100vh;
  background: radial-gradient(circle at center, transparent 25%, sandybrown 25.5%);
  background-size: 100% 100%;
  background-position: 50% 50%;
  transition: all 2s ease;
}
.div-with-hole:hover {
  background-size: 400% 400%; /* should be 100% * (100 / transparent % of radial gradient */
}
body {
  background: url(http://lorempixel.com/800/800/nature/1);
  min-height: 100vh;
  margin: 0;
  padding: 0;
}
<div class='div-with-hole'></div>

1
  • 1
    there is also a mix-blend-mode approach (yep, not for IE) :)
    – G-Cyrillus
    Commented May 3, 2016 at 14:10
12

You can create hole in clip-path with this approach:

let precision = 64;
let radius = 25;
let c = [...Array(precision)].map((_, i) => {
  let a = -i/(precision-1)*Math.PI*2;
  let x = Math.cos(a)*radius + 50;
  let y = Math.sin(a)*radius + 50;
  return `${x}% ${y}%`
})

document.querySelector('div').style.clipPath = 
 `polygon(100% 50%, 100% 100%, 0 100%, 0 0, 100% 0, 100% 50%, ${c.join(',')})`;
div {
  background: blue;
  width: 200px;
  height: 200px;
}
<div></div>

or symply use resulting clip string:

div {
      background: blue;
      width: 200px;
      height: 200px;
    }
<div style="clip-path: polygon(100% 50%, 100% 100%, 0px 100%, 0px 0px, 100% 0px, 100% 50%, 75% 50%, 74.8758% 47.5108%, 74.5043% 45.0463%, 73.8893% 42.6311%, 73.0369% 40.2891%, 71.9555% 38.0437%, 70.656% 35.917%, 69.1511% 33.9303%, 67.4559% 32.1033%, 65.5872% 30.4542%, 63.5637% 28.9994%, 61.4053% 27.7532%, 59.1335% 26.7282%, 56.771% 25.9344%, 54.3412% 25.3798%, 51.8683% 25.0699%, 49.3767% 25.0078%, 46.8914% 25.194%, 44.437% 25.6268%, 42.0378% 26.3018%, 39.7178% 27.2124%, 37.5% 28.3494%, 35.4064% 29.7015%, 33.4579% 31.2555%, 31.6737% 32.9957%, 30.0717% 34.9049%, 28.6677% 36.9641%, 27.4758% 39.1529%, 26.5077% 41.4495%, 25.7731% 43.8311%, 25.2792% 46.2739%, 25.0311% 48.7539%, 25.0311% 51.2461%, 25.2792% 53.7261%, 25.7731% 56.1689%, 26.5077% 58.5505%, 27.4758% 60.8471%, 28.6677% 63.0359%, 30.0717% 65.0951%, 31.6737% 67.0043%, 33.4579% 68.7445%, 35.4064% 70.2985%, 37.5% 71.6506%, 39.7178% 72.7876%, 42.0378% 73.6982%, 44.437% 74.3732%, 46.8914% 74.806%, 49.3767% 74.9922%, 51.8683% 74.9301%, 54.3412% 74.6202%, 56.771% 74.0656%, 59.1335% 73.2718%, 61.4053% 72.2468%, 63.5637% 71.0006%, 65.5872% 69.5458%, 67.4559% 67.8967%, 69.1511% 66.0697%, 70.656% 64.083%, 71.9555% 61.9563%, 73.0369% 59.7109%, 73.8893% 57.3689%, 74.5043% 54.9537%, 74.8758% 52.4892%, 75% 50%);"></div>

1
  • 1
    Thanks a lot! It works flawlessly. Small note for others — keep in mind that 'radius' must be in %, not pixels.
    – emvaized
    Commented Oct 26, 2021 at 3:07
10

mask can do this and it will work with any kind of background:

div {
  background: linear-gradient(blue, red);
  width: 200px;
  height: 200px;
  -webkit-mask: radial-gradient(50px, #0000 98%, #000);
          mask: radial-gradient(50px, #0000 98%, #000);
}
<div></div>

Can also be animated:

div {
  background: linear-gradient(blue,red);
  width: 200px;
  height: 200px;
  -webkit-mask:
    radial-gradient(farthest-side,#000 98%,#0000) center/50px 50px no-repeat,
    linear-gradient(#000 0 0);
  -webkit-mask-composite:destination-out;
   
  mask:
    radial-gradient(farthest-side,#000 98%,#0000) center/50px 50px no-repeat,
    linear-gradient(#000 0 0);
  mask-composite:exclude;
  transition:0.5s;
}
div:hover {
  -webkit-mask-size:290px 290px,auto;
          mask-size:290px 290px,auto;
}
<div></div>

1
  • Mask is much better for animations ! Thanks mate ;) Commented Mar 17, 2022 at 9:22
6

You could also do this with box-shadow on :after pseudo-element

div {
  position: relative;
  width: 300px;
  height: 200px;
  overflow: hidden;
  background: url('http://planetcompas.com/live/wp-content/uploads/2013/04/2015-01-Beautiful-Planet-And-Space-4-Cool-Wallpapers-HD.jpg');
  background-size: cover;
  background-position: center;
}
div:after {
  width: 50px;
  height: 50px;
  content: '';
  border-radius: 50%;
  background: transparent;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  box-shadow: 0px 0px 0px 300px lightblue;
  transition: all 0.3s linear;
}
div:hover:after {
  opacity: 0;
}
<div></div>

1

SVG mask solution

A circle is used as a mask, and its radius is animated.

The animation will start after clicking on the image

<svg id="svg1" version="1.1" xmlns="http://www.w3.org/2000/svg" 
    xmlns:xlink="http://www.w3.org/1999/xlink"  width="300" height="300" viewBox="0 0 600 600" >  
     <defs>
         <!-- Multicolor radial gradient -->
     <radialGradient id="grad" x1="0" y1="0" x2="100%" y2="0">

        <stop offset="10%" stop-color="#48afc1" />
        <stop offset="10%" stop-color="#b4c63b" />
       <stop offset="20%" stop-color="#ef5b2b" />
       <stop offset="20%" stop-color="#503969" />
        <stop offset="30%" stop-color="#ab6294" />
        <stop offset="30%" stop-color="#1cb98f" />
        <stop offset="40%" stop-color="#48afc1" />
        <stop offset="40%" stop-color="#b4c63b" />
        <stop offset="50%" stop-color="#ef5b2b" />
        <stop offset="50%" stop-color="#503969" />  
        <stop offset="60%" stop-color="#ab6294" />
        <stop offset="60%" stop-color="#1cb98f" />
       <stop offset="70%" stop-color="#48afc1" />
        <stop offset="70%" stop-color="#b4c63b" />
        <stop offset="80%" stop-color="#ef5b2b" />
        <stop offset="80%" stop-color="#503969" />
        <stop offset="90%" stop-color="#ab6294" />
        <stop offset="90%" stop-color="#1cb98f" />
        <stop offset="100%" stop-color="#48afc1" />
     </radialGradient>
         <!-- Mask -->
     <mask id="msk1" > 
            <rect  width="100%" height="100%"  fill="black" /> 
             
       <circle cx="300" cy="300"  r="0" fill="white" >
            <!-- Animation of a mask cutting through the image of a dragon and showing a radial gradient  -->
         <animate attributeName="r" begin="svg1.click" dur="8s" values="0;300;300;0;0" fill="freeze" repeatCount="3" />
        </circle>
     </mask>
     </defs> 
               <!-- Radial gradient background image  -->
      <circle  cx="300" cy="300"  r="300"  fill="url(#grad)"  />  
                 
          <image xlink:href="https://i.sstatic.net/6kywq.png" x="-140" y="-60" mask="url(#msk1)" width="800" height="780" />  

</svg>

0

Based on Stranger in the Q's answer I created a dynamic solution for this problem.

My method works with rem, em, px, and percent as well also uses attributes on the element.

function parseUnitsToPercent(str, divider){
  var match = str.match(/^calc\((.+)\);?$/);
  if(match){
    var val = [...match[1].matchAll(/((?:\d(?:\.\d)?)+(?:px|rem|em|%)?)(?:\s([\+|\-|\*|\/])\s)?/g)].map(e=>e.slice(1)).flat().filter(e=>!!e);
    val = val.map(e=>{
      if(['+','-','*','/'].includes(e)){
        return e;
      }
      return convertUnitsToPercent(e, divider);
    });
    try {
      str = eval(val.join(' '));
    } catch(e){
      str = 0;
    }
  }
  return convertUnitsToPercent(str, divider); 
}
function convertUnitsToPercent(str, divider){
  if(str.toString().endsWith('rem')) {
    str = parseFloat(str) * parseFloat(getComputedStyle(document.documentElement).fontSize) + 'px';
  }
  if(str.toString().endsWith('em')) {
    str = parseFloat(str) * parseFloat(getComputedStyle(elem).fontSize) + 'px';
  }
  if(str.toString().endsWith('px')) {
    str = parseFloat(str)/divider*100;
  }
  if(str.toString().endsWith('%')) {
    str = parseFloat(str);
  }
  return str;
}
document.querySelectorAll('[clip-x][clip-y][clip-size]').forEach(elem=>{
  if(elem.getAttribute('width')) elem.style.width = elem.getAttribute('width') + 'px';
  if(elem.getAttribute('height')) elem.style.height = elem.getAttribute('height') + 'px';
  var x = elem.getAttribute('clip-x');
  var y = elem.getAttribute('clip-y');
  var size = elem.getAttribute('clip-size');
  var elem_size = elem.getBoundingClientRect();
  x = parseUnitsToPercent(x, elem_size.width);
  y = parseUnitsToPercent(y, elem_size.height);
  var radius_x = parseUnitsToPercent(size, elem_size.width);
  var radius_y = parseUnitsToPercent(size, elem_size.height);
  var precision = parseFloat(elem.getAttribute('clip-precision') || 64);
  var c = [...Array(precision)].map((_, i) => {
    var a = -i/(precision-1)*Math.PI*2;
    var _x = Math.cos(a)*radius_x + x;
    var _y = Math.sin(a)*radius_y + y;
    return `${_x}% ${_y}%`
  });
  elem.style.clipPath = `polygon(100% 50%, 100% 100%, 0 100%, 0 0, 100% 0, 100% 50%, ${c.join(',')})`;
});
div {
  background: blue;
}
<div clip-x="50%" clip-y="calc(50% + 20px)" clip-size="50px" clip-precision="64" width="400" height="200"></div>

I know this question is really old, but I found it really useful so I modified it for myself. I decided to share with you my result.

My code supports calc() as well (Please note: It uses eval() which makes it insecure for user-input codes!)

-2

You can do it by adding an elements in <div>.

I have used <span>. Here is the code

HTML

<div>
  <span></span>
</div>

CSS

div{
  background: blue;
  width: 200px;
  height: 200px;
  -webkit-clip-path: circle(50px at center);
  position:relative;
}
div span{
  position:absolute;
  display:block;
  width:30px;
  height:30px;
  border-radius:100%;
  background:#fff;
  top:50%;
  left:50%;
  transform:translate(-50%,-50%)
}

example : https://jsfiddle.net/pm4yvbxn/2/

You can also use border-radius in div for rounded borders.

2
  • Sorry I might not of been entirely clear, ive updated the question with an image of what I am looking for. Its to make the clip path punch a hole in the center of the div instead of only showing the center of the div.
    – Rafty
    Commented May 3, 2016 at 9:48
  • Thats not what I mean. Im wanting it so that instead of the clip path only showing the center circle of the div it should clip a circle out of the center of the div so that everything but the center circle is shown.
    – Rafty
    Commented May 3, 2016 at 9:53

Not the answer you're looking for? Browse other questions tagged or ask your own question.