Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sigmoid Curve Shape with CSS

I'd like to create a sigmoid curve-like shape for a full-screen layout that would reveal decorative patterned background on one side and have the solid color background on the other side, for text to be placed on top of it.

The goal is to have a full-screen page with a sigmoid-like top-left side filled with pattern, and the rest of the page just to have white background.

JSFiddle: Unfinished sigmoid curve

#container {
  padding-top: 10%;
  padding-bottom: 10%;
  background: white url(http://famok.com/wp-content/uploads/2016/10/WhiteOnWhite.jpg) top left / 26px 32px repeat;
  width: 100%;
  height: 100%;
  overflow: hidden;
}
#parallelogram {
  margin-left: 35%;
  width: 100%;
  height: 900px;
  -webkit-transform: skew(-15deg);
  -moz-transform: skew(-15deg);
  -o-transform: skew(-15deg);
  transform: skew(-15deg);
  background: white;
  -moz-border-radius: 100px;
  -webkit-border-radius: 100px;
  border-radius: 100px;
  -moz-box-shadow: inset 0 0 15px rgba(0, 0, 0, .4);
  -webkit-box-shadow: inset 0 0 15px rgba(0, 0, 0, .4);
  box-shadow: inset 0 0 15px rgba(0, 0, 0, .4);
}
<div id="container">
  <div id="parallelogram">
  </div>
</div>

I can't figure out how to create (or simulate) inverted rounded corner near lower left corner.

Or perhaps there's a conceptually different (better) solution available?

Update: I figured out how to create the shape I need entirely with CSS.

#container {
  padding-top: 100px;
  background: red;
  width: 100%;
  height: 100%;
  overflow: hidden;
}
#parallelogram {
  margin-left: 400px;
  width: 100%;
  height: 900px;
  -webkit-transform: skew(-15deg);
  -moz-transform: skew(-15deg);
  -o-transform: skew(-15deg);
  transform: skew(-15deg);
  background: white;
  -moz-border-top-left-radius: 100px;
  -webkit-top-left-border-radius: 100px;
  border-top-left-radius: 100px;
}
#bottom {
  height: 200px;
  width: 100%;
  background: white;
}
#bottom-corner {
  height: 100px;
  width: 300px;
  margin-left: -34px;
  background: red;
  -moz-border-bottom-right-radius: 100px;
  -webkit-bottom-right-border-radius: 100px;
  border-bottom-right-radius: 100px;
  -webkit-transform: skew(-15deg);
  -moz-transform: skew(-15deg);
  -o-transform: skew(-15deg);
  transform: skew(-15deg);
}
<div id="container">
  <div id="parallelogram">
  </div>

  <div id="bottom">
    <div id="bottom-corner">
    </div>
  </div>
</div>

However, this is still not the final solution because the shape doesn't allow me to use the kind of background effects I have in mind. Here's what happens when I try: fiddle.

#container {
  padding-top: 100px;
  background: white url(http://famok.com/wp-content/uploads/2016/10/WhiteOnWhite.jpg) bottom left / 26px 32px repeat;
  width: 100%;
  height: 100%;
  overflow: hidden;
}
#parallelogram {
  margin-left: 400px;
  width: 100%;
  height: 900px;
  -webkit-transform: skew(-15deg);
  -moz-transform: skew(-15deg);
  -o-transform: skew(-15deg);
  transform: skew(-15deg);
  background: white;
  -moz-border-top-left-radius: 100px;
  -webkit-top-left-border-radius: 100px;
  border-top-left-radius: 100px;
  -moz-box-shadow: inset 0 0 15px rgba(0, 0, 0, .4);
  -webkit-box-shadow: inset 0 0 15px rgba(0, 0, 0, .4);
  box-shadow: inset 0 0 15px rgba(0, 0, 0, .4);
}
#bottom {
  height: 200px;
  width: 100%;
  background: white;
  -moz-box-shadow: inset 0 0 15px rgba(0, 0, 0, .4);
  -webkit-box-shadow: inset 0 0 15px rgba(0, 0, 0, .4);
  box-shadow: inset 0 0 15px rgba(0, 0, 0, .4);
}
#bottom-corner {
  height: 100px;
  width: 300px;
  margin-left: -34px;
  background: white url(http://famok.com/wp-content/uploads/2016/10/WhiteOnWhite.jpg) top left / 26px 32px repeat;
  -moz-border-bottom-right-radius: 100px;
  -webkit-bottom-right-border-radius: 100px;
  border-bottom-right-radius: 100px;
  -webkit-transform: skew(-15deg);
  -moz-transform: skew(-15deg);
  -o-transform: skew(-15deg);
  transform: skew(-15deg);
  -moz-box-shadow: 0 0 15px rgba(0, 0, 0, .4);
  -webkit-box-shadow: 0 0 15px rgba(0, 0, 0, .4);
  box-shadow: 0 0 15px rgba(0, 0, 0, .4);
}
<div id="container">
  <div id="parallelogram">
  </div>

  <div id="bottom">
    <div id="bottom-corner">
    </div>
  </div>
