skip to Main Content

I’m trying to clone some projects interfaces on dribble for study purposes and raise my css skills, i’ve selected the layout below:

enter image description here

As you can see it’s a mobile interface but i’m using react dom to build it ( as i mention before, it’s just for study purpose ).

My problem is about this wavy bordered div’s on the top and the bottom of the layout, i have tried to follow some tutorials but i did not be able to do it.

What i’ve tried:

At first i have tried css to solve this, using position absulote an z-index to render divs to "hide" the purple background, but the final result was weird. Here is my code:

import { Flex, useBreakpointValue } from '@chakra-ui/react';
import { Box, Flex } from '@chakra-ui/react';


const Curve = () => (
    <Box
        position="absolute"
        height="33vh"
        width="100%"
        bottom="0"
        textAlign="center"
    >
        <Box
            content='""'
            display="block"
            position="absolute"
            borderRadius="100% 28%"
            width="69.5%"
            height="33vh"
            transform="translate(85%, 60%)"
            backgroundColor="#f8f9fc"
            top={5}
            left={-20}
        />
        <Box
            content='""'
            display="block"
            position="absolute"
            borderRadius="100% 45%"
            width="75%"
            height="33vh"
            backgroundColor="#3B3486"
            transform="translate(-4%, 40%)"
            zIndex="1"
            top={5}
            left={-20}
        />
    </Box>
);

export function Login() {
    return (
        <>
            <Box background="#3B3486" position="relative">
                <Flex pt="9rem" />
                <Curve />
            </Box>
            <Flex
                h={'70vh'}
                flex={1}
                justifyContent={'center'}
                alignItems={'center'}
            >
                Content
            </Flex>
        </>
    );
}

At last, i read that i can use SVG elements to handle it, but the final result was not responsive to other layouts. I would like to use css to solve it because it is the purpose of this clone. Is it possible to do it?

2

