juliangarnier / anime

JavaScript animation engine
https://animejs.com
MIT License
50.45k stars 3.69k forks source link

setDashoffset and vector-effect="non-scaling-stroke" miscalculation #793

Open davidwebca opened 2 years ago

davidwebca commented 2 years ago

Describe the bug This is a similar issue that was closed by the user without resolving the real issue in #428.

When an SVG is embedded in a page and scaled up, but their shapes have the attribute vector-effect="non-scaling-stroke", the length gets miscalculated by setDashoffset.

Here's a solution on how to solve this on Stackoverflow. https://stackoverflow.com/a/66928982/462943

Basically, it would mean that even paths shouldn't use getTotalLength in lib/anime.js:545, but instead use something like this:

function getTotalLength(el) {
  // Calculate Ratio of rendered size over real size over its definitive bounding box
  let ratio = 1;
  if(el.getBoundingClientRect().width) {
      // Lines can have width of zero
      ratio = el.getBoundingClientRect().width/el.getBBox().width;
  }else if(el.getBoundingClientRect().height) {
      ratio = el.getBoundingClientRect().height/el.getBBox().height;
  }

  if (el.getTotalLength) { return el.getTotalLength() * ratio; }
  switch(el.tagName.toLowerCase()) {
    case 'circle': return getCircleLength(el) * ratio;
    case 'rect': return getRectLength(el) * ratio;
    case 'line': return getLineLength(el) * ratio;
    case 'polyline': return getPolylineLength(el) * ratio;
    case 'polygon': return getPolygonLength(el) * ratio;
  }
}

This would also mean that this ratio should not affect existing non-scaled paths.

davidwebca commented 2 years ago

For those of you having this issue, you can patch in these functions in your project pretty easily:

function getRealTotalLength(el) {
    // Calculate Ratio of rendered size over real size over its definitive bounding box
    let ratio = 1;
    if(el.getBoundingClientRect().width) {
        // Lines can have width of zero
        ratio = el.getBoundingClientRect().width/el.getBBox().width;
    }else if(el.getBoundingClientRect().height) {
        ratio = el.getBoundingClientRect().height/el.getBBox().height;
    }

    if (el.getTotalLength) { return el.getTotalLength() * ratio; }
}

function setRealDashoffset(el) {
    let pathLength = getRealTotalLength(el);
    pathLength = Math.round(pathLength * 1000) / 1000;
    el.setAttribute('stroke-dasharray', pathLength);
    return pathLength;
}
anime({
  targets: '.line-drawing-demo .lines path',
  strokeDashoffset: [setRealDashoffset, 0], // <-- replace it here instead of anime.setDashoffset
  easing: 'easeInOutSine',
  duration: 1500,
  delay: function(el, i) { return i * 250 },
  direction: 'alternate',
  loop: true
});

It should also be mentioned that this inconsistency is still a thing: #504 so I added a bit of code to round properly to 3 decimals.