Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CSS triangular cutout with border and transparent gap

I need to draw the following pattern with CSS as a separator between sections of my page:

separator

Using the skewX() technique from this answer, I was able to mimic the triangular cutout accurately (two pseudo-elements are appended to the top of the lower section, one skewed left and one skewed right, so that the background of the upper section shows through):

enter image description here

But I can't figure out how to then add the border, as shown in the first image.

The problem is that the gap between the border and the lower section has to be transparent, because the background of the upper section can be a gradient, an image, etc. Therefore, I can't simply use an image for the triangular cutout, because I can't know what content will be behind it.

Is there any possible way to do this with CSS?

like image 905
daGUY Avatar asked Dec 07 '22 22:12

daGUY


1 Answers

It is possible to do this with CSS but the only approach that I could find is a very complex one using a fair amount of gradients to mimic the borders. This couldn't be done with normal borders because that resulted in either the borders extending beyond their meeting point or the inner area never meeting. It couldn't be done with box shadow's also because the gap between the border and the filled area need to be transparent. (Not saying it cannot be done with those but just that I couldn't get it to work, maybe there is a way with those too).

The below is how I've managed to achieve the border:

  • Four linear gradients were used to create the borders that are required.
  • The 1st and 2nd are actually solid colors (that is, there is no change from 1 color to another) but I had used gradients because that allows us to control the background-size. These two produce a solid horizontal line where one is positioned on the left and another is positioned on the right a bit above the transparent cut that was produced using the pseudo-elements.
  • The 3rd and 4th produce the angled lines with the same thickness as required for the border.
  • The size and position of all the four gradients are set such that they produce the border effect.

The output - as you can see by hovering the element - is responsive too.

