How to create a blended straight-line/curved section boundary using svg or css?

377 Views Asked by At

For my app, I'm creating a sign-up page with a nonstandard section boundary. You can see the effect I'm trying to achieve here: enter image description here It's not a simple arc -- it has two straight lines but also an arc in the middle.

From what I have read, the best way to achieve something like this is with an SVG. The problem is, that white area is going to have an image covering the entire thing. For demo purposes I'll use a light blue color. If you use a standard div with a background-image property, the white of the SVG isn't transparent, so you get this: enter image description here

Cue a couple hours of reading later on shape-outside and clip-path properties, and this is the latest approach I've tried (note I'm using React with styled-components):

const LeftContainer = styled(FlexContainer)`
  width: 55%;
  height: 100%;
  background-color: lightblue;
  z-index: 1;
  /* background: linear-gradient(360deg, #FFFFFF 24.95%, rgba(255, 255, 255, 0) 50.95%), url(${process.env.PUBLIC_URL}/TestImage.png); */
`;
const RightContainer = styled(FlexContainer)`
  width: 45%;
  height: 100%;
  /* background-color: ${colors.secondary600}; */
  float: left;
   /* background-image: url(${process.env.PUBLIC_URL}/SignUpBackground.svg); */
  background-repeat: no-repeat;
  background-size: cover;
  background-position: center;
  position: relative;
  z-index: 5;    
  clip-path: url(${process.env.PUBLIC_URL}/SignUpBackground.svg#sidebar);
`;
 

There's still a gap, but not only that, the SVG shape has the bottom third cut off: enter image description here

The only way I've been able to make the left container fill that extra space is by making the right container position: absolute, but this is going to cause issues with the login form I plan to add to the right side (and it doesn't seem to solve the issue of cutting off the bottom third of the svg).

Is there a way I can keep the right container in the flow of the page, show 100% of the svg, and ensure the left container is flush with the right container?

EDIT: Here is the SVG code:

<svg width="708" height="1056" viewBox="0 0 708 1056" fill="none" xmlns="http://www.w3.org/2000/svg">
<clipPath id="sidebar">
<path d="M144.5 0H799.5V1056H144.5L23.4254 775.169C0.402533 721.768 -5.09917 662.442 7.71089 605.718L144.5 0Z" fill="#16979A"/>
</clipPath>
</svg>

And here is the React component so far (early stage of build as you can see):

const SignUpPage = (props) => {
  return ( 
    <Container>
      <LeftContainer>
        {/* Site logo and marketing copy, promo bubbles to go here. This side should be the one to shrink */}
      </LeftContainer>
      <RightContainer flexDirection="column" justify="center">
        {/* Signup/Login form to go here */}
      </RightContainer>
    </Container>
   );
}

EDIT2: I've tried implementing the solutions from the answers below, but so far each one has at least one issue. Beginning with ccprog's suggestion, I was a little uncertain because the code didn't seem to match up with the description of the approach, but I attempted to implement it and came up with this result: (code first, then resulting image)

const Container = styled(FlexContainer)`  
    height: 523px;
    background-image: linear-gradient(360deg, #FFFFFF 24.95%, rgba(255, 255, 255, 0) 50.95%), url(${process.env.PUBLIC_URL}/SignUpImage.jpg);
    background-position: top right 282px;
    background-size: cover;
    background-repeat: no-repeat;
`;

const LeftContainer = styled(FlexContainer)`
    flex-grow: 1;
`
const RightContainer = styled(FlexContainer)`
    width: 354px;
    background-image: url('data:image/svg+xml,<svg viewBox="0 0 708 1056" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M144.5 0H799.5V1056H144.5L23.4254 775.169C0.402533 721.768 -5.09917 662.442 7.71089 605.718L144.5 0Z" fill="%2316979A"/></svg>');
    background-size: 100% 100%;
`

enter image description here

As you can see, this doesn't actually fill the full height of the screen, even on the right side, and the left side image is really cut off (the left side image is a problem in all of the solutions as you'll see).

Next, I tried implementing the 2nd of chrwahl's solutions. This one came a bit closer (the right side looks great) but the left side image has some issues:

const Container = styled(FlexContainer)`
  width: 100%;
  height: 100vh;
  background-image: url(${process.env.PUBLIC_URL}/SignUpBackground.svg),
  linear-gradient(360deg, #FFFFFF 24.95%, rgba(255, 255, 255, 0) 50.95%), url(${process.env.PUBLIC_URL}/SignUpImage.jpg);
  background-repeat: no-repeat;
  background-position: right, left;
  background-size: contain, cover;
  margin-bottom: 5px;
`;

