Alan Fernandes
Alan Fernandes

Reputation: 33

Trying to morph an SVG in javascript

I'm trying to make a layout with some cards, which, when clicked, open to reveal more information, like you can see in this image. I managed to get most of the stuff working, but I can't get the little banner on the left to morph into it's open state. From what I've seen, the best way to go about this is using anime.js, but it doesn't work, for some reason. Here's the code:

let cardToggle = document.querySelectorAll('.card-toggle');

for (i = 0; i < cardToggle.length; i++) {
  cardToggle[i].addEventListener("click", function() {

    //Get the required elements
    let cardContent = this.nextElementSibling;
    let cardBackground = this.parentElement;
    let metaWrapper = this.querySelector('.spell-meta-wraper');
    let spellBanner = this.querySelector('.spell-banner');
    //spellBanner is the svg I'm trying to animate

    //Close
    if (cardContent.style.display === "block") {

      cardContent.style.display = "none";
      metaWrapper.style.display = "none";
      cardContent.style.height = "0px";
      cardBackground.style.height = "95px";

    //Open
    } else {

      cardContent.style.display = "block";
      metaWrapper.style.display = "block";
      cardContent.style.height = "410px";
      cardBackground.style.height = "505px";
      anime({
        targets: spellBanner,
        points: [
          { value: 'M0,0h47v156l-23.5-21.14L0,156V0z' }
        ],
        easing: 'easeOutQuad',
        duration: 2000,
        loop: true
      });
    }
  });
}

It might be useful to point out that I'm trying to do this in wordpress, and the cards I'm trying to edit come from a while loop.

I have tried changing the "points" attribute to "d", but that didn't work. I also tested the "spellBanner" variable to see if it's referencing the right element, and it does. I also checked the page's source to see if wordpress is loading the anime.min.js script, and it is.

I have no idea what I'm doing wrong or what alternatives I have for lving this problem.

Upvotes: 2

Views: 1560

Answers (3)

Exodus 4D
Exodus 4D

Reputation: 2822

Still a lot code for a tiny feature.

I suggest SVG <path> + CSS transition for morphing the path (d attribute).

(To avoid dublicate path declaration, you could use CSS vars or SASS vars.)

path {
  d: path("M0,0h50v140l-25-20L0,140V0z");
  transition: d .18s linear;
}

path:hover {
  d: path("M0,0h50v140l-25 20L0,140V0z");
}
<svg width="50" height="160"><path d=""/></svg>

Upvotes: 6

Alan Fernandes
Alan Fernandes

Reputation: 33

Ok, after a bunch more failed attempts, I decided to try something a lot simpler: doing it with CSS. Basically, instead of an svg, I used two divs, togheter, one in the shape of a rectangle, and one in the shape of a triangle.

That being said, this is probably not the ideal solution, and I'll probably implement enxaneta's solution

let spellBanner = document.querySelector('.card-banner');
let spellBannerTip = document.querySelector('.card-banner-tip');

spellBanner.addEventListener("click", function() {
  spellBanner.style.height = "135px";
  
  spellBannerTip.style.top = "133px";
  spellBannerTip.style.borderBottom = "10px solid white";
  spellBannerTip.style.borderTop = "0";
});
.card-banner {
  
    position: absolute;

    background-color: black;
    width: 47px;
    height: 75px;
    transition: all 0.2s ease-in-out;
}

.card-banner-tip {

    position: absolute;
    top: 83px;

    width: 0; 
    height: 0; 
    border-left: 23.5px solid transparent;
    border-right: 23.5px solid transparent;  
    border-top: 10px solid rgb(0, 0, 0);

    transition: all 0.2s ease-in-out;

}
<div class = "card-banner"></div>
<div class = "card-banner-tip"></div>

Upvotes: 0

enxaneta
enxaneta

Reputation: 33044

You don't need to use external libraries for this. In fact the animation you need is very simple since the d attribute will change from:

M0,0H47V156L23.5, 134.86L0,156V0z

to:

M0,0H47V156L23.5, 177.14L0,156V0z

The only thing changing here is the value of the y of one point. In order to get the path morphing you need to animate the y coordinate from 134.86 to 177.14. You can say that the actual value of the y coordinate is 134.86 and the target value is 177.14.

let rid = null;
// the value of the y coordinate of the point is changing between 134.86 and 177.14
let memory = [134.86, 177.14];
let target = memory[0]; 
let value = memory[1];

//a function that updates that value
function updateValue() {
    let dist = target - value;
    let vel = dist/10;
    value += vel;
    // stopping the animation when the distance between the target and the value is very small
    if (Math.abs(dist) < .01) {
    if(rid){window.cancelAnimationFrame(rid);
    rid = null;
    }
  }  
}

function updatePath() {  
  morphingPath.setAttributeNS(null, "d",`M0,0H47V156L23.5, ${value}L0,156V0z`);
}

function Frame() {
  rid = window.requestAnimationFrame(Frame);
  updateValue();
  updatePath();
}

window.addEventListener(
  "load",
   updatePath,
   false
);

svg.addEventListener(//animate the path on mousedown
  "mousedown",
  function() {
    if (rid) {
      window.cancelAnimationFrame(rid);
      rid = null;
    }
    
    memory.reverse();
    target = memory[1];
    Frame();
  },
  false
);
svg{width:50px;}
<svg id="svg" viewBox="0 0 46 180"><path id="morphingPath" d="M0,0h47v156L23.5,134.86L0,156V0z" /></svg>

For more details please read the first part of this codepen post: Morphing in SVG - first steps

Upvotes: 2

Related Questions