The SVG specification introduced the 'pointer-events' property to provide SVG authors with more control over which parts of an element can intercept pointer event, and under what conditions. This document discusses 'pointer-events', and how it might work in HTML.
pointer-events in SVG 1.1
The following table summarizes which areas of an SVG element can intercept a pointer event, and under what conditions, for all the values of pointer-events in SVG 1.1, plus 'auto'.
Value Areas that intercept, and the required conditions ----- ------------------------------------------------- auto /* like 'visiblePainted', like 'none' on outer-<svg> */ visiblePainted requires(visibility:visible), fill if-not(fill:none), stroke if-not(stroke:none) painted fill if-not(fill:none), stroke if-not(stroke:none) visible requires(visibility:visible), * visibleFill requires(visibility:visible), fill visibleStroke requires(visibility:visible), stroke fill fill stroke stroke all * none -
The syntax of the lines in the column on the right might seem like they could be alternative values for pointer-events that users could use. However, they're pretty verbose, and its not clear that authors want a lot of granularity (yet).
Please only append to this list so that we have consistent numbering so we can refer to cases by number.
- Provide a way to allow pointer events to go "through" an element even when it's visible to the user.
- Provide a way to allow pointer events to be "caught" by an element even when it is invisible to the user.
- Provide a way to allow pointer events to go "through" areas of an element that are transparent, clipped/masked out, or otherwise not visible to users/viewers, while allowing pointer events to be "caught" by parts of the element that are.
- Provide a way to filter which pointer events are intercepted by an element. For example, to specify that an element should intercept most pointer events, but not mouse wheel events. This would allow elements with fixed position to pass wheel events on to the elements below them.
- Provide a way to allow pointer events to go "through" an element but still be "caught" by all its descendants.
The effect of clipping and masking
One of the long running debates in the SVG WG and on www-svg is whether an element should intercept pointer events if the event is over an area of the element that is clipping away or masked out. Current (2010) implementations do not take masking into account for hit testing, but Mozilla and Opera do take clipping into account, while Webkit and Batik do not.
My (jwatt's) personal opinion is that it's most intuitive for authors to have both clipping and masking affect hit testing by default. In fact I think it would have been best for the default behavior to have been: "if an element affects the color of a given pixel, then pointer events at that location are intercepted by that element, but if the element does not affect the pixel, then pointer events at that location are not intercepted by the element."
Text needs to be a special case, by default. When a user wishes to click on it to activate a link, or perhaps to select it, it would be unexpected and annoying for the pointer event to go between letters or through the middle of an "o", say, and target whatever is underneath. For the purposes of hit testing text the character cell is used as the fill area for a glyph by default. However, for large text, having event interception depend on the inked area instead of the character cell could be what authors want. We should really provide them with some way to do that. (Another property they can set?)
The effect of filters
Filters can significantly expand/contract or move elements.
A case where authors would not want filters being taken into account: Consider an SVG object where a filter is used to give it a shadow. Perhaps the object is an image of a man in a game where the shadow is being cast along the floor. Authors would not want pointer events that hit the man to act as if the man was hit.
A case where authors would want filters to be taken into account: Consider again the case of an image of a man in a game, but this time a filter is used to make him appear wasted and thin.
feOffset could potentially make hit testing appear all off.
The effect of markers
Rather than modifying the DOM to add and manage drag handles to an editable object, life could be a lot simpler if markers could be used to create drag handles. (Would that really work well? How would you distinguish between different types of handle, and whether the user is trying to drag or resize, for example.)
The current property values suck
The current values for 'pointer-events' suck - especially for HTML. Quite apart from the fact that they use the SVG specific terms "fill" and "stroke" in their names, they're confusing, and even authors that have been using SVG for a long time frequently have to refer back to the spec to figure out which value they need to use. For example:
- What's the difference between 'visiblePainted', 'visible', 'painted' and 'all'? (Note that none of these tell you if the element in question actually affects the pixel that was clicked.)
- Does the value of the 'fill' property matter for the 'painted' and 'fill' values? (The answer is "yes" and "no" respectively.)
'none', 'painted' and 'all'.
In SVG it may make sense to be able to have separate treatment for fill and stroke. In that case maybe the values 'all', 'fill', 'stroke' and 'painted', 'paintedFill', 'paintedStroke' would make sense. The former three would ignore opacity and visibility, the latter three would not. In fact, for these extra names to make a little more sense in CSS box model contexts such as HTML where you have 'background' and 'border' instead of 'fill' and 'stroke', the names could be 'all', 'perimeter', 'interior', and 'painted', 'paintedPerimeter', 'paintedInterior'.
To address usecase 3, it would be useful to have an "inked" value. The question is what to do when authors don't want to take account of some inputs that affect whether a pixel is inked, such as filters, markers, masking.
The current specification makes the value of the 'visibility' property special, but for some strange reason ignores the effects of opacity. As a result the 'visible' in value names such as 'visiblePainted' is nothing to do with whether something visible is actually painted, but rather to do with whether the 'visibility' property is set to 'visible' or not. The 'fill-opacity', 'stroke-opacity' or 'opacity', or the 'fill' or 'stroke' properties with rgba()/hsla() values that set opacity to zero, could all prevent any paint from being output, but as far as 'visiblePainted' et. al. are concerned, it's visible and painted if 'visibility' is set to 'visible'. Perhaps these values could be fixed to take account of opacity?
In fact 'visibleFill', 'visibleStroke' and 'visible' don't care about the values of 'fill' or 'stroke', even if they are 'none'.
Another source of confusion is the difference between 'visiblePainted', 'visible', 'painted' and 'all'. For all four the geometry is the combined stroke and fill geometry. The difference is in whether the 'fill' and 'stroke' properties are set to 'none' or not ('visiblePainted' and 'painted' do not allow them to be 'none', but 'visible' and 'all' do). Why do we even need a distinction between non-painting due to the value of 'visibility' vs 'fill'/'stroke'? Why don't we also have 'paintedFill', 'paintedStroke' and 'paintedAll' values then???
The 'visible' value should really have been 'visibleAll' for consistency. Maybe it seemed easier to remember, but the inconsistency it brings to the naming makes it harder to remember how everything works as a whole.
What about when 'fill' or 'stroke' references a gradient or pattern that is fully transparent/empty (or has large areas that are)? I think it would be fine to always treat paint server references as fully opaque paint. The utility behind 'painter-events' is to