Jump to content

Search In
  • More options...
Find results that contain...
Find results in...

Julius Friedman

  • Posts

  • Joined

  • Last visited

Everything posted by Julius Friedman

  1. Hows that @ https://codepen.io/juliusfriedman/pen/oKaNPj I kept the other graphics for when you had more time but I added two simple graphics at the top which morph to each other (a circle to a square) Both have matrix transforms which are similar but in most cases I would image the locations to not match and make the issue more noticeable. (had to add a scale to get it to manifest...) Let me know if you need something different. I was also able to update the logic in the morphFromTo function to remove the matrix which was causing the issue and as a result the morph looks as it's supposed to in the end now. The logic I needed was: //Get all groups to remove their transforms.. const adjust = from.querySelectorAll('g'); for (let i = 0; i < adjust.length; ++i) { const x = adjust[i], tr = x.getAttribute('transform'); if (tr && tr.startsWith('matrix')) x.removeAttribute('transform'); } And should probably be modified to translate the coordinates but for my purposes it just works to remove them for now. Hopefully others find this logic useful and you can better see what I mean now. I also added more morphs to the CodePen so you can see the fill issue as well as the positions issue. The position issue is mostly resolved expect where it's left right now as can be seen on the Circle and Square as well as the coins which were morphed to people, somehow I get lucky in that the center position of the head ends up where the transformed position of the right most body should be but they are off in some morphs due to the matrix. Let me know if I can do anything else! About to head to sleep but I wanted to give you another way to visualize that the problem... If you fork my pen and change it to do this you will see even more the fill issue with the coins as well as the location issue, it seems that when scale is applied that calculations are made to the points which can be thrown off if the transforms on the element or it's group etc are not taken into account. I believe the advice is to remove transforms from graphics before you do stuff like this but just encase someone hit this early on like I did (with un-optimized stock graphics) then perhaps this post may come in useful. //Morph the square to a circle, reverse the simpleExample const firstExample = morphFromTo(document.getElementById('example_0'), document.getElementById('example_a'), simpleExample.reverse).play(); //Morph the people into other people, reverse the firstExample const nextExample = morphFromTo(document.getElementById('example_1'), document.getElementById('example_2'), firstExample.reverse).play(); //Morph the coins into the people const lastExample = morphFromTo(document.getElementById('example_3'), document.getElementById('example_1'), function(){ //Reverse the morph lastExample.reverse(); //When that morph is completed then start another morph lastExample.vars.onComplete = function(){ //Start another morph morphFromTo(document.getElementById('example_2'), document.getElementById('example_3')).play(); } }).play(); Regards!
  2. I can try my best, it seems the issue would be present if I just took any path and then applied a transform via the attribute and then subsequently used that then it should show the problem manifest however I have to write that test case to show it. I will see if I can make another pen or modify that one to just have the data that counts and keep you updated. Same thing with the fill issue. Didn't mean to bog you down further, just wanted to try and show you what I was talking about
  3. I can definitely do that although I would also have to hijack set as I described previously so please don't get caught off guard by that. Check out https://codepen.io/juliusfriedman/pen/oKaNPj In that example I haven't even touched scale or position of the source and yet the morphed locations are off. If you remove the transform on the group in which those paths are contained then the result looks like it should. Scroll down to review the original for reference. Please let me know if that helps! I also added a graphic which exhibited the opacity and color issue, its included in the pen under example_3 (the coins) You can see there that when getFillOfT is called on an element which has the classes used e.g. 'st0' or otherwise the fill doesn't get calculated with respect to the opacity, one would have to edit the graphic or calculate the resulting color from the opacity used in the styles....
  4. I would agree that any data that is redundant or pointless should be removed; During the prototyping I am trying to avoid having to modify any graphics or at least note where things needs to be modified to accommodate special requirements of design later on. I just wanted to ask because it seemed trivial to address in what was an otherwise very pleasant experience when using the API and I am quite sure that others would benefit from the minimal change this would require. I appreciate your kind words and additionally if there is anything else I can do to help please just let me know! The only other feedback it seems I would like to provide is that when using the `MorphSVGPlugin.convertToPath` function, it would be helpful if one would take into account transforms on the source and target path, possibly with a custom function if someone desire or even more options but the main importance seems to be coordinate origin related `transform` attribute on the object being converted. One can seem to override this behavior only currently by hijacking `e.getBBox` which is used during the `h` function to calculate the points of the path data being created or doing so afterwards the conversion but before the `morphSvg` is used in an animation. I found most of this writing a function to morph a graphic from a source to a destination when I found this among a few other quirks which GSAP might be able to help with if your interested... I have included that function below which is definitely NOT complete but functions as expected minus the coordinate transform issue I am describing. Others may find it useful when morphing from one thing to another... //from is the source, to is the destination, onComplete is called when the timeline is elapsed. function morphFromTo(from, to, onComplete) { //Have to query the paths or polygons or other supported objects. (Todo, could create a clipPath or other cool effect from the path data... or could translate the shape to a clipPath...) const f = from.querySelectorAll('path, circle, rect, ellipse, line, polygon, polyline'), tt = to.querySelectorAll('path, circle, rect, ellipse, line, polygon, polyline') //NodeList length const ffMax = f.length; //The resulting timeline const timeline = new TimelineMax({ paused: true, onComplete: onComplete }); //The forward index and the lastUsed index within to let fIndex = 0, lastMorphIndex = tt.length; //Loop for every object to morph to for (let i = 0; i < tt.length; ++i) { //If there is an existing path which can be morphed if (fIndex < ffMax) { const t = tt[i]; //Get the path we are morphing to //If we won't use them then skip them. if (t.style && t.style.display === 'none') { //Decrease the morph index --lastMorphIndex; continue; } //Get the path we are converting and move the index const p = f[fIndex++], //Compute the style style = context.getComputedStyle(t), //Convert both to paths c = MorphSVGPlugin.convertToPath(t), d = MorphSVGPlugin.convertToPath(p); //needed because we might have transformed the path previously... //timeline.set(d, { clearProps: 'transform display' }); //timeline.set(d, { attr: { trasform: t.style.transform } }); timeline.to(d, 1, { morphSVG: c[0], autoAlpha: true, scale: 1, opacity: 1,//getOpacityOf(t), //opacity: t.getAttribute('opacity') || t.style.opacity || style.opacity, fill: getFillOf(t) || style.fill, ease: Power2.easeInOut }, 0); //if (style.stroke && style.strokeWidth) { // timeline.to(d, 1, { // drawSVG : true, // ease: Expo.easeInOut // }, 1); //} } else { const t = tt[i]; if (t.style && t.style.display === 'none') { //t.parentElement.removeChild(t); --lastMorphIndex; continue; } const style = context.getComputedStyle(t), np = MorphSVGPlugin.convertToPath(t); //from.appendChild(np[0]); //timeline.fromTo(np, 1, { scale: 0, opacity: 1, autoAlpha: true }, { // scale: 1, // opacity: 1, // autoAlpha: true, // attr: { // fill: getFillOf(t) || style.fill, // }, // ease: Power2.easeInOut //}, 0); const emptyPath = document.createElementNS('http://www.w3.org/2000/svg', 'path'); emptyPath.setAttribute('d', 'm0,0v0'); from.appendChild(emptyPath); timeline.to(emptyPath, 1, { morphSVG: np[0], autoAlpha: true, scale: 1, opacity: 1,//getOpacityOf(t), fill: getFillOf(t) || style.fill, ease: Power2.easeInOut }, 0); //if (style.stroke && style.strokeWidth) { // timeline.to(np, 1, { // drawSVG: true, // ease: Expo.easeInOut // }, 1); //} } } //Find any unsed paths and scale them out. for (let i = lastMorphIndex; i < ffMax; ++i) { const ff = f[i]; timeline.to(ff, 1, { scale: 0, opacity: 0, ease: Power2.easeInOut, onComplete: function () { ff.parentElement.removeChild(ff); } }, 0); } return timeline; }; You can see where I am having some difficultly with the `needed because we might have transformed the path previously...` comment. I also noticed that getBoundingClientRect seems to provide a good value even when transforms are applied but the coordinate systems / units still have to be converted... This is common in my current scenario because the graphics are [much] larger than then the graphic they are being inserted into, as a result I must first scale and position the graphics when inserting them. One can definitely improve that function in some way to allow for control at each stage of the morph, custom tweens and also the transforming of coordinates as required and when I get further along in my project I can revisit the function as well as the required `getFillOf` function to make it more general and return the style(s) associated for use in the morphing function. //Todo, modify this function to also return fill modified by opacity if needed or have a function which can compute that function getFillOf(w) { //I would just use getComputedStyle but it doesn't always seem to return the correct value on my graphics especially when more than 1 class is used... //Todo, return whole style so it's opacity can be used //Todo, support multiple styles... let r = w.getAttribute('fill') || w.style.fill; if (r) return r; let className = w.className.baseVal; if (className && className.length && className !== 'tmp') r = CSSRulePlugin.getRule('.' + className).fill; if (r) return r; let p = w.parentElement; while (p) { r = p.getAttribute('fill') || p.style.fill; if (r) return r; className = p.className.baseVal; if (className && className.length && className !== 'tmp') r = CSSRulePlugin.getRule('.' + className).fill; if(r) return r; p = p.parentElement; } //When there is no fill return transparent or background color et }; That function is used because of some color difficulties I was noticing where fill and opacity were also needing to be set on some paths of the graphic because they were different from source and target. I noted my struggles in the code and made use of the CSSRulePlugin however I imagine that getComputedStyle would be able to accurately determine the fill and opacity especially when there are multiple classes but alas I get values that don't make sense like: `rbga(0,0,0,0)` or `rbg(0,0,0)` for fill when the fill is easily found on the attribute or the style as found in the function.... I was thinking that the ColorProps plugin might be able to also compute the values I needed with respect to opacity since it seemed like a common task. If you test it out then be sure to note how `getComputedStyle.fill` and `getFillOf` differ for an unknown reason. That doesn't seem to be a problem with your code however I thought you would find it interesting especially since you work around browser issues and possibly others might find it useful for the same type of technique. The last tid bit is that removal ceremony is so common that possibly there could be an options 'remove' or 'destroy' which will ensure that that the element is removed after completion, it would save having to create and or bind a function for such trivial things. Let me know what you think and thank you again for your kind words!
  5. Thank you for your input Blake! I can attempt to simplify the points and remove those which I do not require however some of these points are converted using the `MorphSVGPlugin.convertToPath` function which I would have imagined would have either formatted them as required or removed the points which it did not need, either way the issue still seems to be valid based on the information I provided.... I am not the Author of most of the graphics in use at the current time, I am using stock graphics for the prototyping and hence why I didn't attempt to fiddle with the point data therein. In short, I agree with what your saying however I feel like there other cases which may cause the issue to exhibit itself as I have attempted to explain in the post above e.g. point data with only 1 point etc. In such cases it can be handled to either use the transform origin or a 0 coordinate etc but I just wanted to make the issue known such that it could be fixed. Please let me know if my code above didn't solve this issue in the correct way and what your take on the correct logic therein would be for such cases. Regards!
  6. Hello Guys, First of all just a quick kudos on the API and performance of GSAP; I have been using it for a project I am prototyping and I find it quite pleasant to work with. The question: Please see the attached codepen, there appears to be a bug in the set function of `morphsvg` from what I can see however I wanted to ask this question to make sure. The easiest way to replicate the issue is to just attempt a morph on that path, in order to simplify the replication I have extracted the code required here: <svg id="graphic"> <path id="source" style="" d="m256 421h30l60-135h-150zm0 0"/> <path id="dest" d="M139.911,237.64c-40.751,40.701-72.005,18.178-110.759,56.751 C-16.039,339.37-6.793,417.925,43.784,468.264c52.284,52.039,130.479,58.57,174.667,14.589 c38.638-38.457,16.203-69.146,57.208-110.041c7.348-7.328,54.912-24.028,79.847-56.991c60.491-79.964-144.48,38.09-56.838-99.987 C198.803,116.092,155.828,221.742,139.911,237.64z" /> </svg> TweenMax.to('.source', 1, { morphSVG: '.dest', ease: Power2.easeInOut }); The error can be seen in the console Error: <path> attribute d: Unexpected end of attribute. Expected number, "…6 196 286 M0 0 C The fix?!? `morphsvg` defines a set method which I have extracted and placed here for illustration. set: function (e) { var X, L, t, r, n, o, i, a, h, s, l, f, g, p, c, u = this._rawPath, d = this._controlPT, m = this._anchorPT, _ = this._rnd, y = this._target; if (this._super.setRatio.call(this, e), 1 === e && this._apply) for (n = this._firstPT; n;) n.end && (this._prop ? y[this._prop] = n.end : y.setAttribute(n.endProp, n.end)), n = n._next; else if (u) { for (; m;) a = m.sa + e * m.ca, i = m.sl + e * m.cl, m.t[m.i] = this._origin.x + U(a) * i, m.t[m.i + 1] = this._origin.y + q(a) * i, m = m._next; for (r = e < .5 ? 2 * e * e : (4 - 2 * e) * e - 1; d;) c = (h = d.i) + (h === (o = u[d.j]).length - 4 ? 7 - o.length : 5), a = C(o[c] - o[h + 1], o[c - 1] - o[h]), g = q(a), p = U(a), l = o[h + 2], f = o[h + 3], i = d.l1s + r * d.l1c, o[h] = l - p * i, o[h + 1] = f - g * i, i = d.l2s + r * d.l2c, o[c - 1] = l + p * i, o[c] = f + g * i, d = d._next; //When there are only 2 points the " C" is not part of the path. if (y._gsRawPath = u, this._apply) { for (t = "", s = 0; s < u.length; s++) for (i = (o = u[s]).length, L = o.length, //Move to the x, y and setup a curve to the next point OR mark this at the end of the path? X = "M" + (o[0] * _ | 0) / _ + " " + (o[1] * _ | 0) / _ + (L > 2 ? " C" : ""), t += X, h = 2; h < i; h++) t += (o[h] * _ | 0) / _ + " "; this._prop ? y[this._prop] = t : y.setAttribute("d", t) } } this._render && u && this._render.call(this._tween, u, y) } As you can see I have added 'X and L' vars at the top, I use those to inspect the variables being built in the loops below, what I think is occurring here is as I have annotated in the code, we are going through the points to build a path string, we use the first 2 values in the array as the X, Y positions and then it seems we expect more points as we create a C(urve) to the remaining points `(h = 2; h < i; h++)`. Without my modification the 't' variable ends up with a string like: "M ${X} ${Y} C", where ${X} and ${Y} are given by the first 2 points in the path and then that loop ends and we proceed to the next with the following logic: for (t = "", s = 0; s < u.length; s++) for (i = (o = u).length, That seems straight forward as we have just used the first 2 points in the loop, however it seems that there are occasions where there can be less or equal to 2 points? In such case a string will be resulting which is not valid and will cause that frame of the animation to fail on that frame. Obviously without an X and Y the path would likely be incomplete however there are definitely more points in the path, so I attempted to debug the loop and it seems the animation recovers on the next steps so I imagine this has something to do with the way the points and ratio are calculated in the loop above however I have not dived in enough to determine exactly the root cause although I have a suspicion which I will indicate below. FYI, My fix seems to work as there are no more errors in the console however I just wanted to bring this to your attention as it seemed like possibly something which could be fixed in the library itself. E.g. one thing I wanted to change but didn't want to dig myself to far in was in the algorithm: I wanted to make more use of the L variable however it seems that all the points get normalized to 0 even if they are undefined in that loop. e.g. of o.length = 0 then h + 1 is out of bounds and so is c - 1 in the following logic (depending on the ease I suppose) a = C(o[c] - o[h + 1], o[c - 1] - o[h]), ... l = o[h + 2], ... f = o[h + 3], .. Perhaps as `| 0` there can be useful to force the possibly undefined value into a number as we do when we calculate the points for the string: (o[0] * _ | 0) Please do let me know if I have missed something or what you guys determine! Thanks for your library and assistance!