Search code examples
htmlcsssvgresponsive-design

Non-proportionally scale complex SVG paths


I'm trying to find the best method for building unique border shapes in HTML/CSS, for example:

example of border shape I'm attempting to acheive

The goal is to be able to have this responsive so width/height can change to anything, but have a fixed size for the parts of the shape that are marked red, like the cut corners and notch on the left side.

I attempted to use clip-path with an inner div to create a border, but this falls apart with more complex shapes, like the notch on the left side. clip-path also doesn't allow for rounding without using an SVG filter that introduces other artifacts to more complex shapes, and doesn't allow for precise control of the corner radius.


Solution

  • The cleanest reproductible way is to dive into how the path are drawn: read this first Paths.

    • You need to separate each commands, to identify each part that represent visual blocks of your svg;
    • From there you can dynamically compute for each block there definitive positions taking into consideration two variables width and height, example below.

    const { useRef, useState, useEffect } = React;
    
    function App() {
      const boxRef = useRef(null)
      const [w, setW] = useState(200)
      const [h, setH] = useState(200)
      const [path, setPath] = useState("")
    
      const computeX = x => {
        const cur = w / 2 - (520 / 2 - x)
        return cur
      }
      const computeXend = x => {
        const cur = w - 520 + x
        return cur
      }
      const computeY = y => {
        const cur = h - (679 - y)
        return cur
      }
      const resize = () => {
    
          setPath(
            `M0 ${computeY(664.576)}C0 ${computeY(667.098)} 0.953274 ${computeY(
              669.528
            )} 2.66879 ${computeY(671.377)}L6.77293 ${computeY(
              675.801
            )}C8.66521 ${computeY(677.841)} 11.3218 ${computeY(
              679
            )} 14.1041 ${computeY(679)}H${computeX(215.778)}C` +
              `${computeX(218.323)} ${computeY(679)} ${computeX(
                220.773
              )} ${computeY(678.029)} ${computeX(222.628)} ${computeY(
                676.286
              )}L${computeX(227.49)} ${computeY(671.714)}C${computeX(
                229.345
              )} ${computeY(669.971)} ${computeX(231.795)} ${computeY(
                669
              )} ${computeX(234.34)} ${computeY(669)}H${computeX(
                285.78
              )}C${computeX(288.478)} ${computeY(669)} ${computeX(
                291.061
              )} ${computeY(670.09)} ${computeX(292.944)} ${computeY(
                672.022
              )}L${computeX(296.797)} ${computeY(675.978)}C` +
              `${computeX(298.679)} ${computeY(677.91)} ${computeX(
                301.262
              )} ${computeY(679)} ${computeX(303.96)} ${computeY(
                679
              )}H${computeXend(505.896)}C${computeXend(508.678)} ${computeY(
                679
              )} ${computeXend(511.334)} ${computeY(677.841)} ${computeXend(
                513.227
              )} ${computeY(675.801)}L${computeXend(517.331)} ${computeY(
                671.377
              )}C${computeXend(519.047)} ${computeY(669.528)} ${computeXend(
                520
              )} ${computeY(667.098)} ${computeXend(520)} ${computeY(
                664.576
              )}V14.4242C` +
              `${computeXend(520)} 11.9017 ${computeXend(
                519.047
              )} 9.47246 ${computeXend(517.331)} 7.62318L${computeXend(
                513.227
              )} 3.19905C${computeXend(511.335)} 1.15925 ${computeXend(
                508.678
              )} 4.3237e-05 ${computeXend(505.896)} 4.29938e-05L${computeX(
                304.222
              )} 2.53629e-05C` +
              `${computeX(301.677)} 2.51404e-05 ${computeX(
                299.227
              )} 0.970698 ${computeX(297.372)} 2.71423L${computeX(
                292.51
              )} 7.28582C${computeX(290.655)} 9.02935 ${computeX(
                288.206
              )} 10 ${computeX(285.66)} 10L${computeX(234.22)} 10C${computeX(
                231.522
              )} 10 ${computeX(228.939)} 8.91008 ${computeX(
                227.056
              )} 6.97768L${computeX(223.203)} 3.02236C` +
              `${computeX(221.321)} 1.08995 ${computeX(
                218.738
              )} 1.78896e-05 ${computeX(
                216.04
              )} 1.76538e-05L14.1044 0C11.3221 -2.43238e-07 8.66562 1.15915 6.77334 3.19888L2.66891 7.62311C` +
              `0.953336 9.47238 2.80713e-05 11.9017 2.78508e-05 14.4242L0 ${computeY(
                664.576
              )}Z`
          )
        
      }
      useEffect(() => {
        resize();
      }, [])
      return ([
        <input
          type="number"
          value={w}
          onChange={
            e => {
              setW(e.target.value)
              setH(e.target.value)
              resize()
            }
          }
        />,
        <svg
          className={"w-full h-full absolute left-0 bot-0"}
          width={w}
          height={h}
          viewBox={`0 0 ${w} ${h}`}
          fill="none"
          xmlns="http://www.w3.org/2000/svg"
          overflow={"visible"}
        >
          <path
            d={path}
            fill="#ffeeef"
            stroke={"black"}
          />
        </svg>
      ])
    }
    
    ReactDOM.render(<App />,
    document.getElementById("root"))
    #root {
      width: 100vw;
      height: 100vh;
      display: flex;
      align-items: center;
      justify-content: center;
    }
    
    input {
      position: absolute;
      top: 4px;
      left: 4px;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
    
    <div id="root"></div>