w3c / w3c/csswg-drafts

Proposal: Expose Pointer Position Information on Hovered Elements

Open
#6,733 16 comments 34 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

css-env-1 css-values-5
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:

  1. Provide a way to activate positional tracking
  2. Provide a way to use the calculated value in CSS

I came up with these:

  1. Introduction of a :hover-3d pseudo-class
  2. 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-3d selectors

      • (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-3d loses 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-origin is 50% 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,0 sits at top-left of the box
      • Side-Effect: center-center is is at [0.5,0.5]
    • Range of values for --pointer-x and --pointer-y: [0,1]
  • 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.5 before they can be used.
    • Calculating Polar Coordinates from this seems a bit useless here?
X/Y Coordinate System (XYCS) + range [-0.5,0.5]
  • Params

    • Origin 0,0 sits at center-center of the box
    • Range of values for --pointer-x and --pointer-y: [-0.5,0.5]
  • 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 (or 50%) resembles the actual distance that was travelled from the origin
      • E.g. A --pointer-x value of 0.5 is also 50% of the width”
        • Handy when used with cqw 😎
    • The resulting Polar Coordinates are handy
  • Disadvantages:

    • A value of 0.5 is 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, …
X/Y Coordinate System (XYCS) + range [-1,1]
  • Params

    • Origin 0,0 sits at center-center of the box
    • Range of values for --pointer-x and --pointer-y: [-1,1]
  • 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 1 is handy for scaling purposes
    • The resulting Polar Coordinates are handy
  • Disadvantages:

    • 1 does not resemble the actual distance that was travelled from the origin
      • E.g. A --pointer-x value of 1 is not “100% of the width”, but 50%.
        • Not so handy when used with cqw 😎
    • Values needs manual adjustment to be used with Things™ that use the Page Coordinate System
      • E.g. background-position, top, left, …

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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.