skip to Main Content

I came across https://flecto.io(use a vpn if the site doesn’t open) and the site caught my attention. I want to replicate the animation in the hero section. To be more precise, that is the part where the green rectangle turned into a cross-like shape.

Starting shape
enter image description here

Final Shape (I removed unwanted distractions)
enter image description here

I understand that this could be done with the use of gsap morphSVG plugin. But I am finding the use of that plugin for this purpose to be difficult. Here is a link to the demo I am working on Here.

HTML

<div class="container">
    <svg viewBox="0 0 1900 1500" fill="none">
      <g>
        <path id="rectangle" d="M0 0H1889V157V758V915H0V758V178.5V0Z" fill="black"/>
       
         <path id="final-stage" class="hide" d="M505.5 125.5H22.5H19C9.05887 125.5 1 133.559 1 143.5V774C1 783.941 9.05887 792 19 792H198.5C208.441 792 216.5 800.059 216.5 810V898C216.5 907.941 224.559 916 234.5 916H1426.5C1436.44 916 1444.5 907.941 1444.5 898V809.5C1444.5 799.559 1452.56 791.5 1462.5 791.5H1872C1881.94 791.5 1890 783.441 1890 773.5V144C1890 134.059 1881.94 126 1872 126H1385.5C1375.56 126 1367.5 117.941 1367.5 108V19C1367.5 9.05887 1359.44 1 1349.5 1H541.5C531.559 1 523.5 9.05888 523.5 19V107.5C523.5 117.441 515.441 125.5 505.5 125.5Z" fill="black" stroke="black" />
      </g>
    </svg>
  </div>

CSS

.container {
  width: 800px;
  height: 800px;
  display: flex;
  justify-content: center;
  align-items: center;
}
svg {
  width: 100%;
  height: 100%;
}

.hide {
  visibility: hidden;
}

Javascript (I am using gsap plugin, and gsap morphSVG plugin on codepen)

gsap.registerPlugin(MorphSVGPlugin);

// MorphSVGPlugin.defaultType = "rotational";

var tl = gsap.timeline();
const rectangle = document.getElementById("rectangle");

  tl.to(rectangle, { duration: 1, morphSVG: {shape: "#final-stage", shapeIndex: -2 }});

This was my result https://streamable.com/mnh46o (this content might not be available in the future)

The thing is that after inspecting the site, the creators didn’t morph a rectangle into a cross. They broke down their shape into smaller forms, consisting of two frames with different builds.

enter image description here

Notice that the first frame is a solid shape that has a cut out of a cross, while the second is a combination of several shapes that form the cut out of a cross. Why did they build the hero section this way? I am yet to figure it out. But I believe that this method is what makes their animation smoother than my method of morphing an SVG into a cross.

So, what do you all think? How can I replicate this design? What is the best method to do this?

2