enter image description here

I'm not sure if it comes through in this image, but the whole left side of the screen is blank here, so it seems the image is not starting from the left edge even though the position is specified as left. The issue is worse with background-size: 40%, 70%: enter image description here

2

There are 2 best solutions below

1
ccprog On

First a bit of terminology: let's call the width of the area covered by the SVG shape at top and bottom the "right side minimum", and the width covered in the middle the "right side maximum".

The way I understand your question, the right area a) has a constant width and b) should always show the complete SVG shape. From that follows that it must have a constant height and an aspect ratio of 708 : 1056. That makes it easy to compute the needed sizes, the most important being that the ratio between the right side minimum and the right side maximum is 564 : 708.

Now, I would suggest to solve you problem by moving the left-side image as a background-image to the outer container element, with a width that ensures it goes under the curved part, i. e. 100% minus 564px (or a fixed fraction). The left container element, containing the promo content gets a width of 100% minus 708px, and the right container a width of 708px (or a fixed fraction). (For simplicity, the containers get identified by a class name matching their component name)

.container {
    display: flex;
    flex-direction: row;
    justify-items: stretch;
    align-items: stretch;
    height: 523px;
    background-image: linear-gradient(360deg, white, red);
    background-position: top right 282px;
    background-size: cover;
    background-repeat: no-repeat;
}
.container-left {
    flex-grow: 1;
}
.container-right {
    width: 354px;
    background-image: url('data:image/svg+xml,<svg viewBox="0 0 708 1056" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M144.5 0H799.5V1056H144.5L23.4254 775.169C0.402533 721.768 -5.09917 662.442 7.71089 605.718L144.5 0Z" fill="%2316979A"/></svg>');
    background-size: 100% 100%;
}
.child {
  box-sizing: border-box;
  height: 100%;
  margin: 2px;
  border: 2px solid blue;
}
<div class="container">
  <div class="container-left">
    <div class="child"></div>
  </div>
  <div class="container-right">
    <div class="child"></div>
  </div>
</div>

You can use other px size values as long as the ratio between them is maintained.

And if you do not want to show the full SVG, but only make sure the curve on the left remains completely visible, add the following attribute to the root <svg> element:

preserveAspectRatio="xMinYMid slice"

When changing the aspect ratio of the right container, this will have the same effect as the CSS cover, plus the area of the viewBox is always ligned up with the left side. This will work only if the aspect ratio will move to a higher height in relation to the width. If the aspect ratio gains in width, equal parts of the curve will be cut of at the top and bottom - but the other choice would be that it isn't wide enough to cover the right side.

If you go that route, remember that you can only know the width of the area between maximum and minimum right side if you set a fixed height in advance. CSS just cannot use the height of an element to compute a width value.

2
chrwahl On

You can place both the image and the SVG as a background in CSS. Here are three different examples. In the first I have the same issue as you do. The second will always work, but part of the image is covered by the SVG and cut off. The third is a matter of testing for different sizes.

Now, my examples are not THE SOLUTION. Takeaway here is: You can have more background images in your CSS. Notice that the images are separated by a comma. The number of values in the background-position and -size must/can match the number of images. The configuration depends on the size, aspect ratio, the image quality and the purpose of the image. You need to figure out what matches your scenario.

The SVG in the example looks like this (notice the viewBox has been changed):

<?xml version="1.0" encoding="UTF-8"?>
<svg viewBox="0 0 798 1056" xmlns="http://www.w3.org/2000/svg">
  <path d="M144.5 0H799.5V1056H144.5L23.4254 775.169C0.402533 721.768 -5.09917 662.442 7.71089 605.718L144.5 0Z" fill="#16979A"/>
</svg>

.login {
  width: 400px;
  height: 200px;
  background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB2aWV3Qm94PSIwIDAgNzk4IDEwNTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGQ9Ik0xNDQuNSAwSDc5OS41VjEwNTZIMTQ0LjVMMjMuNDI1NCA3NzUuMTY5QzAuNDAyNTMzIDcyMS43NjggLTUuMDk5MTcgNjYyLjQ0MiA3LjcxMDg5IDYwNS43MThMMTQ0LjUgMFoiIGZpbGw9IiMxNjk3OUEiLz4KPC9zdmc+Cgo='),
  url('https://via.placeholder.com/800x600');
  background-repeat: no-repeat;
  background-position: right, left;
  margin-bottom: 5px;
}

.size1 {
  background-size: contain, contain;
}

.size2 {
  background-size: contain, cover;
}

.size3 {
  background-size: 40%, 70%;
}
<div class="login size1"></div>
<div class="login size2"></div>
<div class="login size3"></div>