Open Bug 1318591 Opened 8 years ago Updated 2 years ago

Distance calculation for transform lists should use distance of transformed point

Categories

(Core :: DOM: Animation, defect, P3)

defect

Tracking

()

Tracking Status
firefox53 --- affected

People

(Reporter: birtles, Unassigned)

References

Details

When I met up with Google last week[1], they had a proposal for how distance of transform lists should be calculated. The thinking is, the whole point of paced timing is to get an even rate of change. So, if you have a translate followed by a rotate, if you simply transform a point on, say, the edge of the object you're transforming, you can calculate the distance it moves as it travels along and use that to produce an even rate of change. Two tricky bits are: 1) That requires knowing the size of the element you're transforming, i.e. makes this layout dependent? 2) If you have a transform-origin at 50%,50% and the point you track is the transform-origin, you'll effectively get zero distance when you scale since the center doesn't move. (Likewise for rotation I guess.) At the same time, you *do* need to consider the transform-origin in order to produce the right transformation. I think we decided that if you choose the point on the object's bounding box that is the furthest corner from the transform-origin then you'll get the right result. (If all corners are equidistant from the transform-origin then bias towards the top-left.) I wonder if that would work, though? As an aside, we discussed how important distance calculations and paced timing are (since in SVG the primary usage was for animateMotion but when you use CSS motion path, because it's already distance-based, you don't need paced timing). We discussed that there are at least three places we know of so far where distance is useful: * Producing smooth motion as in the above example of complex transform animations * Within DevTools where you want to represent on a graph "how much" an animation is changing at different points--i.e. you need to transform a multidimensional change into a single dimension * For possible spring timing functions where you want the duration of the spring bounce function to be based on "how far" the animation is moving. (Although this might require giving some sort of meaning to absolute values of distance, rather than just giving them a relative meaning.) [1] https://docs.google.com/document/d/1oqxM46jdM4fWKqRAEmy1KE5FTgW0dvIOo273B5mM8Ao/edit?ts=581fea65#
(In reply to Brian Birtles (:birtles) from comment #0) > When I met up with Google last week[1], they had a proposal for how distance > of transform lists should be calculated. > > The thinking is, the whole point of paced timing is to get an even rate of > change. So, if you have a translate followed by a rotate, if you simply > transform a point on, say, the edge of the object you're transforming, you > can calculate the distance it moves as it travels along and use that to > produce an even rate of change. > > Two tricky bits are: > > 1) That requires knowing the size of the element you're transforming, i.e. > makes this layout dependent? > > 2) If you have a transform-origin at 50%,50% and the point you track is the > transform-origin, you'll effectively get zero distance when you scale since > the center doesn't move. (Likewise for rotation I guess.) At the same time, > you *do* need to consider the transform-origin in order to produce the right > transformation. I think we decided that if you choose the point on the > object's bounding box that is the furthest corner from the transform-origin > then you'll get the right result. (If all corners are equidistant from the > transform-origin then bias towards the top-left.) > > I wonder if that would work, though? Using the furthest corner from the transform-origin makes sense to me, but as you mentioned, we need the layout information, i.e. nsStyleTransformMatrix::TransformReferenceBox(targetElement->GetPrimaryFrame()). Transform could be the only one case to be layout dependent while calculating the distance. We use the layout info at the moment of computing the computedOffsets and will not re-calculate it again. Therefore, now we need to spec/propose how to find a specific |reference point| (the furthest point from the transform-origin), so we can calculate its final position after applying a transform function list. Therefore, we can get the distance by the following steps: 1. Get the position of the |reference point| by applying the 1st transform function list, and 2. Get the position of the |reference point| by applying the 2nd transform function list. 3. Compute the distance along the path from the first position to the second one. But the problem is: the movement is not along a straight line, so we should not use the Euclidean distance of the two positions. It seems that we have to know all positions during the interpolation, so we can know the path from the first keyframe to the second one. e.g. from {transform: "rotate3d(0,0,1, 60deg) translate(10px)"} to {transform: "rotate3d(0,0,1, 30deg) translate(40px)"} The path might be something like: 1) |reference point| applying rotate3d(0,0,1, 60deg) translate(10px) 2) |reference point| applying rotate3d(0,0,1, 59deg) translate(11px) 3) |reference point| applying rotate3d(0,0,1, 58deg) translate(12px) ... n) |reference point| applying rotate3d(0,0,1, 30deg) translate(40px) we need to know the path/curve that the reference point travels, so we can get the correct path distance. Or do I misunderstand this? Thanks for your update.
Yes, you're right, we'd need to sample the point along the path it follows. The trouble is, even if we say, for example, that you sample 10 times between each pair of keyframes, that wouldn't be enough for cases like: keyframe n: transform: rotate(0deg); keyframe n+1: transform: rotate(3600deg); In the above case you might even get a distance of zero! Worse still, unless we clearly specify the number of sample points, the result for cases like that could be implementation-dependent. Of course, we could special case rotate() and calculate the circumference of the circle it traces but that becomes very difficult once rotate() is combined with other transform functions in the same list. I really wonder how practical this approach is. How do you feel about the dependency on layout information? Is that likely to cause problems?
(In reply to Brian Birtles (:birtles) from comment #2) > Yes, you're right, we'd need to sample the point along the path it follows. > The trouble is, even if we say, for example, that you sample 10 times > between each pair of keyframes, that wouldn't be enough for cases like: > > keyframe n: transform: rotate(0deg); > keyframe n+1: transform: rotate(3600deg); > > In the above case you might even get a distance of zero! Worse still, unless > we clearly specify the number of sample points, the result for cases like > that could be implementation-dependent. > > Of course, we could special case rotate() and calculate the circumference of > the circle it traces but that becomes very difficult once rotate() is > combined with other transform functions in the same list. Oh yes, we need to handle the special cases while sampling the points along the path. > > I really wonder how practical this approach is. > > How do you feel about the dependency on layout information? Is that likely > to cause problems? I think it may be ok now because we only calculate the computedOffsets once. We can use the layout information at the moment of calculating the computedOffsets (of course, need to flush the layout first). But users may feel weird because we compute the distances by computed values for other css properties. However, "dependency on layout information" might be necessary to have an even rate of change, so we can achieve the perfect paced animations. All we need to know are the reference width and height [1] (from nsIFrame object), and then we can compute the "relative" distances of each pair of keyframes. Besides, I think we could still use the original computedOffsets even if we change the layout information frequently. Actually, I'm not sure what problems it might cause now. Therefore, I think the biggest problem of using the layout information is something like what you said in Bug 1276193 Comment 5, it seems a little odd to have to flush layout to determine keyframe offsets. [1] http://searchfox.org/mozilla-central/rev/935627b015aaabbd2dffa90cce051521f22dd7e6/layout/style/nsStyleTransformMatrix.h#104-111
(In reply to Boris Chiou [:boris] from comment #3) > (In reply to Brian Birtles (:birtles) from comment #2) > > Yes, you're right, we'd need to sample the point along the path it follows. > > The trouble is, even if we say, for example, that you sample 10 times > > between each pair of keyframes, that wouldn't be enough for cases like: > > > > keyframe n: transform: rotate(0deg); > > keyframe n+1: transform: rotate(3600deg); > > > > In the above case you might even get a distance of zero! Worse still, unless > > we clearly specify the number of sample points, the result for cases like > > that could be implementation-dependent. > > > > Of course, we could special case rotate() and calculate the circumference of > > the circle it traces but that becomes very difficult once rotate() is > > combined with other transform functions in the same list. > > Oh yes, we need to handle the special cases while sampling the points along > the path. If you can find a way to do this, that's great! > > I really wonder how practical this approach is. > > > > How do you feel about the dependency on layout information? Is that likely > > to cause problems? > > I think it may be ok now because we only calculate the computedOffsets once. > We can use the layout information at the moment of calculating the > computedOffsets (of course, need to flush the layout first). But users may > feel weird because we compute the distances by computed values for other css > properties. However, "dependency on layout information" might be necessary > to have an even rate of change, so we can achieve the perfect paced > animations. All we need to know are the reference width and height [1] (from > nsIFrame object), and then we can compute the "relative" distances of each > pair of keyframes. Besides, I think we could still use the original > computedOffsets even if we change the layout information frequently. > Actually, I'm not sure what problems it might cause now. Therefore, I think > the biggest problem of using the layout information is something like what > you said in Bug 1276193 Comment 5, it seems a little odd to have to flush > layout to determine keyframe offsets. Yes, I'm a bit concerned that we'll discover cases that produces cycle between style and layout but provided you can find a way around that, then we can certainly try this. I do think that if it works it could be useful to authors.
(In reply to Brian Birtles (:birtles) from comment #4) > > > keyframe n: transform: rotate(0deg); > > > keyframe n+1: transform: rotate(3600deg); > > > ... > If you can find a way to do this, that's great! I think the simplest way is: we sample the points according to the absolute difference of two angle values. For example, we use pi/4 (90deg, or a smaller degree) as an unit to calculate the possible number of sample points. 1) > keyframe n: transform: rotate(0deg); > keyframe n+1: transform: rotate(3600deg); (3600-0) / 90 = 40, so we sample 40 points 2) > keyframe n: transform: rotate(0deg); > keyframe n+1: transform: rotate(1234deg); (1234 - 0) / 90 ~= 13.xxx, so we sample 14 points We can do something like this, or applying a more sophisticated way to find a better number for sampling to avoid the "zero distance" problem of rotate(). Besides, we also need a specific way to sample points for matrix(3d) because it could be a rotate equivalent matrix.
Priority: -- → P3
Depends on: 1362896
No longer depends on: 1362896
Severity: normal → S3
You need to log in before you can comment on or make changes to this bug.