Proposal: Expose Pointer Position Information on Hovered Elements
Nobody has claimed this yet.
- Dominant language
- Bikeshed
- Stars
- 4.9k
- Forks
- 816
- PR merge metrics
- PR metrics pending
Description
Introduction
To implement effects based on the Pointer's position relative to the hovered element, one needs to resort to JavaScript. See example libraries like Tilt.js, Atropos, etc. that provide this functionality.
I would like CSS to have this ability built in: i.e. have CSS use the Pointer's position, without needing to rely on JavaScript nor a clever but nasty hack that relies on injecting a few 100 extra elements.
This position information could be used for 3D effects, popover information boxes, Houdini code that takes the mouse position as input, etc.
Proposed Solution
The proposed solution is two-fold:
- Provide a way to activate positional tracking
- Provide a way to use the calculated value in CSS
I came up with these:
- Introduction of a
:hover-3dpseudo-class - Introducion of some new Environment Variables
--pointer-x: X-position of the pointer--pointer-y: Y-position of the pointer--pointer-angle: Angle from the Origin to the Pointer Position--pointer-distance: Distance from the Origin to the Pointer Position
Syntax
el:hover-3d {
transform: rotate3d(
env(--pointer-y),
env(--pointer-x),
0,
-15deg
);
}
Demo
Here's a demo that exposes the 4 proposed env vars as Custom Properties, to see what you can do with them
https://codepen.io/bramus/full/porJLgR/250186328bafbb5e63bb1a6f6f2ada044
Considerations / Questions
I've given all of this some thought, which might come in handy in possible future discussions on this.
Nesting
- Should nesting be allowed or not?
- Either way: I've have the children only gain access to the positional info from nearest parent with positional tracking activated.
Why a pseudo-class, and why :hover-3d?
😬 As the relative Pointer Position can only be calculated while hovering I started off from :hover and built further upon that. Seemed like a logical thing to do.
- Advantages of using a pseudo-class other than
:hover:- Rendering Engines should not unnecessarily calc the pointer's position for all elements, but only for those that request it.
- Rendering Engines need to know which element to calculate against
- E.g. is it the position of the mouse on top of the inside the , or the itself?
- Disadvantages of using a pseudo-class:
-
You can't use the calculated values in non
:hover-3dselectors-
(Clunky) Workaround: use Custom Properties and have the Env Vars overwrite their values.
div { --pointer-x: 0; --pointer-y: 0; transform: rotate3d( var(--pointer-y), var(--pointer-x), 0, -15deg ); } div:hover-3d { --pointer-x: env(--pointer-x); --pointer-y: env(--pointer-y); }
-
-
🤔 Or would the calculated values also be applied to the targeted element (sans
:hover-3d) itself?-
If so: then
:hover-3dloses some value, as the code could as well go in the regular selector.div { transform: rotate3d( env(--pointer-y, 0), env(--pointer-x, 0), 0, -15deg ); } div:hover-3d { /* 🤔 What would be put here now? Or would we simply need a different way to activate positional tracking? */ }
-
-
The choice for :hover-3d was purely based on the fact that I saw a 3D demo which sparked this idea. Don't have any strong opinion on this name. It can as well be :hover-with-position-info-yolo-web3-crypto if that's more in line with how things are named.
Coordinate System
I played a bit with the Coordinate System that could be used with this. Main question I had here was: Should the origin be at center-center (X/Y Coordinate System), or at top-left (Page Coordinate System)?
The choice of Coordinate System has some side-effects for CSS Authors:
- Many things in CSS use the Page Coordinate System, so using a Page Coordinate System is handy for those type of things
- For 3D-rotations (my initial intent) a X/Y Coordinate system with the Origin at the center-center of the element is more handy as the default
transform-originis50% 50%
I've compared various possible systems, but am personally leaning to the X/Y Coordinate System (XYCS) + range [-1,1] system.
Page Coordinate System (PCS)
-
Params
- Origin
0,0sits at top-left of the box- Side-Effect: center-center is is at
[0.5,0.5]
- Side-Effect: center-center is is at
- Range of values for
--pointer-xand--pointer-y:[0,1]
- Origin
-
Demo (using Custom Properties): https://codepen.io/bramus/pen/4b04dbf201c6a542d276506503a56e68
div { /* Coordinates that indicate the center of the element */ --pointer-x: 0.5; --pointer-y: 0.5; background: transparent radial-gradient( 25vw 25vw at calc(var(--pointer-x) * 100%) /* ✅ Values can be used directly, as bg-position also uses PCS */ calc(var(--pointer-y) * 100%) , lightblue, rebeccapurple ) no-repeat 0 0 ; transform: rotate3d( calc((-1 * var(--pointer-y)) + 0.5), /* ❌ Values need to be manually offset by 0.5 due to origins of transform and PCS not aligning */ calc((1 * var(--pointer-x)) - 0.5), 0, -15deg ); } div::before { /* ❌ Polar Coordinates don't make any sense here?! */ } -
Advantages:
- Handy for nested stuff that also use PCS (positioned elements, backgrounds, etc.)
-
Disadvantages:
- Less handy for 3D effects, as the used Origin (top-left) differs the default
transform-origin- Values need to be offset by
0.5before they can be used.
- Values need to be offset by
- Calculating Polar Coordinates from this seems a bit useless here?
- Less handy for 3D effects, as the used Origin (top-left) differs the default
X/Y Coordinate System (XYCS) + range [-0.5,0.5]
-
Params
- Origin
0,0sits at center-center of the box - Range of values for
--pointer-xand--pointer-y:[-0.5,0.5]
- Origin
-
Demo (using Custom Properties): https://codepen.io/bramus/pen/2711c3083d1c9892ad044dacf9526c26
div { /* Coordinates that indicate the center of the element */ --pointer-x: 0; --pointer-y: 0; background: transparent radial-gradient( 25vw 25vw at calc((var(--pointer-x) + 0.5) * 100%) /* ❌ Values need to be manually offset by 0.5 due to origins of background (which uses PCS) not aligning with XYCS */ calc((-1 * (var(--pointer-y)) + 0.5) * 100%) , lightblue, rebeccapurple ) no-repeat 0 0 ; transform: rotate3d( calc(var(--pointer-y) * 2), /* ⚠️ Values need to be multiplied by 2 if you want them to go from "nothing" to "full". Otherwise you'd end up with -7.5deg */ calc(var(--pointer-x) * 2), 0, -15deg ); } div::before { content: ''; pointer-events: none; display: block; height: 1px; width: 50%; background: red; position: absolute; top: 50%; left: 50%; transform-origin: 0 50%; transform: rotate(calc(var(--pointer-angle, 0) * -1deg)) /* ✅ Values can be used directly */ scaleX(var(--pointer-distance)) ; } -
Advantage(s):
- Easy for 3D effects, as the used Origin is equal to the default
transform-origin 0.5(or50%) resembles the actual distance that was travelled from the origin- E.g. A
--pointer-xvalue of0.5is also “50%of the width”- Handy when used with
cqw😎
- Handy when used with
- E.g. A
- The resulting Polar Coordinates are handy
- Easy for 3D effects, as the used Origin is equal to the default
-
Disadvantages:
- A value of
0.5is not-handy for scaling purposes: you need to multiply the values by 2. - Values needs manual adjustment to be used with Things™ that use the Page Coordinate System
- E.g.
background-position,top,left, …
- E.g.
- A value of
X/Y Coordinate System (XYCS) + range [-1,1]
-
Params
- Origin
0,0sits at center-center of the box - Range of values for
--pointer-xand--pointer-y:[-1,1]
- Origin
-
Demo (using Custom Properties): https://codepen.io/bramus/pen/250186328bafbb5e63bb1a6f6f2ada04
div { /* Coordinates that indicate the center of the element */ --pointer-x: 0; --pointer-y: 0; background: transparent radial-gradient( 25vw 25vw at calc(((var(--pointer-x) / 2) + 0.5) * 100%) /* ❌ Values need to be manually divided by 2 and be offset by 0.5 due to origins of background (which uses PCS) not aligning with XYCS */ calc((-1 * (var(--pointer-y) / 2) + 0.5) * 100%) , lightblue, rebeccapurple ) no-repeat 0 0; transform: rotate3d( var(--pointer-y), /* ✅ Values can be used directly */ var(--pointer-x), 0, -15deg ); } div::before { content: ''; pointer-events: none; display: block; height: 1px; width: 50%; background: red; position: absolute; top: 50%; left: 50%; transform-origin: 0 50%; transform: rotate(calc(var(--pointer-angle, 0) * -1deg)) /* ✅ Values can be used directly */ scaleX(var(--pointer-distance)) ; } -
Advantage(s):
- Easy for 3D effects, as the used Origin is equal to the default
transform-origin - A max-value of
1is handy for scaling purposes - The resulting Polar Coordinates are handy
- Easy for 3D effects, as the used Origin is equal to the default
-
Disadvantages:
1does not resemble the actual distance that was travelled from the origin- E.g. A
--pointer-xvalue of1is not “100%of the width”, but50%.- Not so handy when used with
cqw😎
- Not so handy when used with
- E.g. A
- Values needs manual adjustment to be used with Things™ that use the Page Coordinate System
- E.g.
background-position,top,left, …
- E.g.
Can't we just do this with Houdini?
While the calculations to determine the position can indeed be done via Houdini, there's no way to bounce those calculated values back to the CSS. Custom Properties are Input for Worklets, not Output.
(Feel free to correct me on this, would love to see that 🤩)
Which Pointer?
- First pointer only
Performance
- Impact on performance needs to be measured
- Also see remark about “Nesting” from earlier
Privacy
- ?
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
The issue names no repository files, tests, or implementation entry points; begin with the proposal and its open questions about activation, nesting, coordinates, and performance. Done would require an agreed CSS design and a clear implementation target, but the issue does not define one.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- css
- Domain
- frontend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100