Jump to content
GreenSock

foundartfactory

Issue with transformOrigin & arbitrary values?

Go to solution Solved by GreenSock,

Recommended Posts

Howdy folks!

 

I've run into an issue with rotating some SVG rects around an arbitrary transformOrigin, using gsap.set and then gasp.to. I've had great success in other projects, but I'm scratching my head on this one. It's gotta be some typo of mine, I'm guessing. 

 

Once the page loads, you can click on it; Some rects get created and placed, and then animated -- rotation around arbitrary transformOrigin. Various approaches work, most don't on a per-rect basis.

 

The only time I get the expected rotation is when I use a classname a la gsap.set(".firstRectShot", { transformOrigin: "50% 90%" });, rather than say gsap.set(`.${rectID}`, { transformOrigin: "50% 90%" }); . If I use standard setAttribute lingo, then hilarity ensues when gsap picks up the animation (svgOrigin is set to the percentages, and everything rotates around that point, rather than at that point on themselves)

 

Here's the pertinent code below (starting around line 125 in CodePen example), which sets up the rects, then calls a function that displays the rects, which then calls the animation.

 

Ideas? I'm sure it's so obvious I'm missing it! 

 

Thanks!

 

Patrick

 

function makeNewScattershot() {
  // DEFINE RANGES FOR HSL, type of shape, size(s), etc
  const someDot = {
    name: "firstRectShot",
    shape: "rect",
    w: 60,
    h: 95,
    x: 0,
    y: 0,
    jitterXY: [-2, 2, false],
    jitterOrigin: [45, 60, true],
    jitterRotation: [-30, 30, true],
    fillHue: [90, 120, true],
    fillSat: [50, 70, true],
    fillLight: [50, 80, true],
    fillOpacity: [0.035, 0.085, false],
    strokeHue: [58, 62, true],
    strokeSat: [80, 95, true],
    strokeLight: [70, 90, true],
    strokeOpacity: [0.05, 0.15, false]
  };

  const someDots = [someDot];

  //DEFINE NEW SCATTERSHOT
  //(how many elements, width, height, rand variance, color/placement object array)
  const myNewScattershot = new Scattershot(50, 1600, 700, 1, someDots); // > 4000 gets pokey
  const rectShots = myNewScattershot.scattershotArray;

  //COLOR & 'JITTER':
  const fillHue = rectShots[0][0].fillHue;
  const fillSat = rectShots[0][0].fillSat;
  const fillLight = rectShots[0][0].fillLight;
  const strokeHue = rectShots[0][0].strokeHue;
  const strokeSat = rectShots[0][0].strokeSat;
  const strokeLight = rectShots[0][0].strokeLight;
  const jitterXY = rectShots[0][0].jitterXY;
  const jitterOrigin = rectShots[0][0].jitterOrigin;
  const jitterRotation = rectShots[0][0].jitterRotation;
  const fillOpacity = rectShots[0][0].fillOpacity;
  const strokeOpacity = rectShots[0][0].strokeOpacity;

  const rectWidth = `${rectShots[0][0].w}`;
  const rectHeight = `${rectShots[0][0].h}`;

  let rectClass;
  rectClass = `${rectShots[0][0].name}`;

  for (let i = 1; i < +rectShots.length; i++) {
    const rectElement = makeElement("rect");
    const rectID = rectShots[i].id;

    rectElement.setAttribute("class", `${rectID} ${rectClass}`);
    rectElement.setAttribute("id", rectID);

    //No-go for expected animated rotation around point at 50% 90%:
    //rectElement.setAttribute("transform-origin", "50% 90%");

    svg.appendChild(rectElement);

    /*WORKS on all in class (which is all of them, currently):*/
    // gsap.set(".firstRectShot", { transformOrigin: "50% 50%" });

    /*WORKS on all in class (which is all of them, currently):*/
    //gsap.set(`.${rectClass}`, { transformOrigin: "30% 90%" });

    /*NO-GO on all in class (should be just one, as each uses its ID as also a classname):*/
    //gsap.set(`.${rectID}`, { transformOrigin: "50% 90%" });

    /*NO-GO using ID:*/
    gsap.set(`#${rectID}`, { transformOrigin: "50% 90%" });

    gsap.set(rectElement, {
      //No difference: gsap.set(`#${rectID}`, {
      x: `${(rectShots[i].x += getRandomInt(
        jitterXY[0],
        jitterXY[1],
        jitterXY[2]
      ))}`,
      y: `${(rectShots[i].y += getRandomInt(
        jitterXY[0],
        jitterXY[1],
        jitterXY[2]
      ))}`,
      width: `${rectWidth}`,
      height: `${rectHeight}`,
      /*WHAT I WANT TO HAVE WORK:
            transformOrigin: `${getRandomInt(jitterOrigin[0], jitterOrigin[1], jitterOrigin[2])}% ${getRandomInt(jitterOrigin[0], jitterOrigin[1], jitterOrigin[2])}%`,*/
      /*WORKS, oddly enough:
            svgOrigin: "30 45",*/
      rotation: `${getRandomInt(
        jitterRotation[0],
        jitterRotation[1],
        jitterRotation[2]
      )}`,
      fill: `${hsl2hex(
        getRandomInt(fillHue[0], fillHue[1], fillHue[2]),
        getRandomInt(fillSat[0], fillSat[1], fillSat[2]),
        getRandomInt(fillLight[0], fillLight[1], fillLight[2])
      )}`,
      fillOpacity: `${getRandomInt(fillOpacity[0], fillOpacity[1], 0).toFixed(
        3
      )}`,
      stroke: `${hsl2hex(
        getRandomInt(strokeHue[0], strokeHue[1], strokeHue[2]),
        getRandomInt(strokeSat[0], strokeSat[1], strokeSat[2]),
        getRandomInt(strokeLight[0], strokeLight[1], strokeLight[2])
      )}`,
      strokeWidth: 2,
      strokeOpacity: `${getRandomInt(
        strokeOpacity[0],
        strokeOpacity[1],
        0
      ).toFixed(3)}`,
      autoAlpha: 0,
      visibility: 1
    });
  }

  showSatellites(`${rectClass}`);
}

 

