2

enter image description here

I am looking to create two shapes in CSS. The first one, the one on the left, I am close to making, but my shape goes out too far.

The second shape, I am struggling with completely.

Is there anyway to replicate these shapes? Is there anyway to do it without the use of Psuedo elements/as one div only?

The goal is to use CSS animation to make the first shape into the second, so I do not believe SVG is an option.

The div should start at shape one, and gradually transition into shape two. I'm having issues getting the bulge to not stick out so far, with my method.

HTML

<div id="one"></div>
<div id="two"></div>

CSS

div {
    width: 80px;
    height: 80px;
    background: black;
    margin: 60px;
    position: relative;
}

#one:before {
  content: "";
  display: block;
  position: absolute;
  border-radius: 55%;
  width: 80px;
  height: 80px;
  background: black;
  left: 0;
  top: -34px;
}

JS Fiddle Link

Harry
  • 87,580
  • 25
  • 202
  • 214
DRB
  • 633
  • 11
  • 24
  • The second shape is a duplicate of http://stackoverflow.com/questions/8503636/transparent-half-circle-cut-out-of-a-div/30726390#30726390. While the one in question is not exactly a half-circle cut, the approaches would be pretty much the same and the linked thread provides a fair few of them. – Harry Oct 04 '15 at 09:50
  • @Harry I am looking to animate these sides in/out, so I don't think SVG is an option. – DRB Oct 04 '15 at 09:54
  • SVGs can be animated too (inline SVGs) and maybe if you could clarify what type of animations you are planning to do then you would get better choices. (*Edit*: If you wish to morph one shape to another then you could use SVG path and animate the coordinates of the path when required). – Harry Oct 04 '15 at 09:55
  • Added a bit in the origina post, looking to use CSS animation. @Harry – DRB Oct 04 '15 at 09:58
  • How should the animation be? Should it be like the bulged part is gradually moving/folding in? – Harry Oct 04 '15 at 10:06
  • Yes, exactly - it should start at shape one, and gradually transition into shape two. I'm having issues getting the bulge to not stick out so far, with my method. @Harry – DRB Oct 04 '15 at 10:14

3 Answers3

4

You can use Raphael or SVG for morphing bezier paths but i dont know how to do that. That should give the cleanest solution

I think this might work :-

  • Create one element and put Overflow hidden

  • Set the timing on pseudo element, animating border radius and height

  • Use box shadow of pseudo element for coloring.

> Fiddle <

body{
    background: url("http://www.placekitten.com/g/600/400");
    background-size: cover;
}

#rect {
    height: 300px;
    width: 100px;
    border-top-left-radius: 50px 40px;
    border-top-right-radius: 50px 40px;
    position: relative;
    overflow: hidden;
    -webkit-animation: morph 1s linear 1;
    -webkit-animation-fill-mode: forwards;
    
    animation: morph 1s linear 1;
    animation-fill-mode: forwards; 
}

#rect:before {
    position: absolute;
    content: "";
    width: 100px;
    box-shadow: 0px 0px 0px 400px #A34;
    -webkit-animation: morph2 1s linear 1;
    -webkit-animation-delay: 1s;
    -webkit-animation-fill-mode: forwards;
    
    animation: morph2 1s linear 1;
    animation-delay: 1s;
    animation-fill-mode: forwards;
    
}

@-webkit-keyframes morph {
    0%{
        border-top-left-radius: 50px 40px;
        border-top-right-radius: 50px 40px;
        top:0px;
        height: 300px;
    }
    100%{
        border-top-left-radius: 40px 0px;
        border-top-right-radius: 40px 0px;
        top: 40px;
        height: 260px;
    }
}

@-webkit-keyframes morph2 {
    0%{
        height: 0px;
        border-bottom-left-radius: 40px 0px;
        border-bottom-right-radius: 40px 0px;
    }
    100%{
        border-bottom-left-radius: 50px 40px;
        border-bottom-right-radius: 50px 40px;
        height: 40px;
    }
}

/*FOR OTHER BROWSERS*/

