Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[motion-1] the description of contain flag in ray() function #363

Open
BorisChiou opened this issue Aug 9, 2019 · 10 comments
Open

[motion-1] the description of contain flag in ray() function #363

BorisChiou opened this issue Aug 9, 2019 · 10 comments

Comments

@BorisChiou
Copy link

BorisChiou commented Aug 9, 2019

This is more like a question than an issue. I'm working on offset-path: ray(), and the definition of contain is a little bit not clear enough to me.
Per the spec:

The used value of offset-distance is clamped so that the box is entirely contained within the path.
If no offset-distance would lead to the box being enclosed by the path, the path size is minimally increased so that such an offset-distance exists.

For examples: if the box size is 100px x 100px.

  1. If the offset-anchor is within the box, e.g. 50% 50%
    1. Assume the path length is long enough to enclose all the box (i.e. larger than 100px):
      • offset-distance is 0%, so part of the box is not contained within the path. In this case, do we have to move the box forward (i.e. 50px) to make sure all box is entirely contained within the path? (I guess the answer is yes.)
      • offset-distance is 100%. This case is clear, so we just move the box backward (i.e. 50px) to make sure all box is entirely contained within the path.
    2. Assuem the path length is not long enough to enclose the path (i.e. less than 100px):
      • offset-distance is 0%, so part of the box is not contained within the path. We have to increase the path length to 100px. The question is which direction should we use to increase the path? For example, increase the tail, so we have enough space to move the box forward?
      • offset-distance is 100%. Same case, just increase the tail of the path to make sure the path size is 100px, right?
  2. If the offset-anchor is outside the box, e.g. 200% -300%
    • What is the definition of the box is entirely contained within the path? the box is outside the path already because the anchor point is outside the box.

Thanks. cc @ericwilligers

@BorisChiou
Copy link
Author

BorisChiou commented Aug 9, 2019

After re-reading the spec, for the following questions:

Assuem the path length is not long enough to enclose the path (i.e. less than 100px):

offset-distance is 0%

In this case, because the path size should be minimally increased, so I think we should move the box forward until the end of the path, and increase the head of the path to make sure the box is entirely contained (i.e. total path size is 100px).

offset-distance is 100%

And for this, move the box backward until the beginning of the path, and increase the tail of the path (i.e. total path size is 100px).

However, still not sure how to do this if offset-anchor is outside the box.

@ewilligers
Copy link
Contributor

You haven't mentioned the direction of the ray. I'll assume a multiple of 90 degrees, e.g. 0deg.

Assume the path length is long enough to enclose all the box

By path length, think of circle radius. Perhaps different spec wording is needed.

At offset-distance 0%, any radius of calc(sqrt(2) * 50px) or more encloses the box. We do not move the box.

At offset-distance 100%, we move the box backwards slightly more than 50px so that the entire box (all 4 corners, not just the center points of edges) fits within the circle. For example, using a 5-12-13 triangle, if the path length was 130px, we would move the box backwards 60px.

Assume the path length is not long enough to enclose the box

At offset-distance 0%, we would increase the radius and not move the box.

At offset-distance 100%, the minimal increase to the path length is to calc(sqrt(2) * 50px). Now an offset-distance (0px) exists such the box fits entirely within the circle.

If the offset-anchor is outside the box, e.g. 200% -300%

I used https://petrogale-purpureicollis.appspot.com/ray/ to generate SVG images for the spec.

You can change offset-path to have contain and change offset-anchor from auto to 200% -300%, and Plot to generate an SVG.

@BorisChiou
Copy link
Author

BorisChiou commented Aug 14, 2019

By path length, think of circle radius. Perhaps different spec wording is needed.

OK. I misunderstood the meaning of entirely contained within the path in the spec. Ya, the size of the path (e.g. closest-side) means the circle radius, so the entirely contained means the box is entirely inside the circle. That's why the examples use circles to demo contain. Thanks for this explanation.

