[motion-1] offset-position + circle()
tabatkins opened this issue · comments
In the definition of <basic-shape>
for 'offset-path', there's a paragraph that states:
If a circle or ellipse basic shape has no explicit center position, the shape is centered at the [=initial position=] of the path, as described in 'offset-position'.
This contradicts Shapes 1, which simply defaults an omitted center-position to "center" without making it configurable.
Worse, this means that 'offset-position' now does two completely unrelated things - for ray(), it sets where the ray emerges from (aka where offset-distance: 0%
places you), but for circle() and ellipse() it (sometimes!) sets the center of the ellipse, which is not a point on the path at all.
I don't understand why this is necessary; you can just set the center position directly in the function if you need to, and if you omit it I think it's confusing for it not to act like an omitted position in other contexts. To get the default behavior that Shapes defines I have to explicitly say circle(... at center)
.
Can we just drop this odd behavior and have circle()/ellipse() default their center position as normal?
Since the spec doesn't say this behavior only applies to non-auto 'offset-position', this also means that the circle()/ellipse() defaults to being centered on the top-left corner of the element's laid-out position, which similarly doesn't seem very useful.
A different take with larger potential compat issues - what if we defined that ray() takes an optional initial position, similar to the circle() function, and just dropped offset-position entirely? I think offset-position is, in general, just a relic of the old "based on abspos" spec. It gets completely ignored by most of the basic shapes (and is only hacked into working with circle()/ellipse()).
Do we have any data on usage of the property? If it is usually set to 'center' when paired with ray(), we might be able to just default the ray() position to center, like Shapes does for circle()/ellipse().
Okay, looking into this more, the "offset-position affects circle()/ellipse()" behavior was committed in a1faed1, claiming to fix #78. However, #78 ended without any resolutions, and just some topics we needed to address:
(1) paths can't be specified relative to the container. One suggestion for fixing this was to define a CSS path syntax.
Done now, via shape().
(2) there's no simple rectangle or polygon shape that can be positioned relative to the element. CSS path syntax somewhat fixes this, but for rectangles we thought that adding a new rect() type that included a position and width/height information would be useful.
Done now, via xywh().
(3) we need to specify the fallback behavior for circle and ellipse
After rereading the issue a few times, still not sure what this is referring to.
(4) we need to specify path() positioning and make sure it makes sense
I think this was about the "path relative to element's current position" thing.
So I think the edits ended up doing the one thing we agreed to punt on (making offset-position interact with basic shapes) and failed to add the one thing we seemed to agree needed doing (allowing path() to be specified relative to the element's current position).
If we added the ability for path() to respond to offset-position, we have an issue - ray() pays attention to offset-position by default, but path doesn't pay attention by default. We need to resolve this somehow.
My proposal, then:
- Add an
at <position>
argument to ray(), path(), and shape(), following circle()/ellipse().- For ray(), it sets the starting point of the ray. It defaults to
center
by default. - For path()/shape(), it sets the origin of the coordinate system (but does not affect the unit size; it's just a shift). It defaults to
left top
by default.
- For ray(), it sets the starting point of the ray. It defaults to
- Add a new
none
value to offset-position, and make it the initial value. Whennone
, it does nothing. - When non-none, offset-position is used as the default for any
at ...
positions in the offset-path functions.auto
, as it does now, effectively gives you element-relative positioning. A<position>
lets you provide a separately-cascaded/animated starting position.
Hopefully this minimizes the chance of breakage, since rays will default to coming from the center if the author didn't touch offset-position, identical to what is probably the common case today where the element is abspos'd with left:50%; top:50%
and offset-position:auto
is used by default. But it gives us a more consistent approach to positioning shapes in general, and gives some useful new functionality to path() and shape(). (Today you could only achieve that if you wrote your path entirely with relative coordinates; with the new functionality you can use absolute coords if that's easier to understand, and they'll all effectively shift for you.)
This requires an edit to Shapes 1 as well, so /cc @astearns .
The CSS Working Group just discussed [motion-1] offset-position + circle()
, and agreed to the following:
RESOLVED: Add "at <position>" to path(), shape(), and ray()
RESOLVED: Add 'normal' keyword to 'offset-position'
The full IRC log of that discussion
<fantasai> s/Topic/Subtopic/<fantasai> TabAtkins: About the interaction of offset-position and circle() etc.
<fantasai> TabAtkins: originally offset-position was replacement for left/top
<fantasai> TabAtkins: could use polar
<fantasai> TabAtkins: When we changed to transforms, it got weird
<fantasai> TabAtkins: After discussion years ago, we came to some moderate conclusions that we wanted offset-position to apply to the ray() function and path() and probably nothing else?
<fantasai> TabAtkins: benefit was offset-position has a way to say "use my current position" which seemed useful for thngs like SVG path, which are in global coordinate space
<fantasai> TabAtkins: starting a path from "right here" was useful
<fantasai> TabAtkins: that wasn't edited in
<fantasai> TabAtkins: it ended up affecting circle() and ellipse(), to set the center
<fantasai> TabAtkins: rather than defaulting to center like css-shapes
<fantasai> TabAtkins: My proposal is to pursue the original intent in the thread to allow path() to also be relative as well
<fantasai> TabAtkins: making things consistent, taking ray/path/shape and giving them all an "at <position>" syntax that sets the starting point
<fantasai> TabAtkins: for path start at 0,0, for ray at center
<fantasai> TabAtkins: if you omit, will take value from offset-position
<fantasai> TabAtkins: paired with this, since we want shapes to default differently, we'd add a new 'none' value to offset-position
<fantasai> TabAtkins: so by default won't affect the default behavior
<fantasai> TabAtkins: if you do nothing special, your ray will come out of the center
<fantasai> TabAtkins: which is generally what people want?
<fantasai> TabAtkins: it keeps these two things consistent
<fantasai> TabAtkins: So 1) add "at <position>" to all things
<fantasai> TabAtkins: 2) add 'none' value to offset-position to not affect shape functions
<fantasai> TabAtkins: 3) ???
<fantasai> TabAtkins: Other functions don't affect
<fantasai> TabAtkins: but for ray/path/circle/ellipse, seems to make sense
<fantasai> TabAtkins: Note this spec is very inconsistently written
<fantasai> TabAtkins: and very little interop
<fantasai> TabAtkins: part of reason why under interop 2023
<fantasai> TabAtkins: very hard to do anything consistently
<fantasai> TabAtkins: anything we do will break stuff
<fantasai> dbaron: I tried to use Motion Path to cause an element to maintain its own position, couldn't do it interoperably
<astearns> ack fantasai
<TabAtkins> fantasai: I don't ahve the history of all this loaded up
<TabAtkins> fantasai: Not 100% sure I want to sign off because I'm not sure what we were trying to do.
<fantasai> TabAtkins: auto is the box's current position
<TabAtkins> fantasai: So you're proposing a none value, which uses the default position for each function
<dbaron> (the test I was working on was https://dbaron.org/css/test/2018/stacking-context-z-order )
<TabAtkins> fantasai: And what's the default value? still auto?
<fantasai> TabAtkins: no, it's none
<fantasai> TabAtkins: path starts at 0,0, but ray looks at offset-position
<fantasai> TabAtkins: if we default to auto, paths will change, if we default to none, rays will change
<fantasai> TabAtkins: for rays defaulting to center is almost always what you want
<fantasai> TabAtkins: so that's the core use case for ray()
<fantasai> TabAtkins: so I think that would be the most sensible behavior going forward
<fantasai> TabAtkins: and might be reasonably compatible with the content out there
<florian> fantasai: first comment if none is supposed to mean the default, then maybe it should be called normal instead
<florian> fantasai: currently, if you don't specify anything for path, it'll be auto
<florian> TabAtkins: path has no way to specify a position, so it goes by the containing block's origin
<florian> fantasai: and that's interoperable?
<florian> TabAtkins: probably, no change in spec in a long time
<florian> fantasai: for ray?
<florian> TabAtkins: it pays attention to offset position
<fantasai> TabAtkins: no way to set the position otherwise
<florian> fantasai: you want to make them consistent by making them start at a default value
<florian> fantasai: the alternative would be for both to start at the element's own position
<florian> TabAtkins: yes. I think the common case for path will not like that
<florian> TabAtkins: ray will want to center
<florian> TabAtkins: so usecases are probably better served by a default value than the element's own position
<florian> fantasai: not 100% sure…
<fantasai> PROPOSED: Add "at <position>" to path(), shape(), and ray()
<fantasai> PROPOSED: Add 'normal' keyword to 'offset-position'
<florian> astearns: that matches what's already in circle and ellipse?
<florian> TabAtkins: yes
<florian> RESOLVED: Add "at <position>" to path(), shape(), and ray()
<florian> astearns: any question on the second resolution?
<florian> RESOLVED: Add 'normal' keyword to 'offset-position'