Sophia
Sophia

Reputation: 31

Please help me with css tracing/drawing effect animtation

I am trying to make a flowing/drawing animation of a rounded line. The solution I have works, but is not my favorite. My goal is to have this line flowing down and then to the right when the page is opened.

I've tried recreating the environment this animation will be in so that anyone who can help will have a better idea.

The solution I made has an unflattering glitch at the rounded corner. I've tried looked into other solution with p5.js and svg animtions, but I am not sure what would be best. I think its the corners fault for the complexity but that is the shape I need, unfortunately.

body {
  background-color: black;
  color: white;
  overflow: hidden;
}

.layout {
  display: flex;
  flex-direction: column;
  height: 100vh;
  width: 100vw;
  position: relative;
}

.l-shape {
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: flex-end;
  justify-content: flex-start;
  pointer-events: none;
  z-index: -999;

}

.l-line {
  position: absolute !important;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 100%;
  border-left: 20px solid transparent;
  border-bottom: 20px solid transparent;
  border-bottom-left-radius: 100px;
  position: relative;
  overflow: hidden;
  z-index: 1;
}

.l-line::after {
  content: "";
  position: absolute !important;
  top: -30px;
  left: 0;
  width: 0;
  height: 0;
  border-left: 20px solid var(--active-line-color);
  border-bottom: 20px solid transparent;
  border-bottom-left-radius: 100px;
  animation: trace-border 1s ease-in-out forwards;
  z-index: 2;
}


@keyframes trace-border {
  0% {
    height: 0;
    width: 0;
    border-bottom-color: transparent;
  }

  50% {
    height: 100%;
    width: 0;
    border-bottom-color: transparent;
  }

  100% {
    height: 100%;
    width: 100%;
    border-bottom-color: var(--active-line-color);
  }
}

.layout[data-active-section="red"] {
  --active-line-color: #D85140;
}

.layout[data-active-section="blue"] {
  --active-line-color: #3555B4;
}

.layout[data-active-section="white"] {
  --active-line-color: #E6E6E6;
}

.layout[data-active-section="yellow"] {
  --active-line-color: #F6B90B;
}

.navbar {
  display: flex;
  justify-content: center;
  gap: 20px;
  padding: 20px;
}

.navbar button {
  background: none;
  border: 1px solid white;
  color: white;
  padding: 10px 20px;
}
<body>
  <div class="layout" data-active-section="red">
    <div class="l-shape">
      <div class="l-line"></div>
    </div>

    <nav class="navbar">
      <button onclick="changeSection('red')">red</button>
      <button onclick="changeSection('blue')">blue</button>
      <button onclick="changeSection('white')">white</button>
      <button onclick="changeSection('yellow')">yellow</button>
    </nav>
  </div>

  <script>
    function changeSection(section) {
      const layout = document.querySelector(".layout");
      const line = document.querySelector(".l-line");

      layout.setAttribute("data-active-section", section);

      const newLine = line.cloneNode(true);
      line.parentNode.replaceChild(newLine, line);
    }
  </script>
</body>

Upvotes: 3

Views: 97

Answers (2)

SVG SMIL animation might be easier and shorter.

  • Use pathLength=100 so all (animate) units always calculate (any) path at 100 total length

  • Wrapped in a <svg-liner> Web Component, because... well... Web Components are cool

  • Use https://yqnn.github.io/svg-path-editor/# to edit your d-path

<script>
  customElements.define("svg-liner", class extends HTMLElement {
    connectedCallback() {
      let color = this.getAttribute("color");
      this.innerHTML = `<div style="display:inline-block;width:190px;zoom:.9">
<button>red</button> <button>blue</button> <button>white</button> <button>yellow</button><br>
 <svg viewBox="0 0 100 100" style="background:lightgreen">
  <path fill="none" stroke="${color}" pathLength="100" 
                    stroke-width="10" d="M10,8c0,79-2,77,21,77s45,0,62,0">
   <animate attributeName="stroke-dasharray" from="0,100" to="100,0" 
            dur=".5s" repeatCount="once" />
  </path>
</svg></div>`;
      this.onclick = evt => {
        let {tagName , textContent } = evt.target;
        if (tagName == "BUTTON") {
          this.querySelector('path').style.stroke = textContent;
          this.querySelector('animate').beginElement();
        }
      }
    }
  });
</script>
<svg-liner color="red"></svg-liner>
<svg-liner color="yellow"></svg-liner>
<svg-liner color="blue"></svg-liner>

JSWC

Upvotes: 2

Łukasz Daniel Mastalerz
Łukasz Daniel Mastalerz

Reputation: 2275

I think you don't have to use any library for that, you can use simple [choosen] svg shape and animate it with stroke-dasharray and stroke-dashoffset attributes..

const cPath = document.getElementById("cPath");
const animateBtn = document.getElementById("animateBtn");

const animation = cPath.animate(
  [{ strokeDashoffset: 157 }, { strokeDashoffset: 0 }],
  {
    duration: 2000,
    easing: "ease",
    fill: "forwards"
  }
);
animation.cancel();
animateBtn.addEventListener("click", function () {
  animation.cancel();
  animation.play();
});
#cPath {
  stroke: #FF0000;
  stroke-width: 7;
  fill: none;
  stroke-dasharray: 157;
  stroke-dashoffset: 157;
}
<button id="animateBtn">Draw</button>
<svg width="300" height="200">
  <path id="cPath" d="m143.89029,99c0,78.8543 -2.08511,77 21.10971,77c23.19483,0 45.08445,0 62.08375,0" />
</svg>

My goal is to have this line flowing down and then to the right when the page is opened.

Just add script

<script>
  window.addEventListener("load", (event) => {
    const cPath = document.getElementById("cPath");
    cPath.animate(
      [{
        strokeDashoffset: 157
      }, {
        strokeDashoffset: 0
      }], {
        duration: 2000,
        easing: "ease",
        fill: "forwards"
      }
    );
  });
</script>

Small advice, probably you would like add some sequences of animations, like in modern websites... good idea will be use some animations library eg. GSAP. And don't use, in your scenario, DOMContentLoaded, because this will break whole experience, what [probably] you looking for.

Upvotes: 1

Related Questions