See the Pen rNrGxJv by foundartfactory (@foundartfactory) on CodePen

Link to comment
Share on other sites

  • Solution

I saw a few problems: 

  1. You're setting the width/height in the same gsap.set() call as the transformOrigin, and in this very rare edge case that's actually a problem because it just so happens that in the for...in loop through the properties, transformOrigin happens BEFORE the width/height. So when it tries to calculate the percentage-based origin offsets, your <rect> literally has no width or height at all, thus it gets positioned in its upper left corner. The solution: set the width/height FIRST. You could just separate those out into their own gsap.set() that you put first. 
  2. You're creating conflicting animations. If you click again before the first set of animations completely finishes their 4 repeats, you'll be creating new ones that are also fighting with the old ones for control of the same elements. Make sure you kill() the old animations before you create new ones. Or you can just leverage the overwrite feature (overwrite: true here).

Just so you know, the smoothOrigin does absolutely nothing in this line: 

gsap.timeline({ smoothOrigin: true, yoyo: false, repeat: 4 });

Timelines don't have a property like that. Maybe you intended to pass that down as a default for all child tweens?: 

gsap.timeline({ defaults: {smoothOrigin: true}, yoyo: false, repeat: 4 });

See the Pen NWBaLOd?editors=0010 by GreenSock (@GreenSock) on CodePen

 

Does that clear things up? 

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

36 minutes ago, GreenSock said:

in this very rare edge case that's actually a problem because it just so happens that in the for...in loop through the properties, transformOrigin happens BEFORE the width/height. So when it tries to calculate the percentage-based origin offsets, your <rect> literally has no width or height at all, thus it gets positioned in its upper left corner

 

AHA, thanks! Makes total sense, and thanks for running through that. I broke it out like you suggested and set the width & height in their own call, before setting the offsets etc. Works as expected, now! The pen reflects the changes.

 

Noted re: conflicting animations, and setting timeline defaults! Apologies for the messy code. 

 

Thanks again, Jack!

  • Like 1
Link to comment
Share on other sites

No problem. Happy to help!

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×