Answers


  1. You need two paths; the starting and ending path. The animation can either be done using CSS animations or with SVG animation with SMIL.

    In you code there are 27 commands in the ending path, so you should have the same number of commands in the starting path.

    Here, I have created a "crude" version where the starting path is just a square, the commands are relative, and most of the commands just have zeros. In a production ready version you would match all the commands so that they move in the right way and also maintain the rounded corners.

    path {
      d: path('m 1 1 h 0 h 0 c 0 0 0 0 0 0 v 915 c 0 0 0 0 0 0 h 1889 c 0 0 0 0 0 0 v 0 c 0 0 0 0 0 0 h 0 c 0 0 0 0 0 0 v 0 c 0 0 0 0 0 0 h 0 c 0 0 0 0 0 0 v -915 c 0 0 0 0 0 0 h 0 c 0 0 0 0 0 0 v 0 c 0 0 0 0 0 0 h -1889 c 0 0 0 0 0 0 v 0 c 0 0 0 0 0 0 z');
      animation-duration: 3s;
      animation-name: anim;
      animation-fill-mode: forwards;
    }
    
    @keyframes anim {
      from {
        d: path('m 1 1 h 0 h 0 c 0 0 0 0 0 0 v 915 c 0 0 0 0 0 0 h 1889 c 0 0 0 0 0 0 v 0 c 0 0 0 0 0 0 h 0 c 0 0 0 0 0 0 v 0 c 0 0 0 0 0 0 h 0 c 0 0 0 0 0 0 v -915 c 0 0 0 0 0 0 h 0 c 0 0 0 0 0 0 v 0 c 0 0 0 0 0 0 h -1889 c 0 0 0 0 0 0 v 0 c 0 0 0 0 0 0 z');
      }
      to {
        d: path('M505.5 125.5H22.5H19C9.05887 125.5 1 133.559 1 143.5V774C1 783.941 9.05887 792 19 792H198.5C208.441 792 216.5 800.059 216.5 810V898C216.5 907.941 224.559 916 234.5 916H1426.5C1436.44 916 1444.5 907.941 1444.5 898V809.5C1444.5 799.559 1452.56 791.5 1462.5 791.5H1872C1881.94 791.5 1890 783.441 1890 773.5V144C1890 134.059 1881.94 126 1872 126H1385.5C1375.56 126 1367.5 117.941 1367.5 108V19C1367.5 9.05887 1359.44 1 1349.5 1H541.5C531.559 1 523.5 9.05888 523.5 19V107.5C523.5 117.441 515.441 125.5 505.5 125.5Z');
      }
    }
    <svg viewBox="0 0 1900 1500">
      <path fill="black" stroke="black"/>
    </svg>
    Login or Signup to reply.
  2. I know that this is my second answer on this question, but I reconsidered the approach after looking more closely at the website that you refer to.

    This example is relying on the CSS property mask that can be used for masking an element. The great thing about CSS mask is that it has different properties for size, position etc. So, in the example I have a CSS animation on the mask position and a SVG mask-image that has a path. The basic SVG (top left corner) looks like this:

    <?xml version="1.0" encoding="UTF-8"?>
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 100">
      <path d="m 200 0 c -8 0 -10 2 -10 10 v 20 c 0 8 -2 10 -10 10
        h -170 c -8 0 -10 2 -10 10 v 50 h 400 v -100 z" fill="black"/>
    </svg>
    

    And here in a snippet:

    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 100">
      <path d="m 200 0 c -8 0 -10 2 -10 10 v 20 c 0 8 -2 10 -10 10
        h -170 c -8 0 -10 2 -10 10 v 50 h 400 v -100 z" fill="black"/>
    </svg>

    The full example looks like this:

    body {
      height: 200vh;
      margin: 0;
    }
    
    section {
      display: grid;
      grid-template-rows: 50px auto 50px;
      grid-template-columns: 200px auto 200px;
      height: calc(100vh - 1em);
      padding: .5em;
    }
    
    .cell {
      padding: 10px;
    }
    
    .topleft {
      grid-column: 1;
      grid-row: 1;
    }
    
    .topmiddle {
      background-color: SeaGreen;
      position: relative;
      grid-column: 2;
      grid-row: 1;
    }
    
    .topright {
      grid-column: 3;
      grid-row: 1;
    }
    
    .topmiddle:before {
      background-color: SeaGreen;
      width: 200px;
      height: 50px;
      display: block;
      position: absolute;
      top: 0;
      left: -200px;
      content: "";
      mask-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0MDAgMTAwIj4KICA8cGF0aCBkPSJtIDIwMCAwIGMgLTggMCAtMTAgMiAtMTAgMTAgdiAyMCBjIDAgOCAtMiAxMCAtMTAgMTAgaCAtMTcwIGMgLTggMCAtMTAgMiAtMTAgMTAgdiA1MCBoIDQwMCB2IC0xMDAgeiIgZmlsbD0iYmxhY2siLz4KPC9zdmc+');
      mask-size: 400px;
      mask-repeat: no-repeat;
      mask-position: -200px -50px;
      animation-duration: 1s;
      animation-name: topleft;
      animation-fill-mode: forwards;
      animation-delay: 1s;
      border-radius: 10px 0 0 0;
    }
    
    .topmiddle:after {
      background-color: SeaGreen;
      width: 200px;
      height: 50px;
      display: block;
      position: absolute;
      top: 0;
      right: -200px;
      content: "";
      mask-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0MDAgMTAwIj4KICA8cGF0aCBkPSJtIDIwMCAwIGMgOCAwIDEwIDIgMTAgMTAgdiAyMCBjIDAgOCAyIDEwIDEwIDEwIGggMTcwIGMgOCAwIDEwIDIgMTAgMTAgdiA1MCBoIC00MDAgdiAtMTAwIHoiIGZpbGw9ImJsYWNrIi8+Cjwvc3ZnPg==');
      mask-size: 400px;
      mask-repeat: no-repeat;
      mask-position: 0px -50px;
      animation-duration: 1s;
      animation-name: topright;
      animation-fill-mode: forwards;
      animation-delay: 1s;
      border-radius: 0 10px 0 0;
    }
    
    @keyframes topleft {
      from {
        mask-position: -200px -50px;
      }
      to {
        mask-position: 0px 0px;
      }
    }
    
    @keyframes topright {
      from {
        mask-position: 0px -50px;
      }
      to {
        mask-position: -200px 0px;
      }
    }
    
    .fullmiddle {
      grid-column: 1 / span 3;
      grid-row: 2;
      background-color: SeaGreen;
    }
    
    .bottomleft {
      grid-column: 1;
      grid-row: 3;
    }
    
    .bottommiddle {
      background-color: SeaGreen;
      position: relative;
      grid-column: 2;
      grid-row: 3;
    }
    
    .bottomright {
      grid-column: 3;
      grid-row: 3;
    }
    
    .bottommiddle:before {
      background-color: SeaGreen;
      width: 200px;
      height: 50px;
      display: block;
      position: absolute;
      top: 0;
      left: -200px;
      content: "";
      mask-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0MDAgMTAwIj4KICA8cGF0aCBkPSJtIDIwMCAxMDAgYyAtOCAwIC0xMCAtMiAtMTAgLTEwIHYgLTIwIGMgMCAtOCAtMiAtMTAgLTEwIC0xMCBoIC0xNzAgYyAtOCAwIC0xMCAtMiAtMTAgLTEwIHYgLTUwIGggNDAwIHYgMTAwIHoiIGZpbGw9ImJsYWNrIi8+Cjwvc3ZnPg==');
      mask-size: 400px;
      mask-repeat: no-repeat;
      mask-position: -200px -50px;
      animation-duration: 1s;
      animation-name: bottomleft;
      animation-fill-mode: forwards;
      animation-delay: 1s;
      border-radius: 0 0 0 10px;
    }
    
    .bottommiddle:after {
      background-color: SeaGreen;
      width: 200px;
      height: 50px;
      display: block;
      position: absolute;
      top: 0;
      right: -200px;
      content: "";
      mask-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0MDAgMTAwIj4KICA8cGF0aCBkPSJtIDIwMCAxMDAgYyA4IDAgMTAgLTIgMTAgLTEwIHYgLTIwIGMgMCAtOCAyIC0xMCAxMCAtMTAgaCAxNzAgYyA4IDAgMTAgLTIgMTAgLTEwIHYgLTUwIGggLTQwMCB2IDEwMCB6IiBmaWxsPSJibGFjayIvPgo8L3N2Zz4=');
      mask-size: 400px;
      mask-repeat: no-repeat;
      mask-position: 0px 0px;
      animation-duration: 1s;
      animation-name: bottomright;
      animation-fill-mode: forwards;
      animation-delay: 1s;
      border-radius: 0 0 10px 0;
    }
    
    @keyframes bottomleft {
      from {
        mask-position: -200px -50px;
      }
      to {
        mask-position: 0px -50px;
      }
    }
    
    @keyframes bottomright {
      from {
        mask-position: 0px 0px;
      }
      to {
        mask-position: -200px -50px;
      }
    }
    <section>
      <div class="cell topleft">topleft</div>
      <div class="cell topright">topright</div>
      <div class="cell topmiddle">topmiddle</div>
      <div class="cell fullmiddle">
        <p>fullmiddle</p>
      </div>
      <div class="cell bottomleft">bottomleft</div>
      <div class="cell bottomright">bottomright</div>
      <div class="cell bottommiddle">bottommiddle</div>
    </section>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search