div {
  position: relative;
  height: 200px;
  width: 300px;
  background: linear-gradient(#DDD, #DDD), /* horizontal border line on left */
              linear-gradient(#DDD, #DDD),  /* horizontal border line on right */
              linear-gradient(to top right, transparent calc(50% - 2px), #DDD calc(50% - 2px), #DDD calc(50% + 2px), transparent calc(50% + 2px)), /* angled border on left of center */
              linear-gradient(to top left, transparent calc(50% - 2px), #DDD calc(50% - 2px), #DDD calc(50% + 2px), transparent calc(50% + 2px)), /* angled border on right of center */
              radial-gradient(circle, #3F9CBA 0%, #153346 100%) /* actual background */;
  background-size: calc(50% - 38px) 4px, 
                   calc(50% - 38px) 4px, 
                   40px 40px, /* size of one half of the triangle */
                   40px 40px, /* size of one half of the triangle */
                   auto auto /* size of actual bg */;
  background-position: 0% calc(100% - 44px), 
                       100% calc(100% - 44px), 
                       calc(50% - 18px) calc(100% - 8px), 
                       calc(50% + 18px) calc(100% - 8px), 
                       0px 0px;
  background-repeat: no-repeat;
  overflow: hidden;
  border-bottom: 20px solid #DDD;
}
div:before,
div:after {
  position: absolute;
  content: '';
  height: 40px;
  width: 50%;
  bottom: 0;
  background: #DDD;
  backface-visibility: hidden;
}
div:before {
  left: 0;
  transform-origin: left bottom;
  transform: skewX(45deg);
}
div:after {
  right: 0;
  transform-origin: right bottom;
  transform: skewX(-45deg);
}

/* Just for demo */

div {
  transition: all 1s;
}
div:hover {
  height: 250px;
  width: 550px;
}
<div></div>

The following is how the background properties need to be set based on the required border color and width (all calculations for 45 degree skew angle, different angle will need different calculations):

  • The colors used within the gradient is the same as the border color. So, if border color needs to be red instead of #DDD then all color values should be changed to red.
  • The thickness of the border will be determined using background-size for the first two gradients and using the gradient's color-stop points for the next two gradients.

    • For the first two, the background-size in Y-axis is nothing but the border thickness. Change it to suit the needed thickness. The background-size in X-axis is nothing but 50% minus the height of the pseudo minus half of border thickness
    • For the next two, the color stop points of the gradient should be set such that the color starts (and transparent ends) at 50% - half the border thickness and color ends (transparnet starts) and 50% + half the border thickness (so that it produces a stroke of required thickness).
  • The background-position should be set such that the required gap is there between the colored area and the border.
    • For the first two, the background-position in X-axis should be left edge (0%) and right edge (100%) respectively. Their position in Y-axis should be above the pseudo-element and so it should be 100% minus pseudo-element's height minus the spacing.
    • For the next two, the background-position involves more complex calculation involving the border spacing, thickness and pseudo-element's height. The logic is present below.
background: linear-gradient([color, [color]),
            linear-gradient([color], [color]), 
            linear-gradient(to top right, 
                            transparent calc(50% - [border-thickness/2]), 
                            [color] calc(50% - [border-thickness/2]), 
                            [color] calc(50% + [border-thickness/2]),
                            transparent calc(50% + [border-thickness/2])),
            linear-gradient(to top left, 
                            transparent calc(50% - [border-thickness/2]),
                            [color] calc(50% - [border-thickness/2]), 
                            [color] calc(50% + [border-thickness/2]), 
                            transparent calc(50% + [border-thickness/2])), 
                            radial-gradient(circle, #3F9CBA 0%, #153346 100%) /* actual background */;

background-size: calc(50% - [pseudo-height - border-thickness/2]) [border-thickness], 
                 calc(50% - [pseudo-height - border-thickness/2]) [border-thickness], 
                 [pseudo-height] [pseudo-height], 
                 [pseudo-height] [pseudo-height], 
                 auto auto /* size of actual bg */;

background-position: 0% calc(100% - [pseudo-height + border-space]), 
                     100% calc(100% - [pseudo-height + border-space]), 
                     calc(50% - [(pseudo-height - border-space)/2]) calc(100% - [border-space + border-thickness/2]), 
                     calc(50% + [(pseudo-height - border-space)/2]) calc(100% - [border-space + border-thickness/2]), 
                     0px 0px;

If you need the transparent cut with border on the top part of the lower div then you could achieve it like in the below snippet.

div {
  height: 200px;
  width: 300px;
}
div:nth-child(1) {
  background: radial-gradient(circle, #3F9CBA 0%, #153346 100%);
  background-repeat: no-repeat;
}
div:nth-child(2) {
  position: relative;
  margin-top: -48px;
  padding-top: 48px;
  background: linear-gradient(#DDD, #DDD), linear-gradient(#DDD, #DDD), linear-gradient(to top right, transparent calc(50% - 2px), #DDD calc(50% - 2px), #DDD calc(50% + 2px), transparent calc(50% + 2px)), linear-gradient(to top left, transparent calc(50% - 2px), #DDD calc(50% - 2px), #DDD calc(50% + 2px), transparent calc(50% + 2px)), linear-gradient(#DDD, #DDD);
  background-size: calc(50% - 38px) 4px, calc(50% - 38px) 4px, 40px 40px, 40px 40px, auto auto;
  background-position: 0% 0px, 100% 0px, calc(50% - 18px) 0px, calc(50% + 18px) 0px, 0px 0px;
  background-repeat: no-repeat;
  background-clip: border-box, border-box, border-box, border-box, content-box;
  overflow: hidden;
}
div:nth-child(2):before,
div:nth-child(2):after {
  position: absolute;
  content: '';
  height: 40px;
  width: 50%;
  top: 8px;
  background: #DDD;
  backface-visibility: hidden;
}
div:before {
  left: 0;
  transform-origin: left bottom;
  transform: skewX(45deg);
}
div:after {
  right: 0;
  transform-origin: right bottom;
  transform: skewX(-45deg);
}
/* Just for demo */

div {
  transition: all 1s;
}
body:hover > div {
  height: 250px;
  width: 550px;
}
<div></div>
<div></div>
like image 92
Harry Avatar answered Dec 10 '22 12:12

Harry