@keyframes morph {
    0%{
        border-top-left-radius: 50px 40px;
        border-top-right-radius: 50px 40px;
        top:0px;
        height: 300px;
    }
    100%{
        border-top-left-radius: 40px 0px;
        border-top-right-radius: 40px 0px;
        top: 40px;
        height: 260px;
    }
}

@keyframes morph2 {
    0%{
        height: 0px;
        border-bottom-left-radius: 40px 0px;
        border-bottom-right-radius: 40px 0px;
    }
    100%{
        border-bottom-left-radius: 50px 40px;
        border-bottom-right-radius: 50px 40px;
        height: 40px;
    }
}
<div id="rect">
</div>

NOTE Sorry the code got a lil too big, I am writing CSS and HTML after about a year! I think a lot of this mess could be shortened.

Max Payne
  • 2,423
  • 17
  • 32
3

As far as I am aware there is no way to accomplish animation effect without using pseudo-elements. The below is one possible (but very highly complex approach) using multiple gradients and pseudo-elements. The shape consists of the following parts:

  • The main div element which has the caved-in/concave shape. It is produced using a combo of radial-gradient and linear-gradient. Half the height of parent is occupied by the radial gradient and produces the transparent cut, the other (bottom) half is occupied by the linear gradient and ends up producing the solid black color. The linear gradient actually does not produce any gradient but rather produces only a solid color because the color stop of the solid color is too close to the end point. This is still required because without using gradients (as images) we cannot control space that is occupied by a solid color.
  • The :before element which has two radial gradients and produces the effect of two half ellipses stacked on top of one another. The one on top is a transparent half ellipse (and black for the rest) whereas the one at bottom is a black half ellipse (and transparent for the rest). We need both the gradients because when we animate the background-position this makes it look as though the bulged area is caving in.
  • The :after element has one radial gradient which produces a solid black ellipse. This element is positioned half way above the parent element to produce the bulged effect.
  • During the first part of the animation, the :after element is rotated along X-axis by 90 degrees and this makes it look as though it is slowly disappearing from view. During the second part of the animation, the :before element's background-position is animated such that the black colored half ellipse goes out of the way and the transparent half ellipse comes into its place.