</div>

Later update: after a bit of trial and error I ended up with a ridiculously crude hack solution that achieves the visual result I needed:

#container {
  padding-top: 100px;
  background: white url(http://famok.com/wp-content/uploads/2016/10/WhiteOnWhite.jpg) top left / 26px 32px repeat;
  width: 100%;
  height: 100%;
  overflow: hidden;
}
#parallelogram {
  margin-left: 385px;
  width: 100%;
  height: 900px;
  -webkit-transform: skew(-15deg);
  -moz-transform: skew(-15deg);
  -o-transform: skew(-15deg);
  transform: skew(-15deg);
  background: white;
  -moz-border-top-left-radius: 100px;
  -webkit-top-left-border-radius: 100px;
  border-top-left-radius: 100px;
  -moz-box-shadow: inset 0 15px rgba(0, 0, 0, .4);
  -webkit-box-shadow: inset 0 0 15px rgba(0, 0, 0, .4);
  box-shadow: inset 0 0 15px rgba(0, 0, 0, .4);
}
#bottom-rounded-corner {
  height: 122px;
  position: relative;
  width: 200%;
  z-index: 1000;
  margin-top: -80px;
  margin-left: -185px;
  background: url(http://famok.com/wp-content/uploads/2016/11/CornerAndMask.png) top left no-repeat;
}
#bottom-white {
  height: 100px;
  width: 100%;
  background: white;
}
<div id="container">
  <div id="parallelogram">
  </div>
  <div id="bottom-rounded-corner">

  </div>
  <div id="bottom-white">
  </div>
</div>

Try as hard as I could to implement a conceptually better alternative suggested by Harry below, I was unable to use it to create the effect I wanted. I'd still be grateful if someone could help, either by showing how to do it, or by proposing optimizations to my solution.

Thank you in advance!

like image 893
Dimitri Vorontzov Avatar asked Oct 31 '16 20:10

Dimitri Vorontzov


1 Answers

Use SVG for complex shapes and not CSS:

As I had mentioned in my comments, please do not use CSS for creating such complex shapes. SVG is the recommended tool for such complex stuff. SVGs are easy to create, maintain and they are also responsive (scaleable) by default and so it has a lot of advantages.


Creating the sigmoid shape:

