CSS triangular cutout with border and transparent

2019-06-28 04:35发布

问题:

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

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):

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?

回答1:

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>



回答2:

here anoter approach with gradient and (@Harry )borders & shadow : A known width helps much, else, you need to adjust some shadows offset values with calc().

div.bdr {
  padding-bottom: 5px;/* to be used to draw a transparent line */
  background: #1B4046;
  background-clip: content-box;/* here comes the 5 transparent pixel line */
  box-shadow: -273px 5px 0 #1B4046, 274px 5px 0 #1B4046; 
  /* move shadow far away to only draw them by bits 
  Notice, wrapper must be hidding overflow */
  margin-bottom:2em;
}

.bdr.bg-color{
  background-color:tomato;
  background-image:linear-gradient(to top, transparent 5px,   #1B4046 5px);

  background-clip:border-box;
  }
div.bdr:after {  
  content: '';
  height: 30px;
  width: 30px;
  padding: 5px; 
  display: block;
  margin: auto;
  z-index: -1;
  transform: rotate(45deg);
  margin: -20px auto -20px;
  background:  #1B4046;
  background-clip: content-box;/* again, keep some parts transparent */
  box-shadow:6px 6px 0  -1px #1B4046;  
}
.bdr.bg-color:after {
  z-index:0;
  padding:5px 0 0 5px;
  border:5px solid transparent;
  border-left:0;
  border-top:0;
  box-shadow:6px 6px 0  -1px #1B4046;
  
  background:linear-gradient(to bottom right, transparent 50%, #1B4046 50%) no-repeat , linear-gradient(to bottom right, transparent 50%, tomato 50%) 5px 5px  no-repeat;
  }
article {
  width:500px;
  margin:auto;
  color:white;
}
h1 {
  text-align:center;
}
div.shapebdr {
  padding-bottom: 40px;
  margin: 32px 0;
  background: linear-gradient(to top, transparent 30px, #1B4046 30px, #1B4046 35px, transparent 35px) bottom left, linear-gradient(to top, transparent 30px, #1B4046 30px, #1B4046 35px, transparent 35px) bottom right, linear-gradient(to top, transparent 40px, #1B4046 40px) bottom;
  ;
  background-repeat: no-repeat;
  background-size: 230px 100%, 230px 100%, 100% 100%;
  position: relative;
}

div.shapebdr:after {
  content: '';
  height: 40px;
  width: 40px;
  padding: 10px;
  display: block;
  margin: auto;
  z-index: -1;
  background: inherit;
  transform: rotate(45deg);
  margin: -40px auto -20px;
  background: linear-gradient(to top, #1B4046, #1B4046) no-repeat, linear-gradient(to top, #1B4046 0, #1B4046 5px, transparent 5px) no-repeat right, linear-gradient(to left, #1B4046 0, #1B4046 5px, transparent 5px) no-repeat bottom right;
  background-clip: content-box, border-box, border-box;
  background-size: auto auto, 57% 100%, 100% 57%;
}

div.br:after {
  margin: auto;
  margin-bottom: -40px;  
}


article {
  overflow: hidden;
  padding-bottom: 40px;
}
html {
  min-height: 100%;
  background: linear-gradient(30deg, gray, yellow, purple, lime, tomato, turquoise, gray);
}
p {
  padding:0.25em;
  margin:0.25em;
position:relative;/* make sure text shows over the pseudo element */
}
<article>
  <h1>border and shadow approach </h1>
  <div class="bdr">
    <p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est.
      facilisis luctus, metus</p>
  </div>
  <h1>gradient approach</h1>
  <div class="shapebdr">
    <p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est.
      lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor,
      facilisis luctus, metus</p>

  </div>
   <h1>shadow + gradient approach to fill translucide line</h1> 
  <div class="bdr bg-color">
    <p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est.
      lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor,
      facilisis luctus, metus</p>

  </div>
</article>

pen to play with



回答3:

Couldn't resist to post another version ...

Too many hand calculated values to make it a perfect solution, but can suit you if the dimension are fixed.

.test {
  width: 400px;
  height: 80px;
  margin-top: 100px;
  position: relative;
  overflow: hidden;
  background-image: linear-gradient(-120deg, transparent 31px, lightblue 21px),                       linear-gradient(120deg, transparent 31px, lightblue 21px);
  background-size: 50% 80%, 50% 80%;
  background-repeat: no-repeat;
  background-position: 0px 100%, 100% 100%;
}


.test:before {
  content: "";
  position: absolute;
  width: 50%;
  height: 50px;
  border-top: solid 4px blue;
  border-right: solid 4px blue;
  top: 0px;
  right: calc(50% - 2px);
  transform: skewX(30deg);
  transform-origin: right bottom;
}

.test:after {
  content: "";
  position: absolute;
  width: 50%;
  height: 50px;
  border-top: solid 4px blue;
  border-left: solid 4px blue;
  top: 0px;
  left: calc(50% - 2px);
  transform: skewX(-30deg);
  transform-origin: right bottom;
}
<div class="test"></div>