Search code examples
htmlcsssvgclip-pathstepper

How to Maintain the angle without the number of step in stepper : HTML, CSS


I created stepper using following code.

But it seems to be more complex. So I want to create simpler that my code. For example, don't use ::before pseudo selector, and use only one outline element.

It must maintain the angle without relationship with the number of steps, and it is the most important the rouned corner.

If you have any solution, please share your idea.

Thank you.

.stepper1 {
    --stepper1-height:75px;
    --stepper1-strok: 1px;
    --stepper1-deapth: 1.5rem;
    /* outer-outline depth */
    --stepper1-gap: 0.3rem;
    /* outer-outline thickness, gap */
    width: 100%;
    height: var(--stepper1-height);
    display: flex;
    margin-right: var(--stepper1-deapth);
}

.stepper1-step {
    /* margin-right: calc(var(--stepper1-deapth) * -1 + var(--stepper1-gap)); */
    position: relative;
    flex: 1 1 0%;
    clip-path: polygon(0% 0%,
            calc(100% - var(--stepper1-deapth)) 0%,
            100% 50%,
            calc(100% - var(--stepper1-deapth)) 100%,
            0% 100%,
            var(--stepper1-deapth) 50%);

}

.outer-outline {
    position: relative;
    width: 100%;
    height: 100%;
    filter: url(#round);
    color: gray;
}

.outer-outline::before {
    content: "";
    display: block;
    padding-top: var(--stepper1-height);
    background: currentColor;
    clip-path: polygon(0% 0%,
            calc(100% - var(--stepper1-deapth)) 0%,
            100% 50%,
            calc(100% - var(--stepper1-deapth)) 100%,
            0% 100%,
            var(--stepper1-deapth) 50%);
}

.inner-outline {
    position: absolute;
    top: var(--stepper1-strok);
    left: calc(var(--stepper1-strok) * 2);
    width: calc(100% - var(--stepper1-strok) * 3.5);
    height: calc(100% - var(--stepper1-strok) * 2);
    filter: url(#round);
    color: white;
}

.inner-outline::before  {
    content: "";
    display: block;
    padding-top: calc(var(--stepper1-height) - var(--stepper1-strok) * 2);
    background: currentColor;
    clip-path: polygon(0% 0%,
            calc(100% - var(--stepper1-deapth)) 0%,
            100% 50%,
            calc(100% - var(--stepper1-deapth)) 100%,
            0% 100%,
            var(--stepper1-deapth) 50%);
}
.stepper1-step .content {
    position: absolute;
    top: 0;
    justify-content: flex-start;
    align-content: center;
    left: var(--stepper1-deapth);
    width: calc(100% - var(--stepper1-deapth) * 2);
    height: var(--stepper1-height);
    display: grid;
    padding-left: 1rem;
}
.title, .description { 
    margin: 0;
 }
<div class="stepper1 mt-12 px-10">
    <div class="stepper1-step">
        <div class="outer-outline"></div>
        <div class="inner-outline text-white"></div>
        <div class="content">
            <p class="title">Step 1</p>
            <p class="description">description....</p>
        </div>
    </div>
    <div class="stepper1-step">
        <div class="outer-outline text-blue-400"></div>
        <div class="inner-outline text-blue-50"></div>
        <div class="content">
            <p class="title">Step 2</p>
            <p class="description">description....</p>
        </div>
    </div>
    <div class="stepper1-step">
        <div class="outer-outline text-gray-500"></div>
        <div class="inner-outline text-white"></div>
        <div class="content">
            <p class="title">Step 3</p>
            <p class="description">description....</p>
        </div>
    </div>
</div>
<svg style="visibility: hidden; position: absolute;" width="0" height="0" xmlns="http://www.w3.org/2000/svg"
    version="1.1">
    <defs>
        <filter id="round">
            <feGaussianBlur in="SourceGraphic" stdDeviation="4" result="blur" />
            <feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 19 -9"
                result="goo" />
            <feComposite in="SourceGraphic" in2="goo" operator="atop" />
        </filter>
    </defs>
</svg>


Solution

  • If you would like to use SVG one option it to use a path. In the example I have the SVG first and then for the "stepper", the same SVG is used as a background for the list items.

    Example 1

    I use the attribute vector-effect="non-scaling-stroke" (and preserveAspectRatio="none") to maintain the width of the border when the content of the list item changes. This is not a perfect solution because -- when scaled a lot -- there will be a "empty space" around the shape now that the border is not getting wider. But if you are not writing an essay in each, I guess it is ok.

    ul.stepper {
      display: flex;
      list-style: none;
      padding: 0;
      margin: 0;
      gap: .2em;
    }
    
    ul.stepper li {
      min-height: 6em;
      min-width: 5em;
      padding: 0 2em;
      background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 104 104" width="100" preserveAspectRatio="none"><path transform="translate(2 2)" d="M8 0Q-2 0 1 10L13 45Q15 50 13 55L1 90Q-2 100 8 100H73Q78 100 83 90L98 55Q100 50 98 45L83 10Q79 0 73 0Z" fill="orange" stroke="black" stroke-width="4" vector-effect="non-scaling-stroke" /></svg>');
      background-repeat: no-repeat;
      background-position: center;
      background-size: 100% 100%;
      display: flex;
      flex-direction: column;
      justify-content: center;
    }
    
    ul.stepper li p {
      margin: 0;
    }
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 104 104"
      width="100" preserveAspectRatio="none">
      <path transform="translate(2 2)"
        d="M8 0Q-2 0 1 10L13 45Q15 50 13 55L1 90Q-2 100 8 100
        H73Q78 100 83 90L98 55Q100 50 98 45L83 10Q79 0 73 0Z"
        fill="orange" stroke="black" stroke-width="4"
        vector-effect="non-scaling-stroke" />
    </svg>
    
    <ul class="stepper">
      <li>
        <p class="title">Step 1</p>
        <p class="description">description....</p>
      </li>
      <li>
        <p class="title">Step 2</p>
        <p class="description">description....</p>
      </li>
    </ul>

    Example 2

    I use the attribute vector-effect="non-scaling-stroke" to maintain the width of the border when the content of the list item changes. To maintain the aspect ratio of the SVG there are two background SVGs combined into a Sliding Doors technique (hot stuff 20 years ago). YOu can make different tweaks to this, using CSS Flexbox -- you can see that I commented out align-items: flex-start; that controls the height of the li elements. There is a white rectangle in the right SVG image to cover the left SVG image -- that is a bit annoying, but necessary. It needs the same color as the background.

    svg {
      height: 100px;
    }
    
    ul.stepper {
      display: flex;
      list-style: none;
      padding: 0;
      margin: 0;
      gap: .2em;
      /*align-items: flex-start;*/
    }
    
    ul.stepper li {
      min-height: 4em;
      min-width: 5em;
      padding: 1em 2em;
      background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 104"><rect x="2" y="-1" width="31" height="106" fill="white" /><path transform="translate(0 2)" d="M 0 100 H 2 Q 7 100 12 90 L 27 55 Q 29 50 27 45 L 12 10 Q 8 0 2 0 H 0" fill="orange" stroke="black" stroke-width="4" vector-effect="non-scaling-stroke" /></svg>'), url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 504 104"><path transform="translate(2 2)" d="M 500 0 H 8 Q -2 0 1 10 L 13 45 Q 15 50 13 55 L 1 90 Q -2 100 8 100 H 500" fill="orange" stroke="black" stroke-width="4" vector-effect="non-scaling-stroke" /></svg>');
      background-repeat: no-repeat, no-repeat;
      background-position: right top, left top;
      background-size: contain, cover;
      display: flex;
      flex-direction: column;
      justify-content: center;
    }
    
    ul.stepper li p {
      margin: 0;
    }
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 504 104">
      <path transform="translate(2 2)"
        d="M 500 0 H 8 Q -2 0 1 10 L 13 45 Q 15 50 13 55 L 1 90
        Q -2 100 8 100 H 500" fill="orange" stroke="black" stroke-width="4"
        vector-effect="non-scaling-stroke" />
    </svg>
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 104">
      <rect x="2" y="-1" width="31" height="106" fill="white" />
      <path transform="translate(0 2)"
        d="M 0 100 H 2 Q 7 100 12 90 L 27 55 Q 29 50 27 45
        L 12 10 Q 8 0 2 0 H 0" fill="orange" stroke="black" stroke-width="4"
        vector-effect="non-scaling-stroke" />
    </svg>
    
    <ul class="stepper">
      <li>
        <p class="title">Step 1</p>
        <p class="description">long description....</p>
      </li>
      <li>
        <p class="title">Step 2</p>
        <p class="description">description....<br>
        description....<br>
        description....<br>
        description....<br>
        description....<br></p>
      </li>
    </ul>

    I use SvgPathEditor to create the path.