Creating the sigmoid curve shape with SVG itself is pretty simple and just needs one path element:

  • M0,750 moves the imaginary pen close to the bottom-left of the SVG element (co-ordinates are set slightly lower than the SVG's height so that there is a gap at the bottom where the shadow will be visible).
  • L250,750 produces a horizontal Line from the point (0,768) to (250,768)
  • C650,730 500,154 1024,154 creates the actual curve. Here the first two coordinates are control points of the curve ((650,730), (500,154)) and the third one is the end point (1024,154). Curvature of the curve can be adjusted by modifying the control points.
  • L1024,0 0,0 0,750 is for completing the shape. The shape needs to be complete for fill to work.

body {
  margin: 0;
}
svg {
  width: 100%;
  height: 100vh;
}
<svg viewBox='0 0 1024 768' preserveAspectRatio='none'>

  <!-- For the shadow -->
  <defs>
    <filter id="dropShadow">
      <feGaussianBlur in="SourceAlpha" stdDeviation="6" />
      <feOffset dx="3" dy="3" result="offsetBlur" />
      <feFlood flood-color="#AAA" flood-opacity="1" result="offsetColor" />
      <feComposite in="offsetColor" in2="offsetBlur" operator="in" result="offsetBlur" />
      <feMerge>
        <feMergeNode />
        <feMergeNode in="SourceGraphic" />
      </feMerge>
    </filter>
  </defs>
  <!-- End of shadow -->

  <!-- For filling the top-left with pattern -->
  <pattern id='dots' patternUnits='userSpaceOnUse' width='25' height='25'>
    <polygon points='0,0 0,25 25,25 25,0' fill='yellowgreen' />
    <circle cx='12.5' cy='12.5' r='4' fill='rebeccapurple' />
  </pattern>
  <!-- End of pattern -->

  <!-- Actual sigmoid curve -->
  <path d='M0,750 L250,750 C650,730 500,154 1024,154 L1024,0 0,0 0,750' fill='url(#dots)' filter='url(#dropShadow)' />

</svg>

Applying Pattern to the Shape:

In the above demo, I had created a polka dot pattern using the polygon and circle elements but it is not mandatory to create it within SVG itself, we can also use image element and fill the shape with the image pattern.

If you want to change the background image (pattern) into another image of your choice, just specify the URL of your image in the xlink:href attribute of the image tag like in the below snippet. Based on your needs and image, you may have to change the height and width of pattern and image.

<pattern id='dots' patternUnits='userSpaceOnUse' width='25' height='25'>
  <image xlink:href='https://yourwebsite.com/yourpath' x='0' y='0' width='15' height='15' />
</pattern>

body {
  margin: 0;
}
svg {
  width: 100%;
  height: 100vh;
}
<svg viewBox='0 0 1024 768' preserveAspectRatio='none'>
  <defs>
    <filter id="dropShadow">
      <feGaussianBlur in="SourceAlpha" stdDeviation="6" />
      <feOffset dx="3" dy="3" result="offsetBlur" />
      <feFlood flood-color="#AAA" flood-opacity="1" result="offsetColor" />
      <feComposite in="offsetColor" in2="offsetBlur" operator="in" result="offsetBlur" />
      <feMerge>
        <feMergeNode />
        <feMergeNode in="SourceGraphic" />
      </feMerge>
    </filter>
    <pattern id='dots' patternUnits='userSpaceOnUse' width='36.6' height='46'>
      <image xlink:href='http://famok.com/wp-content/uploads/2016/10/WhiteOnWhite.jpg' x='0' y='0' width='36.6' height='46' />
    </pattern>
  </defs>
  <path d='M0,750 L250,750 C650,730 500,154 1024,154 L1024,0 0,0 0,750' fill='url(#dots)' filter='url(#dropShadow)' />
</svg>

Note: The image used in the above demo is not my own. It was taken from the internet.


The Shadow:

The shadow effect is created by using SVG filter element along with feGaussianBlur, feOffset and the feMerge elements. The feGaussianBlur element blurs the source graphic (our sigmoid) by the specified standard deviation value and the feOffset offsets the resulting image by the dx, dy values. The original image and the blurred one are the merged using feMerge. The feFlood and the feComposite are added just in case you want to give the shadow a different color. The colors can be specified using the flood-color and flood-opacity attributes. (The method for changing the SVG drop-shadow's color was taken from this answer by Joe W.)


Adding Text:

Now this is the really tricky bit of the whole thing. If you need to place the text only on the solid colored area of the page then you would need to use positioning attributes carefully. If the text is small or just a single line of text then we could use SVG text element itself like in the demo that I linked earlier. If it is not then you'd have to make sure that the container box of the text doesn't overlap onto the sigmoid shape's area.

body {
  margin: 0;
}
div.container {
  position: relative;
  width: 100%;
  height: 100vh;
}
svg {
  position: absolute;
  top: 0px;
  left: 0px;
  height: 100%;
  width: 100%;
}
.container div {
  position: absolute;
  top: 50%;
  right: 0px;
  height: 30vh;
  width: 33.33%;
  font-size: 20px;
}
<div class='container'>
  <svg viewBox='0 0 1024 768' preserveAspectRatio='none'>

    <defs>
      <!-- For the shadow -->
      <filter id="dropShadow">
        <feGaussianBlur in="SourceAlpha" stdDeviation="6" />
        <feOffset dx="3" dy="3" result="offsetBlur" />
        <feFlood flood-color="#AAA" flood-opacity="1" result="offsetColor" />
        <feComposite in="offsetColor" in2="offsetBlur" operator="in" result="offsetBlur" />
        <feMerge>
          <feMergeNode />
          <feMergeNode in="SourceGraphic" />
        </feMerge>
      </filter>
      <!-- End of shadow -->

      <!-- For filling the top-left with pattern -->
      <pattern id='dots' patternUnits='userSpaceOnUse' width='25' height='25'>
        <polygon points='0,0 0,25 25,25 25,0' fill='yellowgreen' />
        <circle cx='12.5' cy='12.5' r='4' fill='rebeccapurple' />
      </pattern>
      <!-- End of pattern -->
    </defs>

    <!-- Actual sigmoid curve -->
    <path d='M0,750 L250,750 C650,730 500,154 1024,154 L1024,0 0,0 0,750' fill='url(#dots)' filter='url(#dropShadow)' />

  </svg>
  <div>Hello! Here is some text that is placed on the solid colored area of the page.</div>
</div>

We can try using the CSS shape-outside property but the browser support for that is pretty poor at the moment. Here is a demo using this shape-outside property. Could not host it in-site because it needed creating a separate SVG file. The demo is an adapted version of the one provided in the W3C CSS Shapes Spec.


Alternate Approach: (apply the pattern to the container instead of SVG)

Since you don't want the image to get squished or stretched, an alternate approach would be to do the following:

  • Create the SVG shape such that it is only the solid colored part (and not the patterned area)
  • Apply the pattern to the div.container and then place the SVG absolutely on top of the element. The SVG shape (which has a white colored fill) will prevent the pattern from being visible on the other side.
  • Change the shadow on the SVG from a normal drop shadow to an inset shadow. (Inset Shadow's code is completely taken from here.

body {
  margin: 0;
}
div.container {
  position: relative;
  background: white url(http://famok.com/wp-content/uploads/2016/10/WhiteOnWhite.jpg) top left / 26px 32px repeat;
  width: 100%;
  height: 100vh;
}
svg {
  position: absolute;
  top: 0px;
  left: 0px;
  height: 100%;
  width: 100%;
}
.container div {
  position: absolute;
  top: 50%;
  right: 0px;
  height: 30vh;
  width: 33.33%;
  font-size: 20px;
}
<div class='container'>
  <svg viewBox='0 0 1024 768' preserveAspectRatio='none'>
    <defs>
      <filter id="dropShadow" x="-50%" y="-50%" width="200%" height="200%">
        <feComponentTransfer in=SourceAlpha>
          <feFuncA type="table" tableValues="1 0" />
        </feComponentTransfer>
        <feGaussianBlur stdDeviation="6" />
        <feOffset dx="2" dy="2" result="offsetblur" />
        <feFlood flood-color="#AAA" result="color" />
        <feComposite in2="offsetblur" operator="in" />
        <feComposite in2="SourceAlpha" operator="in" />
        <feMerge>
          <feMergeNode in="SourceGraphic" />
          <feMergeNode />
        </feMerge>
      </filter>
    </defs>
    <path d='M0,750 L250,750 C650,730 500,154 1024,154 L1024,768 0,768 0,750' fill='white' filter='url(#dropShadow)' />
  </svg>
  <div>Hello! Here is some text that is placed on the solid colored area of the page.</div>
</div>

Here is Plunker Demo for the alternate approach. This is a bit more complex than the previous one because here we need one SVG to produce the solid colored area (used as img) and another SVG which is the inverse (the patterned area) to be used with shape-outside.

like image 157
Harry Avatar answered Oct 10 '22 06:10

Harry