I used https://petrogale-purpureicollis.appspot.com/ray/ to generate SVG images for the spec.

Cool. This tool is what I need. Thanks!

@BorisChiou
Copy link
Author

I close this because I got the answer already. However, IMO, it'd be better to give more words to explain contain in the spec. Thanks.

@tabatkins
Copy link
Member

Reopening because I'm trying to fix the spec text and I'm still not 100% clear on what this is trying to say.

Okay so, first, it looks like it doesn't actually change the length of the offset path - 100% is still, technically, at the same distance regardless of whether contain is specified or not. We'll just automatically adjust the actual offset-distance to satisfy the contain constraint, so some portion of the possible offset-distance space near the end all maps to the same actual position.

Then, at positioning time, we look at where the 100% point on the ray is, and possibly adjust the actual offset-distance so that the furthest side/corner (along the specified angle) of the transformed element's (border box?) bounds are further from the initial position than the 100% point.

I think this explains the second example in https://drafts.fxtf.org/motion-1/#example-ray - the red box is pulled inward so that its furthest point from the initial position (its top-right corner) is exactly at the 100% point, while the blue box is pulled in so that its furthest points (the entire bottom edge) are exactly at the 100% point. (Notably, the blue box overflow the "100% shape" slightly, but we only actually care about the precise point along the ray, and distances along lines parallel to the ray.)

Tho, hm, this quote from Eric:

At offset-distance 100%, we move the box backwards slightly more than 50px so that the entire box (all 4 corners, not just the center points of edges) fits within the circle. For example, using a 5-12-13 triangle, if the path length was 130px, we would move the box backwards 60px.

suggests that we do need to actually contain it within the "100% shape", so the corners don't overflow the shape at all. This means the example is wrong.


I'm completely confused by what's supposed to happen when the transformed box overflows the "100% shape" even at offset-distance 0%. The third example trying to show this off is impossible to parse - the code indicates a ray pointing west, but there's a dotted line pointing east, and then the box itself is positioned to the south for some reason.

Hm, I guess the weird offset-anchor: 200% -300%; is relevant here? Like, the end of the dotted line happens to be the offset-anchor of the transformed element, and it's positioned there, at a negative offset-distance, because that places the transformed element's bounds within the smallest possible expansion of the "100% shape"?

I'm confused by Eric's assertion that we'd have different behavior at offset-distance:0% and 100%. It seems like, given the transformed element's size and offset-anchor, it's outside the 100% shape in both cases. Wouldn't we do the same thing in both cases, then?


I'm also not sure if this is meant to take border-radius into account?


Also, while the side/corner keywords have a reasonably straightforward notion of what it means to "extend" them (increase the radius of the 100% circle they define), it's not clear to me what extending the sides keyword means. Is the rectangle expanded a fixed distance in each direction? Something % based? Something else?


Overall I don't understand what the contain keyword is trying to accomplish here. I believe it's intended to solve the use-case of "put some elements around the inner edge of a circle, aka the numbers around clock face", without requiring the author to do some trig to know how far in from 100% they should position things. It does it in such an odd way, however, and seems like it'll result in slightly bad behavior even for the most straightforward cases, since the actual "max distance" varies depending on angle.