div {
  position: relative;
  height: 80px;
  width: 80px;
  margin-top: 60px; /* safe to have half of parent height */
  background: radial-gradient(ellipse at 50% 0%, transparent 70%, black 72%), linear-gradient(to bottom, black 99.9%, transparent 99.9%);
  background-size: 100% 50%, 100% 50%; /* the height of first image will be same as height of transparent cut, height of second image is the rest */
  background-position: 0% 0%, 0% 100%;
  background-repeat: no-repeat;
}
div:before,
div:after {
  position: absolute;
  content: '';
  width: 100%;
}
div:before {
  height: 100%; /* height of first image on parent * 2 */
  top: 0px;
  background-image: radial-gradient(ellipse at 50% 0%, black 70%, transparent 72%), radial-gradient(ellipse at 50% 0%, transparent 70%, black 72%);
  background-size: 100% 50%;
  background-position: 0% 2px, 0% -38px;
  background-repeat: no-repeat;
  animation: morph-shape 2s 2s linear forwards;
}
div:after {
  height: 50%; /* this should be same as height of first image on parent */
  top: -38px; /* this is height of pseudo-element in % * height of parent - 2px */
  background-image: radial-gradient(ellipse at 50% 50%, black 70%, transparent 72%);
  background-size: 100% 200%;
  background-repeat: no-repeat;
  transform-origin: bottom;
  animation: morph-shape2 2s linear forwards;
}
@keyframes morph-shape2 {
  from {
    transform: rotateX(0deg);
  }
  to {
    transform: rotateX(90deg);
  }
}
@keyframes morph-shape {
  from {
    background-position: 0% 2px, 0% -38px; /* the negative value is height of image - 2px = 50% of container height (which is 50% of 80px) - 2px*/
  }
  to {
    background-position: 0% 100%, 0% 0%;
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js"></script>
<div></div>

Just for illustration, here is how the three elements look like when they are not placed one on top of the other. I have left the animations as-is for you to still get a feel of how exactly it is happening. Also, it does seem to work even if the dimensions of parent are changed (provided the calculations mentioned in comments are done properly).

div {
  position: relative;
  height: 160px;
  width: 120px;
  margin-top: 80px;
  background: radial-gradient(ellipse at 50% 0%, transparent 70%, black 72%), linear-gradient(to bottom, black 99.9%, transparent 99.9%);
  background-size: 100% 40%, 100% 60%;
  background-position: 0% 0%, 0% 100%;
  background-repeat: no-repeat;
}
div:before,
div:after {
  position: absolute;
  content: '';
  width: 100%;
}
div:before {
  height: 80%;
  top: 0px;
  left: 150px;
  background-image: radial-gradient(ellipse at 50% 0%, black 70%, transparent 72%), radial-gradient(ellipse at 50% 0%, transparent 70%, black 72%);
  background-size: 100% 50%;
  background-position: 0 2px, 0% -62px;
  background-repeat: no-repeat;
  animation: morph-shape 2s 2s linear forwards;
}
div:after {
  height: 40%;
  top: -62px;
  left: 300px;
  background-image: radial-gradient(ellipse at 50% 50%, black 70%, transparent 72%);
  background-size: 100% 200%;
  background-repeat: no-repeat;
  transform-origin: bottom;
  animation: morph-shape2 2s linear forwards;
}
@keyframes morph-shape2 {
  from {
    transform: rotateX(0deg);
  }
  to {
    transform: rotateX(90deg);
  }
}
@keyframes morph-shape {
  from {
    background-position: 0% 2px, 0% -62px;
  }
  to {
    background-position: 0% 100%, 0% 0%;
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js"></script>
<div></div>

Below is a version with different dimensions for the container and the transparent cut.

div {
  position: relative;
  height: 200px;
  width: 150px;
  margin-top: 100px;
  background: radial-gradient(ellipse at 50% 0%, transparent 70%, black 72%), linear-gradient(to bottom, black 99.9%, transparent 99.9%);
  background-size: 100% 30%, 100% 70%;
  background-position: 0% 0%, 0% 100%;
  background-repeat: no-repeat;
}
div:before,
div:after {
  position: absolute;
  content: '';
  width: 100%;
}
div:before {
  height: 60%;
  top: 0px;
  background-image: radial-gradient(ellipse at 50% 0%, black 70%, transparent 72%), radial-gradient(ellipse at 50% 0%, transparent 70%, black 72%);
  background-size: 100% 50%;
  background-position: 0% 2px, 0% -58px;
  background-repeat: no-repeat;
  animation: morph-shape 2s 2s linear forwards;
}
div:after {
  height: 30%;
  top: -58px;
  background-image: radial-gradient(ellipse at 50% 50%, black 70%, transparent 72%);
  background-size: 100% 200%;
  background-repeat: no-repeat;
  transform-origin: bottom;
  animation: morph-shape2 2s linear forwards;
}
@keyframes morph-shape2 {
  from {
    transform: rotateX(0deg);
  }
  to {
    transform: rotateX(90deg);
  }
}
@keyframes morph-shape {
  from {
    background-position: 0% 2px, 0% -58px;
  }
  to {
    background-position: 0% 100%, 0% 0%;
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js"></script>
<div></div>
Harry
  • 87,580
  • 25
  • 202
  • 214
0

You can use pseudo element and overflow hidden

*{box-sizing: border-box}

html{text-align: center;padding: 60px 10px}

.fig{
  margin: 20px auto;
  width: 100px;
  height: 200px;
  position: relative
}
.fig:before{
  content:'';
  position: absolute;
  top: -50px;
  left:0;
  width: 100px;
  height: 100px;
  border-radius: 50%
}
#one{background: black}
#two{overflow: hidden; box-shadow: inset 0 -150px black}
#one:before{background: black}
#two:before{box-shadow: 0 3em black}
<div id=one class=fig></div>
<div id=two class=fig></div>
Gildas.Tambo
  • 22,173
  • 7
  • 50
  • 78