Answers


  1. The clip-path CSS property can do what you want. However, arriving at a path that matches your shape and is responsive has some steps.

    First, we can create the path we will use using an SVG editor. For example I have created an approximate curve using this tool. This gives me the SVG path:

    m429 0c-46 97-392 39-430 129l430 0V0
    

    Unfortunately, using this like clip-path: path("m429 0c-46 97-392 39-430 129l430 0V0"); won’t yield what you want since it won’t be responsive. The path needs to be treated as relative to the container for that to happen and by default, this will not do that.

    One approach is to insert a dummy SVG into the page containing a <clipPath clipPathUnits="objectBoundingBox"> and a child <path> with those points above, and then reference this using clip-path: url(#your-clippath-id). This is detailed in this answer.

    However, this is a little cumbersome. Another simpler approach is to convert the path to a polygon (which uses percentages) using some separate tool. I used this answer to successfully derive a polygon, which can be used with clip-path directly. This gave me:

    clip-path: polygon(99.77% 0.00%, 98.58% 6.65%, 97.06% 12.50%, 95.29% 17.54%, 93.36% 21.86%, 91.32% 25.56%, 89.20% 28.75%, 87.03% 31.52%, 84.82% 33.95%, 82.58% 36.08%, 80.33% 37.97%, 78.06% 39.66%, 75.78% 41.17%, 73.49% 42.53%, 71.19% 43.77%, 68.89% 44.90%, 66.59% 45.93%, 64.28% 46.89%, 61.97% 47.79%, 59.66% 48.63%, 57.34% 49.42%, 55.03% 50.18%, 52.72% 50.91%, 50.40% 51.63%, 48.08% 52.34%, 45.77% 53.04%, 43.45% 53.76%, 41.14% 54.49%, 38.82% 55.24%, 36.51% 56.03%, 34.20% 56.87%, 31.89% 57.76%, 29.58% 58.72%, 27.27% 59.76%, 24.97% 60.90%, 22.68% 62.17%, 20.39% 63.57%, 18.12% 65.14%, 15.85% 66.92%, 13.61% 68.95%, 11.39% 71.28%, 9.21% 73.98%, 7.09% 77.15%, 5.06% 80.91%, 3.16% 85.41%, 1.50% 90.80%, 0.18% 97.16%, 1.15% 100.00%, 3.47% 100.00%, 5.80% 100.00%, 8.13% 100.00%, 10.45% 100.00%, 12.78% 100.00%, 15.10% 100.00%, 17.43% 100.00%, 19.75% 100.00%, 22.08% 100.00%, 24.40% 100.00%, 26.73% 100.00%, 29.06% 100.00%, 31.38% 100.00%, 33.71% 100.00%, 36.03% 100.00%, 38.36% 100.00%, 40.68% 100.00%, 43.01% 100.00%, 45.33% 100.00%, 47.66% 100.00%, 49.99% 100.00%, 52.31% 100.00%, 54.64% 100.00%, 56.96% 100.00%, 59.29% 100.00%, 61.61% 100.00%, 63.94% 100.00%, 66.26% 100.00%, 68.59% 100.00%, 70.92% 100.00%, 73.24% 100.00%, 75.57% 100.00%, 77.89% 100.00%, 80.22% 100.00%, 82.54% 100.00%, 84.87% 100.00%, 87.20% 100.00%, 89.52% 100.00%, 91.85% 100.00%, 94.17% 100.00%, 96.50% 100.00%, 98.82% 100.00%, 99.77% 95.40%, 99.77% 87.64%, 99.77% 79.89%, 99.77% 72.14%, 99.77% 64.39%, 99.77% 56.64%, 99.77% 48.88%, 99.77% 41.13%, 99.77% 33.38%, 99.77% 25.63%, 99.77% 17.88%, 99.77% 10.12%);
    
    

    Edit: I needed to clean this up a bit and remove some useless points as I think this generative code is imperfect and could be improved. This gives me:

    clip-path: polygon(100% 0%, 98.58% 6.65%, 97.06% 12.5%, 95.29% 17.54%, 93.36% 21.86%, 91.32% 25.56%, 89.2% 28.75%, 87.03% 31.52%, 84.82% 33.95%, 82.58% 36.08%, 80.33% 37.97%, 78.06% 39.66%, 75.78% 41.17%, 73.49% 42.53%, 71.19% 43.77%, 68.89% 44.9%, 66.59% 45.93%, 64.28% 46.89%, 61.97% 47.79%, 59.66% 48.63%, 57.34% 49.42%, 55.03% 50.18%, 52.72% 50.91%, 50.4% 51.63%, 48.08% 52.34%, 45.77% 53.04%, 43.45% 53.76%, 41.14% 54.49%, 38.82% 55.24%, 36.51% 56.03%, 34.2% 56.87%, 31.89% 57.76%, 29.58% 58.72%, 27.27% 59.76%, 24.97% 60.9%, 22.68% 62.17%, 20.39% 63.57%, 18.12% 65.14%, 15.85% 66.92%, 13.61% 68.95%, 11.39% 71.28%, 9.21% 73.98%, 7.09% 77.15%, 5.06% 80.91%, 3.16% 85.41%, 1.5% 90.8%, 0.18% 97.16%, 0% 100%, 100% 100%);
    

    This is big, but the less points (inversely proportional to the steps variable on the above answer) there are the more inaccurate the shape.

    You can then use this directly with a single <Box> component. The curve will scale with the width/height of that box.

    For the one at the top of the page, you can just use the same setup but with transform: scale(-1); applied to reflect it so its the right way up.

    Here is a visual demo of the result:

    body, html {
      margin: 0;
      padding: 0;
    }
    
    .clipped {
      margin:20px 0;
      width: 100vw;
      height: 200px;
      background-color: purple;
      clip-path: polygon(100% 0%, 98.58% 6.65%, 97.06% 12.5%, 95.29% 17.54%, 93.36% 21.86%, 91.32% 25.56%, 89.2% 28.75%, 87.03% 31.52%, 84.82% 33.95%, 82.58% 36.08%, 80.33% 37.97%, 78.06% 39.66%, 75.78% 41.17%, 73.49% 42.53%, 71.19% 43.77%, 68.89% 44.9%, 66.59% 45.93%, 64.28% 46.89%, 61.97% 47.79%, 59.66% 48.63%, 57.34% 49.42%, 55.03% 50.18%, 52.72% 50.91%, 50.4% 51.63%, 48.08% 52.34%, 45.77% 53.04%, 43.45% 53.76%, 41.14% 54.49%, 38.82% 55.24%, 36.51% 56.03%, 34.2% 56.87%, 31.89% 57.76%, 29.58% 58.72%, 27.27% 59.76%, 24.97% 60.9%, 22.68% 62.17%, 20.39% 63.57%, 18.12% 65.14%, 15.85% 66.92%, 13.61% 68.95%, 11.39% 71.28%, 9.21% 73.98%, 7.09% 77.15%, 5.06% 80.91%, 3.16% 85.41%, 1.5% 90.8%, 0.18% 97.16%, 0% 100%, 100% 100%);
    }
    
    .inverted {
       transform: scale(-1);
    }
    <strong>Top:</strong>
    
    <div class="clipped inverted"></div>
    
    <strong>Bottom:</strong>
    <div class="clipped"></div>
    Login or Signup to reply.
  2. adsy’s answer already show you the steps to use clip-path, so here’s my attempt to recreate the path. (Click the "Full page" button and resize your windows to test responsiveness)

    :root {
      --bg: #3B3486;
    }
    
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    
    .svg {
      position: absolute;
      top: 0;
      left: 0;
      width: 0;
      height: 0;
    }
    
    body::before,
    body::after {
      content: '';
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 33.33%;
      background-color: var(--bg);
      clip-path: url(#wavy-clip-path);
      z-index: -1;
    }
    
    body::after {
      top: unset;
      bottom: 0;
      scale: -1;
    }
    <svg class="svg">
      <clipPath id="wavy-clip-path" clipPathUnits="objectBoundingBox">
        <path d="M0,0 l1,0 c0,0.25 -0.15,0.33 -0.5,0.5 -0.33,0.15 -0.5,0.25 -0.5,0.5 z"></path>
      </clipPath>
    </svg>

    Alternatively, if you want pure CSS without SVG, you can try border-radius (the curve won’t be exactly like your example though)

    :root {
      --bg: #3B3486;
    }
    
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    
    body {
      width: 100vw;
      height: 100vh;
      display: grid;
      grid-template-rows: 33.33% 33.33% 33.33%;
    }
    
    header,
    footer,
    main {
      position: relative;
    }
    
    header::before,
    footer::before,
    main::before,
    header::after,
    footer::after,
    main::after {
      content: '';
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 50%;
      background-color: var(--bg);
      z-index: -1;
    }
    
    header::before {
      border-bottom-right-radius: 50% 100%;
    }
    
    header::after {
      top: unset;
      bottom: 0;
    }
    
    footer::before {
      border-top-left-radius: 50% 100%;
      top: unset;
      bottom: 0;
    }
    
    footer::after {
      z-index: -2;
    }
    
    main::before {
      border-top-left-radius: 50% 100%;
      top: unset;
      bottom: 100%;
      background-color: #fff;
    }
    
    main::after {
      border-bottom-right-radius: 50% 100%;
      top: 100%;
      background-color: #fff;
    }
    <header></header>
    <main></main>
    <footer></footer>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search