The way it's defined, it'll also result in funky animation behavior for animating from 0% to 100% - depending on the angle, it'll reach its "max distance" at varying %s, so a bunch of elements all animating from the center to the edge at once will "hit the edge" at different points in their animations. Similarly, an animation from the edge to the center will look like it's delayed for an unpredictable amount of time (while it's still past its "max distance"), and then depending on angle will start moving at different times.

I feel like the entire feature needs to be rethought slightly. Thoughts, @BorisChiou @ericwilligers ?

@tabatkins
Copy link
Member

I propose the following simplification: we take the larger of the transformed element's width or height, and subtract half that from the length we'd otherwise compute for the ray. This models treating the element as a circle and pulling it in to avoid overlap. This shortening is applied to the offset path itself, rather than being a used-value time adjustment to the position.

If the shortening would result in a negative-length path, it clamps to zero length.

  • This massively simplifies the math; chord geometry is pretty complex as it is, even ignoring the question about sides.
  • This makes the result direction-agnostic, maintaining the agnosticism of the non-sides values.
  • This makes animation of offset-distance work more sensibly and uniformly, rather than having an unpredictable "dead zone" at its higher %s.
  • Rotation of the transformed element no longer has a potentially massive impact on the shortening, either.
  • This successfully handles the use-case it was designed for - positioning UI elements around the inner edge of a circular watch face - and reasonably handles other random use-cases in at least as meaningful a manner as the current spec.

@BorisChiou
Copy link
Author

The way it's defined, it'll also result in funky animation behavior for animating from 0% to 100% - depending on the angle, it'll reach its "max distance" at varying %s, so a bunch of elements all animating from the center to the edge at once will "hit the edge" at different points in their animations. Similarly, an animation from the edge to the center will look like it's delayed for an unpredictable amount of time (while it's still past its "max distance"), and then depending on angle will start moving at different times.

Agree. The animation may look weird because the used offset-distance is different from the expected one. I have no specific thought for this. This simplification looks good for animations (and I believe the most of the use cases of motion path is for animations), so I'm happy with it. Besides, the original math calculation is really complicated, as you mentioned, and it's also great to simplify the position calculation. No objection for this proposal.

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed [motion-1] the description of contain flag in ray() function.

The full IRC log of that discussion <dael> TabAtkins: Unless people agree with me, I don't expect resolution but want eyes. Motion is in interop 2023 and have issues
<dael> TabAtkins: Problem is the ray function was added to make it easy to put things around a circle. Wanted easy way to postion around the edge of a watch face.
<dael> TabAtkins: Have origin out to some distance. Element is along a 0-100% path. 100% is edge of the watch face but you want to pull it in and not have the center at 100%.
<dael> TabAtkins: We had the contain flag which would shorten the ray for that. Definition was pretty complicated and even ignoring the math I think it's not good based on complications
<dael> TabAtkins: Have suggestion to simplify at end that would have little change in practice, but better behavior in general. Better animation and more predictable.
<dael> TabAtkins: If anyone has opinions would like them in thread. If I don't hear for a while I'll add to spec
<dael> TabAtkins: Summary is previously took rectangle of eleemnt and pulled in until it's in circle defined by what the ray projects into. Figuring out how much to pull a square into the circle is not trivial. The angle mattered which made positioning weird.
<dael> TabAtkins: Now is treat element like a circle based on larger dimension. Circle to circle make is way easier
<Rossen_> q?
<dael> TabAtkins: Don't need resolution right now, but want attention.
<dael> Rossen_: Thoughts or feedback?
<dael> fantasai: This was supposed to also handle non-circular watches. Is that still doing that or does it only work for circles?
<dael> TabAtkins: Should generally do it. Especially if element you're trying to postion is square should work approx same. Rectangle will be a bit different. Old if you're positioning rectangle to rectangle you would get right at the edge. But that's not the original thing it was trying to solve. Mine gives you good on circle and okay on rectangle
<dael> fantasai: Would be good to get people that do watch stuff to take a look. I'll look also
<dael> Rossen_: Feedback here is general temp is positive to proceed forward. I guess that's what you're looking for
<dael> fantasai: I think we should bring it back once we have spec text to resolve on. People should review with examples and stuff

@tabatkins
Copy link
Member

Okay, edits are in the spec now https://drafts.fxtf.org/motion-1/#valdef-ray-contain. Agenda+ to confirm this looks good.

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed [motion-1] the description of contain flag in ray() function, and agreed to the following:

  • RESOLVED: use half width/height for horiziontal/vertical rays, interpolate for other angles
  • RESOLVED: for sides value, have snugness
The full IRC log of that discussion <TabAtkins> https://drafts.fxtf.org/motion-1/#valdef-ray-contain
<fantasai> TabAtkins: when we first talked about motion path, I didn't realize how much needed fixing
<fantasai> TabAtkins: first thing was that contain flag was overcomplicated and didn't do what we wanted it to do
<fantasai> TabAtkins: I proposed some simplifications, supported by FF and Blink implementations
<fantasai> TabAtkins: Basically you take the larger of width or height, and subtract that from the ray
<fantasai> TabAtkins: in the common case of you start the ray from the center and get digits around a clock face, they will sit a consistent distance from the edge no matter what angle you're using
<fantasai> TabAtkins: in other cases where elements aren't round or ???
<fantasai> TabAtkins: you'll get similar behavior, it will be direction-agnostic, but not exactly the same as the old spec
<fantasai> TabAtkins: but should be close
<fantasai> florian: if old spec didn't do what it meant to do, not losing anything, but can we gain something and do what old spec meant to do?
<fantasai> TabAtkins: I don't know what it meant to do
<fantasai> TabAtkins: afaict, it was optiized for the sides value for ray, which made the ray ?? angle-dependent
<fantasai> TabAtkins: old spec if rectangular, you would get snug against the edge of the box
<fantasai> TabAtkins: for any other case, it was just wrong
<fantasai> TabAtkins: because it made sure that rectangle fit against the element
<fantasai> TabAtkins: square fits into circle more closely when axis-aligned and less so when [missed]
<fantasai> TabAtkins: so I think it was one way to interpret the way the intent, but a bad way
<fantasai> fantasai: If you're working with rectangles (or ellipses) rather than circles and squares
<fantasai> fantasai: you won't get a snug fit because of the difference in width/height -- we're only using the smallest, so it fits in one axis and not in the other
<TabAtkins> s/smallest/largest/
<fantasai> fantasai: what if we used widht in horizontal axis and height in the other
<fantasai> fantasai: and then interpolated between the two at the angles in-between?
<fantasai> TabAtkins: That's identical if element is square, but not unreasonable if element is off-square
<fantasai> TabAtkins: it does mean we regain angle dependence, but not strictly a bad thing...
<fantasai> TabAtkins: I guess I'm ok with that. Do a sinusoidal interpolation between half width and half height
<fantasai> florian: that sounds like a good idea to me too
<fantasai> astearns: so proposal is to change spec to use half of width/height and iterpolate between the two?
<fantasai> TabAtkins: yes, then a wrinkle based on that
<fantasai> astearns: objections? concerns?
<fantasai> RESOLVED: use half width/height for horiziontal/vertical rays, interpolate for other angles
<fantasai> TabAtkins: old spec worked great for the "sides" value of ray, always fit against containing block edges
<fantasai> TabAtkins: worked badly for other things
<fantasai> TabAtkins: when I simplified, doesn't quite work as well for sides
<fantasai> TabAtkins: but thought it was acceptable loss
<fantasai> TabAtkins: but if we're regaining angle dependence, I think it's OK to break simplicity a tiny bit more
<fantasai> TabAtkins: and say that for sides value, we do calculate the intersection bit
<fantasai> TabAtkins: much easier in this case because it's rectangle against rectangle
<fantasai> TabAtkins: old one wanted coord geomtery is harder
<fantasai> TabAtkins: question is how much pull in the box to make it actually fit
<fantasai> TabAtkins: that's relatively easy math, I can put in the spec
<TabAtkins> s/coord/chord/
<fantasai> astearns: sounds reasonable
<fantasai> astearns: proposed for sides value we will have snugness
<fantasai> astearns: objections?
<fantasai> RESOLVED: for sides value, have snugness
<fantasai> TabAtkins: that's it, just need a decision on the initial value of offset-position
<fantasai> florian: Last time we worked on this, was triggered by work on round display
<fantasai> florian: that faded, but Jihye is back maybe we can ask her to review?
<fantasai> TabAtkins: I did some issue archeology, so pretty sure I captured intent, but happy to ask her for review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants